use crate::cfg::dominators::DominatorTree;
use crate::cfg::Cfg;
use petgraph::graph::NodeIndex;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct DominanceFrontiers {
frontiers: HashMap<NodeIndex, HashSet<NodeIndex>>,
dominator_tree: DominatorTree,
}
impl DominanceFrontiers {
pub fn new(cfg: &Cfg, dominator_tree: DominatorTree) -> Self {
let mut frontiers: HashMap<NodeIndex, HashSet<NodeIndex>> = HashMap::new();
let mut nodes: Vec<NodeIndex> = cfg.node_indices().collect();
nodes.sort_by_key(|&n| std::cmp::Reverse(dominator_tree.depth(n)));
for &n in &nodes {
let mut df = HashSet::new();
for &v in &nodes {
for p in cfg.neighbors_directed(v, petgraph::Direction::Incoming) {
if dominator_tree.dominates(n, p) && !dominator_tree.strictly_dominates(n, v) {
df.insert(v);
}
}
}
for &child in dominator_tree.children(n) {
if let Some(child_df) = frontiers.get(&child) {
for &v in child_df {
if !dominator_tree.strictly_dominates(n, v) {
df.insert(v);
}
}
}
}
frontiers.insert(n, df);
}
Self {
frontiers,
dominator_tree,
}
}
pub fn frontier(&self, node: NodeIndex) -> HashSet<NodeIndex> {
self.frontiers.get(&node).cloned().unwrap_or_default()
}
pub fn in_frontier(&self, n: NodeIndex, v: NodeIndex) -> bool {
self.frontiers
.get(&n)
.map(|set| set.contains(&v))
.unwrap_or(false)
}
pub fn dominator_tree(&self) -> &DominatorTree {
&self.dominator_tree
}
pub fn nodes_with_frontiers(&self) -> impl Iterator<Item = NodeIndex> + '_ {
self.frontiers
.iter()
.filter(|(_, df)| !df.is_empty())
.map(|(&n, _)| n)
}
pub fn iterated_frontier(&self, nodes: &[NodeIndex]) -> HashSet<NodeIndex> {
let mut result = HashSet::new();
let mut worklist: Vec<NodeIndex> = nodes.to_vec();
while let Some(n) = worklist.pop() {
for v in self.frontier(n) {
if result.insert(v) {
worklist.push(v);
}
}
}
result
}
pub fn union_frontier(&self, nodes: &[NodeIndex]) -> HashSet<NodeIndex> {
nodes.iter().flat_map(|&n| self.frontier(n)).collect()
}
}
pub fn compute_dominance_frontiers(cfg: &Cfg, dominator_tree: DominatorTree) -> DominanceFrontiers {
DominanceFrontiers::new(cfg, dominator_tree)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cfg::dominators::DominatorTree;
use crate::cfg::{BasicBlock, BlockKind, EdgeType, Terminator};
use petgraph::graph::DiGraph;
fn create_diamond_cfg() -> Cfg {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![1],
otherwise: 2,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["branch 1".to_string()],
terminator: Terminator::Goto { target: 3 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["branch 2".to_string()],
terminator: Terminator::Goto { target: 3 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::TrueBranch);
g.add_edge(b0, b2, EdgeType::FalseBranch);
g.add_edge(b1, b3, EdgeType::Fallthrough);
g.add_edge(b2, b3, EdgeType::Fallthrough);
g
}
fn create_loop_cfg() -> Cfg {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["loop body".to_string()],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::TrueBranch);
g.add_edge(b1, b3, EdgeType::FalseBranch);
g.add_edge(b2, b1, EdgeType::LoopBack);
g
}
#[test]
fn test_dominance_frontiers_diamond() {
let cfg = create_diamond_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
let df0 = frontiers.frontier(NodeIndex::new(0));
assert!(df0.is_empty());
let df1 = frontiers.frontier(NodeIndex::new(1));
assert!(df1.contains(&NodeIndex::new(3)));
assert_eq!(df1.len(), 1);
let df2 = frontiers.frontier(NodeIndex::new(2));
assert!(df2.contains(&NodeIndex::new(3)));
assert_eq!(df2.len(), 1);
}
#[test]
fn test_dominance_frontiers_loop() {
let cfg = create_loop_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
assert!(frontiers.frontier(NodeIndex::new(0)).is_empty());
let df1 = frontiers.frontier(NodeIndex::new(1));
assert!(df1.contains(&NodeIndex::new(1)));
let df2 = frontiers.frontier(NodeIndex::new(2));
assert!(df2.contains(&NodeIndex::new(1)));
}
#[test]
fn test_in_frontier() {
let cfg = create_diamond_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
assert!(frontiers.in_frontier(NodeIndex::new(1), NodeIndex::new(3)));
assert!(frontiers.in_frontier(NodeIndex::new(2), NodeIndex::new(3)));
assert!(!frontiers.in_frontier(NodeIndex::new(0), NodeIndex::new(3)));
}
#[test]
fn test_nodes_with_frontiers() {
let cfg = create_diamond_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
let nodes: Vec<_> = frontiers.nodes_with_frontiers().collect();
assert_eq!(nodes.len(), 2);
assert!(nodes.contains(&NodeIndex::new(1)));
assert!(nodes.contains(&NodeIndex::new(2)));
}
#[test]
fn test_iterated_frontier() {
let cfg = create_diamond_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
let idf = frontiers.iterated_frontier(&[NodeIndex::new(1)]);
assert!(idf.contains(&NodeIndex::new(3)));
let idf = frontiers.iterated_frontier(&[NodeIndex::new(2)]);
assert!(idf.contains(&NodeIndex::new(3)));
let idf = frontiers.iterated_frontier(&[NodeIndex::new(0)]);
assert!(idf.is_empty());
}
#[test]
fn test_union_frontier() {
let cfg = create_diamond_cfg();
let dom_tree = DominatorTree::new(&cfg).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&cfg, dom_tree);
let union = frontiers.union_frontier(&[NodeIndex::new(0), NodeIndex::new(1)]);
assert!(union.contains(&NodeIndex::new(3)));
}
#[test]
fn test_linear_cfg() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 2 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 3 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::Fallthrough);
g.add_edge(b2, b3, EdgeType::Fallthrough);
let dom_tree = DominatorTree::new(&g).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&g, dom_tree);
for node in g.node_indices() {
assert!(
frontiers.frontier(node).is_empty(),
"Linear CFG should have empty dominance frontiers"
);
}
}
#[test]
fn test_complex_join() {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![1, 2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 4 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 4 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 5 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b4 = g.add_node(BasicBlock {
id: 4,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 5 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b5 = g.add_node(BasicBlock {
id: 5,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b0, b2, EdgeType::Fallthrough);
g.add_edge(b0, b3, EdgeType::Fallthrough);
g.add_edge(b1, b4, EdgeType::Fallthrough);
g.add_edge(b2, b4, EdgeType::Fallthrough);
g.add_edge(b3, b5, EdgeType::Fallthrough);
g.add_edge(b4, b5, EdgeType::Fallthrough);
let dom_tree = DominatorTree::new(&g).expect("CFG has entry");
let frontiers = DominanceFrontiers::new(&g, dom_tree);
let df1 = frontiers.frontier(b1);
assert!(df1.contains(&b4));
let df2 = frontiers.frontier(b2);
assert!(df2.contains(&b4));
let df3 = frontiers.frontier(b3);
assert!(df3.contains(&b5));
let df4 = frontiers.frontier(b4);
assert!(df4.contains(&b5));
}
#[test]
fn test_empty_cfg() {
let cfg: Cfg = DiGraph::new();
let dom_tree = DominatorTree::new(&cfg);
assert!(dom_tree.is_none());
}
}