use crate::cfg::{Cfg, EdgeType};
use petgraph::graph::NodeIndex;
use petgraph::visit::EdgeRef;
use std::collections::{HashMap, HashSet};
pub fn calculate_dominator_depth(cfg: &Cfg, entry: NodeIndex) -> HashMap<NodeIndex, i64> {
let mut depths = HashMap::new();
let mut visited = HashSet::new();
let mut stack = vec![(entry, 0i64)];
while let Some((node, depth)) = stack.pop() {
if visited.contains(&node) {
continue;
}
visited.insert(node);
depths.insert(node, depth);
for neighbor in cfg.neighbors(node) {
if !visited.contains(&neighbor) {
stack.push((neighbor, depth + 1));
}
}
}
depths
}
pub fn calculate_loop_nesting_depth(cfg: &Cfg, entry: NodeIndex) -> HashMap<NodeIndex, i64> {
let mut nesting_depths = HashMap::new();
for node in cfg.node_indices() {
nesting_depths.insert(node, 0);
}
let back_edges = find_back_edges(cfg, entry);
for (source, target) in back_edges {
let loop_body = identify_loop_body(cfg, source, target);
for node in loop_body {
*nesting_depths.get_mut(&node).unwrap() += 1;
}
}
nesting_depths
}
pub fn calculate_branch_distance(cfg: &Cfg, entry: NodeIndex) -> HashMap<NodeIndex, i64> {
let mut distances = HashMap::new();
let mut queue = std::collections::VecDeque::new();
let mut visited = HashSet::new();
queue.push_back((entry, 0i64));
visited.insert(entry);
while let Some((node, distance)) = queue.pop_front() {
distances.insert(node, distance);
for edge in cfg.edges(node) {
let neighbor = edge.target();
if !visited.contains(&neighbor) {
visited.insert(neighbor);
let new_distance = match edge.weight() {
EdgeType::TrueBranch | EdgeType::FalseBranch => distance + 1,
_ => distance,
};
queue.push_back((neighbor, new_distance));
}
}
}
distances
}
fn find_back_edges(cfg: &Cfg, entry: NodeIndex) -> Vec<(NodeIndex, NodeIndex)> {
let mut back_edges = Vec::new();
let dominators = compute_immediate_dominators(cfg, entry);
for edge in cfg.edge_indices() {
let (source, target) = cfg.edge_endpoints(edge).unwrap();
if is_dominated_by(source, target, &dominators) {
back_edges.push((source, target));
}
}
back_edges
}
fn identify_loop_body(cfg: &Cfg, source: NodeIndex, target: NodeIndex) -> HashSet<NodeIndex> {
let mut loop_body = HashSet::new();
let mut stack = vec![source];
let mut visited = HashSet::new();
visited.insert(source);
while let Some(node) = stack.pop() {
loop_body.insert(node);
for predecessor in cfg.neighbors_directed(node, petgraph::Direction::Incoming) {
if predecessor != target && !visited.contains(&predecessor) {
visited.insert(predecessor);
stack.push(predecessor);
}
}
}
loop_body.insert(target);
loop_body
}
fn compute_immediate_dominators(cfg: &Cfg, entry: NodeIndex) -> HashMap<NodeIndex, NodeIndex> {
let mut idom = HashMap::new();
let _all_nodes: HashSet<NodeIndex> = cfg.node_indices().collect();
idom.insert(entry, entry);
for node in cfg.node_indices() {
if node != entry {
idom.insert(node, entry); }
}
let mut changed = true;
while changed {
changed = false;
for node in cfg.node_indices() {
if node == entry {
continue;
}
let predecessors: Vec<NodeIndex> = cfg
.neighbors_directed(node, petgraph::Direction::Incoming)
.collect();
if predecessors.is_empty() {
continue;
}
let mut new_idom = predecessors[0];
for pred in &predecessors[1..] {
new_idom = intersect_dominators(cfg, &idom, new_idom, *pred);
}
if idom.get(&node) != Some(&new_idom) {
idom.insert(node, new_idom);
changed = true;
}
}
}
idom
}
fn intersect_dominators(
_cfg: &Cfg,
idom: &HashMap<NodeIndex, NodeIndex>,
n1: NodeIndex,
n2: NodeIndex,
) -> NodeIndex {
let mut finger1 = n1;
let mut finger2 = n2;
while finger1 != finger2 {
let idx1 = finger1.index();
let idx2 = finger2.index();
if idx1 > idx2 {
finger1 = *idom.get(&finger1).unwrap_or(&finger1);
} else {
finger2 = *idom.get(&finger2).unwrap_or(&finger2);
}
}
finger1
}
fn is_dominated_by(
node: NodeIndex,
dominator: NodeIndex,
idom: &HashMap<NodeIndex, NodeIndex>,
) -> bool {
if node == dominator {
return true;
}
let mut current = node;
while let Some(&idom_node) = idom.get(¤t) {
if idom_node == dominator {
return true;
}
if idom_node == current {
break;
}
current = idom_node;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cfg::{BasicBlock, BlockKind, Terminator};
fn create_linear_cfg() -> (Cfg, NodeIndex, NodeIndex, NodeIndex) {
let mut cfg = Cfg::new();
let a = cfg.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 b = cfg.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 c = cfg.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
cfg.add_edge(a, b, EdgeType::Fallthrough);
cfg.add_edge(b, c, EdgeType::Fallthrough);
(cfg, a, b, c)
}
fn create_loop_cfg() -> (Cfg, NodeIndex, NodeIndex, NodeIndex) {
let mut cfg = Cfg::new();
let a = cfg.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 b = cfg.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::SwitchInt {
targets: vec![2], otherwise: 2, }, source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let c = cfg.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Goto { target: 1 }, source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
cfg.add_edge(a, b, EdgeType::Fallthrough);
cfg.add_edge(b, c, EdgeType::Fallthrough); cfg.add_edge(c, b, EdgeType::Fallthrough);
(cfg, a, b, c)
}
fn create_branch_cfg() -> (Cfg, NodeIndex, NodeIndex, NodeIndex) {
let mut cfg = Cfg::new();
let a = cfg.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 b = cfg.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let c = cfg.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Normal,
statements: vec![],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
cfg.add_edge(a, b, EdgeType::TrueBranch);
cfg.add_edge(a, c, EdgeType::FalseBranch);
(cfg, a, b, c)
}
#[test]
fn test_calculate_dominator_depth_linear_cfg() {
let (cfg, entry, b, c) = create_linear_cfg();
let depths = calculate_dominator_depth(&cfg, entry);
assert_eq!(*depths.get(&entry).unwrap(), 0, "Entry should have depth 0");
assert_eq!(*depths.get(&b).unwrap(), 1, "B should have depth 1");
assert_eq!(*depths.get(&c).unwrap(), 2, "C should have depth 2");
}
#[test]
fn test_calculate_dominator_depth_loop_cfg() {
let (cfg, entry, b, c) = create_loop_cfg();
let depths = calculate_dominator_depth(&cfg, entry);
assert_eq!(*depths.get(&entry).unwrap(), 0, "Entry should have depth 0");
assert_eq!(
*depths.get(&b).unwrap(),
1,
"Loop header should have depth 1"
);
assert_eq!(*depths.get(&c).unwrap(), 2, "Loop body should have depth 2");
}
#[test]
fn test_calculate_dominator_depth_branch_cfg() {
let (cfg, entry, b, c) = create_branch_cfg();
let depths = calculate_dominator_depth(&cfg, entry);
assert_eq!(*depths.get(&entry).unwrap(), 0, "Entry should have depth 0");
assert_eq!(*depths.get(&b).unwrap(), 1, "Branch B should have depth 1");
assert_eq!(*depths.get(&c).unwrap(), 1, "Branch C should have depth 1");
}
#[test]
fn test_calculate_loop_nesting_depth_linear_cfg() {
let (cfg, entry, b, c) = create_linear_cfg();
let depths = calculate_loop_nesting_depth(&cfg, entry);
assert_eq!(
*depths.get(&entry).unwrap(),
0,
"Entry should not be in a loop"
);
assert_eq!(*depths.get(&b).unwrap(), 0, "B should not be in a loop");
assert_eq!(*depths.get(&c).unwrap(), 0, "C should not be in a loop");
}
#[test]
fn test_calculate_loop_nesting_depth_loop_cfg() {
let (cfg, entry, b, c) = create_loop_cfg();
let depths = calculate_loop_nesting_depth(&cfg, entry);
assert_eq!(
*depths.get(&entry).unwrap(),
0,
"Entry should not be in the loop"
);
assert_eq!(
*depths.get(&b).unwrap(),
1,
"Loop header should have depth 1"
);
assert_eq!(*depths.get(&c).unwrap(), 1, "Loop body should have depth 1");
}
#[test]
fn test_calculate_loop_nesting_depth_branch_cfg() {
let (cfg, entry, b, c) = create_branch_cfg();
let depths = calculate_loop_nesting_depth(&cfg, entry);
assert_eq!(
*depths.get(&entry).unwrap(),
0,
"Entry should not be in a loop"
);
assert_eq!(
*depths.get(&b).unwrap(),
0,
"Branch B should not be in a loop"
);
assert_eq!(
*depths.get(&c).unwrap(),
0,
"Branch C should not be in a loop"
);
}
#[test]
fn test_calculate_branch_distance_linear_cfg() {
let (cfg, entry, b, c) = create_linear_cfg();
let distances = calculate_branch_distance(&cfg, entry);
assert_eq!(
*distances.get(&entry).unwrap(),
0,
"Entry should have distance 0"
);
assert_eq!(*distances.get(&b).unwrap(), 0, "B should have distance 0");
assert_eq!(*distances.get(&c).unwrap(), 0, "C should have distance 0");
}
#[test]
fn test_calculate_branch_distance_branch_cfg() {
let (cfg, entry, b, c) = create_branch_cfg();
let distances = calculate_branch_distance(&cfg, entry);
assert_eq!(
*distances.get(&entry).unwrap(),
0,
"Entry should have distance 0"
);
assert_eq!(
*distances.get(&b).unwrap(),
1,
"Branch B should have distance 1"
);
assert_eq!(
*distances.get(&c).unwrap(),
1,
"Branch C should have distance 1"
);
}
#[test]
fn test_find_back_edges_loop_cfg() {
let (cfg, entry, b, c) = create_loop_cfg();
let back_edges = find_back_edges(&cfg, entry);
assert_eq!(back_edges.len(), 1, "Should have exactly one back edge");
assert_eq!(back_edges[0].0, c, "Back edge should start from C");
assert_eq!(back_edges[0].1, b, "Back edge should point to B");
}
#[test]
fn test_find_back_edges_linear_cfg() {
let (cfg, entry, _, _) = create_linear_cfg();
let back_edges = find_back_edges(&cfg, entry);
assert_eq!(back_edges.len(), 0, "Linear CFG should have no back edges");
}
#[test]
fn test_identify_loop_body() {
let (cfg, _, b, c) = create_loop_cfg();
let loop_body = identify_loop_body(&cfg, c, b);
assert!(loop_body.contains(&b), "Loop body should include header B");
assert!(loop_body.contains(&c), "Loop body should include C");
assert_eq!(loop_body.len(), 2, "Loop body should have exactly 2 nodes");
}
}