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