Coverage Report

Created: 2026-01-22 08:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/regalloc2/src/fuzzing/domtree.rs
Line
Count
Source
1
//! Fuzz the dominator-tree calculation.
2
3
use crate::{domtree, postorder, Block};
4
use arbitrary::{Arbitrary, Result, Unstructured};
5
use std::collections::HashSet;
6
use std::{vec, vec::Vec};
7
8
#[derive(Clone, Debug)]
9
struct CFG {
10
    num_blocks: usize,
11
    preds: Vec<Vec<Block>>,
12
    succs: Vec<Vec<Block>>,
13
}
14
15
impl Arbitrary<'_> for CFG {
16
0
    fn arbitrary(u: &mut Unstructured) -> Result<CFG> {
17
0
        let num_blocks = u.int_in_range(1..=1000)?;
18
0
        let mut succs = vec![];
19
0
        for _ in 0..num_blocks {
20
0
            let mut block_succs = vec![];
21
0
            for _ in 0..u.int_in_range(0..=5)? {
22
0
                block_succs.push(Block::new(u.int_in_range(0..=(num_blocks - 1))?));
23
            }
24
0
            succs.push(block_succs);
25
        }
26
0
        let mut preds = vec![];
27
0
        for _ in 0..num_blocks {
28
0
            preds.push(vec![]);
29
0
        }
30
0
        for from in 0..num_blocks {
31
0
            for succ in &succs[from] {
32
0
                preds[succ.index()].push(Block::new(from));
33
0
            }
34
        }
35
0
        Ok(CFG {
36
0
            num_blocks,
37
0
            preds,
38
0
            succs,
39
0
        })
40
0
    }
41
}
42
43
#[derive(Clone, Debug)]
44
struct Path {
45
    blocks: Vec<Block>,
46
}
47
48
impl Path {
49
0
    fn choose_from_cfg(cfg: &CFG, u: &mut Unstructured) -> Result<Path> {
50
0
        let succs = u.int_in_range(0..=(2 * cfg.num_blocks))?;
51
0
        let mut block = Block::new(0);
52
0
        let mut blocks = vec![];
53
0
        blocks.push(block);
54
0
        for _ in 0..succs {
55
0
            if cfg.succs[block.index()].is_empty() {
56
0
                break;
57
0
            }
58
0
            block = *u.choose(&cfg.succs[block.index()])?;
59
0
            blocks.push(block);
60
        }
61
0
        Ok(Path { blocks })
62
0
    }
63
}
64
65
0
fn check_idom_violations(idom: &[Block], path: &Path) {
66
    // "a dom b" means that any path from the entry block through the CFG that
67
    // contains a and b will contain a before b.
68
    //
69
    // To test this, for any given block b_i, we have the set S of b_0 ..
70
    // b_{i-1}, and we walk up the domtree from b_i to get all blocks that
71
    // dominate b_i; each such block must appear in S. (Otherwise, we have a
72
    // counterexample for which dominance says it should appear in the path
73
    // prefix, but it does not.)
74
0
    let mut visited = HashSet::new();
75
0
    visited.insert(Block::new(0));
76
0
    for block in &path.blocks {
77
0
        let mut parent = idom[block.index()];
78
0
        let mut domset = HashSet::new();
79
0
        domset.insert(*block);
80
0
        while parent.is_valid() {
81
0
            assert!(visited.contains(&parent));
82
0
            domset.insert(parent);
83
0
            let next = idom[parent.index()];
84
0
            parent = next;
85
        }
86
87
        // Check that `dominates()` returns true for every block in domset,
88
        // and false for every other block.
89
0
        for domblock in 0..idom.len() {
90
0
            let domblock = Block::new(domblock);
91
0
            assert_eq!(
92
0
                domset.contains(&domblock),
93
0
                domtree::dominates(idom, domblock, *block)
94
            );
95
        }
96
0
        visited.insert(*block);
97
    }
98
0
}
99
100
/// A control-flow graph ([`CFG`]) and a [`Path`] through it.
101
#[derive(Clone, Debug)]
102
pub struct TestCase {
103
    cfg: CFG,
104
    path: Path,
105
}
106
107
impl Arbitrary<'_> for TestCase {
108
0
    fn arbitrary(u: &mut Unstructured) -> Result<TestCase> {
109
0
        let cfg = CFG::arbitrary(u)?;
110
0
        let path = Path::choose_from_cfg(&cfg, u)?;
111
0
        Ok(TestCase { cfg, path })
112
0
    }
113
}
114
115
0
pub fn check(t: TestCase) {
116
0
    let mut postorder = vec![];
117
0
    postorder::calculate(
118
0
        t.cfg.num_blocks,
119
0
        Block::new(0),
120
0
        &mut vec![],
121
0
        &mut postorder,
122
0
        |block| &t.cfg.succs[block.index()],
123
    )
124
0
    .unwrap();
125
126
0
    let mut idom = vec![];
127
0
    domtree::calculate(
128
0
        t.cfg.num_blocks,
129
0
        |block| &t.cfg.preds[block.index()],
130
0
        &postorder[..],
131
0
        &mut vec![],
132
0
        &mut idom,
133
0
        Block::new(0),
134
    );
135
0
    check_idom_violations(&idom[..], &t.path);
136
0
}
137
138
#[test]
139
fn smoke() {
140
    arbtest::arbtest(|u| {
141
        let test_case = TestCase::arbitrary(u)?;
142
        check(test_case);
143
        Ok(())
144
    })
145
    .budget_ms(1_000);
146
}