Path: blob/main/cranelift/codegen/src/loop_analysis.rs
1693 views
//! A loop analysis represented as mappings of loops to their header Block1//! and parent in the loop tree.23use crate::dominator_tree::DominatorTree;4use crate::entity::SecondaryMap;5use crate::entity::entity_impl;6use crate::entity::{Keys, PrimaryMap};7use crate::flowgraph::ControlFlowGraph;8use crate::ir::{Block, Function};9use crate::packed_option::PackedOption;10use crate::timing;11use alloc::vec::Vec;12use smallvec::SmallVec;1314/// A opaque reference to a code loop.15#[derive(Copy, Clone, PartialEq, Eq, Hash)]16pub struct Loop(u32);17entity_impl!(Loop, "loop");1819/// 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.23pub struct LoopAnalysis {24loops: PrimaryMap<Loop, LoopData>,25block_loop_map: SecondaryMap<Block, PackedOption<Loop>>,26valid: bool,27}2829struct LoopData {30header: Block,31parent: PackedOption<Loop>,32level: LoopLevel,33}3435/// A level in a loop nest.36#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]37pub struct LoopLevel(u8);38impl LoopLevel {39const INVALID: u8 = u8::MAX;4041/// Get the root level (no loop).42pub fn root() -> Self {43Self(0)44}45/// Get the loop level.46pub fn level(self) -> usize {47self.0 as usize48}49/// Invalid loop level.50pub fn invalid() -> Self {51Self(Self::INVALID)52}53/// One loop level deeper.54pub fn inc(self) -> Self {55if self.0 == (Self::INVALID - 1) {56self57} else {58Self(self.0 + 1)59}60}61/// A clamped loop level from a larger-width (usize) depth.62pub fn clamped(level: usize) -> Self {63Self(64u8::try_from(std::cmp::min(level, (Self::INVALID as usize) - 1))65.expect("Clamped value must always convert"),66)67}68}6970impl std::default::Default for LoopLevel {71fn default() -> Self {72LoopLevel::invalid()73}74}7576impl LoopData {77/// Creates a `LoopData` object with the loop header and its eventual parent in the loop tree.78pub fn new(header: Block, parent: Option<Loop>) -> Self {79Self {80header,81parent: parent.into(),82level: LoopLevel::invalid(),83}84}85}8687/// Methods for querying the loop analysis.88impl LoopAnalysis {89/// Allocate a new blank loop analysis struct. Use `compute` to compute the loop analysis for90/// a function.91pub fn new() -> Self {92Self {93valid: false,94loops: PrimaryMap::new(),95block_loop_map: SecondaryMap::new(),96}97}9899/// Returns all the loops contained in a function.100pub fn loops(&self) -> Keys<Loop> {101self.loops.keys()102}103104/// Returns the header block of a particular loop.105///106/// The characteristic property of a loop header block is that it dominates some of its107/// predecessors.108pub fn loop_header(&self, lp: Loop) -> Block {109self.loops[lp].header110}111112/// Return the eventual parent of a loop in the loop tree.113pub fn loop_parent(&self, lp: Loop) -> Option<Loop> {114self.loops[lp].parent.expand()115}116117/// Return the innermost loop for a given block.118pub fn innermost_loop(&self, block: Block) -> Option<Loop> {119self.block_loop_map[block].expand()120}121122/// Determine if a Block is a loop header. If so, return the loop.123pub fn is_loop_header(&self, block: Block) -> Option<Loop> {124self.innermost_loop(block)125.filter(|&lp| self.loop_header(lp) == block)126}127128/// Determine if a Block belongs to a loop by running a finger along the loop tree.129///130/// Returns `true` if `block` is in loop `lp`.131pub fn is_in_loop(&self, block: Block, lp: Loop) -> bool {132let block_loop = self.block_loop_map[block];133match block_loop.expand() {134None => false,135Some(block_loop) => self.is_child_loop(block_loop, lp),136}137}138139/// Determines if a loop is contained in another loop.140///141/// `is_child_loop(child,parent)` returns `true` if and only if `child` is a child loop of142/// `parent` (or `child == parent`).143pub fn is_child_loop(&self, child: Loop, parent: Loop) -> bool {144let mut finger = Some(child);145while let Some(finger_loop) = finger {146if finger_loop == parent {147return true;148}149finger = self.loop_parent(finger_loop);150}151false152}153154/// Returns the loop-nest level of a given block.155pub fn loop_level(&self, block: Block) -> LoopLevel {156self.innermost_loop(block)157.map_or(LoopLevel(0), |lp| self.loops[lp].level)158}159}160161impl LoopAnalysis {162/// Detects the loops in a function. Needs the control flow graph and the dominator tree.163pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) {164let _tt = timing::loop_analysis();165self.loops.clear();166self.block_loop_map.clear();167self.block_loop_map.resize(func.dfg.num_blocks());168self.find_loop_headers(cfg, domtree);169self.discover_loop_blocks(cfg, domtree);170self.assign_loop_levels();171self.valid = true;172}173174/// Check if the loop analysis is in a valid state.175///176/// Note that this doesn't perform any kind of validity checks. It simply checks if the177/// `compute()` method has been called since the last `clear()`. It does not check that the178/// loop analysis is consistent with the CFG.179pub fn is_valid(&self) -> bool {180self.valid181}182183/// Clear all the data structures contained in the loop analysis. This will leave the184/// analysis in a similar state to a context returned by `new()` except that allocated185/// memory be retained.186pub fn clear(&mut self) {187self.loops.clear();188self.block_loop_map.clear();189self.valid = false;190}191192// Determines if a block dominates any predecessor193// and thus is a loop header.194fn is_block_loop_header(block: Block, cfg: &ControlFlowGraph, domtree: &DominatorTree) -> bool {195// A block is a loop header if it dominates any of its predecessors.196cfg.pred_iter(block)197.any(|pred| domtree.block_dominates(block, pred.block))198}199200// Traverses the CFG in reverse postorder and create a loop object for every block having a201// back edge.202fn find_loop_headers(&mut self, cfg: &ControlFlowGraph, domtree: &DominatorTree) {203for &block in domtree204.cfg_rpo()205.filter(|&&block| Self::is_block_loop_header(block, cfg, domtree))206{207// This block is a loop header, so we create its associated loop208let lp = self.loops.push(LoopData::new(block, None));209self.block_loop_map[block] = lp.into();210}211}212213// Intended to be called after `find_loop_headers`. For each detected loop header,214// discovers all the block belonging to the loop and its inner loops. After a call to this215// function, the loop tree is fully constructed.216fn discover_loop_blocks(&mut self, cfg: &ControlFlowGraph, domtree: &DominatorTree) {217let mut stack: Vec<Block> = Vec::new();218// We handle each loop header in reverse order, corresponding to a pseudo postorder219// traversal of the graph.220for lp in self.loops().rev() {221// Push all predecessors of this header that it dominates onto the stack.222stack.extend(223cfg.pred_iter(self.loops[lp].header)224.filter(|pred| {225// We follow the back edges226domtree.block_dominates(self.loops[lp].header, pred.block)227})228.map(|pred| pred.block),229);230while let Some(node) = stack.pop() {231let continue_dfs: Option<Block>;232match self.block_loop_map[node].expand() {233None => {234// The node hasn't been visited yet, we tag it as part of the loop235self.block_loop_map[node] = PackedOption::from(lp);236continue_dfs = Some(node);237}238Some(node_loop) => {239// We copy the node_loop into a mutable reference passed along the while240let mut node_loop = node_loop;241// The node is part of a loop, which can be lp or an inner loop242let mut node_loop_parent_option = self.loops[node_loop].parent;243while let Some(node_loop_parent) = node_loop_parent_option.expand() {244if node_loop_parent == lp {245// We have encountered lp so we stop (already visited)246break;247} else {248//249node_loop = node_loop_parent;250// We lookup the parent loop251node_loop_parent_option = self.loops[node_loop].parent;252}253}254// Now node_loop_parent is either:255// - None and node_loop is an new inner loop of lp256// - Some(...) and the initial node_loop was a known inner loop of lp257match node_loop_parent_option.expand() {258Some(_) => continue_dfs = None,259None => {260if node_loop != lp {261self.loops[node_loop].parent = lp.into();262continue_dfs = Some(self.loops[node_loop].header)263} else {264// If lp is a one-block loop then we make sure we stop265continue_dfs = None266}267}268}269}270}271// Now we have handled the popped node and need to continue the DFS by adding the272// predecessors of that node273if let Some(continue_dfs) = continue_dfs {274stack.extend(cfg.pred_iter(continue_dfs).map(|pred| pred.block));275}276}277}278}279280fn assign_loop_levels(&mut self) {281let mut stack: SmallVec<[Loop; 8]> = SmallVec::new();282for lp in self.loops.keys() {283if self.loops[lp].level == LoopLevel::invalid() {284stack.push(lp);285while let Some(&lp) = stack.last() {286if let Some(parent) = self.loops[lp].parent.into() {287if self.loops[parent].level != LoopLevel::invalid() {288self.loops[lp].level = self.loops[parent].level.inc();289stack.pop();290} else {291stack.push(parent);292}293} else {294self.loops[lp].level = LoopLevel::root().inc();295stack.pop();296}297}298}299}300}301}302303#[cfg(test)]304mod tests {305use crate::cursor::{Cursor, FuncCursor};306use crate::dominator_tree::DominatorTree;307use crate::flowgraph::ControlFlowGraph;308use crate::ir::{Function, InstBuilder, types};309use crate::loop_analysis::{Loop, LoopAnalysis};310use alloc::vec::Vec;311312#[test]313fn nested_loops_detection() {314let mut func = Function::new();315let block0 = func.dfg.make_block();316let block1 = func.dfg.make_block();317let block2 = func.dfg.make_block();318let block3 = func.dfg.make_block();319let block4 = func.dfg.make_block();320let cond = func.dfg.append_block_param(block0, types::I32);321322{323let mut cur = FuncCursor::new(&mut func);324325cur.insert_block(block0);326cur.ins().jump(block1, &[]);327328cur.insert_block(block1);329cur.ins().jump(block2, &[]);330331cur.insert_block(block2);332cur.ins().brif(cond, block1, &[], block3, &[]);333334cur.insert_block(block3);335cur.ins().brif(cond, block0, &[], block4, &[]);336337cur.insert_block(block4);338cur.ins().return_(&[]);339}340341let mut loop_analysis = LoopAnalysis::new();342let mut cfg = ControlFlowGraph::new();343let mut domtree = DominatorTree::new();344cfg.compute(&func);345domtree.compute(&func, &cfg);346loop_analysis.compute(&func, &cfg, &domtree);347348let loops = loop_analysis.loops().collect::<Vec<Loop>>();349assert_eq!(loops.len(), 2);350assert_eq!(loop_analysis.loop_header(loops[0]), block0);351assert_eq!(loop_analysis.loop_header(loops[1]), block1);352assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0]));353assert_eq!(loop_analysis.loop_parent(loops[0]), None);354assert_eq!(loop_analysis.is_in_loop(block0, loops[0]), true);355assert_eq!(loop_analysis.is_in_loop(block0, loops[1]), false);356assert_eq!(loop_analysis.is_in_loop(block1, loops[1]), true);357assert_eq!(loop_analysis.is_in_loop(block1, loops[0]), true);358assert_eq!(loop_analysis.is_in_loop(block2, loops[1]), true);359assert_eq!(loop_analysis.is_in_loop(block2, loops[0]), true);360assert_eq!(loop_analysis.is_in_loop(block3, loops[0]), true);361assert_eq!(loop_analysis.is_in_loop(block0, loops[1]), false);362assert_eq!(loop_analysis.loop_level(block0).level(), 1);363assert_eq!(loop_analysis.loop_level(block1).level(), 2);364assert_eq!(loop_analysis.loop_level(block2).level(), 2);365assert_eq!(loop_analysis.loop_level(block3).level(), 1);366}367368#[test]369fn complex_loop_detection() {370let mut func = Function::new();371let block0 = func.dfg.make_block();372let block1 = func.dfg.make_block();373let block2 = func.dfg.make_block();374let block3 = func.dfg.make_block();375let block4 = func.dfg.make_block();376let block5 = func.dfg.make_block();377let block6 = func.dfg.make_block();378let cond = func.dfg.append_block_param(block0, types::I32);379380{381let mut cur = FuncCursor::new(&mut func);382383cur.insert_block(block0);384cur.ins().brif(cond, block1, &[], block3, &[]);385386cur.insert_block(block1);387cur.ins().jump(block2, &[]);388389cur.insert_block(block2);390cur.ins().brif(cond, block1, &[], block5, &[]);391392cur.insert_block(block3);393cur.ins().jump(block4, &[]);394395cur.insert_block(block4);396cur.ins().brif(cond, block3, &[], block5, &[]);397398cur.insert_block(block5);399cur.ins().brif(cond, block0, &[], block6, &[]);400401cur.insert_block(block6);402cur.ins().return_(&[]);403}404405let mut loop_analysis = LoopAnalysis::new();406let cfg = ControlFlowGraph::with_function(&func);407let domtree = DominatorTree::with_function(&func, &cfg);408loop_analysis.compute(&func, &cfg, &domtree);409410let loops = loop_analysis.loops().collect::<Vec<Loop>>();411assert_eq!(loops.len(), 3);412assert_eq!(loop_analysis.loop_header(loops[0]), block0);413assert_eq!(loop_analysis.loop_header(loops[1]), block3);414assert_eq!(loop_analysis.loop_header(loops[2]), block1);415assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0]));416assert_eq!(loop_analysis.loop_parent(loops[2]), Some(loops[0]));417assert_eq!(loop_analysis.loop_parent(loops[0]), None);418assert_eq!(loop_analysis.is_in_loop(block0, loops[0]), true);419assert_eq!(loop_analysis.is_in_loop(block1, loops[2]), true);420assert_eq!(loop_analysis.is_in_loop(block2, loops[2]), true);421assert_eq!(loop_analysis.is_in_loop(block3, loops[1]), true);422assert_eq!(loop_analysis.is_in_loop(block4, loops[1]), true);423assert_eq!(loop_analysis.is_in_loop(block5, loops[0]), true);424assert_eq!(loop_analysis.loop_level(block0).level(), 1);425assert_eq!(loop_analysis.loop_level(block1).level(), 2);426assert_eq!(loop_analysis.loop_level(block2).level(), 2);427assert_eq!(loop_analysis.loop_level(block3).level(), 2);428assert_eq!(loop_analysis.loop_level(block4).level(), 2);429assert_eq!(loop_analysis.loop_level(block5).level(), 1);430}431}432433434