Skip to content

Commit 4695183

Browse files
Precompute blocks using locals
Instead of collecting all uses every time. Should be faster than before, but there's probably still things to improve.
1 parent a763b96 commit 4695183

File tree

1 file changed

+97
-64
lines changed

1 file changed

+97
-64
lines changed

src/librustc_mir/transform/dag_nrvo.rs

+97-64
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@
112112
use crate::transform::{MirPass, MirSource};
113113
use rustc_index::{bit_set::BitSet, vec::IndexVec};
114114
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};
116116
use rustc_middle::mir::{
117117
read_only, Body, BodyAndCache, Local, LocalKind, Location, Operand, Place, PlaceElem,
118118
ReadOnlyBodyAndCache, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
119+
BasicBlock,
119120
};
120121
use rustc_middle::ty::{self, Ty, TyCtxt};
121-
use smallvec::SmallVec;
122122
use std::collections::VecDeque;
123123

124124
pub struct Nrvo;
@@ -140,11 +140,7 @@ impl<'tcx> MirPass<'tcx> for Nrvo {
140140
debug!("running on {}", tcx.def_path_str(source.def_id()));
141141
}
142142

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);
148144

149145
let mut candidates = find_candidates(tcx, body);
150146
candidates.retain(|candidate @ &CandidateAssignment { dest, src, loc }| {
@@ -161,19 +157,15 @@ impl<'tcx> MirPass<'tcx> for Nrvo {
161157
// uses of `src` by moving backwards through the CFG, and all other uses of `dest` by
162158
// moving forwards.
163159

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-
169160
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)");
177169
return false;
178170
}
179171

@@ -281,7 +273,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
281273
fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
282274
if let Some(replacement) = self.map[place.local] {
283275
// Rebase `place`s projections onto `self.with`.
284-
self.place_elem_cache.clear(); // FIXME faster
276+
self.place_elem_cache.clear();
285277
self.place_elem_cache.extend(replacement.projection);
286278
self.place_elem_cache.extend(place.projection);
287279
let projection = self.tcx.intern_place_elems(&self.place_elem_cache);
@@ -308,38 +300,94 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
308300
}
309301

310302
enum Direction {
303+
/// All known uses of a local must happen after the assignment.
311304
Forward,
305+
/// All known uses of a local must happen before the assignment.
312306
Backward,
313307
}
314308

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(
318310
local: Local,
319311
loc: Location,
320312
dir: Direction,
313+
used_by: &BitSet<BasicBlock>,
321314
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+
};
326338

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;
327366
let mut collector = UseCollector {
328-
callback: |current_local, location| {
367+
callback: |current_local, _| {
329368
if current_local == local {
330-
uses.push(location);
369+
found_use = true;
331370
}
332371
},
333372
};
334-
let mut seen = BitSet::new_empty(body.basic_blocks().len());
335-
336-
seen.insert(loc.block);
337373

338374
let statements = &body.basic_blocks()[loc.block].statements;
339375

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+
}
343391

344392
// Process statements before/after `loc` in the starting block.
345393
let stmt_range = match dir {
@@ -357,30 +405,12 @@ fn uses_relative_to_location(
357405
);
358406
}
359407

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(())
379413
}
380-
381-
uses.sort_unstable();
382-
uses.dedup();
383-
uses
384414
}
385415

386416
/// A visitor that invokes a callback when any local is used in a way that's relevant to this
@@ -422,16 +452,19 @@ where
422452
}
423453
}
424454

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());
428461
let mut collector = UseCollector {
429-
callback: |local, location| {
430-
uses[local].push(location);
462+
callback: |local, location: Location| {
463+
map[local].insert(location.block);
431464
},
432465
};
433466
collector.visit_body(body);
434-
uses
467+
map
435468
}
436469

437470
/// A `dest = {move} src;` statement at `loc`.

0 commit comments

Comments
 (0)