Skip to main content

weavatrix_graph/algo/
dominators.rs

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