Skip to content

Commit 8ef0caa

Browse files
committed
interpret: remove LocalValue::Unallocated, add Operand::Uninit
Operand::Uninit is an *allocated* operand that is fully uninitialized. This lets us lazily allocate the actual backing store of *all* locals (no matter their ABI). I also reordered things in pop_stack_frame at the same time. I should probably have made that a separate commit...
1 parent 049308c commit 8ef0caa

File tree

8 files changed

+180
-176
lines changed

8 files changed

+180
-176
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ pub(super) fn op_to_const<'tcx>(
189189
let len: usize = len.try_into().unwrap();
190190
ConstValue::Slice { data, start, end: start + len }
191191
}
192+
Immediate::Uninit => to_const_value(&op.assert_mem_place()),
192193
},
193194
}
194195
}

compiler/rustc_const_eval/src/interpret/cast.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
153153
assert_eq!(dest_layout.size, self.pointer_size());
154154
assert!(src.layout.ty.is_unsafe_ptr());
155155
return match **src {
156-
Immediate::ScalarPair(data, _) => Ok(data.into()),
156+
Immediate::ScalarPair(data, _) => Ok(data.check_init()?.into()),
157157
Immediate::Scalar(..) => span_bug!(
158158
self.cur_span(),
159159
"{:?} input to a fat-to-thin cast ({:?} -> {:?})",
160160
*src,
161161
src.layout.ty,
162162
cast_ty
163163
),
164+
Immediate::Uninit => throw_ub!(InvalidUninitBytes(None)),
164165
};
165166
}
166167
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
112112
/// The locals are stored as `Option<Value>`s.
113113
/// `None` represents a local that is currently dead, while a live local
114114
/// can either directly contain `Scalar` or refer to some part of an `Allocation`.
115+
///
116+
/// Do *not* access this directly; always go through the machine hook!
115117
pub locals: IndexVec<mir::Local, LocalState<'tcx, Tag>>,
116118

117119
/// The span of the `tracing` crate is stored here.
@@ -179,10 +181,6 @@ pub struct LocalState<'tcx, Tag: Provenance = AllocId> {
179181
pub enum LocalValue<Tag: Provenance = AllocId> {
180182
/// This local is not currently alive, and cannot be used at all.
181183
Dead,
182-
/// This local is alive but not yet allocated. It cannot be read from or have its address taken,
183-
/// and will be allocated on the first write. This is to support unsized locals, where we cannot
184-
/// know their size in advance.
185-
Unallocated,
186184
/// A normal, live local.
187185
/// Mostly for convenience, we re-use the `Operand` type here.
188186
/// This is an optimization over just always having a pointer here;
@@ -196,12 +194,10 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
196194
///
197195
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
198196
/// anywhere else. You may be invalidating machine invariants if you do!
199-
pub fn access(&self) -> InterpResult<'tcx, Operand<Tag>> {
200-
match self.value {
201-
LocalValue::Dead => throw_ub!(DeadLocal),
202-
LocalValue::Unallocated => {
203-
bug!("The type checker should prevent reading from a never-written local")
204-
}
197+
#[inline]
198+
pub fn access(&self) -> InterpResult<'tcx, &Operand<Tag>> {
199+
match &self.value {
200+
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
205201
LocalValue::Live(val) => Ok(val),
206202
}
207203
}
@@ -211,15 +207,11 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
211207
///
212208
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
213209
/// anywhere else. You may be invalidating machine invariants if you do!
214-
pub fn access_mut(
215-
&mut self,
216-
) -> InterpResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {
217-
match self.value {
218-
LocalValue::Dead => throw_ub!(DeadLocal),
219-
LocalValue::Live(Operand::Indirect(mplace)) => Ok(Err(mplace)),
220-
ref mut local @ (LocalValue::Live(Operand::Immediate(_)) | LocalValue::Unallocated) => {
221-
Ok(Ok(local))
222-
}
210+
#[inline]
211+
pub fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Tag>> {
212+
match &mut self.value {
213+
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
214+
LocalValue::Live(val) => Ok(val),
223215
}
224216
}
225217
}
@@ -710,16 +702,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
710702
})?;
711703
}
712704

713-
// Locals are initially unallocated.
714-
let dummy = LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
705+
// Most locals are initially dead.
706+
let dummy = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
715707
let mut locals = IndexVec::from_elem(dummy, &body.local_decls);
716708

717-
// Now mark those locals as dead that we do not want to initialize
718-
// Mark locals that use `Storage*` annotations as dead on function entry.
709+
// Now mark those locals as live that have no `Storage*` annotations.
719710
let always_live = always_live_locals(self.body());
720711
for local in locals.indices() {
721-
if !always_live.contains(local) {
722-
locals[local].value = LocalValue::Dead;
712+
if always_live.contains(local) {
713+
locals[local].value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
723714
}
724715
}
725716
// done
@@ -791,59 +782,69 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
791782
if unwinding { "during unwinding" } else { "returning from function" }
792783
);
793784

794-
// Sanity check `unwinding`.
785+
// Check `unwinding`.
795786
assert_eq!(
796787
unwinding,
797788
match self.frame().loc {
798789
Ok(loc) => self.body().basic_blocks()[loc.block].is_cleanup,
799790
Err(_) => true,
800791
}
801792
);
802-
803793
if unwinding && self.frame_idx() == 0 {
804794
throw_ub_format!("unwinding past the topmost frame of the stack");
805795
}
806796

807-
let frame =
808-
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
809-
810-
if !unwinding {
811-
let op = self.local_to_op(&frame, mir::RETURN_PLACE, None)?;
812-
self.copy_op_transmute(&op, &frame.return_place)?;
813-
trace!("{:?}", self.dump_place(*frame.return_place));
814-
}
815-
816-
let return_to_block = frame.return_to_block;
817-
818-
// Now where do we jump next?
797+
// Copy return value. Must of course happen *before* we deallocate the locals.
798+
let copy_ret_result = if !unwinding {
799+
let op = self
800+
.local_to_op(self.frame(), mir::RETURN_PLACE, None)
801+
.expect("return place should always be live");
802+
let dest = self.frame().return_place;
803+
let err = self.copy_op_transmute(&op, &dest);
804+
trace!("return value: {:?}", self.dump_place(*dest));
805+
// We delay actually short-circuiting on this error until *after* the stack frame is
806+
// popped, since we want this error to be attributed to the caller, whose type defines
807+
// this transmute.
808+
err
809+
} else {
810+
Ok(())
811+
};
819812

813+
// Cleanup: deallocate locals.
820814
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
821-
// In that case, we return early. We also avoid validation in that case,
822-
// because this is CTFE and the final value will be thoroughly validated anyway.
815+
// We do this while the frame is still on the stack, so errors point to the callee.
816+
let return_to_block = self.frame().return_to_block;
823817
let cleanup = match return_to_block {
824818
StackPopCleanup::Goto { .. } => true,
825819
StackPopCleanup::Root { cleanup, .. } => cleanup,
826820
};
821+
if cleanup {
822+
// We need to take the locals out, since we need to mutate while iterating.
823+
let locals = mem::take(&mut self.frame_mut().locals);
824+
for local in &locals {
825+
self.deallocate_local(local.value)?;
826+
}
827+
}
828+
829+
// All right, now it is time to actually pop the frame.
830+
// Note that its locals are gone already, but that's fine.
831+
let frame =
832+
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
833+
// Report error from return value copy, if any.
834+
copy_ret_result?;
827835

836+
// If we are not doing cleanup, also skip everything else.
828837
if !cleanup {
829838
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
830839
assert!(!unwinding, "tried to skip cleanup during unwinding");
831-
// Leak the locals, skip validation, skip machine hook.
840+
// Skip machine hook.
832841
return Ok(());
833842
}
834-
835-
trace!("locals: {:#?}", frame.locals);
836-
837-
// Cleanup: deallocate all locals that are backed by an allocation.
838-
for local in &frame.locals {
839-
self.deallocate_local(local.value)?;
840-
}
841-
842843
if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump {
843844
// The hook already did everything.
844-
// We want to skip the `info!` below, hence early return.
845845
return Ok(());
846846
}
847+
847848
// Normal return, figure out where to jump.
848849
if unwinding {
849850
// Follow the unwind edge.
@@ -874,7 +875,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
874875
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
875876
trace!("{:?} is now live", local);
876877

877-
let local_val = LocalValue::Unallocated;
878+
let local_val = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
878879
// StorageLive expects the local to be dead, and marks it live.
879880
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
880881
if !matches!(old, LocalValue::Dead) {
@@ -977,7 +978,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
977978

978979
match self.ecx.stack()[frame].locals[local].value {
979980
LocalValue::Dead => write!(fmt, " is dead")?,
980-
LocalValue::Unallocated => write!(fmt, " is unallocated")?,
981+
LocalValue::Live(Operand::Immediate(Immediate::Uninit)) => {
982+
write!(fmt, " is uninitialized")?
983+
}
981984
LocalValue::Live(Operand::Indirect(mplace)) => {
982985
write!(
983986
fmt,

compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use rustc_target::spec::abi::Abi;
1414

1515
use super::{
1616
AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
17-
LocalValue, MemPlace, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar,
18-
StackPopUnwind,
17+
MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, StackPopUnwind,
1918
};
2019

2120
/// Data returned by Machine::stack_pop,
@@ -226,11 +225,13 @@ pub trait Machine<'mir, 'tcx>: Sized {
226225
/// Since reading a ZST is not actually accessing memory or locals, this is never invoked
227226
/// for ZST reads.
228227
#[inline]
229-
fn access_local(
230-
_ecx: &InterpCx<'mir, 'tcx, Self>,
231-
frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
228+
fn access_local<'a>(
229+
frame: &'a Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
232230
local: mir::Local,
233-
) -> InterpResult<'tcx, Operand<Self::PointerTag>> {
231+
) -> InterpResult<'tcx, &'a Operand<Self::PointerTag>>
232+
where
233+
'tcx: 'mir,
234+
{
234235
frame.locals[local].access()
235236
}
236237

@@ -242,7 +243,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
242243
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
243244
frame: usize,
244245
local: mir::Local,
245-
) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
246+
) -> InterpResult<'tcx, &'a mut Operand<Self::PointerTag>>
246247
where
247248
'tcx: 'mir,
248249
{
@@ -418,12 +419,14 @@ pub trait Machine<'mir, 'tcx>: Sized {
418419
}
419420

420421
/// Called immediately after a stack frame got popped, but before jumping back to the caller.
422+
/// The `locals` have already been destroyed!
421423
fn after_stack_pop(
422424
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
423425
_frame: Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
424-
_unwinding: bool,
426+
unwinding: bool,
425427
) -> InterpResult<'tcx, StackPopJump> {
426428
// By default, we do not support unwinding from panics
429+
assert!(!unwinding);
427430
Ok(StackPopJump::Normal)
428431
}
429432
}

compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_target::abi::{self, Abi, Align, HasDataLayout, Size, TagEncoding};
1414
use rustc_target::abi::{VariantIdx, Variants};
1515

1616
use super::{
17-
alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, GlobalId,
17+
alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, Frame, GlobalId,
1818
InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, Place, PlaceTy, Pointer,
1919
PointerArithmetic, Provenance, Scalar, ScalarMaybeUninit,
2020
};
@@ -28,8 +28,15 @@ use super::{
2828
/// defined on `Immediate`, and do not have to work with a `Place`.
2929
#[derive(Copy, Clone, PartialEq, Eq, HashStable, Hash, Debug)]
3030
pub enum Immediate<Tag: Provenance = AllocId> {
31+
/// A single scalar value (must have *initialized* `Scalar` ABI).
32+
/// FIXME: we also currently often use this for ZST.
33+
/// `ScalarMaybeUninit` should reject ZST, and we should use `Uninit` for them instead.
3134
Scalar(ScalarMaybeUninit<Tag>),
35+
/// A pair of two scalar value (must have `ScalarPair` ABI where both fields are
36+
/// `Scalar::Initialized`).
3237
ScalarPair(ScalarMaybeUninit<Tag>, ScalarMaybeUninit<Tag>),
38+
/// A value of fully uninitialized memory. Can have and size and layout.
39+
Uninit,
3340
}
3441

3542
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
@@ -75,6 +82,7 @@ impl<'tcx, Tag: Provenance> Immediate<Tag> {
7582
match self {
7683
Immediate::Scalar(val) => val,
7784
Immediate::ScalarPair(..) => bug!("Got a scalar pair where a scalar was expected"),
85+
Immediate::Uninit => ScalarMaybeUninit::Uninit,
7886
}
7987
}
8088

@@ -88,6 +96,7 @@ impl<'tcx, Tag: Provenance> Immediate<Tag> {
8896
match self {
8997
Immediate::ScalarPair(val1, val2) => (val1, val2),
9098
Immediate::Scalar(..) => bug!("Got a scalar where a scalar pair was expected"),
99+
Immediate::Uninit => (ScalarMaybeUninit::Uninit, ScalarMaybeUninit::Uninit),
91100
}
92101
}
93102

@@ -149,7 +158,10 @@ impl<Tag: Provenance> std::fmt::Display for ImmTy<'_, Tag> {
149158
}
150159
Immediate::ScalarPair(a, b) => {
151160
// FIXME(oli-obk): at least print tuples and slices nicely
152-
write!(f, "({:x}, {:x}): {}", a, b, self.layout.ty,)
161+
write!(f, "({:x}, {:x}): {}", a, b, self.layout.ty)
162+
}
163+
Immediate::Uninit => {
164+
write!(f, "uninit: {}", self.layout.ty)
153165
}
154166
}
155167
})
@@ -397,7 +409,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
397409
self.scalar_to_ptr(self.read_scalar(op)?.check_init()?)
398410
}
399411

400-
// Turn the wide MPlace into a string (must already be dereferenced!)
412+
/// Turn the wide MPlace into a string (must already be dereferenced!)
401413
pub fn read_str(&self, mplace: &MPlaceTy<'tcx, M::PointerTag>) -> InterpResult<'tcx, &str> {
402414
let len = mplace.len(self)?;
403415
let bytes = self.read_bytes_ptr(mplace.ptr, Size::from_bytes(len))?;
@@ -528,10 +540,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
528540
/// Will not access memory, instead an indirect `Operand` is returned.
529541
///
530542
/// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
531-
/// OpTy from a local
543+
/// OpTy from a local.
532544
pub fn local_to_op(
533545
&self,
534-
frame: &super::Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
546+
frame: &Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
535547
local: mir::Local,
536548
layout: Option<TyAndLayout<'tcx>>,
537549
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
@@ -540,7 +552,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
540552
// Do not read from ZST, they might not be initialized
541553
Operand::Immediate(Scalar::ZST.into())
542554
} else {
543-
M::access_local(&self, frame, local)?
555+
*M::access_local(frame, local)?
544556
};
545557
Ok(OpTy { op, layout, align: Some(layout.align.abi) })
546558
}

0 commit comments

Comments
 (0)