Skip to main content

llvm_analysis/
cfg.rs

1//! Control-flow graph: predecessor and successor maps over basic blocks.
2//!
3//! `Cfg::compute` builds the graph from a `Function` by inspecting each
4//! block's terminator. Unreachable blocks (no path from entry) are included
5//! in the graph but will have no predecessors.
6
7use llvm_ir::{BlockId, Function};
8
9/// Control-flow graph for a single function.
10///
11/// Block indices map directly to `BlockId(i as u32)`. The entry block is
12/// always `BlockId(0)`.
13///
14/// # Reachability
15///
16/// The CFG stores edges for **all** blocks, including those unreachable from
17/// the entry.  Methods that return block counts or iterate over blocks
18/// document whether they include or exclude unreachable blocks:
19///
20/// | Method | Includes unreachable? |
21/// |--------|-----------------------|
22/// | `num_blocks()` | yes |
23/// | `num_reachable_blocks()` | no |
24/// | `rpo()` / `post_order()` | no |
25/// | `is_reachable()` | — |
26pub struct Cfg {
27    // `num_blocks` field.
28    num_blocks: usize,
29    // `succs` field.
30    succs: Vec<Vec<BlockId>>,
31    // `preds` field.
32    preds: Vec<Vec<BlockId>>,
33    /// `reachable[i]` is `true` iff `BlockId(i)` is reachable from entry.
34    reachable: Vec<bool>,
35    // `reachable_count` field.
36    reachable_count: usize,
37}
38
39impl Cfg {
40    /// Build the CFG for `func` by walking each block's terminator.
41    pub fn compute(func: &Function) -> Self {
42        let n = func.num_blocks();
43        let mut succs = vec![Vec::new(); n];
44        let mut preds = vec![Vec::new(); n];
45
46        for (bi, bb) in func.blocks.iter().enumerate() {
47            let src = BlockId(bi as u32);
48            if let Some(tid) = bb.terminator {
49                for dst in func.instr(tid).kind.successors() {
50                    succs[bi].push(dst);
51                    preds[dst.0 as usize].push(src);
52                }
53            }
54        }
55
56        // DFS from entry to mark reachable blocks.
57        let mut reachable = vec![false; n];
58        let mut reachable_count = 0;
59        if n > 0 {
60            let mut stack = vec![0usize];
61            while let Some(b) = stack.pop() {
62                if reachable[b] {
63                    continue;
64                }
65                reachable[b] = true;
66                reachable_count += 1;
67                for &succ in &succs[b] {
68                    stack.push(succ.0 as usize);
69                }
70            }
71        }
72
73        Cfg {
74            num_blocks: n,
75            succs,
76            preds,
77            reachable,
78            reachable_count,
79        }
80    }
81
82    /// The function entry block (always index 0).
83    pub fn entry(&self) -> BlockId {
84        BlockId(0)
85    }
86
87    /// Total number of blocks in the function, **including unreachable blocks**.
88    ///
89    /// To iterate only reachable blocks use [`rpo`](Self::rpo).
90    /// To count only reachable blocks use [`num_reachable_blocks`](Self::num_reachable_blocks).
91    pub fn num_blocks(&self) -> usize {
92        self.num_blocks
93    }
94
95    /// Number of blocks reachable from the entry block.
96    ///
97    /// This is an O(1) operation. Equivalent to `cfg.rpo().len()` but without
98    /// the allocation or DFS traversal cost.
99    pub fn num_reachable_blocks(&self) -> usize {
100        self.reachable_count
101    }
102
103    /// Returns `true` if `bid` is reachable from the entry block.
104    ///
105    /// # Panics
106    ///
107    /// Panics if `bid` is not a valid block index for this function (same
108    /// behaviour as [`successors`](Self::successors) and
109    /// [`predecessors`](Self::predecessors)).
110    pub fn is_reachable(&self, bid: BlockId) -> bool {
111        self.reachable[bid.0 as usize]
112    }
113
114    /// Successor blocks of `bid`.
115    pub fn successors(&self, bid: BlockId) -> &[BlockId] {
116        &self.succs[bid.0 as usize]
117    }
118
119    /// Predecessor blocks of `bid`.
120    pub fn predecessors(&self, bid: BlockId) -> &[BlockId] {
121        &self.preds[bid.0 as usize]
122    }
123
124    /// Blocks in post-order (a block appears after all blocks it can reach).
125    ///
126    /// **Unreachable blocks are omitted.** Use [`num_blocks`](Self::num_blocks)
127    /// if you need a count that includes them.
128    pub fn post_order(&self) -> Vec<BlockId> {
129        let mut visited = vec![false; self.num_blocks];
130        let mut order = Vec::with_capacity(self.num_blocks);
131        self.dfs_post(self.entry(), &mut visited, &mut order);
132        order
133    }
134
135    /// Blocks in reverse post-order (RPO). The entry block is first;
136    /// a block always appears before any block it dominates.
137    ///
138    /// **Unreachable blocks are omitted.** Use [`num_blocks`](Self::num_blocks)
139    /// if you need a count that includes them.
140    pub fn rpo(&self) -> Vec<BlockId> {
141        let mut order = self.post_order();
142        order.reverse();
143        order
144    }
145
146    fn dfs_post(&self, bid: BlockId, visited: &mut Vec<bool>, order: &mut Vec<BlockId>) {
147        let idx = bid.0 as usize;
148        if visited[idx] {
149            return;
150        }
151        visited[idx] = true;
152        for &succ in &self.succs[idx] {
153            self.dfs_post(succ, visited, order);
154        }
155        order.push(bid);
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use llvm_ir::{BasicBlock, Context, Function, InstrKind, Instruction, Linkage, ValueRef};
163
164    /// Build a test function with `num_blocks` blocks.
165    /// `edges`: (src_idx, vec_of_dst_idx) — at most 2 successors per block.
166    /// Blocks not listed get an `unreachable` terminator.
167    fn build_func(num_blocks: usize, edges: &[(usize, Vec<usize>)]) -> (Context, Function) {
168        let mut ctx = Context::new();
169        let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
170        let mut func = Function::new("test", fn_ty, vec![], Linkage::External);
171
172        for i in 0..num_blocks {
173            func.add_block(BasicBlock::new(format!("b{}", i)));
174        }
175
176        let mut has_term = vec![false; num_blocks];
177        for &(src, ref dsts) in edges {
178            has_term[src] = true;
179            let kind = match dsts.as_slice() {
180                [] => InstrKind::Unreachable,
181                [dst] => InstrKind::Br {
182                    dest: BlockId(*dst as u32),
183                },
184                [t, f] => {
185                    let cond = ValueRef::Constant(ctx.const_int(ctx.i1_ty, 0));
186                    InstrKind::CondBr {
187                        cond,
188                        then_dest: BlockId(*t as u32),
189                        else_dest: BlockId(*f as u32),
190                    }
191                }
192                _ => panic!("test only supports 0/1/2 successors"),
193            };
194            let iid = func.alloc_instr(Instruction {
195                name: None,
196                ty: ctx.void_ty,
197                kind,
198            });
199            func.blocks[src].set_terminator(iid);
200        }
201        for (i, &needs_term) in has_term.iter().enumerate() {
202            if !needs_term {
203                let iid = func.alloc_instr(Instruction {
204                    name: None,
205                    ty: ctx.void_ty,
206                    kind: InstrKind::Unreachable,
207                });
208                func.blocks[i].set_terminator(iid);
209            }
210        }
211        (ctx, func)
212    }
213
214    #[test]
215    fn cfg_single_block() {
216        let (_ctx, func) = build_func(1, &[(0, vec![])]);
217        let cfg = Cfg::compute(&func);
218        assert_eq!(cfg.num_blocks(), 1);
219        assert!(cfg.successors(BlockId(0)).is_empty());
220        assert!(cfg.predecessors(BlockId(0)).is_empty());
221        assert_eq!(cfg.rpo(), vec![BlockId(0)]);
222    }
223
224    #[test]
225    fn cfg_linear() {
226        let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![2]), (2, vec![])]);
227        let cfg = Cfg::compute(&func);
228        assert_eq!(cfg.successors(BlockId(0)), &[BlockId(1)]);
229        assert_eq!(cfg.successors(BlockId(1)), &[BlockId(2)]);
230        assert!(cfg.successors(BlockId(2)).is_empty());
231        assert!(cfg.predecessors(BlockId(0)).is_empty());
232        assert_eq!(cfg.predecessors(BlockId(2)), &[BlockId(1)]);
233        assert_eq!(cfg.rpo(), vec![BlockId(0), BlockId(1), BlockId(2)]);
234    }
235
236    #[test]
237    fn cfg_diamond() {
238        // 0 -> {1, 2} -> 3
239        let (_ctx, func) = build_func(
240            4,
241            &[(0, vec![1, 2]), (1, vec![3]), (2, vec![3]), (3, vec![])],
242        );
243        let cfg = Cfg::compute(&func);
244        assert_eq!(cfg.successors(BlockId(0)).len(), 2);
245        assert_eq!(cfg.predecessors(BlockId(3)).len(), 2);
246        let rpo = cfg.rpo();
247        assert_eq!(rpo[0], BlockId(0));
248        assert_eq!(*rpo.last().unwrap(), BlockId(3));
249    }
250
251    #[test]
252    fn cfg_loop() {
253        // 0 -> 1 -> 2 -> {1, 3}  (back-edge 2→1)
254        let (_ctx, func) = build_func(
255            4,
256            &[(0, vec![1]), (1, vec![2]), (2, vec![1, 3]), (3, vec![])],
257        );
258        let cfg = Cfg::compute(&func);
259        assert!(cfg.predecessors(BlockId(1)).contains(&BlockId(2)));
260        assert_eq!(cfg.rpo().len(), 4);
261    }
262
263    #[test]
264    fn cfg_unreachable_block() {
265        // 0 -> 1; block 2 is unreachable
266        let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![]), (2, vec![])]);
267        let cfg = Cfg::compute(&func);
268        let rpo = cfg.rpo();
269        assert_eq!(rpo.len(), 2);
270        assert!(!rpo.contains(&BlockId(2)));
271    }
272
273    #[test]
274    fn cfg_is_reachable() {
275        // 0 -> 1; block 2 is unreachable
276        let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![]), (2, vec![])]);
277        let cfg = Cfg::compute(&func);
278        assert!(cfg.is_reachable(BlockId(0)));
279        assert!(cfg.is_reachable(BlockId(1)));
280        assert!(!cfg.is_reachable(BlockId(2)));
281    }
282
283    #[test]
284    fn cfg_num_reachable_blocks() {
285        // 0 -> 1; blocks 2 and 3 are unreachable
286        let (_ctx, func) = build_func(4, &[(0, vec![1]), (1, vec![]), (2, vec![3]), (3, vec![])]);
287        let cfg = Cfg::compute(&func);
288        assert_eq!(cfg.num_blocks(), 4);
289        assert_eq!(cfg.num_reachable_blocks(), 2);
290        // num_reachable_blocks() must equal rpo().len()
291        assert_eq!(cfg.num_reachable_blocks(), cfg.rpo().len());
292    }
293
294    #[test]
295    fn cfg_empty_function_reachable_count() {
296        // Zero-block function: num_reachable_blocks() must return 0, not panic.
297        let mut ctx = Context::new();
298        let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
299        let func = Function::new("empty", fn_ty, vec![], Linkage::External);
300        let cfg = Cfg::compute(&func);
301        assert_eq!(cfg.num_blocks(), 0);
302        assert_eq!(cfg.num_reachable_blocks(), 0);
303    }
304}