@@ -3,9 +3,10 @@ use rustc_index::bit_set::DenseBitSet;
33use rustc_middle:: mir:: visit:: * ;
44use rustc_middle:: mir:: * ;
55use rustc_middle:: ty:: TyCtxt ;
6+ use rustc_mir_dataflow:: { Analysis , ResultsCursor } ;
67use tracing:: { debug, instrument} ;
78
8- use crate :: ssa:: SsaLocals ;
9+ use crate :: ssa:: { MaybeUninitializedLocals , SsaLocals } ;
910
1011/// Unify locals that copy each other.
1112///
@@ -16,7 +17,7 @@ use crate::ssa::SsaLocals;
1617/// _d = move? _c
1718/// where each of the locals is only assigned once.
1819///
19- /// We want to replace all those locals by `copy _a`.
20+ /// We want to replace all those locals by `_a` (the "head"), either copied or moved .
2021pub ( super ) struct CopyProp ;
2122
2223impl < ' tcx > crate :: MirPass < ' tcx > for CopyProp {
@@ -30,15 +31,19 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
3031
3132 let typing_env = body. typing_env ( tcx) ;
3233 let ssa = SsaLocals :: new ( tcx, body, typing_env) ;
34+
3335 debug ! ( borrowed_locals = ?ssa. borrowed_locals( ) ) ;
3436 debug ! ( copy_classes = ?ssa. copy_classes( ) ) ;
3537
3638 let mut any_replacement = false ;
3739 // Locals that participate in copy propagation either as a source or a destination.
3840 let mut unified = DenseBitSet :: new_empty ( body. local_decls . len ( ) ) ;
41+ let mut storage_to_remove = DenseBitSet :: new_empty ( body. local_decls . len ( ) ) ;
42+
3943 for ( local, & head) in ssa. copy_classes ( ) . iter_enumerated ( ) {
4044 if local != head {
4145 any_replacement = true ;
46+ storage_to_remove. insert ( head) ;
4247 unified. insert ( head) ;
4348 unified. insert ( local) ;
4449 }
@@ -48,7 +53,46 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
4853 return ;
4954 }
5055
51- Replacer { tcx, copy_classes : ssa. copy_classes ( ) , unified } . visit_body_preserves_cfg ( body) ;
56+ // When emitting storage statements, we want to retain the head locals' storage statements,
57+ // as this enables better optimizations. For each local use location, we mark the head for storage removal
58+ // only if the head might be uninitialized at that point, or if the local is borrowed
59+ // (since we cannot easily determine when it's used).
60+ let storage_to_remove = if tcx. sess . emit_lifetime_markers ( ) {
61+ storage_to_remove. clear ( ) ;
62+
63+ // If the local is borrowed, we cannot easily determine if it is used, so we have to remove the storage statements.
64+ let borrowed_locals = ssa. borrowed_locals ( ) ;
65+
66+ for ( local, & head) in ssa. copy_classes ( ) . iter_enumerated ( ) {
67+ if local != head && borrowed_locals. contains ( local) {
68+ storage_to_remove. insert ( head) ;
69+ }
70+ }
71+
72+ let maybe_uninit = MaybeUninitializedLocals
73+ . iterate_to_fixpoint ( tcx, body, Some ( "mir_opt::copy_prop" ) )
74+ . into_results_cursor ( body) ;
75+
76+ let mut storage_checker = StorageChecker {
77+ maybe_uninit,
78+ copy_classes : ssa. copy_classes ( ) ,
79+ storage_to_remove,
80+ } ;
81+
82+ for ( bb, data) in traversal:: reachable ( body) {
83+ storage_checker. visit_basic_block_data ( bb, data) ;
84+ }
85+
86+ storage_checker. storage_to_remove
87+ } else {
88+ // Remove the storage statements of all the head locals.
89+ storage_to_remove
90+ } ;
91+
92+ debug ! ( ?storage_to_remove) ;
93+
94+ Replacer { tcx, copy_classes : ssa. copy_classes ( ) , unified, storage_to_remove }
95+ . visit_body_preserves_cfg ( body) ;
5296
5397 crate :: simplify:: remove_unused_definitions ( body) ;
5498 }
@@ -63,6 +107,7 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
63107struct Replacer < ' a , ' tcx > {
64108 tcx : TyCtxt < ' tcx > ,
65109 unified : DenseBitSet < Local > ,
110+ storage_to_remove : DenseBitSet < Local > ,
66111 copy_classes : & ' a IndexSlice < Local , Local > ,
67112}
68113
@@ -73,7 +118,13 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
73118
74119 #[ tracing:: instrument( level = "trace" , skip( self ) ) ]
75120 fn visit_local ( & mut self , local : & mut Local , ctxt : PlaceContext , _: Location ) {
76- * local = self . copy_classes [ * local] ;
121+ let new_local = self . copy_classes [ * local] ;
122+ match ctxt {
123+ // Do not modify the local in storage statements.
124+ PlaceContext :: NonUse ( NonUseContext :: StorageLive | NonUseContext :: StorageDead ) => { }
125+ // We access the value.
126+ _ => * local = new_local,
127+ }
77128 }
78129
79130 #[ tracing:: instrument( level = "trace" , skip( self ) ) ]
@@ -93,7 +144,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
93144 fn visit_statement ( & mut self , stmt : & mut Statement < ' tcx > , loc : Location ) {
94145 // When removing storage statements, we need to remove both (#107511).
95146 if let StatementKind :: StorageLive ( l) | StatementKind :: StorageDead ( l) = stmt. kind
96- && self . unified . contains ( l)
147+ && self . storage_to_remove . contains ( l)
97148 {
98149 stmt. make_nop ( true ) ;
99150 }
@@ -109,3 +160,39 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
109160 }
110161 }
111162}
163+
164+ // Marks heads of copy classes that are maybe uninitialized at the location of a local
165+ // as needing storage statement removal.
166+ struct StorageChecker < ' a , ' tcx > {
167+ maybe_uninit : ResultsCursor < ' a , ' tcx , MaybeUninitializedLocals > ,
168+ copy_classes : & ' a IndexSlice < Local , Local > ,
169+ storage_to_remove : DenseBitSet < Local > ,
170+ }
171+
172+ impl < ' a , ' tcx > Visitor < ' tcx > for StorageChecker < ' a , ' tcx > {
173+ fn visit_local ( & mut self , local : Local , context : PlaceContext , loc : Location ) {
174+ if !context. is_use ( ) {
175+ return ;
176+ }
177+
178+ let head = self . copy_classes [ local] ;
179+
180+ // If the local is the head, or if we already marked it for deletion, we do not need to check it.
181+ if head == local || self . storage_to_remove . contains ( head) {
182+ return ;
183+ }
184+
185+ self . maybe_uninit . seek_before_primary_effect ( loc) ;
186+
187+ if self . maybe_uninit . get ( ) . contains ( head) {
188+ debug ! (
189+ ?loc,
190+ ?context,
191+ ?local,
192+ ?head,
193+ "local's head is maybe uninit at this location, marking head for storage statement removal"
194+ ) ;
195+ self . storage_to_remove . insert ( head) ;
196+ }
197+ }
198+ }
0 commit comments