Make some improvements to clarity of checker implementation. (#37)
This PR makes two changes, both suggested by @fitzgen in #36: 1. It updates the top-level description of the analysis to more simply and accurately describe the analysis lattice. 2. It modifies both the `CheckerValue` and `CheckerState` types to be enums with separate arms for the top/universe value, and adds helpers as appropriate to update the values. There should be no functional change; this update just makes the meet-functions and updates more clear, and makes a bad state ("top" but with values) unrepresentable. Closes #36.
This commit is contained in:
241
src/checker.rs
241
src/checker.rs
@@ -43,18 +43,17 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Formal Definition
|
//! ## Formal Definition
|
||||||
//!
|
//!
|
||||||
//! The analysis lattice is:
|
//! The analysis lattice consists of the elements of 𝒫(V), the
|
||||||
|
//! powerset (set of all subsets) of V (the set of all virtual
|
||||||
|
//! registers). The ⊤ (top) value in the lattice is V itself, and the
|
||||||
|
//! ⊥ (bottom) value in the lattice is ∅ (the empty set). The lattice
|
||||||
|
//! ordering relation is the subset relation: S ≤ U iff S ⊆ U. These
|
||||||
|
//! definitions imply that the lattice meet-function (greatest lower
|
||||||
|
//! bound) is set-intersection.
|
||||||
//!
|
//!
|
||||||
//! ```plain
|
//! (For efficiency, we represent ⊤ not by actually listing out all
|
||||||
//! Top (V)
|
//! virtual registers, but by representing a special "top" value, but
|
||||||
//! |
|
//! the semantics are the same.)
|
||||||
//! 𝒫(V) // the Powerset of the set of virtual regs
|
|
||||||
//! |
|
|
||||||
//! Bottom ( ∅ ) // the empty set
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! and the lattice ordering relation is the subset relation: S ≤ U
|
|
||||||
//! iff S ⊆ U. The lattice meet-function is intersection.
|
|
||||||
//!
|
//!
|
||||||
//! The dataflow analysis state at each program point (each point
|
//! The dataflow analysis state at each program point (each point
|
||||||
//! before or after an instruction) is:
|
//! before or after an instruction) is:
|
||||||
@@ -178,19 +177,33 @@ pub enum CheckerError {
|
|||||||
/// universe-set as top and empty set as bottom lattice element. The
|
/// universe-set as top and empty set as bottom lattice element. The
|
||||||
/// meet-function is thus set intersection.
|
/// meet-function is thus set intersection.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct CheckerValue {
|
enum CheckerValue {
|
||||||
/// This value is the "universe set".
|
/// The lattice top-value: this value could be equivalent to any
|
||||||
universe: bool,
|
/// vreg (i.e., the universe set).
|
||||||
/// The VRegs that this value is equal to.
|
Universe,
|
||||||
vregs: FxHashSet<VReg>,
|
/// The set of VRegs that this value is equal to.
|
||||||
|
VRegs(FxHashSet<VReg>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerValue {
|
||||||
|
fn vregs(&self) -> Option<&FxHashSet<VReg>> {
|
||||||
|
match self {
|
||||||
|
CheckerValue::Universe => None,
|
||||||
|
CheckerValue::VRegs(vregs) => Some(vregs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vregs_mut(&mut self) -> Option<&mut FxHashSet<VReg>> {
|
||||||
|
match self {
|
||||||
|
CheckerValue::Universe => None,
|
||||||
|
CheckerValue::VRegs(vregs) => Some(vregs),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CheckerValue {
|
impl Default for CheckerValue {
|
||||||
fn default() -> CheckerValue {
|
fn default() -> CheckerValue {
|
||||||
CheckerValue {
|
CheckerValue::Universe
|
||||||
universe: true,
|
|
||||||
vregs: FxHashSet::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,63 +212,128 @@ impl CheckerValue {
|
|||||||
/// lattice. Returns a boolean value indicating whether `self` was
|
/// lattice. Returns a boolean value indicating whether `self` was
|
||||||
/// changed.
|
/// changed.
|
||||||
fn meet_with(&mut self, other: &CheckerValue) {
|
fn meet_with(&mut self, other: &CheckerValue) {
|
||||||
if other.universe {
|
match (self, other) {
|
||||||
|
(_, CheckerValue::Universe) => {
|
||||||
// Nothing.
|
// Nothing.
|
||||||
} else if self.universe {
|
}
|
||||||
*self = other.clone();
|
(this @ CheckerValue::Universe, _) => {
|
||||||
} else {
|
*this = other.clone();
|
||||||
self.vregs.retain(|vreg| other.vregs.contains(vreg));
|
}
|
||||||
|
(CheckerValue::VRegs(my_vregs), CheckerValue::VRegs(other_vregs)) => {
|
||||||
|
my_vregs.retain(|vreg| other_vregs.contains(vreg));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_reg(reg: VReg) -> CheckerValue {
|
fn from_reg(reg: VReg) -> CheckerValue {
|
||||||
CheckerValue {
|
CheckerValue::VRegs(std::iter::once(reg).collect())
|
||||||
universe: false,
|
|
||||||
vregs: std::iter::once(reg).collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_vreg(&mut self, reg: VReg) {
|
fn remove_vreg(&mut self, reg: VReg) {
|
||||||
self.vregs.remove(®);
|
match self {
|
||||||
|
CheckerValue::Universe => {
|
||||||
|
panic!("Cannot remove VReg from Universe set (we do not have the full list of vregs available");
|
||||||
|
}
|
||||||
|
CheckerValue::VRegs(vregs) => {
|
||||||
|
vregs.remove(®);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty() -> CheckerValue {
|
fn empty() -> CheckerValue {
|
||||||
CheckerValue {
|
CheckerValue::VRegs(FxHashSet::default())
|
||||||
universe: false,
|
|
||||||
vregs: FxHashSet::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State that steps through program points as we scan over the instruction stream.
|
/// State that steps through program points as we scan over the instruction stream.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct CheckerState {
|
enum CheckerState {
|
||||||
top: bool,
|
Top,
|
||||||
allocations: FxHashMap<Allocation, CheckerValue>,
|
Allocations(FxHashMap<Allocation, CheckerValue>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerState {
|
||||||
|
fn get_value(&self, alloc: &Allocation) -> Option<&CheckerValue> {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => None,
|
||||||
|
CheckerState::Allocations(allocs) => allocs.get(alloc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_values_mut(&mut self) -> impl Iterator<Item = &mut CheckerValue> {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => panic!("Cannot get mutable values iterator on Top state"),
|
||||||
|
CheckerState::Allocations(allocs) => allocs.values_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mappings(&self) -> impl Iterator<Item = (&Allocation, &CheckerValue)> {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => panic!("Cannot get mappings iterator on Top state"),
|
||||||
|
CheckerState::Allocations(allocs) => allocs.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mappings_mut(&mut self) -> impl Iterator<Item = (&Allocation, &mut CheckerValue)> {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => panic!("Cannot get mutable mappings iterator on Top state"),
|
||||||
|
CheckerState::Allocations(allocs) => allocs.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transition from a "top" (undefined/unanalyzed) state to an empty set of allocations.
|
||||||
|
fn become_defined(&mut self) {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => *self = CheckerState::Allocations(FxHashMap::default()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_value(&mut self, alloc: Allocation, value: CheckerValue) {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => {
|
||||||
|
panic!("Cannot set value on Top state");
|
||||||
|
}
|
||||||
|
CheckerState::Allocations(allocs) => {
|
||||||
|
allocs.insert(alloc, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_value(&mut self, alloc: &Allocation) {
|
||||||
|
match self {
|
||||||
|
CheckerState::Top => {
|
||||||
|
panic!("Cannot remove value on Top state");
|
||||||
|
}
|
||||||
|
CheckerState::Allocations(allocs) => {
|
||||||
|
allocs.remove(alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CheckerState {
|
impl Default for CheckerState {
|
||||||
fn default() -> CheckerState {
|
fn default() -> CheckerState {
|
||||||
CheckerState {
|
CheckerState::Top
|
||||||
top: true,
|
|
||||||
allocations: FxHashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for CheckerValue {
|
impl std::fmt::Display for CheckerValue {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
if self.universe {
|
match self {
|
||||||
|
CheckerValue::Universe => {
|
||||||
write!(f, "top")
|
write!(f, "top")
|
||||||
} else {
|
}
|
||||||
|
CheckerValue::VRegs(vregs) => {
|
||||||
write!(f, "{{ ")?;
|
write!(f, "{{ ")?;
|
||||||
for vreg in &self.vregs {
|
for vreg in vregs {
|
||||||
write!(f, "{} ", vreg)?;
|
write!(f, "{} ", vreg)?;
|
||||||
}
|
}
|
||||||
write!(f, "}}")?;
|
write!(f, "}}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meet function for analysis value: meet individual values at
|
/// Meet function for analysis value: meet individual values at
|
||||||
@@ -281,13 +359,19 @@ impl CheckerState {
|
|||||||
|
|
||||||
/// Merge this checker state with another at a CFG join-point.
|
/// Merge this checker state with another at a CFG join-point.
|
||||||
fn meet_with(&mut self, other: &CheckerState) {
|
fn meet_with(&mut self, other: &CheckerState) {
|
||||||
if other.top {
|
match (self, other) {
|
||||||
|
(_, CheckerState::Top) => {
|
||||||
// Nothing.
|
// Nothing.
|
||||||
} else if self.top {
|
}
|
||||||
*self = other.clone();
|
(this @ CheckerState::Top, _) => {
|
||||||
} else {
|
*this = other.clone();
|
||||||
self.top = false;
|
}
|
||||||
merge_map(&mut self.allocations, &other.allocations);
|
(
|
||||||
|
CheckerState::Allocations(my_allocations),
|
||||||
|
CheckerState::Allocations(other_allocations),
|
||||||
|
) => {
|
||||||
|
merge_map(my_allocations, other_allocations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,18 +387,20 @@ impl CheckerState {
|
|||||||
return Err(CheckerError::MissingAllocation { inst, op });
|
return Err(CheckerError::MissingAllocation { inst, op });
|
||||||
}
|
}
|
||||||
|
|
||||||
if val.universe {
|
match val {
|
||||||
|
CheckerValue::Universe => {
|
||||||
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
||||||
}
|
}
|
||||||
|
CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
|
||||||
if !val.vregs.contains(&op.vreg()) {
|
|
||||||
return Err(CheckerError::IncorrectValuesInAllocation {
|
return Err(CheckerError::IncorrectValuesInAllocation {
|
||||||
inst,
|
inst,
|
||||||
op,
|
op,
|
||||||
alloc,
|
alloc,
|
||||||
actual: val.vregs.clone(),
|
actual: vregs.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
self.check_constraint(inst, op, alloc, allocs)?;
|
self.check_constraint(inst, op, alloc, allocs)?;
|
||||||
|
|
||||||
@@ -364,7 +450,7 @@ impl CheckerState {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = self.allocations.get(alloc).unwrap_or(&default_val);
|
let val = self.get_value(alloc).unwrap_or(&default_val);
|
||||||
trace!(
|
trace!(
|
||||||
"checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
|
"checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
|
||||||
checkinst,
|
checkinst,
|
||||||
@@ -377,7 +463,7 @@ impl CheckerState {
|
|||||||
}
|
}
|
||||||
&CheckerInst::Safepoint { inst, ref allocs } => {
|
&CheckerInst::Safepoint { inst, ref allocs } => {
|
||||||
for &alloc in allocs {
|
for &alloc in allocs {
|
||||||
let val = self.allocations.get(&alloc).unwrap_or(&default_val);
|
let val = self.get_value(&alloc).unwrap_or(&default_val);
|
||||||
trace!(
|
trace!(
|
||||||
"checker: checkinst {:?}: safepoint slot {}, checker value {:?}",
|
"checker: checkinst {:?}: safepoint slot {}, checker value {:?}",
|
||||||
checkinst,
|
checkinst,
|
||||||
@@ -386,14 +472,15 @@ impl CheckerState {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let reffy = val
|
let reffy = val
|
||||||
.vregs
|
.vregs()
|
||||||
|
.expect("checker value should not be Universe set")
|
||||||
.iter()
|
.iter()
|
||||||
.any(|vreg| checker.reftyped_vregs.contains(vreg));
|
.any(|vreg| checker.reftyped_vregs.contains(vreg));
|
||||||
if !reffy {
|
if !reffy {
|
||||||
return Err(CheckerError::NonRefValuesInStackmap {
|
return Err(CheckerError::NonRefValuesInStackmap {
|
||||||
inst,
|
inst,
|
||||||
alloc,
|
alloc,
|
||||||
vregs: val.vregs.clone(),
|
vregs: val.vregs().unwrap().clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,11 +496,12 @@ impl CheckerState {
|
|||||||
|
|
||||||
/// Update according to instruction.
|
/// Update according to instruction.
|
||||||
fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) {
|
fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) {
|
||||||
self.top = false;
|
self.become_defined();
|
||||||
|
|
||||||
let default_val = Default::default();
|
let default_val = Default::default();
|
||||||
match checkinst {
|
match checkinst {
|
||||||
&CheckerInst::Move { into, from } => {
|
&CheckerInst::Move { into, from } => {
|
||||||
let val = self.allocations.get(&from).unwrap_or(&default_val).clone();
|
let val = self.get_value(&from).unwrap_or(&default_val).clone();
|
||||||
trace!(
|
trace!(
|
||||||
"checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
|
"checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
|
||||||
checkinst,
|
checkinst,
|
||||||
@@ -421,7 +509,7 @@ impl CheckerState {
|
|||||||
into,
|
into,
|
||||||
val
|
val
|
||||||
);
|
);
|
||||||
self.allocations.insert(into, val);
|
self.set_value(into, val);
|
||||||
}
|
}
|
||||||
&CheckerInst::ParallelMove { ref moves } => {
|
&CheckerInst::ParallelMove { ref moves } => {
|
||||||
// First, build map of actions for each vreg in an
|
// First, build map of actions for each vreg in an
|
||||||
@@ -445,20 +533,19 @@ impl CheckerState {
|
|||||||
// first deleting those labels that were updated by
|
// first deleting those labels that were updated by
|
||||||
// this parallel move, then adding back labels
|
// this parallel move, then adding back labels
|
||||||
// redefined by the move.
|
// redefined by the move.
|
||||||
for value in self.allocations.values_mut() {
|
for value in self.get_values_mut() {
|
||||||
if value.universe {
|
if let Some(vregs) = value.vregs_mut() {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut insertions: SmallVec<[VReg; 2]> = smallvec![];
|
let mut insertions: SmallVec<[VReg; 2]> = smallvec![];
|
||||||
for &vreg in &value.vregs {
|
for &vreg in vregs.iter() {
|
||||||
if let Some(additions) = additions.get(&vreg) {
|
if let Some(additions) = additions.get(&vreg) {
|
||||||
insertions.extend(additions.iter().cloned());
|
insertions.extend(additions.iter().cloned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &d in &deletions {
|
for &d in &deletions {
|
||||||
value.vregs.remove(&d);
|
vregs.remove(&d);
|
||||||
|
}
|
||||||
|
vregs.extend(insertions);
|
||||||
}
|
}
|
||||||
value.vregs.extend(insertions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&CheckerInst::Op {
|
&CheckerInst::Op {
|
||||||
@@ -475,31 +562,31 @@ impl CheckerState {
|
|||||||
if op.kind() != OperandKind::Def {
|
if op.kind() != OperandKind::Def {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.allocations
|
self.set_value(*alloc, CheckerValue::from_reg(op.vreg()));
|
||||||
.insert(*alloc, CheckerValue::from_reg(op.vreg()));
|
for (other_alloc, other_value) in self.get_mappings_mut() {
|
||||||
for (other_alloc, other_value) in &mut self.allocations {
|
|
||||||
if *alloc != *other_alloc {
|
if *alloc != *other_alloc {
|
||||||
other_value.remove_vreg(op.vreg());
|
other_value.remove_vreg(op.vreg());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for clobber in clobbers {
|
for clobber in clobbers {
|
||||||
self.allocations.remove(&Allocation::reg(*clobber));
|
self.remove_value(&Allocation::reg(*clobber));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&CheckerInst::Safepoint { ref allocs, .. } => {
|
&CheckerInst::Safepoint { ref allocs, .. } => {
|
||||||
for (alloc, value) in &mut self.allocations {
|
for (alloc, value) in self.get_mappings_mut() {
|
||||||
if alloc.is_reg() {
|
if alloc.is_reg() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !allocs.contains(&alloc) {
|
if !allocs.contains(&alloc) {
|
||||||
// Remove all reftyped vregs as labels.
|
// Remove all reftyped vregs as labels.
|
||||||
let new_vregs = value
|
let new_vregs = value
|
||||||
.vregs
|
.vregs()
|
||||||
|
.unwrap()
|
||||||
.difference(&checker.reftyped_vregs)
|
.difference(&checker.reftyped_vregs)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
value.vregs = new_vregs;
|
*value = CheckerValue::VRegs(new_vregs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -817,12 +904,14 @@ impl<'a, F: Function> Checker<'a, F> {
|
|||||||
|
|
||||||
trace!("=== CHECKER RESULT ===");
|
trace!("=== CHECKER RESULT ===");
|
||||||
fn print_state(state: &CheckerState) {
|
fn print_state(state: &CheckerState) {
|
||||||
|
if let CheckerState::Allocations(allocs) = state {
|
||||||
let mut s = vec![];
|
let mut s = vec![];
|
||||||
for (alloc, state) in &state.allocations {
|
for (alloc, state) in allocs {
|
||||||
s.push(format!("{} := {}", alloc, state));
|
s.push(format!("{} := {}", alloc, state));
|
||||||
}
|
}
|
||||||
trace!(" {{ {} }}", s.join(", "))
|
trace!(" {{ {} }}", s.join(", "))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for vreg in self.f.reftype_vregs() {
|
for vreg in self.f.reftype_vregs() {
|
||||||
trace!(" REF: {}", vreg);
|
trace!(" REF: {}", vreg);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user