llvm_ir_analysis/
dominator_tree.rs

1use crate::control_flow_graph::{CFGNode, ControlFlowGraph};
2use llvm_ir::Name;
3use petgraph::prelude::{Dfs, DiGraphMap, Direction};
4use petgraph::visit::Walker;
5use std::cmp::Ordering;
6use std::collections::HashMap;
7
8/// The dominator tree for a particular function.
9///
10/// To construct a `DominatorTree`, use
11/// [`FunctionAnalysis`](struct.FunctionAnalysis.html), which you can get
12/// from [`ModuleAnalysis`](struct.ModuleAnalysis.html).
13pub struct DominatorTree<'m> {
14    /// The graph itself. An edge from bbX to bbY indicates that bbX is the
15    /// immediate dominator of bbY.
16    ///
17    /// That is:
18    ///   - bbX strictly dominates bbY, i.e., bbX appears on every control-flow
19    ///     path from the entry block to bbY (but bbX =/= bbY)
20    ///   - Of the blocks that strictly dominate bbY, bbX is the closest to bbY
21    ///     (farthest from entry) along paths from the entry block to bbY
22    pub(crate) graph: DiGraphMap<CFGNode<'m>, ()>,
23
24    /// Entry node for the function
25    pub(crate) entry_node: CFGNode<'m>,
26}
27
28/// The postdominator tree for a particular function.
29///
30/// To construct a `PostDominatorTree`, use
31/// [`FunctionAnalysis`](struct.FunctionAnalysis.html), which you can get
32/// from [`ModuleAnalysis`](struct.ModuleAnalysis.html).
33pub struct PostDominatorTree<'m> {
34    /// The graph itself. An edge from bbX to bbY indicates that bbX is the
35    /// immediate postdominator of bbY.
36    ///
37    /// That is:
38    ///   - bbX strictly postdominates bbY, i.e., bbX appears on every control-flow
39    ///     path from bbY to the function exit (but bbX =/= bbY)
40    ///   - Of the blocks that strictly postdominate bbY, bbX is the closest to bbY
41    ///     (farthest from exit) along paths from bbY to the function exit
42    pub(crate) graph: DiGraphMap<CFGNode<'m>, ()>,
43}
44
45/// Contains state used when constructing the `DominatorTree` or `PostDominatorTree`
46struct DomTreeBuilder<'m, 'a> {
47    /// The `ControlFlowGraph` we're working from
48    cfg: &'a ControlFlowGraph<'m>,
49
50    /// Map from `CFGNode` to its rpo number.
51    ///
52    /// Unreachable blocks won't be in this map; all reachable blocks will have
53    /// positive rpo numbers.
54    rpo_numbers: HashMap<CFGNode<'m>, usize>,
55
56    /// Map from `CFGNode` to the current estimate for its immediate dominator
57    /// (the entry node maps to `None`).
58    ///
59    /// Unreachable blocks won't be in this map.
60    idoms: HashMap<CFGNode<'m>, Option<CFGNode<'m>>>,
61}
62
63impl<'m, 'a> DomTreeBuilder<'m, 'a> {
64    /// Construct a new `DomTreeBuilder`.
65    ///
66    /// This will have no estimates for the immediate dominators.
67    fn new(cfg: &'a ControlFlowGraph<'m>) -> Self {
68        Self {
69            cfg,
70            rpo_numbers: Dfs::new(&cfg.graph, cfg.entry_node)
71                .iter(&cfg.graph)
72                .zip(1..)
73                .collect(),
74            idoms: HashMap::new(),
75        }
76    }
77
78    /// Build the dominator tree
79    fn build(mut self) -> DiGraphMap<CFGNode<'m>, ()> {
80        // algorithm heavily inspired by the domtree algorithm in Cranelift,
81        // which itself is Keith D. Cooper's "Simple, Fast, Dominator Algorithm"
82        // according to comments in Cranelift's code.
83
84        // first compute initial (preliminary) estimates for the immediate
85        // dominator of each block
86        for block in Dfs::new(&self.cfg.graph, self.cfg.entry_node).iter(&self.cfg.graph) {
87            self.idoms.insert(block, self.compute_idom(block));
88        }
89
90        let mut changed = true;
91        while changed {
92            changed = false;
93            for block in Dfs::new(&self.cfg.graph, self.cfg.entry_node).iter(&self.cfg.graph) {
94                let idom = self.compute_idom(block);
95                let prev_idom = self
96                    .idoms
97                    .get_mut(&block)
98                    .expect("All nodes in the dfs should have an initialized idom by now");
99                if idom != *prev_idom {
100                    *prev_idom = idom;
101                    changed = true;
102                }
103            }
104        }
105
106        DiGraphMap::from_edges(
107            self.idoms
108                .into_iter()
109                .filter_map(|(block, idom)| Some((idom?, block))),
110        )
111    }
112
113    /// Compute the immediate dominator for `block` using the current `idom`
114    /// states for the nodes.
115    ///
116    /// `block` must be reachable in the CFG. Returns `None` only for the entry
117    /// block.
118    fn compute_idom(&self, block: CFGNode<'m>) -> Option<CFGNode<'m>> {
119        if block == self.cfg.entry_node {
120            return None;
121        }
122        // technically speaking, these are just the reachable preds which already have an idom estimate
123        let mut reachable_preds = self
124            .cfg
125            .preds_as_nodes(block)
126            .filter(|block| self.idoms.contains_key(block));
127
128        let mut idom = reachable_preds
129            .next()
130            .expect("expected a reachable block to have at least one reachable predecessor");
131
132        for pred in reachable_preds {
133            idom = self.common_dominator(idom, pred);
134        }
135
136        Some(idom)
137    }
138
139    /// Compute the common dominator of two nodes.
140    ///
141    /// Both nodes are assumed to be reachable.
142    fn common_dominator(&self, mut node_a: CFGNode<'m>, mut node_b: CFGNode<'m>) -> CFGNode<'m> {
143        loop {
144            match self.rpo_numbers[&node_a].cmp(&self.rpo_numbers[&node_b]) {
145                Ordering::Less => {
146                    node_b = self.idoms[&node_b]
147                        .expect("entry node should have the smallest rpo number");
148                }
149                Ordering::Greater => {
150                    node_a = self.idoms[&node_a]
151                        .expect("entry node should have the smallest rpo number");
152                }
153                Ordering::Equal => break,
154            }
155        }
156
157        node_a
158    }
159}
160
161impl<'m> DominatorTree<'m> {
162    pub(crate) fn new(cfg: &ControlFlowGraph<'m>) -> Self {
163        Self {
164            graph: DomTreeBuilder::new(cfg).build(),
165            entry_node: cfg.entry_node,
166        }
167    }
168
169    /// Get the immediate dominator of the basic block with the given `Name`.
170    ///
171    /// This will be `None` for the entry block or for any unreachable blocks,
172    /// and `Some` for all other blocks.
173    ///
174    /// A block bbX is the immediate dominator of bbY if and only if:
175    ///   - bbX strictly dominates bbY, i.e., bbX appears on every control-flow
176    ///     path from the entry block to bbY (but bbX =/= bbY)
177    ///   - Of the blocks that strictly dominate bbY, bbX is the closest to bbY
178    ///     (farthest from entry) along paths from the entry block to bbY
179    pub fn idom(&self, block: &'m Name) -> Option<&'m Name> {
180        let mut parents = self
181            .graph
182            .neighbors_directed(CFGNode::Block(block), Direction::Incoming);
183        let idom = parents.next()?;
184        if let Some(_) = parents.next() {
185            panic!("Block {:?} should have only one immediate dominator", block);
186        }
187        match idom {
188            CFGNode::Block(block) => Some(block),
189            CFGNode::Return => {
190                panic!("Return node shouldn't be the immediate dominator of anything")
191            }
192        }
193    }
194
195    /// Get the immediate dominator of `CFGNode::Return`.
196    ///
197    /// This will be the block bbX such that:
198    ///   - bbX strictly dominates `CFGNode::Return`, i.e., bbX appears on every
199    ///     control-flow path through the function (but bbX =/= `CFGNode::Return`)
200    ///   - Of the blocks that strictly dominate `CFGNode::Return`, bbX is the
201    ///     closest to `CFGNode::Return` (farthest from entry) along paths through
202    ///     the function
203    ///
204    /// If the return node is unreachable (e.g., due to an infinite loop in the
205    /// function), then the return node has no immediate dominator, and `None` will
206    /// be returned.
207    pub fn idom_of_return(&self) -> Option<&'m Name> {
208        let mut parents = self
209            .graph
210            .neighbors_directed(CFGNode::Return, Direction::Incoming);
211        let idom = parents.next()?;
212        if let Some(_) = parents.next() {
213            panic!("Return node should have only one immediate dominator");
214        }
215        match idom {
216            CFGNode::Block(block) => Some(block),
217            CFGNode::Return => panic!("Return node shouldn't be its own immediate dominator"),
218        }
219    }
220
221    /// Get the children of the given basic block in the dominator tree, i.e.,
222    /// get all the blocks which are immediately dominated by `block`.
223    ///
224    /// See notes on `idom()`.
225    pub fn children<'s>(&'s self, block: &'m Name) -> impl Iterator<Item = CFGNode<'m>> + 's {
226        self.graph
227            .neighbors_directed(CFGNode::Block(block), Direction::Outgoing)
228    }
229
230    /// Does `node_a` dominate `node_b`?
231    ///
232    /// Note that every node dominates itself by definition, so if
233    /// `node_a == node_b`, this returns `true`.
234    /// See also `strictly_dominates()`
235    pub fn dominates(&self, node_a: CFGNode<'m>, node_b: CFGNode<'m>) -> bool {
236        petgraph::algo::has_path_connecting(&self.graph, node_a, node_b, None)
237    }
238
239    /// Does `node_a` strictly dominate `node_b`?
240    ///
241    /// This is the same as `dominates()`, except that if
242    /// `node_a == node_b`, this returns `false`.
243    pub fn strictly_dominates(&self, node_a: CFGNode<'m>, node_b: CFGNode<'m>) -> bool {
244        node_a != node_b && self.dominates(node_a, node_b)
245    }
246
247    /// Get the `Name` of the entry block for the function
248    pub fn entry(&self) -> &'m Name {
249        match self.entry_node {
250            CFGNode::Block(block) => block,
251            CFGNode::Return => panic!("Return node should not be entry"),
252        }
253    }
254}
255
256impl<'m> PostDominatorTree<'m> {
257    pub(crate) fn new(cfg: &ControlFlowGraph<'m>) -> Self {
258        // The postdominator relation for `cfg` is the dominator relation on
259        // the reversed `cfg` (Cytron et al, p. 477)
260
261        Self {
262            graph: DomTreeBuilder::new(&cfg.reversed()).build(),
263        }
264    }
265
266    /// Get the immediate postdominator of the basic block with the given `Name`.
267    ///
268    /// This will be `None` for unreachable blocks (or, in some cases, when the
269    /// function return is unreachable, e.g. due to an infinite loop), and `Some`
270    /// for all other blocks.
271    ///
272    /// A block bbX is the immediate postdominator of bbY if and only if:
273    ///   - bbX strictly postdominates bbY, i.e., bbX appears on every control-flow
274    ///     path from bbY to the function exit (but bbX =/= bbY)
275    ///   - Of the blocks that strictly postdominate bbY, bbX is the closest to bbY
276    ///     (farthest from exit) along paths from bbY to the function exit
277    ///
278    /// If the immediate postdominator is `CFGNode::Return`, that indicates that
279    /// there is no single basic block that postdominates the given block.
280    pub fn ipostdom(&self, block: &'m Name) -> Option<CFGNode<'m>> {
281        self.ipostdom_of_cfgnode(CFGNode::Block(block))
282    }
283
284    /// See notes on `ipostdom()`, but in addition, this will be `None` for
285    /// `CFGNode::Return`
286    pub(crate) fn ipostdom_of_cfgnode(&self, node: CFGNode<'m>) -> Option<CFGNode<'m>> {
287        let mut parents = self.graph.neighbors_directed(node, Direction::Incoming);
288        let ipostdom = parents.next()?;
289        if let Some(_) = parents.next() {
290            panic!(
291                "Block {:?} should have only one immediate postdominator",
292                node
293            );
294        }
295        Some(ipostdom)
296    }
297
298    /// Get the children of the given basic block in the postdominator tree, i.e.,
299    /// get all the blocks which are immediately postdominated by `block`.
300    ///
301    /// See notes on `ipostdom()`.
302    pub fn children<'s>(&'s self, block: &'m Name) -> impl Iterator<Item = CFGNode<'m>> + 's {
303        self.children_of_cfgnode(CFGNode::Block(block))
304    }
305
306    pub(crate) fn children_of_cfgnode<'s>(
307        &'s self,
308        node: CFGNode<'m>,
309    ) -> impl Iterator<Item = CFGNode<'m>> + 's {
310        self.graph.neighbors_directed(node, Direction::Outgoing)
311    }
312
313    /// Get the children of `CFGNode::Return` in the postdominator tree, i.e.,
314    /// get all the blocks which are immediately postdominated by `CFGNode::Return`.
315    ///
316    /// See notes on `ipostdom()`.
317    pub fn children_of_return<'s>(&'s self) -> impl Iterator<Item = &'m Name> + 's {
318        self.graph
319            .neighbors_directed(CFGNode::Return, Direction::Outgoing)
320            .map(|child| match child {
321                CFGNode::Block(block) => block,
322                CFGNode::Return => {
323                    panic!("Return node shouldn't be the immediate postdominator of itself")
324                }
325            })
326    }
327
328    /// Does `node_a` postdominate `node_b`?
329    ///
330    /// Note that every node postdominates itself by definition, so if
331    /// `node_a == node_b`, this returns `true`.
332    /// See also `strictly_postdominates()`
333    pub fn postdominates(&self, node_a: CFGNode<'m>, node_b: CFGNode<'m>) -> bool {
334        petgraph::algo::has_path_connecting(&self.graph, node_a, node_b, None)
335    }
336
337    /// Does `node_a` strictly postdominate `node_b`?
338    ///
339    /// This is the same as `postdominates()`, except that if
340    /// `node_a == node_b`, this returns `false`.
341    pub fn strictly_postdominates(&self, node_a: CFGNode<'m>, node_b: CFGNode<'m>) -> bool {
342        node_a != node_b && self.postdominates(node_a, node_b)
343    }
344}