use crate::{
analysis::{
cfg::{detect_loops, LoopForest},
SsaCfg, SsaFunction,
},
utils::graph::{algorithms, RootedGraph},
};
pub struct LoopAnalyzer<'a> {
cfg: SsaCfg<'a>,
}
impl<'a> LoopAnalyzer<'a> {
#[must_use]
pub fn new(ssa: &'a SsaFunction) -> Self {
let cfg = SsaCfg::from_ssa(ssa);
Self { cfg }
}
#[must_use]
pub fn analyze(&self) -> LoopForest {
let dominators = algorithms::compute_dominators(&self.cfg, self.cfg.entry());
detect_loops(&self.cfg, &dominators)
}
}
pub trait SsaLoopAnalysis {
fn analyze_loops(&self) -> LoopForest;
}
impl SsaLoopAnalysis for SsaFunction {
fn analyze_loops(&self) -> LoopForest {
LoopAnalyzer::new(self).analyze()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
analysis::{cfg::LoopType, SsaFunctionBuilder, SsaVarId},
utils::graph::NodeId,
};
#[test]
fn test_find_condition_in_body() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| b.jump(2));
f.block(2, |b| {
let cond = b.const_true();
b.branch(cond, 3, 4);
});
f.block(3, |b| b.jump(1));
f.block(4, |b| b.ret());
});
let forest = ssa.analyze_loops();
assert_eq!(forest.len(), 1, "Should have one loop");
let loop_info = &forest.loops()[0];
assert_eq!(loop_info.header, NodeId::new(1), "Header should be B1");
let condition = loop_info.find_condition_in_body(&ssa);
assert_eq!(
condition,
Some(NodeId::new(2)),
"Condition block should be B2"
);
}
#[test]
fn test_find_all_conditions_in_body() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| b.jump(2));
f.block(2, |b| {
let cond = b.const_true();
b.branch(cond, 3, 4);
});
f.block(3, |b| {
let cond = b.const_true();
b.branch(cond, 5, 1);
});
f.block(4, |b| {
let cond = b.const_true();
b.branch(cond, 1, 5);
});
f.block(5, |b| b.ret());
});
let forest = ssa.analyze_loops();
assert!(!forest.is_empty(), "Should have at least one loop");
let loop_info = &forest.loops()[0];
let conditions = loop_info.find_all_conditions_in_body(&ssa);
assert!(
!conditions.is_empty(),
"Should find at least one condition block"
);
}
#[test]
fn test_simple_while_loop() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| {
let cond = b.const_true();
b.branch(cond, 2, 3);
});
f.block(2, |b| b.jump(1));
f.block(3, |b| b.ret());
});
let forest = ssa.analyze_loops();
assert_eq!(forest.len(), 1);
let loop_info = &forest.loops()[0];
assert_eq!(loop_info.header, NodeId::new(1));
assert!(loop_info.contains(NodeId::new(1)));
assert!(loop_info.contains(NodeId::new(2)));
assert!(!loop_info.contains(NodeId::new(0)));
assert!(!loop_info.contains(NodeId::new(3)));
assert!(loop_info.has_single_latch());
assert_eq!(loop_info.single_latch(), Some(NodeId::new(2)));
assert!(loop_info.has_preheader());
assert_eq!(loop_info.preheader, Some(NodeId::new(0)));
assert_eq!(loop_info.loop_type, LoopType::PreTested);
assert!(loop_info.is_canonical());
}
#[test]
fn test_do_while_loop() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| {
let cond = b.const_true();
b.branch(cond, 1, 2); });
f.block(2, |b| b.ret());
});
let forest = ssa.analyze_loops();
assert_eq!(forest.len(), 1);
let loop_info = &forest.loops()[0];
assert_eq!(loop_info.header, NodeId::new(1));
assert!(loop_info.has_single_latch());
assert_eq!(loop_info.single_latch(), Some(NodeId::new(1)));
assert_eq!(loop_info.loop_type, LoopType::PostTested);
}
#[test]
fn test_nested_loops() {
let ssa = {
let mut cond_out = SsaVarId::new();
SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| b.jump(2));
f.block(2, |b| {
let c = b.const_true();
cond_out = c;
b.branch(c, 2, 3); });
f.block(3, |b| b.branch(cond_out, 1, 4)); f.block(4, |b| b.ret());
})
};
let forest = ssa.analyze_loops();
assert_eq!(forest.len(), 2);
let inner = forest.loop_for_header(NodeId::new(2)).unwrap();
let outer = forest.loop_for_header(NodeId::new(1)).unwrap();
assert_eq!(inner.parent, Some(NodeId::new(1)));
assert!(outer.children.contains(&NodeId::new(2)));
assert_eq!(outer.depth, 0);
assert_eq!(inner.depth, 1);
assert_eq!(forest.loop_depth(NodeId::new(2)), 2);
}
#[test]
fn test_induction_variable_api() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| b.jump(1));
f.block(1, |b| {
let cond = b.const_true();
b.branch(cond, 2, 3);
});
f.block(2, |b| b.jump(1));
f.block(3, |b| b.ret());
});
let forest = ssa.analyze_loops();
assert_eq!(forest.len(), 1, "Should have one loop");
let loop_info = &forest.loops()[0];
let induction_vars = loop_info.find_induction_vars(&ssa);
assert!(
induction_vars.is_empty(),
"Simple loop without phi should have no induction vars"
);
}
}