Skip to main content

weavatrix_graph/algo/
dominators.rs

1use crate::IndexGraphView;
2use std::collections::HashMap;
3use std::hash::Hash;
4
5type Adjacency<Node> = Vec<Vec<Node>>;
6
7#[derive(Debug, Clone)]
8pub struct Dominators<Node> {
9    root: Node,
10    reachable: Vec<Node>,
11    immediate: HashMap<Node, Node>,
12}
13
14impl<Node> Dominators<Node>
15where
16    Node: Copy + Eq + Hash,
17{
18    #[must_use]
19    pub const fn root(&self) -> Node {
20        self.root
21    }
22
23    #[must_use]
24    pub fn reachable_nodes(&self) -> &[Node] {
25        &self.reachable
26    }
27
28    #[must_use]
29    pub fn immediate_dominator(&self, node: Node) -> Option<Node> {
30        (node != self.root)
31            .then(|| self.immediate.get(&node).copied())
32            .flatten()
33    }
34
35    pub fn dominators(&self, node: Node) -> Option<DominatorsIter<'_, Node>> {
36        self.is_reachable(node).then_some(DominatorsIter {
37            result: self,
38            next: Some(node),
39        })
40    }
41
42    pub fn strict_dominators(&self, node: Node) -> Option<DominatorsIter<'_, Node>> {
43        let mut result = self.dominators(node)?;
44        result.next();
45        Some(result)
46    }
47
48    #[must_use]
49    pub fn immediately_dominated_by(&self, node: Node) -> Vec<Node> {
50        self.reachable
51            .iter()
52            .copied()
53            .filter(|candidate| self.immediate_dominator(*candidate) == Some(node))
54            .collect()
55    }
56
57    #[must_use]
58    pub fn dominates(&self, dominator: Node, node: Node) -> bool {
59        self.dominators(node)
60            .is_some_and(|mut chain| chain.any(|candidate| candidate == dominator))
61    }
62
63    fn is_reachable(&self, node: Node) -> bool {
64        node == self.root || self.immediate.contains_key(&node)
65    }
66}
67
68pub struct DominatorsIter<'result, Node> {
69    result: &'result Dominators<Node>,
70    next: Option<Node>,
71}
72
73impl<Node> Iterator for DominatorsIter<'_, Node>
74where
75    Node: Copy + Eq + Hash,
76{
77    type Item = Node;
78
79    fn next(&mut self) -> Option<Self::Item> {
80        let node = self.next?;
81        self.next = (node != self.result.root)
82            .then(|| self.result.immediate.get(&node).copied())
83            .flatten();
84        Some(node)
85    }
86}
87
88#[must_use]
89pub fn dominators<G>(graph: &G, root: G::Node) -> Option<Dominators<G::Node>>
90where
91    G: IndexGraphView,
92{
93    dominators_filtered(graph, root, |_| true)
94}
95
96#[must_use]
97pub fn dominators_filtered<G, F>(
98    graph: &G,
99    root: G::Node,
100    allows_edge: F,
101) -> Option<Dominators<G::Node>>
102where
103    G: IndexGraphView,
104    F: Fn(G::Edge) -> bool,
105{
106    if !graph.contains_node(root) {
107        return None;
108    }
109    let (adjacency, predecessors) = adjacency_pair(graph, &allows_edge);
110    let postorder = reachable_postorder::<G>(root, &adjacency);
111    let reachable = postorder.iter().rev().copied().collect::<Vec<_>>();
112    let mut position = vec![None; graph.node_bound()];
113    for (index, &node) in reachable.iter().enumerate() {
114        position[G::node_slot(node)] = Some(index);
115    }
116    let mut immediate = vec![None; reachable.len()];
117    immediate[0] = Some(0);
118    let mut changed = true;
119    while changed {
120        changed = false;
121        for index in 1..reachable.len() {
122            let node = reachable[index];
123            let candidate = predecessors[G::node_slot(node)]
124                .iter()
125                .filter_map(|predecessor| position[G::node_slot(*predecessor)])
126                .find(|predecessor| immediate[*predecessor].is_some());
127            let Some(mut new_idom) = candidate else {
128                continue;
129            };
130            for predecessor in predecessors[G::node_slot(node)]
131                .iter()
132                .filter_map(|predecessor| position[G::node_slot(*predecessor)])
133                .filter(|predecessor| immediate[*predecessor].is_some())
134            {
135                new_idom = intersect(predecessor, new_idom, &immediate);
136            }
137            if immediate[index] != Some(new_idom) {
138                immediate[index] = Some(new_idom);
139                changed = true;
140            }
141        }
142    }
143    let immediate = reachable
144        .iter()
145        .copied()
146        .enumerate()
147        .skip(1)
148        .filter_map(|(index, node)| immediate[index].map(|parent| (node, reachable[parent])))
149        .collect();
150    Some(Dominators {
151        root,
152        reachable,
153        immediate,
154    })
155}
156
157fn adjacency_pair<G, F>(graph: &G, allows_edge: &F) -> (Adjacency<G::Node>, Adjacency<G::Node>)
158where
159    G: IndexGraphView,
160    F: Fn(G::Edge) -> bool,
161{
162    let mut adjacency = vec![Vec::new(); graph.node_bound()];
163    let mut predecessors = vec![Vec::new(); graph.node_bound()];
164    for (edge, endpoints) in graph.edge_references() {
165        if allows_edge(edge) {
166            adjacency[G::node_slot(endpoints.source())].push(endpoints.target());
167            predecessors[G::node_slot(endpoints.target())].push(endpoints.source());
168        }
169    }
170    (adjacency, predecessors)
171}
172
173fn reachable_postorder<G>(root: G::Node, adjacency: &[Vec<G::Node>]) -> Vec<G::Node>
174where
175    G: IndexGraphView,
176{
177    let mut seen = vec![false; adjacency.len()];
178    let mut order = Vec::new();
179    let mut stack = vec![(root, 0_usize)];
180    seen[G::node_slot(root)] = true;
181    while let Some((node, next)) = stack.last_mut() {
182        let Some(&target) = adjacency[G::node_slot(*node)].get(*next) else {
183            order.push(*node);
184            stack.pop();
185            continue;
186        };
187        *next += 1;
188        let slot = G::node_slot(target);
189        if !seen[slot] {
190            seen[slot] = true;
191            stack.push((target, 0));
192        }
193    }
194    order
195}
196
197fn intersect(mut left: usize, mut right: usize, immediate: &[Option<usize>]) -> usize {
198    while left != right {
199        while left > right {
200            left = immediate[left].expect("processed dominator");
201        }
202        while right > left {
203            right = immediate[right].expect("processed dominator");
204        }
205    }
206    left
207}