/src/regalloc2/src/fuzzing/moves.rs
Line | Count | Source |
1 | | //! Fuzz the parallel-move resolver. |
2 | | |
3 | | use crate::moves::{MoveAndScratchResolver, ParallelMoves}; |
4 | | use crate::{Allocation, PReg, RegClass, SpillSlot}; |
5 | | use arbitrary::{Arbitrary, Result, Unstructured}; |
6 | | use std::collections::{HashMap, HashSet}; |
7 | | use std::{vec, vec::Vec}; |
8 | | |
9 | 0 | fn is_stack_alloc(alloc: Allocation) -> bool { |
10 | | // Treat registers 20..=29 as fixed stack slots. |
11 | 0 | if let Some(reg) = alloc.as_reg() { |
12 | 0 | reg.index() > 20 |
13 | | } else { |
14 | 0 | alloc.is_stack() |
15 | | } |
16 | 0 | } |
17 | | |
18 | | /// |
19 | | #[derive(Clone, Debug)] |
20 | | pub struct TestCase { |
21 | | moves: Vec<(Allocation, Allocation)>, |
22 | | available_pregs: Vec<Allocation>, |
23 | | } |
24 | | |
25 | | impl Arbitrary<'_> for TestCase { |
26 | 0 | fn arbitrary(u: &mut Unstructured) -> Result<Self> { |
27 | 0 | let mut ret = TestCase { |
28 | 0 | moves: vec![], |
29 | 0 | available_pregs: vec![], |
30 | 0 | }; |
31 | 0 | let mut written = HashSet::new(); |
32 | | // An arbitrary sequence of moves between registers 0 to 29 |
33 | | // inclusive. |
34 | 0 | while bool::arbitrary(u)? { |
35 | 0 | let src = if bool::arbitrary(u)? { |
36 | 0 | let reg = u.int_in_range(0..=29)?; |
37 | 0 | Allocation::reg(PReg::new(reg, RegClass::Int)) |
38 | | } else { |
39 | 0 | let slot = u.int_in_range(0..=31)?; |
40 | 0 | Allocation::stack(SpillSlot::new(slot)) |
41 | | }; |
42 | 0 | let dst = if bool::arbitrary(u)? { |
43 | 0 | let reg = u.int_in_range(0..=29)?; |
44 | 0 | Allocation::reg(PReg::new(reg, RegClass::Int)) |
45 | | } else { |
46 | 0 | let slot = u.int_in_range(0..=31)?; |
47 | 0 | Allocation::stack(SpillSlot::new(slot)) |
48 | | }; |
49 | | |
50 | | // Stop if we are going to write a reg more than once: |
51 | | // that creates an invalid parallel move set. |
52 | 0 | if written.contains(&dst) { |
53 | 0 | break; |
54 | 0 | } |
55 | 0 | written.insert(dst); |
56 | | |
57 | 0 | ret.moves.push((src, dst)); |
58 | | } |
59 | | |
60 | | // We might have some unallocated registers free for scratch |
61 | | // space... |
62 | 0 | for i in 0..u.int_in_range(0..=2)? { |
63 | 0 | let reg = PReg::new(30 + i, RegClass::Int); |
64 | 0 | ret.available_pregs.push(Allocation::reg(reg)); |
65 | 0 | } |
66 | 0 | Ok(ret) |
67 | 0 | } |
68 | | } |
69 | | |
70 | 0 | pub fn check(t: TestCase) { |
71 | 0 | let mut par = ParallelMoves::new(); |
72 | 0 | for &(src, dst) in &t.moves { |
73 | 0 | par.add(src, dst, ()); |
74 | 0 | } |
75 | | |
76 | 0 | let moves = par.resolve(); |
77 | 0 | log::trace!("raw resolved moves: {:?}", moves); |
78 | | |
79 | | // Resolve uses of scratch reg and stack-to-stack moves with the scratch |
80 | | // resolver. |
81 | 0 | let mut avail = t.available_pregs.clone(); |
82 | 0 | let find_free_reg = || avail.pop(); |
83 | 0 | let mut next_slot = 32; |
84 | 0 | let get_stackslot = || { |
85 | 0 | let slot = next_slot; |
86 | 0 | next_slot += 1; |
87 | 0 | Allocation::stack(SpillSlot::new(slot)) |
88 | 0 | }; |
89 | 0 | let preferred_victim = PReg::new(0, RegClass::Int); |
90 | 0 | let scratch_resolver = MoveAndScratchResolver { |
91 | 0 | find_free_reg, |
92 | 0 | get_stackslot, |
93 | 0 | is_stack_alloc, |
94 | 0 | borrowed_scratch_reg: preferred_victim, |
95 | 0 | }; |
96 | 0 | let moves = scratch_resolver.compute(moves); |
97 | 0 | log::trace!("resolved moves: {:?}", moves); |
98 | | |
99 | | // Compute the final source reg for each dest reg in the original |
100 | | // parallel-move set. |
101 | 0 | let mut final_src_per_dest: HashMap<Allocation, Allocation> = HashMap::new(); |
102 | 0 | for &(src, dst) in &t.moves { |
103 | 0 | final_src_per_dest.insert(dst, src); |
104 | 0 | } |
105 | 0 | log::trace!("expected final state: {:?}", final_src_per_dest); |
106 | | |
107 | | // Simulate the sequence of moves. |
108 | 0 | let mut locations: HashMap<Allocation, Allocation> = HashMap::new(); |
109 | 0 | for (src, dst, _) in moves { |
110 | 0 | let data = locations.get(&src).cloned().unwrap_or(src); |
111 | 0 | locations.insert(dst, data); |
112 | 0 | } |
113 | 0 | log::trace!("simulated final state: {:?}", locations); |
114 | | |
115 | | // Assert that the expected register-moves occurred. |
116 | 0 | for (reg, data) in locations { |
117 | 0 | if let Some(&expected_data) = final_src_per_dest.get(®) { |
118 | 0 | assert_eq!(expected_data, data); |
119 | | } else { |
120 | 0 | if data != reg { |
121 | | // If not just the original value, then this location has been |
122 | | // modified, but it was not part of the original parallel move. |
123 | | // It must have been an available preg or a scratch stackslot. |
124 | 0 | assert!( |
125 | 0 | t.available_pregs.contains(®) |
126 | 0 | || (reg.is_stack() && reg.as_stack().unwrap().index() >= 32) |
127 | | ); |
128 | 0 | } |
129 | | } |
130 | | } |
131 | 0 | } |
132 | | |
133 | | #[test] |
134 | | fn smoke() { |
135 | | arbtest::arbtest(|u| { |
136 | | let test_case = TestCase::arbitrary(u)?; |
137 | | check(test_case); |
138 | | Ok(()) |
139 | | }) |
140 | | .budget_ms(1_000); |
141 | | } |