use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
};
pub struct NaturalLoop<T: Ord + Copy + Debug> {
pub loop_header: T,
pub loop_latch: T, pub loop_body: BTreeSet<T>,
}
pub struct Graph<T: Ord + Copy + Debug> {
entry: T,
nodes: Vec<T>,
edges: Vec<(T, T)>,
predecessors: BTreeMap<T, BTreeSet<T>>,
successors: BTreeMap<T, BTreeSet<T>>,
}
impl<T: Ord + Copy + Debug> Graph<T> {
pub fn new(entry: T, nodes: Vec<T>, edges: Vec<(T, T)>) -> Self {
let mut predecessors: BTreeMap<T, BTreeSet<T>> =
nodes.iter().map(|x| (*x, BTreeSet::new())).collect();
let mut successors: BTreeMap<T, BTreeSet<T>> =
nodes.iter().map(|x| (*x, BTreeSet::new())).collect();
for edge in &edges {
successors.entry(edge.0).and_modify(|x| {
x.insert(edge.1);
});
predecessors.entry(edge.1).and_modify(|x| {
x.insert(edge.0);
});
}
Self {
entry,
nodes,
edges,
predecessors,
successors,
}
}
pub fn compute_reducible(&self) -> Option<Vec<NaturalLoop<T>>> {
let dom_relation = DomRelation::new(self);
let mut loop_headers = BTreeSet::new();
let mut back_edges = vec![];
let mut non_back_edges = vec![];
for e in &self.edges {
if !dom_relation.is_reachable(e.0) {
continue;
}
if dom_relation.is_dominated_by(e.0, e.1) {
back_edges.push(*e);
loop_headers.insert(e.1);
} else {
non_back_edges.push(*e);
}
}
if Graph::new(self.entry, self.nodes.clone(), non_back_edges).is_acyclic() {
let natural_loops = back_edges
.into_iter()
.map(|edge| self.natural_loop(edge))
.collect();
Some(natural_loops)
} else {
None
}
}
fn is_acyclic(&self) -> bool {
let mut visited = BTreeMap::new();
let mut stack = vec![];
visited.insert(self.entry, false);
stack.push(self.entry);
while !stack.is_empty() {
let n = stack.pop().unwrap();
if visited[&n] {
visited.entry(n).and_modify(|x| {
*x = false;
});
continue;
}
stack.push(n);
visited.entry(n).and_modify(|x| {
*x = true;
});
for s in &self.successors[&n] {
if visited.contains_key(s) {
if visited[s] {
return false;
}
} else {
visited.insert(*s, false);
stack.push(*s);
}
}
}
true
}
fn natural_loop(&self, back_edge: (T, T)) -> NaturalLoop<T> {
let loop_latch = back_edge.0;
let loop_header = back_edge.1;
let mut stack = vec![];
let mut loop_body = BTreeSet::new();
loop_body.insert(loop_header);
if loop_latch != loop_header {
loop_body.insert(loop_latch);
stack.push(loop_latch);
}
while !stack.is_empty() {
let m = stack.pop().unwrap();
for p in &self.predecessors[&m] {
if !loop_body.contains(p) {
loop_body.insert(*p);
stack.push(*p);
}
}
}
NaturalLoop {
loop_header,
loop_latch,
loop_body,
}
}
}
struct DomRelation<T: Ord + Copy + Debug> {
node_to_postorder_num: BTreeMap<T, usize>,
postorder_num_to_node: Vec<T>,
idom_tree: BTreeMap<usize, usize>,
}
impl<T: Ord + Copy + Debug> DomRelation<T> {
pub fn new(graph: &Graph<T>) -> Self {
let mut dom_relation = Self {
node_to_postorder_num: BTreeMap::new(),
postorder_num_to_node: vec![],
idom_tree: BTreeMap::new(),
};
dom_relation.postorder_visit(graph);
dom_relation.compute_dominators(graph);
dom_relation
}
pub fn is_reachable(&self, x: T) -> bool {
self.node_to_postorder_num.contains_key(&x)
}
pub fn is_dominated_by(&self, x: T, y: T) -> bool {
let x_num = self.node_to_postorder_num[&x];
let y_num = self.node_to_postorder_num[&y];
let mut curr_num = x_num;
loop {
if curr_num == y_num {
return true;
}
if curr_num == self.entry_num() {
return false;
}
curr_num = self.idom_tree[&curr_num];
}
}
fn entry_num(&self) -> usize {
self.num_nodes() - 1
}
fn num_nodes(&self) -> usize {
self.node_to_postorder_num.len()
}
fn postorder_visit(&mut self, graph: &Graph<T>) {
let mut stack = vec![];
let mut visited = BTreeSet::new();
let mut grey = BTreeSet::new();
stack.push(graph.entry);
visited.insert(graph.entry);
while !stack.is_empty() {
let curr = stack.pop().unwrap();
if grey.contains(&curr) {
let curr_num = self.postorder_num_to_node.len();
self.postorder_num_to_node.push(curr);
self.node_to_postorder_num.insert(curr, curr_num);
} else {
grey.insert(curr);
stack.push(curr);
for child in &graph.successors[&curr] {
if !visited.contains(child) {
visited.insert(*child);
stack.push(*child);
}
}
}
}
}
fn compute_dominators(&mut self, graph: &Graph<T>) {
let entry_num = self.entry_num();
self.idom_tree.insert(entry_num, entry_num);
let mut changed = true;
while changed {
changed = false;
for node_num in (0..self.num_nodes() - 1).rev() {
let b = self.postorder_num_to_node[node_num];
let mut new_idom = self.num_nodes();
for p in &graph.predecessors[&b] {
if !self.node_to_postorder_num.contains_key(p) {
continue; }
let pred_num = self.node_to_postorder_num[p];
if self.idom_tree.contains_key(&pred_num) {
if new_idom == self.num_nodes() {
new_idom = pred_num;
} else {
new_idom = self.intersect(pred_num, new_idom);
}
}
}
assert!(new_idom != self.num_nodes());
if self.idom_tree.contains_key(&node_num) && self.idom_tree[&node_num] == new_idom {
continue;
} else {
self.idom_tree
.entry(node_num)
.and_modify(|x| {
*x = new_idom;
})
.or_insert(new_idom);
changed = true;
}
}
}
}
fn intersect(&self, x: usize, y: usize) -> usize {
let mut finger1 = x;
let mut finger2 = y;
while finger1 != finger2 {
while finger1 < finger2 {
finger1 = self.idom_tree[&finger1];
}
while finger2 < finger1 {
finger2 = self.idom_tree[&finger2];
}
}
finger1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test1() {
let nodes = vec![1, 2, 3, 4, 5];
let edges = vec![(1, 2), (2, 1), (4, 1), (3, 2), (5, 3), (5, 4)];
let source = 5;
let graph = Graph::new(source, nodes, edges);
assert!(graph.compute_reducible().is_none());
}
#[test]
fn test2() {
let nodes = vec![1, 2, 3, 4, 5, 6];
let edges = vec![
(1, 2),
(2, 1),
(2, 3),
(3, 2),
(4, 2),
(4, 3),
(5, 1),
(6, 5),
(6, 4),
];
let source = 6;
let graph = Graph::new(source, nodes, edges);
assert!(graph.compute_reducible().is_none());
}
#[test]
fn test3() {
let nodes = vec![1, 2, 3, 4, 5, 6];
let edges = vec![(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)];
let source = 1;
let graph = Graph::new(source, nodes, edges);
let natural_loops = graph.compute_reducible().unwrap();
assert_eq!(natural_loops.len(), 1);
let single_loop = &natural_loops[0];
assert_eq!(single_loop.loop_header, 2);
assert_eq!(single_loop.loop_latch, 5);
assert_eq!(
single_loop.loop_body,
vec![2, 3, 4, 5].into_iter().collect()
);
}
#[test]
fn test4() {
let nodes = vec![1, 2, 3, 4, 5, 6];
let edges = vec![(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)];
let source = 6;
let graph = Graph::new(source, nodes, edges);
assert!(graph.compute_reducible().is_some());
}
#[test]
fn test5() {
let nodes = vec![1, 2, 3, 4, 5, 6];
let edges = vec![
(1, 2),
(2, 3),
(2, 4),
(2, 6),
(3, 5),
(4, 5),
(5, 2),
(3, 2),
];
let source = 1;
let graph = Graph::new(source, nodes, edges);
let natural_loops = graph.compute_reducible().unwrap();
assert_eq!(natural_loops.len(), 2);
let (inner_loop, outer_loop) = if natural_loops[0].loop_latch == 3 {
assert_eq!(natural_loops[1].loop_latch, 5);
(&natural_loops[0], &natural_loops[1])
} else {
assert_eq!(natural_loops[0].loop_latch, 5);
assert_eq!(natural_loops[1].loop_latch, 3);
(&natural_loops[1], &natural_loops[0])
};
assert_eq!(inner_loop.loop_header, 2);
assert_eq!(inner_loop.loop_body, vec![2, 3].into_iter().collect());
assert_eq!(outer_loop.loop_header, 2);
assert_eq!(outer_loop.loop_body, vec![2, 3, 4, 5].into_iter().collect());
}
}