Skip to content

Commit a09df43

Browse files
committed
move marking-locals-live out of push_stack_frame, so it happens with argument passing
this entirely avoids even creating unsized locals in Immediate::Uninitialized state
1 parent bdd5855 commit a09df43

File tree

15 files changed

+188
-105
lines changed

15 files changed

+188
-105
lines changed

compiler/rustc_const_eval/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ const_eval_unreachable_unwind =
384384
385385
const_eval_unsigned_offset_from_overflow =
386386
`ptr_offset_from_unsigned` called when first pointer has smaller offset than second: {$a_offset} < {$b_offset}
387-
387+
const_eval_unsized_local = unsized locals are not supported
388388
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
389389
390390
const_eval_unstable_in_stable =

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
6161
&ret.clone().into(),
6262
StackPopCleanup::Root { cleanup: false },
6363
)?;
64+
ecx.storage_live_for_always_live_locals()?;
6465

6566
// The main interpreter loop.
6667
while ecx.step()? {}

compiler/rustc_const_eval/src/errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ impl ReportErrorExt for UnsupportedOpInfo {
795795
use crate::fluent_generated::*;
796796
match self {
797797
UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
798+
UnsupportedOpInfo::UnsizedLocal => const_eval_unsized_local,
798799
UnsupportedOpInfo::OverwritePartialPointer(_) => const_eval_partial_pointer_overwrite,
799800
UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_copy,
800801
UnsupportedOpInfo::ReadPointerAsInt(_) => const_eval_read_pointer_as_int,
@@ -814,7 +815,7 @@ impl ReportErrorExt for UnsupportedOpInfo {
814815
// `ReadPointerAsInt(Some(info))` is never printed anyway, it only serves as an error to
815816
// be further processed by validity checking which then turns it into something nice to
816817
// print. So it's not worth the effort of having diagnostics that can print the `info`.
817-
Unsupported(_) | ReadPointerAsInt(_) => {}
818+
UnsizedLocal | Unsupported(_) | ReadPointerAsInt(_) => {}
818819
OverwritePartialPointer(ptr) | ReadPartialPointer(ptr) => {
819820
builder.set_arg("ptr", ptr);
820821
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

+26-18
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ pub enum StackPopCleanup {
158158
#[derive(Clone, Debug)]
159159
pub struct LocalState<'tcx, Prov: Provenance = AllocId> {
160160
pub value: LocalValue<Prov>,
161-
/// Don't modify if `Some`, this is only used to prevent computing the layout twice
161+
/// Don't modify if `Some`, this is only used to prevent computing the layout twice.
162+
/// Layout needs to be computed lazily because ConstProp wants to run on frames where we can't
163+
/// compute the layout of all locals.
162164
pub layout: Cell<Option<TyAndLayout<'tcx>>>,
163165
}
164166

@@ -483,7 +485,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
483485
}
484486

485487
#[inline(always)]
486-
pub(super) fn body(&self) -> &'mir mir::Body<'tcx> {
488+
pub fn body(&self) -> &'mir mir::Body<'tcx> {
487489
self.frame().body
488490
}
489491

@@ -705,15 +707,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
705707
return_to_block: StackPopCleanup,
706708
) -> InterpResult<'tcx> {
707709
trace!("body: {:#?}", body);
710+
let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
711+
let locals = IndexVec::from_elem(dead_local, &body.local_decls);
708712
// First push a stack frame so we have access to the local args
709713
let pre_frame = Frame {
710714
body,
711715
loc: Right(body.span), // Span used for errors caused during preamble.
712716
return_to_block,
713717
return_place: return_place.clone(),
714-
// empty local array, we fill it in below, after we are inside the stack frame and
715-
// all methods actually know about the frame
716-
locals: IndexVec::new(),
718+
locals,
717719
instance,
718720
tracing_span: SpanGuard::new(),
719721
extra: (),
@@ -728,19 +730,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
728730
self.eval_mir_constant(&ct, Some(span), None)?;
729731
}
730732

731-
// Most locals are initially dead.
732-
let dummy = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
733-
let mut locals = IndexVec::from_elem(dummy, &body.local_decls);
734-
735-
// Now mark those locals as live that have no `Storage*` annotations.
736-
let always_live = always_storage_live_locals(self.body());
737-
for local in locals.indices() {
738-
if always_live.contains(local) {
739-
locals[local].value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
740-
}
741-
}
742733
// done
743-
self.frame_mut().locals = locals;
744734
M::after_stack_push(self)?;
745735
self.frame_mut().loc = Left(mir::Location::START);
746736

@@ -907,11 +897,29 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
907897
}
908898
}
909899

900+
/// In the current stack frame, mark all locals as live that are not arguments and don't have
901+
/// `Storage*` annotations (this includes the return place).
902+
pub fn storage_live_for_always_live_locals(&mut self) -> InterpResult<'tcx> {
903+
self.storage_live(mir::RETURN_PLACE)?;
904+
905+
let body = self.body();
906+
let always_live = always_storage_live_locals(body);
907+
for local in body.vars_and_temps_iter() {
908+
if always_live.contains(local) {
909+
self.storage_live(local)?;
910+
}
911+
}
912+
Ok(())
913+
}
914+
910915
/// Mark a storage as live, killing the previous content.
911916
pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> {
912-
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
913917
trace!("{:?} is now live", local);
914918

919+
if self.layout_of_local(self.frame(), local, None)?.is_unsized() {
920+
throw_unsup!(UnsizedLocal);
921+
}
922+
915923
let local_val = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
916924
// StorageLive expects the local to be dead, and marks it live.
917925
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);

compiler/rustc_const_eval/src/interpret/operand.rs

+25-18
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum Immediate<Prov: Provenance = AllocId> {
3333
/// A pair of two scalar value (must have `ScalarPair` ABI where both fields are
3434
/// `Scalar::Initialized`).
3535
ScalarPair(Scalar<Prov>, Scalar<Prov>),
36-
/// A value of fully uninitialized memory. Can have arbitrary size and layout.
36+
/// A value of fully uninitialized memory. Can have arbitrary size and layout, but must be sized.
3737
Uninit,
3838
}
3939

@@ -190,16 +190,19 @@ impl<'tcx, Prov: Provenance> From<ImmTy<'tcx, Prov>> for OpTy<'tcx, Prov> {
190190
impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
191191
#[inline]
192192
pub fn from_scalar(val: Scalar<Prov>, layout: TyAndLayout<'tcx>) -> Self {
193+
debug_assert!(layout.abi.is_scalar(), "`ImmTy::from_scalar` on non-scalar layout");
193194
ImmTy { imm: val.into(), layout }
194195
}
195196

196-
#[inline]
197+
#[inline(always)]
197198
pub fn from_immediate(imm: Immediate<Prov>, layout: TyAndLayout<'tcx>) -> Self {
199+
debug_assert!(layout.is_sized(), "immediates must be sized");
198200
ImmTy { imm, layout }
199201
}
200202

201203
#[inline]
202204
pub fn uninit(layout: TyAndLayout<'tcx>) -> Self {
205+
debug_assert!(layout.is_sized(), "immediates must be sized");
203206
ImmTy { imm: Immediate::Uninit, layout }
204207
}
205208

@@ -322,15 +325,12 @@ impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for OpTy<'tcx, Pr
322325
self.layout
323326
}
324327

328+
#[inline]
325329
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
326330
Ok(match self.as_mplace_or_imm() {
327331
Left(mplace) => mplace.meta,
328332
Right(_) => {
329-
if self.layout.is_unsized() {
330-
// Unsized immediate OpTy cannot occur. We create a MemPlace for all unsized locals during argument passing.
331-
// However, ConstProp doesn't do that, so we can run into this nonsense situation.
332-
throw_inval!(ConstPropNonsense);
333-
}
333+
debug_assert!(self.layout.is_sized(), "unsized immediates are not a thing");
334334
MemPlaceMeta::None
335335
}
336336
})
@@ -346,9 +346,10 @@ impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for OpTy<'tcx, Pr
346346
match self.as_mplace_or_imm() {
347347
Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, ecx)?.into()),
348348
Right(imm) => {
349-
assert!(!meta.has_meta()); // no place to store metadata here
349+
debug_assert!(layout.is_sized(), "unsized immediates are not a thing");
350+
assert_matches!(meta, MemPlaceMeta::None); // no place to store metadata here
350351
// Every part of an uninit is uninit.
351-
Ok(imm.offset(offset, layout, ecx)?.into())
352+
Ok(imm.offset_(offset, layout, ecx).into())
352353
}
353354
}
354355
}
@@ -576,6 +577,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
576577
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
577578
let layout = self.layout_of_local(frame, local, layout)?;
578579
let op = *frame.locals[local].access()?;
580+
if matches!(op, Operand::Immediate(_)) {
581+
if layout.is_unsized() {
582+
// ConstProp marks *all* locals as `Immediate::Uninit` since it cannot
583+
// efficiently check whether they are sized. We have to catch that case here.
584+
throw_inval!(ConstPropNonsense);
585+
}
586+
}
579587
Ok(OpTy { op, layout, align: Some(layout.align.abi) })
580588
}
581589

@@ -589,16 +597,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
589597
match place.as_mplace_or_local() {
590598
Left(mplace) => Ok(mplace.into()),
591599
Right((frame, local, offset)) => {
600+
debug_assert!(place.layout.is_sized()); // only sized locals can ever be `Place::Local`.
592601
let base = self.local_to_op(&self.stack()[frame], local, None)?;
593-
let mut field = if let Some(offset) = offset {
594-
// This got offset. We can be sure that the field is sized.
595-
base.offset(offset, place.layout, self)?
596-
} else {
597-
assert_eq!(place.layout, base.layout);
598-
// Unsized cases are possible here since an unsized local will be a
599-
// `Place::Local` until the first projection calls `place_to_op` to extract the
600-
// underlying mplace.
601-
base
602+
let mut field = match offset {
603+
Some(offset) => base.offset(offset, place.layout, self)?,
604+
None => {
605+
// In the common case this hasn't been projected.
606+
debug_assert_eq!(place.layout, base.layout);
607+
base
608+
}
602609
};
603610
field.align = Some(place.align);
604611
Ok(field)

compiler/rustc_const_eval/src/interpret/place.rs

+18-12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ impl<Prov: Provenance> MemPlaceMeta<Prov> {
4141
}
4242
}
4343

44+
#[inline(always)]
4445
pub fn has_meta(self) -> bool {
4546
match self {
4647
Self::Meta(_) => true,
@@ -255,15 +256,12 @@ impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for PlaceTy<'tcx,
255256
self.layout
256257
}
257258

259+
#[inline]
258260
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
259261
Ok(match self.as_mplace_or_local() {
260262
Left(mplace) => mplace.meta,
261263
Right(_) => {
262-
if self.layout.is_unsized() {
263-
// Unsized `Place::Local` cannot occur. We create a MemPlace for all unsized locals during argument passing.
264-
// However, ConstProp doesn't do that, so we can run into this nonsense situation.
265-
throw_inval!(ConstPropNonsense);
266-
}
264+
debug_assert!(self.layout.is_sized(), "unsized locals should live in memory");
267265
MemPlaceMeta::None
268266
}
269267
})
@@ -331,7 +329,7 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
331329

332330
impl<'tcx, Prov: Provenance + 'static> PlaceTy<'tcx, Prov> {
333331
/// A place is either an mplace or some local.
334-
#[inline]
332+
#[inline(always)]
335333
pub fn as_mplace_or_local(
336334
&self,
337335
) -> Either<MPlaceTy<'tcx, Prov>, (usize, mir::Local, Option<Size>)> {
@@ -535,9 +533,19 @@ where
535533
// So we eagerly check here if this local has an MPlace, and if yes we use it.
536534
let frame_ref = &self.stack()[frame];
537535
let layout = self.layout_of_local(frame_ref, local, None)?;
538-
let place = match frame_ref.locals[local].access()? {
539-
Operand::Immediate(_) => Place::Local { frame, local, offset: None },
540-
Operand::Indirect(mplace) => Place::Ptr(*mplace),
536+
let place = if layout.is_sized() {
537+
// We can just always use the `Local` for sized values.
538+
Place::Local { frame, local, offset: None }
539+
} else {
540+
// Unsized `Local` isn't okay (we cannot store the metadata).
541+
match frame_ref.locals[local].access()? {
542+
Operand::Immediate(_) => {
543+
// ConstProp marks *all* locals as `Immediate::Uninit` since it cannot
544+
// efficiently check whether they are sized. We have to catch that case here.
545+
throw_inval!(ConstPropNonsense);
546+
}
547+
Operand::Indirect(mplace) => Place::Ptr(*mplace),
548+
}
541549
};
542550
Ok(PlaceTy { place, layout, align: layout.align.abi })
543551
}
@@ -896,9 +904,7 @@ where
896904
// that has different alignment than the outer field.
897905
let local_layout =
898906
self.layout_of_local(&self.stack()[frame], local, None)?;
899-
if local_layout.is_unsized() {
900-
throw_unsup_format!("unsized locals are not supported");
901-
}
907+
assert!(local_layout.is_sized(), "unsized locals cannot be immediate");
902908
let mplace = self.allocate(local_layout, MemoryKind::Stack)?;
903909
// Preserve old value. (As an optimization, we can skip this if it was uninit.)
904910
if !matches!(local_val, Immediate::Uninit) {

0 commit comments

Comments
 (0)