1
1
//! See the docs for [`RenameReturnPlace`].
2
2
3
3
use 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:: * ;
7
7
use rustc_middle:: ty:: TyCtxt ;
8
+ use rustc_mir_dataflow:: impls:: borrowed_locals;
8
9
9
10
use crate :: MirPass ;
10
11
@@ -34,21 +35,20 @@ pub struct RenameReturnPlace;
34
35
35
36
impl < ' tcx > MirPass < ' tcx > for RenameReturnPlace {
36
37
fn is_enabled ( & self , sess : & rustc_session:: Session ) -> bool {
37
- // #111005
38
- sess. mir_opt_level ( ) > 0 && sess. opts . unstable_opts . unsound_mir_opts
38
+ sess. mir_opt_level ( ) > 0
39
39
}
40
40
41
- fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut mir :: Body < ' tcx > ) {
41
+ fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
42
42
let def_id = body. source . def_id ( ) ;
43
+ if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
44
+ return ;
45
+ }
46
+
43
47
let Some ( returned_local) = local_eligible_for_nrvo ( body) else {
44
48
debug ! ( "`{:?}` was ineligible for NRVO" , def_id) ;
45
49
return ;
46
50
} ;
47
51
48
- if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
49
- return ;
50
- }
51
-
52
52
debug ! (
53
53
"`{:?}` was eligible for NRVO, making {:?} the return place" ,
54
54
def_id, returned_local
@@ -58,12 +58,11 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
58
58
59
59
// Clean up the `NOP`s we inserted for statements made useless by our renaming.
60
60
for block_data in body. basic_blocks . as_mut_preserves_cfg ( ) {
61
- block_data. statements . retain ( |stmt| stmt. kind != mir :: StatementKind :: Nop ) ;
61
+ block_data. statements . retain ( |stmt| stmt. kind != StatementKind :: Nop ) ;
62
62
}
63
63
64
64
// 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 ) ;
65
+ let ( renamed_decl, ret_decl) = body. local_decls . pick2_mut ( returned_local, RETURN_PLACE ) ;
67
66
68
67
// Sometimes, the return place is assigned a local of a different but coercible type, for
69
68
// example `&mut T` instead of `&T`. Overwriting the `LocalInfo` for the return place means
@@ -84,26 +83,26 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
84
83
///
85
84
/// If the MIR fulfills both these conditions, this function returns the `Local` that is assigned
86
85
/// 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 > {
86
+ fn local_eligible_for_nrvo ( body : & mut Body < ' _ > ) -> Option < Local > {
88
87
if IsReturnPlaceRead :: run ( body) {
89
88
return None ;
90
89
}
91
90
92
91
let mut copied_to_return_place = None ;
93
92
for block in body. basic_blocks . indices ( ) {
94
93
// Look for blocks with a `Return` terminator.
95
- if !matches ! ( body[ block] . terminator( ) . kind, mir :: TerminatorKind :: Return ) {
94
+ if !matches ! ( body[ block] . terminator( ) . kind, TerminatorKind :: Return ) {
96
95
continue ;
97
96
}
98
97
99
98
// Look for an assignment of a single local to the return place prior to the `Return`.
100
99
let returned_local = find_local_assigned_to_return_place ( block, body) ?;
101
100
match body. local_kind ( returned_local) {
102
101
// FIXME: Can we do this for arguments as well?
103
- mir :: LocalKind :: Arg => return None ,
102
+ LocalKind :: Arg => return None ,
104
103
105
- mir :: LocalKind :: ReturnPointer => bug ! ( "Return place was assigned to itself?" ) ,
106
- mir :: LocalKind :: Temp => { }
104
+ LocalKind :: ReturnPointer => bug ! ( "Return place was assigned to itself?" ) ,
105
+ LocalKind :: Temp => { }
107
106
}
108
107
109
108
// If multiple different locals are copied to the return place. We can't pick a
@@ -118,20 +117,54 @@ fn local_eligible_for_nrvo(body: &mut mir::Body<'_>) -> Option<Local> {
118
117
copied_to_return_place
119
118
}
120
119
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 ( ) ) ;
120
+ #[ instrument( level = "trace" , skip( body) , ret) ]
121
+ fn find_local_assigned_to_return_place ( start : BasicBlock , body : & mut Body < ' _ > ) -> Option < Local > {
122
+ // The locals that are assigned-to between `return` and `_0 = _rvo_local`.
123
+ let mut assigned_locals = BitSet :: new_empty ( body. local_decls . len ( ) ) ;
124
+ // Whether we have seen an indirect write.
125
+ let mut seen_indirect = false ;
126
+
127
+ let mut discard_borrowed_locals = |assigned_locals : & mut BitSet < Local > | {
128
+ // We have an indirect assignment to a local between the assignment to `_0 = _rvo`
129
+ // and `return`. This means we may be modifying the RVO local after the assignment.
130
+ // Discard all borrowed locals to be safe.
131
+ if !seen_indirect {
132
+ assigned_locals. union ( & borrowed_locals ( body) ) ;
133
+ // Mark that we have seen an indirect write to avoid recomputing `borrowed_locals`.
134
+ seen_indirect = true ;
135
+ }
136
+ } ;
127
137
128
138
// Iterate as long as `block` has exactly one predecessor that we have not yet visited.
129
- while seen. insert ( block) {
139
+ let mut block = start;
140
+ let mut seen_blocks = BitSet :: new_empty ( body. basic_blocks . len ( ) ) ;
141
+ while seen_blocks. insert ( block) {
130
142
trace ! ( "Looking for assignments to `_0` in {:?}" , block) ;
143
+ let bbdata = & body. basic_blocks [ block] ;
144
+
145
+ let mut vis = DiscardWrites { assigned_locals : & mut assigned_locals, seen_indirect : false } ;
146
+ vis. visit_terminator ( & bbdata. terminator ( ) , body. terminator_loc ( block) ) ;
147
+ if vis. seen_indirect {
148
+ discard_borrowed_locals ( & mut assigned_locals) ;
149
+ }
150
+
151
+ for ( statement_index, stmt) in bbdata. statements . iter ( ) . enumerate ( ) . rev ( ) {
152
+ if let StatementKind :: Assign ( box ( lhs, ref rhs) ) = stmt. kind
153
+ && lhs. as_local ( ) == Some ( RETURN_PLACE )
154
+ && let Rvalue :: Use ( rhs) = rhs
155
+ && let Some ( rhs) = rhs. place ( )
156
+ && let Some ( rhs) = rhs. as_local ( )
157
+ && !assigned_locals. contains ( rhs)
158
+ {
159
+ return Some ( rhs) ;
160
+ }
131
161
132
- let local = body[ block] . statements . iter ( ) . rev ( ) . find_map ( as_local_assigned_to_return_place) ;
133
- if local. is_some ( ) {
134
- return local;
162
+ let mut vis =
163
+ DiscardWrites { assigned_locals : & mut assigned_locals, seen_indirect : false } ;
164
+ vis. visit_statement ( stmt, Location { block, statement_index } ) ;
165
+ if vis. seen_indirect {
166
+ discard_borrowed_locals ( & mut assigned_locals) ;
167
+ }
135
168
}
136
169
137
170
match body. basic_blocks . predecessors ( ) [ block] . as_slice ( ) {
@@ -145,10 +178,10 @@ fn find_local_assigned_to_return_place(
145
178
146
179
// If this statement is an assignment of an unprojected local to the return place,
147
180
// 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 {
181
+ fn as_local_assigned_to_return_place ( stmt : & Statement < ' _ > ) -> Option < Local > {
182
+ if let StatementKind :: Assign ( box ( lhs, rhs) ) = & stmt. kind {
183
+ if lhs. as_local ( ) == Some ( RETURN_PLACE ) {
184
+ if let Rvalue :: Use ( Operand :: Copy ( rhs) | Operand :: Move ( rhs) ) = rhs {
152
185
return rhs. as_local ( ) ;
153
186
}
154
187
}
@@ -168,51 +201,38 @@ impl<'tcx> MutVisitor<'tcx> for RenameToReturnPlace<'tcx> {
168
201
self . tcx
169
202
}
170
203
171
- fn visit_statement ( & mut self , stmt : & mut mir :: Statement < ' tcx > , loc : Location ) {
204
+ fn visit_statement ( & mut self , stmt : & mut Statement < ' tcx > , loc : Location ) {
172
205
// Remove assignments of the local being replaced to the return place, since it is now the
173
206
// return place:
174
207
// _0 = _1
175
208
if as_local_assigned_to_return_place ( stmt) == Some ( self . to_rename ) {
176
- stmt. kind = mir :: StatementKind :: Nop ;
209
+ stmt. kind = StatementKind :: Nop ;
177
210
return ;
178
211
}
179
212
180
213
// Remove storage annotations for the local being replaced:
181
214
// StorageLive(_1)
182
- if let mir:: StatementKind :: StorageLive ( local) | mir:: StatementKind :: StorageDead ( local) =
183
- stmt. kind
184
- {
215
+ if let StatementKind :: StorageLive ( local) | StatementKind :: StorageDead ( local) = stmt. kind {
185
216
if local == self . to_rename {
186
- stmt. kind = mir :: StatementKind :: Nop ;
217
+ stmt. kind = StatementKind :: Nop ;
187
218
return ;
188
219
}
189
220
}
190
221
191
222
self . super_statement ( stmt, loc)
192
223
}
193
224
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 ;
225
+ fn visit_local ( & mut self , l : & mut Local , _: PlaceContext , _: Location ) {
226
+ if * l == self . to_rename {
227
+ * l = RETURN_PLACE ;
208
228
}
209
229
}
210
230
}
211
231
212
232
struct IsReturnPlaceRead ( bool ) ;
213
233
214
234
impl IsReturnPlaceRead {
215
- fn run ( body : & mir :: Body < ' _ > ) -> bool {
235
+ fn run ( body : & Body < ' _ > ) -> bool {
216
236
let mut vis = IsReturnPlaceRead ( false ) ;
217
237
vis. visit_body ( body) ;
218
238
vis. 0
@@ -221,17 +241,35 @@ impl IsReturnPlaceRead {
221
241
222
242
impl < ' tcx > Visitor < ' tcx > for IsReturnPlaceRead {
223
243
fn visit_local ( & mut self , l : Local , ctxt : PlaceContext , _: Location ) {
224
- if l == mir :: RETURN_PLACE && ctxt. is_use ( ) && !ctxt. is_place_assignment ( ) {
244
+ if l == RETURN_PLACE && ctxt. is_use ( ) && !ctxt. is_place_assignment ( ) {
225
245
self . 0 = true ;
226
246
}
227
247
}
228
248
229
- fn visit_terminator ( & mut self , terminator : & mir :: Terminator < ' tcx > , loc : Location ) {
249
+ fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , loc : Location ) {
230
250
// Ignore the implicit "use" of the return place in a `Return` statement.
231
- if let mir :: TerminatorKind :: Return = terminator. kind {
251
+ if let TerminatorKind :: Return = terminator. kind {
232
252
return ;
233
253
}
234
254
235
255
self . super_terminator ( terminator, loc) ;
236
256
}
237
257
}
258
+
259
+ struct DiscardWrites < ' a > {
260
+ assigned_locals : & ' a mut BitSet < Local > ,
261
+ seen_indirect : bool ,
262
+ }
263
+
264
+ impl < ' tcx > Visitor < ' tcx > for DiscardWrites < ' _ > {
265
+ fn visit_place ( & mut self , place : & Place < ' tcx > , ctxt : PlaceContext , _: Location ) {
266
+ match ctxt {
267
+ PlaceContext :: MutatingUse ( _)
268
+ | PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: Move ) => {
269
+ self . seen_indirect |= place. is_indirect_first_projection ( ) ;
270
+ self . assigned_locals . insert ( place. local ) ;
271
+ }
272
+ PlaceContext :: NonMutatingUse ( _) | PlaceContext :: NonUse ( _) => { }
273
+ }
274
+ }
275
+ }
0 commit comments