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