Skip to content

Commit 837b710

Browse files
committed
Auto merge of #136035 - SpecificProtagonist:miri-zeroed-alloc, r=<try>
miri: optimize zeroed alloc When allocating zero-initialized memory in MIR interpretation, rustc allocates zeroed memory, marks it as initialized and then re-zeroes it. Remove the last step. I don't expect this to have much of an effect on performance normally, but in my case in which I'm creating a large allocation via mmap miri is unusable without this. There's probably a better way – with less code duplication – to implement this. Maybe adding a zero_init flag to the relevant methods, but then `Allocation::uninit` & co need a new name :)
2 parents 6365178 + bd28faf commit 837b710

File tree

6 files changed

+81
-39
lines changed

6 files changed

+81
-39
lines changed

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
250250
self.insert_allocation(alloc, kind)
251251
}
252252

253+
pub fn allocate_zeroed_ptr(
254+
&mut self,
255+
size: Size,
256+
align: Align,
257+
kind: MemoryKind<M::MemoryKind>,
258+
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
259+
let alloc = if M::PANIC_ON_ALLOC_FAIL {
260+
Allocation::zeroed(size, align)
261+
} else {
262+
Allocation::try_zeroed(size, align)?
263+
};
264+
self.insert_allocation(alloc, kind)
265+
}
266+
253267
pub fn insert_allocation(
254268
&mut self,
255269
alloc: Allocation<M::Provenance, (), M::Bytes>,

compiler/rustc_middle/src/mir/interpret/allocation.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,49 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
340340
}
341341
}
342342

343+
fn zeroed_inner<R>(size: Size, align: Align, fail: impl FnOnce() -> R) -> Result<Self, R> {
344+
// We raise an error if we cannot create the allocation on the host.
345+
// This results in an error that can happen non-deterministically, since the memory
346+
// available to the compiler can change between runs. Normally queries are always
347+
// deterministic. However, we can be non-deterministic here because all uses of const
348+
// evaluation (including ConstProp!) will make compilation fail (via hard error
349+
// or ICE) upon encountering a `MemoryExhausted` error.
350+
let bytes = Bytes::zeroed(size, align).ok_or_else(fail)?;
351+
352+
Ok(Allocation {
353+
bytes,
354+
provenance: ProvenanceMap::new(),
355+
init_mask: InitMask::new(size, true),
356+
align,
357+
mutability: Mutability::Mut,
358+
extra: (),
359+
})
360+
}
361+
362+
/// Try to create an Allocation of `size` zero-initialized bytes, failing if there is not enough memory
363+
/// available to the compiler to do so.
364+
pub fn try_zeroed<'tcx>(size: Size, align: Align) -> InterpResult<'tcx, Self> {
365+
Self::zeroed_inner(size, align, || {
366+
ty::tls::with(|tcx| tcx.dcx().delayed_bug("exhausted memory during interpretation"));
367+
InterpErrorKind::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
368+
})
369+
.into()
370+
}
371+
372+
/// Try to create an Allocation of `size` zero-initialized bytes, panics if there is not enough memory
373+
/// available to the compiler to do so.
374+
pub fn zeroed(size: Size, align: Align) -> Self {
375+
match Self::zeroed_inner(size, align, || {
376+
panic!(
377+
"interpreter ran out of memory: cannot create allocation of {} bytes",
378+
size.bytes()
379+
);
380+
}) {
381+
Ok(x) => x,
382+
Err(x) => x,
383+
}
384+
}
385+
343386
/// Add the extra.
344387
pub fn with_extra<Extra>(self, extra: Extra) -> Allocation<Prov, Extra, Bytes> {
345388
Allocation {

src/tools/miri/src/shims/alloc.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::iter;
2-
31
use rustc_abi::{Align, Size};
42
use rustc_ast::expand::allocator::AllocatorKind;
53

@@ -83,15 +81,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8381
fn malloc(&mut self, size: u64, zero_init: bool) -> InterpResult<'tcx, Pointer> {
8482
let this = self.eval_context_mut();
8583
let align = this.malloc_align(size);
86-
let ptr = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into())?;
87-
if zero_init {
88-
// We just allocated this, the access is definitely in-bounds and fits into our address space.
89-
this.write_bytes_ptr(
90-
ptr.into(),
91-
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
92-
)
93-
.unwrap();
94-
}
84+
let ptr = if zero_init {
85+
this.allocate_zeroed_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into())?
86+
} else {
87+
this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into())?
88+
};
9589
interp_ok(ptr.into())
9690
}
9791

src/tools/miri/src/shims/foreign_items.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::collections::hash_map::Entry;
22
use std::io::Write;
3-
use std::iter;
43
use std::path::Path;
54

65
use rustc_abi::{Align, AlignFromBytesError, Size};
@@ -533,18 +532,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
533532

534533
this.check_rustc_alloc_request(size, align)?;
535534

536-
let ptr = this.allocate_ptr(
535+
let ptr = this.allocate_zeroed_ptr(
537536
Size::from_bytes(size),
538537
Align::from_bytes(align).unwrap(),
539538
MiriMemoryKind::Rust.into(),
540539
)?;
541-
542-
// We just allocated this, the access is definitely in-bounds.
543-
this.write_bytes_ptr(
544-
ptr.into(),
545-
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
546-
)
547-
.unwrap();
548540
this.write_pointer(ptr, dest)
549541
});
550542
}

src/tools/miri/src/shims/unix/mem.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
111111
return interp_ok(this.eval_libc("MAP_FAILED"));
112112
}
113113

114-
let ptr =
115-
this.allocate_ptr(Size::from_bytes(map_length), align, MiriMemoryKind::Mmap.into())?;
116-
// We just allocated this, the access is definitely in-bounds and fits into our address space.
117114
// mmap guarantees new mappings are zero-init.
118-
this.write_bytes_ptr(
119-
ptr.into(),
120-
std::iter::repeat(0u8).take(usize::try_from(map_length).unwrap()),
121-
)
122-
.unwrap();
115+
let ptr = this.allocate_zeroed_ptr(
116+
Size::from_bytes(map_length),
117+
align,
118+
MiriMemoryKind::Mmap.into(),
119+
)?;
123120

124121
interp_ok(Scalar::from_pointer(ptr, this))
125122
}

src/tools/miri/src/shims/windows/foreign_items.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
258258
// Alignment is twice the pointer size.
259259
// Source: <https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc>
260260
let align = this.tcx.pointer_size().bytes().strict_mul(2);
261-
let ptr = this.allocate_ptr(
262-
Size::from_bytes(size),
263-
Align::from_bytes(align).unwrap(),
264-
MiriMemoryKind::WinHeap.into(),
265-
)?;
266-
if zero_init {
267-
this.write_bytes_ptr(
268-
ptr.into(),
269-
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
270-
)?;
271-
}
261+
let ptr = if zero_init {
262+
this.allocate_zeroed_ptr(
263+
Size::from_bytes(size),
264+
Align::from_bytes(align).unwrap(),
265+
MiriMemoryKind::WinHeap.into(),
266+
)?
267+
} else {
268+
this.allocate_ptr(
269+
Size::from_bytes(size),
270+
Align::from_bytes(align).unwrap(),
271+
MiriMemoryKind::WinHeap.into(),
272+
)?
273+
};
272274
this.write_pointer(ptr, dest)?;
273275
}
274276
"HeapFree" => {

0 commit comments

Comments
 (0)