Skip to main content

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}