Coverage Report

Created: 2025-12-09 07:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/regalloc2/src/checker.rs
Line
Count
Source
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, FxHashMap, FxHashSet, Inst, InstOrEdit,
100
    InstPosition, MachineEnv, Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg,
101
    PRegSet, VReg,
102
};
103
use alloc::vec::Vec;
104
use alloc::{format, vec};
105
use core::default::Default;
106
use core::hash::Hash;
107
use core::ops::Range;
108
use core::result::Result;
109
use smallvec::{smallvec, SmallVec};
110
111
/// A set of errors detected by the regalloc checker.
112
#[derive(Clone, Debug)]
113
pub struct CheckerErrors {
114
    errors: Vec<CheckerError>,
115
}
116
117
/// A single error detected by the regalloc checker.
118
#[derive(Clone, Debug)]
119
pub enum CheckerError {
120
    MissingAllocation {
121
        inst: Inst,
122
        op: Operand,
123
    },
124
    UnknownValueInAllocation {
125
        inst: Inst,
126
        op: Operand,
127
        alloc: Allocation,
128
    },
129
    ConflictedValueInAllocation {
130
        inst: Inst,
131
        op: Operand,
132
        alloc: Allocation,
133
    },
134
    IncorrectValuesInAllocation {
135
        inst: Inst,
136
        op: Operand,
137
        alloc: Allocation,
138
        actual: FxHashSet<VReg>,
139
    },
140
    ConstraintViolated {
141
        inst: Inst,
142
        op: Operand,
143
        alloc: Allocation,
144
    },
145
    AllocationIsNotReg {
146
        inst: Inst,
147
        op: Operand,
148
        alloc: Allocation,
149
    },
150
    AllocationIsNotFixedReg {
151
        inst: Inst,
152
        op: Operand,
153
        alloc: Allocation,
154
    },
155
    AllocationIsNotReuse {
156
        inst: Inst,
157
        op: Operand,
158
        alloc: Allocation,
159
        expected_alloc: Allocation,
160
    },
161
    AllocationIsNotStack {
162
        inst: Inst,
163
        op: Operand,
164
        alloc: Allocation,
165
    },
166
    StackToStackMove {
167
        into: Allocation,
168
        from: Allocation,
169
    },
170
    AllocationOutsideLimit {
171
        inst: Inst,
172
        op: Operand,
173
        alloc: Allocation,
174
        range: Range<usize>,
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
    fn vregs(&self) -> Option<&FxHashSet<VReg>> {
194
        match self {
195
            CheckerValue::Universe => None,
196
            CheckerValue::VRegs(vregs) => Some(vregs),
197
        }
198
    }
199
200
23.4M
    fn vregs_mut(&mut self) -> Option<&mut FxHashSet<VReg>> {
201
23.4M
        match self {
202
0
            CheckerValue::Universe => None,
203
23.4M
            CheckerValue::VRegs(vregs) => Some(vregs),
204
        }
205
23.4M
    }
206
}
207
208
impl Default for CheckerValue {
209
12.1M
    fn default() -> CheckerValue {
210
12.1M
        CheckerValue::Universe
211
12.1M
    }
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
56.7M
    fn meet_with(&mut self, other: &CheckerValue) {
219
56.7M
        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
56.7M
            (CheckerValue::VRegs(my_vregs), CheckerValue::VRegs(other_vregs)) => {
227
58.6M
                my_vregs.retain(|vreg| other_vregs.contains(vreg));
228
            }
229
        }
230
56.7M
    }
231
232
30.8M
    fn from_reg(reg: VReg) -> CheckerValue {
233
30.8M
        CheckerValue::VRegs(core::iter::once(reg).collect())
234
30.8M
    }
235
236
3.17G
    fn remove_vreg(&mut self, reg: VReg) {
237
3.17G
        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
3.17G
            CheckerValue::VRegs(vregs) => {
242
3.17G
                vregs.remove(&reg);
243
3.17G
            }
244
        }
245
3.17G
    }
246
247
    fn copy_vreg(&mut self, src: VReg, dst: VReg) {
248
        match self {
249
            CheckerValue::Universe => {
250
                // Nothing.
251
            }
252
            CheckerValue::VRegs(vregs) => {
253
                if vregs.contains(&src) {
254
                    vregs.insert(dst);
255
                }
256
            }
257
        }
258
    }
259
260
    fn empty() -> CheckerValue {
261
        CheckerValue::VRegs(FxHashSet::default())
262
    }
263
}
264
265
fn visit_all_vregs<F: Function, V: FnMut(VReg)>(f: &F, mut v: V) {
266
    for block in 0..f.num_blocks() {
267
        let block = Block::new(block);
268
        for inst in f.block_insns(block).iter() {
269
            for op in f.inst_operands(inst) {
270
                v(op.vreg());
271
            }
272
            if f.is_branch(inst) {
273
                for succ_idx in 0..f.block_succs(block).len() {
274
                    for &param in f.branch_blockparams(block, inst, succ_idx) {
275
                        v(param);
276
                    }
277
                }
278
            }
279
        }
280
        for &vreg in f.block_params(block) {
281
            v(vreg);
282
        }
283
    }
284
}
285
286
/// State that steps through program points as we scan over the instruction stream.
287
#[derive(Clone, Debug, PartialEq, Eq)]
288
enum CheckerState {
289
    Top,
290
    Allocations(FxHashMap<Allocation, CheckerValue>),
291
}
292
293
impl CheckerState {
294
22.6M
    fn get_value(&self, alloc: &Allocation) -> Option<&CheckerValue> {
295
22.6M
        match self {
296
4.59k
            CheckerState::Top => None,
297
22.6M
            CheckerState::Allocations(allocs) => allocs.get(alloc),
298
        }
299
22.6M
    }
300
301
369k
    fn get_values_mut(&mut self) -> impl Iterator<Item = &mut CheckerValue> {
302
369k
        match self {
303
0
            CheckerState::Top => panic!("Cannot get mutable values iterator on Top state"),
304
369k
            CheckerState::Allocations(allocs) => allocs.values_mut(),
305
        }
306
369k
    }
307
308
    fn get_mappings(&self) -> impl Iterator<Item = (&Allocation, &CheckerValue)> {
309
        match self {
310
            CheckerState::Top => panic!("Cannot get mappings iterator on Top state"),
311
            CheckerState::Allocations(allocs) => allocs.iter(),
312
        }
313
    }
314
315
30.8M
    fn get_mappings_mut(&mut self) -> impl Iterator<Item = (&Allocation, &mut CheckerValue)> {
316
30.8M
        match self {
317
0
            CheckerState::Top => panic!("Cannot get mutable mappings iterator on Top state"),
318
30.8M
            CheckerState::Allocations(allocs) => allocs.iter_mut(),
319
        }
320
30.8M
    }
321
322
    /// Transition from a "top" (undefined/unanalyzed) state to an empty set of allocations.
323
25.7M
    fn become_defined(&mut self) {
324
25.7M
        match self {
325
22.3k
            CheckerState::Top => *self = CheckerState::Allocations(FxHashMap::default()),
326
25.7M
            _ => {}
327
        }
328
25.7M
    }
329
330
38.3M
    fn set_value(&mut self, alloc: Allocation, value: CheckerValue) {
331
38.3M
        match self {
332
            CheckerState::Top => {
333
0
                panic!("Cannot set value on Top state");
334
            }
335
38.3M
            CheckerState::Allocations(allocs) => {
336
38.3M
                allocs.insert(alloc, value);
337
38.3M
            }
338
        }
339
38.3M
    }
340
341
    fn copy_vreg(&mut self, src: VReg, dst: VReg) {
342
        match self {
343
            CheckerState::Top => {
344
                // Nothing.
345
            }
346
            CheckerState::Allocations(allocs) => {
347
                for value in allocs.values_mut() {
348
                    value.copy_vreg(src, dst);
349
                }
350
            }
351
        }
352
    }
353
354
4.65M
    fn remove_value(&mut self, alloc: &Allocation) {
355
4.65M
        match self {
356
            CheckerState::Top => {
357
0
                panic!("Cannot remove value on Top state");
358
            }
359
4.65M
            CheckerState::Allocations(allocs) => {
360
4.65M
                allocs.remove(alloc);
361
4.65M
            }
362
        }
363
4.65M
    }
364
365
    fn initial() -> Self {
366
        CheckerState::Allocations(FxHashMap::default())
367
    }
368
}
369
370
impl Default for CheckerState {
371
484k
    fn default() -> CheckerState {
372
484k
        CheckerState::Top
373
484k
    }
374
}
375
376
impl core::fmt::Display for CheckerValue {
377
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
378
0
        match self {
379
            CheckerValue::Universe => {
380
0
                write!(f, "top")
381
            }
382
0
            CheckerValue::VRegs(vregs) => {
383
0
                write!(f, "{{ ")?;
384
0
                for vreg in vregs {
385
0
                    write!(f, "{} ", vreg)?;
386
                }
387
0
                write!(f, "}}")?;
388
0
                Ok(())
389
            }
390
        }
391
0
    }
392
}
393
394
/// Meet function for analysis value: meet individual values at
395
/// matching allocations, and intersect keys (remove key-value pairs
396
/// only on one side). Returns boolean flag indicating whether `into`
397
/// changed.
398
715k
fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
399
715k
    into: &mut FxHashMap<K, CheckerValue>,
400
715k
    from: &FxHashMap<K, CheckerValue>,
401
715k
) {
402
71.7M
    into.retain(|k, _| from.contains_key(k));
403
56.7M
    for (k, into_v) in into.iter_mut() {
404
56.7M
        let from_v = from.get(k).unwrap();
405
56.7M
        into_v.meet_with(from_v);
406
56.7M
    }
407
715k
}
408
409
impl CheckerState {
410
    /// Create a new checker state.
411
    fn new() -> CheckerState {
412
        Default::default()
413
    }
414
415
    /// Merge this checker state with another at a CFG join-point.
416
1.18M
    fn meet_with(&mut self, other: &CheckerState) {
417
1.18M
        match (self, other) {
418
467k
            (_, CheckerState::Top) => {
419
467k
                // Nothing.
420
467k
            }
421
0
            (this @ CheckerState::Top, _) => {
422
0
                *this = other.clone();
423
0
            }
424
            (
425
715k
                CheckerState::Allocations(my_allocations),
426
715k
                CheckerState::Allocations(other_allocations),
427
715k
            ) => {
428
715k
                merge_map(my_allocations, other_allocations);
429
715k
            }
430
        }
431
1.18M
    }
432
433
15.0M
    fn check_val<'a, F: Function>(
434
15.0M
        &self,
435
15.0M
        inst: Inst,
436
15.0M
        op: Operand,
437
15.0M
        alloc: Allocation,
438
15.0M
        val: &CheckerValue,
439
15.0M
        allocs: &[Allocation],
440
15.0M
        checker: &Checker<'a, F>,
441
15.0M
    ) -> Result<(), CheckerError> {
442
15.0M
        if alloc == Allocation::none() {
443
0
            return Err(CheckerError::MissingAllocation { inst, op });
444
15.0M
        }
445
446
15.0M
        if op.kind() == OperandKind::Use && op.as_fixed_nonallocatable().is_none() {
447
8.03M
            match val {
448
                CheckerValue::Universe => {
449
0
                    return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
450
                }
451
8.03M
                CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
452
0
                    return Err(CheckerError::IncorrectValuesInAllocation {
453
0
                        inst,
454
0
                        op,
455
0
                        alloc,
456
0
                        actual: vregs.clone(),
457
0
                    });
458
                }
459
8.03M
                _ => {}
460
            }
461
7.05M
        }
462
463
15.0M
        self.check_constraint(inst, op, alloc, allocs, checker)?;
464
465
15.0M
        Ok(())
466
15.0M
    }
467
468
    /// Check an instruction against this state. This must be called
469
    /// twice: once with `InstPosition::Before`, and once with
470
    /// `InstPosition::After` (after updating state with defs).
471
12.1M
    fn check<'a, F: Function>(
472
12.1M
        &self,
473
12.1M
        pos: InstPosition,
474
12.1M
        checkinst: &CheckerInst,
475
12.1M
        checker: &Checker<'a, F>,
476
12.1M
    ) -> Result<(), CheckerError> {
477
12.1M
        let default_val = Default::default();
478
12.1M
        match checkinst {
479
            &CheckerInst::Op {
480
8.78M
                inst,
481
8.78M
                ref operands,
482
8.78M
                ref allocs,
483
                ..
484
            } => {
485
                // Skip Use-checks at the After point if there are any
486
                // reused inputs: the Def which reuses the input
487
                // happens early.
488
8.78M
                let has_reused_input = operands
489
8.78M
                    .iter()
490
26.1M
                    .any(|op| matches!(op.constraint(), OperandConstraint::Reuse(_)));
491
8.78M
                if has_reused_input && pos == InstPosition::After {
492
555k
                    return Ok(());
493
8.23M
                }
494
495
                // For each operand, check (i) that the allocation
496
                // contains the expected vreg, and (ii) that it meets
497
                // the requirements of the OperandConstraint.
498
28.1M
                for (op, alloc) in operands.iter().zip(allocs.iter()) {
499
28.1M
                    let is_here = match (op.pos(), pos) {
500
10.7M
                        (OperandPos::Early, InstPosition::Before) => true,
501
4.28M
                        (OperandPos::Late, InstPosition::After) => true,
502
13.0M
                        _ => false,
503
                    };
504
28.1M
                    if !is_here {
505
13.0M
                        continue;
506
15.0M
                    }
507
508
15.0M
                    let val = self.get_value(alloc).unwrap_or(&default_val);
509
15.0M
                    trace!(
510
0
                        "checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
511
                        checkinst,
512
                        op,
513
                        alloc,
514
                        val
515
                    );
516
15.0M
                    self.check_val(inst, *op, *alloc, val, allocs, checker)?;
517
                }
518
            }
519
3.37M
            &CheckerInst::Move { into, from } => {
520
                // Ensure that the allocator never returns stack-to-stack moves.
521
4.07M
                let is_stack = |alloc: Allocation| {
522
4.07M
                    if let Some(reg) = alloc.as_reg() {
523
3.74M
                        checker.stack_pregs.contains(reg)
524
                    } else {
525
336k
                        alloc.is_stack()
526
                    }
527
4.07M
                };
528
3.37M
                if is_stack(into) && is_stack(from) {
529
0
                    return Err(CheckerError::StackToStackMove { into, from });
530
3.37M
                }
531
            }
532
0
            &CheckerInst::ParallelMove { .. } => {
533
0
                // This doesn't need verification; we just update
534
0
                // according to the move semantics in the step
535
0
                // function below.
536
0
            }
537
        }
538
11.6M
        Ok(())
539
12.1M
    }
540
541
    /// Update according to instruction.
542
25.7M
    fn update(&mut self, checkinst: &CheckerInst) {
543
25.7M
        self.become_defined();
544
545
25.7M
        match checkinst {
546
7.52M
            &CheckerInst::Move { into, from } => {
547
                // Value may not be present if this move is part of
548
                // the parallel move resolver's fallback sequence that
549
                // saves a victim register elsewhere. (In other words,
550
                // that sequence saves an undefined value and restores
551
                // it, so has no effect.) The checker needs to avoid
552
                // putting Universe lattice values into the map.
553
7.52M
                if let Some(val) = self.get_value(&from).cloned() {
554
7.52M
                    trace!(
555
0
                        "checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
556
                        checkinst,
557
                        from,
558
                        into,
559
                        val
560
                    );
561
7.52M
                    self.set_value(into, val);
562
5
                }
563
            }
564
369k
            &CheckerInst::ParallelMove { ref moves } => {
565
                // First, build map of actions for each vreg in an
566
                // alloc. If an alloc has a reg V_i before a parallel
567
                // move, then for each use of V_i as a source (V_i ->
568
                // V_j), we might add a new V_j wherever V_i appears;
569
                // and if V_i is used as a dest (at most once), then
570
                // it must be removed first from allocs' vreg sets.
571
369k
                let mut additions: FxHashMap<VReg, SmallVec<[VReg; 2]>> = FxHashMap::default();
572
369k
                let mut deletions: FxHashSet<VReg> = FxHashSet::default();
573
574
1.05M
                for &(dest, src) in moves {
575
683k
                    deletions.insert(dest);
576
683k
                    additions
577
683k
                        .entry(src)
578
683k
                        .or_insert_with(|| smallvec![])
579
683k
                        .push(dest);
580
                }
581
582
                // Now process each allocation's set of vreg labels,
583
                // first deleting those labels that were updated by
584
                // this parallel move, then adding back labels
585
                // redefined by the move.
586
23.4M
                for value in self.get_values_mut() {
587
23.4M
                    if let Some(vregs) = value.vregs_mut() {
588
23.4M
                        let mut insertions: SmallVec<[VReg; 2]> = smallvec![];
589
31.4M
                        for &vreg in vregs.iter() {
590
31.4M
                            if let Some(additions) = additions.get(&vreg) {
591
890k
                                insertions.extend(additions.iter().cloned());
592
30.5M
                            }
593
                        }
594
65.5M
                        for &d in &deletions {
595
42.0M
                            vregs.remove(&d);
596
42.0M
                        }
597
23.4M
                        vregs.extend(insertions);
598
0
                    }
599
                }
600
            }
601
            &CheckerInst::Op {
602
17.8M
                ref operands,
603
17.8M
                ref allocs,
604
17.8M
                ref clobbers,
605
                ..
606
            } => {
607
                // For each def, (i) update alloc to reflect defined
608
                // vreg (and only that vreg), and (ii) update all
609
                // other allocs in the checker state by removing this
610
                // vreg, if defined (other defs are now stale).
611
67.1M
                for (op, alloc) in operands.iter().zip(allocs.iter()) {
612
67.1M
                    if op.kind() != OperandKind::Def {
613
36.3M
                        continue;
614
30.8M
                    }
615
30.8M
                    self.remove_vreg(op.vreg());
616
30.8M
                    self.set_value(*alloc, CheckerValue::from_reg(op.vreg()));
617
                }
618
22.5M
                for clobber in clobbers {
619
4.65M
                    self.remove_value(&Allocation::reg(*clobber));
620
4.65M
                }
621
            }
622
        }
623
25.7M
    }
624
625
30.8M
    fn remove_vreg(&mut self, vreg: VReg) {
626
3.17G
        for (_, value) in self.get_mappings_mut() {
627
3.17G
            value.remove_vreg(vreg);
628
3.17G
        }
629
30.8M
    }
630
631
15.0M
    fn check_constraint<'a, F: Function>(
632
15.0M
        &self,
633
15.0M
        inst: Inst,
634
15.0M
        op: Operand,
635
15.0M
        alloc: Allocation,
636
15.0M
        allocs: &[Allocation],
637
15.0M
        checker: &Checker<'a, F>,
638
15.0M
    ) -> Result<(), CheckerError> {
639
15.0M
        match op.constraint() {
640
9.78M
            OperandConstraint::Any => {}
641
            OperandConstraint::Reg => {
642
4.74M
                if let Some(preg) = alloc.as_reg() {
643
                    // Reject pregs that represent a fixed stack slot.
644
4.74M
                    if !checker.machine_env.fixed_stack_slots.contains(&preg) {
645
4.74M
                        return Ok(());
646
0
                    }
647
0
                }
648
0
                return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
649
            }
650
            OperandConstraint::Stack => {
651
0
                if alloc.kind() != AllocationKind::Stack {
652
                    // Accept pregs that represent a fixed stack slot.
653
0
                    if let Some(preg) = alloc.as_reg() {
654
0
                        if checker.machine_env.fixed_stack_slots.contains(&preg) {
655
0
                            return Ok(());
656
0
                        }
657
0
                    }
658
0
                    return Err(CheckerError::AllocationIsNotStack { inst, op, alloc });
659
0
                }
660
            }
661
562k
            OperandConstraint::FixedReg(preg) => {
662
562k
                if alloc != Allocation::reg(preg) {
663
0
                    return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
664
562k
                }
665
            }
666
0
            OperandConstraint::Reuse(idx) => {
667
0
                if alloc.kind() != AllocationKind::Reg {
668
0
                    return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
669
0
                }
670
0
                if alloc != allocs[idx] {
671
0
                    return Err(CheckerError::AllocationIsNotReuse {
672
0
                        inst,
673
0
                        op,
674
0
                        alloc,
675
0
                        expected_alloc: allocs[idx],
676
0
                    });
677
0
                }
678
            }
679
0
            OperandConstraint::Limit(max) => {
680
0
                if let Some(preg) = alloc.as_reg() {
681
0
                    if preg.hw_enc() >= max {
682
0
                        return Err(CheckerError::AllocationOutsideLimit {
683
0
                            inst,
684
0
                            op,
685
0
                            alloc,
686
0
                            range: (0..max),
687
0
                        });
688
0
                    }
689
0
                }
690
            }
691
        }
692
10.3M
        Ok(())
693
15.0M
    }
694
}
695
696
/// An instruction representation in the checker's BB summary.
697
#[derive(Clone, Debug)]
698
pub(crate) enum CheckerInst {
699
    /// A move between allocations (these could be registers or
700
    /// spillslots).
701
    Move { into: Allocation, from: Allocation },
702
703
    /// A parallel move in the original program. Simultaneously moves
704
    /// from all source vregs to all corresponding dest vregs,
705
    /// permitting overlap in the src and dest sets and doing all
706
    /// reads before any writes.
707
    ParallelMove {
708
        /// Vector of (dest, src) moves.
709
        moves: Vec<(VReg, VReg)>,
710
    },
711
712
    /// A regular instruction with fixed use and def slots. Contains
713
    /// both the original operands (as given to the regalloc) and the
714
    /// allocation results.
715
    Op {
716
        inst: Inst,
717
        operands: Vec<Operand>,
718
        allocs: Vec<Allocation>,
719
        clobbers: Vec<PReg>,
720
    },
721
}
722
723
#[derive(Debug)]
724
pub struct Checker<'a, F: Function> {
725
    f: &'a F,
726
    bb_in: FxHashMap<Block, CheckerState>,
727
    bb_insts: FxHashMap<Block, Vec<CheckerInst>>,
728
    edge_insts: FxHashMap<(Block, Block), Vec<CheckerInst>>,
729
    machine_env: &'a MachineEnv,
730
    stack_pregs: PRegSet,
731
}
732
733
impl<'a, F: Function> Checker<'a, F> {
734
    /// Create a new checker for the given function, initializing CFG
735
    /// info immediately.  The client should call the `add_*()`
736
    /// methods to add abstract instructions to each BB before
737
    /// invoking `run()` to check for errors.
738
10.8k
    pub fn new(f: &'a F, machine_env: &'a MachineEnv) -> Checker<'a, F> {
739
10.8k
        let mut bb_in = FxHashMap::default();
740
10.8k
        let mut bb_insts = FxHashMap::default();
741
10.8k
        let mut edge_insts = FxHashMap::default();
742
743
473k
        for block in 0..f.num_blocks() {
744
473k
            let block = Block::new(block);
745
473k
            bb_in.insert(block, Default::default());
746
473k
            bb_insts.insert(block, vec![]);
747
582k
            for &succ in f.block_succs(block) {
748
582k
                edge_insts.insert((block, succ), vec![]);
749
582k
            }
750
        }
751
752
10.8k
        bb_in.insert(f.entry_block(), CheckerState::default());
753
754
10.8k
        let mut stack_pregs = PRegSet::empty();
755
1.01M
        for &preg in &machine_env.fixed_stack_slots {
756
1.00M
            stack_pregs.add(preg);
757
1.00M
        }
758
759
10.8k
        Checker {
760
10.8k
            f,
761
10.8k
            bb_in,
762
10.8k
            bb_insts,
763
10.8k
            edge_insts,
764
10.8k
            machine_env,
765
10.8k
            stack_pregs,
766
10.8k
        }
767
10.8k
    }
768
769
    /// Build the list of checker instructions based on the given func
770
    /// and allocation results.
771
10.8k
    pub fn prepare(&mut self, out: &Output) {
772
10.8k
        trace!("checker: out = {:?}", out);
773
10.8k
        let mut last_inst = None;
774
473k
        for block in 0..self.f.num_blocks() {
775
473k
            let block = Block::new(block);
776
6.08M
            for inst_or_edit in out.block_insts_and_edits(self.f, block) {
777
6.08M
                match inst_or_edit {
778
4.39M
                    InstOrEdit::Inst(inst) => {
779
4.39M
                        debug_assert!(last_inst.is_none() || inst > last_inst.unwrap());
780
4.39M
                        last_inst = Some(inst);
781
4.39M
                        self.handle_inst(block, inst, out);
782
                    }
783
1.68M
                    InstOrEdit::Edit(edit) => self.handle_edit(block, edit),
784
                }
785
            }
786
        }
787
10.8k
    }
788
789
    /// For each original instruction, create an `Op`.
790
4.39M
    fn handle_inst(&mut self, block: Block, inst: Inst, out: &Output) {
791
        // Process uses, defs, and clobbers.
792
4.39M
        let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
793
4.39M
        let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
794
4.39M
        let clobbers: Vec<_> = self.f.inst_clobbers(inst).into_iter().collect();
795
4.39M
        let checkinst = CheckerInst::Op {
796
4.39M
            inst,
797
4.39M
            operands,
798
4.39M
            allocs,
799
4.39M
            clobbers,
800
4.39M
        };
801
4.39M
        trace!("checker: adding inst {:?}", checkinst);
802
4.39M
        self.bb_insts.get_mut(&block).unwrap().push(checkinst);
803
804
        // If this is a branch, emit a ParallelMove on each outgoing
805
        // edge as necessary to handle blockparams.
806
4.39M
        if self.f.is_branch(inst) {
807
582k
            for (i, &succ) in self.f.block_succs(block).iter().enumerate() {
808
582k
                let args = self.f.branch_blockparams(block, inst, i);
809
582k
                let params = self.f.block_params(succ);
810
582k
                assert_eq!(
811
582k
                    args.len(),
812
582k
                    params.len(),
813
0
                    "block{} has succ block{}; gave {} args for {} params",
814
0
                    block.index(),
815
0
                    succ.index(),
816
0
                    args.len(),
817
0
                    params.len()
818
                );
819
582k
                if args.len() > 0 {
820
121k
                    let moves = params.iter().cloned().zip(args.iter().cloned()).collect();
821
121k
                    self.edge_insts
822
121k
                        .get_mut(&(block, succ))
823
121k
                        .unwrap()
824
121k
                        .push(CheckerInst::ParallelMove { moves });
825
460k
                }
826
            }
827
3.93M
        }
828
4.39M
    }
829
830
1.68M
    fn handle_edit(&mut self, block: Block, edit: &Edit) {
831
1.68M
        trace!("checker: adding edit {:?}", edit);
832
1.68M
        match edit {
833
1.68M
            &Edit::Move { from, to } => {
834
1.68M
                self.bb_insts
835
1.68M
                    .get_mut(&block)
836
1.68M
                    .unwrap()
837
1.68M
                    .push(CheckerInst::Move { into: to, from });
838
1.68M
            }
839
        }
840
1.68M
    }
841
842
    /// Perform the dataflow analysis to compute checker state at each BB entry.
843
10.8k
    fn analyze(&mut self) {
844
10.8k
        let mut queue = Vec::new();
845
10.8k
        let mut queue_set = FxHashSet::default();
846
847
        // Put every block in the queue to start with, to ensure
848
        // everything is visited even if the initial state remains
849
        // `Top` after preds update it.
850
        //
851
        // We add blocks in reverse order so that when we process
852
        // back-to-front below, we do our initial pass in input block
853
        // order, which is (usually) RPO order or at least a
854
        // reasonable visit order.
855
473k
        for block in (0..self.f.num_blocks()).rev() {
856
473k
            let block = Block::new(block);
857
473k
            queue.push(block);
858
473k
            queue_set.insert(block);
859
473k
        }
860
861
951k
        while let Some(block) = queue.pop() {
862
940k
            queue_set.remove(&block);
863
940k
            let mut state = self.bb_in.get(&block).cloned().unwrap();
864
940k
            trace!("analyze: block {} has state {:?}", block.index(), state);
865
13.2M
            for inst in self.bb_insts.get(&block).unwrap() {
866
13.2M
                state.update(inst);
867
13.2M
                trace!("analyze: inst {:?} -> state {:?}", inst, state);
868
            }
869
870
1.18M
            for &succ in self.f.block_succs(block) {
871
1.18M
                let mut new_state = state.clone();
872
1.18M
                for edge_inst in self.edge_insts.get(&(block, succ)).unwrap() {
873
248k
                    new_state.update(edge_inst);
874
248k
                    trace!(
875
0
                        "analyze: succ {:?}: inst {:?} -> state {:?}",
876
                        succ,
877
                        edge_inst,
878
                        new_state
879
                    );
880
                }
881
882
1.18M
                let cur_succ_in = self.bb_in.get(&succ).unwrap();
883
1.18M
                trace!(
884
0
                    "meeting state {:?} for block {} with state {:?} for block {}",
885
                    new_state,
886
0
                    block.index(),
887
                    cur_succ_in,
888
0
                    succ.index()
889
                );
890
1.18M
                new_state.meet_with(cur_succ_in);
891
1.18M
                let changed = &new_state != cur_succ_in;
892
1.18M
                trace!(" -> {:?}, changed {}", new_state, changed);
893
894
1.18M
                if changed {
895
954k
                    trace!(
896
0
                        "analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
897
0
                        succ.index(),
898
                        cur_succ_in,
899
                        new_state
900
                    );
901
954k
                    self.bb_in.insert(succ, new_state);
902
954k
                    if queue_set.insert(succ) {
903
466k
                        queue.push(succ);
904
487k
                    }
905
228k
                }
906
            }
907
        }
908
10.8k
    }
909
910
    /// Using BB-start state computed by `analyze()`, step the checker state
911
    /// through each BB and check each instruction's register allocations
912
    /// for errors.
913
10.8k
    fn find_errors(&self) -> Result<(), CheckerErrors> {
914
10.8k
        let mut errors = vec![];
915
484k
        for (block, input) in &self.bb_in {
916
473k
            let mut state = input.clone();
917
6.08M
            for inst in self.bb_insts.get(block).unwrap() {
918
6.08M
                if let Err(e) = state.check(InstPosition::Before, inst, self) {
919
0
                    trace!("Checker error: {:?}", e);
920
0
                    errors.push(e);
921
6.08M
                }
922
6.08M
                state.update(inst);
923
6.08M
                if let Err(e) = state.check(InstPosition::After, inst, self) {
924
0
                    trace!("Checker error: {:?}", e);
925
0
                    errors.push(e);
926
6.08M
                }
927
            }
928
        }
929
930
10.8k
        if errors.is_empty() {
931
10.8k
            Ok(())
932
        } else {
933
0
            Err(CheckerErrors { errors })
934
        }
935
10.8k
    }
936
937
    /// Find any errors, returning `Err(CheckerErrors)` with all errors found
938
    /// or `Ok(())` otherwise.
939
10.8k
    pub fn run(mut self) -> Result<(), CheckerErrors> {
940
10.8k
        self.analyze();
941
10.8k
        let result = self.find_errors();
942
943
10.8k
        trace!("=== CHECKER RESULT ===");
944
6.67M
        fn print_state(state: &CheckerState) {
945
6.67M
            if !trace_enabled!() {
946
6.67M
                return;
947
0
            }
948
0
            if let CheckerState::Allocations(allocs) = state {
949
0
                let mut s = vec![];
950
0
                for (alloc, state) in allocs {
951
0
                    s.push(format!("{} := {}", alloc, state));
952
0
                }
953
0
                trace!("    {{ {} }}", s.join(", "))
954
0
            }
955
6.67M
        }
956
473k
        for bb in 0..self.f.num_blocks() {
957
473k
            let bb = Block::new(bb);
958
473k
            trace!("block{}:", bb.index());
959
473k
            let insts = self.bb_insts.get(&bb).unwrap();
960
473k
            let mut state = self.bb_in.get(&bb).unwrap().clone();
961
473k
            print_state(&state);
962
6.55M
            for inst in insts {
963
6.08M
                match inst {
964
                    &CheckerInst::Op {
965
4.39M
                        inst,
966
4.39M
                        ref operands,
967
4.39M
                        ref allocs,
968
4.39M
                        ref clobbers,
969
                    } => {
970
4.39M
                        trace!(
971
0
                            "  inst{}: {:?} ({:?}) clobbers:{:?}",
972
0
                            inst.index(),
973
                            operands,
974
                            allocs,
975
                            clobbers
976
                        );
977
                    }
978
1.68M
                    &CheckerInst::Move { from, into } => {
979
1.68M
                        trace!("    {} -> {}", from, into);
980
                    }
981
                    &CheckerInst::ParallelMove { .. } => {
982
0
                        panic!("unexpected parallel_move in body (non-edge)")
983
                    }
984
                }
985
6.08M
                state.update(inst);
986
6.08M
                print_state(&state);
987
            }
988
989
582k
            for &succ in self.f.block_succs(bb) {
990
582k
                trace!("  succ {:?}:", succ);
991
582k
                let mut state = state.clone();
992
582k
                for edge_inst in self.edge_insts.get(&(bb, succ)).unwrap() {
993
121k
                    match edge_inst {
994
121k
                        &CheckerInst::ParallelMove { ref moves } => {
995
121k
                            let moves = moves
996
121k
                                .iter()
997
223k
                                .map(|(dest, src)| format!("{} -> {}", src, dest))
998
121k
                                .collect::<Vec<_>>();
999
121k
                            trace!("    parallel_move {}", moves.join(", "));
1000
                        }
1001
0
                        _ => panic!("unexpected edge_inst: not a parallel move"),
1002
                    }
1003
121k
                    state.update(edge_inst);
1004
121k
                    print_state(&state);
1005
                }
1006
            }
1007
        }
1008
1009
10.8k
        result
1010
10.8k
    }
1011
}