use alloc::vec::Vec;
use crate::{
context::{Context, Ptr},
graph::{
ControlFlowGraph, HasLabel,
dominance::{DomInfo, DomTree},
traversals::region::DFSTraversal,
},
operation::Operation,
pass::{Analysis, AnalysisManager},
printable::{Printable, State, indented_nl},
region::Region,
result::Result,
utils::table::{HMap, ISet},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LoopId(usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LoopTreeNode<N> {
Block(N),
Loop(LoopId),
}
struct Loop<N> {
header: N,
parent: Option<LoopId>,
contents: Vec<LoopTreeNode<N>>,
latches: ISet<N>,
blocks: ISet<N>,
depth: usize,
}
pub struct LoopTree<G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
loops: Vec<Loop<G::Node>>,
loop_order: Vec<LoopId>,
root_contents: Vec<LoopTreeNode<G::Node>>,
block_parent: HMap<G::Node, LoopId>,
}
pub fn compute_loop_tree<G, GraphContext>(
ctx: &GraphContext,
graph: &G,
dom_tree: &DomTree<G, GraphContext>,
) -> LoopTree<G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
assert!(
dom_tree.root() == graph.entry_node(ctx),
"The dominator tree must have the graph entry as its root"
);
let mut intervals = HMap::<G::Node, (usize, usize)>::default();
let mut dom_stack = Vec::new();
if let Some(root) = dom_tree.root() {
dom_stack.push((root, false));
}
let mut clock = 0;
while let Some((node, finished)) = dom_stack.pop() {
if finished {
intervals.get_mut(&node).unwrap().1 = clock;
} else {
intervals.insert(node.clone(), (clock, 0));
clock += 1;
dom_stack.push((node.clone(), true));
dom_stack.extend(dom_tree.children(&node).map(|child| (child, false)));
}
}
let mut tree = LoopTree {
loops: Vec::new(),
loop_order: Vec::new(),
root_contents: Vec::new(),
block_parent: HMap::default(),
};
let dfs = DFSTraversal::new(ctx, graph);
for header in dfs.post_order() {
let &(header_start, header_end) = intervals
.get(&header)
.expect("The dominator tree must contain every reachable block");
let latches: ISet<_> = graph
.predecessors(ctx, &header)
.into_iter()
.filter(|pred| {
let Some(&(pred_start, _)) = intervals.get(pred) else {
return false;
};
header_start <= pred_start && pred_start < header_end
})
.collect();
if !latches.is_empty() {
tree.find_body(ctx, graph, dom_tree, header, latches);
}
}
let nodes: ISet<_> = dfs.reverse_post_order().collect();
let rpo_position = |node: &G::Node| {
nodes
.get_index_of(node)
.expect("Reachable block must have an RPO position")
};
let header_positions: Vec<_> = tree.loops.iter().map(|l| rpo_position(&l.header)).collect();
let child_position = |node: &LoopTreeNode<G::Node>| match node {
LoopTreeNode::Block(block) => rpo_position(block),
LoopTreeNode::Loop(id) => header_positions[id.0],
};
tree.loop_order = (0..tree.loops.len()).map(LoopId).collect();
tree.loop_order.sort_by_key(|id| header_positions[id.0]);
for l in &mut tree.loops {
l.contents.sort_by_key(&child_position);
l.latches.sort_by_key(|node| rpo_position(node));
}
for node in &nodes {
let mut parent = tree.block_loop_parent(node);
if parent.is_none() {
tree.root_contents.push(LoopTreeNode::Block(node.clone()));
}
while let Some(id) = parent {
tree.loops[id.0].blocks.insert(node.clone());
parent = tree.loops[id.0].parent;
}
}
for &id in &tree.loop_order {
if tree.loops[id.0].parent.is_none() {
tree.root_contents.push(LoopTreeNode::Loop(id));
}
let mut depth = 1;
let mut parent = tree.loops[id.0].parent;
while let Some(id) = parent {
depth += 1;
parent = tree.loops[id.0].parent;
}
tree.loops[id.0].depth = depth;
}
tree.root_contents.sort_by_key(child_position);
tree
}
impl<G, GraphContext> LoopTree<G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
fn loop_ancestor(&self, block: G::Node) -> LoopTreeNode<G::Node> {
let Some(mut id) = self.block_loop_parent(&block) else {
return LoopTreeNode::Block(block);
};
while let Some(parent) = self.loops[id.0].parent {
id = parent;
}
LoopTreeNode::Loop(id)
}
fn find_body(
&mut self,
ctx: &GraphContext,
graph: &G,
dom_tree: &DomTree<G, GraphContext>,
header: G::Node,
latches: ISet<G::Node>,
) {
let mut body = ISet::default();
let mut queue = Vec::new();
for latch in &latches {
if *latch != header {
let ancestor = self.loop_ancestor(latch.clone());
if body.insert(ancestor.clone()) {
queue.push(ancestor);
}
}
}
while let Some(node) = queue.pop() {
let block = match node {
LoopTreeNode::Block(block) => block,
LoopTreeNode::Loop(id) => self.loops[id.0].header.clone(),
};
for pred in graph.predecessors(ctx, &block) {
if pred != header && dom_tree.contains(&pred) {
let ancestor = self.loop_ancestor(pred);
if body.insert(ancestor.clone()) {
queue.push(ancestor);
}
}
}
}
body.insert(LoopTreeNode::Block(header.clone()));
let id = LoopId(self.loops.len());
for node in &body {
match node {
LoopTreeNode::Block(block) => {
self.block_parent.insert(block.clone(), id);
}
LoopTreeNode::Loop(child) => {
self.loops[child.0].parent = Some(id);
}
}
}
self.loops.push(Loop {
header: header.clone(),
parent: None,
contents: body.into_iter().collect(),
latches,
blocks: core::iter::once(header).collect(),
depth: 0,
});
}
pub fn loops(&self) -> impl Iterator<Item = LoopId> + Clone + '_ {
self.loop_order.iter().copied()
}
pub fn num_loops(&self) -> usize {
self.loops.len()
}
pub fn top_level_loops(&self) -> impl Iterator<Item = LoopId> + Clone + '_ {
self.loops().filter(|id| self.loop_parent(*id).is_none())
}
pub fn root_contents(&self) -> impl Iterator<Item = LoopTreeNode<G::Node>> + Clone + '_ {
self.root_contents.iter().cloned()
}
pub fn loop_header(&self, id: LoopId) -> G::Node {
self.loops[id.0].header.clone()
}
pub fn loop_parent(&self, id: LoopId) -> Option<LoopId> {
self.loops[id.0].parent
}
pub fn loop_contents(
&self,
id: LoopId,
) -> impl Iterator<Item = LoopTreeNode<G::Node>> + Clone + '_ {
self.loops[id.0].contents.iter().cloned()
}
pub fn subloops(&self, id: LoopId) -> impl Iterator<Item = LoopId> + Clone + '_ {
self.loop_contents(id).filter_map(|node| match node {
LoopTreeNode::Block(_) => None,
LoopTreeNode::Loop(id) => Some(id),
})
}
pub fn blocks(&self, id: LoopId) -> impl Iterator<Item = G::Node> + Clone + '_ {
self.loops[id.0].blocks.iter().cloned()
}
pub fn latches(&self, id: LoopId) -> impl Iterator<Item = G::Node> + Clone + '_ {
self.loops[id.0].latches.iter().cloned()
}
pub fn block_loop_parent(&self, block: &G::Node) -> Option<LoopId> {
self.block_parent.get(block).copied()
}
pub fn contains(&self, id: LoopId, block: &G::Node) -> bool {
self.loops[id.0].blocks.contains(block)
}
pub fn loop_depth(&self, id: LoopId) -> usize {
self.loops[id.0].depth
}
pub fn block_loop_depth(&self, block: &G::Node) -> usize {
self.block_loop_parent(block)
.map_or(0, |id| self.loop_depth(id))
}
pub fn exiting_blocks<'a>(
&'a self,
ctx: &'a GraphContext,
graph: &'a G,
id: LoopId,
) -> impl Iterator<Item = G::Node> + 'a {
self.blocks(id).filter(move |block| {
graph
.successors(ctx, block)
.iter()
.any(|succ| !self.contains(id, succ))
})
}
pub fn exit_blocks(
&self,
ctx: &GraphContext,
graph: &G,
id: LoopId,
) -> impl Iterator<Item = G::Node> {
let mut exits = ISet::default();
for block in self.blocks(id) {
exits.extend(
graph
.successors(ctx, &block)
.into_iter()
.filter(|succ| !self.contains(id, succ)),
);
}
exits.into_iter()
}
}
pub fn print_loop_tree<G, GraphContext>(
ctx: &GraphContext,
tree: &LoopTree<G, GraphContext>,
state: &State,
f: &mut impl core::fmt::Write,
) -> core::fmt::Result
where
G: ControlFlowGraph<GraphContext>,
{
f.write_str("root:")?;
let mut stack = Vec::new();
stack.push((tree.root_contents.iter(), state.indent()));
while let Some((contents, _)) = stack.last_mut() {
let Some(node) = contents.next() else {
stack.pop();
continue;
};
write!(f, "{}", indented_nl(state))?;
match node {
LoopTreeNode::Block(block) => write!(f, "block {}", block.label(ctx))?,
LoopTreeNode::Loop(id) => {
let l = &tree.loops[id.0];
let parent = l
.parent
.map_or_else(|| "root".into(), |id| tree.loop_header(id).label(ctx));
write!(
f,
"loop {} (parent: {}, depth: {})",
l.header.label(ctx),
parent,
l.depth,
)?;
stack.push((l.contents.iter(), state.indent()));
for (name, nodes) in [("latches", &l.latches), ("blocks", &l.blocks)] {
write!(f, "{}{name}: [", indented_nl(state))?;
for (i, node) in nodes.iter().enumerate() {
if i != 0 {
f.write_str(", ")?;
}
f.write_str(&node.label(ctx))?;
}
f.write_str("]")?;
}
}
}
}
Ok(())
}
impl<G> Printable for LoopTree<G, Context>
where
G: ControlFlowGraph<Context>,
{
fn fmt(
&self,
ctx: &Context,
state: &State,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
print_loop_tree(ctx, self, state, f)
}
}
#[derive(Default)]
pub struct LoopInfo(HMap<Ptr<Region>, LoopTree<Ptr<Region>, Context>>);
impl LoopInfo {
pub fn get_loop_tree(
&mut self,
ctx: &Context,
region: Ptr<Region>,
dom_info: &mut DomInfo,
) -> &LoopTree<Ptr<Region>, Context> {
self.0
.entry(region)
.or_insert_with(|| compute_loop_tree(ctx, ®ion, dom_info.get_dom_tree(ctx, region)))
}
}
impl Analysis for LoopInfo {
fn name(&self) -> &'static str {
"loop_info"
}
fn compute(op: Ptr<Operation>, ctx: &Context, analyses: &mut AnalysisManager) -> Result<Self> {
let mut dom_info = analyses.get_analysis_mut::<DomInfo>(op, ctx)?;
let mut loop_info = Self::default();
for region in op.deref(ctx).regions() {
loop_info.get_loop_tree(ctx, region, &mut dom_info);
}
Ok(loop_info)
}
}
#[cfg(test)]
mod tests {
use alloc::{boxed::Box, format, string::String, string::ToString, vec};
use core::fmt::Write as _;
use expect_test::expect;
use super::*;
use crate::graph::dominance::compute_dominator_tree;
struct TestGraph {
successors: Vec<Vec<usize>>,
predecessors: Vec<Vec<usize>>,
}
impl TestGraph {
fn new(successors: Vec<Vec<usize>>) -> Self {
let mut predecessors = vec![Vec::new(); successors.len()];
for (node, succs) in successors.iter().enumerate() {
for &succ in succs {
predecessors[succ].push(node);
}
}
Self {
successors,
predecessors,
}
}
fn loop_tree(&self) -> LoopTree<Self, ()> {
compute_loop_tree(&(), self, &compute_dominator_tree(&(), self))
}
}
impl HasLabel<()> for usize {
fn label(&self, _ctx: &()) -> String {
self.to_string()
}
}
impl ControlFlowGraph<()> for TestGraph {
type Node = usize;
fn num_successors(&self, _ctx: &(), node: &usize) -> usize {
self.successors[*node].len()
}
fn get_successor(&self, _ctx: &(), node: &usize, i: usize) -> usize {
self.successors[*node][i]
}
fn num_predecessors(&self, _ctx: &(), node: &usize) -> usize {
self.predecessors[*node].len()
}
fn get_predecessor(&self, _ctx: &(), node: &usize, i: usize) -> usize {
self.predecessors[*node][i]
}
fn entry_node(&self, _ctx: &()) -> Option<usize> {
(!self.successors.is_empty()).then_some(0)
}
fn nodes<'a>(&'a self, _ctx: &'a ()) -> Box<dyn Iterator<Item = usize> + 'a> {
Box::new(0..self.successors.len())
}
}
fn graph(successors: &[&[usize]]) -> TestGraph {
TestGraph::new(successors.iter().map(|s| s.to_vec()).collect())
}
fn loop_at(tree: &LoopTree<TestGraph, ()>, header: usize) -> LoopId {
tree.loops()
.find(|&id| tree.loop_header(id) == header)
.expect("Expected a loop with this header")
}
fn print_tree<G, GraphContext>(
ctx: &GraphContext,
graph: &G,
tree: &LoopTree<G, GraphContext>,
) -> String
where
G: ControlFlowGraph<GraphContext>,
{
let labels = |nodes: Vec<G::Node>| {
nodes
.iter()
.map(|node| node.label(ctx))
.collect::<Vec<_>>()
.join(", ")
};
let parent_label = |parent: Option<LoopId>| {
parent.map_or_else(|| "root".to_string(), |id| tree.loop_header(id).label(ctx))
};
let mut output = String::new();
writeln!(
output,
"loops ({}): [{}]",
tree.num_loops(),
labels(tree.loops().map(|id| tree.loop_header(id)).collect())
)
.unwrap();
writeln!(
output,
"top-level loops: [{}]",
labels(
tree.top_level_loops()
.map(|id| tree.loop_header(id))
.collect()
)
)
.unwrap();
writeln!(
output,
"block parents: [{}]",
graph
.nodes(ctx)
.map(|node| {
format!(
"{}: {}",
node.label(ctx),
parent_label(tree.block_loop_parent(&node))
)
})
.collect::<Vec<_>>()
.join(", ")
)
.unwrap();
writeln!(
output,
"block depths: [{}]",
graph
.nodes(ctx)
.map(|node| { format!("{}: {}", node.label(ctx), tree.block_loop_depth(&node)) })
.collect::<Vec<_>>()
.join(", ")
)
.unwrap();
print_loop_tree(ctx, tree, &State::default(), &mut output).unwrap();
writeln!(output).unwrap();
for id in tree.loops() {
writeln!(
output,
"loop {}: exiting blocks: [{}]; exit blocks: [{}]",
tree.loop_header(id).label(ctx),
labels(tree.exiting_blocks(ctx, graph, id).collect()),
labels(tree.exit_blocks(ctx, graph, id).collect()),
)
.unwrap();
}
output
}
#[test]
fn empty_and_acyclic_graphs() {
for (g, expected) in [
(
graph(&[]),
expect![[r#"
loops (0): []
top-level loops: []
block parents: []
block depths: []
root:
"#]],
),
(
graph(&[&[]]),
expect![[r#"
loops (0): []
top-level loops: []
block parents: [0: root]
block depths: [0: 0]
root:
block 0
"#]],
),
(
graph(&[&[1, 2], &[3], &[3], &[]]),
expect![[r#"
loops (0): []
top-level loops: []
block parents: [0: root, 1: root, 2: root, 3: root]
block depths: [0: 0, 1: 0, 2: 0, 3: 0]
root:
block 0
block 2
block 1
block 3
"#]],
),
] {
expected.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
}
#[test]
fn self_edge_at_graph_entry() {
let g = graph(&[&[0, 1], &[]]);
expect![[r#"
loops (1): [0]
top-level loops: [0]
block parents: [0: 0, 1: root]
block depths: [0: 1, 1: 0]
root:
loop 0 (parent: root, depth: 1)
latches: [0]
blocks: [0]
block 0
block 1
loop 0: exiting blocks: [0]; exit blocks: [1]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn several_latches_and_duplicate_edges() {
let g = graph(&[&[1], &[1, 2, 3], &[1, 1], &[1]]);
expect![[r#"
loops (1): [1]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 1, 3: 1]
block depths: [0: 0, 1: 1, 2: 1, 3: 1]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [1, 3, 2]
blocks: [1, 3, 2]
block 1
block 3
block 2
loop 1: exiting blocks: []; exit blocks: []
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn nested_loops_use_loop_ancestors() {
let g = graph(&[&[1], &[2, 5], &[3, 4], &[2], &[1], &[]]);
let tree = g.loop_tree();
expect![[r#"
loops (2): [1, 2]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 2, 3: 2, 4: 1, 5: root]
block depths: [0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [4]
blocks: [1, 2, 4, 3]
block 1
loop 2 (parent: 1, depth: 2)
latches: [3]
blocks: [2, 3]
block 2
block 3
block 4
block 5
loop 1: exiting blocks: [1]; exit blocks: [5]
loop 2: exiting blocks: [2]; exit blocks: [4]
"#]]
.assert_eq(&print_tree(&(), &g, &tree));
assert!(tree.contains(loop_at(&tree, 1), &3));
assert!(!tree.contains(loop_at(&tree, 2), &4));
}
#[test]
fn latch_within_a_subloop() {
let g = graph(&[&[1], &[2, 4], &[3], &[2, 1], &[]]);
expect![[r#"
loops (2): [1, 2]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 2, 3: 2, 4: root]
block depths: [0: 0, 1: 1, 2: 2, 3: 2, 4: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [3]
blocks: [1, 2, 3]
block 1
loop 2 (parent: 1, depth: 2)
latches: [3]
blocks: [2, 3]
block 2
block 3
block 4
loop 1: exiting blocks: [1]; exit blocks: [4]
loop 2: exiting blocks: [3]; exit blocks: [1]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn loop_ancestor_crosses_several_parents() {
let g = graph(&[&[1], &[2, 7], &[3, 6], &[4, 5], &[3], &[2], &[1], &[]]);
let tree = g.loop_tree();
expect![[r#"
loops (3): [1, 2, 3]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1, 7: root]
block depths: [0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1, 7: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [6]
blocks: [1, 2, 6, 3, 5, 4]
block 1
loop 2 (parent: 1, depth: 2)
latches: [5]
blocks: [2, 3, 5, 4]
block 2
loop 3 (parent: 2, depth: 3)
latches: [4]
blocks: [3, 4]
block 3
block 4
block 5
block 6
block 7
loop 1: exiting blocks: [1]; exit blocks: [7]
loop 2: exiting blocks: [2]; exit blocks: [6]
loop 3: exiting blocks: [3]; exit blocks: [5]
"#]]
.assert_eq(&print_tree(&(), &g, &tree));
assert_eq!(tree.loop_ancestor(4), LoopTreeNode::Loop(loop_at(&tree, 1)));
}
#[test]
fn sibling_loops_and_public_order() {
let g = graph(&[&[1, 3], &[2, 5], &[1], &[4, 5], &[3], &[]]);
expect![[r#"
loops (2): [3, 1]
top-level loops: [3, 1]
block parents: [0: root, 1: 1, 2: 1, 3: 3, 4: 3, 5: root]
block depths: [0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 0]
root:
block 0
loop 3 (parent: root, depth: 1)
latches: [4]
blocks: [3, 4]
block 3
block 4
loop 1 (parent: root, depth: 1)
latches: [2]
blocks: [1, 2]
block 1
block 2
block 5
loop 3: exiting blocks: [3]; exit blocks: [5]
loop 1: exiting blocks: [1]; exit blocks: [5]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn sibling_subloops_follow_rpo() {
let g = graph(&[&[1], &[2, 4, 6], &[3, 6], &[2], &[5, 6], &[4], &[1, 7], &[]]);
expect![[r#"
loops (3): [1, 4, 2]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 2, 3: 2, 4: 4, 5: 4, 6: 1, 7: root]
block depths: [0: 0, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 1, 7: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [6]
blocks: [1, 4, 5, 2, 6, 3]
block 1
loop 4 (parent: 1, depth: 2)
latches: [5]
blocks: [4, 5]
block 4
block 5
loop 2 (parent: 1, depth: 2)
latches: [3]
blocks: [2, 3]
block 2
block 3
block 6
block 7
loop 1: exiting blocks: [6]; exit blocks: [7]
loop 4: exiting blocks: [4]; exit blocks: [6]
loop 2: exiting blocks: [2]; exit blocks: [6]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn rpo_puts_parents_before_children_despite_graph_order() {
let g = graph(&[&[2], &[3], &[1, 5], &[1, 4], &[2], &[]]);
expect![[r#"
loops (2): [2, 1]
top-level loops: [2]
block parents: [0: root, 1: 1, 2: 2, 3: 1, 4: 2, 5: root]
block depths: [0: 0, 1: 2, 2: 1, 3: 2, 4: 1, 5: 0]
root:
block 0
loop 2 (parent: root, depth: 1)
latches: [4]
blocks: [2, 1, 3, 4]
block 2
loop 1 (parent: 2, depth: 2)
latches: [3]
blocks: [1, 3]
block 1
block 3
block 4
block 5
loop 2: exiting blocks: [2]; exit blocks: [5]
loop 1: exiting blocks: [3]; exit blocks: [4]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn unreachable_predecessors_and_cycles() {
let g = graph(&[&[1], &[2, 3], &[1], &[], &[1, 2, 5], &[4]]);
expect![[r#"
loops (1): [1]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 1, 3: root, 4: root, 5: root]
block depths: [0: 0, 1: 1, 2: 1, 3: 0, 4: 0, 5: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [2]
blocks: [1, 2]
block 1
block 2
block 3
loop 1: exiting blocks: [1]; exit blocks: [3]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn cycles_with_multiple_entries() {
let g = graph(&[&[1, 2], &[2], &[1]]);
expect![[r#"
loops (0): []
top-level loops: []
block parents: [0: root, 1: root, 2: root]
block depths: [0: 0, 1: 0, 2: 0]
root:
block 0
block 1
block 2
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
let g = graph(&[&[1, 2], &[3], &[1], &[1, 2]]);
expect![[r#"
loops (1): [1]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: root, 3: 1]
block depths: [0: 0, 1: 1, 2: 0, 3: 1]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [3]
blocks: [1, 3]
block 1
block 3
block 2
loop 1: exiting blocks: [3]; exit blocks: [2]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn infinite_loop_and_distinct_exits() {
let g = graph(&[&[1], &[2], &[1]]);
expect![[r#"
loops (1): [1]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 1]
block depths: [0: 0, 1: 1, 2: 1]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [2]
blocks: [1, 2]
block 1
block 2
loop 1: exiting blocks: []; exit blocks: []
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
let g = graph(&[&[1], &[2, 3, 3], &[1, 3], &[]]);
expect![[r#"
loops (1): [1]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 1, 3: root]
block depths: [0: 0, 1: 1, 2: 1, 3: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [2]
blocks: [1, 2]
block 1
block 2
block 3
loop 1: exiting blocks: [1, 2]; exit blocks: [3]
"#]]
.assert_eq(&print_tree(&(), &g, &g.loop_tree()));
}
#[test]
fn predecessor_order_does_not_change_the_tree() {
let mut g = graph(&[&[1], &[2, 5], &[3, 4], &[2, 1], &[2, 1], &[]]);
let expected = expect![[r#"
loops (2): [1, 2]
top-level loops: [1]
block parents: [0: root, 1: 1, 2: 2, 3: 2, 4: 2, 5: root]
block depths: [0: 0, 1: 1, 2: 2, 3: 2, 4: 2, 5: 0]
root:
block 0
loop 1 (parent: root, depth: 1)
latches: [4, 3]
blocks: [1, 2, 4, 3]
block 1
loop 2 (parent: 1, depth: 2)
latches: [4, 3]
blocks: [2, 4, 3]
block 2
block 4
block 3
block 5
loop 1: exiting blocks: [1]; exit blocks: [5]
loop 2: exiting blocks: [4, 3]; exit blocks: [1]
"#]];
let first = print_tree(&(), &g, &g.loop_tree());
for preds in &mut g.predecessors {
preds.reverse();
}
assert_eq!(first, print_tree(&(), &g, &g.loop_tree()));
expected.assert_eq(&first);
}
fn reachable_from(g: &TestGraph, start: usize, allowed: usize) -> usize {
let mut seen = 0;
let mut queue = vec![start];
while let Some(node) = queue.pop() {
let bit = 1 << node;
if allowed & bit == 0 || seen & bit != 0 {
continue;
}
seen |= bit;
queue.extend(g.successors[node].iter().copied());
}
seen
}
fn check_rpo_order(g: &TestGraph, tree: &LoopTree<TestGraph, ()>) {
let dfs = DFSTraversal::new(&(), g);
let rpo_position = |node| dfs.reverse_post_order_number(&node);
let child_position = |node| match node {
LoopTreeNode::Block(block) => rpo_position(block),
LoopTreeNode::Loop(id) => rpo_position(tree.loop_header(id)),
};
let loops: ISet<_> = tree.loops().collect();
assert!(
loops
.iter()
.map(|&id| rpo_position(tree.loop_header(id)))
.is_sorted()
);
assert!(tree.root_contents().map(&child_position).is_sorted());
for &id in &loops {
assert_eq!(tree.blocks(id).next(), Some(tree.loop_header(id)));
assert!(tree.blocks(id).map(&rpo_position).is_sorted());
assert!(tree.loop_contents(id).map(&child_position).is_sorted());
assert!(tree.latches(id).map(&rpo_position).is_sorted());
for child in tree.subloops(id) {
assert!(loops.get_index_of(&id).unwrap() < loops.get_index_of(&child).unwrap());
}
}
}
fn check_with_reachability(g: &TestGraph) {
let tree = g.loop_tree();
let all = (1 << g.successors.len()) - 1;
let reachable = reachable_from(g, 0, all);
let mut expected = Vec::new();
for header in 0..g.successors.len() {
if reachable & (1 << header) == 0 {
continue;
}
let dominated = reachable & !reachable_from(g, 0, all & !(1 << header));
let forward = reachable_from(g, header, dominated);
let body: Vec<_> = (0..g.successors.len())
.filter(|&node| {
forward & (1 << node) != 0
&& reachable_from(g, node, dominated) & (1 << header) != 0
})
.collect();
if body.len() == 1 && !g.successors[header].contains(&header) {
continue;
}
expected.push(header);
let id = loop_at(&tree, header);
let mut actual: Vec<_> = tree.blocks(id).collect();
actual.sort_unstable();
assert_eq!(actual, body, "Graph {:?}, header {header}", g.successors);
let latches: Vec<_> = body
.iter()
.copied()
.filter(|&node| g.successors[node].contains(&header))
.collect();
let mut actual_latches: Vec<_> = tree.latches(id).collect();
actual_latches.sort_unstable();
assert_eq!(actual_latches, latches);
}
let mut actual_headers: Vec<_> = tree.loops().map(|id| tree.loop_header(id)).collect();
actual_headers.sort_unstable();
assert_eq!(actual_headers, expected);
let mut leaves = Vec::new();
let mut queue: Vec<_> = tree.root_contents().collect();
while let Some(node) = queue.pop() {
match node {
LoopTreeNode::Block(block) => leaves.push(block),
LoopTreeNode::Loop(id) => queue.extend(tree.loop_contents(id)),
}
}
leaves.sort_unstable();
assert_eq!(
leaves,
(0..g.successors.len())
.filter(|node| reachable & (1 << node) != 0)
.collect::<Vec<_>>()
);
for id in tree.loops() {
for child in tree.subloops(id) {
assert_eq!(tree.loop_parent(child), Some(id));
assert_eq!(tree.loop_depth(child), tree.loop_depth(id) + 1);
}
}
check_rpo_order(g, &tree);
}
#[test]
fn all_three_block_graphs_match_reachability() {
for edges in 0usize..(1 << 9) {
let successors = (0..3)
.map(|node| {
(0..3)
.filter(|succ| edges & (1 << (node * 3 + succ)) != 0)
.collect()
})
.collect();
check_with_reachability(&TestGraph::new(successors));
}
}
#[test]
fn four_block_graphs_match_reachability() {
for i in 0usize..1024 {
let edges = i.wrapping_mul(40503) & 0xffff;
let successors = (0..4)
.map(|node| {
(0..4)
.filter(|succ| edges & (1 << (node * 4 + succ)) != 0)
.collect()
})
.collect();
check_with_reachability(&TestGraph::new(successors));
}
}
#[test]
fn sampled_larger_graphs_match_reachability() {
let mut state = 0x4d59_5df4_d0f3_3173u64;
for size in [6, 7] {
for sample in 0..1024 {
let mut successors = vec![Vec::new(); size];
for (node, succs) in successors.iter_mut().enumerate() {
for succ in 0..size {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
if state & 7 < 1 + sample % 4 || (sample % 2 == 0 && succ == node + 1) {
succs.push(succ);
}
}
}
check_with_reachability(&TestGraph::new(successors));
}
}
check_with_reachability(&graph(&[
&[1, 6],
&[2],
&[3, 5],
&[4],
&[3, 2],
&[1, 6],
&[1],
]));
}
}