112
112
use crate :: transform:: { MirPass , MirSource } ;
113
113
use rustc_index:: { bit_set:: BitSet , vec:: IndexVec } ;
114
114
use rustc_middle:: mir:: tcx:: PlaceTy ;
115
- use rustc_middle:: mir:: visit:: { MutVisitor , PlaceContext , Visitor } ;
115
+ use rustc_middle:: mir:: visit:: { MutVisitor , PlaceContext , Visitor , MutatingUseContext } ;
116
116
use rustc_middle:: mir:: {
117
117
read_only, Body , BodyAndCache , Local , LocalKind , Location , Operand , Place , PlaceElem ,
118
118
ReadOnlyBodyAndCache , Rvalue , Statement , StatementKind , Terminator , TerminatorKind ,
119
+ BasicBlock ,
119
120
} ;
120
121
use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
121
- use smallvec:: SmallVec ;
122
122
use std:: collections:: VecDeque ;
123
123
124
124
pub struct Nrvo ;
@@ -140,11 +140,7 @@ impl<'tcx> MirPass<'tcx> for Nrvo {
140
140
debug ! ( "running on {}" , tcx. def_path_str( source. def_id( ) ) ) ;
141
141
}
142
142
143
- let mut uses_of = uses_of_locals ( body) ;
144
- uses_of. iter_mut ( ) . for_each ( |v| {
145
- v. sort_unstable ( ) ;
146
- v. dedup ( ) ;
147
- } ) ;
143
+ let usage_map = usage_map ( body) ;
148
144
149
145
let mut candidates = find_candidates ( tcx, body) ;
150
146
candidates. retain ( |candidate @ & CandidateAssignment { dest, src, loc } | {
@@ -161,19 +157,15 @@ impl<'tcx> MirPass<'tcx> for Nrvo {
161
157
// uses of `src` by moving backwards through the CFG, and all other uses of `dest` by
162
158
// moving forwards.
163
159
164
- let past_src_uses =
165
- uses_relative_to_location ( src, loc, Direction :: Backward , & read_only ! ( body) ) ;
166
- let future_dest_uses =
167
- uses_relative_to_location ( dest. local , loc, Direction :: Forward , & read_only ! ( body) ) ;
168
-
169
160
debug ! ( "{:?} = {:?} at {:?}" , dest, src, loc) ;
170
- debug ! ( "dest: uses_of[{:?}] = {:?}" , dest. local, uses_of[ dest. local] ) ;
171
- debug ! ( "future_dest_uses = {:?}" , future_dest_uses) ;
172
- debug ! ( "src: uses_of[{:?}] = {:?}" , src, uses_of[ src] ) ;
173
- debug ! ( "past_src_uses = {:?}" , past_src_uses) ;
174
-
175
- if uses_of[ dest. local ] != future_dest_uses || uses_of[ src] != past_src_uses {
176
- debug ! ( "(ineligible)" ) ;
161
+ debug ! ( "usage_map[src] = {:?}" , usage_map[ src] ) ;
162
+ debug ! ( "usage_map[dest.local] = {:?}" , usage_map[ dest. local] ) ;
163
+ if expect_uses_relative_to ( src, loc, Direction :: Backward , & usage_map[ src] , & read_only ! ( body) ) . is_err ( ) {
164
+ debug ! ( "(ineligible, src used after assignment)" ) ;
165
+ return false ;
166
+ }
167
+ if expect_uses_relative_to ( dest. local , loc, Direction :: Forward , & usage_map[ dest. local ] , & read_only ! ( body) ) . is_err ( ) {
168
+ debug ! ( "(ineligible, dest used before assignment)" ) ;
177
169
return false ;
178
170
}
179
171
@@ -281,7 +273,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
281
273
fn visit_place ( & mut self , place : & mut Place < ' tcx > , context : PlaceContext , location : Location ) {
282
274
if let Some ( replacement) = self . map [ place. local ] {
283
275
// Rebase `place`s projections onto `self.with`.
284
- self . place_elem_cache . clear ( ) ; // FIXME faster
276
+ self . place_elem_cache . clear ( ) ;
285
277
self . place_elem_cache . extend ( replacement. projection ) ;
286
278
self . place_elem_cache . extend ( place. projection ) ;
287
279
let projection = self . tcx . intern_place_elems ( & self . place_elem_cache ) ;
@@ -308,38 +300,94 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
308
300
}
309
301
310
302
enum Direction {
303
+ /// All known uses of a local must happen after the assignment.
311
304
Forward ,
305
+ /// All known uses of a local must happen before the assignment.
312
306
Backward ,
313
307
}
314
308
315
- /// Starting at the `Assign` statement at `loc` and moving in `dir`, collects all reachable uses of
316
- /// `local`.
317
- fn uses_relative_to_location (
309
+ fn expect_uses_relative_to (
318
310
local : Local ,
319
311
loc : Location ,
320
312
dir : Direction ,
313
+ used_by : & BitSet < BasicBlock > ,
321
314
body : & ReadOnlyBodyAndCache < ' _ , ' _ > ,
322
- ) -> SmallVec < [ Location ; 1 ] > {
323
- let mut uses = SmallVec :: new ( ) ;
324
- // The assignment at `loc` always contributes a use, regardless of `dir`.
325
- uses. push ( loc) ;
315
+ ) -> Result < ( ) , ( ) > {
316
+ // Traverse CFG in `dir`, collect all reachable blocks that are in `used_by`. If we find all of
317
+ // them (minus the block containing the assignment at `loc`), check that there's no use of
318
+ // `local` in `loc.block` (in opposite direction of `dir`).
319
+
320
+ assert ! ( used_by. contains( loc. block) , "used_by set does not contain block with assignment" ) ;
321
+
322
+ // Checking other blocks is only necessary if there is more than 1 block using `local`.
323
+ // (many locals are generated for temporaries and only get used in a single block, so this
324
+ // optimizes for that case)
325
+ if used_by. count ( ) > 1 {
326
+ let mut seen = BitSet :: new_empty ( body. basic_blocks ( ) . len ( ) ) ;
327
+
328
+ // FIXME: Instead of doing the discovery iteratively, precompute reachability matrices
329
+ let mut work_queue = VecDeque :: new ( ) ;
330
+ let enqueue_next_blocks = |block, queue : & mut VecDeque < _ > | match dir {
331
+ Direction :: Forward => {
332
+ queue. extend ( body. basic_blocks ( ) [ block] . terminator ( ) . successors ( ) . copied ( ) ) ;
333
+ }
334
+ Direction :: Backward => {
335
+ queue. extend ( body. predecessors_for ( block) ) ;
336
+ }
337
+ } ;
326
338
339
+ enqueue_next_blocks ( loc. block , & mut work_queue) ;
340
+
341
+ while let Some ( block) = work_queue. pop_front ( ) {
342
+ if !seen. insert ( block) {
343
+ continue ; // already seen
344
+ }
345
+
346
+ enqueue_next_blocks ( block, & mut work_queue) ;
347
+ }
348
+
349
+ assert ! ( !seen. contains( loc. block) , "should be impossible since the CFG is acyclic" ) ;
350
+
351
+ // Now `seen` contains all reachable blocks in the direction we're interested in. This must be
352
+ // superset of the previously discovered blocks that use `local`, which is `used_by`.
353
+ if !seen. superset ( used_by) {
354
+ return Err ( ( ) ) ;
355
+ }
356
+ }
357
+
358
+ // We haven't checked the contents of `loc.block` itself yet. For that, we just walk in the
359
+ // other direction, in which we don't expect any uses of `local`.
360
+ let dir = match dir {
361
+ Direction :: Backward => Direction :: Forward ,
362
+ Direction :: Forward => Direction :: Backward ,
363
+ } ;
364
+
365
+ let mut found_use = false ;
327
366
let mut collector = UseCollector {
328
- callback : |current_local, location | {
367
+ callback : |current_local, _ | {
329
368
if current_local == local {
330
- uses . push ( location ) ;
369
+ found_use = true ;
331
370
}
332
371
} ,
333
372
} ;
334
- let mut seen = BitSet :: new_empty ( body. basic_blocks ( ) . len ( ) ) ;
335
-
336
- seen. insert ( loc. block ) ;
337
373
338
374
let statements = & body. basic_blocks ( ) [ loc. block ] . statements ;
339
375
340
- // FIXME: Must visit the missing half of the assign stmt too!
341
- // (actually this might not work since `Location` isn't granular enough if there's a use on
342
- // both sides of the assignment)
376
+ // We're interested in uses of `local` basically before or after the `=` sign of the assignment.
377
+ // That mean we have to visit one half of the assign statement here.
378
+ match & statements[ loc. statement_index ] . kind {
379
+ StatementKind :: Assign ( box ( place, rvalue) ) => {
380
+ match dir {
381
+ Direction :: Backward => {
382
+ collector. visit_rvalue ( rvalue, loc) ;
383
+ }
384
+ Direction :: Forward => {
385
+ collector. visit_place ( place, PlaceContext :: MutatingUse ( MutatingUseContext :: Store ) , loc) ;
386
+ }
387
+ }
388
+ }
389
+ _ => bug ! ( "{:?} should be an assignment" , loc) ,
390
+ }
343
391
344
392
// Process statements before/after `loc` in the starting block.
345
393
let stmt_range = match dir {
@@ -357,30 +405,12 @@ fn uses_relative_to_location(
357
405
) ;
358
406
}
359
407
360
- let mut work_queue = VecDeque :: new ( ) ;
361
- let enqueue_next_blocks = |block, queue : & mut VecDeque < _ > | match dir {
362
- Direction :: Forward => {
363
- queue. extend ( body. basic_blocks ( ) [ block] . terminator ( ) . successors ( ) . copied ( ) ) ;
364
- }
365
- Direction :: Backward => {
366
- queue. extend ( body. predecessors_for ( block) ) ;
367
- }
368
- } ;
369
-
370
- enqueue_next_blocks ( loc. block , & mut work_queue) ;
371
-
372
- while let Some ( block) = work_queue. pop_front ( ) {
373
- if !seen. insert ( block) {
374
- continue ; // already seen
375
- }
376
-
377
- collector. visit_basic_block_data ( block, & body. basic_blocks ( ) [ block] ) ;
378
- enqueue_next_blocks ( block, & mut work_queue) ;
408
+ if found_use {
409
+ // Found a use on the "wrong side" of the assignment in `loc.block`.
410
+ Err ( ( ) )
411
+ } else {
412
+ Ok ( ( ) )
379
413
}
380
-
381
- uses. sort_unstable ( ) ;
382
- uses. dedup ( ) ;
383
- uses
384
414
}
385
415
386
416
/// A visitor that invokes a callback when any local is used in a way that's relevant to this
@@ -422,16 +452,19 @@ where
422
452
}
423
453
}
424
454
425
- /// Collects all locations where any local is accessed in any way.
426
- fn uses_of_locals ( body : & Body < ' _ > ) -> IndexVec < Local , SmallVec < [ Location ; 1 ] > > {
427
- let mut uses = IndexVec :: from_elem_n ( SmallVec :: new ( ) , body. local_decls . len ( ) ) ;
455
+ /// A map from `Local`s to sets of `BasicBlock`s that use them.
456
+ type UsageMap = IndexVec < Local , BitSet < BasicBlock > > ;
457
+
458
+ /// Builds a usage map, mapping `Local`s to the `BasicBlock`s using them.
459
+ fn usage_map ( body : & Body < ' _ > ) -> UsageMap {
460
+ let mut map = IndexVec :: from_elem_n ( BitSet :: new_empty ( body. basic_blocks ( ) . len ( ) ) , body. local_decls . len ( ) ) ;
428
461
let mut collector = UseCollector {
429
- callback : |local, location| {
430
- uses [ local] . push ( location) ;
462
+ callback : |local, location : Location | {
463
+ map [ local] . insert ( location. block ) ;
431
464
} ,
432
465
} ;
433
466
collector. visit_body ( body) ;
434
- uses
467
+ map
435
468
}
436
469
437
470
/// A `dest = {move} src;` statement at `loc`.
0 commit comments