use crate::cfg::Cfg;
use crate::dominators::DomTree;
use llvm_ir::{BlockId, Function};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug)]
pub struct Loop {
pub header: BlockId,
pub body: Vec<BlockId>,
pub parent: Option<usize>,
}
pub struct LoopInfo {
loops: Vec<Loop>,
block_loop: HashMap<BlockId, usize>,
}
impl LoopInfo {
pub fn compute(func: &Function, cfg: &Cfg, dom: &DomTree) -> Self {
if func.num_blocks() == 0 {
return LoopInfo {
loops: vec![],
block_loop: HashMap::new(),
};
}
let back_edges = Self::find_back_edges(cfg, dom);
let mut loops: Vec<Loop> = back_edges
.into_iter()
.map(|(tail, header)| Loop {
header,
body: Self::collect_loop_body(tail, header, cfg),
parent: None,
})
.collect();
loops.sort_by(|a, b| b.body.len().cmp(&a.body.len()));
for i in 0..loops.len() {
let header = loops[i].header;
loops[i].parent = loops
.iter()
.enumerate()
.filter(|(j, l)| *j != i && l.body.contains(&header))
.min_by_key(|(_, l)| l.body.len())
.map(|(j, _)| j);
}
let mut block_loop: HashMap<BlockId, usize> = HashMap::new();
for (i, lp) in loops.iter().enumerate() {
for &b in &lp.body {
let entry = block_loop.entry(b).or_insert(i);
if loops[i].body.len() < loops[*entry].body.len() {
*entry = i;
}
}
}
LoopInfo { loops, block_loop }
}
fn find_back_edges(cfg: &Cfg, dom: &DomTree) -> Vec<(BlockId, BlockId)> {
let mut back_edges = Vec::new();
let mut visited = HashSet::new();
let mut stack = vec![cfg.entry()];
while let Some(b) = stack.pop() {
if !visited.insert(b) {
continue;
}
for &succ in cfg.successors(b) {
if dom.dominates(succ, b) {
back_edges.push((b, succ));
} else {
stack.push(succ);
}
}
}
back_edges
}
fn collect_loop_body(tail: BlockId, header: BlockId, cfg: &Cfg) -> Vec<BlockId> {
let mut body: HashSet<BlockId> = HashSet::new();
body.insert(header);
let mut queue = VecDeque::new();
if body.insert(tail) {
queue.push_back(tail);
}
while let Some(b) = queue.pop_front() {
for &pred in cfg.predecessors(b) {
if body.insert(pred) {
queue.push_back(pred);
}
}
}
let mut v: Vec<BlockId> = body.into_iter().collect();
v.sort_unstable_by_key(|b| b.0);
v
}
pub fn loops(&self) -> &[Loop] {
&self.loops
}
pub fn loop_of(&self, bid: BlockId) -> Option<usize> {
self.block_loop.get(&bid).copied()
}
pub fn is_loop_header(&self, bid: BlockId) -> bool {
self.loops.iter().any(|l| l.header == bid)
}
pub fn depth(&self, bid: BlockId) -> usize {
let mut idx = match self.block_loop.get(&bid) {
None => return 0,
Some(&i) => i,
};
let mut d = 1;
while let Some(p) = self.loops[idx].parent {
idx = p;
d += 1;
}
d
}
}
#[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!("max 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 no_loops() {
let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![2]), (2, vec![])]);
let cfg = Cfg::compute(&func);
let dom = DomTree::compute(&func, &cfg);
let li = LoopInfo::compute(&func, &cfg, &dom);
assert!(li.loops().is_empty());
assert_eq!(li.depth(BlockId(0)), 0);
assert_eq!(li.depth(BlockId(1)), 0);
assert_eq!(li.depth(BlockId(2)), 0);
}
#[test]
fn simple_loop() {
let (_ctx, func) = build_func(
4,
&[(0, vec![1]), (1, vec![2]), (2, vec![1, 3]), (3, vec![])],
);
let cfg = Cfg::compute(&func);
let dom = DomTree::compute(&func, &cfg);
let li = LoopInfo::compute(&func, &cfg, &dom);
assert_eq!(li.loops().len(), 1);
assert_eq!(li.loops()[0].header, BlockId(1));
assert!(li.loops()[0].body.contains(&BlockId(1)));
assert!(li.loops()[0].body.contains(&BlockId(2)));
assert!(!li.loops()[0].body.contains(&BlockId(0)));
assert!(!li.loops()[0].body.contains(&BlockId(3)));
assert!(li.is_loop_header(BlockId(1)));
assert!(!li.is_loop_header(BlockId(0)));
assert_eq!(li.depth(BlockId(0)), 0);
assert_eq!(li.depth(BlockId(1)), 1);
assert_eq!(li.depth(BlockId(2)), 1);
assert_eq!(li.depth(BlockId(3)), 0);
}
#[test]
fn nested_loops() {
let (_ctx, func) = build_func(
5,
&[
(0, vec![1]),
(1, vec![2, 4]),
(2, vec![3]),
(3, vec![2, 1]),
(4, vec![]),
],
);
let cfg = Cfg::compute(&func);
let dom = DomTree::compute(&func, &cfg);
let li = LoopInfo::compute(&func, &cfg, &dom);
assert_eq!(li.loops().len(), 2);
let headers: Vec<BlockId> = li.loops().iter().map(|l| l.header).collect();
assert!(headers.contains(&BlockId(1)));
assert!(headers.contains(&BlockId(2)));
assert_eq!(li.depth(BlockId(1)), 1); assert_eq!(li.depth(BlockId(2)), 2); assert_eq!(li.depth(BlockId(3)), 2); assert_eq!(li.depth(BlockId(0)), 0);
assert_eq!(li.depth(BlockId(4)), 0);
}
#[test]
fn irreducible_cfg_not_detected() {
let (_ctx, func) = build_func(3, &[(0, vec![1, 2]), (1, vec![2]), (2, vec![1])]);
let cfg = Cfg::compute(&func);
let dom = DomTree::compute(&func, &cfg);
let li = LoopInfo::compute(&func, &cfg, &dom);
assert_eq!(li.loops().len(), 0,
"dominance-based algorithm does not detect irreducible cycle (known limitation, see issue #9)");
}
}