Skip to main content

cranelift_codegen/
loop_analysis.rs

1//! A loop analysis represented as mappings of loops to their header Ebb
2//! and parent in the loop tree.
3
4use crate::dominator_tree::DominatorTree;
5use crate::entity::entity_impl;
6use crate::entity::SecondaryMap;
7use crate::entity::{Keys, PrimaryMap};
8use crate::flowgraph::{BasicBlock, ControlFlowGraph};
9use crate::ir::{Ebb, Function, Layout};
10use crate::packed_option::PackedOption;
11use crate::timing;
12use alloc::vec::Vec;
13
14/// A opaque reference to a code loop.
15#[derive(Copy, Clone, PartialEq, Eq, Hash)]
16pub struct Loop(u32);
17entity_impl!(Loop, "loop");
18
19/// Loop tree information for a single function.
20///
21/// Loops are referenced by the Loop object, and for each loop you can access its header EBB,
22/// its eventual parent in the loop tree and all the EBB belonging to the loop.
23pub struct LoopAnalysis {
24    loops: PrimaryMap<Loop, LoopData>,
25    ebb_loop_map: SecondaryMap<Ebb, PackedOption<Loop>>,
26    valid: bool,
27}
28
29struct LoopData {
30    header: Ebb,
31    parent: PackedOption<Loop>,
32}
33
34impl LoopData {
35    /// Creates a `LoopData` object with the loop header and its eventual parent in the loop tree.
36    pub fn new(header: Ebb, parent: Option<Loop>) -> Self {
37        Self {
38            header,
39            parent: parent.into(),
40        }
41    }
42}
43
44/// Methods for querying the loop analysis.
45impl LoopAnalysis {
46    /// Allocate a new blank loop analysis struct. Use `compute` to compute the loop analysis for
47    /// a function.
48    pub fn new() -> Self {
49        Self {
50            valid: false,
51            loops: PrimaryMap::new(),
52            ebb_loop_map: SecondaryMap::new(),
53        }
54    }
55
56    /// Returns all the loops contained in a function.
57    pub fn loops(&self) -> Keys<Loop> {
58        self.loops.keys()
59    }
60
61    /// Returns the header EBB of a particular loop.
62    ///
63    /// The characteristic property of a loop header block is that it dominates some of its
64    /// predecessors.
65    pub fn loop_header(&self, lp: Loop) -> Ebb {
66        self.loops[lp].header
67    }
68
69    /// Return the eventual parent of a loop in the loop tree.
70    pub fn loop_parent(&self, lp: Loop) -> Option<Loop> {
71        self.loops[lp].parent.expand()
72    }
73
74    /// Determine if an Ebb belongs to a loop by running a finger along the loop tree.
75    ///
76    /// Returns `true` if `ebb` is in loop `lp`.
77    pub fn is_in_loop(&self, ebb: Ebb, lp: Loop) -> bool {
78        let ebb_loop = self.ebb_loop_map[ebb];
79        match ebb_loop.expand() {
80            None => false,
81            Some(ebb_loop) => self.is_child_loop(ebb_loop, lp),
82        }
83    }
84
85    /// Determines if a loop is contained in another loop.
86    ///
87    /// `is_child_loop(child,parent)` returns `true` if and only if `child` is a child loop of
88    /// `parent` (or `child == parent`).
89    pub fn is_child_loop(&self, child: Loop, parent: Loop) -> bool {
90        let mut finger = Some(child);
91        while let Some(finger_loop) = finger {
92            if finger_loop == parent {
93                return true;
94            }
95            finger = self.loop_parent(finger_loop);
96        }
97        false
98    }
99}
100
101impl LoopAnalysis {
102    /// Detects the loops in a function. Needs the control flow graph and the dominator tree.
103    pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) {
104        let _tt = timing::loop_analysis();
105        self.loops.clear();
106        self.ebb_loop_map.clear();
107        self.ebb_loop_map.resize(func.dfg.num_ebbs());
108        self.find_loop_headers(cfg, domtree, &func.layout);
109        self.discover_loop_blocks(cfg, domtree, &func.layout);
110        self.valid = true;
111    }
112
113    /// Check if the loop analysis is in a valid state.
114    ///
115    /// Note that this doesn't perform any kind of validity checks. It simply checks if the
116    /// `compute()` method has been called since the last `clear()`. It does not check that the
117    /// loop analysis is consistent with the CFG.
118    pub fn is_valid(&self) -> bool {
119        self.valid
120    }
121
122    /// Clear all the data structures contained in the loop analysis. This will leave the
123    /// analysis in a similar state to a context returned by `new()` except that allocated
124    /// memory be retained.
125    pub fn clear(&mut self) {
126        self.loops.clear();
127        self.ebb_loop_map.clear();
128        self.valid = false;
129    }
130
131    // Traverses the CFG in reverse postorder and create a loop object for every EBB having a
132    // back edge.
133    fn find_loop_headers(
134        &mut self,
135        cfg: &ControlFlowGraph,
136        domtree: &DominatorTree,
137        layout: &Layout,
138    ) {
139        // We traverse the CFG in reverse postorder
140        for &ebb in domtree.cfg_postorder().iter().rev() {
141            for BasicBlock {
142                inst: pred_inst, ..
143            } in cfg.pred_iter(ebb)
144            {
145                // If the ebb dominates one of its predecessors it is a back edge
146                if domtree.dominates(ebb, pred_inst, layout) {
147                    // This ebb is a loop header, so we create its associated loop
148                    let lp = self.loops.push(LoopData::new(ebb, None));
149                    self.ebb_loop_map[ebb] = lp.into();
150                    break;
151                    // We break because we only need one back edge to identify a loop header.
152                }
153            }
154        }
155    }
156
157    // Intended to be called after `find_loop_headers`. For each detected loop header,
158    // discovers all the ebb belonging to the loop and its inner loops. After a call to this
159    // function, the loop tree is fully constructed.
160    fn discover_loop_blocks(
161        &mut self,
162        cfg: &ControlFlowGraph,
163        domtree: &DominatorTree,
164        layout: &Layout,
165    ) {
166        let mut stack: Vec<Ebb> = Vec::new();
167        // We handle each loop header in reverse order, corresponding to a pseudo postorder
168        // traversal of the graph.
169        for lp in self.loops().rev() {
170            for BasicBlock {
171                ebb: pred,
172                inst: pred_inst,
173            } in cfg.pred_iter(self.loops[lp].header)
174            {
175                // We follow the back edges
176                if domtree.dominates(self.loops[lp].header, pred_inst, layout) {
177                    stack.push(pred);
178                }
179            }
180            while let Some(node) = stack.pop() {
181                let continue_dfs: Option<Ebb>;
182                match self.ebb_loop_map[node].expand() {
183                    None => {
184                        // The node hasn't been visited yet, we tag it as part of the loop
185                        self.ebb_loop_map[node] = PackedOption::from(lp);
186                        continue_dfs = Some(node);
187                    }
188                    Some(node_loop) => {
189                        // We copy the node_loop into a mutable reference passed along the while
190                        let mut node_loop = node_loop;
191                        // The node is part of a loop, which can be lp or an inner loop
192                        let mut node_loop_parent_option = self.loops[node_loop].parent;
193                        while let Some(node_loop_parent) = node_loop_parent_option.expand() {
194                            if node_loop_parent == lp {
195                                // We have encountered lp so we stop (already visited)
196                                break;
197                            } else {
198                                //
199                                node_loop = node_loop_parent;
200                                // We lookup the parent loop
201                                node_loop_parent_option = self.loops[node_loop].parent;
202                            }
203                        }
204                        // Now node_loop_parent is either:
205                        // - None and node_loop is an new inner loop of lp
206                        // - Some(...) and the initial node_loop was a known inner loop of lp
207                        match node_loop_parent_option.expand() {
208                            Some(_) => continue_dfs = None,
209                            None => {
210                                if node_loop != lp {
211                                    self.loops[node_loop].parent = lp.into();
212                                    continue_dfs = Some(self.loops[node_loop].header)
213                                } else {
214                                    // If lp is a one-block loop then we make sure we stop
215                                    continue_dfs = None
216                                }
217                            }
218                        }
219                    }
220                }
221                // Now we have handled the popped node and need to continue the DFS by adding the
222                // predecessors of that node
223                if let Some(continue_dfs) = continue_dfs {
224                    for BasicBlock { ebb: pred, .. } in cfg.pred_iter(continue_dfs) {
225                        stack.push(pred)
226                    }
227                }
228            }
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use crate::cursor::{Cursor, FuncCursor};
236    use crate::dominator_tree::DominatorTree;
237    use crate::flowgraph::ControlFlowGraph;
238    use crate::ir::{types, Function, InstBuilder};
239    use crate::loop_analysis::{Loop, LoopAnalysis};
240    use alloc::vec::Vec;
241
242    #[test]
243    fn nested_loops_detection() {
244        let mut func = Function::new();
245        let ebb0 = func.dfg.make_ebb();
246        let ebb1 = func.dfg.make_ebb();
247        let ebb2 = func.dfg.make_ebb();
248        let ebb3 = func.dfg.make_ebb();
249        let cond = func.dfg.append_ebb_param(ebb0, types::I32);
250
251        {
252            let mut cur = FuncCursor::new(&mut func);
253
254            cur.insert_ebb(ebb0);
255            cur.ins().jump(ebb1, &[]);
256
257            cur.insert_ebb(ebb1);
258            cur.ins().jump(ebb2, &[]);
259
260            cur.insert_ebb(ebb2);
261            cur.ins().brnz(cond, ebb1, &[]);
262            cur.ins().jump(ebb3, &[]);
263
264            cur.insert_ebb(ebb3);
265            cur.ins().brnz(cond, ebb0, &[]);
266        }
267
268        let mut loop_analysis = LoopAnalysis::new();
269        let mut cfg = ControlFlowGraph::new();
270        let mut domtree = DominatorTree::new();
271        cfg.compute(&func);
272        domtree.compute(&func, &cfg);
273        loop_analysis.compute(&func, &cfg, &domtree);
274
275        let loops = loop_analysis.loops().collect::<Vec<Loop>>();
276        assert_eq!(loops.len(), 2);
277        assert_eq!(loop_analysis.loop_header(loops[0]), ebb0);
278        assert_eq!(loop_analysis.loop_header(loops[1]), ebb1);
279        assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0]));
280        assert_eq!(loop_analysis.loop_parent(loops[0]), None);
281        assert_eq!(loop_analysis.is_in_loop(ebb0, loops[0]), true);
282        assert_eq!(loop_analysis.is_in_loop(ebb0, loops[1]), false);
283        assert_eq!(loop_analysis.is_in_loop(ebb1, loops[1]), true);
284        assert_eq!(loop_analysis.is_in_loop(ebb1, loops[0]), true);
285        assert_eq!(loop_analysis.is_in_loop(ebb2, loops[1]), true);
286        assert_eq!(loop_analysis.is_in_loop(ebb2, loops[0]), true);
287        assert_eq!(loop_analysis.is_in_loop(ebb3, loops[0]), true);
288        assert_eq!(loop_analysis.is_in_loop(ebb0, loops[1]), false);
289    }
290
291    #[test]
292    fn complex_loop_detection() {
293        let mut func = Function::new();
294        let ebb0 = func.dfg.make_ebb();
295        let ebb1 = func.dfg.make_ebb();
296        let ebb2 = func.dfg.make_ebb();
297        let ebb3 = func.dfg.make_ebb();
298        let ebb4 = func.dfg.make_ebb();
299        let ebb5 = func.dfg.make_ebb();
300        let cond = func.dfg.append_ebb_param(ebb0, types::I32);
301
302        {
303            let mut cur = FuncCursor::new(&mut func);
304
305            cur.insert_ebb(ebb0);
306            cur.ins().brnz(cond, ebb1, &[]);
307            cur.ins().jump(ebb3, &[]);
308
309            cur.insert_ebb(ebb1);
310            cur.ins().jump(ebb2, &[]);
311
312            cur.insert_ebb(ebb2);
313            cur.ins().brnz(cond, ebb1, &[]);
314            cur.ins().jump(ebb5, &[]);
315
316            cur.insert_ebb(ebb3);
317            cur.ins().jump(ebb4, &[]);
318
319            cur.insert_ebb(ebb4);
320            cur.ins().brnz(cond, ebb3, &[]);
321            cur.ins().jump(ebb5, &[]);
322
323            cur.insert_ebb(ebb5);
324            cur.ins().brnz(cond, ebb0, &[]);
325        }
326
327        let mut loop_analysis = LoopAnalysis::new();
328        let mut cfg = ControlFlowGraph::new();
329        let mut domtree = DominatorTree::new();
330        cfg.compute(&func);
331        domtree.compute(&func, &cfg);
332        loop_analysis.compute(&func, &cfg, &domtree);
333
334        let loops = loop_analysis.loops().collect::<Vec<Loop>>();
335        assert_eq!(loops.len(), 3);
336        assert_eq!(loop_analysis.loop_header(loops[0]), ebb0);
337        assert_eq!(loop_analysis.loop_header(loops[1]), ebb1);
338        assert_eq!(loop_analysis.loop_header(loops[2]), ebb3);
339        assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0]));
340        assert_eq!(loop_analysis.loop_parent(loops[2]), Some(loops[0]));
341        assert_eq!(loop_analysis.loop_parent(loops[0]), None);
342        assert_eq!(loop_analysis.is_in_loop(ebb0, loops[0]), true);
343        assert_eq!(loop_analysis.is_in_loop(ebb1, loops[1]), true);
344        assert_eq!(loop_analysis.is_in_loop(ebb2, loops[1]), true);
345        assert_eq!(loop_analysis.is_in_loop(ebb3, loops[2]), true);
346        assert_eq!(loop_analysis.is_in_loop(ebb4, loops[2]), true);
347        assert_eq!(loop_analysis.is_in_loop(ebb5, loops[0]), true);
348    }
349}