@@ -158,7 +158,8 @@ pub enum StackPopCleanup {
158158#[ derive( Clone , Debug ) ]
159159pub 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+ /// Avoids computing the layout of locals that are never actually initialized.
162163 pub layout : Cell < Option < TyAndLayout < ' tcx > > > ,
163164}
164165
@@ -177,7 +178,7 @@ pub enum LocalValue<Prov: Provenance = AllocId> {
177178
178179impl < ' tcx , Prov : Provenance + ' static > LocalState < ' tcx , Prov > {
179180 /// Read the local's value or error if the local is not yet live or not live anymore.
180- #[ inline]
181+ #[ inline( always ) ]
181182 pub fn access ( & self ) -> InterpResult < ' tcx , & Operand < Prov > > {
182183 match & self . value {
183184 LocalValue :: Dead => throw_ub ! ( DeadLocal ) , // could even be "invalid program"?
@@ -190,7 +191,7 @@ impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
190191 ///
191192 /// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
192193 /// anywhere else. You may be invalidating machine invariants if you do!
193- #[ inline]
194+ #[ inline( always ) ]
194195 pub fn access_mut ( & mut self ) -> InterpResult < ' tcx , & mut Operand < Prov > > {
195196 match & mut self . value {
196197 LocalValue :: Dead => throw_ub ! ( DeadLocal ) , // could even be "invalid program"?
@@ -483,7 +484,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
483484 }
484485
485486 #[ inline( always) ]
486- pub ( super ) fn body ( & self ) -> & ' mir mir:: Body < ' tcx > {
487+ pub fn body ( & self ) -> & ' mir mir:: Body < ' tcx > {
487488 self . frame ( ) . body
488489 }
489490
@@ -705,15 +706,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
705706 return_to_block : StackPopCleanup ,
706707 ) -> InterpResult < ' tcx > {
707708 trace ! ( "body: {:#?}" , body) ;
709+ let dead_local = LocalState { value : LocalValue :: Dead , layout : Cell :: new ( None ) } ;
710+ let locals = IndexVec :: from_elem ( dead_local, & body. local_decls ) ;
708711 // First push a stack frame so we have access to the local args
709712 let pre_frame = Frame {
710713 body,
711714 loc : Right ( body. span ) , // Span used for errors caused during preamble.
712715 return_to_block,
713716 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 ( ) ,
717+ locals,
717718 instance,
718719 tracing_span : SpanGuard :: new ( ) ,
719720 extra : ( ) ,
@@ -728,19 +729,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
728729 self . eval_mir_constant ( & ct, Some ( span) , None ) ?;
729730 }
730731
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- }
742732 // done
743- self . frame_mut ( ) . locals = locals;
744733 M :: after_stack_push ( self ) ?;
745734 self . frame_mut ( ) . loc = Left ( mir:: Location :: START ) ;
746735
@@ -907,12 +896,96 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
907896 }
908897 }
909898
910- /// Mark a storage as live, killing the previous content.
911- pub fn storage_live ( & mut self , local : mir:: Local ) -> InterpResult < ' tcx > {
912- assert ! ( local != mir:: RETURN_PLACE , "Cannot make return place live" ) ;
899+ /// In the current stack frame, mark all locals as live that are not arguments and don't have
900+ /// `Storage*` annotations (this includes the return place).
901+ pub fn storage_live_for_always_live_locals ( & mut self ) -> InterpResult < ' tcx > {
902+ self . storage_live ( mir:: RETURN_PLACE ) ?;
903+
904+ let body = self . body ( ) ;
905+ let always_live = always_storage_live_locals ( body) ;
906+ for local in body. vars_and_temps_iter ( ) {
907+ if always_live. contains ( local) {
908+ self . storage_live ( local) ?;
909+ }
910+ }
911+ Ok ( ( ) )
912+ }
913+
914+ pub fn storage_live_dyn (
915+ & mut self ,
916+ local : mir:: Local ,
917+ meta : MemPlaceMeta < M :: Provenance > ,
918+ ) -> InterpResult < ' tcx > {
913919 trace ! ( "{:?} is now live" , local) ;
914920
915- let local_val = LocalValue :: Live ( 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
965+ } else {
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.
976+ let dest_place = self . allocate_dyn ( layout, MemoryKind :: Stack , meta) ?;
977+ 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 )
987+ } ) ;
988+
916989 // StorageLive expects the local to be dead, and marks it live.
917990 let old = mem:: replace ( & mut self . frame_mut ( ) . locals [ local] . value , local_val) ;
918991 if !matches ! ( old, LocalValue :: Dead ) {
@@ -921,6 +994,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
921994 Ok ( ( ) )
922995 }
923996
997+ /// Mark a storage as live, killing the previous content.
998+ #[ inline( always) ]
999+ pub fn storage_live ( & mut self , local : mir:: Local ) -> InterpResult < ' tcx > {
1000+ self . storage_live_dyn ( local, MemPlaceMeta :: None )
1001+ }
1002+
9241003 pub fn storage_dead ( & mut self , local : mir:: Local ) -> InterpResult < ' tcx > {
9251004 assert ! ( local != mir:: RETURN_PLACE , "Cannot make return place dead" ) ;
9261005 trace ! ( "{:?} is now dead" , local) ;
0 commit comments