Skip to main content

petgraph/algo/
dominators.rs

1//! Compute dominators of a control-flow graph.
2//!
3//! # The Dominance Relation
4//!
5//! In a directed graph with a root node **R**, a node **A** is said to *dominate* a
6//! node **B** iff every path from **R** to **B** contains **A**.
7//!
8//! The node **A** is said to *strictly dominate* the node **B** iff **A** dominates
9//! **B** and **A ≠ B**.
10//!
11//! The node **A** is said to be the *immediate dominator* of a node **B** iff it
12//! strictly dominates **B** and there does not exist any node **C** where **A**
13//! dominates **C** and **C** dominates **B**.
14
15#[cfg(not(feature = "std"))]
16use alloc::{
17    collections::{BTreeSet as HashSet, BTreeMap as HashMap, btree_map::Iter},
18    vec::Vec,
19};
20
21#[cfg(not(feature = "std"))]
22use core::{cmp::Ordering, hash::Hash, usize, iter::Iterator};
23
24#[cfg(feature = "std")]
25use std::{
26    cmp::Ordering,
27    collections::{HashMap, HashSet, hash_map::Iter},
28    hash::Hash,
29    usize,
30};
31
32use crate::visit::{DfsPostOrder, GraphBase, IntoNeighbors, Visitable, Walker};
33
34/// The dominance relation for some graph and root.
35#[derive(Debug, Clone)]
36pub struct Dominators<N>
37where
38    N: Copy + Eq + Hash,
39{
40    root: N,
41    dominators: HashMap<N, N>,
42}
43
44impl<N> Dominators<N>
45where
46    N: Copy + Eq + Hash + Ord,
47{
48    /// Get the root node used to construct these dominance relations.
49    pub fn root(&self) -> N {
50        self.root
51    }
52
53    /// Get the immediate dominator of the given node.
54    ///
55    /// Returns `None` for any node that is not reachable from the root, and for
56    /// the root itself.
57    pub fn immediate_dominator(&self, node: N) -> Option<N> {
58        if node == self.root {
59            None
60        } else {
61            self.dominators.get(&node).cloned()
62        }
63    }
64
65    /// Iterate over the given node's that strict dominators.
66    ///
67    /// If the given node is not reachable from the root, then `None` is
68    /// returned.
69    pub fn strict_dominators(&self, node: N) -> Option<DominatorsIter<N>> {
70        if self.dominators.contains_key(&node) {
71            Some(DominatorsIter {
72                dominators: self,
73                node: self.immediate_dominator(node),
74            })
75        } else {
76            None
77        }
78    }
79
80    /// Iterate over all of the given node's dominators (including the given
81    /// node itself).
82    ///
83    /// If the given node is not reachable from the root, then `None` is
84    /// returned.
85    pub fn dominators(&self, node: N) -> Option<DominatorsIter<N>> {
86        if self.dominators.contains_key(&node) {
87            Some(DominatorsIter {
88                dominators: self,
89                node: Some(node),
90            })
91        } else {
92            None
93        }
94    }
95
96    /// Iterate over all nodes immediately dominated by the given node (not
97    /// including the given node itself).
98    pub fn immediately_dominated_by(&self, node: N) -> DominatedByIter<N> {
99        DominatedByIter {
100            iter: self.dominators.iter(),
101            node: node
102        }
103    }
104}
105
106/// Iterator for a node's dominators.
107pub struct DominatorsIter<'a, N>
108where
109    N: 'a + Copy + Eq + Hash,
110{
111    dominators: &'a Dominators<N>,
112    node: Option<N>,
113}
114
115impl<'a, N> Iterator for DominatorsIter<'a, N>
116where
117    N: 'a + Copy + Eq + Hash + Ord,
118{
119    type Item = N;
120
121    fn next(&mut self) -> Option<Self::Item> {
122        let next = self.node.take();
123        if let Some(next) = next {
124            self.node = self.dominators.immediate_dominator(next);
125        }
126        next
127    }
128}
129
130/// Iterator for nodes dominated by a given node.
131pub struct DominatedByIter<'a, N>
132where
133    N: 'a + Copy + Eq + Hash,
134{
135    iter: Iter<'a, N, N>,
136    node: N,
137}
138
139impl<'a, N> Iterator for DominatedByIter<'a, N>
140where
141    N: 'a + Copy + Eq + Hash,
142{
143    type Item = N;
144
145    fn next(&mut self) -> Option<Self::Item> {
146        while let Some(next) = self.iter.next() {
147            if next.1 == &self.node {
148                return Some(*next.0);
149            }
150        }
151        None
152    }
153}
154
155/// The undefined dominator sentinel, for when we have not yet discovered a
156/// node's dominator.
157const UNDEFINED: usize = usize::MAX;
158
159/// This is an implementation of the engineered ["Simple, Fast Dominance
160/// Algorithm"][0] discovered by Cooper et al.
161///
162/// This algorithm is **O(|V|²)**, and therefore has slower theoretical running time
163/// than the Lengauer-Tarjan algorithm (which is **O(|E| log |V|)**. However,
164/// Cooper et al found it to be faster in practice on control flow graphs of up
165/// to ~30,000 vertices.
166///
167/// [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf
168pub fn simple_fast<G>(graph: G, root: G::NodeId) -> Dominators<G::NodeId>
169where
170    G: IntoNeighbors + Visitable,
171    <G as GraphBase>::NodeId: Eq + Hash + Ord,
172{
173    let (post_order, predecessor_sets) = simple_fast_post_order(graph, root);
174    let length = post_order.len();
175    debug_assert!(length > 0);
176    debug_assert!(post_order.last() == Some(&root));
177
178    // From here on out we use indices into `post_order` instead of actual
179    // `NodeId`s wherever possible. This greatly improves the performance of
180    // this implementation, but we have to pay a little bit of upfront cost to
181    // convert our data structures to play along first.
182
183    // Maps a node to its index into `post_order`.
184    let node_to_post_order_idx: HashMap<_, _> = post_order
185        .iter()
186        .enumerate()
187        .map(|(idx, &node)| (node, idx))
188        .collect();
189
190    // Maps a node's `post_order` index to its set of predecessors's indices
191    // into `post_order` (as a vec).
192    let idx_to_predecessor_vec =
193        predecessor_sets_to_idx_vecs(&post_order, &node_to_post_order_idx, predecessor_sets);
194
195    let mut dominators = vec![UNDEFINED; length];
196    dominators[length - 1] = length - 1;
197
198    let mut changed = true;
199    while changed {
200        changed = false;
201
202        // Iterate in reverse post order, skipping the root.
203
204        for idx in (0..length - 1).rev() {
205            debug_assert!(post_order[idx] != root);
206
207            // Take the intersection of every predecessor's dominator set; that
208            // is the current best guess at the immediate dominator for this
209            // node.
210
211            let new_idom_idx = {
212                let mut predecessors = idx_to_predecessor_vec[idx]
213                    .iter()
214                    .filter(|&&p| dominators[p] != UNDEFINED);
215                let new_idom_idx = predecessors.next().expect(
216                    "Because the root is initialized to dominate itself, and is the \
217                     first node in every path, there must exist a predecessor to this \
218                     node that also has a dominator",
219                );
220                predecessors.fold(*new_idom_idx, |new_idom_idx, &predecessor_idx| {
221                    intersect(&dominators, new_idom_idx, predecessor_idx)
222                })
223            };
224
225            debug_assert!(new_idom_idx < length);
226
227            if new_idom_idx != dominators[idx] {
228                dominators[idx] = new_idom_idx;
229                changed = true;
230            }
231        }
232    }
233
234    // All done! Translate the indices back into proper `G::NodeId`s.
235
236    debug_assert!(!dominators.iter().any(|&dom| dom == UNDEFINED));
237
238    Dominators {
239        root: root,
240        dominators: dominators
241            .into_iter()
242            .enumerate()
243            .map(|(idx, dom_idx)| (post_order[idx], post_order[dom_idx]))
244            .collect(),
245    }
246}
247
248fn intersect(dominators: &[usize], mut finger1: usize, mut finger2: usize) -> usize {
249    loop {
250        match finger1.cmp(&finger2) {
251            Ordering::Less => finger1 = dominators[finger1],
252            Ordering::Greater => finger2 = dominators[finger2],
253            Ordering::Equal => return finger1,
254        }
255    }
256}
257
258#[cfg(feature = "std")]
259fn predecessor_sets_to_idx_vecs<N>(
260    post_order: &[N],
261    node_to_post_order_idx: &HashMap<N, usize>,
262    mut predecessor_sets: HashMap<N, HashSet<N>>,
263) -> Vec<Vec<usize>>
264where
265    N: Copy + Eq + Hash,
266{
267    post_order
268        .iter()
269        .map(|node| {
270            predecessor_sets
271                .remove(node)
272                .map(|predecessors| {
273                    predecessors
274                        .into_iter()
275                        .map(|p| *node_to_post_order_idx.get(&p).unwrap())
276                        .collect()
277                })
278                .unwrap_or_else(Vec::new)
279        })
280        .collect()
281}
282
283#[cfg(not(feature = "std"))]
284fn predecessor_sets_to_idx_vecs<N>(
285    post_order: &[N],
286    node_to_post_order_idx: &HashMap<N, usize>,
287    mut predecessor_sets: HashMap<N, HashSet<N>>,
288) -> Vec<Vec<usize>>
289where
290    N: Copy + Eq + Hash + Ord,
291{
292    post_order
293        .iter()
294        .map(|node| {
295            predecessor_sets
296                .remove(node)
297                .map(|predecessors| {
298                    predecessors
299                        .into_iter()
300                        .map(|p| *node_to_post_order_idx.get(&p).unwrap())
301                        .collect()
302                })
303                .unwrap_or_else(Vec::new)
304        })
305        .collect()
306}
307
308#[cfg(feature = "std")]
309fn simple_fast_post_order<G>(
310    graph: G,
311    root: G::NodeId,
312) -> (Vec<G::NodeId>, HashMap<G::NodeId, HashSet<G::NodeId>>)
313where
314    G: IntoNeighbors + Visitable,
315    <G as GraphBase>::NodeId: Eq + Hash,
316{
317    let mut post_order = vec![];
318    let mut predecessor_sets = HashMap::new();
319
320    for node in DfsPostOrder::new(graph, root).iter(graph) {
321        post_order.push(node);
322
323        for successor in graph.neighbors(node) {
324            predecessor_sets
325                .entry(successor)
326                .or_insert_with(HashSet::new)
327                .insert(node);
328        }
329    }
330
331    (post_order, predecessor_sets)
332}
333
334#[cfg(not(feature = "std"))]
335fn simple_fast_post_order<G>(
336    graph: G,
337    root: G::NodeId,
338) -> (Vec<G::NodeId>, HashMap<G::NodeId, HashSet<G::NodeId>>)
339where
340    G: IntoNeighbors + Visitable,
341    <G as GraphBase>::NodeId: Eq + Hash + Ord,
342{
343    let mut post_order = vec![];
344    let mut predecessor_sets = HashMap::new();
345
346    for node in DfsPostOrder::new(graph, root).iter(graph) {
347        post_order.push(node);
348
349        for successor in graph.neighbors(node) {
350            predecessor_sets
351                .entry(successor)
352                .or_insert_with(HashSet::new)
353                .insert(node);
354        }
355    }
356
357    (post_order, predecessor_sets)
358}
359
360#[cfg(test)]
361#[cfg(not(feature = "std"))]
362mod tests {
363    use super::*;
364
365    #[test]
366    fn test_iter_dominators() {
367        let doms: Dominators<u32> = Dominators {
368            root: 0,
369            dominators: [(2, 1), (1, 0), (0, 0)].iter().cloned().collect(),
370        };
371
372        let all_doms: Vec<_> = doms.dominators(2).unwrap().collect();
373        assert_eq!(vec![2, 1, 0], all_doms);
374
375        assert_eq!(None::<()>, doms.dominators(99).map(|_| unreachable!()));
376
377        let strict_doms: Vec<_> = doms.strict_dominators(2).unwrap().collect();
378        assert_eq!(vec![1, 0], strict_doms);
379
380        assert_eq!(
381            None::<()>,
382            doms.strict_dominators(99).map(|_| unreachable!())
383        );
384
385        let dom_by: Vec<_> = doms.immediately_dominated_by(1).collect();
386        assert_eq!(vec![2], dom_by);
387        assert_eq!(None, doms.immediately_dominated_by(99).next());
388    }
389}