use rucc_ir::Block;
use crate::cfg::Cfg;
type Node = u32;
const NONE: Node = Node::MAX;
trait Graph {
fn nodes(&self) -> usize;
fn root(&self) -> Node;
fn preds(&self, node: Node) -> impl Iterator<Item = Node>;
fn succs(&self, node: Node) -> impl Iterator<Item = Node>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Tree {
idom: Vec<Node>,
children: Vec<Vec<Node>>,
enter: Vec<u32>,
leave: Vec<u32>,
root: Node,
}
impl Tree {
fn new(graph: &impl Graph) -> Self {
let nodes = graph.nodes();
let root = graph.root();
let mut order: Vec<Node> = Vec::new();
let mut seen = vec![false; nodes];
seen[root as usize] = true;
let mut stack = vec![(root, graph.succs(root))];
while let Some((node, mut walk)) = stack.pop() {
let step = walk.find(|&next| !seen[next as usize]);
match step {
Some(next) => {
stack.push((node, walk));
seen[next as usize] = true;
stack.push((next, graph.succs(next)));
}
None => order.push(node),
}
}
let rpo: Vec<Node> = order.iter().rev().copied().collect();
let mut rank = vec![NONE; nodes];
for (index, &node) in rpo.iter().enumerate() {
rank[node as usize] = index as u32;
}
let mut preds: Vec<Vec<u32>> = vec![Vec::new(); rpo.len()];
for (index, &node) in rpo.iter().enumerate() {
for pred in graph.preds(node) {
if rank[pred as usize] != NONE {
preds[index].push(rank[pred as usize]);
}
}
}
let mut idom_by_rank = vec![NONE; rpo.len()];
if !rpo.is_empty() {
idom_by_rank[0] = 0;
}
let mut changed = true;
while changed {
changed = false;
for index in 1..rpo.len() {
let mut new = NONE;
for &pred in &preds[index] {
if idom_by_rank[pred as usize] == NONE {
continue;
}
new = if new == NONE { pred } else { meet(&idom_by_rank, new, pred) };
}
if new != NONE && idom_by_rank[index] != new {
idom_by_rank[index] = new;
changed = true;
}
}
}
let mut idom = vec![NONE; nodes];
let mut children: Vec<Vec<Node>> = vec![Vec::new(); nodes];
for (index, &node) in rpo.iter().enumerate() {
let parent = rpo[idom_by_rank[index] as usize];
idom[node as usize] = parent;
if node != root {
children[parent as usize].push(node);
}
}
let mut enter = vec![0; nodes];
let mut leave = vec![0; nodes];
let mut time = 0;
let mut stack = vec![(root, 0usize)];
enter[root as usize] = time;
time += 1;
while let Some((node, next)) = stack.pop() {
match children[node as usize].get(next) {
Some(&child) => {
stack.push((node, next + 1));
enter[child as usize] = time;
time += 1;
stack.push((child, 0));
}
None => leave[node as usize] = time,
}
}
Self { idom, children, enter, leave, root }
}
fn reached(&self, node: Node) -> bool {
self.idom.get(node as usize).is_some_and(|&parent| parent != NONE)
}
fn dominates(&self, of: Node, node: Node) -> bool {
if !self.reached(of) || !self.reached(node) {
return false;
}
let (enter, leave) = (self.enter[of as usize], self.leave[of as usize]);
enter <= self.enter[node as usize] && self.enter[node as usize] < leave
}
fn common(&self, a: Node, b: Node) -> Option<Node> {
if !self.reached(a) || !self.reached(b) {
return None;
}
let mut walk = a;
while !self.dominates(walk, b) {
walk = self.idom[walk as usize];
}
Some(walk)
}
}
fn meet(idom: &[u32], mut a: u32, mut b: u32) -> u32 {
while a != b {
while a > b {
a = idom[a as usize];
}
while b > a {
b = idom[b as usize];
}
}
a
}
struct Forward<'a>(&'a Cfg);
impl Graph for Forward<'_> {
fn nodes(&self) -> usize {
self.0.capacity()
}
fn root(&self) -> Node {
self.0.entry().map_or(0, |block| block.index() as Node)
}
fn preds(&self, node: Node) -> impl Iterator<Item = Node> {
self.0.predecessors(Block::from_usize(node as usize)).iter().map(|b| b.index() as Node)
}
fn succs(&self, node: Node) -> impl Iterator<Item = Node> {
self.0.successors(Block::from_usize(node as usize)).iter().map(|b| b.index() as Node)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Dominators {
tree: Tree,
}
impl Dominators {
#[must_use]
pub fn new(cfg: &Cfg) -> Self {
if cfg.entry().is_none() {
return Self { tree: Tree::empty() };
}
Self { tree: Tree::new(&Forward(cfg)) }
}
#[must_use]
pub fn dominates(&self, of: Block, block: Block) -> bool {
self.tree.dominates(of.index() as Node, block.index() as Node)
}
#[must_use]
pub fn strictly_dominates(&self, of: Block, block: Block) -> bool {
of != block && self.dominates(of, block)
}
#[must_use]
pub fn immediate_dominator(&self, block: Block) -> Option<Block> {
let node = block.index() as Node;
if !self.tree.reached(node) || node == self.tree.root {
return None;
}
Some(Block::from_usize(self.tree.idom[node as usize] as usize))
}
pub fn children(&self, block: Block) -> impl Iterator<Item = Block> + use<'_> {
self.tree
.children
.get(block.index())
.map_or(&[][..], Vec::as_slice)
.iter()
.map(|&node| Block::from_usize(node as usize))
}
#[must_use]
pub fn nearest_common_dominator(&self, a: Block, b: Block) -> Option<Block> {
self.tree
.common(a.index() as Node, b.index() as Node)
.map(|node| Block::from_usize(node as usize))
}
}
struct Reverse {
succs: Vec<Vec<Node>>,
preds: Vec<Vec<Node>>,
exit: Node,
}
impl Graph for Reverse {
fn nodes(&self) -> usize {
self.succs.len()
}
fn root(&self) -> Node {
self.exit
}
fn preds(&self, node: Node) -> impl Iterator<Item = Node> {
self.preds[node as usize].iter().copied()
}
fn succs(&self, node: Node) -> impl Iterator<Item = Node> {
self.succs[node as usize].iter().copied()
}
}
impl Reverse {
fn new(cfg: &Cfg) -> Self {
let exit = cfg.capacity() as Node;
let mut succs: Vec<Vec<Node>> = vec![Vec::new(); cfg.capacity() + 1];
let mut preds: Vec<Vec<Node>> = vec![Vec::new(); cfg.capacity() + 1];
for &block in cfg.postorder() {
let from = block.index() as Node;
for &next in cfg.successors(block) {
succs[next.index()].push(from);
preds[from as usize].push(next.index() as Node);
}
if cfg.successors(block).is_empty() {
succs[exit as usize].push(from);
preds[from as usize].push(exit);
}
}
Self { succs, preds, exit }
}
fn connect(&mut self, cfg: &Cfg) -> Vec<Block> {
let mut arrives = vec![false; self.nodes()];
let mut stack = vec![self.exit];
arrives[self.exit as usize] = true;
let mut fake = Vec::new();
let mut stamp = vec![u32::MAX; self.nodes()];
let mut round = 0;
loop {
while let Some(node) = stack.pop() {
for &next in &self.succs[node as usize] {
if !arrives[next as usize] {
arrives[next as usize] = true;
stack.push(next);
}
}
}
let Some(stranded) = cfg.reverse_postorder().find(|block| !arrives[block.index()])
else {
break;
};
let end = far_end(cfg, stranded, &mut stamp, round).index() as Node;
round += 1;
self.succs[self.exit as usize].push(end);
self.preds[end as usize].push(self.exit);
fake.push(Block::from_usize(end as usize));
arrives[end as usize] = true;
stack.push(end);
}
fake
}
}
fn far_end(cfg: &Cfg, from: Block, stamp: &mut [u32], round: u32) -> Block {
let mut block = from;
loop {
stamp[block.index()] = round;
let next = cfg.successors(block).iter().copied().find(|b| stamp[b.index()] != round);
match next {
Some(next) => block = next,
None => return block,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PostDominators {
tree: Tree,
exit: Node,
fake: Vec<Block>,
}
impl PostDominators {
#[must_use]
pub fn new(cfg: &Cfg) -> Self {
if cfg.entry().is_none() {
return Self { tree: Tree::empty(), exit: 0, fake: Vec::new() };
}
let mut reverse = Reverse::new(cfg);
let fake = reverse.connect(cfg);
let exit = reverse.exit;
let tree = Tree::new(&reverse);
for &block in cfg.postorder() {
assert!(
tree.reached(block.index() as Node),
"a block control reaches has no path to the exit, so post-dominance is undefined"
);
}
Self { tree, exit, fake }
}
#[must_use]
pub fn post_dominates(&self, of: Block, block: Block) -> bool {
self.tree.dominates(of.index() as Node, block.index() as Node)
}
#[must_use]
pub fn strictly_post_dominates(&self, of: Block, block: Block) -> bool {
of != block && self.post_dominates(of, block)
}
#[must_use]
pub fn immediate_post_dominator(&self, block: Block) -> Option<Block> {
let node = block.index() as Node;
if !self.tree.reached(node) {
return None;
}
let parent = self.tree.idom[node as usize];
if parent == self.exit || parent == node {
return None;
}
Some(Block::from_usize(parent as usize))
}
#[must_use]
pub fn fake_exits(&self) -> &[Block] {
&self.fake
}
}
impl Tree {
fn empty() -> Self {
Self {
idom: Vec::new(),
children: Vec::new(),
enter: Vec::new(),
leave: Vec::new(),
root: NONE,
}
}
}
#[cfg(test)]
mod tests {
use rucc_ir::Block;
use crate::cfg::Cfg;
use crate::dom::{Dominators, PostDominators};
use crate::testing::{computed_goto, graph};
fn b(n: usize) -> Block {
Block::from_usize(n)
}
fn idoms(doms: &Dominators, blocks: usize) -> Vec<Option<usize>> {
(0..blocks).map(|n| doms.immediate_dominator(b(n)).map(|d| d.index())).collect()
}
#[test]
fn a_straight_line_is_a_chain() {
let func = graph(&[&[1], &[2], &[]]);
let doms = Dominators::new(&Cfg::new(&func));
assert_eq!(idoms(&doms, 3), [None, Some(0), Some(1)]);
assert!(doms.dominates(b(0), b(2)));
assert!(!doms.dominates(b(2), b(0)));
assert!(doms.dominates(b(1), b(1)));
assert!(!doms.strictly_dominates(b(1), b(1)));
}
#[test]
fn a_diamond_is_dominated_by_the_block_it_came_from() {
let func = graph(&[&[1, 2], &[3], &[3], &[]]);
let doms = Dominators::new(&Cfg::new(&func));
assert_eq!(idoms(&doms, 4), [None, Some(0), Some(0), Some(0)]);
assert!(!doms.dominates(b(1), b(3)));
assert_eq!(doms.nearest_common_dominator(b(1), b(2)), Some(b(0)));
}
#[test]
fn a_loop_header_dominates_its_body_and_its_latch() {
let func = graph(&[&[1], &[2, 3], &[1], &[]]);
let doms = Dominators::new(&Cfg::new(&func));
assert_eq!(idoms(&doms, 4), [None, Some(0), Some(1), Some(1)]);
assert!(doms.dominates(b(1), b(2)));
assert!(!doms.dominates(b(2), b(1)));
}
#[test]
fn neither_entry_of_an_irreducible_loop_dominates_the_other() {
let func = graph(&[&[1, 2], &[2], &[1, 3], &[]]);
let doms = Dominators::new(&Cfg::new(&func));
assert_eq!(idoms(&doms, 4), [None, Some(0), Some(0), Some(2)]);
assert!(!doms.dominates(b(1), b(2)));
assert!(!doms.dominates(b(2), b(1)));
}
#[test]
fn an_unreachable_block_dominates_nothing_and_nothing_dominates_it() {
let func = graph(&[&[1], &[], &[2]]);
let doms = Dominators::new(&Cfg::new(&func));
assert!(!doms.dominates(b(0), b(2)));
assert!(!doms.dominates(b(2), b(2)));
assert!(doms.immediate_dominator(b(2)).is_none());
assert!(doms.nearest_common_dominator(b(0), b(2)).is_none());
}
#[test]
fn a_block_only_a_computed_goto_reaches_is_dominated_by_the_branch() {
let doms = Dominators::new(&Cfg::new(&computed_goto()));
assert_eq!(doms.immediate_dominator(b(2)), Some(b(1)));
}
#[test]
fn a_declaration_answers_no_to_everything() {
let func =
rucc_ir::Func::new(rucc_base::Interner::new().intern("f"), rucc_ir::Signature::new());
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
assert!(!doms.dominates(b(0), b(0)));
assert!(doms.immediate_dominator(b(0)).is_none());
assert_eq!(doms.children(b(0)).count(), 0);
let posts = PostDominators::new(&cfg);
assert!(posts.fake_exits().is_empty());
assert!(!posts.post_dominates(b(0), b(0)));
}
#[test]
fn the_children_of_a_block_are_what_it_immediately_dominates() {
let func = graph(&[&[1, 2], &[3], &[3], &[]]);
let doms = Dominators::new(&Cfg::new(&func));
let mut children: Vec<usize> = doms.children(b(0)).map(|c| c.index()).collect();
children.sort_unstable();
assert_eq!(children, [1, 2, 3]);
assert_eq!(doms.children(b(1)).count(), 0);
}
#[test]
fn a_join_is_post_dominated_by_what_comes_after_it() {
let func = graph(&[&[1, 2], &[3], &[3], &[]]);
let posts = PostDominators::new(&Cfg::new(&func));
assert!(posts.post_dominates(b(3), b(0)));
assert!(!posts.post_dominates(b(1), b(0)));
assert_eq!(posts.immediate_post_dominator(b(1)), Some(b(3)));
assert!(posts.immediate_post_dominator(b(3)).is_none());
assert!(posts.fake_exits().is_empty());
}
#[test]
fn an_infinite_loop_gets_an_edge_to_the_exit_and_says_which() {
let func = graph(&[&[1, 2], &[1], &[]]);
let posts = PostDominators::new(&Cfg::new(&func));
let fake: Vec<usize> = posts.fake_exits().iter().map(|block| block.index()).collect();
assert_eq!(fake, [1]);
assert!(!posts.post_dominates(b(2), b(0)));
assert!(posts.post_dominates(b(1), b(1)));
}
#[test]
fn two_infinite_loops_get_an_edge_each() {
let func = graph(&[&[1, 2, 3], &[1], &[2], &[]]);
let posts = PostDominators::new(&Cfg::new(&func));
let mut fake: Vec<usize> = posts.fake_exits().iter().map(|block| block.index()).collect();
fake.sort_unstable();
assert_eq!(fake, [1, 2]);
assert!(posts.immediate_post_dominator(b(0)).is_none());
}
}