regalloc/
checker.rs

1//! Checker: verifies that spills/reloads/moves retain equivalent dataflow to original, vreg-based
2//! code.
3//!
4//! The basic idea is that we track symbolic values as they flow through spills and reloads.
5//! The symbolic values represent particular virtual or real registers in the original
6//! function body presented to the register allocator. Any instruction in the original
7//! function body (i.e., not added by the allocator) conceptually generates a symbolic
8//! value "Rn" or "Vn" when storing to (or modifying) a real or virtual register. This
9//! includes moves (from e.g. phi-node lowering): they also generate a new value.
10//!
11//! In other words, the dataflow analysis state at each program point is:
12//!
13//!   - map `R` of: real reg -> lattice value  (top > Rn/Vn symbols (unordered) > bottom)
14//!   - map `S` of: spill slot -> lattice value (same)
15//!
16//! And the transfer functions for each statement type are:
17//!
18//!   - spill (inserted by RA):    [ store spill_i, R_j ]
19//!
20//!       S[spill_i] := R[R_j]
21//!
22//!   - reload (inserted by RA):   [ load R_i, spill_j ]
23//!
24//!       R[R_i] := S[spill_j]
25//!
26//!   - move (inserted by RA):     [ R_i := R_j ]
27//!
28//!       R[R_i] := R[R_j]
29//!
30//!   - statement in pre-regalloc function [ V_i := op V_j, V_k, ... ]
31//!     with allocated form                [ R_i := op R_j, R_k, ... ]
32//!
33//!       R[R_i] := `V_i`
34//!
35//!     In other words, a statement, even after allocation, generates a symbol
36//!     that corresponds to its original virtual-register def.
37//!
38//!     (N.B.: moves in pre-regalloc function fall into this last case -- they
39//!      are "just another operation" and generate a new symbol)
40//!
41//!     (Slight extension for multi-def ops, and ops with "modify" args: the op
42//!      generates symbol `V_i` into reg `R_i` allocated for that particular def/mod).
43//!
44//! The initial state is: for each real reg R_livein where R_livein is in the livein set, we set
45//! R[R_livein] to `R_livein`.
46//!
47//! At control-flow join points, the symbols meet using a very simple lattice meet-function:
48//! two different symbols in the same real-reg or spill-slot meet to "conflicted"; otherwise,
49//! the symbol meets with itself to produce itself (reflexivity).
50//!
51//! To check correctness, we first find the dataflow fixpoint with the above lattice and
52//! transfer/meet functions. Then, at each op, we examine the dataflow solution at the preceding
53//! program point, and check that the real reg for each op arg (input/use) contains the symbol
54//! corresponding to the original (usually virtual) register specified for this arg.
55
56#![allow(dead_code)]
57
58use crate::data_structures::{
59    BlockIx, InstIx, Map, RealReg, RealRegUniverse, Reg, RegSets, SpillSlot, VirtualReg, Writable,
60};
61use crate::inst_stream::{ExtPoint, InstExtPoint, InstToInsertAndExtPoint};
62use crate::{analysis_data_flow::get_san_reg_sets_for_insn, StackmapRequestInfo};
63use crate::{Function, RegUsageMapper};
64
65use rustc_hash::FxHashSet;
66use std::collections::VecDeque;
67use std::default::Default;
68use std::hash::Hash;
69use std::result::Result;
70
71use log::debug;
72
73/// A set of errors detected by the regalloc checker.
74#[derive(Clone, Debug)]
75pub struct CheckerErrors {
76    errors: Vec<CheckerError>,
77}
78
79/// A single error detected by the regalloc checker.
80#[derive(Clone, Debug)]
81pub enum CheckerError {
82    MissingAllocationForReg {
83        reg: VirtualReg,
84        inst: InstIx,
85    },
86    UnknownValueInReg {
87        real_reg: RealReg,
88        inst: InstIx,
89    },
90    IncorrectValueInReg {
91        actual: Reg,
92        expected: Reg,
93        real_reg: RealReg,
94        inst: InstIx,
95    },
96    UnknownValueInSlot {
97        slot: SpillSlot,
98        expected: Reg,
99        inst: InstIx,
100    },
101    IncorrectValueInSlot {
102        slot: SpillSlot,
103        expected: Reg,
104        actual: Reg,
105        inst: InstIx,
106    },
107    StackMapSpecifiesNonRefSlot {
108        inst: InstIx,
109        slot: SpillSlot,
110    },
111    StackMapSpecifiesUndefinedSlot {
112        inst: InstIx,
113        slot: SpillSlot,
114    },
115}
116
117/// Abstract state for a storage slot (real register or spill slot).
118///
119/// Forms a lattice with \top (`Unknown`), \bot (`Conflicted`), and a number of mutually unordered
120/// value-points in between, one per real or virtual register. Any two different registers
121/// meet to \bot.
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123enum CheckerValue {
124    /// "top" value: this storage slot has no known value.
125    Unknown,
126    /// "bottom" value: this storage slot has a conflicted value.
127    Conflicted,
128    /// Reg: this storage slot has a value that originated as a def into
129    /// the given register, either implicitly (RealRegs at beginning of
130    /// function) or explicitly (as an instruction's def).
131    ///
132    /// The boolean flag indicates whether the value is reference-typed.
133    Reg(Reg, bool),
134}
135
136impl Default for CheckerValue {
137    fn default() -> CheckerValue {
138        CheckerValue::Unknown
139    }
140}
141
142impl CheckerValue {
143    /// Meet function of the abstract-interpretation value lattice.
144    fn meet(&self, other: &CheckerValue) -> CheckerValue {
145        match (self, other) {
146            (&CheckerValue::Unknown, _) => *other,
147            (_, &CheckerValue::Unknown) => *self,
148            (&CheckerValue::Conflicted, _) => *self,
149            (_, &CheckerValue::Conflicted) => *other,
150            (&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2)) if r1 == r2 => {
151                CheckerValue::Reg(r1, ref1 || ref2)
152            }
153            _ => CheckerValue::Conflicted,
154        }
155    }
156}
157
158/// State that steps through program points as we scan over the instruction stream.
159#[derive(Clone, Debug, PartialEq, Eq)]
160struct CheckerState {
161    /// For each RealReg, abstract state.
162    reg_values: Map<RealReg, CheckerValue>,
163    /// For each spill slot, abstract state.
164    spill_slots: Map<SpillSlot, CheckerValue>,
165}
166
167impl Default for CheckerState {
168    fn default() -> CheckerState {
169        CheckerState {
170            reg_values: Map::default(),
171            spill_slots: Map::default(),
172        }
173    }
174}
175
176fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
177    into: &mut Map<K, CheckerValue>,
178    from: &Map<K, CheckerValue>,
179) {
180    for (k, v) in from {
181        let into_v = into.entry(*k).or_insert(Default::default());
182        let merged = into_v.meet(v);
183        *into_v = merged;
184    }
185}
186
187impl CheckerState {
188    /// Create a new checker state.
189    fn new() -> CheckerState {
190        Default::default()
191    }
192
193    /// Produce an entry checker state with all real regs holding themselves, symbolically.
194    fn entry_state(ru: &RealRegUniverse) -> CheckerState {
195        let mut state = CheckerState::new();
196        for &(rreg, _) in &ru.regs {
197            state
198                .reg_values
199                .insert(rreg, CheckerValue::Reg(rreg.to_reg(), false));
200        }
201        state
202    }
203
204    /// Merge this checker state with another at a CFG join-point.
205    fn meet_with(&mut self, other: &CheckerState) {
206        merge_map(&mut self.reg_values, &other.reg_values);
207        merge_map(&mut self.spill_slots, &other.spill_slots);
208    }
209
210    /// Check an instruction against this state.
211    fn check(&self, inst: &Inst) -> Result<(), CheckerError> {
212        match inst {
213            &Inst::Op {
214                inst_ix,
215                ref uses_orig,
216                ref uses,
217                ..
218            } => {
219                // For each use, check the mapped RealReg's symbolic value; it must
220                // be the original reg.
221                assert!(uses_orig.len() == uses.len());
222                for (orig, mapped) in uses_orig.iter().cloned().zip(uses.iter().cloned()) {
223                    let val = self
224                        .reg_values
225                        .get(&mapped)
226                        .cloned()
227                        .unwrap_or(Default::default());
228                    debug!(
229                        "checker: inst {:?}: orig {:?}, mapped {:?}, checker state {:?}",
230                        inst, orig, mapped, val
231                    );
232                    match val {
233                        CheckerValue::Unknown | CheckerValue::Conflicted => {
234                            return Err(CheckerError::UnknownValueInReg {
235                                real_reg: mapped,
236                                inst: inst_ix,
237                            });
238                        }
239                        CheckerValue::Reg(r, _) if r != orig => {
240                            return Err(CheckerError::IncorrectValueInReg {
241                                actual: r,
242                                expected: orig,
243                                real_reg: mapped,
244                                inst: inst_ix,
245                            });
246                        }
247                        _ => {}
248                    }
249                }
250            }
251            &Inst::ChangeSpillSlotOwnership {
252                inst_ix,
253                slot,
254                from_reg,
255                ..
256            } => {
257                let val = self
258                    .spill_slots
259                    .get(&slot)
260                    .cloned()
261                    .unwrap_or(Default::default());
262                debug!("checker: inst {:?}: slot value {:?}", inst, val);
263                match val {
264                    CheckerValue::Unknown | CheckerValue::Conflicted => {
265                        return Err(CheckerError::UnknownValueInSlot {
266                            slot,
267                            expected: from_reg,
268                            inst: inst_ix,
269                        });
270                    }
271                    CheckerValue::Reg(r, _) if r != from_reg => {
272                        return Err(CheckerError::IncorrectValueInSlot {
273                            slot,
274                            expected: from_reg,
275                            actual: r,
276                            inst: inst_ix,
277                        });
278                    }
279                    _ => {}
280                }
281            }
282            &Inst::Safepoint { inst_ix, ref slots } => {
283                self.check_stackmap(inst_ix, slots)?;
284            }
285            _ => {}
286        }
287        Ok(())
288    }
289
290    fn check_stackmap(&self, inst: InstIx, slots: &Vec<SpillSlot>) -> Result<(), CheckerError> {
291        // N.B.: it's OK for the stackmap to omit a slot that has a ref value in
292        // it; it might be dead. We simply update such a slot's value to
293        // 'undefined' in the transfer function.
294        for &slot in slots {
295            match self.spill_slots.get(&slot) {
296                Some(CheckerValue::Reg(_, false)) => {
297                    return Err(CheckerError::StackMapSpecifiesNonRefSlot { inst, slot });
298                }
299                Some(CheckerValue::Reg(_, true)) => {
300                    // OK.
301                }
302                _ => {
303                    return Err(CheckerError::StackMapSpecifiesUndefinedSlot { inst, slot });
304                }
305            }
306        }
307        Ok(())
308    }
309
310    fn update_stackmap(&mut self, slots: &Vec<SpillSlot>) {
311        for (&slot, val) in &mut self.spill_slots {
312            if let &mut CheckerValue::Reg(_, true) = val {
313                let in_stackmap = slots.binary_search(&slot).is_ok();
314                if !in_stackmap {
315                    *val = CheckerValue::Unknown;
316                }
317            }
318        }
319        // Mark registers holding reference values as unknown, forcing the need for a reload around
320        // the safepoint.
321        for (_, val) in &mut self.reg_values {
322            if let &mut CheckerValue::Reg(_, true) = val {
323                *val = CheckerValue::Unknown;
324            }
325        }
326    }
327
328    /// Update according to instruction.
329    fn update(&mut self, inst: &Inst) {
330        match inst {
331            &Inst::Op {
332                ref defs_orig,
333                ref defs,
334                ref defs_reftyped,
335                ..
336            } => {
337                // For each def, set the symbolic value of the mapped RealReg to a
338                // symbol corresponding to the original def.
339                assert!(defs_orig.len() == defs.len());
340                for i in 0..defs.len() {
341                    let orig = defs_orig[i];
342                    let mapped = defs[i];
343                    let reftyped = defs_reftyped[i];
344                    self.reg_values
345                        .insert(mapped, CheckerValue::Reg(orig, reftyped));
346                }
347            }
348            &Inst::Move { into, from } => {
349                let val = self
350                    .reg_values
351                    .get(&from)
352                    .cloned()
353                    .unwrap_or(Default::default());
354                self.reg_values.insert(into.to_reg(), val);
355            }
356            &Inst::ChangeSpillSlotOwnership { slot, to_reg, .. } => {
357                let reftyped = if let Some(val) = self.spill_slots.get(&slot) {
358                    match val {
359                        &CheckerValue::Reg(_, reftyped) => reftyped,
360                        _ => false,
361                    }
362                } else {
363                    false
364                };
365                self.spill_slots
366                    .insert(slot, CheckerValue::Reg(to_reg, reftyped));
367            }
368            &Inst::Spill { into, from } => {
369                let val = self
370                    .reg_values
371                    .get(&from)
372                    .cloned()
373                    .unwrap_or(Default::default());
374                self.spill_slots.insert(into, val);
375            }
376            &Inst::Reload { into, from } => {
377                let val = self
378                    .spill_slots
379                    .get(&from)
380                    .cloned()
381                    .unwrap_or(Default::default());
382                self.reg_values.insert(into.to_reg(), val);
383            }
384            &Inst::Safepoint { ref slots, .. } => {
385                self.update_stackmap(slots);
386            }
387        }
388    }
389}
390
391/// An instruction representation in the checker's BB summary.
392#[derive(Clone, Debug)]
393pub(crate) enum Inst {
394    /// A register spill into memory.
395    Spill { into: SpillSlot, from: RealReg },
396    /// A register reload from memory.
397    Reload {
398        into: Writable<RealReg>,
399        from: SpillSlot,
400    },
401    /// A regalloc-inserted move (not a move in the original program!)
402    Move {
403        into: Writable<RealReg>,
404        from: RealReg,
405    },
406    /// A spillslot ghost move (between vregs) resulting from an user-program
407    /// move whose source and destination regs are both vregs that are currently
408    /// spilled.
409    ChangeSpillSlotOwnership {
410        inst_ix: InstIx,
411        slot: SpillSlot,
412        from_reg: Reg,
413        to_reg: Reg,
414    },
415    /// A regular instruction with fixed use and def slots. Contains both
416    /// the original registers (as given to the regalloc) and the allocated ones.
417    Op {
418        inst_ix: InstIx,
419        defs_orig: Vec<Reg>,
420        uses_orig: Vec<Reg>,
421        defs: Vec<RealReg>,
422        uses: Vec<RealReg>,
423        defs_reftyped: Vec<bool>,
424    },
425    /// A safepoint, with a list of expected slots.
426    Safepoint {
427        inst_ix: InstIx,
428        slots: Vec<SpillSlot>,
429    },
430}
431
432#[derive(Debug)]
433pub(crate) struct Checker {
434    bb_entry: BlockIx,
435    bb_in: Map<BlockIx, CheckerState>,
436    bb_succs: Map<BlockIx, Vec<BlockIx>>,
437    bb_insts: Map<BlockIx, Vec<Inst>>,
438    reftyped_vregs: FxHashSet<VirtualReg>,
439    has_run: bool,
440}
441
442fn map_regs<F: Fn(VirtualReg) -> Option<RealReg>>(
443    inst: InstIx,
444    regs: &[Reg],
445    f: &F,
446) -> Result<Vec<RealReg>, CheckerErrors> {
447    let mut errors = Vec::new();
448    let real_regs = regs
449        .iter()
450        .map(|r| {
451            if r.is_virtual() {
452                f(r.to_virtual_reg()).unwrap_or_else(|| {
453                    errors.push(CheckerError::MissingAllocationForReg {
454                        reg: r.to_virtual_reg(),
455                        inst,
456                    });
457                    // Provide a dummy value for the register, it'll never be read.
458                    Reg::new_real(r.get_class(), 0x0, 0).to_real_reg()
459                })
460            } else {
461                r.to_real_reg()
462            }
463        })
464        .collect();
465    if errors.is_empty() {
466        Ok(real_regs)
467    } else {
468        Err(CheckerErrors { errors })
469    }
470}
471
472impl Checker {
473    /// Create a new checker for the given function, initializing CFG info immediately.
474    /// The client should call the `add_*()` methods to add abstract instructions to each
475    /// BB before invoking `run()` to check for errors.
476    pub(crate) fn new<F: Function>(
477        f: &F,
478        ru: &RealRegUniverse,
479        reftyped_vregs: &[VirtualReg],
480    ) -> Checker {
481        let mut bb_in = Map::default();
482        let mut bb_succs = Map::default();
483        let mut bb_insts = Map::default();
484
485        for block in f.blocks() {
486            bb_in.insert(block, Default::default());
487            bb_succs.insert(block, f.block_succs(block).to_vec());
488            bb_insts.insert(block, vec![]);
489        }
490
491        bb_in.insert(f.entry_block(), CheckerState::entry_state(ru));
492
493        let reftyped_vregs = reftyped_vregs.iter().cloned().collect::<FxHashSet<_>>();
494        Checker {
495            bb_entry: f.entry_block(),
496            bb_in,
497            bb_succs,
498            bb_insts,
499            reftyped_vregs,
500            has_run: false,
501        }
502    }
503
504    /// Add an abstract instruction (spill, reload, or move) to a BB.
505    ///
506    /// Can also accept an `Inst::Op`, but `add_op()` is better-suited
507    /// for this.
508    pub(crate) fn add_inst(&mut self, block: BlockIx, inst: Inst) {
509        let insts = self.bb_insts.get_mut(&block).unwrap();
510        insts.push(inst);
511    }
512
513    /// Add a "normal" instruction that uses, modifies, and/or defines certain
514    /// registers. The `SanitizedInstRegUses` must be the pre-allocation state;
515    /// the `mapper` must be provided to give the virtual -> real mappings at
516    /// the program points immediately before and after this instruction.
517    pub(crate) fn add_op<RUM: RegUsageMapper>(
518        &mut self,
519        block: BlockIx,
520        inst_ix: InstIx,
521        regsets: &RegSets,
522        mapper: &RUM,
523    ) -> Result<(), CheckerErrors> {
524        debug!(
525            "add_op: block {} inst {} regsets {:?}",
526            block.get(),
527            inst_ix.get(),
528            regsets
529        );
530        assert!(regsets.is_sanitized());
531        let mut uses_set = regsets.uses.clone();
532        let mut defs_set = regsets.defs.clone();
533        uses_set.union(&regsets.mods);
534        defs_set.union(&regsets.mods);
535        if uses_set.is_empty() && defs_set.is_empty() {
536            return Ok(());
537        }
538
539        let uses_orig = uses_set.to_vec();
540        let defs_orig = defs_set.to_vec();
541        let uses = map_regs(inst_ix, &uses_orig[..], &|vreg| mapper.get_use(vreg))?;
542        let defs = map_regs(inst_ix, &defs_orig[..], &|vreg| mapper.get_def(vreg))?;
543        let defs_reftyped = defs_orig
544            .iter()
545            .map(|reg| reg.is_virtual() && self.reftyped_vregs.contains(&reg.to_virtual_reg()))
546            .collect();
547        let insts = self.bb_insts.get_mut(&block).unwrap();
548        let op = Inst::Op {
549            inst_ix,
550            uses_orig,
551            defs_orig,
552            uses,
553            defs,
554            defs_reftyped,
555        };
556        debug!("add_op: adding {:?}", op);
557        insts.push(op);
558        Ok(())
559    }
560
561    /// Perform the dataflow analysis to compute checker state at each BB entry.
562    fn analyze(&mut self) {
563        let mut queue = VecDeque::new();
564        queue.push_back(self.bb_entry);
565
566        while !queue.is_empty() {
567            let block = queue.pop_front().unwrap();
568            let mut state = self.bb_in.get(&block).cloned().unwrap();
569            debug!("analyze: block {} has state {:?}", block.get(), state);
570            for inst in self.bb_insts.get(&block).unwrap() {
571                state.update(inst);
572                debug!("analyze: inst {:?} -> state {:?}", inst, state);
573            }
574
575            for succ in self.bb_succs.get(&block).unwrap() {
576                let cur_succ_in = self.bb_in.get(succ).unwrap();
577                let mut new_state = state.clone();
578                new_state.meet_with(cur_succ_in);
579                let changed = &new_state != cur_succ_in;
580                if changed {
581                    debug!(
582                        "analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
583                        succ.get(),
584                        cur_succ_in,
585                        new_state
586                    );
587                    self.bb_in.insert(*succ, new_state);
588                    queue.push_back(*succ);
589                }
590            }
591        }
592    }
593
594    /// Using BB-start state computed by `analyze()`, step the checker state
595    /// through each BB and check each instruction's register allocations
596    /// for errors.
597    fn find_errors(&self) -> Result<(), CheckerErrors> {
598        let mut errors = vec![];
599        for (block, input) in &self.bb_in {
600            let mut state = input.clone();
601            for inst in self.bb_insts.get(block).unwrap() {
602                if let Err(e) = state.check(inst) {
603                    debug!("Checker error: {:?}", e);
604                    errors.push(e);
605                }
606                state.update(inst);
607            }
608        }
609
610        if errors.is_empty() {
611            Ok(())
612        } else {
613            Err(CheckerErrors { errors })
614        }
615    }
616
617    /// Find any errors, returning `Err(CheckerErrors)` with all errors found
618    /// or `Ok(())` otherwise.
619    pub(crate) fn run(mut self) -> Result<(), CheckerErrors> {
620        debug!("Checker: full body is:\n{:?}", self.bb_insts);
621        self.has_run = true;
622        self.analyze();
623        self.find_errors()
624    }
625}
626
627/// A wrapper around `Checker` that assists its use with `InstToInsertAndExtPoint`s and
628/// `Function` together.
629pub(crate) struct CheckerContext {
630    checker: Checker,
631    checker_inst_map: Map<InstExtPoint, Vec<Inst>>,
632}
633
634/// Full infromation needed by the checker for checking reference types.
635pub(crate) struct CheckerStackmapInfo<'a> {
636    pub(crate) request: &'a StackmapRequestInfo,
637    pub(crate) stackmaps: &'a [Vec<SpillSlot>],
638}
639
640impl CheckerContext {
641    /// Create a new checker context for the given function, which is about to be edited with the
642    /// given instruction insertions.
643    pub(crate) fn new<F: Function>(
644        f: &F,
645        ru: &RealRegUniverse,
646        insts_to_add: &Vec<InstToInsertAndExtPoint>,
647        stackmap_info: Option<CheckerStackmapInfo>,
648    ) -> CheckerContext {
649        let mut checker_inst_map: Map<InstExtPoint, Vec<Inst>> = Map::default();
650        for &InstToInsertAndExtPoint { ref inst, ref iep } in insts_to_add {
651            let checker_insts = checker_inst_map
652                .entry(iep.clone())
653                .or_insert_with(|| vec![]);
654            checker_insts.push(inst.to_checker_inst());
655        }
656
657        let reftyped_vregs = if let Some(info) = stackmap_info {
658            assert!(info.request.safepoint_insns.len() == info.stackmaps.len());
659            for (iix, slots) in info
660                .request
661                .safepoint_insns
662                .iter()
663                .zip(info.stackmaps.iter())
664            {
665                let iep = InstExtPoint::new(*iix, ExtPoint::Use);
666                let mut slots = slots.clone();
667                slots.sort();
668                checker_inst_map
669                    .entry(iep)
670                    .or_insert_with(|| vec![])
671                    .push(Inst::Safepoint {
672                        inst_ix: *iix,
673                        slots,
674                    });
675            }
676            info.request.reftyped_vregs.as_slice()
677        } else {
678            &[]
679        };
680
681        let checker = Checker::new(f, ru, reftyped_vregs);
682        CheckerContext {
683            checker,
684            checker_inst_map,
685        }
686    }
687
688    /// Update the checker with the given instruction and the given pre- and post-maps. Instructions
689    /// within a block must be visited in program order.
690    pub(crate) fn handle_insn<F: Function, RUM: RegUsageMapper>(
691        &mut self,
692        ru: &RealRegUniverse,
693        func: &F,
694        bix: BlockIx,
695        iix: InstIx,
696        mapper: &RUM,
697    ) -> Result<(), CheckerErrors> {
698        let empty = vec![];
699        let mut skip_inst = false;
700
701        debug!("CheckerContext::handle_insn: inst {:?}", iix,);
702
703        for &pre_point in &[ExtPoint::Reload, ExtPoint::SpillBefore, ExtPoint::Use] {
704            let pre_point = InstExtPoint::new(iix, pre_point);
705            for checker_inst in self.checker_inst_map.get(&pre_point).unwrap_or(&empty) {
706                debug!("at inst {:?}: pre checker_inst: {:?}", iix, checker_inst);
707                self.checker.add_inst(bix, checker_inst.clone());
708                if let Inst::ChangeSpillSlotOwnership { .. } = checker_inst {
709                    // Unlike spills/reloads/moves inserted by the regalloc, ChangeSpillSlotOwnership
710                    // pseudo-insts replace the instruction itself.
711                    skip_inst = true;
712                }
713            }
714        }
715
716        if !skip_inst {
717            let regsets = get_san_reg_sets_for_insn::<F>(func.get_insn(iix), ru)
718                .expect("only existing real registers at this point");
719            assert!(regsets.is_sanitized());
720
721            debug!(
722                "at inst {:?}: regsets {:?} mapper {:?}",
723                iix, regsets, mapper
724            );
725            self.checker.add_op(bix, iix, &regsets, mapper)?;
726        }
727
728        for &post_point in &[ExtPoint::ReloadAfter, ExtPoint::Spill] {
729            let post_point = InstExtPoint::new(iix, post_point);
730            for checker_inst in self.checker_inst_map.get(&post_point).unwrap_or(&empty) {
731                debug!("at inst {:?}: post checker_inst: {:?}", iix, checker_inst);
732                self.checker.add_inst(bix, checker_inst.clone());
733            }
734        }
735
736        Ok(())
737    }
738
739    /// Run the underlying checker, once all instructions have been added.
740    pub(crate) fn run(self) -> Result<(), CheckerErrors> {
741        self.checker.run()
742    }
743}
744
745impl Drop for Checker {
746    fn drop(&mut self) {
747        if !self.has_run {
748            panic!("Programmer error: the CheckerContext run() function hasn't been called");
749        }
750    }
751}