regalloc2/
checker.rs

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
98use crate::{
99    Allocation, AllocationKind, Block, Edit, Function, Inst, InstOrEdit, InstPosition, MachineEnv,
100    Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg, PRegSet, VReg,
101};
102use fxhash::{FxHashMap, FxHashSet};
103use smallvec::{smallvec, SmallVec};
104use std::default::Default;
105use std::hash::Hash;
106use std::result::Result;
107
108/// A set of errors detected by the regalloc checker.
109#[derive(Clone, Debug)]
110pub struct CheckerErrors {
111    errors: Vec<CheckerError>,
112}
113
114/// A single error detected by the regalloc checker.
115#[derive(Clone, Debug)]
116pub 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)]
184enum 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
192impl 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    fn vregs_mut(&mut self) -> Option<&mut FxHashSet<VReg>> {
201        match self {
202            CheckerValue::Universe => None,
203            CheckerValue::VRegs(vregs) => Some(vregs),
204        }
205    }
206}
207
208impl Default for CheckerValue {
209    fn default() -> CheckerValue {
210        CheckerValue::Universe
211    }
212}
213
214impl CheckerValue {
215    /// Meet function of the abstract-interpretation value
216    /// lattice. Returns a boolean value indicating whether `self` was
217    /// changed.
218    fn meet_with(&mut self, other: &CheckerValue) {
219        match (self, other) {
220            (_, CheckerValue::Universe) => {
221                // Nothing.
222            }
223            (this @ CheckerValue::Universe, _) => {
224                *this = other.clone();
225            }
226            (CheckerValue::VRegs(my_vregs), CheckerValue::VRegs(other_vregs)) => {
227                my_vregs.retain(|vreg| other_vregs.contains(vreg));
228            }
229        }
230    }
231
232    fn from_reg(reg: VReg) -> CheckerValue {
233        CheckerValue::VRegs(std::iter::once(reg).collect())
234    }
235
236    fn remove_vreg(&mut self, reg: VReg) {
237        match self {
238            CheckerValue::Universe => {
239                panic!("Cannot remove VReg from Universe set (we do not have the full list of vregs available");
240            }
241            CheckerValue::VRegs(vregs) => {
242                vregs.remove(&reg);
243            }
244        }
245    }
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
265fn 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 let Some((src, dst)) = f.is_move(inst) {
273                v(src.vreg());
274                v(dst.vreg());
275            }
276            if f.is_branch(inst) {
277                for succ_idx in 0..f.block_succs(block).len() {
278                    for &param in f.branch_blockparams(block, inst, succ_idx) {
279                        v(param);
280                    }
281                }
282            }
283        }
284        for &vreg in f.block_params(block) {
285            v(vreg);
286        }
287    }
288}
289
290/// State that steps through program points as we scan over the instruction stream.
291#[derive(Clone, Debug, PartialEq, Eq)]
292enum CheckerState {
293    Top,
294    Allocations(FxHashMap<Allocation, CheckerValue>),
295}
296
297impl CheckerState {
298    fn get_value(&self, alloc: &Allocation) -> Option<&CheckerValue> {
299        match self {
300            CheckerState::Top => None,
301            CheckerState::Allocations(allocs) => allocs.get(alloc),
302        }
303    }
304
305    fn get_values_mut(&mut self) -> impl Iterator<Item = &mut CheckerValue> {
306        match self {
307            CheckerState::Top => panic!("Cannot get mutable values iterator on Top state"),
308            CheckerState::Allocations(allocs) => allocs.values_mut(),
309        }
310    }
311
312    fn get_mappings(&self) -> impl Iterator<Item = (&Allocation, &CheckerValue)> {
313        match self {
314            CheckerState::Top => panic!("Cannot get mappings iterator on Top state"),
315            CheckerState::Allocations(allocs) => allocs.iter(),
316        }
317    }
318
319    fn get_mappings_mut(&mut self) -> impl Iterator<Item = (&Allocation, &mut CheckerValue)> {
320        match self {
321            CheckerState::Top => panic!("Cannot get mutable mappings iterator on Top state"),
322            CheckerState::Allocations(allocs) => allocs.iter_mut(),
323        }
324    }
325
326    /// Transition from a "top" (undefined/unanalyzed) state to an empty set of allocations.
327    fn become_defined(&mut self) {
328        match self {
329            CheckerState::Top => *self = CheckerState::Allocations(FxHashMap::default()),
330            _ => {}
331        }
332    }
333
334    fn set_value(&mut self, alloc: Allocation, value: CheckerValue) {
335        match self {
336            CheckerState::Top => {
337                panic!("Cannot set value on Top state");
338            }
339            CheckerState::Allocations(allocs) => {
340                allocs.insert(alloc, value);
341            }
342        }
343    }
344
345    fn copy_vreg(&mut self, src: VReg, dst: VReg) {
346        match self {
347            CheckerState::Top => {
348                // Nothing.
349            }
350            CheckerState::Allocations(allocs) => {
351                for value in allocs.values_mut() {
352                    value.copy_vreg(src, dst);
353                }
354            }
355        }
356    }
357
358    fn remove_value(&mut self, alloc: &Allocation) {
359        match self {
360            CheckerState::Top => {
361                panic!("Cannot remove value on Top state");
362            }
363            CheckerState::Allocations(allocs) => {
364                allocs.remove(alloc);
365            }
366        }
367    }
368
369    fn initial() -> Self {
370        CheckerState::Allocations(FxHashMap::default())
371    }
372}
373
374impl Default for CheckerState {
375    fn default() -> CheckerState {
376        CheckerState::Top
377    }
378}
379
380impl std::fmt::Display for CheckerValue {
381    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
382        match self {
383            CheckerValue::Universe => {
384                write!(f, "top")
385            }
386            CheckerValue::VRegs(vregs) => {
387                write!(f, "{{ ")?;
388                for vreg in vregs {
389                    write!(f, "{} ", vreg)?;
390                }
391                write!(f, "}}")?;
392                Ok(())
393            }
394        }
395    }
396}
397
398/// Meet function for analysis value: meet individual values at
399/// matching allocations, and intersect keys (remove key-value pairs
400/// only on one side). Returns boolean flag indicating whether `into`
401/// changed.
402fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
403    into: &mut FxHashMap<K, CheckerValue>,
404    from: &FxHashMap<K, CheckerValue>,
405) {
406    into.retain(|k, _| from.contains_key(k));
407    for (k, into_v) in into.iter_mut() {
408        let from_v = from.get(k).unwrap();
409        into_v.meet_with(from_v);
410    }
411}
412
413impl CheckerState {
414    /// Create a new checker state.
415    fn new() -> CheckerState {
416        Default::default()
417    }
418
419    /// Merge this checker state with another at a CFG join-point.
420    fn meet_with(&mut self, other: &CheckerState) {
421        match (self, other) {
422            (_, CheckerState::Top) => {
423                // Nothing.
424            }
425            (this @ CheckerState::Top, _) => {
426                *this = other.clone();
427            }
428            (
429                CheckerState::Allocations(my_allocations),
430                CheckerState::Allocations(other_allocations),
431            ) => {
432                merge_map(my_allocations, other_allocations);
433            }
434        }
435    }
436
437    fn check_val<'a, F: Function>(
438        &self,
439        inst: Inst,
440        op: Operand,
441        alloc: Allocation,
442        val: &CheckerValue,
443        allocs: &[Allocation],
444        checker: &Checker<'a, F>,
445    ) -> Result<(), CheckerError> {
446        if alloc == Allocation::none() {
447            return Err(CheckerError::MissingAllocation { inst, op });
448        }
449
450        if op.as_fixed_nonallocatable().is_none() {
451            match val {
452                CheckerValue::Universe => {
453                    return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
454                }
455                CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
456                    return Err(CheckerError::IncorrectValuesInAllocation {
457                        inst,
458                        op,
459                        alloc,
460                        actual: vregs.clone(),
461                    });
462                }
463                _ => {}
464            }
465        }
466
467        self.check_constraint(inst, op, alloc, allocs, checker)?;
468
469        Ok(())
470    }
471
472    /// Check an instruction against this state. This must be called
473    /// twice: once with `InstPosition::Before`, and once with
474    /// `InstPosition::After` (after updating state with defs).
475    fn check<'a, F: Function>(
476        &self,
477        pos: InstPosition,
478        checkinst: &CheckerInst,
479        checker: &Checker<'a, F>,
480    ) -> Result<(), CheckerError> {
481        let default_val = Default::default();
482        match checkinst {
483            &CheckerInst::Op {
484                inst,
485                ref operands,
486                ref allocs,
487                ..
488            } => {
489                // Skip Use-checks at the After point if there are any
490                // reused inputs: the Def which reuses the input
491                // happens early.
492                let has_reused_input = operands
493                    .iter()
494                    .any(|op| matches!(op.constraint(), OperandConstraint::Reuse(_)));
495                if has_reused_input && pos == InstPosition::After {
496                    return Ok(());
497                }
498
499                // For each operand, check (i) that the allocation
500                // contains the expected vreg, and (ii) that it meets
501                // the requirements of the OperandConstraint.
502                for (op, alloc) in operands.iter().zip(allocs.iter()) {
503                    let is_here = match (op.pos(), pos) {
504                        (OperandPos::Early, InstPosition::Before) => true,
505                        (OperandPos::Late, InstPosition::After) => true,
506                        _ => false,
507                    };
508                    if !is_here {
509                        continue;
510                    }
511                    if op.kind() == OperandKind::Def {
512                        continue;
513                    }
514
515                    let val = self.get_value(alloc).unwrap_or(&default_val);
516                    trace!(
517                        "checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
518                        checkinst,
519                        op,
520                        alloc,
521                        val
522                    );
523                    self.check_val(inst, *op, *alloc, val, allocs, checker)?;
524                }
525            }
526            &CheckerInst::Safepoint { inst, ref allocs } => {
527                for &alloc in allocs {
528                    let val = self.get_value(&alloc).unwrap_or(&default_val);
529                    trace!(
530                        "checker: checkinst {:?}: safepoint slot {}, checker value {:?}",
531                        checkinst,
532                        alloc,
533                        val
534                    );
535
536                    let reffy = val
537                        .vregs()
538                        .expect("checker value should not be Universe set")
539                        .iter()
540                        .any(|vreg| checker.reftyped_vregs.contains(vreg));
541                    if !reffy {
542                        return Err(CheckerError::NonRefValuesInStackmap {
543                            inst,
544                            alloc,
545                            vregs: val.vregs().unwrap().clone(),
546                        });
547                    }
548                }
549            }
550            &CheckerInst::Move { into, from } => {
551                // Ensure that the allocator never returns stack-to-stack moves.
552                let is_stack = |alloc: Allocation| {
553                    if let Some(reg) = alloc.as_reg() {
554                        checker.stack_pregs.contains(reg)
555                    } else {
556                        alloc.is_stack()
557                    }
558                };
559                if is_stack(into) && is_stack(from) {
560                    return Err(CheckerError::StackToStackMove { into, from });
561                }
562            }
563            &CheckerInst::ParallelMove { .. } => {
564                // This doesn't need verification; we just update
565                // according to the move semantics in the step
566                // function below.
567            }
568            &CheckerInst::ProgramMove { inst, src, dst: _ } => {
569                // Validate that the fixed-reg constraint, if any, on
570                // `src` is satisfied.
571                if let OperandConstraint::FixedReg(preg) = src.constraint() {
572                    let alloc = Allocation::reg(preg);
573                    let val = self.get_value(&alloc).unwrap_or(&default_val);
574                    trace!(
575                        "checker: checkinst {:?}: cheker value in {:?} is {:?}",
576                        checkinst,
577                        alloc,
578                        val
579                    );
580                    self.check_val(inst, src, alloc, val, &[alloc], checker)?;
581                }
582                // Note that we don't do anything with `dst`
583                // here. That is implicitly checked whenever `dst` is
584                // used; the `update()` step below adds the symbolic
585                // vreg for `dst` into wherever `src` may be stored.
586            }
587        }
588        Ok(())
589    }
590
591    /// Update according to instruction.
592    fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) {
593        self.become_defined();
594
595        match checkinst {
596            &CheckerInst::Move { into, from } => {
597                // Value may not be present if this move is part of
598                // the parallel move resolver's fallback sequence that
599                // saves a victim register elsewhere. (In other words,
600                // that sequence saves an undefined value and restores
601                // it, so has no effect.) The checker needs to avoid
602                // putting Universe lattice values into the map.
603                if let Some(val) = self.get_value(&from).cloned() {
604                    trace!(
605                        "checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
606                        checkinst,
607                        from,
608                        into,
609                        val
610                    );
611                    self.set_value(into, val);
612                }
613            }
614            &CheckerInst::ParallelMove { ref moves } => {
615                // First, build map of actions for each vreg in an
616                // alloc. If an alloc has a reg V_i before a parallel
617                // move, then for each use of V_i as a source (V_i ->
618                // V_j), we might add a new V_j wherever V_i appears;
619                // and if V_i is used as a dest (at most once), then
620                // it must be removed first from allocs' vreg sets.
621                let mut additions: FxHashMap<VReg, SmallVec<[VReg; 2]>> = FxHashMap::default();
622                let mut deletions: FxHashSet<VReg> = FxHashSet::default();
623
624                for &(dest, src) in moves {
625                    deletions.insert(dest);
626                    additions
627                        .entry(src)
628                        .or_insert_with(|| smallvec![])
629                        .push(dest);
630                }
631
632                // Now process each allocation's set of vreg labels,
633                // first deleting those labels that were updated by
634                // this parallel move, then adding back labels
635                // redefined by the move.
636                for value in self.get_values_mut() {
637                    if let Some(vregs) = value.vregs_mut() {
638                        let mut insertions: SmallVec<[VReg; 2]> = smallvec![];
639                        for &vreg in vregs.iter() {
640                            if let Some(additions) = additions.get(&vreg) {
641                                insertions.extend(additions.iter().cloned());
642                            }
643                        }
644                        for &d in &deletions {
645                            vregs.remove(&d);
646                        }
647                        vregs.extend(insertions);
648                    }
649                }
650            }
651            &CheckerInst::Op {
652                ref operands,
653                ref allocs,
654                ref clobbers,
655                ..
656            } => {
657                // For each def, (i) update alloc to reflect defined
658                // vreg (and only that vreg), and (ii) update all
659                // other allocs in the checker state by removing this
660                // vreg, if defined (other defs are now stale).
661                for (op, alloc) in operands.iter().zip(allocs.iter()) {
662                    if op.kind() != OperandKind::Def {
663                        continue;
664                    }
665                    self.remove_vreg(op.vreg());
666                    self.set_value(*alloc, CheckerValue::from_reg(op.vreg()));
667                }
668                for clobber in clobbers {
669                    self.remove_value(&Allocation::reg(*clobber));
670                }
671            }
672            &CheckerInst::Safepoint { ref allocs, .. } => {
673                for (alloc, value) in self.get_mappings_mut() {
674                    if alloc.is_reg() {
675                        continue;
676                    }
677                    if !allocs.contains(&alloc) {
678                        // Remove all reftyped vregs as labels.
679                        let new_vregs = value
680                            .vregs()
681                            .unwrap()
682                            .difference(&checker.reftyped_vregs)
683                            .cloned()
684                            .collect();
685                        *value = CheckerValue::VRegs(new_vregs);
686                    }
687                }
688            }
689            &CheckerInst::ProgramMove { inst: _, src, dst } => {
690                // Remove all earlier instances of `dst`: this vreg is
691                // now stale (it is being overwritten).
692                self.remove_vreg(dst.vreg());
693                // Define `dst` wherever `src` occurs.
694                for (_, value) in self.get_mappings_mut() {
695                    value.copy_vreg(src.vreg(), dst.vreg());
696                }
697            }
698        }
699    }
700
701    fn remove_vreg(&mut self, vreg: VReg) {
702        for (_, value) in self.get_mappings_mut() {
703            value.remove_vreg(vreg);
704        }
705    }
706
707    fn check_constraint<'a, F: Function>(
708        &self,
709        inst: Inst,
710        op: Operand,
711        alloc: Allocation,
712        allocs: &[Allocation],
713        checker: &Checker<'a, F>,
714    ) -> Result<(), CheckerError> {
715        match op.constraint() {
716            OperandConstraint::Any => {}
717            OperandConstraint::Reg => {
718                if let Some(preg) = alloc.as_reg() {
719                    // Reject pregs that represent a fixed stack slot.
720                    if !checker.machine_env.fixed_stack_slots.contains(&preg) {
721                        return Ok(());
722                    }
723                }
724                return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
725            }
726            OperandConstraint::Stack => {
727                if alloc.kind() != AllocationKind::Stack {
728                    // Accept pregs that represent a fixed stack slot.
729                    if let Some(preg) = alloc.as_reg() {
730                        if checker.machine_env.fixed_stack_slots.contains(&preg) {
731                            return Ok(());
732                        }
733                    }
734                    return Err(CheckerError::AllocationIsNotStack { inst, op, alloc });
735                }
736            }
737            OperandConstraint::FixedReg(preg) => {
738                if alloc != Allocation::reg(preg) {
739                    return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
740                }
741            }
742            OperandConstraint::Reuse(idx) => {
743                if alloc.kind() != AllocationKind::Reg {
744                    return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
745                }
746                if alloc != allocs[idx] {
747                    return Err(CheckerError::AllocationIsNotReuse {
748                        inst,
749                        op,
750                        alloc,
751                        expected_alloc: allocs[idx],
752                    });
753                }
754            }
755        }
756        Ok(())
757    }
758}
759
760/// An instruction representation in the checker's BB summary.
761#[derive(Clone, Debug)]
762pub(crate) enum CheckerInst {
763    /// A move between allocations (these could be registers or
764    /// spillslots).
765    Move { into: Allocation, from: Allocation },
766
767    /// A parallel move in the original program. Simultaneously moves
768    /// from all source vregs to all corresponding dest vregs,
769    /// permitting overlap in the src and dest sets and doing all
770    /// reads before any writes.
771    ParallelMove {
772        /// Vector of (dest, src) moves.
773        moves: Vec<(VReg, VReg)>,
774    },
775
776    /// A regular instruction with fixed use and def slots. Contains
777    /// both the original operands (as given to the regalloc) and the
778    /// allocation results.
779    Op {
780        inst: Inst,
781        operands: Vec<Operand>,
782        allocs: Vec<Allocation>,
783        clobbers: Vec<PReg>,
784    },
785
786    /// A safepoint, with the given Allocations specified as containing
787    /// reftyped values. All other reftyped values become invalid.
788    Safepoint { inst: Inst, allocs: Vec<Allocation> },
789
790    /// An op with one source operand, and one dest operand, that
791    /// copies any symbolic values from the source to the dest, in
792    /// addition to adding the symbolic value of the dest vreg to the
793    /// set. This "program move" is distinguished from the above
794    /// `Move` by being semantically relevant in the original
795    /// (pre-regalloc) program.
796    ///
797    /// We transform checker values as follows: for any vreg-set that
798    /// contains `dst`'s vreg, we first delete that vreg (because it
799    /// is being redefined). Then, for any vreg-set with `src`
800    /// present, we add `dst`.
801    ProgramMove {
802        inst: Inst,
803        src: Operand,
804        dst: Operand,
805    },
806}
807
808#[derive(Debug)]
809pub struct Checker<'a, F: Function> {
810    f: &'a F,
811    bb_in: FxHashMap<Block, CheckerState>,
812    bb_insts: FxHashMap<Block, Vec<CheckerInst>>,
813    edge_insts: FxHashMap<(Block, Block), Vec<CheckerInst>>,
814    reftyped_vregs: FxHashSet<VReg>,
815    machine_env: &'a MachineEnv,
816    stack_pregs: PRegSet,
817}
818
819impl<'a, F: Function> Checker<'a, F> {
820    /// Create a new checker for the given function, initializing CFG
821    /// info immediately.  The client should call the `add_*()`
822    /// methods to add abstract instructions to each BB before
823    /// invoking `run()` to check for errors.
824    pub fn new(f: &'a F, machine_env: &'a MachineEnv) -> Checker<'a, F> {
825        let mut bb_in = FxHashMap::default();
826        let mut bb_insts = FxHashMap::default();
827        let mut edge_insts = FxHashMap::default();
828        let mut reftyped_vregs = FxHashSet::default();
829
830        for block in 0..f.num_blocks() {
831            let block = Block::new(block);
832            bb_in.insert(block, Default::default());
833            bb_insts.insert(block, vec![]);
834            for &succ in f.block_succs(block) {
835                edge_insts.insert((block, succ), vec![]);
836            }
837        }
838
839        for &vreg in f.reftype_vregs() {
840            reftyped_vregs.insert(vreg);
841        }
842
843        bb_in.insert(f.entry_block(), CheckerState::default());
844
845        let mut stack_pregs = PRegSet::empty();
846        for &preg in &machine_env.fixed_stack_slots {
847            stack_pregs.add(preg);
848        }
849
850        Checker {
851            f,
852            bb_in,
853            bb_insts,
854            edge_insts,
855            reftyped_vregs,
856            machine_env,
857            stack_pregs,
858        }
859    }
860
861    /// Build the list of checker instructions based on the given func
862    /// and allocation results.
863    pub fn prepare(&mut self, out: &Output) {
864        trace!("checker: out = {:?}", out);
865        // Preprocess safepoint stack-maps into per-inst vecs.
866        let mut safepoint_slots: FxHashMap<Inst, Vec<Allocation>> = FxHashMap::default();
867        for &(progpoint, slot) in &out.safepoint_slots {
868            safepoint_slots
869                .entry(progpoint.inst())
870                .or_insert_with(|| vec![])
871                .push(slot);
872        }
873
874        let mut last_inst = None;
875        for block in 0..self.f.num_blocks() {
876            let block = Block::new(block);
877            for inst_or_edit in out.block_insts_and_edits(self.f, block) {
878                match inst_or_edit {
879                    InstOrEdit::Inst(inst) => {
880                        debug_assert!(last_inst.is_none() || inst > last_inst.unwrap());
881                        last_inst = Some(inst);
882                        self.handle_inst(block, inst, &mut safepoint_slots, out);
883                    }
884                    InstOrEdit::Edit(edit) => self.handle_edit(block, edit),
885                }
886            }
887        }
888    }
889
890    /// For each original instruction, create an `Op`.
891    fn handle_inst(
892        &mut self,
893        block: Block,
894        inst: Inst,
895        safepoint_slots: &mut FxHashMap<Inst, Vec<Allocation>>,
896        out: &Output,
897    ) {
898        // If this is a safepoint, then check the spillslots at this point.
899        if self.f.requires_refs_on_stack(inst) {
900            let allocs = safepoint_slots.remove(&inst).unwrap_or_else(|| vec![]);
901
902            let checkinst = CheckerInst::Safepoint { inst, allocs };
903            self.bb_insts.get_mut(&block).unwrap().push(checkinst);
904        }
905
906        // If this is a move, handle specially. Note that the
907        // regalloc2-inserted moves are not semantically present in
908        // the original program and so do not modify the sets of
909        // symbolic values at all, but rather just move them around;
910        // but "program moves" *are* present, and have the following
911        // semantics: they define the destination vreg, but also
912        // retain any symbolic values in the source.
913        //
914        // regalloc2 reifies all moves into edits in its unified
915        // move/edit framework, so we don't get allocs for these moves
916        // in the post-regalloc output, and the embedder is not
917        // supposed to emit the moves. But we *do* want to check the
918        // semantic implications, namely definition of new vregs. So
919        // we emit `ProgramMove` ops that do just this.
920        if let Some((src, dst)) = self.f.is_move(inst) {
921            let src_op = Operand::any_use(src.vreg());
922            let dst_op = Operand::any_def(dst.vreg());
923            let checkinst = CheckerInst::ProgramMove {
924                inst,
925                src: src_op,
926                dst: dst_op,
927            };
928            trace!("checker: adding inst {:?}", checkinst);
929            self.bb_insts.get_mut(&block).unwrap().push(checkinst);
930        }
931        // Skip normal checks if this is a branch: the blockparams do
932        // not exist in post-regalloc code, and the edge-moves have to
933        // be inserted before the branch rather than after.
934        else if !self.f.is_branch(inst) {
935            let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
936            let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
937            let clobbers: Vec<_> = self.f.inst_clobbers(inst).into_iter().collect();
938            let checkinst = CheckerInst::Op {
939                inst,
940                operands,
941                allocs,
942                clobbers,
943            };
944            trace!("checker: adding inst {:?}", checkinst);
945            self.bb_insts.get_mut(&block).unwrap().push(checkinst);
946        }
947        // Instead, if this is a branch, emit a ParallelMove on each
948        // outgoing edge as necessary to handle blockparams.
949        else {
950            for (i, &succ) in self.f.block_succs(block).iter().enumerate() {
951                let args = self.f.branch_blockparams(block, inst, i);
952                let params = self.f.block_params(succ);
953                assert_eq!(
954                    args.len(),
955                    params.len(),
956                    "block{} has succ block{}; gave {} args for {} params",
957                    block.index(),
958                    succ.index(),
959                    args.len(),
960                    params.len()
961                );
962                if args.len() > 0 {
963                    let moves = params.iter().cloned().zip(args.iter().cloned()).collect();
964                    self.edge_insts
965                        .get_mut(&(block, succ))
966                        .unwrap()
967                        .push(CheckerInst::ParallelMove { moves });
968                }
969            }
970        }
971    }
972
973    fn handle_edit(&mut self, block: Block, edit: &Edit) {
974        trace!("checker: adding edit {:?}", edit);
975        match edit {
976            &Edit::Move { from, to } => {
977                self.bb_insts
978                    .get_mut(&block)
979                    .unwrap()
980                    .push(CheckerInst::Move { into: to, from });
981            }
982        }
983    }
984
985    /// Perform the dataflow analysis to compute checker state at each BB entry.
986    fn analyze(&mut self) {
987        let mut queue = Vec::new();
988        let mut queue_set = FxHashSet::default();
989
990        // Put every block in the queue to start with, to ensure
991        // everything is visited even if the initial state remains
992        // `Top` after preds update it.
993        //
994        // We add blocks in reverse order so that when we process
995        // back-to-front below, we do our initial pass in input block
996        // order, which is (usually) RPO order or at least a
997        // reasonable visit order.
998        for block in (0..self.f.num_blocks()).rev() {
999            let block = Block::new(block);
1000            queue.push(block);
1001            queue_set.insert(block);
1002        }
1003
1004        while let Some(block) = queue.pop() {
1005            queue_set.remove(&block);
1006            let mut state = self.bb_in.get(&block).cloned().unwrap();
1007            trace!("analyze: block {} has state {:?}", block.index(), state);
1008            for inst in self.bb_insts.get(&block).unwrap() {
1009                state.update(inst, self);
1010                trace!("analyze: inst {:?} -> state {:?}", inst, state);
1011            }
1012
1013            for &succ in self.f.block_succs(block) {
1014                let mut new_state = state.clone();
1015                for edge_inst in self.edge_insts.get(&(block, succ)).unwrap() {
1016                    new_state.update(edge_inst, self);
1017                    trace!(
1018                        "analyze: succ {:?}: inst {:?} -> state {:?}",
1019                        succ,
1020                        edge_inst,
1021                        new_state
1022                    );
1023                }
1024
1025                let cur_succ_in = self.bb_in.get(&succ).unwrap();
1026                trace!(
1027                    "meeting state {:?} for block {} with state {:?} for block {}",
1028                    new_state,
1029                    block.index(),
1030                    cur_succ_in,
1031                    succ.index()
1032                );
1033                new_state.meet_with(cur_succ_in);
1034                let changed = &new_state != cur_succ_in;
1035                trace!(" -> {:?}, changed {}", new_state, changed);
1036
1037                if changed {
1038                    trace!(
1039                        "analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
1040                        succ.index(),
1041                        cur_succ_in,
1042                        new_state
1043                    );
1044                    self.bb_in.insert(succ, new_state);
1045                    if queue_set.insert(succ) {
1046                        queue.push(succ);
1047                    }
1048                }
1049            }
1050        }
1051    }
1052
1053    /// Using BB-start state computed by `analyze()`, step the checker state
1054    /// through each BB and check each instruction's register allocations
1055    /// for errors.
1056    fn find_errors(&self) -> Result<(), CheckerErrors> {
1057        let mut errors = vec![];
1058        for (block, input) in &self.bb_in {
1059            let mut state = input.clone();
1060            for inst in self.bb_insts.get(block).unwrap() {
1061                if let Err(e) = state.check(InstPosition::Before, inst, self) {
1062                    trace!("Checker error: {:?}", e);
1063                    errors.push(e);
1064                }
1065                state.update(inst, self);
1066                if let Err(e) = state.check(InstPosition::After, inst, self) {
1067                    trace!("Checker error: {:?}", e);
1068                    errors.push(e);
1069                }
1070            }
1071        }
1072
1073        if errors.is_empty() {
1074            Ok(())
1075        } else {
1076            Err(CheckerErrors { errors })
1077        }
1078    }
1079
1080    /// Find any errors, returning `Err(CheckerErrors)` with all errors found
1081    /// or `Ok(())` otherwise.
1082    pub fn run(mut self) -> Result<(), CheckerErrors> {
1083        self.analyze();
1084        let result = self.find_errors();
1085
1086        trace!("=== CHECKER RESULT ===");
1087        fn print_state(state: &CheckerState) {
1088            if let CheckerState::Allocations(allocs) = state {
1089                let mut s = vec![];
1090                for (alloc, state) in allocs {
1091                    s.push(format!("{} := {}", alloc, state));
1092                }
1093                trace!("    {{ {} }}", s.join(", "))
1094            }
1095        }
1096        for vreg in self.f.reftype_vregs() {
1097            trace!("  REF: {}", vreg);
1098        }
1099        for bb in 0..self.f.num_blocks() {
1100            let bb = Block::new(bb);
1101            trace!("block{}:", bb.index());
1102            let insts = self.bb_insts.get(&bb).unwrap();
1103            let mut state = self.bb_in.get(&bb).unwrap().clone();
1104            print_state(&state);
1105            for inst in insts {
1106                match inst {
1107                    &CheckerInst::Op {
1108                        inst,
1109                        ref operands,
1110                        ref allocs,
1111                        ref clobbers,
1112                    } => {
1113                        trace!(
1114                            "  inst{}: {:?} ({:?}) clobbers:{:?}",
1115                            inst.index(),
1116                            operands,
1117                            allocs,
1118                            clobbers
1119                        );
1120                    }
1121                    &CheckerInst::Move { from, into } => {
1122                        trace!("    {} -> {}", from, into);
1123                    }
1124                    &CheckerInst::Safepoint { ref allocs, .. } => {
1125                        let mut slotargs = vec![];
1126                        for &slot in allocs {
1127                            slotargs.push(format!("{}", slot));
1128                        }
1129                        trace!("    safepoint: {}", slotargs.join(", "));
1130                    }
1131                    &CheckerInst::ProgramMove { inst, src, dst } => {
1132                        trace!("    inst{}: prog_move {} -> {}", inst.index(), src, dst);
1133                    }
1134                    &CheckerInst::ParallelMove { .. } => {
1135                        panic!("unexpected parallel_move in body (non-edge)")
1136                    }
1137                }
1138                state.update(inst, &self);
1139                print_state(&state);
1140            }
1141
1142            for &succ in self.f.block_succs(bb) {
1143                trace!("  succ {:?}:", succ);
1144                let mut state = state.clone();
1145                for edge_inst in self.edge_insts.get(&(bb, succ)).unwrap() {
1146                    match edge_inst {
1147                        &CheckerInst::ParallelMove { ref moves } => {
1148                            let moves = moves
1149                                .iter()
1150                                .map(|(dest, src)| format!("{} -> {}", src, dest))
1151                                .collect::<Vec<_>>();
1152                            trace!("    parallel_move {}", moves.join(", "));
1153                        }
1154                        _ => panic!("unexpected edge_inst: not a parallel move"),
1155                    }
1156                    state.update(edge_inst, &self);
1157                    print_state(&state);
1158                }
1159            }
1160        }
1161
1162        result
1163    }
1164}