Skip to main content

solar_codegen/analysis/
cfg.rs

1//! Shared control-flow analysis utilities for MIR passes.
2//!
3//! The transformation passes need the same basic CFG facts over and over:
4//! reachable blocks, reverse postorder, immediate dominators, dominator-tree
5//! children, and path reachability. Keeping those in one place avoids subtle
6//! differences between passes when unreachable predecessors or critical-edge
7//! rewrites are involved.
8
9use crate::mir::{BlockId, Function};
10use solar_data_structures::map::{FxHashMap, FxHashSet};
11
12/// Control-flow facts for one MIR function.
13#[derive(Clone, Debug)]
14pub struct CfgInfo {
15    successors: Vec<Vec<BlockId>>,
16    reachable: FxHashSet<BlockId>,
17    rpo: Vec<BlockId>,
18    dominators: DominatorTree,
19    reachability: Option<FxHashMap<BlockId, FxHashSet<BlockId>>>,
20}
21
22impl CfgInfo {
23    /// Computes shared CFG facts for `func`.
24    #[must_use]
25    pub fn new(func: &Function) -> Self {
26        let successors = collect_successors(func);
27        let (reachable, postorder, rpo) = reachable_orders(func, &successors);
28        let dominators = DominatorTree::compute(func, &postorder);
29        Self { successors, reachable, rpo, dominators, reachability: None }
30    }
31
32    /// Returns successor blocks for `block`.
33    #[must_use]
34    pub fn successors(&self, block: BlockId) -> &[BlockId] {
35        &self.successors[block.index()]
36    }
37
38    /// Returns the blocks reachable from the entry.
39    #[must_use]
40    pub fn reachable(&self) -> &FxHashSet<BlockId> {
41        &self.reachable
42    }
43
44    /// Returns true if `block` is reachable from the entry.
45    #[must_use]
46    pub fn is_reachable(&self, block: BlockId) -> bool {
47        self.reachable.contains(&block)
48    }
49
50    /// Returns reachable blocks in reverse postorder.
51    #[must_use]
52    pub fn rpo(&self) -> &[BlockId] {
53        &self.rpo
54    }
55
56    /// Returns immediate-dominator information.
57    #[must_use]
58    pub fn dominators(&self) -> &DominatorTree {
59        &self.dominators
60    }
61
62    /// Returns block-to-block reachability through at least one CFG edge.
63    ///
64    /// The map is computed lazily because only memory/state-aware passes need
65    /// this more expensive transitive query.
66    pub fn transitive_reachability(&mut self) -> &FxHashMap<BlockId, FxHashSet<BlockId>> {
67        self.reachability.get_or_insert_with(|| compute_transitive_reachability(&self.successors))
68    }
69}
70
71/// Immediate-dominator tree for one MIR function.
72#[derive(Clone, Debug)]
73pub struct DominatorTree {
74    idoms: Vec<Option<BlockId>>,
75    children: Vec<Vec<BlockId>>,
76}
77
78impl DominatorTree {
79    fn compute(func: &Function, postorder: &[BlockId]) -> Self {
80        let block_count = func.blocks.len();
81        let mut rpo_numbers = vec![usize::MAX; block_count];
82        for (number, &block) in postorder.iter().rev().enumerate() {
83            rpo_numbers[block.index()] = number;
84        }
85
86        let mut idoms = vec![None; block_count];
87        idoms[func.entry_block.index()] = Some(func.entry_block);
88        let mut changed = true;
89        while changed {
90            changed = false;
91            for &block in postorder.iter().rev() {
92                if block == func.entry_block {
93                    continue;
94                }
95                let mut new_idom: Option<BlockId> = None;
96                for &pred in &func.blocks[block].predecessors {
97                    if idoms[pred.index()].is_none() {
98                        continue;
99                    }
100                    new_idom = Some(match new_idom {
101                        None => pred,
102                        Some(current) => Self::intersect(&idoms, &rpo_numbers, pred, current),
103                    });
104                }
105                if let Some(new_idom) = new_idom
106                    && idoms[block.index()] != Some(new_idom)
107                {
108                    idoms[block.index()] = Some(new_idom);
109                    changed = true;
110                }
111            }
112        }
113
114        let mut children = vec![Vec::new(); block_count];
115        for (block_index, idom) in idoms.iter().copied().enumerate() {
116            let block = BlockId::from_usize(block_index);
117            if block == func.entry_block {
118                continue;
119            }
120            if let Some(idom) = idom {
121                children[idom.index()].push(block);
122            }
123        }
124        for children in &mut children {
125            children.sort_by_key(|block| block.index());
126        }
127
128        Self { idoms, children }
129    }
130
131    fn intersect(
132        idoms: &[Option<BlockId>],
133        rpo_numbers: &[usize],
134        a: BlockId,
135        b: BlockId,
136    ) -> BlockId {
137        let (mut a, mut b) = (a, b);
138        while a != b {
139            while rpo_numbers[a.index()] > rpo_numbers[b.index()] {
140                a = idoms[a.index()].expect("processed block has an immediate dominator");
141            }
142            while rpo_numbers[b.index()] > rpo_numbers[a.index()] {
143                b = idoms[b.index()].expect("processed block has an immediate dominator");
144            }
145        }
146        a
147    }
148
149    /// Returns the immediate dominator of `block`, if reachable.
150    #[must_use]
151    pub fn idom(&self, block: BlockId) -> Option<BlockId> {
152        self.idoms.get(block.index()).copied().flatten()
153    }
154
155    /// Returns true if `dominator` dominates `block`.
156    #[must_use]
157    pub fn dominates(&self, dominator: BlockId, block: BlockId) -> bool {
158        let mut current = block;
159        loop {
160            if current == dominator {
161                return true;
162            }
163            match self.idom(current) {
164                Some(idom) if idom != current => current = idom,
165                _ => return false,
166            }
167        }
168    }
169
170    /// Returns dominator-tree children of `block`.
171    #[must_use]
172    pub fn children(&self, block: BlockId) -> &[BlockId] {
173        self.children.get(block.index()).map_or(&[], Vec::as_slice)
174    }
175
176    /// Returns `block`, then its immediate dominators up to the entry.
177    #[must_use]
178    pub fn self_and_dominators(&self, block: BlockId) -> Vec<BlockId> {
179        let mut out = Vec::new();
180        let mut current = Some(block);
181        while let Some(block) = current {
182            out.push(block);
183            current = self.idom(block).filter(|&idom| idom != block);
184        }
185        out
186    }
187}
188
189fn compute_transitive_reachability(
190    successors: &[Vec<BlockId>],
191) -> FxHashMap<BlockId, FxHashSet<BlockId>> {
192    let mut reachability = FxHashMap::default();
193    for block_index in 0..successors.len() {
194        let block_id = BlockId::from_usize(block_index);
195        let mut reachable = FxHashSet::default();
196        let mut stack = successors[block_id.index()].clone();
197        while let Some(block) = stack.pop() {
198            if reachable.insert(block) {
199                stack.extend(successors[block.index()].iter().copied());
200            }
201        }
202        reachability.insert(block_id, reachable);
203    }
204    reachability
205}
206
207fn collect_successors(func: &Function) -> Vec<Vec<BlockId>> {
208    func.blocks
209        .iter()
210        .map(|block| {
211            block
212                .terminator
213                .as_ref()
214                .map(|term| term.successors().into_iter().collect())
215                .unwrap_or_default()
216        })
217        .collect()
218}
219
220fn reachable_orders(
221    func: &Function,
222    successors: &[Vec<BlockId>],
223) -> (FxHashSet<BlockId>, Vec<BlockId>, Vec<BlockId>) {
224    let mut reachable = FxHashSet::default();
225    let mut postorder = Vec::with_capacity(func.blocks.len());
226    let mut stack = vec![(func.entry_block, 0usize)];
227    reachable.insert(func.entry_block);
228    while let Some((block, next)) = stack.last_mut() {
229        if let Some(&succ) = successors[block.index()].get(*next) {
230            *next += 1;
231            if reachable.insert(succ) {
232                stack.push((succ, 0));
233            }
234        } else {
235            postorder.push(*block);
236            stack.pop();
237        }
238    }
239    let mut rpo = postorder.clone();
240    rpo.reverse();
241    (reachable, postorder, rpo)
242}