1+ use std:: borrow:: Cow ;
2+
13use rustc_index:: IndexSlice ;
24use rustc_index:: bit_set:: DenseBitSet ;
35use rustc_middle:: mir:: visit:: * ;
46use rustc_middle:: mir:: * ;
57use rustc_middle:: ty:: TyCtxt ;
8+ use rustc_mir_dataflow:: impls:: { MaybeStorageDead , always_storage_live_locals} ;
9+ use rustc_mir_dataflow:: { Analysis , ResultsCursor } ;
610use tracing:: { debug, instrument} ;
711
812use crate :: ssa:: SsaLocals ;
@@ -16,7 +20,7 @@ use crate::ssa::SsaLocals;
1620/// _d = move? _c
1721/// where each of the locals is only assigned once.
1822///
19- /// We want to replace all those locals by `_a`, either copied or moved.
23+ /// We want to replace all those locals by `_a` (the "head") , either copied or moved.
2024pub ( super ) struct CopyProp ;
2125
2226impl < ' tcx > crate :: MirPass < ' tcx > for CopyProp {
@@ -34,15 +38,40 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
3438 let fully_moved = fully_moved_locals ( & ssa, body) ;
3539 debug ! ( ?fully_moved) ;
3640
37- let mut storage_to_remove = DenseBitSet :: new_empty ( fully_moved. domain_size ( ) ) ;
41+ let mut head_storage_to_check = DenseBitSet :: new_empty ( fully_moved. domain_size ( ) ) ;
42+
3843 for ( local, & head) in ssa. copy_classes ( ) . iter_enumerated ( ) {
3944 if local != head {
40- storage_to_remove. insert ( head) ;
45+ // We need to determine if we can keep the head's storage statements (which enables better optimizations).
46+ // For every local's usage location, if the head is in `maybe_storage_dead`, we have to remove the storage statements for it.
47+ head_storage_to_check. insert ( head) ;
4148 }
4249 }
4350
4451 let any_replacement = ssa. copy_classes ( ) . iter_enumerated ( ) . any ( |( l, & h) | l != h) ;
4552
53+ let storage_to_remove = if any_replacement {
54+ let always_live_locals = & always_storage_live_locals ( body) ;
55+
56+ let maybe_storage_dead = MaybeStorageDead :: new ( Cow :: Borrowed ( always_live_locals) )
57+ . iterate_to_fixpoint ( tcx, body, None )
58+ . into_results_cursor ( body) ;
59+
60+ let mut storage_checker = StorageChecker {
61+ copy_classes : ssa. copy_classes ( ) ,
62+ maybe_storage_dead,
63+ head_storage_to_check,
64+ storage_to_remove : DenseBitSet :: new_empty ( fully_moved. domain_size ( ) ) ,
65+ } ;
66+
67+ storage_checker. visit_body ( body) ;
68+
69+ storage_checker. storage_to_remove
70+ } else {
71+ // Will be empty anyway.
72+ head_storage_to_check
73+ } ;
74+
4675 Replacer {
4776 tcx,
4877 copy_classes : ssa. copy_classes ( ) ,
@@ -119,6 +148,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
119148 if self . borrowed_locals . contains ( * local) {
120149 return ;
121150 }
151+
122152 match ctxt {
123153 // Do not modify the local in storage statements.
124154 PlaceContext :: NonUse ( NonUseContext :: StorageLive | NonUseContext :: StorageDead ) => { }
@@ -172,3 +202,29 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
172202 }
173203 }
174204}
205+
206+ struct StorageChecker < ' a , ' tcx > {
207+ storage_to_remove : DenseBitSet < Local > ,
208+ head_storage_to_check : DenseBitSet < Local > ,
209+ maybe_storage_dead : ResultsCursor < ' a , ' tcx , MaybeStorageDead < ' a > > ,
210+ copy_classes : & ' a IndexSlice < Local , Local > ,
211+ }
212+
213+ impl < ' a , ' tcx > Visitor < ' tcx > for StorageChecker < ' a , ' tcx > {
214+ fn visit_local ( & mut self , local : Local , context : PlaceContext , location : Location ) {
215+ let head = self . copy_classes [ local] ;
216+
217+ if context. is_use ( ) && self . head_storage_to_check . contains ( head) {
218+ self . maybe_storage_dead . seek_after_primary_effect ( location) ;
219+ if self . maybe_storage_dead . get ( ) . contains ( head) {
220+ debug ! (
221+ ?location,
222+ ?local,
223+ ?head,
224+ "found use of local with head at a location in which head is maybe dead, marking head for storage removal"
225+ ) ;
226+ self . storage_to_remove . insert ( head) ;
227+ }
228+ }
229+ }
230+ }
0 commit comments