Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assorted improvements for rustc_middle::mir::traversal #116254

Merged
merged 9 commits into from
Sep 30, 2023
7 changes: 5 additions & 2 deletions compiler/rustc_middle/src/mir/basic_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ impl<'tcx> BasicBlocks<'tcx> {
}

/// Returns basic blocks in a reverse postorder.
///
/// See [`traversal::reverse_postorder`]'s docs to learn what is preorder traversal.
///
/// [`traversal::reverse_postorder`]: crate::mir::traversal::reverse_postorder
#[inline]
pub fn reverse_postorder(&self) -> &[BasicBlock] {
self.cache.reverse_postorder.get_or_init(|| {
let mut rpo: Vec<_> =
Postorder::new(&self.basic_blocks, START_BLOCK).map(|(bb, _)| bb).collect();
let mut rpo: Vec<_> = Postorder::new(&self.basic_blocks, START_BLOCK).collect();
rpo.reverse();
rpo
})
Expand Down
110 changes: 40 additions & 70 deletions compiler/rustc_middle/src/mir/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ impl<'a, 'tcx> Preorder<'a, 'tcx> {
}
}

/// Preorder traversal of a graph.
///
/// This function creates an iterator over the `Body`'s basic blocks, that
/// returns basic blocks in a preorder.
///
/// See [`Preorder`]'s docs to learn what is preorder traversal.
pub fn preorder<'a, 'tcx>(body: &'a Body<'tcx>) -> Preorder<'a, 'tcx> {
Preorder::new(body, START_BLOCK)
}
Expand Down Expand Up @@ -178,7 +184,7 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
// When we yield `C` and call `traverse_successor`, we push `B` to the stack, but
// since we've already visited `E`, that child isn't added to the stack. The last
// two iterations yield `B` and finally `A` for a final traversal of [E, D, C, B, A]
while let Some(&mut (_, ref mut iter)) = self.visit_stack.last_mut() && let Some(bb) = iter.next_back() {
while let Some(bb) = self.visit_stack.last_mut().and_then(|(_, iter)| iter.next_back()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use a let chain ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can drop 0e0dc59 if you want :P

My point for using and_then was to show that iter is only used for next_back and is not used in the body of the loop. IMO with let chain it was a bit harder to read

if self.visited.insert(bb) {
if let Some(term) = &self.basic_blocks[bb].terminator {
self.visit_stack.push((bb, term.successors()));
Expand All @@ -188,16 +194,14 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
}
}

impl<'a, 'tcx> Iterator for Postorder<'a, 'tcx> {
type Item = (BasicBlock, &'a BasicBlockData<'tcx>);
impl<'tcx> Iterator for Postorder<'_, 'tcx> {
type Item = BasicBlock;

fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> {
let next = self.visit_stack.pop();
if next.is_some() {
self.traverse_successor();
}
fn next(&mut self) -> Option<BasicBlock> {
let (bb, _) = self.visit_stack.pop()?;
self.traverse_successor();

next.map(|(bb, _)| (bb, &self.basic_blocks[bb]))
Some(bb)
}

fn size_hint(&self) -> (usize, Option<usize>) {
Expand All @@ -215,18 +219,43 @@ impl<'a, 'tcx> Iterator for Postorder<'a, 'tcx> {
}
}

/// Creates an iterator over the `Body`'s basic blocks, that:
/// Postorder traversal of a graph.
///
/// This function creates an iterator over the `Body`'s basic blocks, that:
/// - returns basic blocks in a postorder,
/// - traverses the `BasicBlocks` CFG cache's reverse postorder backwards, and does not cache the
/// postorder itself.
///
/// See [`Postorder`]'s docs to learn what is postorder traversal.
pub fn postorder<'a, 'tcx>(
body: &'a Body<'tcx>,
) -> impl Iterator<Item = (BasicBlock, &'a BasicBlockData<'tcx>)> + ExactSizeIterator + DoubleEndedIterator
{
reverse_postorder(body).rev()
}

/// Reverse postorder traversal of a graph
/// Returns an iterator over all basic blocks reachable from the `START_BLOCK` in no particular
/// order.
///
/// This is clearer than writing `preorder` in cases where the order doesn't matter.
pub fn reachable<'a, 'tcx>(
body: &'a Body<'tcx>,
) -> impl 'a + Iterator<Item = (BasicBlock, &'a BasicBlockData<'tcx>)> {
preorder(body)
}

/// Returns a `BitSet` containing all basic blocks reachable from the `START_BLOCK`.
pub fn reachable_as_bitset(body: &Body<'_>) -> BitSet<BasicBlock> {
let mut iter = preorder(body);
iter.by_ref().for_each(drop);
iter.visited
}

/// Reverse postorder traversal of a graph.
///
/// This function creates an iterator over the `Body`'s basic blocks, that:
/// - returns basic blocks in a reverse postorder,
/// - makes use of the `BasicBlocks` CFG cache's reverse postorder.
///
/// Reverse postorder is the reverse order of a postorder traversal.
/// This is different to a preorder traversal and represents a natural
Expand All @@ -246,65 +275,6 @@ pub fn postorder<'a, 'tcx>(
/// A reverse postorder traversal of this graph is either `A B C D` or `A C B D`
/// Note that for a graph containing no loops (i.e., A DAG), this is equivalent to
/// a topological sort.
///
/// Construction of a `ReversePostorder` traversal requires doing a full
/// postorder traversal of the graph, therefore this traversal should be
/// constructed as few times as possible. Use the `reset` method to be able
/// to re-use the traversal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is very useful to newcomers that do not know the different types of traversal. Can it be preserved somewhere ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about ef3c5bb?

#[derive(Clone)]
pub struct ReversePostorder<'a, 'tcx> {
body: &'a Body<'tcx>,
blocks: Vec<BasicBlock>,
idx: usize,
}

impl<'a, 'tcx> ReversePostorder<'a, 'tcx> {
pub fn new(body: &'a Body<'tcx>, root: BasicBlock) -> ReversePostorder<'a, 'tcx> {
let blocks: Vec<_> = Postorder::new(&body.basic_blocks, root).map(|(bb, _)| bb).collect();
let len = blocks.len();
ReversePostorder { body, blocks, idx: len }
}
}

impl<'a, 'tcx> Iterator for ReversePostorder<'a, 'tcx> {
type Item = (BasicBlock, &'a BasicBlockData<'tcx>);

fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> {
if self.idx == 0 {
return None;
}
self.idx -= 1;

self.blocks.get(self.idx).map(|&bb| (bb, &self.body[bb]))
}

fn size_hint(&self) -> (usize, Option<usize>) {
(self.idx, Some(self.idx))
}
}

impl<'a, 'tcx> ExactSizeIterator for ReversePostorder<'a, 'tcx> {}

/// Returns an iterator over all basic blocks reachable from the `START_BLOCK` in no particular
/// order.
///
/// This is clearer than writing `preorder` in cases where the order doesn't matter.
pub fn reachable<'a, 'tcx>(
body: &'a Body<'tcx>,
) -> impl 'a + Iterator<Item = (BasicBlock, &'a BasicBlockData<'tcx>)> {
preorder(body)
}

/// Returns a `BitSet` containing all basic blocks reachable from the `START_BLOCK`.
pub fn reachable_as_bitset(body: &Body<'_>) -> BitSet<BasicBlock> {
let mut iter = preorder(body);
(&mut iter).for_each(drop);
iter.visited
}

/// Creates an iterator over the `Body`'s basic blocks, that:
/// - returns basic blocks in a reverse postorder,
/// - makes use of the `BasicBlocks` CFG cache's reverse postorder.
pub fn reverse_postorder<'a, 'tcx>(
body: &'a Body<'tcx>,
) -> impl Iterator<Item = (BasicBlock, &'a BasicBlockData<'tcx>)> + ExactSizeIterator + DoubleEndedIterator
Expand Down
32 changes: 19 additions & 13 deletions src/tools/clippy/clippy_utils/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,26 @@ pub fn visit_local_usage(locals: &[Local], mir: &Body<'_>, location: Location) -
locals.len()
];

traversal::ReversePostorder::new(mir, location.block).try_fold(init, |usage, (tbb, tdata)| {
// Give up on loops
if tdata.terminator().successors().any(|s| s == location.block) {
return None;
}
traversal::Postorder::new(&mir.basic_blocks, location.block)
.collect::<Vec<_>>()
.into_iter()
.rev()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the cached traversal be used here ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not easily I think, this uses a root block setting, but we only cache this for root = START_BLOCK.

.try_fold(init, |usage, tbb| {
let tdata = &mir.basic_blocks[tbb];

// Give up on loops
if tdata.terminator().successors().any(|s| s == location.block) {
return None;
}

let mut v = V {
locals,
location,
results: usage,
};
v.visit_basic_block_data(tbb, tdata);
Some(v.results)
})
let mut v = V {
locals,
location,
results: usage,
};
v.visit_basic_block_data(tbb, tdata);
Some(v.results)
})
}

struct V<'a> {
Expand Down
Loading