-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cranelift: Split out dominator tree's depth-first traversal into a re…
…usable iterator (#8640) We intend to use this when computing liveness of GC references in `cranelift-frontend` to manually construct safepoints and ultimately remove `r{32,64}` reference types from CLIF, `cranelift-codegen`, and `regalloc2`. Co-authored-by: Trevor Elliott <[email protected]>
- Loading branch information
Showing
6 changed files
with
227 additions
and
84 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
//! Traversals over the IR. | ||
|
||
use crate::ir; | ||
use alloc::vec::Vec; | ||
use core::fmt::Debug; | ||
use core::hash::Hash; | ||
use cranelift_entity::EntitySet; | ||
|
||
/// A low-level DFS traversal event: either entering or exiting the traversal of | ||
/// a block. | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
pub enum Event { | ||
/// Entering traversal of a block. | ||
/// | ||
/// Processing a block upon this event corresponds to a pre-order, | ||
/// depth-first traversal. | ||
Enter, | ||
|
||
/// Exiting traversal of a block. | ||
/// | ||
/// Processing a block upon this event corresponds to a post-order, | ||
/// depth-first traversal. | ||
Exit, | ||
} | ||
|
||
/// A depth-first traversal. | ||
/// | ||
/// This is a fairly low-level traversal type, and is generally intended to be | ||
/// used as a building block for making specific pre-order or post-order | ||
/// traversals for whatever problem is at hand. | ||
/// | ||
/// This type may be reused multiple times across different passes or functions | ||
/// and will internally reuse any heap allocations its already made. | ||
/// | ||
/// Traversal is not recursive. | ||
#[derive(Debug, Default, Clone)] | ||
pub struct Dfs { | ||
stack: Vec<(Event, ir::Block)>, | ||
seen: EntitySet<ir::Block>, | ||
} | ||
|
||
impl Dfs { | ||
/// Construct a new depth-first traversal. | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
/// Perform a depth-first traversal over the given function. | ||
/// | ||
/// Yields pairs of `(Event, ir::Block)`. | ||
/// | ||
/// This iterator can be used to perform either pre- or post-order | ||
/// traversals, or a combination of the two. | ||
pub fn iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsIter<'a> { | ||
self.seen.clear(); | ||
self.stack.clear(); | ||
if let Some(e) = func.layout.entry_block() { | ||
self.stack.push((Event::Enter, e)); | ||
} | ||
DfsIter { dfs: self, func } | ||
} | ||
|
||
/// Perform a pre-order traversal over the given function. | ||
/// | ||
/// Yields `ir::Block` items. | ||
pub fn pre_order_iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsPreOrderIter<'a> { | ||
DfsPreOrderIter(self.iter(func)) | ||
} | ||
|
||
/// Perform a post-order traversal over the given function. | ||
/// | ||
/// Yields `ir::Block` items. | ||
pub fn post_order_iter<'a>(&'a mut self, func: &'a ir::Function) -> DfsPostOrderIter<'a> { | ||
DfsPostOrderIter(self.iter(func)) | ||
} | ||
} | ||
|
||
/// An iterator that yields pairs of `(Event, ir::Block)` items as it performs a | ||
/// depth-first traversal over its associated function. | ||
pub struct DfsIter<'a> { | ||
dfs: &'a mut Dfs, | ||
func: &'a ir::Function, | ||
} | ||
|
||
impl Iterator for DfsIter<'_> { | ||
type Item = (Event, ir::Block); | ||
|
||
fn next(&mut self) -> Option<(Event, ir::Block)> { | ||
let (event, block) = self.dfs.stack.pop()?; | ||
|
||
if event == Event::Enter && self.dfs.seen.insert(block) { | ||
self.dfs.stack.push((Event::Exit, block)); | ||
if let Some(inst) = self.func.layout.last_inst(block) { | ||
self.dfs.stack.extend( | ||
self.func.dfg.insts[inst] | ||
.branch_destination(&self.func.dfg.jump_tables) | ||
.iter() | ||
// Heuristic: chase the children in reverse. This puts | ||
// the first successor block first in the postorder, all | ||
// other things being equal, which tends to prioritize | ||
// loop backedges over out-edges, putting the edge-block | ||
// closer to the loop body and minimizing live-ranges in | ||
// linear instruction space. This heuristic doesn't have | ||
// any effect on the computation of dominators, and is | ||
// purely for other consumers of the postorder we cache | ||
// here. | ||
.rev() | ||
.map(|block| block.block(&self.func.dfg.value_lists)) | ||
// This is purely an optimization to avoid additional | ||
// iterations of the loop, and is not required; it's | ||
// merely inlining the check from the outer conditional | ||
// of this case to avoid the extra loop iteration. This | ||
// also avoids potential excess stack growth. | ||
.filter(|block| !self.dfs.seen.contains(*block)) | ||
.map(|block| (Event::Enter, block)), | ||
); | ||
} | ||
} | ||
|
||
Some((event, block)) | ||
} | ||
} | ||
|
||
/// An iterator that yields `ir::Block` items during a depth-first, pre-order | ||
/// traversal over its associated function. | ||
pub struct DfsPreOrderIter<'a>(DfsIter<'a>); | ||
|
||
impl Iterator for DfsPreOrderIter<'_> { | ||
type Item = ir::Block; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
loop { | ||
match self.0.next()? { | ||
(Event::Enter, b) => return Some(b), | ||
(Event::Exit, _) => continue, | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// An iterator that yields `ir::Block` items during a depth-first, post-order | ||
/// traversal over its associated function. | ||
pub struct DfsPostOrderIter<'a>(DfsIter<'a>); | ||
|
||
impl Iterator for DfsPostOrderIter<'_> { | ||
type Item = ir::Block; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
loop { | ||
match self.0.next()? { | ||
(Event::Exit, b) => return Some(b), | ||
(Event::Enter, _) => continue, | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::cursor::{Cursor, FuncCursor}; | ||
use crate::ir::{types::I32, Function, InstBuilder, TrapCode}; | ||
|
||
#[test] | ||
fn test_dfs_traversal() { | ||
let _ = env_logger::try_init(); | ||
|
||
let mut func = Function::new(); | ||
|
||
let block0 = func.dfg.make_block(); | ||
let v0 = func.dfg.append_block_param(block0, I32); | ||
let block1 = func.dfg.make_block(); | ||
let block2 = func.dfg.make_block(); | ||
let block3 = func.dfg.make_block(); | ||
|
||
let mut cur = FuncCursor::new(&mut func); | ||
|
||
// block0(v0): | ||
// br_if v0, block2, trap_block | ||
cur.insert_block(block0); | ||
cur.ins().brif(v0, block2, &[], block3, &[]); | ||
|
||
// block3: | ||
// trap user0 | ||
cur.insert_block(block3); | ||
cur.ins().trap(TrapCode::User(0)); | ||
|
||
// block1: | ||
// v1 = iconst.i32 1 | ||
// v2 = iadd v0, v1 | ||
// jump block0(v2) | ||
cur.insert_block(block1); | ||
let v1 = cur.ins().iconst(I32, 1); | ||
let v2 = cur.ins().iadd(v0, v1); | ||
cur.ins().jump(block0, &[v2]); | ||
|
||
// block2: | ||
// return v0 | ||
cur.insert_block(block2); | ||
cur.ins().return_(&[v0]); | ||
|
||
let mut dfs = Dfs::new(); | ||
|
||
assert_eq!( | ||
dfs.iter(&func).collect::<Vec<_>>(), | ||
vec![ | ||
(Event::Enter, block0), | ||
(Event::Enter, block2), | ||
(Event::Exit, block2), | ||
(Event::Enter, block3), | ||
(Event::Exit, block3), | ||
(Event::Exit, block0) | ||
], | ||
); | ||
} | ||
} |
Oops, something went wrong.