@@ -158,7 +158,8 @@ pub enum StackPopCleanup {
158
158
#[ derive( Clone , Debug ) ]
159
159
pub struct LocalState < ' tcx , Prov : Provenance = AllocId > {
160
160
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.
162
163
pub layout : Cell < Option < TyAndLayout < ' tcx > > > ,
163
164
}
164
165
@@ -177,7 +178,7 @@ pub enum LocalValue<Prov: Provenance = AllocId> {
177
178
178
179
impl < ' tcx , Prov : Provenance + ' static > LocalState < ' tcx , Prov > {
179
180
/// Read the local's value or error if the local is not yet live or not live anymore.
180
- #[ inline]
181
+ #[ inline( always ) ]
181
182
pub fn access ( & self ) -> InterpResult < ' tcx , & Operand < Prov > > {
182
183
match & self . value {
183
184
LocalValue :: Dead => throw_ub ! ( DeadLocal ) , // could even be "invalid program"?
@@ -190,7 +191,7 @@ impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
190
191
///
191
192
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
192
193
/// anywhere else. You may be invalidating machine invariants if you do!
193
- #[ inline]
194
+ #[ inline( always ) ]
194
195
pub fn access_mut ( & mut self ) -> InterpResult < ' tcx , & mut Operand < Prov > > {
195
196
match & mut self . value {
196
197
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> {
483
484
}
484
485
485
486
#[ inline( always) ]
486
- pub ( super ) fn body ( & self ) -> & ' mir mir:: Body < ' tcx > {
487
+ pub fn body ( & self ) -> & ' mir mir:: Body < ' tcx > {
487
488
self . frame ( ) . body
488
489
}
489
490
@@ -705,15 +706,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
705
706
return_to_block : StackPopCleanup ,
706
707
) -> InterpResult < ' tcx > {
707
708
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 ) ;
708
711
// First push a stack frame so we have access to the local args
709
712
let pre_frame = Frame {
710
713
body,
711
714
loc : Right ( body. span ) , // Span used for errors caused during preamble.
712
715
return_to_block,
713
716
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,
717
718
instance,
718
719
tracing_span : SpanGuard :: new ( ) ,
719
720
extra : ( ) ,
@@ -728,19 +729,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
728
729
self . eval_mir_constant ( & ct, Some ( span) , None ) ?;
729
730
}
730
731
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
- }
742
732
// done
743
- self . frame_mut ( ) . locals = locals;
744
733
M :: after_stack_push ( self ) ?;
745
734
self . frame_mut ( ) . loc = Left ( mir:: Location :: START ) ;
746
735
@@ -907,12 +896,96 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
907
896
}
908
897
}
909
898
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 > {
913
919
trace ! ( "{:?} is now live" , local) ;
914
920
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
+
916
989
// StorageLive expects the local to be dead, and marks it live.
917
990
let old = mem:: replace ( & mut self . frame_mut ( ) . locals [ local] . value , local_val) ;
918
991
if !matches ! ( old, LocalValue :: Dead ) {
@@ -921,6 +994,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
921
994
Ok ( ( ) )
922
995
}
923
996
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
+
924
1003
pub fn storage_dead ( & mut self , local : mir:: Local ) -> InterpResult < ' tcx > {
925
1004
assert ! ( local != mir:: RETURN_PLACE , "Cannot make return place dead" ) ;
926
1005
trace ! ( "{:?} is now dead" , local) ;
0 commit comments