mapgraph/algo/
dom.rs

1//! Contains an implementation of an algorithm to build a map of immediate dominators in a
2//! [`Graph`](crate::Graph).
3
4use crate::{
5    graph::{iter::EdgesWalker, Edge, Edges, Node, Nodes},
6    map::{IntKeyMap, Map},
7    FrozenGraph,
8};
9use alloc::vec;
10use core::fmt::Debug;
11
12/// A trait for graph weights that store indices of their immediate dominators.
13pub trait WithImmDom {
14    /// The type for the immediate dominator index.
15    type Ty: Copy + Eq + Debug + 'static;
16
17    /// Returns the immediate dominator of a weight.
18    fn imm_dom(&self) -> Option<Self::Ty>;
19
20    /// Sets the immediate dominator of a weight.
21    fn set_imm_dom(&mut self, dom: Self::Ty);
22}
23
24fn calc_imm_dominators_impl<N, E, NI, EI, NM, EM>(
25    nodes: &mut Nodes<N, EI, NM>,
26    edges: &Edges<E, NI, EI, EM>,
27    node_index: NI,
28    entry_idx: NI,
29) where
30    N: WithImmDom<Ty = NI>,
31    NI: Copy + Eq + Debug + 'static,
32    EI: Copy + Eq + Debug + 'static,
33    NM: Map<Node<N, EI>, Key = NI>,
34    EM: IntKeyMap<Edge<E, NI, EI>, Key = EI>,
35{
36    let mut successors = nodes.walk_successors(node_index);
37
38    while let Some(successor_idx) = successors.walk_edges_next(edges) {
39        // In case we reach the entry block this is definitely a back edge (since there is no
40        // other way to reach the entry block twice).
41        if successor_idx == entry_idx {
42            //back_edges.insert(edge_idx);
43            continue;
44        }
45
46        match nodes[successor_idx].weight().imm_dom() {
47            None => {
48                // In case a successor has no immediate dominator it will be us (until some
49                // other predecessor of the block finds out).
50                nodes[successor_idx].weight_mut().set_imm_dom(node_index);
51                calc_imm_dominators_impl(nodes, edges, successor_idx, entry_idx);
52            }
53            Some(dominator) => {
54                // We should never visit the same path.
55                assert_ne!(dominator, node_index);
56
57                // TODO: Maybe it's a good idea to cache current dominance path for each block?
58                // Caching seems nice at first glance since it allows to avoid rebuilding the
59                // dominance path, however it introduces new costs:
60                // 1. The consumed memory.
61                // 2. Each time a block is found that has wrong immediate dominator all blocks
62                //    descending from it in the dominator tree must update their caches.
63                let mut left_idx = dominator;
64                let mut left_path = vec![dominator];
65                let mut right_idx = node_index;
66                let mut right_path = vec![node_index];
67                loop {
68                    // Left path is the path from the successor to the entry block. In case we
69                    // find an intersection with the right path we set it as the new immediate
70                    // dominator for the successor and exit the loop.
71                    if let Some(left_dominator) = nodes[left_idx].weight_mut().imm_dom() {
72                        if right_path.contains(&left_dominator) {
73                            nodes[successor_idx]
74                                .weight_mut()
75                                .set_imm_dom(left_dominator);
76                            break;
77                        }
78                        left_idx = left_dominator;
79                        left_path.push(left_dominator);
80                    }
81
82                    // Right path is the path from the current block to the entry block. In case
83                    // we find an intersection with the left path we set it as the new immediate
84                    // dominator for the successor and exit the loop. In case we find the
85                    // successor in the right path we have found a loop. Remember that info and
86                    // exit the loop without modifying anything.
87                    if let Some(right_dominator) = nodes[right_idx].weight_mut().imm_dom() {
88                        if right_dominator == successor_idx {
89                            //back_edges.insert(edge_idx);
90                            break;
91                        } else if left_path.contains(&right_dominator) {
92                            nodes[successor_idx]
93                                .weight_mut()
94                                .set_imm_dom(right_dominator);
95                            break;
96                        }
97                        right_idx = right_dominator;
98                        right_path.push(right_dominator);
99                    }
100                }
101            }
102        }
103    }
104}
105
106/// Calculates immediate dominators map for a graph.
107///
108/// The dominator indices are stored into node weights using the [`WithImmDom`] trait.
109pub fn calc_imm_dominators<N, E, NI, EI, NM, EM>(
110    graph: &mut FrozenGraph<N, E, NI, EI, NM, EM>,
111    entry_index: NI,
112) where
113    N: WithImmDom<Ty = NI>,
114    NI: Copy + Eq + Debug + 'static,
115    EI: Copy + Eq + Debug + 'static,
116    NM: Map<Node<N, EI>, Key = NI>,
117    EM: IntKeyMap<Edge<E, NI, EI>, Key = EI>,
118{
119    let (nodes, edges) = graph.as_nodes_mut_and_edges();
120    calc_imm_dominators_impl(nodes, edges, entry_index, entry_index)
121}
122
123#[cfg(all(test, feature = "slotmap"))]
124mod tests {
125    use super::*;
126    use crate::{aliases::SlotMapGraph, map::slotmap::NodeIndex};
127
128    struct TestNode(Option<NodeIndex>);
129
130    impl Default for TestNode {
131        fn default() -> Self {
132            TestNode(None)
133        }
134    }
135
136    impl WithImmDom for TestNode {
137        type Ty = NodeIndex;
138
139        fn imm_dom(&self) -> Option<Self::Ty> {
140            self.0
141        }
142
143        fn set_imm_dom(&mut self, dom: Self::Ty) {
144            self.0 = Some(dom);
145        }
146    }
147
148    #[test]
149    fn test_imm_dom_simple_diamond() {
150        let mut graph = SlotMapGraph::<TestNode, ()>::with_capacities(4, 4);
151        let entry_idx = graph.add_default_node();
152        let left_idx = graph.add_default_node();
153        let right_idx = graph.add_default_node();
154        let end_idx = graph.add_default_node();
155
156        graph.add_edge((), entry_idx, left_idx).unwrap();
157        graph.add_edge((), entry_idx, right_idx).unwrap();
158        graph.add_edge((), left_idx, end_idx).unwrap();
159        graph.add_edge((), right_idx, end_idx).unwrap();
160
161        calc_imm_dominators(&mut graph, entry_idx);
162
163        assert_eq!(graph.node_weight(entry_idx).unwrap().imm_dom(), None);
164        assert_eq!(
165            graph.node_weight(left_idx).unwrap().imm_dom(),
166            Some(entry_idx)
167        );
168        assert_eq!(
169            graph.node_weight(right_idx).unwrap().imm_dom(),
170            Some(entry_idx)
171        );
172        assert_eq!(
173            graph.node_weight(end_idx).unwrap().imm_dom(),
174            Some(entry_idx)
175        );
176    }
177
178    #[test]
179    fn test_imm_dom_simple_loop() {
180        let mut graph = SlotMapGraph::<TestNode, ()>::with_capacities(5, 5);
181        let entry_idx = graph.add_default_node();
182        let loop_entry_idx = graph.add_default_node();
183        let loop_body_idx = graph.add_default_node();
184        let loop_exit_idx = graph.add_default_node();
185        let end_idx = graph.add_default_node();
186
187        graph.add_edge((), entry_idx, loop_entry_idx).unwrap();
188        graph.add_edge((), loop_entry_idx, loop_body_idx).unwrap();
189        graph.add_edge((), loop_body_idx, loop_exit_idx).unwrap();
190        graph.add_edge((), loop_exit_idx, loop_entry_idx).unwrap();
191        graph.add_edge((), loop_entry_idx, end_idx).unwrap();
192
193        calc_imm_dominators(&mut graph, entry_idx);
194
195        assert_eq!(graph.node_weight(entry_idx).unwrap().imm_dom(), None);
196        assert_eq!(
197            graph.node_weight(loop_entry_idx).unwrap().imm_dom(),
198            Some(entry_idx)
199        );
200        assert_eq!(
201            graph.node_weight(loop_body_idx).unwrap().imm_dom(),
202            Some(loop_entry_idx)
203        );
204        assert_eq!(
205            graph.node_weight(loop_exit_idx).unwrap().imm_dom(),
206            Some(loop_body_idx)
207        );
208        assert_eq!(
209            graph.node_weight(end_idx).unwrap().imm_dom(),
210            Some(loop_entry_idx)
211        );
212    }
213
214    #[test]
215    fn test_imm_dom_nested_loop() {
216        let mut graph = SlotMapGraph::<TestNode, ()>::with_capacities(9, 10);
217        let entry_idx = graph.add_default_node();
218        let outer_loop_entry_idx = graph.add_default_node();
219        let outer_loop_start_idx = graph.add_default_node();
220        let inner_loop_entry_idx = graph.add_default_node();
221        let inner_loop_body_idx = graph.add_default_node();
222        let inner_loop_exit_idx = graph.add_default_node();
223        let outer_loop_end_idx = graph.add_default_node();
224        let outer_loop_exit_idx = graph.add_default_node();
225        let end_idx = graph.add_default_node();
226
227        graph.add_edge((), entry_idx, outer_loop_entry_idx).unwrap();
228        graph
229            .add_edge((), outer_loop_entry_idx, outer_loop_start_idx)
230            .unwrap();
231        graph.add_edge((), outer_loop_entry_idx, end_idx).unwrap();
232        graph
233            .add_edge((), outer_loop_start_idx, inner_loop_entry_idx)
234            .unwrap();
235        graph
236            .add_edge((), inner_loop_entry_idx, inner_loop_body_idx)
237            .unwrap();
238        graph
239            .add_edge((), inner_loop_entry_idx, outer_loop_end_idx)
240            .unwrap();
241        graph
242            .add_edge((), inner_loop_body_idx, inner_loop_exit_idx)
243            .unwrap();
244        graph
245            .add_edge((), inner_loop_exit_idx, inner_loop_entry_idx)
246            .unwrap();
247        graph
248            .add_edge((), outer_loop_end_idx, outer_loop_exit_idx)
249            .unwrap();
250        graph
251            .add_edge((), outer_loop_exit_idx, outer_loop_entry_idx)
252            .unwrap();
253
254        calc_imm_dominators(&mut graph, entry_idx);
255
256        assert_eq!(graph.node_weight(entry_idx).unwrap().imm_dom(), None);
257        assert_eq!(
258            graph.node_weight(outer_loop_entry_idx).unwrap().imm_dom(),
259            Some(entry_idx)
260        );
261        assert_eq!(
262            graph.node_weight(outer_loop_start_idx).unwrap().imm_dom(),
263            Some(outer_loop_entry_idx)
264        );
265        assert_eq!(
266            graph.node_weight(inner_loop_entry_idx).unwrap().imm_dom(),
267            Some(outer_loop_start_idx)
268        );
269        assert_eq!(
270            graph.node_weight(inner_loop_body_idx).unwrap().imm_dom(),
271            Some(inner_loop_entry_idx)
272        );
273        assert_eq!(
274            graph.node_weight(inner_loop_exit_idx).unwrap().imm_dom(),
275            Some(inner_loop_body_idx)
276        );
277        assert_eq!(
278            graph.node_weight(outer_loop_end_idx).unwrap().imm_dom(),
279            Some(inner_loop_entry_idx)
280        );
281        assert_eq!(
282            graph.node_weight(outer_loop_exit_idx).unwrap().imm_dom(),
283            Some(outer_loop_end_idx)
284        );
285        assert_eq!(
286            graph.node_weight(end_idx).unwrap().imm_dom(),
287            Some(outer_loop_entry_idx)
288        );
289    }
290}