Skip to content

Commit 6d1ce9b

Browse files
committed
storage_live: avoid computing the layout unless necessary
1 parent f87e91d commit 6d1ce9b

File tree

3 files changed

+69
-18
lines changed

3 files changed

+69
-18
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

+65-14
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ pub enum StackPopCleanup {
159159
pub struct LocalState<'tcx, Prov: Provenance = AllocId> {
160160
pub value: LocalValue<Prov>,
161161
/// 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.
162+
/// Avoids computing the layout of locals that are never actually initialized.
164163
pub layout: Cell<Option<TyAndLayout<'tcx>>>,
165164
}
166165

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

922-
let layout = self.layout_of_local(self.frame(), local, None)?;
923-
let local_val = LocalValue::Live(if layout.is_sized() {
924-
assert!(matches!(meta, MemPlaceMeta::None)); // we're dropping the metadata
925-
// Just make this an efficient immediate.
926-
Operand::Immediate(Immediate::Uninit)
921+
// We avoid `ty.is_trivially_sized` since that (a) cannot assume WF, so it recurses through
922+
// all fields of a tuple, and (b) does something expensive for ADTs.
923+
fn is_very_trivially_sized(ty: Ty<'_>) -> bool {
924+
match ty.kind() {
925+
ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
926+
| ty::Uint(_)
927+
| ty::Int(_)
928+
| ty::Bool
929+
| ty::Float(_)
930+
| ty::FnDef(..)
931+
| ty::FnPtr(_)
932+
| ty::RawPtr(..)
933+
| ty::Char
934+
| ty::Ref(..)
935+
| ty::Generator(..)
936+
| ty::GeneratorWitness(..)
937+
| ty::GeneratorWitnessMIR(..)
938+
| ty::Array(..)
939+
| ty::Closure(..)
940+
| ty::Never
941+
| ty::Error(_) => true,
942+
943+
ty::Str | ty::Slice(_) | ty::Dynamic(..) | ty::Foreign(..) => false,
944+
945+
ty::Tuple(tys) => tys.last().iter().all(|ty| is_very_trivially_sized(**ty)),
946+
947+
// We don't want to do any queries, so there is not much we can do with ADTs.
948+
ty::Adt(..) => false,
949+
950+
ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false,
951+
952+
ty::Infer(ty::TyVar(_)) => false,
953+
954+
ty::Bound(..)
955+
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
956+
bug!("`is_very_trivially_sized` applied to unexpected type: {:?}", ty)
957+
}
958+
}
959+
}
960+
961+
// This is a hot function, we avoid computing the layout when possible.
962+
// `unsized_` will be `None` for sized types and `Some(layout)` for unsized types.
963+
let unsized_ = if is_very_trivially_sized(self.body().local_decls[local].ty) {
964+
None
927965
} else {
928-
// Need to allocate some memory.
966+
// We need the layout.
967+
let layout = self.layout_of_local(self.frame(), local, None)?;
968+
if layout.is_sized() { None } else { Some(layout) }
969+
};
970+
971+
let local_val = LocalValue::Live(if let Some(layout) = unsized_ {
972+
if !meta.has_meta() {
973+
throw_unsup!(UnsizedLocal);
974+
}
975+
// Need to allocate some memory, since `Immediate::Uninit` cannot be unsized.
929976
let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
930977
Operand::Indirect(*dest_place)
978+
} else {
979+
assert!(!meta.has_meta()); // we're dropping the metadata
980+
// Just make this an efficient immediate.
981+
// Note that not calling `layout_of` here does have one real consequence:
982+
// if the type is too big, we'll only notice this when the local is actually initialized,
983+
// which is a bit too late -- we should ideally notice this alreayd here, when the memory
984+
// is conceptually allocated. But given how rare that error is and that this is a hot function,
985+
// we accept this downside for now.
986+
Operand::Immediate(Immediate::Uninit)
931987
});
932988

933989
// 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> {
939995
}
940996

941997
/// Mark a storage as live, killing the previous content.
998+
#[inline(always)]
942999
pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> {
943-
trace!("{:?} is now live", local);
944-
945-
if self.layout_of_local(self.frame(), local, None)?.is_unsized() {
946-
throw_unsup!(UnsizedLocal);
947-
}
948-
9491000
self.storage_live_dyn(local, MemPlaceMeta::None)
9501001
}
9511002

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//@ignore-32bit
22

33
fn main() {
4-
let _fat: [u8; (1 << 61) + (1 << 31)]; //~ ERROR: post-monomorphization error
5-
_fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize];
4+
let _fat: [u8; (1 << 61) + (1 << 31)]; // ideally we'd error here, but we avoid computing the layout until absolutely necessary
5+
_fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize]; //~ ERROR: post-monomorphization error
66
}

src/tools/miri/tests/fail/type-too-large.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: post-monomorphization error: values of the type `[u8; 2305843011361177600]` are too big for the current architecture
22
--> $DIR/type-too-large.rs:LL:CC
33
|
4-
LL | let _fat: [u8; (1 << 61) + (1 << 31)];
5-
| ^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture
4+
LL | _fat = [0; (1u64 << 61) as usize + (1u64 << 31) as usize];
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ values of the type `[u8; 2305843011361177600]` are too big for the current architecture
66
|
77
= note: inside `main` at $DIR/type-too-large.rs:LL:CC
88

0 commit comments

Comments
 (0)