#![allow(dead_code)]
use crate::analysis_data_flow::get_san_reg_sets_for_insn;
use crate::data_structures::{
BlockIx, InstIx, Map, RealReg, RealRegUniverse, Reg, RegSets, SpillSlot, VirtualReg, Writable,
};
use crate::inst_stream::{ExtPoint, InstExtPoint, InstToInsertAndExtPoint};
use crate::{Function, RegUsageMapper};
use rustc_hash::FxHashSet;
use std::collections::VecDeque;
use std::default::Default;
use std::hash::Hash;
use std::result::Result;
use log::debug;
#[derive(Clone, Debug)]
pub struct CheckerErrors {
errors: Vec<CheckerError>,
}
#[derive(Clone, Debug)]
pub enum CheckerError {
MissingAllocationForReg {
reg: VirtualReg,
inst: InstIx,
},
UnknownValueInReg {
real_reg: RealReg,
inst: InstIx,
},
IncorrectValueInReg {
actual: Reg,
expected: Reg,
real_reg: RealReg,
inst: InstIx,
},
UnknownValueInSlot {
slot: SpillSlot,
expected: Reg,
inst: InstIx,
},
IncorrectValueInSlot {
slot: SpillSlot,
expected: Reg,
actual: Reg,
inst: InstIx,
},
StackMapSpecifiesNonRefSlot {
inst: InstIx,
slot: SpillSlot,
},
StackMapSpecifiesUndefinedSlot {
inst: InstIx,
slot: SpillSlot,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CheckerValue {
Unknown,
Conflicted,
Reg(Reg, bool),
}
impl Default for CheckerValue {
fn default() -> CheckerValue {
CheckerValue::Unknown
}
}
impl CheckerValue {
fn meet(&self, other: &CheckerValue) -> CheckerValue {
match (self, other) {
(&CheckerValue::Unknown, _) => *other,
(_, &CheckerValue::Unknown) => *self,
(&CheckerValue::Conflicted, _) => *self,
(_, &CheckerValue::Conflicted) => *other,
(&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2)) if r1 == r2 => {
CheckerValue::Reg(r1, ref1 || ref2)
}
_ => CheckerValue::Conflicted,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct CheckerState {
reg_values: Map<RealReg, CheckerValue>,
spill_slots: Map<SpillSlot, CheckerValue>,
}
impl Default for CheckerState {
fn default() -> CheckerState {
CheckerState {
reg_values: Map::default(),
spill_slots: Map::default(),
}
}
}
fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
into: &mut Map<K, CheckerValue>,
from: &Map<K, CheckerValue>,
) {
for (k, v) in from {
let into_v = into.entry(*k).or_insert(Default::default());
let merged = into_v.meet(v);
*into_v = merged;
}
}
impl CheckerState {
fn new() -> CheckerState {
Default::default()
}
fn entry_state(ru: &RealRegUniverse) -> CheckerState {
let mut state = CheckerState::new();
for &(rreg, _) in &ru.regs {
state
.reg_values
.insert(rreg, CheckerValue::Reg(rreg.to_reg(), false));
}
state
}
fn meet_with(&mut self, other: &CheckerState) {
merge_map(&mut self.reg_values, &other.reg_values);
merge_map(&mut self.spill_slots, &other.spill_slots);
}
fn check(&self, inst: &Inst) -> Result<(), CheckerError> {
match inst {
&Inst::Op {
inst_ix,
ref uses_orig,
ref uses,
..
} => {
assert!(uses_orig.len() == uses.len());
for (orig, mapped) in uses_orig.iter().cloned().zip(uses.iter().cloned()) {
let val = self
.reg_values
.get(&mapped)
.cloned()
.unwrap_or(Default::default());
debug!(
"checker: inst {:?}: orig {:?}, mapped {:?}, checker state {:?}",
inst, orig, mapped, val
);
match val {
CheckerValue::Unknown | CheckerValue::Conflicted => {
return Err(CheckerError::UnknownValueInReg {
real_reg: mapped,
inst: inst_ix,
});
}
CheckerValue::Reg(r, _) if r != orig => {
return Err(CheckerError::IncorrectValueInReg {
actual: r,
expected: orig,
real_reg: mapped,
inst: inst_ix,
});
}
_ => {}
}
}
}
&Inst::ChangeSpillSlotOwnership {
inst_ix,
slot,
from_reg,
..
} => {
let val = self
.spill_slots
.get(&slot)
.cloned()
.unwrap_or(Default::default());
debug!("checker: inst {:?}: slot value {:?}", inst, val);
match val {
CheckerValue::Unknown | CheckerValue::Conflicted => {
return Err(CheckerError::UnknownValueInSlot {
slot,
expected: from_reg,
inst: inst_ix,
});
}
CheckerValue::Reg(r, _) if r != from_reg => {
return Err(CheckerError::IncorrectValueInSlot {
slot,
expected: from_reg,
actual: r,
inst: inst_ix,
});
}
_ => {}
}
}
&Inst::Safepoint { inst_ix, ref slots } => {
self.check_stackmap(inst_ix, slots)?;
}
_ => {}
}
Ok(())
}
fn check_stackmap(&self, inst: InstIx, slots: &Vec<SpillSlot>) -> Result<(), CheckerError> {
for &slot in slots {
match self.spill_slots.get(&slot) {
Some(CheckerValue::Reg(_, false)) => {
return Err(CheckerError::StackMapSpecifiesNonRefSlot { inst, slot });
}
Some(CheckerValue::Reg(_, true)) => {
}
_ => {
return Err(CheckerError::StackMapSpecifiesUndefinedSlot { inst, slot });
}
}
}
Ok(())
}
fn update_stackmap(&mut self, slots: &Vec<SpillSlot>) {
for (&slot, val) in &mut self.spill_slots {
if let &mut CheckerValue::Reg(_, true) = val {
let in_stackmap = slots.binary_search(&slot).is_ok();
if !in_stackmap {
*val = CheckerValue::Unknown;
}
}
}
}
pub(crate) fn update(&mut self, inst: &Inst) {
match inst {
&Inst::Op {
ref defs_orig,
ref defs,
ref defs_reftyped,
..
} => {
assert!(defs_orig.len() == defs.len());
for i in 0..defs.len() {
let orig = defs_orig[i];
let mapped = defs[i];
let reftyped = defs_reftyped[i];
self.reg_values
.insert(mapped, CheckerValue::Reg(orig, reftyped));
}
}
&Inst::Move { into, from } => {
let val = self
.reg_values
.get(&from)
.cloned()
.unwrap_or(Default::default());
self.reg_values.insert(into.to_reg(), val);
}
&Inst::ChangeSpillSlotOwnership { slot, to_reg, .. } => {
let reftyped = if let Some(val) = self.spill_slots.get(&slot) {
match val {
&CheckerValue::Reg(_, reftyped) => reftyped,
_ => false,
}
} else {
false
};
self.spill_slots
.insert(slot, CheckerValue::Reg(to_reg, reftyped));
}
&Inst::Spill { into, from } => {
let val = self
.reg_values
.get(&from)
.cloned()
.unwrap_or(Default::default());
self.spill_slots.insert(into, val);
}
&Inst::Reload { into, from } => {
let val = self
.spill_slots
.get(&from)
.cloned()
.unwrap_or(Default::default());
self.reg_values.insert(into.to_reg(), val);
}
&Inst::Safepoint { ref slots, .. } => {
self.update_stackmap(slots);
}
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum Inst {
Spill { into: SpillSlot, from: RealReg },
Reload {
into: Writable<RealReg>,
from: SpillSlot,
},
Move {
into: Writable<RealReg>,
from: RealReg,
},
ChangeSpillSlotOwnership {
inst_ix: InstIx,
slot: SpillSlot,
from_reg: Reg,
to_reg: Reg,
},
Op {
inst_ix: InstIx,
defs_orig: Vec<Reg>,
uses_orig: Vec<Reg>,
defs: Vec<RealReg>,
uses: Vec<RealReg>,
defs_reftyped: Vec<bool>,
},
Safepoint {
inst_ix: InstIx,
slots: Vec<SpillSlot>,
},
}
#[derive(Debug)]
pub(crate) struct Checker {
bb_entry: BlockIx,
bb_in: Map<BlockIx, CheckerState>,
bb_succs: Map<BlockIx, Vec<BlockIx>>,
bb_insts: Map<BlockIx, Vec<Inst>>,
reftyped_vregs: FxHashSet<VirtualReg>,
}
fn map_regs<F: Fn(VirtualReg) -> Option<RealReg>>(
inst: InstIx,
regs: &[Reg],
f: &F,
) -> Result<Vec<RealReg>, CheckerErrors> {
let mut errors = Vec::new();
let real_regs = regs
.iter()
.map(|r| {
if r.is_virtual() {
f(r.to_virtual_reg()).unwrap_or_else(|| {
errors.push(CheckerError::MissingAllocationForReg {
reg: r.to_virtual_reg(),
inst,
});
Reg::new_real(r.get_class(), 0x0, 0).to_real_reg()
})
} else {
r.to_real_reg()
}
})
.collect();
if errors.is_empty() {
Ok(real_regs)
} else {
Err(CheckerErrors { errors })
}
}
impl Checker {
pub(crate) fn new<F: Function>(
f: &F,
ru: &RealRegUniverse,
reftyped_vregs: &[VirtualReg],
) -> Checker {
let mut bb_in = Map::default();
let mut bb_succs = Map::default();
let mut bb_insts = Map::default();
for block in f.blocks() {
bb_in.insert(block, Default::default());
bb_succs.insert(block, f.block_succs(block).to_vec());
bb_insts.insert(block, vec![]);
}
bb_in.insert(f.entry_block(), CheckerState::entry_state(ru));
let reftyped_vregs = reftyped_vregs.iter().cloned().collect::<FxHashSet<_>>();
Checker {
bb_entry: f.entry_block(),
bb_in,
bb_succs,
bb_insts,
reftyped_vregs,
}
}
pub(crate) fn add_inst(&mut self, block: BlockIx, inst: Inst) {
let insts = self.bb_insts.get_mut(&block).unwrap();
insts.push(inst);
}
pub(crate) fn add_op<RUM: RegUsageMapper>(
&mut self,
block: BlockIx,
inst_ix: InstIx,
regsets: &RegSets,
mapper: &RUM,
) -> Result<(), CheckerErrors> {
debug!(
"add_op: block {} inst {} regsets {:?}",
block.get(),
inst_ix.get(),
regsets
);
assert!(regsets.is_sanitized());
let mut uses_set = regsets.uses.clone();
let mut defs_set = regsets.defs.clone();
uses_set.union(®sets.mods);
defs_set.union(®sets.mods);
if uses_set.is_empty() && defs_set.is_empty() {
return Ok(());
}
let uses_orig = uses_set.to_vec();
let defs_orig = defs_set.to_vec();
let uses = map_regs(inst_ix, &uses_orig[..], &|vreg| mapper.get_use(vreg))?;
let defs = map_regs(inst_ix, &defs_orig[..], &|vreg| mapper.get_def(vreg))?;
let defs_reftyped = defs_orig
.iter()
.map(|reg| reg.is_virtual() && self.reftyped_vregs.contains(®.to_virtual_reg()))
.collect();
let insts = self.bb_insts.get_mut(&block).unwrap();
let op = Inst::Op {
inst_ix,
uses_orig,
defs_orig,
uses,
defs,
defs_reftyped,
};
debug!("add_op: adding {:?}", op);
insts.push(op);
Ok(())
}
fn analyze(&mut self) {
let mut queue = VecDeque::new();
queue.push_back(self.bb_entry);
while !queue.is_empty() {
let block = queue.pop_front().unwrap();
let mut state = self.bb_in.get(&block).cloned().unwrap();
debug!("analyze: block {} has state {:?}", block.get(), state);
for inst in self.bb_insts.get(&block).unwrap() {
state.update(inst);
debug!("analyze: inst {:?} -> state {:?}", inst, state);
}
for succ in self.bb_succs.get(&block).unwrap() {
let cur_succ_in = self.bb_in.get(succ).unwrap();
let mut new_state = state.clone();
new_state.meet_with(cur_succ_in);
let changed = &new_state != cur_succ_in;
if changed {
debug!(
"analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
succ.get(),
cur_succ_in,
new_state
);
self.bb_in.insert(*succ, new_state);
queue.push_back(*succ);
}
}
}
}
fn find_errors(&self) -> Result<(), CheckerErrors> {
let mut errors = vec![];
for (block, input) in &self.bb_in {
let mut state = input.clone();
for inst in self.bb_insts.get(block).unwrap() {
if let Err(e) = state.check(inst) {
debug!("Checker error: {:?}", e);
errors.push(e);
}
state.update(inst);
}
}
if errors.is_empty() {
Ok(())
} else {
Err(CheckerErrors { errors })
}
}
pub(crate) fn run(mut self) -> Result<(), CheckerErrors> {
debug!("Checker: full body is:\n{:?}", self.bb_insts);
self.analyze();
self.find_errors()
}
}
pub(crate) struct CheckerContext {
checker: Checker,
checker_inst_map: Map<InstExtPoint, Vec<Inst>>,
}
impl CheckerContext {
pub(crate) fn new<F: Function>(
f: &F,
ru: &RealRegUniverse,
insts_to_add: &Vec<InstToInsertAndExtPoint>,
safepoint_insns: &[InstIx],
stackmaps: &[Vec<SpillSlot>],
reftyped_vregs: &[VirtualReg],
) -> CheckerContext {
assert!(safepoint_insns.len() == stackmaps.len());
let mut checker_inst_map: Map<InstExtPoint, Vec<Inst>> = Map::default();
for &InstToInsertAndExtPoint { ref inst, ref iep } in insts_to_add {
let checker_insts = checker_inst_map
.entry(iep.clone())
.or_insert_with(|| vec![]);
checker_insts.push(inst.to_checker_inst());
}
for (iix, slots) in safepoint_insns.iter().zip(stackmaps.iter()) {
let iep = InstExtPoint::new(*iix, ExtPoint::Use);
let mut slots = slots.clone();
slots.sort();
checker_inst_map
.entry(iep)
.or_insert_with(|| vec![])
.push(Inst::Safepoint {
inst_ix: *iix,
slots,
});
}
let checker = Checker::new(f, ru, reftyped_vregs);
CheckerContext {
checker,
checker_inst_map,
}
}
pub(crate) fn handle_insn<F: Function, RUM: RegUsageMapper>(
&mut self,
ru: &RealRegUniverse,
func: &F,
bix: BlockIx,
iix: InstIx,
mapper: &RUM,
) -> Result<(), CheckerErrors> {
let empty = vec![];
let mut skip_inst = false;
debug!("CheckerContext::handle_insn: inst {:?}", iix,);
for &pre_point in &[ExtPoint::Reload, ExtPoint::SpillBefore, ExtPoint::Use] {
let pre_point = InstExtPoint::new(iix, pre_point);
for checker_inst in self.checker_inst_map.get(&pre_point).unwrap_or(&empty) {
debug!("at inst {:?}: pre checker_inst: {:?}", iix, checker_inst);
self.checker.add_inst(bix, checker_inst.clone());
if let Inst::ChangeSpillSlotOwnership { .. } = checker_inst {
skip_inst = true;
}
}
}
if !skip_inst {
let regsets = get_san_reg_sets_for_insn::<F>(func.get_insn(iix), ru)
.expect("only existing real registers at this point");
assert!(regsets.is_sanitized());
debug!(
"at inst {:?}: regsets {:?} mapper {:?}",
iix, regsets, mapper
);
self.checker.add_op(bix, iix, ®sets, mapper)?;
}
for &post_point in &[ExtPoint::ReloadAfter, ExtPoint::Spill] {
let post_point = InstExtPoint::new(iix, post_point);
for checker_inst in self.checker_inst_map.get(&post_point).unwrap_or(&empty) {
debug!("at inst {:?}: post checker_inst: {:?}", iix, checker_inst);
self.checker.add_inst(bix, checker_inst.clone());
}
}
Ok(())
}
pub(crate) fn run(self) -> Result<(), CheckerErrors> {
self.checker.run()
}
}