Skip to content

Commit 5e55679

Browse files
committed
Auto merge of #136035 - SpecificProtagonist:miri-zeroed-alloc, r=oli-obk
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 it gets in the way.
2 parents 4a5f1cc + eee9df4 commit 5e55679

File tree

14 files changed

+88
-79
lines changed

14 files changed

+88
-79
lines changed

Diff for: compiler/rustc_const_eval/src/const_eval/machine.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ use super::error::*;
2121
use crate::errors::{LongRunning, LongRunningWarn};
2222
use crate::fluent_generated as fluent;
2323
use crate::interpret::{
24-
self, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy,
25-
InterpCx, InterpResult, MPlaceTy, OpTy, RangeSet, Scalar, compile_time_machine, interp_ok,
26-
throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format,
24+
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
25+
GlobalAlloc, ImmTy, InterpCx, InterpResult, MPlaceTy, OpTy, RangeSet, Scalar,
26+
compile_time_machine, interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom,
27+
throw_unsup, throw_unsup_format,
2728
};
2829

2930
/// When hitting this many interpreted terminators we emit a deny by default lint
@@ -420,6 +421,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
420421
Size::from_bytes(size),
421422
align,
422423
interpret::MemoryKind::Machine(MemoryKind::Heap),
424+
AllocInit::Uninit,
423425
)?;
424426
ecx.write_pointer(ptr, dest)?;
425427
}

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
2020
use tracing::{debug, instrument, trace};
2121

2222
use super::{
23-
AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckAlignMsg, CheckInAllocMsg,
24-
CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, Misalignment, Pointer,
25-
PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, interp_ok, throw_ub,
26-
throw_ub_custom, throw_unsup, throw_unsup_format,
23+
AllocBytes, AllocId, AllocInit, AllocMap, AllocRange, Allocation, CheckAlignMsg,
24+
CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak,
25+
Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub,
26+
err_ub_custom, interp_ok, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format,
2727
};
2828
use crate::fluent_generated as fluent;
2929

@@ -230,11 +230,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
230230
size: Size,
231231
align: Align,
232232
kind: MemoryKind<M::MemoryKind>,
233+
init: AllocInit,
233234
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
234235
let alloc = if M::PANIC_ON_ALLOC_FAIL {
235-
Allocation::uninit(size, align)
236+
Allocation::new(size, align, init)
236237
} else {
237-
Allocation::try_uninit(size, align)?
238+
Allocation::try_new(size, align, init)?
238239
};
239240
self.insert_allocation(alloc, kind)
240241
}
@@ -270,13 +271,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
270271
M::adjust_alloc_root_pointer(self, Pointer::from(id), Some(kind))
271272
}
272273

274+
/// If this grows the allocation, `init_growth` determines
275+
/// whether the additional space will be initialized.
273276
pub fn reallocate_ptr(
274277
&mut self,
275278
ptr: Pointer<Option<M::Provenance>>,
276279
old_size_and_align: Option<(Size, Align)>,
277280
new_size: Size,
278281
new_align: Align,
279282
kind: MemoryKind<M::MemoryKind>,
283+
init_growth: AllocInit,
280284
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
281285
let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?;
282286
if offset.bytes() != 0 {
@@ -289,7 +293,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
289293

290294
// For simplicities' sake, we implement reallocate as "alloc, copy, dealloc".
291295
// This happens so rarely, the perf advantage is outweighed by the maintenance cost.
292-
let new_ptr = self.allocate_ptr(new_size, new_align, kind)?;
296+
// If requested, we zero-init the entire allocation, to ensure that a growing
297+
// allocation has its new bytes properly set. For the part that is copied,
298+
// `mem_copy` below will de-initialize things as necessary.
299+
let new_ptr = self.allocate_ptr(new_size, new_align, kind, init_growth)?;
293300
let old_size = match old_size_and_align {
294301
Some((size, _align)) => size,
295302
None => self.get_alloc_raw(alloc_id)?.size(),

Diff for: compiler/rustc_const_eval/src/interpret/place.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use rustc_middle::{bug, mir, span_bug};
1212
use tracing::{instrument, trace};
1313

1414
use super::{
15-
AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, ImmTy, Immediate, InterpCx, InterpResult,
16-
Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer, Projectable, Provenance,
17-
Scalar, alloc_range, interp_ok, mir_assign_valid_types,
15+
AllocInit, AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, ImmTy, Immediate, InterpCx,
16+
InterpResult, Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer,
17+
Projectable, Provenance, Scalar, alloc_range, interp_ok, mir_assign_valid_types,
1818
};
1919

2020
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
@@ -983,7 +983,7 @@ where
983983
let Some((size, align)) = self.size_and_align_of(&meta, &layout)? else {
984984
span_bug!(self.cur_span(), "cannot allocate space for `extern` type, size is not known")
985985
};
986-
let ptr = self.allocate_ptr(size, align, kind)?;
986+
let ptr = self.allocate_ptr(size, align, kind, AllocInit::Uninit)?;
987987
interp_ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout, /*unaligned*/ false))
988988
}
989989

Diff for: compiler/rustc_const_eval/src/interpret/util.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::ops::ControlFlow;
22

33
use rustc_hir::def_id::LocalDefId;
44
use rustc_middle::mir;
5-
use rustc_middle::mir::interpret::{Allocation, InterpResult, Pointer};
5+
use rustc_middle::mir::interpret::{AllocInit, Allocation, InterpResult, Pointer};
66
use rustc_middle::ty::layout::TyAndLayout;
77
use rustc_middle::ty::{
88
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
@@ -76,7 +76,7 @@ pub(crate) fn create_static_alloc<'tcx>(
7676
static_def_id: LocalDefId,
7777
layout: TyAndLayout<'tcx>,
7878
) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
79-
let alloc = Allocation::try_uninit(layout.size, layout.align.abi)?;
79+
let alloc = Allocation::try_new(layout.size, layout.align.abi, AllocInit::Uninit)?;
8080
let alloc_id = ecx.tcx.reserve_and_set_static_alloc(static_def_id.into());
8181
assert_eq!(ecx.machine.static_root_ids, None);
8282
ecx.machine.static_root_ids = Some((alloc_id, static_def_id));

Diff for: compiler/rustc_middle/src/mir/interpret/allocation.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ impl AllocRange {
270270
}
271271
}
272272

273+
/// Whether a new allocation should be initialized with zero-bytes.
274+
pub enum AllocInit {
275+
Uninit,
276+
Zero,
277+
}
278+
273279
// The constructors are all without extra; the extra gets added by a machine hook later.
274280
impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
275281
/// Creates an allocation initialized by the given bytes
@@ -294,7 +300,12 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
294300
Allocation::from_bytes(slice, Align::ONE, Mutability::Not)
295301
}
296302

297-
fn uninit_inner<R>(size: Size, align: Align, fail: impl FnOnce() -> R) -> Result<Self, R> {
303+
fn new_inner<R>(
304+
size: Size,
305+
align: Align,
306+
init: AllocInit,
307+
fail: impl FnOnce() -> R,
308+
) -> Result<Self, R> {
298309
// We raise an error if we cannot create the allocation on the host.
299310
// This results in an error that can happen non-deterministically, since the memory
300311
// available to the compiler can change between runs. Normally queries are always
@@ -306,7 +317,10 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
306317
Ok(Allocation {
307318
bytes,
308319
provenance: ProvenanceMap::new(),
309-
init_mask: InitMask::new(size, false),
320+
init_mask: InitMask::new(size, match init {
321+
AllocInit::Uninit => false,
322+
AllocInit::Zero => true,
323+
}),
310324
align,
311325
mutability: Mutability::Mut,
312326
extra: (),
@@ -315,8 +329,8 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
315329

316330
/// Try to create an Allocation of `size` bytes, failing if there is not enough memory
317331
/// available to the compiler to do so.
318-
pub fn try_uninit<'tcx>(size: Size, align: Align) -> InterpResult<'tcx, Self> {
319-
Self::uninit_inner(size, align, || {
332+
pub fn try_new<'tcx>(size: Size, align: Align, init: AllocInit) -> InterpResult<'tcx, Self> {
333+
Self::new_inner(size, align, init, || {
320334
ty::tls::with(|tcx| tcx.dcx().delayed_bug("exhausted memory during interpretation"));
321335
InterpErrorKind::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
322336
})
@@ -328,8 +342,8 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
328342
///
329343
/// Example use case: To obtain an Allocation filled with specific data,
330344
/// first call this function and then call write_scalar to fill in the right data.
331-
pub fn uninit(size: Size, align: Align) -> Self {
332-
match Self::uninit_inner(size, align, || {
345+
pub fn new(size: Size, align: Align, init: AllocInit) -> Self {
346+
match Self::new_inner(size, align, init, || {
333347
panic!(
334348
"interpreter ran out of memory: cannot create allocation of {} bytes",
335349
size.bytes()

Diff for: compiler/rustc_middle/src/mir/interpret/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub use {
3030
};
3131

3232
pub use self::allocation::{
33-
AllocBytes, AllocError, AllocRange, AllocResult, Allocation, ConstAllocation, InitChunk,
34-
InitChunkIter, alloc_range,
33+
AllocBytes, AllocError, AllocInit, AllocRange, AllocResult, Allocation, ConstAllocation,
34+
InitChunk, InitChunkIter, alloc_range,
3535
};
3636
pub use self::error::{
3737
BadBytesAccess, CheckAlignMsg, CheckInAllocMsg, ErrorHandled, EvalStaticInitializerRawResult,

Diff for: compiler/rustc_middle/src/ty/vtable.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use rustc_ast::Mutability;
44
use rustc_macros::HashStable;
55
use rustc_type_ir::elaborate;
66

7-
use crate::mir::interpret::{AllocId, Allocation, CTFE_ALLOC_SALT, Pointer, Scalar, alloc_range};
7+
use crate::mir::interpret::{
8+
AllocId, AllocInit, Allocation, CTFE_ALLOC_SALT, Pointer, Scalar, alloc_range,
9+
};
810
use crate::ty::{self, Instance, PolyTraitRef, Ty, TyCtxt};
911

1012
#[derive(Clone, Copy, PartialEq, HashStable)]
@@ -108,7 +110,7 @@ pub(super) fn vtable_allocation_provider<'tcx>(
108110
let ptr_align = tcx.data_layout.pointer_align.abi;
109111

110112
let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
111-
let mut vtable = Allocation::uninit(vtable_size, ptr_align);
113+
let mut vtable = Allocation::new(vtable_size, ptr_align, AllocInit::Uninit);
112114

113115
// No need to do any alignment checks on the memory accesses below, because we know the
114116
// allocation is correctly aligned as we created it above. Also we're only offsetting by

Diff for: compiler/rustc_smir/src/rustc_smir/alloc.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rustc_abi::{Align, Size};
22
use rustc_middle::mir::ConstValue;
3-
use rustc_middle::mir::interpret::{AllocRange, Pointer, alloc_range};
3+
use rustc_middle::mir::interpret::{AllocInit, AllocRange, Pointer, alloc_range};
44
use stable_mir::Error;
55
use stable_mir::mir::Mutability;
66
use stable_mir::ty::{Allocation, ProvenanceMap};
@@ -44,7 +44,8 @@ pub(crate) fn try_new_allocation<'tcx>(
4444
.layout_of(rustc_middle::ty::TypingEnv::fully_monomorphized().as_query_input(ty))
4545
.map_err(|e| e.stable(tables))?
4646
.align;
47-
let mut allocation = rustc_middle::mir::interpret::Allocation::uninit(size, align.abi);
47+
let mut allocation =
48+
rustc_middle::mir::interpret::Allocation::new(size, align.abi, AllocInit::Uninit);
4849
allocation
4950
.write_scalar(&tables.tcx, alloc_range(Size::ZERO, size), scalar)
5051
.map_err(|e| e.stable(tables))?;
@@ -68,8 +69,11 @@ pub(crate) fn try_new_allocation<'tcx>(
6869
.tcx
6970
.layout_of(rustc_middle::ty::TypingEnv::fully_monomorphized().as_query_input(ty))
7071
.map_err(|e| e.stable(tables))?;
71-
let mut allocation =
72-
rustc_middle::mir::interpret::Allocation::uninit(layout.size, layout.align.abi);
72+
let mut allocation = rustc_middle::mir::interpret::Allocation::new(
73+
layout.size,
74+
layout.align.abi,
75+
AllocInit::Uninit,
76+
);
7377
allocation
7478
.write_scalar(
7579
&tables.tcx,

Diff for: src/tools/miri/src/shims/alloc.rs

+6-13
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

@@ -80,18 +78,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8078
}
8179
}
8280

83-
fn malloc(&mut self, size: u64, zero_init: bool) -> InterpResult<'tcx, Pointer> {
81+
fn malloc(&mut self, size: u64, init: AllocInit) -> 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 = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
9585
interp_ok(ptr.into())
9686
}
9787

@@ -115,6 +105,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
115105
Size::from_bytes(size),
116106
Align::from_bytes(align).unwrap(),
117107
MiriMemoryKind::C.into(),
108+
AllocInit::Uninit
118109
)?;
119110
this.write_pointer(ptr, &memptr)?;
120111
interp_ok(Scalar::from_i32(0))
@@ -134,7 +125,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
134125
let new_align = this.malloc_align(new_size);
135126
if this.ptr_is_null(old_ptr)? {
136127
// Here we must behave like `malloc`.
137-
self.malloc(new_size, /*zero_init*/ false)
128+
self.malloc(new_size, AllocInit::Uninit)
138129
} else {
139130
if new_size == 0 {
140131
// C, in their infinite wisdom, made this UB.
@@ -147,6 +138,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
147138
Size::from_bytes(new_size),
148139
new_align,
149140
MiriMemoryKind::C.into(),
141+
AllocInit::Uninit
150142
)?;
151143
interp_ok(new_ptr.into())
152144
}
@@ -187,6 +179,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
187179
Size::from_bytes(size),
188180
Align::from_bytes(align).unwrap(),
189181
MiriMemoryKind::C.into(),
182+
AllocInit::Uninit
190183
)?;
191184
interp_ok(ptr.into())
192185
}

Diff for: src/tools/miri/src/shims/foreign_items.rs

+6-10
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};
@@ -9,6 +8,7 @@ use rustc_ast::expand::allocator::alloc_error_handler_name;
98
use rustc_hir::def::DefKind;
109
use rustc_hir::def_id::CrateNum;
1110
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
11+
use rustc_middle::mir::interpret::AllocInit;
1212
use rustc_middle::ty::Ty;
1313
use rustc_middle::{mir, ty};
1414
use rustc_span::Symbol;
@@ -442,7 +442,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
442442
let [size] = this.check_shim(abi, Conv::C, link_name, args)?;
443443
let size = this.read_target_usize(size)?;
444444
if size <= this.max_size_of_val().bytes() {
445-
let res = this.malloc(size, /*zero_init:*/ false)?;
445+
let res = this.malloc(size, AllocInit::Uninit)?;
446446
this.write_pointer(res, dest)?;
447447
} else {
448448
// If this does not fit in an isize, return null and, on Unix, set errno.
@@ -457,7 +457,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
457457
let items = this.read_target_usize(items)?;
458458
let elem_size = this.read_target_usize(elem_size)?;
459459
if let Some(size) = this.compute_size_in_bytes(Size::from_bytes(elem_size), items) {
460-
let res = this.malloc(size.bytes(), /*zero_init:*/ true)?;
460+
let res = this.malloc(size.bytes(), AllocInit::Zero)?;
461461
this.write_pointer(res, dest)?;
462462
} else {
463463
// On size overflow, return null and, on Unix, set errno.
@@ -509,6 +509,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
509509
Size::from_bytes(size),
510510
Align::from_bytes(align).unwrap(),
511511
memory_kind.into(),
512+
AllocInit::Uninit
512513
)?;
513514

514515
ecx.write_pointer(ptr, dest)
@@ -537,14 +538,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
537538
Size::from_bytes(size),
538539
Align::from_bytes(align).unwrap(),
539540
MiriMemoryKind::Rust.into(),
541+
AllocInit::Zero
540542
)?;
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();
548543
this.write_pointer(ptr, dest)
549544
});
550545
}
@@ -604,6 +599,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
604599
Size::from_bytes(new_size),
605600
align,
606601
MiriMemoryKind::Rust.into(),
602+
AllocInit::Uninit
607603
)?;
608604
this.write_pointer(new_ptr, dest)
609605
});

Diff for: src/tools/miri/src/shims/unix/fs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
11091109
Size::from_bytes(size),
11101110
dirent_layout.align.abi,
11111111
MiriMemoryKind::Runtime.into(),
1112+
AllocInit::Uninit
11121113
)?;
11131114
let entry: Pointer = entry.into();
11141115

Diff for: src/tools/miri/src/shims/unix/linux/mem.rs

+1-9
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
4949
Size::from_bytes(new_size),
5050
align,
5151
MiriMemoryKind::Mmap.into(),
52+
AllocInit::Zero
5253
)?;
53-
if let Some(increase) = new_size.checked_sub(old_size) {
54-
// We just allocated this, the access is definitely in-bounds and fits into our address space.
55-
// mmap guarantees new mappings are zero-init.
56-
this.write_bytes_ptr(
57-
ptr.wrapping_offset(Size::from_bytes(old_size), this).into(),
58-
std::iter::repeat(0u8).take(usize::try_from(increase).unwrap()),
59-
)
60-
.unwrap();
61-
}
6254

6355
interp_ok(Scalar::from_pointer(ptr, this))
6456
}

0 commit comments

Comments
 (0)