Skip to content

Commit 8824d13

Browse files
committed
Auto merge of #98831 - RalfJung:no-more-unsized-locals, r=oli-obk
interpret: remove support for unsized_locals I added support for unsized_locals in #59780 but the current implementation is a crude hack and IMO definitely not the right way to have unsized locals in MIR. It also [causes problems](https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/topic/Missing.20Layout.20Check.20in.20.60interpret.2Foperand.2Ers.60.3F). and what codegen does is unsound and has been for years since clearly nobody cares (so I hope nobody actually relies on that implementation and I'll be happy if Miri ensures they do not). I think if we want to have unsized locals in Miri/MIR we should add them properly, either by having a `StorageLive` that takes metadata or by having an `alloca` that returns a pointer (making the ptr indirection explicit) or something like that. So, this PR removes the `LocalValue::Unallocated` hack. It adds `Immediate::Uninit`, for several reasons: - This lets us still do fairly little work in `push_stack_frame`, in particular we do not actually have to create any allocations. - If/when I remove `ScalarMaybeUninit`, we will need something like this to have an "optimized" representation of uninitialized locals. Without this we'd have to put uninitialized integers into the heap! - const-prop needs some way to indicate "I don't know the value of this local'; it used to use `LocalValue::Unallocated` for that, now it can use `Immediate::Uninit`. There is still a fundamental difference between `LocalValue::Unallocated` and `Immediate::Uninit`: the latter is considered a regular local that you can read from and write to, it just has a more optimized representation when compared with an actual `Allocation` that is fully uninit. In contrast, `LocalValue::Unallocated` had this really odd behavior where you would write to it but not read from it. (This is in fact what caused the problems mentioned above.) While at it I also did two drive-by cleanups/improvements: - In `pop_stack_frame`, do the return value copying and local deallocation while the frame is still on the stack. This leads to better error locations being reported. The old errors were [sometimes rather confusing](https://rust-lang.zulipchat.com/#narrow/stream/269128-miri/topic/Cron.20Job.20Failure.202022-06-24/near/287445522). - Deduplicate `copy_op` and `copy_op_transmute`. r? `@oli-obk`
2 parents 7665c35 + dc9e0bf commit 8824d13

File tree

14 files changed

+300
-259
lines changed

14 files changed

+300
-259
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+1
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

+3-2
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
}
@@ -358,7 +359,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
358359
let src_field = self.operand_field(src, i)?;
359360
let dst_field = self.place_field(dest, i)?;
360361
if src_field.layout.ty == cast_ty_field.ty {
361-
self.copy_op(&src_field, &dst_field)?;
362+
self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?;
362363
} else {
363364
self.unsize_into(&src_field, cast_ty_field, &dst_field)?;
364365
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

+56-53
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(&op, &dest, /*allow_transmute*/ true);
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/intrinsics.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
174174
let val =
175175
self.tcx.const_eval_global_id(self.param_env, gid, Some(self.tcx.span))?;
176176
let val = self.const_val_to_op(val, ty, Some(dest.layout))?;
177-
self.copy_op(&val, dest)?;
177+
self.copy_op(&val, dest, /*allow_transmute*/ false)?;
178178
}
179179

180180
sym::ctpop
@@ -394,7 +394,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
394394
}
395395

396396
sym::transmute => {
397-
self.copy_op_transmute(&args[0], dest)?;
397+
self.copy_op(&args[0], dest, /*allow_transmute*/ true)?;
398398
}
399399
sym::assert_inhabited | sym::assert_zero_valid | sym::assert_uninit_valid => {
400400
let ty = instance.substs.type_at(0);
@@ -461,7 +461,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
461461
let place = self.mplace_index(&dest, i)?;
462462
let value =
463463
if i == index { *elem } else { self.mplace_index(&input, i)?.into() };
464-
self.copy_op(&value, &place.into())?;
464+
self.copy_op(&value, &place.into(), /*allow_transmute*/ false)?;
465465
}
466466
}
467467
sym::simd_extract => {
@@ -473,11 +473,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
473473
index,
474474
input_len
475475
);
476-
self.copy_op(&self.mplace_index(&input, index)?.into(), dest)?;
476+
self.copy_op(
477+
&self.mplace_index(&input, index)?.into(),
478+
dest,
479+
/*allow_transmute*/ false,
480+
)?;
477481
}
478482
sym::likely | sym::unlikely | sym::black_box => {
479483
// These just return their argument
480-
self.copy_op(&args[0], dest)?;
484+
self.copy_op(&args[0], dest, /*allow_transmute*/ false)?;
481485
}
482486
sym::assume => {
483487
let cond = self.read_scalar(&args[0])?.check_init()?.to_bool()?;

compiler/rustc_const_eval/src/interpret/machine.rs

+11-8
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
}

0 commit comments

Comments
 (0)