use rucc_ir::{Block, Func};
#[derive(Clone, Debug)]
pub struct Cfg {
succs: Vec<Vec<Block>>,
preds: Vec<Vec<Block>>,
postorder: Vec<Block>,
rank: Vec<Option<u32>>,
entry: Option<Block>,
}
impl Cfg {
#[must_use]
pub fn new(func: &Func) -> Self {
let counts = func.counts();
let mut succs: Vec<Vec<Block>> = vec![Vec::new(); counts.blocks];
let mut preds: Vec<Vec<Block>> = vec![Vec::new(); counts.blocks];
let mut stamp = vec![usize::MAX; counts.blocks];
for block in func.blocks() {
let Some(term) = func.terminator(block) else { continue };
for call in func.successors(term) {
if stamp[call.block.index()] == block.index() {
continue;
}
stamp[call.block.index()] = block.index();
succs[block.index()].push(call.block);
preds[call.block.index()].push(block);
}
}
let entry = func.entry();
let postorder = match entry {
Some(entry) => postorder(&succs, entry, counts.blocks),
None => Vec::new(),
};
let mut rank = vec![None; counts.blocks];
for (index, &block) in postorder.iter().rev().enumerate() {
rank[block.index()] = Some(index as u32);
}
Self { succs, preds, postorder, rank, entry }
}
#[must_use]
pub fn entry(&self) -> Option<Block> {
self.entry
}
#[must_use]
pub fn successors(&self, block: Block) -> &[Block] {
&self.succs[block.index()]
}
#[must_use]
pub fn predecessors(&self, block: Block) -> &[Block] {
&self.preds[block.index()]
}
#[must_use]
pub fn postorder(&self) -> &[Block] {
&self.postorder
}
pub fn reverse_postorder(&self) -> impl DoubleEndedIterator<Item = Block> + use<'_> {
self.postorder.iter().rev().copied()
}
#[must_use]
pub fn rank(&self, block: Block) -> Option<u32> {
self.rank[block.index()]
}
#[must_use]
pub fn reaches(&self, block: Block) -> bool {
self.rank[block.index()].is_some()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.rank.len()
}
}
fn postorder(succs: &[Vec<Block>], entry: Block, blocks: usize) -> Vec<Block> {
let mut order = Vec::new();
let mut seen = vec![false; blocks];
let mut stack = vec![(entry, 0usize)];
seen[entry.index()] = true;
while let Some((block, next)) = stack.pop() {
match succs[block.index()].get(next) {
Some(&target) => {
stack.push((block, next + 1));
if !seen[target.index()] {
seen[target.index()] = true;
stack.push((target, 0));
}
}
None => order.push(block),
}
}
order
}
#[cfg(test)]
mod tests {
use rucc_ir::{Block, Func, Signature};
use crate::cfg::Cfg;
use crate::testing::{computed_goto, graph};
fn succs(cfg: &Cfg, block: usize) -> Vec<usize> {
cfg.successors(Block::from_usize(block)).iter().map(|b| b.index()).collect()
}
fn preds(cfg: &Cfg, block: usize) -> Vec<usize> {
let mut list: Vec<usize> =
cfg.predecessors(Block::from_usize(block)).iter().map(|b| b.index()).collect();
list.sort_unstable();
list
}
#[test]
fn a_straight_line_goes_one_way() {
let func = graph(&[&[1], &[2], &[]]);
let cfg = Cfg::new(&func);
assert_eq!(succs(&cfg, 0), [1]);
assert_eq!(succs(&cfg, 2), []);
assert_eq!(preds(&cfg, 0), []);
assert_eq!(preds(&cfg, 2), [1]);
assert_eq!(cfg.postorder().iter().map(|b| b.index()).collect::<Vec<_>>(), [2, 1, 0]);
}
#[test]
fn a_join_has_both_arms_as_predecessors() {
let func = graph(&[&[1, 2], &[3], &[3], &[]]);
let cfg = Cfg::new(&func);
assert_eq!(preds(&cfg, 3), [1, 2]);
assert_eq!(cfg.rank(Block::from_usize(0)), Some(0));
}
#[test]
fn two_arms_of_one_branch_to_one_block_is_one_predecessor() {
let func = graph(&[&[1, 1], &[]]);
let cfg = Cfg::new(&func);
assert_eq!(succs(&cfg, 0), [1]);
assert_eq!(preds(&cfg, 1), [0]);
}
#[test]
fn a_block_nothing_branches_to_is_not_reached() {
let func = graph(&[&[1], &[], &[2]]);
let cfg = Cfg::new(&func);
assert!(cfg.reaches(Block::from_usize(1)));
assert!(!cfg.reaches(Block::from_usize(2)));
assert!(cfg.rank(Block::from_usize(2)).is_none());
assert_eq!(cfg.postorder().len(), 2);
}
#[test]
fn a_back_edge_is_an_edge_like_any_other() {
let func = graph(&[&[1], &[2, 3], &[1], &[]]);
let cfg = Cfg::new(&func);
assert_eq!(preds(&cfg, 1), [0, 2]);
let order: Vec<usize> = cfg.reverse_postorder().map(|b| b.index()).collect();
assert_eq!(order[0], 0);
assert!(order.iter().position(|&b| b == 1) < order.iter().position(|&b| b == 2));
}
#[test]
fn taking_the_address_of_a_block_is_not_an_edge_to_it() {
let func = computed_goto();
let cfg = Cfg::new(&func);
assert_eq!(preds(&cfg, 2), [1]);
assert!(cfg.reaches(Block::from_usize(2)));
}
#[test]
fn a_declaration_has_no_graph_and_says_so() {
let func = Func::new(rucc_base::Interner::new().intern("f"), Signature::new());
let cfg = Cfg::new(&func);
assert!(cfg.entry().is_none());
assert!(cfg.postorder().is_empty());
assert_eq!(cfg.capacity(), 0);
}
}