11//! See the docs for [`RenameReturnPlace`].
22
33use rustc_hir:: Mutability ;
4- use rustc_index:: bit_set:: HybridBitSet ;
5- use rustc_middle:: mir:: visit:: { MutVisitor , NonUseContext , PlaceContext , Visitor } ;
6- use rustc_middle:: mir:: { self , BasicBlock , Local , Location } ;
4+ use rustc_index:: bit_set:: BitSet ;
5+ use rustc_middle:: mir:: visit:: { MutVisitor , NonMutatingUseContext , PlaceContext , Visitor } ;
6+ use rustc_middle:: mir:: * ;
77use rustc_middle:: ty:: TyCtxt ;
8+ use rustc_mir_dataflow:: impls:: borrowed_locals;
89
910use crate :: MirPass ;
1011
@@ -38,17 +39,17 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
3839 sess. mir_opt_level ( ) > 0 && sess. opts . unstable_opts . unsound_mir_opts
3940 }
4041
41- fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut mir :: Body < ' tcx > ) {
42+ fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
4243 let def_id = body. source . def_id ( ) ;
44+ if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
45+ return ;
46+ }
47+
4348 let Some ( returned_local) = local_eligible_for_nrvo ( body) else {
4449 debug ! ( "`{:?}` was ineligible for NRVO" , def_id) ;
4550 return ;
4651 } ;
4752
48- if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
49- return ;
50- }
51-
5253 debug ! (
5354 "`{:?}` was eligible for NRVO, making {:?} the return place" ,
5455 def_id, returned_local
@@ -58,12 +59,11 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
5859
5960 // Clean up the `NOP`s we inserted for statements made useless by our renaming.
6061 for block_data in body. basic_blocks . as_mut_preserves_cfg ( ) {
61- block_data. statements . retain ( |stmt| stmt. kind != mir :: StatementKind :: Nop ) ;
62+ block_data. statements . retain ( |stmt| stmt. kind != StatementKind :: Nop ) ;
6263 }
6364
6465 // Overwrite the debuginfo of `_0` with that of the renamed local.
65- let ( renamed_decl, ret_decl) =
66- body. local_decls . pick2_mut ( returned_local, mir:: RETURN_PLACE ) ;
66+ let ( renamed_decl, ret_decl) = body. local_decls . pick2_mut ( returned_local, RETURN_PLACE ) ;
6767
6868 // Sometimes, the return place is assigned a local of a different but coercible type, for
6969 // example `&mut T` instead of `&T`. Overwriting the `LocalInfo` for the return place means
@@ -84,26 +84,26 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
8484///
8585/// If the MIR fulfills both these conditions, this function returns the `Local` that is assigned
8686/// to the return place along all possible paths through the control-flow graph.
87- fn local_eligible_for_nrvo ( body : & mut mir :: Body < ' _ > ) -> Option < Local > {
87+ fn local_eligible_for_nrvo ( body : & mut Body < ' _ > ) -> Option < Local > {
8888 if IsReturnPlaceRead :: run ( body) {
8989 return None ;
9090 }
9191
9292 let mut copied_to_return_place = None ;
9393 for block in body. basic_blocks . indices ( ) {
9494 // Look for blocks with a `Return` terminator.
95- if !matches ! ( body[ block] . terminator( ) . kind, mir :: TerminatorKind :: Return ) {
95+ if !matches ! ( body[ block] . terminator( ) . kind, TerminatorKind :: Return ) {
9696 continue ;
9797 }
9898
9999 // Look for an assignment of a single local to the return place prior to the `Return`.
100100 let returned_local = find_local_assigned_to_return_place ( block, body) ?;
101101 match body. local_kind ( returned_local) {
102102 // FIXME: Can we do this for arguments as well?
103- mir :: LocalKind :: Arg => return None ,
103+ LocalKind :: Arg => return None ,
104104
105- mir :: LocalKind :: ReturnPointer => bug ! ( "Return place was assigned to itself?" ) ,
106- mir :: LocalKind :: Temp => { }
105+ LocalKind :: ReturnPointer => bug ! ( "Return place was assigned to itself?" ) ,
106+ LocalKind :: Temp => { }
107107 }
108108
109109 // If multiple different locals are copied to the return place. We can't pick a
@@ -118,20 +118,54 @@ fn local_eligible_for_nrvo(body: &mut mir::Body<'_>) -> Option<Local> {
118118 copied_to_return_place
119119}
120120
121- fn find_local_assigned_to_return_place (
122- start : BasicBlock ,
123- body : & mut mir:: Body < ' _ > ,
124- ) -> Option < Local > {
125- let mut block = start;
126- let mut seen = HybridBitSet :: new_empty ( body. basic_blocks . len ( ) ) ;
121+ #[ instrument( level = "trace" , skip( body) , ret) ]
122+ fn find_local_assigned_to_return_place ( start : BasicBlock , body : & mut Body < ' _ > ) -> Option < Local > {
123+ // The locals that are assigned-to between `return` and `_0 = _rvo_local`.
124+ let mut assigned_locals = BitSet :: new_empty ( body. local_decls . len ( ) ) ;
125+ // Whether we have seen an indirect write.
126+ let mut seen_indirect = false ;
127+
128+ let mut discard_borrowed_locals = |assigned_locals : & mut BitSet < Local > | {
129+ // We have an indirect assignment to a local between the assignment to `_0 = _rvo`
130+ // and `return`. This means we may be modifying the RVO local after the assignment.
131+ // Discard all borrowed locals to be safe.
132+ if !seen_indirect {
133+ assigned_locals. union ( & borrowed_locals ( body) ) ;
134+ // Mark that we have seen an indirect write to avoid recomputing `borrowed_locals`.
135+ seen_indirect = true ;
136+ }
137+ } ;
127138
128139 // Iterate as long as `block` has exactly one predecessor that we have not yet visited.
129- while seen. insert ( block) {
140+ let mut block = start;
141+ let mut seen_blocks = BitSet :: new_empty ( body. basic_blocks . len ( ) ) ;
142+ while seen_blocks. insert ( block) {
130143 trace ! ( "Looking for assignments to `_0` in {:?}" , block) ;
144+ let bbdata = & body. basic_blocks [ block] ;
145+
146+ let mut vis = DiscardWrites { assigned_locals : & mut assigned_locals, seen_indirect : false } ;
147+ vis. visit_terminator ( & bbdata. terminator ( ) , body. terminator_loc ( block) ) ;
148+ if vis. seen_indirect {
149+ discard_borrowed_locals ( & mut assigned_locals) ;
150+ }
151+
152+ for ( statement_index, stmt) in bbdata. statements . iter ( ) . enumerate ( ) . rev ( ) {
153+ if let StatementKind :: Assign ( box ( lhs, ref rhs) ) = stmt. kind
154+ && lhs. as_local ( ) == Some ( RETURN_PLACE )
155+ && let Rvalue :: Use ( rhs) = rhs
156+ && let Some ( rhs) = rhs. place ( )
157+ && let Some ( rhs) = rhs. as_local ( )
158+ && !assigned_locals. contains ( rhs)
159+ {
160+ return Some ( rhs) ;
161+ }
131162
132- let local = body[ block] . statements . iter ( ) . rev ( ) . find_map ( as_local_assigned_to_return_place) ;
133- if local. is_some ( ) {
134- return local;
163+ let mut vis =
164+ DiscardWrites { assigned_locals : & mut assigned_locals, seen_indirect : false } ;
165+ vis. visit_statement ( stmt, Location { block, statement_index } ) ;
166+ if vis. seen_indirect {
167+ discard_borrowed_locals ( & mut assigned_locals) ;
168+ }
135169 }
136170
137171 match body. basic_blocks . predecessors ( ) [ block] . as_slice ( ) {
@@ -145,10 +179,10 @@ fn find_local_assigned_to_return_place(
145179
146180// If this statement is an assignment of an unprojected local to the return place,
147181// return that local.
148- fn as_local_assigned_to_return_place ( stmt : & mir :: Statement < ' _ > ) -> Option < Local > {
149- if let mir :: StatementKind :: Assign ( box ( lhs, rhs) ) = & stmt. kind {
150- if lhs. as_local ( ) == Some ( mir :: RETURN_PLACE ) {
151- if let mir :: Rvalue :: Use ( mir :: Operand :: Copy ( rhs) | mir :: Operand :: Move ( rhs) ) = rhs {
182+ fn as_local_assigned_to_return_place ( stmt : & Statement < ' _ > ) -> Option < Local > {
183+ if let StatementKind :: Assign ( box ( lhs, rhs) ) = & stmt. kind {
184+ if lhs. as_local ( ) == Some ( RETURN_PLACE ) {
185+ if let Rvalue :: Use ( Operand :: Copy ( rhs) | Operand :: Move ( rhs) ) = rhs {
152186 return rhs. as_local ( ) ;
153187 }
154188 }
@@ -168,51 +202,38 @@ impl<'tcx> MutVisitor<'tcx> for RenameToReturnPlace<'tcx> {
168202 self . tcx
169203 }
170204
171- fn visit_statement ( & mut self , stmt : & mut mir :: Statement < ' tcx > , loc : Location ) {
205+ fn visit_statement ( & mut self , stmt : & mut Statement < ' tcx > , loc : Location ) {
172206 // Remove assignments of the local being replaced to the return place, since it is now the
173207 // return place:
174208 // _0 = _1
175209 if as_local_assigned_to_return_place ( stmt) == Some ( self . to_rename ) {
176- stmt. kind = mir :: StatementKind :: Nop ;
210+ stmt. kind = StatementKind :: Nop ;
177211 return ;
178212 }
179213
180214 // Remove storage annotations for the local being replaced:
181215 // StorageLive(_1)
182- if let mir:: StatementKind :: StorageLive ( local) | mir:: StatementKind :: StorageDead ( local) =
183- stmt. kind
184- {
216+ if let StatementKind :: StorageLive ( local) | StatementKind :: StorageDead ( local) = stmt. kind {
185217 if local == self . to_rename {
186- stmt. kind = mir :: StatementKind :: Nop ;
218+ stmt. kind = StatementKind :: Nop ;
187219 return ;
188220 }
189221 }
190222
191223 self . super_statement ( stmt, loc)
192224 }
193225
194- fn visit_terminator ( & mut self , terminator : & mut mir:: Terminator < ' tcx > , loc : Location ) {
195- // Ignore the implicit "use" of the return place in a `Return` statement.
196- if let mir:: TerminatorKind :: Return = terminator. kind {
197- return ;
198- }
199-
200- self . super_terminator ( terminator, loc) ;
201- }
202-
203- fn visit_local ( & mut self , l : & mut Local , ctxt : PlaceContext , _: Location ) {
204- if * l == mir:: RETURN_PLACE {
205- assert_eq ! ( ctxt, PlaceContext :: NonUse ( NonUseContext :: VarDebugInfo ) ) ;
206- } else if * l == self . to_rename {
207- * l = mir:: RETURN_PLACE ;
226+ fn visit_local ( & mut self , l : & mut Local , _: PlaceContext , _: Location ) {
227+ if * l == self . to_rename {
228+ * l = RETURN_PLACE ;
208229 }
209230 }
210231}
211232
212233struct IsReturnPlaceRead ( bool ) ;
213234
214235impl IsReturnPlaceRead {
215- fn run ( body : & mir :: Body < ' _ > ) -> bool {
236+ fn run ( body : & Body < ' _ > ) -> bool {
216237 let mut vis = IsReturnPlaceRead ( false ) ;
217238 vis. visit_body ( body) ;
218239 vis. 0
@@ -221,17 +242,35 @@ impl IsReturnPlaceRead {
221242
222243impl < ' tcx > Visitor < ' tcx > for IsReturnPlaceRead {
223244 fn visit_local ( & mut self , l : Local , ctxt : PlaceContext , _: Location ) {
224- if l == mir :: RETURN_PLACE && ctxt. is_use ( ) && !ctxt. is_place_assignment ( ) {
245+ if l == RETURN_PLACE && ctxt. is_use ( ) && !ctxt. is_place_assignment ( ) {
225246 self . 0 = true ;
226247 }
227248 }
228249
229- fn visit_terminator ( & mut self , terminator : & mir :: Terminator < ' tcx > , loc : Location ) {
250+ fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , loc : Location ) {
230251 // Ignore the implicit "use" of the return place in a `Return` statement.
231- if let mir :: TerminatorKind :: Return = terminator. kind {
252+ if let TerminatorKind :: Return = terminator. kind {
232253 return ;
233254 }
234255
235256 self . super_terminator ( terminator, loc) ;
236257 }
237258}
259+
260+ struct DiscardWrites < ' a > {
261+ assigned_locals : & ' a mut BitSet < Local > ,
262+ seen_indirect : bool ,
263+ }
264+
265+ impl < ' tcx > Visitor < ' tcx > for DiscardWrites < ' _ > {
266+ fn visit_place ( & mut self , place : & Place < ' tcx > , ctxt : PlaceContext , _: Location ) {
267+ match ctxt {
268+ PlaceContext :: MutatingUse ( _)
269+ | PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: Move ) => {
270+ self . seen_indirect |= place. is_indirect_first_projection ( ) ;
271+ self . assigned_locals . insert ( place. local ) ;
272+ }
273+ PlaceContext :: NonMutatingUse ( _) | PlaceContext :: NonUse ( _) => { }
274+ }
275+ }
276+ }
0 commit comments