solar_codegen/analysis/
cfg.rs1use crate::mir::{BlockId, Function};
10use solar_data_structures::map::{FxHashMap, FxHashSet};
11
12#[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 #[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 #[must_use]
34 pub fn successors(&self, block: BlockId) -> &[BlockId] {
35 &self.successors[block.index()]
36 }
37
38 #[must_use]
40 pub fn reachable(&self) -> &FxHashSet<BlockId> {
41 &self.reachable
42 }
43
44 #[must_use]
46 pub fn is_reachable(&self, block: BlockId) -> bool {
47 self.reachable.contains(&block)
48 }
49
50 #[must_use]
52 pub fn rpo(&self) -> &[BlockId] {
53 &self.rpo
54 }
55
56 #[must_use]
58 pub fn dominators(&self) -> &DominatorTree {
59 &self.dominators
60 }
61
62 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#[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 #[must_use]
151 pub fn idom(&self, block: BlockId) -> Option<BlockId> {
152 self.idoms.get(block.index()).copied().flatten()
153 }
154
155 #[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 #[must_use]
172 pub fn children(&self, block: BlockId) -> &[BlockId] {
173 self.children.get(block.index()).map_or(&[], Vec::as_slice)
174 }
175
176 #[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}