storage_live: avoid computing the layout unless necessary

This commit is contained in:
Ralf Jung 2023-08-07 08:18:50 +02:00
parent f87e91de7d
commit 6d1ce9bd13
3 changed files with 69 additions and 18 deletions

View File

@ -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>>>,
}
@ -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.
@ -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)
}

View File

@ -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
}

View File

@ -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