Skip to main content

backend_lang/mir/
cfg.rs

1//! The control-flow graph of a function: successors, predecessors, reverse
2//! postorder, and the dominator tree, every walk iterative. Dominators are
3//! computed by Lengauer and Tarjan's algorithm (O(m log n) on any graph), not
4//! by the simpler Cooper–Harvey–Kennedy iteration, whose intersections walk
5//! the dominator tree once per predecessor and turn quadratic when a shared
6//! block (a trap block, a landing pad) has thousands of predecessors deep in
7//! the tree; dominance queries are then O(1) interval tests on the tree.
8
9use alloc::vec;
10use alloc::vec::Vec;
11
12use super::entity::Block;
13use super::function::Function;
14use super::operand::Operand;
15use super::verify::MirError;
16use super::visit::Access;
17use crate::target::InstKind;
18
19pub(crate) const NONE: u32 = u32::MAX;
20
21/// A function's control-flow graph and dominator tree.
22#[derive(Clone, Debug, Default)]
23pub(crate) struct Cfg {
24    succ_start: Vec<u32>,
25    succs: Vec<u32>,
26    pred_start: Vec<u32>,
27    preds: Vec<u32>,
28    /// Reachable blocks in reverse postorder (the entry first).
29    pub(crate) rpo: Vec<u32>,
30    /// Each block's position in `rpo`, or `NONE` if unreachable.
31    pub(crate) rpo_index: Vec<u32>,
32    /// Immediate dominators (the entry's is itself; `NONE` if unreachable).
33    pub(crate) idom: Vec<u32>,
34    pre: Vec<u32>,
35    post: Vec<u32>,
36}
37
38/// The successors of `block` named by its terminator, then its call sites'
39/// landing pads.
40pub(crate) fn for_each_succ(f: &Function, block: Block, mut visit: impl FnMut(Block)) {
41    let insts = f.insts(block);
42    if let Some(&last) = insts.last() {
43        let kind = f
44            .opcode(last)
45            .and_then(|o| f.target().inst(o))
46            .map(|d| d.kind());
47        if kind.is_some_and(InstKind::is_terminator) {
48            for op in f.operands(last) {
49                if let Operand::Block(call) = op {
50                    visit(call.block());
51                }
52            }
53        }
54    }
55    if !f.sites.is_empty() {
56        for &inst in insts {
57            if let Some(pad) = f.call_site(inst).and_then(|s| s.landing_pad()) {
58                visit(pad);
59            }
60        }
61    }
62}
63
64impl Cfg {
65    /// Builds the graph, the reverse postorder, and the dominator tree.
66    pub(crate) fn new(f: &Function) -> Cfg {
67        let n = f.block_count();
68        let mut cfg = Cfg {
69            succ_start: Vec::with_capacity(n + 1),
70            ..Cfg::default()
71        };
72        for b in f.blocks() {
73            cfg.succ_start.push(cfg.succs.len() as u32);
74            for_each_succ(f, b, |s| {
75                if s.index() < n {
76                    cfg.succs.push(s.index() as u32);
77                }
78            });
79        }
80        cfg.succ_start.push(cfg.succs.len() as u32);
81
82        // Predecessors by counting sort over the successor lists.
83        let mut count = vec![0u32; n + 1];
84        for &s in &cfg.succs {
85            count[s as usize + 1] += 1;
86        }
87        for i in 0..n {
88            count[i + 1] += count[i];
89        }
90        cfg.pred_start = count.clone();
91        let mut preds = vec![0; cfg.succs.len()];
92        for b in 0..n {
93            for &s in cfg.succ_slice(b) {
94                let at = &mut count[s as usize];
95                preds[*at as usize] = b as u32;
96                *at += 1;
97            }
98        }
99        cfg.preds = preds;
100
101        // Reverse postorder by an explicit-stack depth-first walk.
102        cfg.rpo_index = vec![NONE; n];
103        if n > 0 {
104            let mut post = Vec::with_capacity(n);
105            let mut seen = vec![false; n];
106            let mut stack: Vec<(u32, u32)> = vec![(0, 0)];
107            seen[0] = true;
108            while let Some(top) = stack.last_mut() {
109                let (b, k) = *top;
110                let succs = cfg.succ_slice(b as usize);
111                if let Some(&s) = succs.get(k as usize) {
112                    top.1 += 1;
113                    if !seen[s as usize] {
114                        seen[s as usize] = true;
115                        stack.push((s, 0));
116                    }
117                } else {
118                    post.push(b);
119                    let _ = stack.pop();
120                }
121            }
122            post.reverse();
123            for (i, &b) in post.iter().enumerate() {
124                cfg.rpo_index[b as usize] = i as u32;
125            }
126            cfg.rpo = post;
127        }
128        cfg.dominators();
129        cfg
130    }
131
132    fn succ_slice(&self, b: usize) -> &[u32] {
133        let (s, e) = (self.succ_start[b] as usize, self.succ_start[b + 1] as usize);
134        self.succs.get(s..e).unwrap_or(&[])
135    }
136
137    /// The successors of block `b`.
138    pub(crate) fn succs(&self, b: usize) -> &[u32] {
139        if b + 1 < self.succ_start.len() {
140            self.succ_slice(b)
141        } else {
142            &[]
143        }
144    }
145
146    /// The predecessors of block `b` (with repeats for repeated edges).
147    pub(crate) fn preds(&self, b: usize) -> &[u32] {
148        match (self.pred_start.get(b), self.pred_start.get(b + 1)) {
149            (Some(&s), Some(&e)) => self.preds.get(s as usize..e as usize).unwrap_or(&[]),
150            _ => &[],
151        }
152    }
153
154    /// Whether block `b` is reachable from the entry.
155    pub(crate) fn reachable(&self, b: usize) -> bool {
156        self.rpo_index.get(b).is_some_and(|&i| i != NONE)
157    }
158
159    fn dominators(&mut self) {
160        let n = self.rpo_index.len();
161        self.idom = vec![NONE; n];
162        let Some(&entry) = self.rpo.first() else {
163            return;
164        };
165        self.lengauer_tarjan(entry);
166        // Pre- and postorder numbers of the dominator tree make dominance an
167        // O(1) interval test.
168        let mut child_start = vec![0u32; n + 1];
169        for &b in &self.rpo {
170            let d = self.idom[b as usize];
171            if d != b && d != NONE {
172                child_start[d as usize + 1] += 1;
173            }
174        }
175        for i in 0..n {
176            child_start[i + 1] += child_start[i];
177        }
178        let mut fill = child_start.clone();
179        let mut children = vec![0u32; child_start[n] as usize];
180        for &b in &self.rpo {
181            let d = self.idom[b as usize];
182            if d != b && d != NONE {
183                children[fill[d as usize] as usize] = b;
184                fill[d as usize] += 1;
185            }
186        }
187        self.pre = vec![NONE; n];
188        self.post = vec![NONE; n];
189        let (mut pre, mut post) = (0u32, 0u32);
190        let mut stack: Vec<(u32, u32)> = vec![(entry, child_start[entry as usize])];
191        self.pre[entry as usize] = pre;
192        pre += 1;
193        while let Some(top) = stack.last_mut() {
194            let (b, k) = *top;
195            if k < child_start[b as usize + 1] {
196                top.1 += 1;
197                let c = children[k as usize];
198                self.pre[c as usize] = pre;
199                pre += 1;
200                stack.push((c, child_start[c as usize]));
201            } else {
202                self.post[b as usize] = post;
203                post += 1;
204                let _ = stack.pop();
205            }
206        }
207    }
208
209    /// Immediate dominators by Lengauer and Tarjan's algorithm with path
210    /// compression: O(m log n) on any graph, where the simpler iterative
211    /// algorithm degrades to quadratic time when one block (a shared trap
212    /// block, say) has many predecessors deep in the dominator tree. Every
213    /// walk is iterative.
214    fn lengauer_tarjan(&mut self, entry: u32) {
215        let n = self.rpo_index.len();
216        // Depth-first preorder numbers (1-based; 0 = not reached), parents.
217        let mut dfn = vec![0u32; n];
218        let mut vertex: Vec<u32> = Vec::with_capacity(n + 1);
219        vertex.push(NONE);
220        let mut parent = vec![0u32; n + 1];
221        let mut stack: Vec<(u32, u32)> = vec![(entry, 0)];
222        dfn[entry as usize] = 1;
223        vertex.push(entry);
224        while let Some(top) = stack.last_mut() {
225            let (b, k) = *top;
226            let succs = self.succ_slice(b as usize);
227            if let Some(&s) = succs.get(k as usize) {
228                top.1 += 1;
229                if dfn[s as usize] == 0 {
230                    let num = vertex.len() as u32;
231                    dfn[s as usize] = num;
232                    vertex.push(s);
233                    parent[num as usize] = dfn[b as usize];
234                    stack.push((s, 0));
235                }
236            } else {
237                let _ = stack.pop();
238            }
239        }
240        let count = vertex.len() - 1;
241        // Working arrays indexed by preorder number.
242        let mut semi: Vec<u32> = (0..=count as u32).collect();
243        let mut label: Vec<u32> = (0..=count as u32).collect();
244        let mut ancestor = vec![0u32; count + 1];
245        let mut idom = vec![0u32; count + 1];
246        let mut bucket_head = vec![0u32; count + 1];
247        let mut bucket_next = vec![0u32; count + 1];
248        let mut path: Vec<u32> = Vec::new();
249        // eval(v): the vertex of minimum semi on the compressed path from v
250        // to its forest root, compressing as it goes.
251        let mut eval =
252            |v: u32, ancestor: &mut Vec<u32>, label: &mut Vec<u32>, semi: &Vec<u32>| -> u32 {
253                if ancestor[v as usize] == 0 {
254                    return v;
255                }
256                path.clear();
257                let mut x = v;
258                while ancestor[ancestor[x as usize] as usize] != 0 {
259                    path.push(x);
260                    x = ancestor[x as usize];
261                }
262                // Compress from the top of the path down.
263                while let Some(y) = path.pop() {
264                    let a = ancestor[y as usize];
265                    if semi[label[a as usize] as usize] < semi[label[y as usize] as usize] {
266                        label[y as usize] = label[a as usize];
267                    }
268                    ancestor[y as usize] = ancestor[a as usize];
269                }
270                label[v as usize]
271            };
272        for w in (2..=count as u32).rev() {
273            let block = vertex[w as usize];
274            for &p in self.preds(block as usize) {
275                let v = dfn[p as usize];
276                if v == 0 {
277                    continue;
278                }
279                let u = eval(v, &mut ancestor, &mut label, &semi);
280                if semi[u as usize] < semi[w as usize] {
281                    semi[w as usize] = semi[u as usize];
282                }
283            }
284            // Bucket w under its semidominator.
285            let sd = semi[w as usize] as usize;
286            bucket_next[w as usize] = bucket_head[sd];
287            bucket_head[sd] = w;
288            let pw = parent[w as usize];
289            ancestor[w as usize] = pw;
290            let mut v = bucket_head[pw as usize];
291            bucket_head[pw as usize] = 0;
292            while v != 0 {
293                let next = bucket_next[v as usize];
294                let u = eval(v, &mut ancestor, &mut label, &semi);
295                idom[v as usize] = if semi[u as usize] < semi[v as usize] {
296                    u
297                } else {
298                    pw
299                };
300                v = next;
301            }
302        }
303        for w in 2..=count {
304            if idom[w] != semi[w] {
305                idom[w] = idom[idom[w] as usize];
306            }
307        }
308        self.idom[entry as usize] = entry;
309        for w in 2..=count {
310            self.idom[vertex[w] as usize] = vertex[idom[w] as usize];
311        }
312    }
313
314    /// Whether block `a` dominates block `b` (both reachable).
315    pub(crate) fn dominates(&self, a: usize, b: usize) -> bool {
316        match (
317            self.pre.get(a),
318            self.pre.get(b),
319            self.post.get(a),
320            self.post.get(b),
321        ) {
322            (Some(&pa), Some(&pb), Some(&qa), Some(&qb)) => {
323                pa != NONE && pb != NONE && pa <= pb && qb <= qa
324            }
325            _ => false,
326        }
327    }
328}
329
330impl Function {
331    /// Checks [`verify`](Function::verify)'s rules, then SSA dominance: in
332    /// every block reachable from the entry, each use of a virtual register
333    /// is dominated by its definition (a use in the defining block comes
334    /// after the definition; a branch argument is a use at the end of its
335    /// block). This is the register allocator's precondition. Linear in the
336    /// size of the function.
337    ///
338    /// # Errors
339    ///
340    /// The first [`MirError`]: [`verify`](Function::verify)'s errors, then
341    /// [`MirError::UndefinedVReg`] or [`MirError::NotDominated`].
342    ///
343    /// # Examples
344    ///
345    /// ```
346    /// use backend_lang::mir::{Function, MirError, Operand};
347    /// use backend_lang::x86_64::{op, GPR, SYSV};
348    ///
349    /// let mut f = Function::new("f", &SYSV);
350    /// let (a, b) = (f.block()?, f.block()?);
351    /// let v = f.vreg(GPR)?;
352    /// let to_b = f.block_call(b, &[])?;
353    /// f.push(a, op::JMP, &[to_b.into()])?;
354    /// let w = f.vreg(GPR)?;
355    /// f.push(b, op::MOV64, &[w.into(), v.into()])?; // v is never defined
356    /// f.push(b, op::RET, &[])?;
357    /// assert!(matches!(f.verify_ssa(), Err(MirError::UndefinedVReg { .. })));
358    /// # Ok::<(), MirError>(())
359    /// ```
360    pub fn verify_ssa(&self) -> Result<(), MirError> {
361        self.verify()?;
362        let cfg = Cfg::new(self);
363        self.check_dominance(&cfg)
364    }
365
366    pub(crate) fn check_dominance(&self, cfg: &Cfg) -> Result<(), MirError> {
367        // Each virtual register's defining block and position (0 for a block
368        // parameter, k for the k-th instruction, counting from 1).
369        let mut def_block = vec![NONE; self.vreg_count()];
370        let mut def_pos = vec![0u32; self.vreg_count()];
371        for b in self.blocks() {
372            for p in self.params(b) {
373                if let Some(v) = p.reg().and_then(crate::Reg::as_virt) {
374                    if let Some(d) = def_block.get_mut(v.index()) {
375                        *d = b.index() as u32;
376                    }
377                }
378            }
379            for (k, &inst) in self.insts(b).iter().enumerate() {
380                self.visit_regs(inst, |_, r, access| {
381                    if let (Access::Def, Some(v)) = (access, r.as_virt()) {
382                        if let Some(d) = def_block.get_mut(v.index()) {
383                            *d = b.index() as u32;
384                            def_pos[v.index()] = k as u32 + 1;
385                        }
386                    }
387                });
388            }
389        }
390        for &b in &cfg.rpo {
391            let block = Block::from_index(b as usize);
392            for (k, &inst) in self.insts(block).iter().enumerate() {
393                let mut err = None;
394                self.visit_regs(inst, |_, r, access| {
395                    let Some(v) = r.as_virt() else { return };
396                    if access != Access::Use || err.is_some() {
397                        return;
398                    }
399                    let d = def_block.get(v.index()).copied().unwrap_or(NONE);
400                    if d == NONE {
401                        err = Some(MirError::UndefinedVReg { vreg: v });
402                    } else if d == b {
403                        if def_pos[v.index()] > k as u32 {
404                            err = Some(MirError::NotDominated { vreg: v, inst });
405                        }
406                    } else if !cfg.dominates(d as usize, b as usize) {
407                        err = Some(MirError::NotDominated { vreg: v, inst });
408                    }
409                });
410                if let Some(e) = err {
411                    return Err(e);
412                }
413            }
414        }
415        Ok(())
416    }
417}
418
419#[cfg(test)]
420#[allow(
421    clippy::needless_range_loop,
422    reason = "the reference compares dominator sets by block index"
423)]
424mod tests {
425    use alloc::vec;
426    use alloc::vec::Vec;
427
428    use proptest::prelude::*;
429
430    use super::*;
431    use crate::mir::Operand;
432    use crate::x86_64::{self, SYSV, op};
433
434    /// A function whose block `i` branches to `succs[i]` (zero, one, or two
435    /// targets; more become a jump table).
436    fn graph(succs: &[Vec<usize>]) -> Function {
437        let mut f = Function::new("g", &SYSV);
438        let blocks: Vec<Block> = succs
439            .iter()
440            .map(|_| f.block().unwrap_or(Block::new(0)))
441            .collect();
442        for (i, s) in succs.iter().enumerate() {
443            let b = blocks[i];
444            let call = |f: &mut Function, t: usize| f.block_call(blocks[t], &[]).map(Operand::from);
445            let r = match s.len() {
446                0 => f.push(b, op::RET, &[]),
447                1 => call(&mut f, s[0]).and_then(|c| f.push(b, op::JMP, &[c])),
448                2 => {
449                    let (t, e) = (call(&mut f, s[0]), call(&mut f, s[1]));
450                    match (t, e) {
451                        (Ok(t), Ok(e)) => f.push(
452                            b,
453                            op::JCC,
454                            &[x86_64::cond(isa_lang::x86_64::Cond::E).into(), t, e],
455                        ),
456                        (Err(e), _) | (_, Err(e)) => Err(e),
457                    }
458                }
459                _ => {
460                    let t = f.vreg(x86_64::GPR).and_then(|t| {
461                        let i = f.vreg(x86_64::GPR)?;
462                        Ok((t, i))
463                    });
464                    match t {
465                        Ok((t, i)) => {
466                            let mut ops = vec![Operand::from(t), Operand::from(i)];
467                            for &k in s {
468                                if let Ok(c) = call(&mut f, k) {
469                                    ops.push(c);
470                                }
471                            }
472                            f.push(b, op::JMPTBL, &ops)
473                        }
474                        Err(e) => Err(e),
475                    }
476                }
477            };
478            assert!(r.is_ok(), "{r:?}");
479        }
480        f
481    }
482
483    /// Dominator sets by the textbook data-flow iteration.
484    fn reference(cfg: &Cfg, n: usize) -> Vec<Option<Vec<bool>>> {
485        let mut dom: Vec<Option<Vec<bool>>> = vec![None; n];
486        dom[0] = Some({
487            let mut v = vec![false; n];
488            v[0] = true;
489            v
490        });
491        let mut changed = true;
492        while changed {
493            changed = false;
494            for b in 1..n {
495                if !cfg.reachable(b) {
496                    continue;
497                }
498                let mut acc: Option<Vec<bool>> = None;
499                for &p in cfg.preds(b) {
500                    if let Some(d) = &dom[p as usize] {
501                        acc = Some(match acc {
502                            None => d.clone(),
503                            Some(a) => a.iter().zip(d).map(|(x, y)| *x && *y).collect(),
504                        });
505                    }
506                }
507                if let Some(mut a) = acc {
508                    a[b] = true;
509                    if dom[b].as_ref() != Some(&a) {
510                        dom[b] = Some(a);
511                        changed = true;
512                    }
513                }
514            }
515        }
516        dom
517    }
518
519    proptest! {
520        #![proptest_config(ProptestConfig::with_cases(400))]
521
522        #[test]
523        fn prop_dominators_match_the_dataflow_reference(
524            edges in prop::collection::vec(prop::collection::vec(0usize..24, 0..4), 1..24)
525        ) {
526            let n = edges.len();
527            let succs: Vec<Vec<usize>> = edges
528                .iter()
529                .map(|s| s.iter().map(|&t| t % n).collect::<Vec<_>>())
530                .map(|s: Vec<usize>| if s.len() == 2 && s[0] == s[1] { vec![s[0]] } else { s })
531                .collect();
532            let f = graph(&succs);
533            let cfg = Cfg::new(&f);
534            let dom = reference(&cfg, n);
535            for b in 0..n {
536                prop_assert_eq!(cfg.reachable(b), dom[b].is_some());
537                let Some(d) = &dom[b] else { continue };
538                for a in 0..n {
539                    prop_assert_eq!(cfg.dominates(a, b), d[a], "{} dom {}", a, b);
540                }
541                if b != 0 {
542                    // The immediate dominator: the strict dominator that every
543                    // other strict dominator dominates.
544                    let idom = cfg.idom[b] as usize;
545                    prop_assert!(d[idom] && idom != b);
546                    for a in 0..n {
547                        if d[a] && a != b {
548                            prop_assert!(cfg.dominates(a, idom));
549                        }
550                    }
551                }
552            }
553        }
554    }
555
556    #[test]
557    fn test_many_predecessors_deep_in_the_tree_stay_linear() {
558        // A chain of 20,000 blocks, each also branching to one shared sink:
559        // the shape that makes the simple iterative algorithm quadratic.
560        let n = 20_000;
561        let mut succs: Vec<Vec<usize>> = (0..n).map(|i| vec![i + 1, n]).collect();
562        succs.push(vec![]); // n: the sink
563        succs[n - 1] = vec![n];
564        let f = graph(&succs);
565        let cfg = Cfg::new(&f);
566        assert_eq!(cfg.idom[n] as usize, 0);
567        assert!(cfg.dominates(0, n) && cfg.dominates(n - 1, n - 1));
568    }
569}