Skip to content

Commit cb51f87

Browse files
committed
miri engine: lazily allocate memory for locals on first write
1 parent 209b0b4 commit cb51f87

File tree

4 files changed

+79
-40
lines changed

4 files changed

+79
-40
lines changed

src/librustc_mir/interpret/eval_context.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,26 +116,41 @@ pub struct LocalState<'tcx, Tag=(), Id=AllocId> {
116116
/// State of a local variable
117117
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
118118
pub enum LocalValue<Tag=(), Id=AllocId> {
119+
/// This local is not currently alive, and cannot be used at all.
119120
Dead,
120-
// Mostly for convenience, we re-use the `Operand` type here.
121-
// This is an optimization over just always having a pointer here;
122-
// we can thus avoid doing an allocation when the local just stores
123-
// immediate values *and* never has its address taken.
121+
/// This local is alive but not yet initialized. It can be written to
122+
/// but not read from or its address taken. Locals get initialized on
123+
/// first write because for unsized locals, we do not know their size
124+
/// before that.
125+
Uninitialized,
126+
/// A normal, live local.
127+
/// Mostly for convenience, we re-use the `Operand` type here.
128+
/// This is an optimization over just always having a pointer here;
129+
/// we can thus avoid doing an allocation when the local just stores
130+
/// immediate values *and* never has its address taken.
124131
Live(Operand<Tag, Id>),
125132
}
126133

127-
impl<'tcx, Tag> LocalState<'tcx, Tag> {
134+
impl<'tcx, Tag: Copy> LocalState<'tcx, Tag> {
128135
pub fn access(&self) -> EvalResult<'tcx, &Operand<Tag>> {
129136
match self.state {
130-
LocalValue::Dead => err!(DeadLocal),
137+
LocalValue::Dead | LocalValue::Uninitialized => err!(DeadLocal),
131138
LocalValue::Live(ref val) => Ok(val),
132139
}
133140
}
134141

135-
pub fn access_mut(&mut self) -> EvalResult<'tcx, &mut Operand<Tag>> {
142+
/// Overwrite the local. If the local can be overwritten in place, return a reference
143+
/// to do so; otherwise return the `MemPlace` to consult instead.
144+
pub fn access_mut(
145+
&mut self,
146+
) -> EvalResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {
136147
match self.state {
137148
LocalValue::Dead => err!(DeadLocal),
138-
LocalValue::Live(ref mut val) => Ok(val),
149+
LocalValue::Live(Operand::Indirect(mplace)) => Ok(Err(mplace)),
150+
ref mut local @ LocalValue::Live(Operand::Immediate(_)) |
151+
ref mut local @ LocalValue::Uninitialized => {
152+
Ok(Ok(local))
153+
}
139154
}
140155
}
141156
}
@@ -327,6 +342,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tc
327342
let local_ty = self.monomorphize_with_substs(local_ty, frame.instance.substs);
328343
self.layout_of(local_ty)
329344
})?;
345+
// Layouts of locals are requested a lot, so we cache them.
330346
frame.locals[local].layout.set(Some(layout));
331347
Ok(layout)
332348
}
@@ -473,13 +489,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tc
473489

474490
// don't allocate at all for trivial constants
475491
if mir.local_decls.len() > 1 {
476-
// We put some marker immediate into the locals that we later want to initialize.
477-
// This can be anything except for LocalValue::Dead -- because *that* is the
478-
// value we use for things that we know are initially dead.
492+
// Locals are initially uninitialized.
479493
let dummy = LocalState {
480-
state: LocalValue::Live(Operand::Immediate(Immediate::Scalar(
481-
ScalarMaybeUndef::Undef,
482-
))),
494+
state: LocalValue::Uninitialized,
483495
layout: Cell::new(None),
484496
};
485497
let mut locals = IndexVec::from_elem(dummy, &mir.local_decls);
@@ -506,19 +518,25 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tc
506518
}
507519
},
508520
}
509-
// Finally, properly initialize all those that still have the dummy value
521+
// FIXME: We initialize live ZST here. This should not be needed if MIR was
522+
// consistently generated for ZST, but that seems to not be the case -- there
523+
// is MIR (around promoteds in particular) that reads local ZSTs that never
524+
// were written to.
510525
for (idx, local) in locals.iter_enumerated_mut() {
511526
match local.state {
512-
LocalValue::Live(_) => {
527+
LocalValue::Uninitialized => {
513528
// This needs to be properly initialized.
514529
let ty = self.monomorphize(mir.local_decls[idx].ty)?;
515530
let layout = self.layout_of(ty)?;
516-
local.state = LocalValue::Live(self.uninit_operand(layout)?);
531+
if layout.is_zst() {
532+
local.state = LocalValue::Live(self.uninit_operand(layout)?);
533+
}
517534
local.layout = Cell::new(Some(layout));
518535
}
519536
LocalValue::Dead => {
520537
// Nothing to do
521538
}
539+
LocalValue::Live(_) => bug!("Locals cannot be live yet"),
522540
}
523541
}
524542
// done

src/librustc_mir/interpret/place.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc::ty::TypeFoldable;
1515
use super::{
1616
GlobalId, AllocId, Allocation, Scalar, EvalResult, Pointer, PointerArithmetic,
1717
InterpretCx, Machine, AllocMap, AllocationExtra,
18-
RawConst, Immediate, ImmTy, ScalarMaybeUndef, Operand, OpTy, MemoryKind
18+
RawConst, Immediate, ImmTy, ScalarMaybeUndef, Operand, OpTy, MemoryKind, LocalValue
1919
};
2020

2121
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
@@ -639,6 +639,7 @@ where
639639
None => return err!(InvalidNullPointerUsage),
640640
},
641641
Base(PlaceBase::Local(local)) => PlaceTy {
642+
// This works even for dead/uninitialized locals; we check further when writing
642643
place: Place::Local {
643644
frame: self.cur_frame(),
644645
local,
@@ -714,16 +715,19 @@ where
714715
// but not factored as a separate function.
715716
let mplace = match dest.place {
716717
Place::Local { frame, local } => {
717-
match *self.stack[frame].locals[local].access_mut()? {
718-
Operand::Immediate(ref mut dest_val) => {
719-
// Yay, we can just change the local directly.
720-
*dest_val = src;
718+
match self.stack[frame].locals[local].access_mut()? {
719+
Ok(local) => {
720+
// Local can be updated in-place.
721+
*local = LocalValue::Live(Operand::Immediate(src));
721722
return Ok(());
722-
},
723-
Operand::Indirect(mplace) => mplace, // already in memory
723+
}
724+
Err(mplace) => {
725+
// The local is in memory, go on below.
726+
mplace
727+
}
724728
}
725729
},
726-
Place::Ptr(mplace) => mplace, // already in memory
730+
Place::Ptr(mplace) => mplace, // already referring to memory
727731
};
728732
let dest = MPlaceTy { mplace, layout: dest.layout };
729733

@@ -904,27 +908,40 @@ where
904908
) -> EvalResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
905909
let mplace = match place.place {
906910
Place::Local { frame, local } => {
907-
match *self.stack[frame].locals[local].access()? {
908-
Operand::Indirect(mplace) => mplace,
909-
Operand::Immediate(value) => {
911+
match self.stack[frame].locals[local].access_mut()? {
912+
Ok(local_val) => {
910913
// We need to make an allocation.
911914
// FIXME: Consider not doing anything for a ZST, and just returning
912915
// a fake pointer? Are we even called for ZST?
913916

917+
// We cannot hold on to the reference `local_val` while allocating,
918+
// but we can hold on to the value in there.
919+
let old_val =
920+
if let LocalValue::Live(Operand::Immediate(value)) = *local_val {
921+
Some(value)
922+
} else {
923+
None
924+
};
925+
914926
// We need the layout of the local. We can NOT use the layout we got,
915927
// that might e.g., be an inner field of a struct with `Scalar` layout,
916928
// that has different alignment than the outer field.
917929
let local_layout = self.layout_of_local(&self.stack[frame], local, None)?;
918930
let ptr = self.allocate(local_layout, MemoryKind::Stack);
919-
// We don't have to validate as we can assume the local
920-
// was already valid for its type.
921-
self.write_immediate_to_mplace_no_validate(value, ptr)?;
931+
if let Some(value) = old_val {
932+
// Preserve old value.
933+
// We don't have to validate as we can assume the local
934+
// was already valid for its type.
935+
self.write_immediate_to_mplace_no_validate(value, ptr)?;
936+
}
922937
let mplace = ptr.mplace;
923-
// Update the local
924-
*self.stack[frame].locals[local].access_mut()? =
925-
Operand::Indirect(mplace);
938+
// Now we can call `access_mut` again, asserting it goes well,
939+
// and actually overwrite things.
940+
*self.stack[frame].locals[local].access_mut().unwrap().unwrap() =
941+
LocalValue::Live(Operand::Indirect(mplace));
926942
mplace
927943
}
944+
Err(mplace) => mplace, // this already was an indirect local
928945
}
929946
}
930947
Place::Ptr(mplace) => mplace

src/librustc_mir/interpret/snapshot.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ macro_rules! impl_snapshot_for {
114114
fn snapshot(&self, __ctx: &'a Ctx) -> Self::Item {
115115
match *self {
116116
$(
117-
$enum_name::$variant $( ( $(ref $field),* ) )? =>
117+
$enum_name::$variant $( ( $(ref $field),* ) )? => {
118118
$enum_name::$variant $(
119-
( $( __impl_snapshot_field!($field, __ctx $(, $delegate)?) ),* ),
119+
( $( __impl_snapshot_field!($field, __ctx $(, $delegate)?) ),* )
120120
)?
121+
}
121122
)*
122123
}
123124
}
@@ -250,11 +251,13 @@ impl_snapshot_for!(enum Operand {
250251

251252
impl_stable_hash_for!(enum crate::interpret::LocalValue {
252253
Dead,
254+
Uninitialized,
253255
Live(x),
254256
});
255257
impl_snapshot_for!(enum LocalValue {
256-
Live(v),
257258
Dead,
259+
Uninitialized,
260+
Live(v),
258261
});
259262

260263
impl<'a, Ctx> Snapshot<'a, Ctx> for Relocations

src/librustc_mir/interpret/terminator.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tcx, M>
315315
);
316316

317317
// Figure out how to pass which arguments.
318-
// We have two iterators: Where the arguments come from,
319-
// and where they go to.
318+
// The Rust ABI is special: ZST get skipped.
320319
let rust_abi = match caller_abi {
321320
Abi::Rust | Abi::RustCall => true,
322321
_ => false
323322
};
323+
// We have two iterators: Where the arguments come from,
324+
// and where they go to.
324325

325326
// For where they come from: If the ABI is RustCall, we untuple the
326327
// last incoming argument. These two iterators do not have the same type,
@@ -368,7 +369,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tcx, M>
368369
}
369370
// Now we should have no more caller args
370371
if caller_iter.next().is_some() {
371-
trace!("Caller has too many args over");
372+
trace!("Caller has passed too many args");
372373
return err!(FunctionArgCountMismatch);
373374
}
374375
// Don't forget to check the return type!

0 commit comments

Comments
 (0)