Skip to content

Commit

Permalink
storage_live: avoid computing the layout unless necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Aug 30, 2023
1 parent f87e91d commit 6d1ce9b
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 18 deletions.
79 changes: 65 additions & 14 deletions compiler/rustc_const_eval/src/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ pub enum StackPopCleanup {
pub struct LocalState<'tcx, Prov: Provenance = AllocId> {
pub value: LocalValue<Prov>,
/// Don't modify if `Some`, this is only used to prevent computing the layout twice.
/// Layout needs to be computed lazily because ConstProp wants to run on frames where we can't
/// compute the layout of all locals.
/// Avoids computing the layout of locals that are never actually initialized.
pub layout: Cell<Option<TyAndLayout<'tcx>>>,
}

Expand Down Expand Up @@ -919,15 +918,72 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
) -> InterpResult<'tcx> {
trace!("{:?} is now live", local);

let layout = self.layout_of_local(self.frame(), local, None)?;
let local_val = LocalValue::Live(if layout.is_sized() {
assert!(matches!(meta, MemPlaceMeta::None)); // we're dropping the metadata
// Just make this an efficient immediate.
Operand::Immediate(Immediate::Uninit)
// We avoid `ty.is_trivially_sized` since that (a) cannot assume WF, so it recurses through
// all fields of a tuple, and (b) does something expensive for ADTs.
fn is_very_trivially_sized(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
| ty::Uint(_)
| ty::Int(_)
| ty::Bool
| ty::Float(_)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::RawPtr(..)
| ty::Char
| ty::Ref(..)
| ty::Generator(..)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..)
| ty::Array(..)
| ty::Closure(..)
| ty::Never
| ty::Error(_) => true,

ty::Str | ty::Slice(_) | ty::Dynamic(..) | ty::Foreign(..) => false,

ty::Tuple(tys) => tys.last().iter().all(|ty| is_very_trivially_sized(**ty)),

// We don't want to do any queries, so there is not much we can do with ADTs.
ty::Adt(..) => false,

ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false,

ty::Infer(ty::TyVar(_)) => false,

ty::Bound(..)
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
bug!("`is_very_trivially_sized` applied to unexpected type: {:?}", ty)
}
}
}

// This is a hot function, we avoid computing the layout when possible.
// `unsized_` will be `None` for sized types and `Some(layout)` for unsized types.
let unsized_ = if is_very_trivially_sized(self.body().local_decls[local].ty) {
None
} else {
// Need to allocate some memory.
// We need the layout.
let layout = self.layout_of_local(self.frame(), local, None)?;
if layout.is_sized() { None } else { Some(layout) }
};

let local_val = LocalValue::Live(if let Some(layout) = unsized_ {
if !meta.has_meta() {
throw_unsup!(UnsizedLocal);
}
// Need to allocate some memory, since `Immediate::Uninit` cannot be unsized.
let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
Operand::Indirect(*dest_place)
} else {
assert!(!meta.has_meta()); // we're dropping the metadata
// Just make this an efficient immediate.
// Note that not calling `layout_of` here does have one real consequence:
// if the type is too big, we'll only notice this when the local is actually initialized,
// which is a bit too late -- we should ideally notice this alreayd here, when the memory
// is conceptually allocated. But given how rare that error is and that this is a hot function,
// we accept this downside for now.
Operand::Immediate(Immediate::Uninit)
});

// StorageLive expects the local to be dead, and marks it live.
Expand All @@ -939,13 +995,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}

/// Mark a storage as live, killing the previous content.
#[inline(always)]
pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> {
trace!("{:?} is now live", local);

if self.layout_of_local(self.frame(), local, None)?.is_unsized() {
throw_unsup!(UnsizedLocal);
}

self.storage_live_dyn(local, MemPlaceMeta::None)
}

Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail/type-too-large.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ignore-32bit

fn main() {
let _fat: [u8; (1 << 61) + (1 << 31)]; //~ ERROR: post-monomorphization error
_fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize];
let _fat: [u8; (1 << 61) + (1 << 31)]; // ideally we'd error here, but we avoid computing the layout until absolutely necessary
_fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize]; //~ ERROR: post-monomorphization error
}
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail/type-too-large.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: post-monomorphization error: values of the type `[u8; 2305843011361177600]` are too big for the current architecture
--> $DIR/type-too-large.rs:LL:CC
|
LL | let _fat: [u8; (1 << 61) + (1 << 31)];
| ^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture
LL | _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture
|
= note: inside `main` at $DIR/type-too-large.rs:LL:CC

Expand Down

0 comments on commit 6d1ce9b

Please sign in to comment.