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
@@ -38,17 +39,17 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
38
39
sess. mir_opt_level ( ) > 0 && sess. opts . unstable_opts . unsound_mir_opts
39
40
}
40
41
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 > ) {
42
43
let def_id = body. source . def_id ( ) ;
44
+ if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
45
+ return ;
46
+ }
47
+
43
48
let Some ( returned_local) = local_eligible_for_nrvo ( body) else {
44
49
debug ! ( "`{:?}` was ineligible for NRVO" , def_id) ;
45
50
return ;
46
51
} ;
47
52
48
- if !tcx. consider_optimizing ( || format ! ( "RenameReturnPlace {def_id:?}" ) ) {
49
- return ;
50
- }
51
-
52
53
debug ! (
53
54
"`{:?}` was eligible for NRVO, making {:?} the return place" ,
54
55
def_id, returned_local
@@ -58,12 +59,11 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
58
59
59
60
// Clean up the `NOP`s we inserted for statements made useless by our renaming.
60
61
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 ) ;
62
63
}
63
64
64
65
// 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 ) ;
67
67
68
68
// Sometimes, the return place is assigned a local of a different but coercible type, for
69
69
// example `&mut T` instead of `&T`. Overwriting the `LocalInfo` for the return place means
@@ -84,26 +84,26 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
84
84
///
85
85
/// If the MIR fulfills both these conditions, this function returns the `Local` that is assigned
86
86
/// 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 > {
88
88
if IsReturnPlaceRead :: run ( body) {
89
89
return None ;
90
90
}
91
91
92
92
let mut copied_to_return_place = None ;
93
93
for block in body. basic_blocks . indices ( ) {
94
94
// 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 ) {
96
96
continue ;
97
97
}
98
98
99
99
// Look for an assignment of a single local to the return place prior to the `Return`.
100
100
let returned_local = find_local_assigned_to_return_place ( block, body) ?;
101
101
match body. local_kind ( returned_local) {
102
102
// FIXME: Can we do this for arguments as well?
103
- mir :: LocalKind :: Arg => return None ,
103
+ LocalKind :: Arg => return None ,
104
104
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 => { }
107
107
}
108
108
109
109
// 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> {
118
118
copied_to_return_place
119
119
}
120
120
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
+ } ;
127
138
128
139
// 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) {
130
143
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
+ }
131
162
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
+ }
135
169
}
136
170
137
171
match body. basic_blocks . predecessors ( ) [ block] . as_slice ( ) {
@@ -145,10 +179,10 @@ fn find_local_assigned_to_return_place(
145
179
146
180
// If this statement is an assignment of an unprojected local to the return place,
147
181
// 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 {
152
186
return rhs. as_local ( ) ;
153
187
}
154
188
}
@@ -168,51 +202,38 @@ impl<'tcx> MutVisitor<'tcx> for RenameToReturnPlace<'tcx> {
168
202
self . tcx
169
203
}
170
204
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 ) {
172
206
// Remove assignments of the local being replaced to the return place, since it is now the
173
207
// return place:
174
208
// _0 = _1
175
209
if as_local_assigned_to_return_place ( stmt) == Some ( self . to_rename ) {
176
- stmt. kind = mir :: StatementKind :: Nop ;
210
+ stmt. kind = StatementKind :: Nop ;
177
211
return ;
178
212
}
179
213
180
214
// Remove storage annotations for the local being replaced:
181
215
// 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 {
185
217
if local == self . to_rename {
186
- stmt. kind = mir :: StatementKind :: Nop ;
218
+ stmt. kind = StatementKind :: Nop ;
187
219
return ;
188
220
}
189
221
}
190
222
191
223
self . super_statement ( stmt, loc)
192
224
}
193
225
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 ;
208
229
}
209
230
}
210
231
}
211
232
212
233
struct IsReturnPlaceRead ( bool ) ;
213
234
214
235
impl IsReturnPlaceRead {
215
- fn run ( body : & mir :: Body < ' _ > ) -> bool {
236
+ fn run ( body : & Body < ' _ > ) -> bool {
216
237
let mut vis = IsReturnPlaceRead ( false ) ;
217
238
vis. visit_body ( body) ;
218
239
vis. 0
@@ -221,17 +242,35 @@ impl IsReturnPlaceRead {
221
242
222
243
impl < ' tcx > Visitor < ' tcx > for IsReturnPlaceRead {
223
244
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 ( ) {
225
246
self . 0 = true ;
226
247
}
227
248
}
228
249
229
- fn visit_terminator ( & mut self , terminator : & mir :: Terminator < ' tcx > , loc : Location ) {
250
+ fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , loc : Location ) {
230
251
// 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 {
232
253
return ;
233
254
}
234
255
235
256
self . super_terminator ( terminator, loc) ;
236
257
}
237
258
}
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