use crate::{
analysis::ssa::SsaFunction,
utils::graph::{
algorithms::{postorder, reverse_postorder},
GraphBase, NodeId, Predecessors, RootedGraph, Successors,
},
};
#[derive(Debug)]
pub struct SsaCfg<'a> {
ssa: &'a SsaFunction,
predecessors: Vec<Vec<usize>>,
}
impl<'a> SsaCfg<'a> {
#[must_use]
pub fn from_ssa(ssa: &'a SsaFunction) -> Self {
let block_count = ssa.block_count();
let mut predecessors = vec![Vec::new(); block_count];
for block_idx in 0..block_count {
if let Some(block) = ssa.block(block_idx) {
let successors = block
.instructions()
.iter()
.rev()
.find_map(|instr| {
let op = instr.op();
if op.is_terminator() {
Some(op.successors())
} else {
None
}
})
.unwrap_or_default();
for succ in successors {
if succ < block_count {
predecessors[succ].push(block_idx);
}
}
}
}
Self { ssa, predecessors }
}
#[must_use]
pub const fn ssa(&self) -> &'a SsaFunction {
self.ssa
}
#[must_use]
pub fn block_count(&self) -> usize {
self.ssa.block_count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ssa.is_empty()
}
#[must_use]
pub fn block_successors(&self, block_idx: usize) -> Vec<usize> {
self.ssa
.block(block_idx)
.and_then(|block| {
block.instructions().iter().rev().find_map(|instr| {
let op = instr.op();
if op.is_terminator() {
Some(op.successors())
} else {
None
}
})
})
.unwrap_or_default()
}
#[must_use]
pub fn block_predecessors(&self, block_idx: usize) -> &[usize] {
self.predecessors.get(block_idx).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn exits(&self) -> Vec<NodeId> {
let mut exits = Vec::new();
for idx in 0..self.ssa.block_count() {
if self.block_successors(idx).is_empty() {
exits.push(NodeId::new(idx));
}
}
exits
}
#[must_use]
pub fn postorder(&self) -> Vec<NodeId> {
postorder(self, self.entry())
}
#[must_use]
pub fn reverse_postorder(&self) -> Vec<NodeId> {
reverse_postorder(self, self.entry())
}
}
impl GraphBase for SsaCfg<'_> {
fn node_count(&self) -> usize {
self.ssa.block_count()
}
fn node_ids(&self) -> impl Iterator<Item = NodeId> {
(0..self.ssa.block_count()).map(NodeId::new)
}
}
impl Successors for SsaCfg<'_> {
fn successors(&self, node: NodeId) -> impl Iterator<Item = NodeId> {
self.block_successors(node.index())
.into_iter()
.map(NodeId::new)
}
}
impl Predecessors for SsaCfg<'_> {
fn predecessors(&self, node: NodeId) -> impl Iterator<Item = NodeId> {
self.block_predecessors(node.index())
.iter()
.copied()
.map(NodeId::new)
}
}
impl RootedGraph for SsaCfg<'_> {
fn entry(&self) -> NodeId {
NodeId::new(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::ssa::{SsaBlock, SsaInstruction, SsaOp, SsaVarId};
fn create_test_ssa(terminators: Vec<SsaOp>) -> SsaFunction {
let mut ssa = SsaFunction::new(0, 0);
for (idx, terminator) in terminators.into_iter().enumerate() {
let mut block = SsaBlock::new(idx);
block.add_instruction(SsaInstruction::synthetic(terminator));
ssa.add_block(block);
}
ssa
}
#[test]
fn test_empty_ssa() {
let ssa = SsaFunction::new(0, 0);
let cfg = SsaCfg::from_ssa(&ssa);
assert!(cfg.is_empty());
assert_eq!(cfg.node_count(), 0);
}
#[test]
fn test_single_block() {
let ssa = create_test_ssa(vec![SsaOp::Return { value: None }]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(cfg.node_count(), 1);
assert_eq!(cfg.entry(), NodeId::new(0));
assert!(cfg.block_successors(0).is_empty());
assert!(cfg.block_predecessors(0).is_empty());
}
#[test]
fn test_linear_blocks() {
let ssa = create_test_ssa(vec![
SsaOp::Jump { target: 1 },
SsaOp::Jump { target: 2 },
SsaOp::Return { value: None },
]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(cfg.node_count(), 3);
assert_eq!(cfg.block_successors(0), vec![1]);
assert_eq!(cfg.block_successors(1), vec![2]);
assert!(cfg.block_successors(2).is_empty());
assert!(cfg.block_predecessors(0).is_empty());
assert_eq!(cfg.block_predecessors(1), vec![0]);
assert_eq!(cfg.block_predecessors(2), vec![1]);
}
#[test]
fn test_diamond_cfg() {
let cond = SsaVarId::new();
let ssa = create_test_ssa(vec![
SsaOp::Branch {
condition: cond,
true_target: 1,
false_target: 2,
},
SsaOp::Jump { target: 3 },
SsaOp::Jump { target: 3 },
SsaOp::Return { value: None },
]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(cfg.node_count(), 4);
let b0_succs = cfg.block_successors(0);
assert_eq!(b0_succs.len(), 2);
assert!(b0_succs.contains(&1));
assert!(b0_succs.contains(&2));
let b3_preds = cfg.block_predecessors(3);
assert_eq!(b3_preds.len(), 2);
assert!(b3_preds.contains(&1));
assert!(b3_preds.contains(&2));
}
#[test]
fn test_loop_cfg() {
let cond = SsaVarId::new();
let ssa = create_test_ssa(vec![
SsaOp::Jump { target: 1 },
SsaOp::Branch {
condition: cond,
true_target: 1, false_target: 2,
},
SsaOp::Return { value: None },
]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(cfg.node_count(), 3);
let b1_preds = cfg.block_predecessors(1);
assert_eq!(b1_preds.len(), 2);
assert!(b1_preds.contains(&0));
assert!(b1_preds.contains(&1)); }
#[test]
fn test_switch_cfg() {
let val = SsaVarId::new();
let ssa = create_test_ssa(vec![
SsaOp::Switch {
value: val,
targets: vec![1, 2, 3],
default: 4,
},
SsaOp::Return { value: None },
SsaOp::Return { value: None },
SsaOp::Return { value: None },
SsaOp::Return { value: None },
]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(cfg.node_count(), 5);
let b0_succs = cfg.block_successors(0);
assert_eq!(b0_succs.len(), 4);
assert!(b0_succs.contains(&1));
assert!(b0_succs.contains(&2));
assert!(b0_succs.contains(&3));
assert!(b0_succs.contains(&4));
}
#[test]
fn test_graph_traits() {
let ssa = create_test_ssa(vec![
SsaOp::Jump { target: 1 },
SsaOp::Return { value: None },
]);
let cfg = SsaCfg::from_ssa(&ssa);
assert_eq!(GraphBase::node_count(&cfg), 2);
let node_ids: Vec<_> = GraphBase::node_ids(&cfg).collect();
assert_eq!(node_ids, vec![NodeId::new(0), NodeId::new(1)]);
let succs: Vec<_> = Successors::successors(&cfg, NodeId::new(0)).collect();
assert_eq!(succs, vec![NodeId::new(1)]);
let preds: Vec<_> = Predecessors::predecessors(&cfg, NodeId::new(1)).collect();
assert_eq!(preds, vec![NodeId::new(0)]);
assert_eq!(RootedGraph::entry(&cfg), NodeId::new(0));
}
}