rucc_opt/cfg.rs
1//! The control flow graph, which is the shape of a function with the instructions taken out.
2//!
3//! Design: `spec/optimizer/06-cfg-and-dominators.md`.
4//!
5//! `Func` stores successors and only successors. A terminator names the blocks it goes to and
6//! no block records who arrives at it, which is the right storage, because an argument that
7//! travels on an edge cannot then go out of step with a predecessor list kept somewhere else.
8//! It is the wrong question to ask afresh in six passes, so this is the answer computed once:
9//! the predecessors, both adjacency lists, a postorder, and which blocks the entry reaches.
10//!
11//! Everything here is recomputed from nothing after any change to the shape of the function.
12//! There is no incremental update and section 6.3 of the design says why: a CFG edit already
13//! invalidates almost every other analysis, so a graph that survived one would be the single
14//! survivor of a clearing that took the rest, and a stale dominator tree is the hardest kind of
15//! compiler bug to find.
16
17use rucc_ir::{Block, Func};
18
19/// Who goes where in a function, and in what order to walk it.
20///
21/// Built with [`Cfg::new`] and read only. Nothing here holds a borrow of the function, so a
22/// pass can compute the graph, then edit the code, and the compiler will not stop it. What
23/// stops it is the pass manager, which throws this away when a pass says it changed the shape.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct Cfg {
26 /// The blocks each block branches to, indexed by block number.
27 succs: Vec<Vec<Block>>,
28 /// The blocks that branch to each block, indexed by block number.
29 preds: Vec<Vec<Block>>,
30 /// The blocks the entry reaches, children before parents.
31 postorder: Vec<Block>,
32 /// Where each block sits in reverse postorder, and `None` for one the entry misses.
33 rank: Vec<Option<u32>>,
34 /// Where control arrives, which a function that is only declared does not have.
35 entry: Option<Block>,
36}
37
38impl Cfg {
39 /// Reads the graph out of the function.
40 ///
41 /// Linear in the blocks and the edges between them. A function with no blocks gives an
42 /// empty graph rather than an error, because a declaration is a perfectly ordinary thing
43 /// for a pipeline to be handed and refusing it here would put the check in every caller.
44 #[must_use]
45 pub fn new(func: &Func) -> Self {
46 let counts = func.counts();
47 let mut succs: Vec<Vec<Block>> = vec![Vec::new(); counts.blocks];
48 let mut preds: Vec<Vec<Block>> = vec![Vec::new(); counts.blocks];
49
50 // The terminator and nothing else, which is where the invariant the verifier proves
51 // gets spent: a block has exactly one terminator and it is the last instruction, so
52 // this reads one instruction per block instead of all of them.
53 //
54 // It also means `block_addr` is not read here, and it must not be. The verifier counts
55 // a block whose address is taken as a successor of the block that took it, which can
56 // only add predecessors and so only take dominators away, which makes the verifier's
57 // check stricter and never looser. That is not the graph. Control arrives at such a
58 // block from an `indirect_br`, that instruction is a terminator, and it lists every
59 // block the address can hold, so the edge is already here from the place control
60 // really leaves.
61 let mut stamp = vec![usize::MAX; counts.blocks];
62 for block in func.blocks() {
63 let Some(term) = func.terminator(block) else { continue };
64 for call in func.successors(term) {
65 // A `switch` with two labels on one arm names that block twice, and so does a
66 // `br_if` whose arms agree. Twice is right for an edge, because the two edges
67 // can carry different arguments, and wrong for a graph, where it would make a
68 // block look like it had two predecessors when it has one. The edge list lives
69 // in `Func::target_list` and that is what edge splitting reads. This answers
70 // which blocks, so it says each one once.
71 if stamp[call.block.index()] == block.index() {
72 continue;
73 }
74 stamp[call.block.index()] = block.index();
75 succs[block.index()].push(call.block);
76 preds[call.block.index()].push(block);
77 }
78 }
79
80 let entry = func.entry();
81 let postorder = match entry {
82 Some(entry) => postorder(&succs, entry, counts.blocks),
83 None => Vec::new(),
84 };
85 let mut rank = vec![None; counts.blocks];
86 for (index, &block) in postorder.iter().rev().enumerate() {
87 rank[block.index()] = Some(index as u32);
88 }
89
90 Self { succs, preds, postorder, rank, entry }
91 }
92
93 /// Where control arrives, which is `None` for a function that is only declared.
94 #[must_use]
95 pub fn entry(&self) -> Option<Block> {
96 self.entry
97 }
98
99 /// The blocks this one branches to, each named once however many edges go to it.
100 #[must_use]
101 pub fn successors(&self, block: Block) -> &[Block] {
102 &self.succs[block.index()]
103 }
104
105 /// The blocks that branch to this one, each named once however many edges come from it.
106 #[must_use]
107 pub fn predecessors(&self, block: Block) -> &[Block] {
108 &self.preds[block.index()]
109 }
110
111 /// Every block the entry reaches, children before parents.
112 ///
113 /// This is the order to run a backwards analysis in, and reversing it is the order to run a
114 /// forwards one in. It is computed here rather than in each pass that wants it, which is
115 /// how a compiler avoids acquiring six traversals that differ in ways nobody wrote down.
116 #[must_use]
117 pub fn postorder(&self) -> &[Block] {
118 &self.postorder
119 }
120
121 /// Every block the entry reaches, parents before children.
122 ///
123 /// A block appears after at least one of its predecessors, and after all of them when the
124 /// graph has no back edges. That is what makes it the order a forwards fixed point settles
125 /// in fastest.
126 pub fn reverse_postorder(&self) -> impl DoubleEndedIterator<Item = Block> + use<'_> {
127 self.postorder.iter().rev().copied()
128 }
129
130 /// Where a block sits in reverse postorder, and `None` for one the entry does not reach.
131 #[must_use]
132 pub fn rank(&self, block: Block) -> Option<u32> {
133 self.rank[block.index()]
134 }
135
136 /// Whether control can arrive at this block at all.
137 ///
138 /// An unreachable block is not an error and the front end makes them constantly: the block
139 /// after a `return`, the arm of an `if` on a constant, the code after
140 /// `__builtin_unreachable`. Section 6.5 of the design states the rule for the whole
141 /// optimizer, which is that such a block is invisible to every analysis and every
142 /// transformation, and is deleted by CFG simplification rather than by whoever noticed it.
143 /// A pass that deletes blocks as a side effect of doing something else is a pass whose fuel
144 /// accounting is wrong and whose dumps cannot be read.
145 #[must_use]
146 pub fn reaches(&self, block: Block) -> bool {
147 self.rank[block.index()].is_some()
148 }
149
150 /// How many blocks the function has room for, counting the removed ones.
151 ///
152 /// This is the length of every array indexed by block number, and is what somebody sizing
153 /// their own array wants. It is not how many blocks there are.
154 #[must_use]
155 pub fn capacity(&self) -> usize {
156 self.rank.len()
157 }
158}
159
160/// Every block the entry reaches, children before parents.
161///
162/// An explicit stack rather than recursion, because a chain of blocks is as long as the
163/// function is and a straight line of ten thousand statements is a real program.
164fn postorder(succs: &[Vec<Block>], entry: Block, blocks: usize) -> Vec<Block> {
165 let mut order = Vec::new();
166 let mut seen = vec![false; blocks];
167 let mut stack = vec![(entry, 0usize)];
168 seen[entry.index()] = true;
169 while let Some((block, next)) = stack.pop() {
170 match succs[block.index()].get(next) {
171 Some(&target) => {
172 stack.push((block, next + 1));
173 if !seen[target.index()] {
174 seen[target.index()] = true;
175 stack.push((target, 0));
176 }
177 }
178 None => order.push(block),
179 }
180 }
181 order
182}
183
184#[cfg(test)]
185mod tests {
186 use rucc_ir::{Block, Func, Signature};
187
188 use crate::cfg::Cfg;
189 use crate::testing::{computed_goto, graph};
190
191 /// The successors of a block, as block numbers, in the order the graph holds them.
192 fn succs(cfg: &Cfg, block: usize) -> Vec<usize> {
193 cfg.successors(Block::from_usize(block)).iter().map(|b| b.index()).collect()
194 }
195
196 /// The predecessors of a block, as block numbers, sorted so the test can name a set.
197 fn preds(cfg: &Cfg, block: usize) -> Vec<usize> {
198 let mut list: Vec<usize> =
199 cfg.predecessors(Block::from_usize(block)).iter().map(|b| b.index()).collect();
200 list.sort_unstable();
201 list
202 }
203
204 #[test]
205 fn a_straight_line_goes_one_way() {
206 let func = graph(&[&[1], &[2], &[]]);
207 let cfg = Cfg::new(&func);
208 assert_eq!(succs(&cfg, 0), [1]);
209 assert_eq!(succs(&cfg, 2), []);
210 assert_eq!(preds(&cfg, 0), []);
211 assert_eq!(preds(&cfg, 2), [1]);
212 assert_eq!(cfg.postorder().iter().map(|b| b.index()).collect::<Vec<_>>(), [2, 1, 0]);
213 }
214
215 #[test]
216 fn a_join_has_both_arms_as_predecessors() {
217 let func = graph(&[&[1, 2], &[3], &[3], &[]]);
218 let cfg = Cfg::new(&func);
219 assert_eq!(preds(&cfg, 3), [1, 2]);
220 assert_eq!(cfg.rank(Block::from_usize(0)), Some(0));
221 }
222
223 #[test]
224 fn two_arms_of_one_branch_to_one_block_is_one_predecessor() {
225 // The block is named twice by the terminator and once by the graph. Counting it twice
226 // would make a pass that merges a block into its only predecessor decline this one for
227 // the wrong reason, and the right reason is that the predecessor has two successors,
228 // which it does not.
229 let func = graph(&[&[1, 1], &[]]);
230 let cfg = Cfg::new(&func);
231 assert_eq!(succs(&cfg, 0), [1]);
232 assert_eq!(preds(&cfg, 1), [0]);
233 }
234
235 #[test]
236 fn a_block_nothing_branches_to_is_not_reached() {
237 let func = graph(&[&[1], &[], &[2]]);
238 let cfg = Cfg::new(&func);
239 assert!(cfg.reaches(Block::from_usize(1)));
240 // Block 2 branches to itself and nothing branches to it. A postorder walk that started
241 // anywhere other than the entry would spin here, which is the whole reason the walk
242 // starts at the entry and carries a seen set rather than trusting the shape.
243 assert!(!cfg.reaches(Block::from_usize(2)));
244 assert!(cfg.rank(Block::from_usize(2)).is_none());
245 assert_eq!(cfg.postorder().len(), 2);
246 }
247
248 #[test]
249 fn a_back_edge_is_an_edge_like_any_other() {
250 let func = graph(&[&[1], &[2, 3], &[1], &[]]);
251 let cfg = Cfg::new(&func);
252 assert_eq!(preds(&cfg, 1), [0, 2]);
253 // Reverse postorder puts a block after one of its predecessors, and the header's other
254 // predecessor is the latch, which comes later. That is what a back edge is.
255 let order: Vec<usize> = cfg.reverse_postorder().map(|b| b.index()).collect();
256 assert_eq!(order[0], 0);
257 assert!(order.iter().position(|&b| b == 1) < order.iter().position(|&b| b == 2));
258 }
259
260 #[test]
261 fn taking_the_address_of_a_block_is_not_an_edge_to_it() {
262 // Block 0 takes block 2's address and block 1 is the only thing that branches there.
263 // The verifier counts the first as an edge on purpose. The graph must not, or a pass
264 // would think block 2 had a predecessor that never branches anywhere.
265 let func = computed_goto();
266 let cfg = Cfg::new(&func);
267 assert_eq!(preds(&cfg, 2), [1]);
268 assert!(cfg.reaches(Block::from_usize(2)));
269 }
270
271 #[test]
272 fn a_declaration_has_no_graph_and_says_so() {
273 let func = Func::new(rucc_base::Interner::new().intern("f"), Signature::new());
274 let cfg = Cfg::new(&func);
275 assert!(cfg.entry().is_none());
276 assert!(cfg.postorder().is_empty());
277 assert_eq!(cfg.capacity(), 0);
278 }
279}