use crate::cfg::analysis::find_exits;
use crate::cfg::dominators::DominatorTree;
use crate::cfg::{BlockId, Cfg};
use petgraph::algo::dominators::simple_fast;
use petgraph::graph::NodeIndex;
use petgraph::visit::Reversed;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PostDominatorTree {
inner: DominatorTree,
exit: NodeIndex,
}
impl PostDominatorTree {
pub fn new(cfg: &Cfg) -> Option<Self> {
let exits = find_exits(cfg);
let exit = exits.first().copied()?;
let reversed = Reversed(cfg);
let dominators = simple_fast(reversed, exit);
let mut immediate_dominator = HashMap::new();
let mut children: HashMap<NodeIndex, Vec<NodeIndex>> = HashMap::new();
for node in cfg.node_indices() {
let idom = dominators.immediate_dominator(node);
immediate_dominator.insert(node, idom);
if let Some(parent) = idom {
children.entry(parent).or_default().push(node);
}
}
let inner = DominatorTree::from_parts(exit, immediate_dominator, children);
Some(Self { inner, exit })
}
pub fn root(&self) -> NodeIndex {
self.exit
}
pub fn immediate_post_dominator(&self, node: NodeIndex) -> Option<NodeIndex> {
self.inner.immediate_dominator(node)
}
pub fn post_dominates(&self, a: NodeIndex, b: NodeIndex) -> bool {
self.inner.dominates(a, b)
}
pub fn children(&self, node: NodeIndex) -> &[NodeIndex] {
self.inner.children(node)
}
pub fn strictly_post_dominates(&self, a: NodeIndex, b: NodeIndex) -> bool {
self.inner.strictly_dominates(a, b)
}
pub fn post_dominators(&self, node: NodeIndex) -> PostDominators<'_> {
PostDominators {
tree: self,
current: Some(node),
}
}
pub fn common_post_dominator(&self, a: NodeIndex, b: NodeIndex) -> Option<NodeIndex> {
let a_pdoms: std::collections::HashSet<NodeIndex> = self.post_dominators(a).collect();
for pdom in self.post_dominators(b) {
if a_pdoms.contains(&pdom) {
return Some(pdom);
}
}
None
}
pub fn depth(&self, node: NodeIndex) -> usize {
self.inner.depth(node)
}
pub fn as_dominator_tree(&self) -> &DominatorTree {
&self.inner
}
}
pub struct PostDominators<'a> {
tree: &'a PostDominatorTree,
current: Option<NodeIndex>,
}
impl<'a> Iterator for PostDominators<'a> {
type Item = NodeIndex;
fn next(&mut self) -> Option<Self::Item> {
let node = self.current?;
self.current = self.tree.immediate_post_dominator(node);
Some(node)
}
}
pub fn compute_post_dominator_tree(cfg: &Cfg) -> Option<PostDominatorTree> {
PostDominatorTree::new(cfg)
}
pub fn immediate_post_dominator_id(
tree: &PostDominatorTree,
block_id: BlockId,
cfg: &Cfg,
) -> Option<BlockId> {
let node = node_from_id(cfg, block_id)?;
let ipdom_node = tree.immediate_post_dominator(node)?;
Some(cfg[ipdom_node].id)
}
fn node_from_id(cfg: &Cfg, block_id: BlockId) -> Option<NodeIndex> {
cfg.node_indices().find(|&n| cfg[n].id == block_id)
}
#[cfg(test)]
mod tests {
use super::*;
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
}
#[test]
fn test_post_dominator_tree_construction() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
assert_eq!(post_dom_tree.root(), NodeIndex::new(3));
assert_eq!(
post_dom_tree.immediate_post_dominator(NodeIndex::new(3)),
None
);
assert_eq!(
post_dom_tree.immediate_post_dominator(NodeIndex::new(1)),
Some(NodeIndex::new(3))
);
assert_eq!(
post_dom_tree.immediate_post_dominator(NodeIndex::new(2)),
Some(NodeIndex::new(3))
);
assert_eq!(
post_dom_tree.immediate_post_dominator(NodeIndex::new(0)),
Some(NodeIndex::new(3))
);
}
#[test]
fn test_post_dominates() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
let exit = NodeIndex::new(3);
let entry = NodeIndex::new(0);
assert!(post_dom_tree.post_dominates(exit, exit));
assert!(post_dom_tree.post_dominates(exit, entry));
assert!(!post_dom_tree.post_dominates(entry, exit));
assert!(post_dom_tree.post_dominates(entry, entry));
assert!(post_dom_tree.post_dominates(exit, exit));
}
#[test]
fn test_children() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
let exit = NodeIndex::new(3);
let children = post_dom_tree.children(exit);
assert_eq!(children.len(), 3);
assert!(children.contains(&NodeIndex::new(0)));
assert!(children.contains(&NodeIndex::new(1)));
assert!(children.contains(&NodeIndex::new(2)));
}
#[test]
fn test_strictly_post_dominates() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
let exit = NodeIndex::new(3);
let node1 = NodeIndex::new(1);
assert!(post_dom_tree.strictly_post_dominates(exit, node1));
assert!(!post_dom_tree.strictly_post_dominates(exit, exit));
}
#[test]
fn test_post_dominators_iterator() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
let entry = NodeIndex::new(0);
let pdoms: Vec<_> = post_dom_tree.post_dominators(entry).collect();
assert_eq!(pdoms.len(), 2);
assert_eq!(pdoms[0], entry);
assert_eq!(pdoms[1], NodeIndex::new(3));
}
#[test]
fn test_common_post_dominator() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
let node1 = NodeIndex::new(1);
let node2 = NodeIndex::new(2);
let exit = NodeIndex::new(3);
assert_eq!(
post_dom_tree.common_post_dominator(node1, node2),
Some(exit)
);
assert_eq!(
post_dom_tree.common_post_dominator(node1, node1),
Some(node1)
);
}
#[test]
fn test_depth() {
let cfg = create_diamond_cfg();
let post_dom_tree = PostDominatorTree::new(&cfg).expect("CFG has exit");
assert_eq!(post_dom_tree.depth(NodeIndex::new(3)), 0);
assert_eq!(post_dom_tree.depth(NodeIndex::new(0)), 1);
assert_eq!(post_dom_tree.depth(NodeIndex::new(1)), 1);
assert_eq!(post_dom_tree.depth(NodeIndex::new(2)), 1);
}
#[test]
fn test_empty_cfg() {
let cfg: Cfg = DiGraph::new();
assert!(PostDominatorTree::new(&cfg).is_none());
}
#[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 post_dom_tree = PostDominatorTree::new(&g).expect("CFG has exit");
assert_eq!(post_dom_tree.immediate_post_dominator(b3), None);
assert_eq!(post_dom_tree.immediate_post_dominator(b2), Some(b3));
assert_eq!(post_dom_tree.immediate_post_dominator(b1), Some(b2));
assert_eq!(post_dom_tree.immediate_post_dominator(b0), Some(b1));
}
#[test]
fn test_reversed_is_zero_copy() {
use petgraph::visit::EdgeCount;
use petgraph::visit::NodeCount;
let cfg = create_diamond_cfg();
let reversed = Reversed(&cfg);
assert_eq!(reversed.node_count(), cfg.node_count());
assert_eq!(reversed.edge_count(), cfg.edge_count());
}
}