/rust/registry/src/index.crates.io-6f17d22bba15001f/regalloc2-0.5.1/src/checker.rs
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The following code is derived from `lib/src/checker.rs` in the |
3 | | * regalloc.rs project |
4 | | * (https://github.com/bytecodealliance/regalloc.rs). regalloc.rs is |
5 | | * also licensed under Apache-2.0 with the LLVM exception, as the rest |
6 | | * of regalloc2 is. |
7 | | */ |
8 | | |
9 | | //! Checker: verifies that spills/reloads/moves retain equivalent |
10 | | //! dataflow to original, VReg-based code. |
11 | | //! |
12 | | //! The basic idea is that we track symbolic values as they flow |
13 | | //! through spills and reloads. The symbolic values represent |
14 | | //! particular virtual registers in the original function body |
15 | | //! presented to the register allocator. Any instruction in the |
16 | | //! original function body (i.e., not added by the allocator) |
17 | | //! conceptually generates a symbolic value "Vn" when storing to (or |
18 | | //! modifying) a virtual register. |
19 | | //! |
20 | | //! A symbolic value is logically a *set of virtual registers*, |
21 | | //! representing all virtual registers equal to the value in the given |
22 | | //! storage slot at a given program point. This representation (as |
23 | | //! opposed to tracking just one virtual register) is necessary |
24 | | //! because the regalloc may implement moves in the source program |
25 | | //! (via move instructions or blockparam assignments on edges) in |
26 | | //! "intelligent" ways, taking advantage of values that are already in |
27 | | //! the right place, so we need to know *all* names for a value. |
28 | | //! |
29 | | //! These symbolic values are precise but partial: in other words, if |
30 | | //! a physical register is described as containing a virtual register |
31 | | //! at a program point, it must actually contain the value of this |
32 | | //! register (modulo any analysis bugs); but it may describe fewer |
33 | | //! virtual registers even in cases where one *could* statically prove |
34 | | //! that it contains a certain register, because the analysis is not |
35 | | //! perfectly path-sensitive or value-sensitive. However, all |
36 | | //! assignments *produced by our register allocator* should be |
37 | | //! analyzed fully precisely. (This last point is important and bears |
38 | | //! repeating: we only need to verify the programs that we produce, |
39 | | //! not arbitrary programs.) |
40 | | //! |
41 | | //! Operand constraints (fixed register, register, any) are also checked |
42 | | //! at each operand. |
43 | | //! |
44 | | //! ## Formal Definition |
45 | | //! |
46 | | //! The analysis lattice consists of the elements of 𝒫(V), the |
47 | | //! powerset (set of all subsets) of V (the set of all virtual |
48 | | //! registers). The ⊤ (top) value in the lattice is V itself, and the |
49 | | //! ⊥ (bottom) value in the lattice is ∅ (the empty set). The lattice |
50 | | //! ordering relation is the subset relation: S ≤ U iff S ⊆ U. These |
51 | | //! definitions imply that the lattice meet-function (greatest lower |
52 | | //! bound) is set-intersection. |
53 | | //! |
54 | | //! (For efficiency, we represent ⊤ not by actually listing out all |
55 | | //! virtual registers, but by representing a special "top" value, but |
56 | | //! the semantics are the same.) |
57 | | //! |
58 | | //! The dataflow analysis state at each program point (each point |
59 | | //! before or after an instruction) is: |
60 | | //! |
61 | | //! - map of: Allocation -> lattice value |
62 | | //! |
63 | | //! And the transfer functions for instructions are (where `A` is the |
64 | | //! above map from allocated physical registers to lattice values): |
65 | | //! |
66 | | //! - `Edit::Move` inserted by RA: [ alloc_d := alloc_s ] |
67 | | //! |
68 | | //! A' = A[alloc_d → A[alloc_s]] |
69 | | //! |
70 | | //! - statement in pre-regalloc function [ V_i := op V_j, V_k, ... ] |
71 | | //! with allocated form [ A_i := op A_j, A_k, ... ] |
72 | | //! |
73 | | //! A' = { A_k → A[A_k] \ { V_i } for k ≠ i } ∪ |
74 | | //! { A_i -> { V_i } } |
75 | | //! |
76 | | //! In other words, a statement, even after allocation, generates |
77 | | //! a symbol that corresponds to its original virtual-register |
78 | | //! def. Simultaneously, that same virtual register symbol is removed |
79 | | //! from all other allocs: they no longer carry the current value. |
80 | | //! |
81 | | //! - Parallel moves or blockparam-assignments in original program |
82 | | //! [ V_d1 := V_s1, V_d2 := V_s2, ... ] |
83 | | //! |
84 | | //! A' = { A_k → subst(A[A_k]) for all k } |
85 | | //! where subst(S) removes symbols for overwritten virtual |
86 | | //! registers (V_d1 .. V_dn) and then adds V_di whenever |
87 | | //! V_si appeared prior to the removals. |
88 | | //! |
89 | | //! To check correctness, we first find the dataflow fixpoint with the |
90 | | //! above lattice and transfer/meet functions. Then, at each op, we |
91 | | //! examine the dataflow solution at the preceding program point, and |
92 | | //! check that the allocation for each op arg (input/use) contains the |
93 | | //! symbol corresponding to the original virtual register specified |
94 | | //! for this arg. |
95 | | |
96 | | #![allow(dead_code)] |
97 | | |
98 | | use crate::{ |
99 | | Allocation, AllocationKind, Block, Edit, Function, Inst, InstOrEdit, InstPosition, MachineEnv, |
100 | | Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg, PRegSet, VReg, |
101 | | }; |
102 | | use fxhash::{FxHashMap, FxHashSet}; |
103 | | use smallvec::{smallvec, SmallVec}; |
104 | | use std::default::Default; |
105 | | use std::hash::Hash; |
106 | | use std::result::Result; |
107 | | |
108 | | /// A set of errors detected by the regalloc checker. |
109 | | #[derive(Clone, Debug)] |
110 | | pub struct CheckerErrors { |
111 | | errors: Vec<CheckerError>, |
112 | | } |
113 | | |
114 | | /// A single error detected by the regalloc checker. |
115 | | #[derive(Clone, Debug)] |
116 | | pub enum CheckerError { |
117 | | MissingAllocation { |
118 | | inst: Inst, |
119 | | op: Operand, |
120 | | }, |
121 | | UnknownValueInAllocation { |
122 | | inst: Inst, |
123 | | op: Operand, |
124 | | alloc: Allocation, |
125 | | }, |
126 | | ConflictedValueInAllocation { |
127 | | inst: Inst, |
128 | | op: Operand, |
129 | | alloc: Allocation, |
130 | | }, |
131 | | IncorrectValuesInAllocation { |
132 | | inst: Inst, |
133 | | op: Operand, |
134 | | alloc: Allocation, |
135 | | actual: FxHashSet<VReg>, |
136 | | }, |
137 | | ConstraintViolated { |
138 | | inst: Inst, |
139 | | op: Operand, |
140 | | alloc: Allocation, |
141 | | }, |
142 | | AllocationIsNotReg { |
143 | | inst: Inst, |
144 | | op: Operand, |
145 | | alloc: Allocation, |
146 | | }, |
147 | | AllocationIsNotFixedReg { |
148 | | inst: Inst, |
149 | | op: Operand, |
150 | | alloc: Allocation, |
151 | | }, |
152 | | AllocationIsNotReuse { |
153 | | inst: Inst, |
154 | | op: Operand, |
155 | | alloc: Allocation, |
156 | | expected_alloc: Allocation, |
157 | | }, |
158 | | AllocationIsNotStack { |
159 | | inst: Inst, |
160 | | op: Operand, |
161 | | alloc: Allocation, |
162 | | }, |
163 | | ConflictedValueInStackmap { |
164 | | inst: Inst, |
165 | | alloc: Allocation, |
166 | | }, |
167 | | NonRefValuesInStackmap { |
168 | | inst: Inst, |
169 | | alloc: Allocation, |
170 | | vregs: FxHashSet<VReg>, |
171 | | }, |
172 | | StackToStackMove { |
173 | | into: Allocation, |
174 | | from: Allocation, |
175 | | }, |
176 | | } |
177 | | |
178 | | /// Abstract state for an allocation. |
179 | | /// |
180 | | /// Equivalent to a set of virtual register names, with the |
181 | | /// universe-set as top and empty set as bottom lattice element. The |
182 | | /// meet-function is thus set intersection. |
183 | | #[derive(Clone, Debug, PartialEq, Eq)] |
184 | | enum CheckerValue { |
185 | | /// The lattice top-value: this value could be equivalent to any |
186 | | /// vreg (i.e., the universe set). |
187 | | Universe, |
188 | | /// The set of VRegs that this value is equal to. |
189 | | VRegs(FxHashSet<VReg>), |
190 | | } |
191 | | |
192 | | impl CheckerValue { |
193 | 0 | fn vregs(&self) -> Option<&FxHashSet<VReg>> { |
194 | 0 | match self { |
195 | 0 | CheckerValue::Universe => None, |
196 | 0 | CheckerValue::VRegs(vregs) => Some(vregs), |
197 | | } |
198 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerValue>::vregs Unexecuted instantiation: <regalloc2::checker::CheckerValue>::vregs |
199 | | |
200 | 0 | fn vregs_mut(&mut self) -> Option<&mut FxHashSet<VReg>> { |
201 | 0 | match self { |
202 | 0 | CheckerValue::Universe => None, |
203 | 0 | CheckerValue::VRegs(vregs) => Some(vregs), |
204 | | } |
205 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerValue>::vregs_mut Unexecuted instantiation: <regalloc2::checker::CheckerValue>::vregs_mut |
206 | | } |
207 | | |
208 | | impl Default for CheckerValue { |
209 | 0 | fn default() -> CheckerValue { |
210 | 0 | CheckerValue::Universe |
211 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerValue as core::default::Default>::default Unexecuted instantiation: <regalloc2::checker::CheckerValue as core::default::Default>::default |
212 | | } |
213 | | |
214 | | impl CheckerValue { |
215 | | /// Meet function of the abstract-interpretation value |
216 | | /// lattice. Returns a boolean value indicating whether `self` was |
217 | | /// changed. |
218 | 0 | fn meet_with(&mut self, other: &CheckerValue) { |
219 | 0 | match (self, other) { |
220 | 0 | (_, CheckerValue::Universe) => { |
221 | 0 | // Nothing. |
222 | 0 | } |
223 | 0 | (this @ CheckerValue::Universe, _) => { |
224 | 0 | *this = other.clone(); |
225 | 0 | } |
226 | 0 | (CheckerValue::VRegs(my_vregs), CheckerValue::VRegs(other_vregs)) => { |
227 | 0 | my_vregs.retain(|vreg| other_vregs.contains(vreg)); |
228 | 0 | } |
229 | | } |
230 | 0 | } |
231 | | |
232 | 0 | fn from_reg(reg: VReg) -> CheckerValue { |
233 | 0 | CheckerValue::VRegs(std::iter::once(reg).collect()) |
234 | 0 | } |
235 | | |
236 | 0 | fn remove_vreg(&mut self, reg: VReg) { |
237 | 0 | match self { |
238 | | CheckerValue::Universe => { |
239 | 0 | panic!("Cannot remove VReg from Universe set (we do not have the full list of vregs available"); |
240 | | } |
241 | 0 | CheckerValue::VRegs(vregs) => { |
242 | 0 | vregs.remove(®); |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | 0 | fn copy_vreg(&mut self, src: VReg, dst: VReg) { |
248 | 0 | match self { |
249 | 0 | CheckerValue::Universe => { |
250 | 0 | // Nothing. |
251 | 0 | } |
252 | 0 | CheckerValue::VRegs(vregs) => { |
253 | 0 | if vregs.contains(&src) { |
254 | 0 | vregs.insert(dst); |
255 | 0 | } |
256 | | } |
257 | | } |
258 | 0 | } |
259 | | |
260 | 0 | fn empty() -> CheckerValue { |
261 | 0 | CheckerValue::VRegs(FxHashSet::default()) |
262 | 0 | } |
263 | | } |
264 | | |
265 | 0 | fn visit_all_vregs<F: Function, V: FnMut(VReg)>(f: &F, mut v: V) { |
266 | 0 | for block in 0..f.num_blocks() { |
267 | 0 | let block = Block::new(block); |
268 | 0 | for inst in f.block_insns(block).iter() { |
269 | 0 | for op in f.inst_operands(inst) { |
270 | 0 | v(op.vreg()); |
271 | 0 | } |
272 | 0 | if let Some((src, dst)) = f.is_move(inst) { |
273 | 0 | v(src.vreg()); |
274 | 0 | v(dst.vreg()); |
275 | 0 | } |
276 | 0 | if f.is_branch(inst) { |
277 | 0 | for succ_idx in 0..f.block_succs(block).len() { |
278 | 0 | for ¶m in f.branch_blockparams(block, inst, succ_idx) { |
279 | 0 | v(param); |
280 | 0 | } |
281 | | } |
282 | 0 | } |
283 | | } |
284 | 0 | for &vreg in f.block_params(block) { |
285 | 0 | v(vreg); |
286 | 0 | } |
287 | | } |
288 | 0 | } Unexecuted instantiation: regalloc2::checker::visit_all_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>, <regalloc2::checker::CheckerState>::initial_with_pinned_vregs<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#0}>Unexecuted instantiation: regalloc2::checker::visit_all_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>, <regalloc2::checker::CheckerState>::initial_with_pinned_vregs<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#0}>Unexecuted instantiation: regalloc2::checker::visit_all_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>, <regalloc2::checker::CheckerState>::initial_with_pinned_vregs<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#0}>Unexecuted instantiation: regalloc2::checker::visit_all_vregs::<_, _> |
289 | | |
290 | | /// State that steps through program points as we scan over the instruction stream. |
291 | | #[derive(Clone, Debug, PartialEq, Eq)] |
292 | | enum CheckerState { |
293 | | Top, |
294 | | Allocations(FxHashMap<Allocation, CheckerValue>), |
295 | | } |
296 | | |
297 | | impl CheckerState { |
298 | 0 | fn get_value(&self, alloc: &Allocation) -> Option<&CheckerValue> { |
299 | 0 | match self { |
300 | 0 | CheckerState::Top => None, |
301 | 0 | CheckerState::Allocations(allocs) => allocs.get(alloc), |
302 | | } |
303 | 0 | } |
304 | | |
305 | 0 | fn get_values_mut(&mut self) -> impl Iterator<Item = &mut CheckerValue> { |
306 | 0 | match self { |
307 | 0 | CheckerState::Top => panic!("Cannot get mutable values iterator on Top state"), |
308 | 0 | CheckerState::Allocations(allocs) => allocs.values_mut(), |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | 0 | fn get_mappings(&self) -> impl Iterator<Item = (&Allocation, &CheckerValue)> { |
313 | 0 | match self { |
314 | 0 | CheckerState::Top => panic!("Cannot get mappings iterator on Top state"), |
315 | 0 | CheckerState::Allocations(allocs) => allocs.iter(), |
316 | 0 | } |
317 | 0 | } |
318 | | |
319 | 0 | fn get_mappings_mut(&mut self) -> impl Iterator<Item = (&Allocation, &mut CheckerValue)> { |
320 | 0 | match self { |
321 | 0 | CheckerState::Top => panic!("Cannot get mutable mappings iterator on Top state"), |
322 | 0 | CheckerState::Allocations(allocs) => allocs.iter_mut(), |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | /// Transition from a "top" (undefined/unanalyzed) state to an empty set of allocations. |
327 | 0 | fn become_defined(&mut self) { |
328 | 0 | match self { |
329 | 0 | CheckerState::Top => *self = CheckerState::Allocations(FxHashMap::default()), |
330 | 0 | _ => {} |
331 | | } |
332 | 0 | } |
333 | | |
334 | 0 | fn set_value(&mut self, alloc: Allocation, value: CheckerValue) { |
335 | 0 | match self { |
336 | | CheckerState::Top => { |
337 | 0 | panic!("Cannot set value on Top state"); |
338 | | } |
339 | 0 | CheckerState::Allocations(allocs) => { |
340 | 0 | allocs.insert(alloc, value); |
341 | 0 | } |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | 0 | fn copy_vreg(&mut self, src: VReg, dst: VReg) { |
346 | 0 | match self { |
347 | 0 | CheckerState::Top => { |
348 | 0 | // Nothing. |
349 | 0 | } |
350 | 0 | CheckerState::Allocations(allocs) => { |
351 | 0 | for value in allocs.values_mut() { |
352 | 0 | value.copy_vreg(src, dst); |
353 | 0 | } |
354 | | } |
355 | | } |
356 | 0 | } |
357 | | |
358 | 0 | fn remove_value(&mut self, alloc: &Allocation) { |
359 | 0 | match self { |
360 | | CheckerState::Top => { |
361 | 0 | panic!("Cannot remove value on Top state"); |
362 | | } |
363 | 0 | CheckerState::Allocations(allocs) => { |
364 | 0 | allocs.remove(alloc); |
365 | 0 | } |
366 | 0 | } |
367 | 0 | } |
368 | | |
369 | 0 | fn initial_with_pinned_vregs<F: Function>(f: &F) -> CheckerState { |
370 | 0 | // Scan the function, looking for all vregs that are pinned |
371 | 0 | // vregs, gathering them with their PRegs. |
372 | 0 | let mut pinned_vregs: FxHashMap<VReg, PReg> = FxHashMap::default(); |
373 | 0 | visit_all_vregs(f, |vreg: VReg| { |
374 | 0 | if let Some(preg) = f.is_pinned_vreg(vreg) { |
375 | 0 | pinned_vregs.insert(vreg, preg); |
376 | 0 | } |
377 | 0 | }); Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<_>::{closure#0} |
378 | 0 |
|
379 | 0 | let mut allocs = FxHashMap::default(); |
380 | 0 | for (vreg, preg) in pinned_vregs { |
381 | 0 | allocs.insert( |
382 | 0 | Allocation::reg(preg), |
383 | 0 | CheckerValue::VRegs(std::iter::once(vreg).collect()), |
384 | 0 | ); |
385 | 0 | } |
386 | | |
387 | 0 | CheckerState::Allocations(allocs) |
388 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::initial_with_pinned_vregs::<_> |
389 | | } |
390 | | |
391 | | impl Default for CheckerState { |
392 | 0 | fn default() -> CheckerState { |
393 | 0 | CheckerState::Top |
394 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState as core::default::Default>::default Unexecuted instantiation: <regalloc2::checker::CheckerState as core::default::Default>::default |
395 | | } |
396 | | |
397 | | impl std::fmt::Display for CheckerValue { |
398 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
399 | 0 | match self { |
400 | | CheckerValue::Universe => { |
401 | 0 | write!(f, "top") |
402 | | } |
403 | 0 | CheckerValue::VRegs(vregs) => { |
404 | 0 | write!(f, "{{ ")?; |
405 | 0 | for vreg in vregs { |
406 | 0 | write!(f, "{} ", vreg)?; |
407 | | } |
408 | 0 | write!(f, "}}")?; |
409 | 0 | Ok(()) |
410 | | } |
411 | | } |
412 | 0 | } |
413 | | } |
414 | | |
415 | | /// Meet function for analysis value: meet individual values at |
416 | | /// matching allocations, and intersect keys (remove key-value pairs |
417 | | /// only on one side). Returns boolean flag indicating whether `into` |
418 | | /// changed. |
419 | 0 | fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>( |
420 | 0 | into: &mut FxHashMap<K, CheckerValue>, |
421 | 0 | from: &FxHashMap<K, CheckerValue>, |
422 | 0 | ) { |
423 | 0 | into.retain(|k, _| from.contains_key(k)); |
424 | 0 | for (k, into_v) in into.iter_mut() { |
425 | 0 | let from_v = from.get(k).unwrap(); |
426 | 0 | into_v.meet_with(from_v); |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | impl CheckerState { |
431 | | /// Create a new checker state. |
432 | 0 | fn new() -> CheckerState { |
433 | 0 | Default::default() |
434 | 0 | } |
435 | | |
436 | | /// Merge this checker state with another at a CFG join-point. |
437 | 0 | fn meet_with(&mut self, other: &CheckerState) { |
438 | 0 | match (self, other) { |
439 | 0 | (_, CheckerState::Top) => { |
440 | 0 | // Nothing. |
441 | 0 | } |
442 | 0 | (this @ CheckerState::Top, _) => { |
443 | 0 | *this = other.clone(); |
444 | 0 | } |
445 | | ( |
446 | 0 | CheckerState::Allocations(my_allocations), |
447 | 0 | CheckerState::Allocations(other_allocations), |
448 | 0 | ) => { |
449 | 0 | merge_map(my_allocations, other_allocations); |
450 | 0 | } |
451 | | } |
452 | 0 | } |
453 | | |
454 | 0 | fn check_val<'a, F: Function>( |
455 | 0 | &self, |
456 | 0 | inst: Inst, |
457 | 0 | op: Operand, |
458 | 0 | alloc: Allocation, |
459 | 0 | val: &CheckerValue, |
460 | 0 | allocs: &[Allocation], |
461 | 0 | checker: &Checker<'a, F>, |
462 | 0 | ) -> Result<(), CheckerError> { |
463 | 0 | if alloc == Allocation::none() { |
464 | 0 | return Err(CheckerError::MissingAllocation { inst, op }); |
465 | 0 | } |
466 | 0 |
|
467 | 0 | if op.as_fixed_nonallocatable().is_none() { |
468 | 0 | match val { |
469 | | CheckerValue::Universe => { |
470 | 0 | return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc }); |
471 | | } |
472 | 0 | CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => { |
473 | 0 | return Err(CheckerError::IncorrectValuesInAllocation { |
474 | 0 | inst, |
475 | 0 | op, |
476 | 0 | alloc, |
477 | 0 | actual: vregs.clone(), |
478 | 0 | }); |
479 | | } |
480 | 0 | _ => {} |
481 | | } |
482 | 0 | } |
483 | | |
484 | 0 | self.check_constraint(inst, op, alloc, allocs, checker)?; |
485 | | |
486 | 0 | Ok(()) |
487 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_val::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_val::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_val::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_val::<_> |
488 | | |
489 | | /// Check an instruction against this state. This must be called |
490 | | /// twice: once with `InstPosition::Before`, and once with |
491 | | /// `InstPosition::After` (after updating state with defs). |
492 | 0 | fn check<'a, F: Function>( |
493 | 0 | &self, |
494 | 0 | pos: InstPosition, |
495 | 0 | checkinst: &CheckerInst, |
496 | 0 | checker: &Checker<'a, F>, |
497 | 0 | ) -> Result<(), CheckerError> { |
498 | 0 | let default_val = Default::default(); |
499 | 0 | match checkinst { |
500 | | &CheckerInst::Op { |
501 | 0 | inst, |
502 | 0 | ref operands, |
503 | 0 | ref allocs, |
504 | 0 | .. |
505 | 0 | } => { |
506 | 0 | // Skip Use-checks at the After point if there are any |
507 | 0 | // reused inputs: the Def which reuses the input |
508 | 0 | // happens early. |
509 | 0 | let has_reused_input = operands |
510 | 0 | .iter() |
511 | 0 | .any(|op| matches!(op.constraint(), OperandConstraint::Reuse(_))); Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<_>::{closure#0} |
512 | 0 | if has_reused_input && pos == InstPosition::After { |
513 | 0 | return Ok(()); |
514 | 0 | } |
515 | | |
516 | | // For each operand, check (i) that the allocation |
517 | | // contains the expected vreg, and (ii) that it meets |
518 | | // the requirements of the OperandConstraint. |
519 | 0 | for (op, alloc) in operands.iter().zip(allocs.iter()) { |
520 | 0 | let is_here = match (op.pos(), pos) { |
521 | 0 | (OperandPos::Early, InstPosition::Before) => true, |
522 | 0 | (OperandPos::Late, InstPosition::After) => true, |
523 | 0 | _ => false, |
524 | | }; |
525 | 0 | if !is_here { |
526 | 0 | continue; |
527 | 0 | } |
528 | 0 | if op.kind() == OperandKind::Def { |
529 | 0 | continue; |
530 | 0 | } |
531 | 0 |
|
532 | 0 | let val = self.get_value(alloc).unwrap_or(&default_val); |
533 | 0 | trace!( |
534 | 0 | "checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}", |
535 | | checkinst, |
536 | | op, |
537 | | alloc, |
538 | | val |
539 | | ); |
540 | 0 | self.check_val(inst, *op, *alloc, val, allocs, checker)?; |
541 | | } |
542 | | } |
543 | 0 | &CheckerInst::Safepoint { inst, ref allocs } => { |
544 | 0 | for &alloc in allocs { |
545 | 0 | let val = self.get_value(&alloc).unwrap_or(&default_val); |
546 | 0 | trace!( |
547 | 0 | "checker: checkinst {:?}: safepoint slot {}, checker value {:?}", |
548 | | checkinst, |
549 | | alloc, |
550 | | val |
551 | | ); |
552 | | |
553 | 0 | let reffy = val |
554 | 0 | .vregs() |
555 | 0 | .expect("checker value should not be Universe set") |
556 | 0 | .iter() |
557 | 0 | .any(|vreg| checker.reftyped_vregs.contains(vreg)); Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#1}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#1}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#1}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<_>::{closure#1} |
558 | 0 | if !reffy { |
559 | 0 | return Err(CheckerError::NonRefValuesInStackmap { |
560 | 0 | inst, |
561 | 0 | alloc, |
562 | 0 | vregs: val.vregs().unwrap().clone(), |
563 | 0 | }); |
564 | 0 | } |
565 | | } |
566 | | } |
567 | 0 | &CheckerInst::Move { into, from } => { |
568 | 0 | // Ensure that the allocator never returns stack-to-stack moves. |
569 | 0 | let is_stack = |alloc: Allocation| { |
570 | 0 | if let Some(reg) = alloc.as_reg() { |
571 | 0 | checker.stack_pregs.contains(reg) |
572 | | } else { |
573 | 0 | alloc.is_stack() |
574 | | } |
575 | 0 | }; Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#2}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#2}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#2}Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<_>::{closure#2} |
576 | 0 | if is_stack(into) && is_stack(from) { |
577 | 0 | return Err(CheckerError::StackToStackMove { into, from }); |
578 | 0 | } |
579 | | } |
580 | 0 | &CheckerInst::ParallelMove { .. } => { |
581 | 0 | // This doesn't need verification; we just update |
582 | 0 | // according to the move semantics in the step |
583 | 0 | // function below. |
584 | 0 | } |
585 | 0 | &CheckerInst::ProgramMove { inst, src, dst: _ } => { |
586 | | // Validate that the fixed-reg constraint, if any, on |
587 | | // `src` is satisfied. |
588 | 0 | if let OperandConstraint::FixedReg(preg) = src.constraint() { |
589 | 0 | let alloc = Allocation::reg(preg); |
590 | 0 | let val = self.get_value(&alloc).unwrap_or(&default_val); |
591 | 0 | trace!( |
592 | 0 | "checker: checkinst {:?}: cheker value in {:?} is {:?}", |
593 | | checkinst, |
594 | | alloc, |
595 | | val |
596 | | ); |
597 | 0 | self.check_val(inst, src, alloc, val, &[alloc], checker)?; |
598 | 0 | } |
599 | | // Note that we don't do anything with `dst` |
600 | | // here. That is implicitly checked whenever `dst` is |
601 | | // used; the `update()` step below adds the symbolic |
602 | | // vreg for `dst` into wherever `src` may be stored. |
603 | | } |
604 | | } |
605 | 0 | Ok(()) |
606 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check::<_> |
607 | | |
608 | | /// Update according to instruction. |
609 | 0 | fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) { |
610 | 0 | self.become_defined(); |
611 | 0 |
|
612 | 0 | match checkinst { |
613 | 0 | &CheckerInst::Move { into, from } => { |
614 | | // Value may not be present if this move is part of |
615 | | // the parallel move resolver's fallback sequence that |
616 | | // saves a victim register elsewhere. (In other words, |
617 | | // that sequence saves an undefined value and restores |
618 | | // it, so has no effect.) The checker needs to avoid |
619 | | // putting Universe lattice values into the map. |
620 | 0 | if let Some(val) = self.get_value(&from).cloned() { |
621 | 0 | trace!( |
622 | 0 | "checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}", |
623 | | checkinst, |
624 | | from, |
625 | | into, |
626 | | val |
627 | | ); |
628 | 0 | self.set_value(into, val); |
629 | 0 | } |
630 | | } |
631 | 0 | &CheckerInst::ParallelMove { ref moves } => { |
632 | 0 | // First, build map of actions for each vreg in an |
633 | 0 | // alloc. If an alloc has a reg V_i before a parallel |
634 | 0 | // move, then for each use of V_i as a source (V_i -> |
635 | 0 | // V_j), we might add a new V_j wherever V_i appears; |
636 | 0 | // and if V_i is used as a dest (at most once), then |
637 | 0 | // it must be removed first from allocs' vreg sets. |
638 | 0 | let mut additions: FxHashMap<VReg, SmallVec<[VReg; 2]>> = FxHashMap::default(); |
639 | 0 | let mut deletions: FxHashSet<VReg> = FxHashSet::default(); |
640 | | |
641 | 0 | for &(dest, src) in moves { |
642 | 0 | deletions.insert(dest); |
643 | 0 | additions |
644 | 0 | .entry(src) |
645 | 0 | .or_insert_with(|| smallvec![]) Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>::{closure#0}Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<_>::{closure#0} |
646 | 0 | .push(dest); |
647 | 0 | } |
648 | | |
649 | | // Now process each allocation's set of vreg labels, |
650 | | // first deleting those labels that were updated by |
651 | | // this parallel move, then adding back labels |
652 | | // redefined by the move. |
653 | 0 | for value in self.get_values_mut() { |
654 | 0 | if let Some(vregs) = value.vregs_mut() { |
655 | 0 | let mut insertions: SmallVec<[VReg; 2]> = smallvec![]; |
656 | 0 | for &vreg in vregs.iter() { |
657 | 0 | if let Some(additions) = additions.get(&vreg) { |
658 | 0 | insertions.extend(additions.iter().cloned()); |
659 | 0 | } |
660 | | } |
661 | 0 | for &d in &deletions { |
662 | 0 | vregs.remove(&d); |
663 | 0 | } |
664 | 0 | vregs.extend(insertions); |
665 | 0 | } |
666 | | } |
667 | | } |
668 | | &CheckerInst::Op { |
669 | 0 | ref operands, |
670 | 0 | ref allocs, |
671 | 0 | ref clobbers, |
672 | | .. |
673 | | } => { |
674 | | // For each def, (i) update alloc to reflect defined |
675 | | // vreg (and only that vreg), and (ii) update all |
676 | | // other allocs in the checker state by removing this |
677 | | // vreg, if defined (other defs are now stale). |
678 | 0 | for (op, alloc) in operands.iter().zip(allocs.iter()) { |
679 | 0 | if op.kind() != OperandKind::Def { |
680 | 0 | continue; |
681 | 0 | } |
682 | 0 | self.remove_vreg(op.vreg()); |
683 | 0 | self.set_value(*alloc, CheckerValue::from_reg(op.vreg())); |
684 | | } |
685 | 0 | for clobber in clobbers { |
686 | 0 | self.remove_value(&Allocation::reg(*clobber)); |
687 | 0 | } |
688 | | } |
689 | 0 | &CheckerInst::Safepoint { ref allocs, .. } => { |
690 | 0 | for (alloc, value) in self.get_mappings_mut() { |
691 | 0 | if alloc.is_reg() { |
692 | 0 | continue; |
693 | 0 | } |
694 | 0 | if !allocs.contains(&alloc) { |
695 | 0 | // Remove all reftyped vregs as labels. |
696 | 0 | let new_vregs = value |
697 | 0 | .vregs() |
698 | 0 | .unwrap() |
699 | 0 | .difference(&checker.reftyped_vregs) |
700 | 0 | .cloned() |
701 | 0 | .collect(); |
702 | 0 | *value = CheckerValue::VRegs(new_vregs); |
703 | 0 | } |
704 | | } |
705 | | } |
706 | 0 | &CheckerInst::ProgramMove { inst: _, src, dst } => { |
707 | 0 | // Remove all earlier instances of `dst`: this vreg is |
708 | 0 | // now stale (it is being overwritten). |
709 | 0 | self.remove_vreg(dst.vreg()); |
710 | | // Define `dst` wherever `src` occurs. |
711 | 0 | for (_, value) in self.get_mappings_mut() { |
712 | 0 | value.copy_vreg(src.vreg(), dst.vreg()); |
713 | 0 | } |
714 | | } |
715 | | } |
716 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::update::<_> |
717 | | |
718 | 0 | fn remove_vreg(&mut self, vreg: VReg) { |
719 | 0 | for (_, value) in self.get_mappings_mut() { |
720 | 0 | value.remove_vreg(vreg); |
721 | 0 | } |
722 | 0 | } |
723 | | |
724 | 0 | fn check_constraint<'a, F: Function>( |
725 | 0 | &self, |
726 | 0 | inst: Inst, |
727 | 0 | op: Operand, |
728 | 0 | alloc: Allocation, |
729 | 0 | allocs: &[Allocation], |
730 | 0 | checker: &Checker<'a, F>, |
731 | 0 | ) -> Result<(), CheckerError> { |
732 | 0 | match op.constraint() { |
733 | 0 | OperandConstraint::Any => {} |
734 | | OperandConstraint::Reg => { |
735 | 0 | if let Some(preg) = alloc.as_reg() { |
736 | | // Reject pregs that represent a fixed stack slot. |
737 | 0 | if !checker.machine_env.fixed_stack_slots.contains(&preg) { |
738 | 0 | return Ok(()); |
739 | 0 | } |
740 | 0 | } |
741 | 0 | return Err(CheckerError::AllocationIsNotReg { inst, op, alloc }); |
742 | | } |
743 | | OperandConstraint::Stack => { |
744 | 0 | if alloc.kind() != AllocationKind::Stack { |
745 | | // Accept pregs that represent a fixed stack slot. |
746 | 0 | if let Some(preg) = alloc.as_reg() { |
747 | 0 | if checker.machine_env.fixed_stack_slots.contains(&preg) { |
748 | 0 | return Ok(()); |
749 | 0 | } |
750 | 0 | } |
751 | 0 | return Err(CheckerError::AllocationIsNotStack { inst, op, alloc }); |
752 | 0 | } |
753 | | } |
754 | 0 | OperandConstraint::FixedReg(preg) => { |
755 | 0 | if alloc != Allocation::reg(preg) { |
756 | 0 | return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc }); |
757 | 0 | } |
758 | | } |
759 | 0 | OperandConstraint::Reuse(idx) => { |
760 | 0 | if alloc.kind() != AllocationKind::Reg { |
761 | 0 | return Err(CheckerError::AllocationIsNotReg { inst, op, alloc }); |
762 | 0 | } |
763 | 0 | if alloc != allocs[idx] { |
764 | 0 | return Err(CheckerError::AllocationIsNotReuse { |
765 | 0 | inst, |
766 | 0 | op, |
767 | 0 | alloc, |
768 | 0 | expected_alloc: allocs[idx], |
769 | 0 | }); |
770 | 0 | } |
771 | | } |
772 | | } |
773 | 0 | Ok(()) |
774 | 0 | } Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_constraint::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_constraint::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_constraint::<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>> Unexecuted instantiation: <regalloc2::checker::CheckerState>::check_constraint::<_> |
775 | | } |
776 | | |
777 | | /// An instruction representation in the checker's BB summary. |
778 | | #[derive(Clone, Debug)] |
779 | | pub(crate) enum CheckerInst { |
780 | | /// A move between allocations (these could be registers or |
781 | | /// spillslots). |
782 | | Move { into: Allocation, from: Allocation }, |
783 | | |
784 | | /// A parallel move in the original program. Simultaneously moves |
785 | | /// from all source vregs to all corresponding dest vregs, |
786 | | /// permitting overlap in the src and dest sets and doing all |
787 | | /// reads before any writes. |
788 | | ParallelMove { |
789 | | /// Vector of (dest, src) moves. |
790 | | moves: Vec<(VReg, VReg)>, |
791 | | }, |
792 | | |
793 | | /// A regular instruction with fixed use and def slots. Contains |
794 | | /// both the original operands (as given to the regalloc) and the |
795 | | /// allocation results. |
796 | | Op { |
797 | | inst: Inst, |
798 | | operands: Vec<Operand>, |
799 | | allocs: Vec<Allocation>, |
800 | | clobbers: Vec<PReg>, |
801 | | }, |
802 | | |
803 | | /// A safepoint, with the given Allocations specified as containing |
804 | | /// reftyped values. All other reftyped values become invalid. |
805 | | Safepoint { inst: Inst, allocs: Vec<Allocation> }, |
806 | | |
807 | | /// An op with one source operand, and one dest operand, that |
808 | | /// copies any symbolic values from the source to the dest, in |
809 | | /// addition to adding the symbolic value of the dest vreg to the |
810 | | /// set. This "program move" is distinguished from the above |
811 | | /// `Move` by being semantically relevant in the original |
812 | | /// (pre-regalloc) program. |
813 | | /// |
814 | | /// We transform checker values as follows: for any vreg-set that |
815 | | /// contains `dst`'s vreg, we first delete that vreg (because it |
816 | | /// is being redefined). Then, for any vreg-set with `src` |
817 | | /// present, we add `dst`. |
818 | | ProgramMove { |
819 | | inst: Inst, |
820 | | src: Operand, |
821 | | dst: Operand, |
822 | | }, |
823 | | } |
824 | | |
825 | | #[derive(Debug)] |
826 | | pub struct Checker<'a, F: Function> { |
827 | | f: &'a F, |
828 | | bb_in: FxHashMap<Block, CheckerState>, |
829 | | bb_insts: FxHashMap<Block, Vec<CheckerInst>>, |
830 | | edge_insts: FxHashMap<(Block, Block), Vec<CheckerInst>>, |
831 | | reftyped_vregs: FxHashSet<VReg>, |
832 | | machine_env: &'a MachineEnv, |
833 | | stack_pregs: PRegSet, |
834 | | } |
835 | | |
836 | | impl<'a, F: Function> Checker<'a, F> { |
837 | | /// Create a new checker for the given function, initializing CFG |
838 | | /// info immediately. The client should call the `add_*()` |
839 | | /// methods to add abstract instructions to each BB before |
840 | | /// invoking `run()` to check for errors. |
841 | 0 | pub fn new(f: &'a F, machine_env: &'a MachineEnv) -> Checker<'a, F> { |
842 | 0 | let mut bb_in = FxHashMap::default(); |
843 | 0 | let mut bb_insts = FxHashMap::default(); |
844 | 0 | let mut edge_insts = FxHashMap::default(); |
845 | 0 | let mut reftyped_vregs = FxHashSet::default(); |
846 | | |
847 | 0 | for block in 0..f.num_blocks() { |
848 | 0 | let block = Block::new(block); |
849 | 0 | bb_in.insert(block, Default::default()); |
850 | 0 | bb_insts.insert(block, vec![]); |
851 | 0 | for &succ in f.block_succs(block) { |
852 | 0 | edge_insts.insert((block, succ), vec![]); |
853 | 0 | } |
854 | | } |
855 | | |
856 | 0 | for &vreg in f.reftype_vregs() { |
857 | 0 | reftyped_vregs.insert(vreg); |
858 | 0 | } |
859 | | |
860 | 0 | bb_in.insert(f.entry_block(), CheckerState::initial_with_pinned_vregs(f)); |
861 | 0 |
|
862 | 0 | let mut stack_pregs = PRegSet::empty(); |
863 | 0 | for &preg in &machine_env.fixed_stack_slots { |
864 | 0 | stack_pregs.add(preg); |
865 | 0 | } |
866 | | |
867 | 0 | Checker { |
868 | 0 | f, |
869 | 0 | bb_in, |
870 | 0 | bb_insts, |
871 | 0 | edge_insts, |
872 | 0 | reftyped_vregs, |
873 | 0 | machine_env, |
874 | 0 | stack_pregs, |
875 | 0 | } |
876 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::new Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::new Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::new Unexecuted instantiation: <regalloc2::checker::Checker<_>>::new |
877 | | |
878 | | /// Build the list of checker instructions based on the given func |
879 | | /// and allocation results. |
880 | 0 | pub fn prepare(&mut self, out: &Output) { |
881 | 0 | trace!("checker: out = {:?}", out); |
882 | | // Preprocess safepoint stack-maps into per-inst vecs. |
883 | 0 | let mut safepoint_slots: FxHashMap<Inst, Vec<Allocation>> = FxHashMap::default(); |
884 | 0 | for &(progpoint, slot) in &out.safepoint_slots { |
885 | 0 | safepoint_slots |
886 | 0 | .entry(progpoint.inst()) |
887 | 0 | .or_insert_with(|| vec![]) Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::prepare::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::prepare::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::prepare::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<_>>::prepare::{closure#0} |
888 | 0 | .push(slot); |
889 | 0 | } |
890 | | |
891 | 0 | let mut last_inst = None; |
892 | 0 | for block in 0..self.f.num_blocks() { |
893 | 0 | let block = Block::new(block); |
894 | 0 | for inst_or_edit in out.block_insts_and_edits(self.f, block) { |
895 | 0 | match inst_or_edit { |
896 | 0 | InstOrEdit::Inst(inst) => { |
897 | 0 | debug_assert!(last_inst.is_none() || inst > last_inst.unwrap()); |
898 | 0 | last_inst = Some(inst); |
899 | 0 | self.handle_inst(block, inst, &mut safepoint_slots, out); |
900 | | } |
901 | 0 | InstOrEdit::Edit(edit) => self.handle_edit(block, edit), |
902 | | } |
903 | | } |
904 | | } |
905 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::prepare Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::prepare Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::prepare Unexecuted instantiation: <regalloc2::checker::Checker<_>>::prepare |
906 | | |
907 | | /// For each original instruction, create an `Op`. |
908 | 0 | fn handle_inst( |
909 | 0 | &mut self, |
910 | 0 | block: Block, |
911 | 0 | inst: Inst, |
912 | 0 | safepoint_slots: &mut FxHashMap<Inst, Vec<Allocation>>, |
913 | 0 | out: &Output, |
914 | 0 | ) { |
915 | 0 | // If this is a safepoint, then check the spillslots at this point. |
916 | 0 | if self.f.requires_refs_on_stack(inst) { |
917 | 0 | let allocs = safepoint_slots.remove(&inst).unwrap_or_else(|| vec![]); Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::handle_inst::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::handle_inst::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::handle_inst::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<_>>::handle_inst::{closure#0} |
918 | 0 |
|
919 | 0 | let checkinst = CheckerInst::Safepoint { inst, allocs }; |
920 | 0 | self.bb_insts.get_mut(&block).unwrap().push(checkinst); |
921 | 0 | } |
922 | | |
923 | | // If this is a move, handle specially. Note that the |
924 | | // regalloc2-inserted moves are not semantically present in |
925 | | // the original program and so do not modify the sets of |
926 | | // symbolic values at all, but rather just move them around; |
927 | | // but "program moves" *are* present, and have the following |
928 | | // semantics: they define the destination vreg, but also |
929 | | // retain any symbolic values in the source. |
930 | | // |
931 | | // regalloc2 reifies all moves into edits in its unified |
932 | | // move/edit framework, so we don't get allocs for these moves |
933 | | // in the post-regalloc output, and the embedder is not |
934 | | // supposed to emit the moves. But we *do* want to check the |
935 | | // semantic implications, namely definition of new vregs and, |
936 | | // for moves to/from pinned vregs, the implied register |
937 | | // constraints. So we emit `ProgramMove` ops that do just |
938 | | // this. |
939 | 0 | if let Some((src, dst)) = self.f.is_move(inst) { |
940 | 0 | let src_preg = self.f.is_pinned_vreg(src.vreg()); |
941 | 0 | let src_op = match src_preg { |
942 | 0 | Some(preg) => Operand::reg_fixed_use(src.vreg(), preg), |
943 | 0 | None => Operand::any_use(src.vreg()), |
944 | | }; |
945 | 0 | let dst_preg = self.f.is_pinned_vreg(dst.vreg()); |
946 | 0 | let dst_op = match dst_preg { |
947 | 0 | Some(preg) => Operand::reg_fixed_def(dst.vreg(), preg), |
948 | 0 | None => Operand::any_def(dst.vreg()), |
949 | | }; |
950 | 0 | let checkinst = CheckerInst::ProgramMove { |
951 | 0 | inst, |
952 | 0 | src: src_op, |
953 | 0 | dst: dst_op, |
954 | 0 | }; |
955 | 0 | trace!("checker: adding inst {:?}", checkinst); |
956 | 0 | self.bb_insts.get_mut(&block).unwrap().push(checkinst); |
957 | | } |
958 | | // Skip normal checks if this is a branch: the blockparams do |
959 | | // not exist in post-regalloc code, and the edge-moves have to |
960 | | // be inserted before the branch rather than after. |
961 | 0 | else if !self.f.is_branch(inst) { |
962 | 0 | let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect(); |
963 | 0 | let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect(); |
964 | 0 | let clobbers: Vec<_> = self.f.inst_clobbers(inst).into_iter().collect(); |
965 | 0 | let checkinst = CheckerInst::Op { |
966 | 0 | inst, |
967 | 0 | operands, |
968 | 0 | allocs, |
969 | 0 | clobbers, |
970 | 0 | }; |
971 | 0 | trace!("checker: adding inst {:?}", checkinst); |
972 | 0 | self.bb_insts.get_mut(&block).unwrap().push(checkinst); |
973 | | } |
974 | | // Instead, if this is a branch, emit a ParallelMove on each |
975 | | // outgoing edge as necessary to handle blockparams. |
976 | | else { |
977 | 0 | for (i, &succ) in self.f.block_succs(block).iter().enumerate() { |
978 | 0 | let args = self.f.branch_blockparams(block, inst, i); |
979 | 0 | let params = self.f.block_params(succ); |
980 | 0 | assert_eq!( |
981 | 0 | args.len(), |
982 | 0 | params.len(), |
983 | 0 | "block{} has succ block{}; gave {} args for {} params", |
984 | 0 | block.index(), |
985 | 0 | succ.index(), |
986 | 0 | args.len(), |
987 | 0 | params.len() |
988 | | ); |
989 | 0 | if args.len() > 0 { |
990 | 0 | let moves = params.iter().cloned().zip(args.iter().cloned()).collect(); |
991 | 0 | self.edge_insts |
992 | 0 | .get_mut(&(block, succ)) |
993 | 0 | .unwrap() |
994 | 0 | .push(CheckerInst::ParallelMove { moves }); |
995 | 0 | } |
996 | | } |
997 | | } |
998 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::handle_inst Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::handle_inst Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::handle_inst Unexecuted instantiation: <regalloc2::checker::Checker<_>>::handle_inst |
999 | | |
1000 | 0 | fn handle_edit(&mut self, block: Block, edit: &Edit) { |
1001 | 0 | trace!("checker: adding edit {:?}", edit); |
1002 | 0 | match edit { |
1003 | 0 | &Edit::Move { from, to } => { |
1004 | 0 | self.bb_insts |
1005 | 0 | .get_mut(&block) |
1006 | 0 | .unwrap() |
1007 | 0 | .push(CheckerInst::Move { into: to, from }); |
1008 | 0 | } |
1009 | 0 | } |
1010 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::handle_edit Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::handle_edit Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::handle_edit Unexecuted instantiation: <regalloc2::checker::Checker<_>>::handle_edit |
1011 | | |
1012 | | /// Perform the dataflow analysis to compute checker state at each BB entry. |
1013 | 0 | fn analyze(&mut self) { |
1014 | 0 | let mut queue = Vec::new(); |
1015 | 0 | let mut queue_set = FxHashSet::default(); |
1016 | 0 |
|
1017 | 0 | queue.push(self.f.entry_block()); |
1018 | 0 | queue_set.insert(self.f.entry_block()); |
1019 | | |
1020 | 0 | while !queue.is_empty() { |
1021 | 0 | let block = queue.pop().unwrap(); |
1022 | 0 | queue_set.remove(&block); |
1023 | 0 | let mut state = self.bb_in.get(&block).cloned().unwrap(); |
1024 | 0 | trace!("analyze: block {} has state {:?}", block.index(), state); |
1025 | 0 | for inst in self.bb_insts.get(&block).unwrap() { |
1026 | 0 | state.update(inst, self); |
1027 | 0 | trace!("analyze: inst {:?} -> state {:?}", inst, state); |
1028 | | } |
1029 | | |
1030 | 0 | for &succ in self.f.block_succs(block) { |
1031 | 0 | let mut new_state = state.clone(); |
1032 | 0 | for edge_inst in self.edge_insts.get(&(block, succ)).unwrap() { |
1033 | 0 | new_state.update(edge_inst, self); |
1034 | 0 | trace!( |
1035 | 0 | "analyze: succ {:?}: inst {:?} -> state {:?}", |
1036 | | succ, |
1037 | | edge_inst, |
1038 | | new_state |
1039 | | ); |
1040 | | } |
1041 | | |
1042 | 0 | let cur_succ_in = self.bb_in.get(&succ).unwrap(); |
1043 | 0 | trace!( |
1044 | 0 | "meeting state {:?} for block {} with state {:?} for block {}", |
1045 | 0 | new_state, |
1046 | 0 | block.index(), |
1047 | 0 | cur_succ_in, |
1048 | 0 | succ.index() |
1049 | | ); |
1050 | 0 | new_state.meet_with(cur_succ_in); |
1051 | 0 | let changed = &new_state != cur_succ_in; |
1052 | 0 | trace!(" -> {:?}, changed {}", new_state, changed); |
1053 | | |
1054 | 0 | if changed { |
1055 | 0 | trace!( |
1056 | 0 | "analyze: block {} state changed from {:?} to {:?}; pushing onto queue", |
1057 | 0 | succ.index(), |
1058 | | cur_succ_in, |
1059 | | new_state |
1060 | | ); |
1061 | 0 | self.bb_in.insert(succ, new_state); |
1062 | 0 | if !queue_set.contains(&succ) { |
1063 | 0 | queue.push(succ); |
1064 | 0 | queue_set.insert(succ); |
1065 | 0 | } |
1066 | 0 | } |
1067 | | } |
1068 | | } |
1069 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::analyze Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::analyze Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::analyze Unexecuted instantiation: <regalloc2::checker::Checker<_>>::analyze |
1070 | | |
1071 | | /// Using BB-start state computed by `analyze()`, step the checker state |
1072 | | /// through each BB and check each instruction's register allocations |
1073 | | /// for errors. |
1074 | 0 | fn find_errors(&self) -> Result<(), CheckerErrors> { |
1075 | 0 | let mut errors = vec![]; |
1076 | 0 | for (block, input) in &self.bb_in { |
1077 | 0 | let mut state = input.clone(); |
1078 | 0 | for inst in self.bb_insts.get(block).unwrap() { |
1079 | 0 | if let Err(e) = state.check(InstPosition::Before, inst, self) { |
1080 | 0 | trace!("Checker error: {:?}", e); |
1081 | 0 | errors.push(e); |
1082 | 0 | } |
1083 | 0 | state.update(inst, self); |
1084 | 0 | if let Err(e) = state.check(InstPosition::After, inst, self) { |
1085 | 0 | trace!("Checker error: {:?}", e); |
1086 | 0 | errors.push(e); |
1087 | 0 | } |
1088 | | } |
1089 | | } |
1090 | | |
1091 | 0 | if errors.is_empty() { |
1092 | 0 | Ok(()) |
1093 | | } else { |
1094 | 0 | Err(CheckerErrors { errors }) |
1095 | | } |
1096 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::find_errors Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::find_errors Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::find_errors Unexecuted instantiation: <regalloc2::checker::Checker<_>>::find_errors |
1097 | | |
1098 | | /// Find any errors, returning `Err(CheckerErrors)` with all errors found |
1099 | | /// or `Ok(())` otherwise. |
1100 | 0 | pub fn run(mut self) -> Result<(), CheckerErrors> { |
1101 | 0 | self.analyze(); |
1102 | 0 | let result = self.find_errors(); |
1103 | 0 |
|
1104 | 0 | trace!("=== CHECKER RESULT ==="); |
1105 | 0 | fn print_state(state: &CheckerState) { |
1106 | 0 | if let CheckerState::Allocations(allocs) = state { |
1107 | 0 | let mut s = vec![]; |
1108 | 0 | for (alloc, state) in allocs { |
1109 | 0 | s.push(format!("{} := {}", alloc, state)); |
1110 | 0 | } |
1111 | 0 | trace!(" {{ {} }}", s.join(", ")) |
1112 | 0 | } |
1113 | 0 | } |
1114 | 0 | for vreg in self.f.reftype_vregs() { |
1115 | 0 | trace!(" REF: {}", vreg); |
1116 | | } |
1117 | 0 | for bb in 0..self.f.num_blocks() { |
1118 | 0 | let bb = Block::new(bb); |
1119 | 0 | trace!("block{}:", bb.index()); |
1120 | 0 | let insts = self.bb_insts.get(&bb).unwrap(); |
1121 | 0 | let mut state = self.bb_in.get(&bb).unwrap().clone(); |
1122 | 0 | print_state(&state); |
1123 | 0 | for inst in insts { |
1124 | 0 | match inst { |
1125 | | &CheckerInst::Op { |
1126 | 0 | inst, |
1127 | 0 | ref operands, |
1128 | 0 | ref allocs, |
1129 | 0 | ref clobbers, |
1130 | 0 | } => { |
1131 | 0 | trace!( |
1132 | 0 | " inst{}: {:?} ({:?}) clobbers:{:?}", |
1133 | 0 | inst.index(), |
1134 | | operands, |
1135 | | allocs, |
1136 | | clobbers |
1137 | | ); |
1138 | | } |
1139 | 0 | &CheckerInst::Move { from, into } => { |
1140 | 0 | trace!(" {} -> {}", from, into); |
1141 | | } |
1142 | 0 | &CheckerInst::Safepoint { ref allocs, .. } => { |
1143 | 0 | let mut slotargs = vec![]; |
1144 | 0 | for &slot in allocs { |
1145 | 0 | slotargs.push(format!("{}", slot)); |
1146 | 0 | } |
1147 | 0 | trace!(" safepoint: {}", slotargs.join(", ")); |
1148 | | } |
1149 | 0 | &CheckerInst::ProgramMove { inst, src, dst } => { |
1150 | 0 | trace!(" inst{}: prog_move {} -> {}", inst.index(), src, dst); |
1151 | | } |
1152 | | &CheckerInst::ParallelMove { .. } => { |
1153 | 0 | panic!("unexpected parallel_move in body (non-edge)") |
1154 | | } |
1155 | | } |
1156 | 0 | state.update(inst, &self); |
1157 | 0 | print_state(&state); |
1158 | | } |
1159 | | |
1160 | 0 | for &succ in self.f.block_succs(bb) { |
1161 | 0 | trace!(" succ {:?}:", succ); |
1162 | 0 | let mut state = state.clone(); |
1163 | 0 | for edge_inst in self.edge_insts.get(&(bb, succ)).unwrap() { |
1164 | 0 | match edge_inst { |
1165 | 0 | &CheckerInst::ParallelMove { ref moves } => { |
1166 | 0 | let moves = moves |
1167 | 0 | .iter() |
1168 | 0 | .map(|(dest, src)| format!("{} -> {}", src, dest))Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::run::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::run::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::run::{closure#0}Unexecuted instantiation: <regalloc2::checker::Checker<_>>::run::{closure#0} |
1169 | 0 | .collect::<Vec<_>>(); |
1170 | 0 | trace!(" parallel_move {}", moves.join(", ")); |
1171 | | } |
1172 | 0 | _ => panic!("unexpected edge_inst: not a parallel move"), |
1173 | | } |
1174 | 0 | state.update(edge_inst, &self); |
1175 | 0 | print_state(&state); |
1176 | | } |
1177 | | } |
1178 | | } |
1179 | | |
1180 | 0 | result |
1181 | 0 | } Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::x64::lower::isle::generated_code::MInst>>>::run Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::aarch64::lower::isle::generated_code::MInst>>>::run Unexecuted instantiation: <regalloc2::checker::Checker<cranelift_codegen::machinst::vcode::VCode<cranelift_codegen::isa::riscv64::lower::isle::generated_code::MInst>>>::run Unexecuted instantiation: <regalloc2::checker::Checker<_>>::run |
1182 | | } |