1 //! A loop analysis represented as mappings of loops to their header Block
2 //! and parent in the loop tree.
3 
4 use crate::dominator_tree::DominatorTree;
5 use crate::entity::entity_impl;
6 use crate::entity::SecondaryMap;
7 use crate::entity::{Keys, PrimaryMap};
8 use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
9 use crate::ir::{Block, Function, Layout};
10 use crate::packed_option::PackedOption;
11 use crate::timing;
12 use alloc::vec::Vec;
13 
14 /// A opaque reference to a code loop.
15 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
16 pub struct Loop(u32);
17 entity_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 block,
22 /// its eventual parent in the loop tree and all the block belonging to the loop.
23 pub struct LoopAnalysis {
24     loops: PrimaryMap<Loop, LoopData>,
25     block_loop_map: SecondaryMap<Block, PackedOption<Loop>>,
26     valid: bool,
27 }
28 
29 struct LoopData {
30     header: Block,
31     parent: PackedOption<Loop>,
32 }
33 
34 impl LoopData {
35     /// Creates a `LoopData` object with the loop header and its eventual parent in the loop tree.
new(header: Block, parent: Option<Loop>) -> Self36     pub fn new(header: Block, parent: Option<Loop>) -> Self {
37         Self {
38             header,
39             parent: parent.into(),
40         }
41     }
42 }
43 
44 /// Methods for querying the loop analysis.
45 impl LoopAnalysis {
46     /// Allocate a new blank loop analysis struct. Use `compute` to compute the loop analysis for
47     /// a function.
new() -> Self48     pub fn new() -> Self {
49         Self {
50             valid: false,
51             loops: PrimaryMap::new(),
52             block_loop_map: SecondaryMap::new(),
53         }
54     }
55 
56     /// Returns all the loops contained in a function.
loops(&self) -> Keys<Loop>57     pub fn loops(&self) -> Keys<Loop> {
58         self.loops.keys()
59     }
60 
61     /// Returns the header block of a particular loop.
62     ///
63     /// The characteristic property of a loop header block is that it dominates some of its
64     /// predecessors.
loop_header(&self, lp: Loop) -> Block65     pub fn loop_header(&self, lp: Loop) -> Block {
66         self.loops[lp].header
67     }
68 
69     /// Return the eventual parent of a loop in the loop tree.
loop_parent(&self, lp: Loop) -> Option<Loop>70     pub fn loop_parent(&self, lp: Loop) -> Option<Loop> {
71         self.loops[lp].parent.expand()
72     }
73 
74     /// Determine if a Block belongs to a loop by running a finger along the loop tree.
75     ///
76     /// Returns `true` if `block` is in loop `lp`.
is_in_loop(&self, block: Block, lp: Loop) -> bool77     pub fn is_in_loop(&self, block: Block, lp: Loop) -> bool {
78         let block_loop = self.block_loop_map[block];
79         match block_loop.expand() {
80             None => false,
81             Some(block_loop) => self.is_child_loop(block_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`).
is_child_loop(&self, child: Loop, parent: Loop) -> bool89     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 
101 impl LoopAnalysis {
102     /// Detects the loops in a function. Needs the control flow graph and the dominator tree.
compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree)103     pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) {
104         let _tt = timing::loop_analysis();
105         self.loops.clear();
106         self.block_loop_map.clear();
107         self.block_loop_map.resize(func.dfg.num_blocks());
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.
is_valid(&self) -> bool118     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.
clear(&mut self)125     pub fn clear(&mut self) {
126         self.loops.clear();
127         self.block_loop_map.clear();
128         self.valid = false;
129     }
130 
131     // Traverses the CFG in reverse postorder and create a loop object for every block having a
132     // back edge.
find_loop_headers( &mut self, cfg: &ControlFlowGraph, domtree: &DominatorTree, layout: &Layout, )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 &block in domtree.cfg_postorder().iter().rev() {
141             for BlockPredecessor {
142                 inst: pred_inst, ..
143             } in cfg.pred_iter(block)
144             {
145                 // If the block dominates one of its predecessors it is a back edge
146                 if domtree.dominates(block, pred_inst, layout) {
147                     // This block is a loop header, so we create its associated loop
148                     let lp = self.loops.push(LoopData::new(block, None));
149                     self.block_loop_map[block] = 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 block belonging to the loop and its inner loops. After a call to this
159     // function, the loop tree is fully constructed.
discover_loop_blocks( &mut self, cfg: &ControlFlowGraph, domtree: &DominatorTree, layout: &Layout, )160     fn discover_loop_blocks(
161         &mut self,
162         cfg: &ControlFlowGraph,
163         domtree: &DominatorTree,
164         layout: &Layout,
165     ) {
166         let mut stack: Vec<Block> = 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 BlockPredecessor {
171                 block: 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<Block>;
182                 match self.block_loop_map[node].expand() {
183                     None => {
184                         // The node hasn't been visited yet, we tag it as part of the loop
185                         self.block_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 BlockPredecessor { block: pred, .. } in cfg.pred_iter(continue_dfs) {
225                         stack.push(pred)
226                     }
227                 }
228             }
229         }
230     }
231 }
232 
233 #[cfg(test)]
234 mod 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]
nested_loops_detection()243     fn nested_loops_detection() {
244         let mut func = Function::new();
245         let block0 = func.dfg.make_block();
246         let block1 = func.dfg.make_block();
247         let block2 = func.dfg.make_block();
248         let block3 = func.dfg.make_block();
249         let cond = func.dfg.append_block_param(block0, types::I32);
250 
251         {
252             let mut cur = FuncCursor::new(&mut func);
253 
254             cur.insert_block(block0);
255             cur.ins().jump(block1, &[]);
256 
257             cur.insert_block(block1);
258             cur.ins().jump(block2, &[]);
259 
260             cur.insert_block(block2);
261             cur.ins().brnz(cond, block1, &[]);
262             cur.ins().jump(block3, &[]);
263 
264             cur.insert_block(block3);
265             cur.ins().brnz(cond, block0, &[]);
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]), block0);
278         assert_eq!(loop_analysis.loop_header(loops[1]), block1);
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(block0, loops[0]), true);
282         assert_eq!(loop_analysis.is_in_loop(block0, loops[1]), false);
283         assert_eq!(loop_analysis.is_in_loop(block1, loops[1]), true);
284         assert_eq!(loop_analysis.is_in_loop(block1, loops[0]), true);
285         assert_eq!(loop_analysis.is_in_loop(block2, loops[1]), true);
286         assert_eq!(loop_analysis.is_in_loop(block2, loops[0]), true);
287         assert_eq!(loop_analysis.is_in_loop(block3, loops[0]), true);
288         assert_eq!(loop_analysis.is_in_loop(block0, loops[1]), false);
289     }
290 
291     #[test]
complex_loop_detection()292     fn complex_loop_detection() {
293         let mut func = Function::new();
294         let block0 = func.dfg.make_block();
295         let block1 = func.dfg.make_block();
296         let block2 = func.dfg.make_block();
297         let block3 = func.dfg.make_block();
298         let block4 = func.dfg.make_block();
299         let block5 = func.dfg.make_block();
300         let cond = func.dfg.append_block_param(block0, types::I32);
301 
302         {
303             let mut cur = FuncCursor::new(&mut func);
304 
305             cur.insert_block(block0);
306             cur.ins().brnz(cond, block1, &[]);
307             cur.ins().jump(block3, &[]);
308 
309             cur.insert_block(block1);
310             cur.ins().jump(block2, &[]);
311 
312             cur.insert_block(block2);
313             cur.ins().brnz(cond, block1, &[]);
314             cur.ins().jump(block5, &[]);
315 
316             cur.insert_block(block3);
317             cur.ins().jump(block4, &[]);
318 
319             cur.insert_block(block4);
320             cur.ins().brnz(cond, block3, &[]);
321             cur.ins().jump(block5, &[]);
322 
323             cur.insert_block(block5);
324             cur.ins().brnz(cond, block0, &[]);
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]), block0);
337         assert_eq!(loop_analysis.loop_header(loops[1]), block1);
338         assert_eq!(loop_analysis.loop_header(loops[2]), block3);
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(block0, loops[0]), true);
343         assert_eq!(loop_analysis.is_in_loop(block1, loops[1]), true);
344         assert_eq!(loop_analysis.is_in_loop(block2, loops[1]), true);
345         assert_eq!(loop_analysis.is_in_loop(block3, loops[2]), true);
346         assert_eq!(loop_analysis.is_in_loop(block4, loops[2]), true);
347         assert_eq!(loop_analysis.is_in_loop(block5, loops[0]), true);
348     }
349 }
350