use llvm_ir::{BlockId, Function};
pub struct Cfg {
num_blocks: usize,
succs: Vec<Vec<BlockId>>,
preds: Vec<Vec<BlockId>>,
reachable: Vec<bool>,
reachable_count: usize,
}
impl Cfg {
pub fn compute(func: &Function) -> Self {
let n = func.num_blocks();
let mut succs = vec![Vec::new(); n];
let mut preds = vec![Vec::new(); n];
for (bi, bb) in func.blocks.iter().enumerate() {
let src = BlockId(bi as u32);
if let Some(tid) = bb.terminator {
for dst in func.instr(tid).kind.successors() {
succs[bi].push(dst);
preds[dst.0 as usize].push(src);
}
}
}
let mut reachable = vec![false; n];
let mut reachable_count = 0;
if n > 0 {
let mut stack = vec![0usize];
while let Some(b) = stack.pop() {
if reachable[b] {
continue;
}
reachable[b] = true;
reachable_count += 1;
for &succ in &succs[b] {
stack.push(succ.0 as usize);
}
}
}
Cfg {
num_blocks: n,
succs,
preds,
reachable,
reachable_count,
}
}
pub fn entry(&self) -> BlockId {
BlockId(0)
}
pub fn num_blocks(&self) -> usize {
self.num_blocks
}
pub fn num_reachable_blocks(&self) -> usize {
self.reachable_count
}
pub fn is_reachable(&self, bid: BlockId) -> bool {
self.reachable[bid.0 as usize]
}
pub fn successors(&self, bid: BlockId) -> &[BlockId] {
&self.succs[bid.0 as usize]
}
pub fn predecessors(&self, bid: BlockId) -> &[BlockId] {
&self.preds[bid.0 as usize]
}
pub fn post_order(&self) -> Vec<BlockId> {
let mut visited = vec![false; self.num_blocks];
let mut order = Vec::with_capacity(self.num_blocks);
self.dfs_post(self.entry(), &mut visited, &mut order);
order
}
pub fn rpo(&self) -> Vec<BlockId> {
let mut order = self.post_order();
order.reverse();
order
}
fn dfs_post(&self, bid: BlockId, visited: &mut Vec<bool>, order: &mut Vec<BlockId>) {
let idx = bid.0 as usize;
if visited[idx] {
return;
}
visited[idx] = true;
for &succ in &self.succs[idx] {
self.dfs_post(succ, visited, order);
}
order.push(bid);
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_ir::{BasicBlock, Context, Function, InstrKind, Instruction, Linkage, ValueRef};
fn build_func(num_blocks: usize, edges: &[(usize, Vec<usize>)]) -> (Context, Function) {
let mut ctx = Context::new();
let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
let mut func = Function::new("test", fn_ty, vec![], Linkage::External);
for i in 0..num_blocks {
func.add_block(BasicBlock::new(format!("b{}", i)));
}
let mut has_term = vec![false; num_blocks];
for &(src, ref dsts) in edges {
has_term[src] = true;
let kind = match dsts.as_slice() {
[] => InstrKind::Unreachable,
[dst] => InstrKind::Br {
dest: BlockId(*dst as u32),
},
[t, f] => {
let cond = ValueRef::Constant(ctx.const_int(ctx.i1_ty, 0));
InstrKind::CondBr {
cond,
then_dest: BlockId(*t as u32),
else_dest: BlockId(*f as u32),
}
}
_ => panic!("test only supports 0/1/2 successors"),
};
let iid = func.alloc_instr(Instruction {
name: None,
ty: ctx.void_ty,
kind,
});
func.blocks[src].set_terminator(iid);
}
for (i, &needs_term) in has_term.iter().enumerate() {
if !needs_term {
let iid = func.alloc_instr(Instruction {
name: None,
ty: ctx.void_ty,
kind: InstrKind::Unreachable,
});
func.blocks[i].set_terminator(iid);
}
}
(ctx, func)
}
#[test]
fn cfg_single_block() {
let (_ctx, func) = build_func(1, &[(0, vec![])]);
let cfg = Cfg::compute(&func);
assert_eq!(cfg.num_blocks(), 1);
assert!(cfg.successors(BlockId(0)).is_empty());
assert!(cfg.predecessors(BlockId(0)).is_empty());
assert_eq!(cfg.rpo(), vec![BlockId(0)]);
}
#[test]
fn cfg_linear() {
let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![2]), (2, vec![])]);
let cfg = Cfg::compute(&func);
assert_eq!(cfg.successors(BlockId(0)), &[BlockId(1)]);
assert_eq!(cfg.successors(BlockId(1)), &[BlockId(2)]);
assert!(cfg.successors(BlockId(2)).is_empty());
assert!(cfg.predecessors(BlockId(0)).is_empty());
assert_eq!(cfg.predecessors(BlockId(2)), &[BlockId(1)]);
assert_eq!(cfg.rpo(), vec![BlockId(0), BlockId(1), BlockId(2)]);
}
#[test]
fn cfg_diamond() {
let (_ctx, func) = build_func(
4,
&[(0, vec![1, 2]), (1, vec![3]), (2, vec![3]), (3, vec![])],
);
let cfg = Cfg::compute(&func);
assert_eq!(cfg.successors(BlockId(0)).len(), 2);
assert_eq!(cfg.predecessors(BlockId(3)).len(), 2);
let rpo = cfg.rpo();
assert_eq!(rpo[0], BlockId(0));
assert_eq!(*rpo.last().unwrap(), BlockId(3));
}
#[test]
fn cfg_loop() {
let (_ctx, func) = build_func(
4,
&[(0, vec![1]), (1, vec![2]), (2, vec![1, 3]), (3, vec![])],
);
let cfg = Cfg::compute(&func);
assert!(cfg.predecessors(BlockId(1)).contains(&BlockId(2)));
assert_eq!(cfg.rpo().len(), 4);
}
#[test]
fn cfg_unreachable_block() {
let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![]), (2, vec![])]);
let cfg = Cfg::compute(&func);
let rpo = cfg.rpo();
assert_eq!(rpo.len(), 2);
assert!(!rpo.contains(&BlockId(2)));
}
#[test]
fn cfg_is_reachable() {
let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![]), (2, vec![])]);
let cfg = Cfg::compute(&func);
assert!(cfg.is_reachable(BlockId(0)));
assert!(cfg.is_reachable(BlockId(1)));
assert!(!cfg.is_reachable(BlockId(2)));
}
#[test]
fn cfg_num_reachable_blocks() {
let (_ctx, func) = build_func(4, &[(0, vec![1]), (1, vec![]), (2, vec![3]), (3, vec![])]);
let cfg = Cfg::compute(&func);
assert_eq!(cfg.num_blocks(), 4);
assert_eq!(cfg.num_reachable_blocks(), 2);
assert_eq!(cfg.num_reachable_blocks(), cfg.rpo().len());
}
#[test]
fn cfg_empty_function_reachable_count() {
let mut ctx = Context::new();
let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
let func = Function::new("empty", fn_ty, vec![], Linkage::External);
let cfg = Cfg::compute(&func);
assert_eq!(cfg.num_blocks(), 0);
assert_eq!(cfg.num_reachable_blocks(), 0);
}
}