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