Add a liveness verifier.
The liveness verifier will check that the live ranges are consistent with the function. It runs as part of the register allocation pipeline when enable_verifier is set. The initial implementation checks the live ranges, but not the ISA-specific constraints and affinities.
This commit is contained in:
@@ -40,7 +40,7 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin
|
|||||||
AnyEntity::Inst(inst) => {
|
AnyEntity::Inst(inst) => {
|
||||||
write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap()
|
write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap()
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => msg.push('\n'),
|
||||||
}
|
}
|
||||||
write_function(&mut msg, func, None).unwrap();
|
write_function(&mut msg, func, None).unwrap();
|
||||||
msg
|
msg
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl Context {
|
|||||||
/// Run the register allocator.
|
/// Run the register allocator.
|
||||||
pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult {
|
pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||||
self.regalloc
|
self.regalloc
|
||||||
.run(isa, &mut self.func, &self.cfg, &self.domtree);
|
.run(isa, &mut self.func, &self.cfg, &self.domtree)?;
|
||||||
self.verify_if(isa)
|
self.verify_if(isa)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use isa::TargetIsa;
|
|||||||
use regalloc::coloring::Coloring;
|
use regalloc::coloring::Coloring;
|
||||||
use regalloc::live_value_tracker::LiveValueTracker;
|
use regalloc::live_value_tracker::LiveValueTracker;
|
||||||
use regalloc::liveness::Liveness;
|
use regalloc::liveness::Liveness;
|
||||||
|
use result::CtonResult;
|
||||||
|
use verifier::verify_liveness;
|
||||||
|
|
||||||
/// Persistent memory allocations for register allocation.
|
/// Persistent memory allocations for register allocation.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
@@ -40,7 +42,8 @@ impl Context {
|
|||||||
isa: &TargetIsa,
|
isa: &TargetIsa,
|
||||||
func: &mut Function,
|
func: &mut Function,
|
||||||
cfg: &ControlFlowGraph,
|
cfg: &ControlFlowGraph,
|
||||||
domtree: &DominatorTree) {
|
domtree: &DominatorTree)
|
||||||
|
-> CtonResult {
|
||||||
// `Liveness` and `Coloring` are self-clearing.
|
// `Liveness` and `Coloring` are self-clearing.
|
||||||
// Tracker state (dominator live sets) is actually reused between the spilling and coloring
|
// Tracker state (dominator live sets) is actually reused between the spilling and coloring
|
||||||
// phases.
|
// phases.
|
||||||
@@ -49,10 +52,16 @@ impl Context {
|
|||||||
// First pass: Liveness analysis.
|
// First pass: Liveness analysis.
|
||||||
self.liveness.compute(isa, func, cfg);
|
self.liveness.compute(isa, func, cfg);
|
||||||
|
|
||||||
|
if isa.flags().enable_verifier() {
|
||||||
|
verify_liveness(isa, func, cfg, &self.liveness)?;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Second pass: Spilling.
|
// TODO: Second pass: Spilling.
|
||||||
|
|
||||||
// Third pass: Reload and coloring.
|
// Third pass: Reload and coloring.
|
||||||
self.coloring
|
self.coloring
|
||||||
.run(isa, func, domtree, &mut self.liveness, &mut self.tracker);
|
.run(isa, func, domtree, &mut self.liveness, &mut self.tracker);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,12 +174,12 @@ pub struct LiveRange {
|
|||||||
/// for contiguous EBBs where all but the last live-in interval covers the whole EBB.
|
/// for contiguous EBBs where all but the last live-in interval covers the whole EBB.
|
||||||
///
|
///
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct Interval {
|
pub struct Interval {
|
||||||
/// Interval starting point.
|
/// Interval starting point.
|
||||||
///
|
///
|
||||||
/// Since this interval does not represent the def of the value, it must begin at an EBB header
|
/// Since this interval does not represent the def of the value, it must begin at an EBB header
|
||||||
/// where the value is live-in.
|
/// where the value is live-in.
|
||||||
begin: Ebb,
|
pub begin: Ebb,
|
||||||
|
|
||||||
/// Interval end point.
|
/// Interval end point.
|
||||||
///
|
///
|
||||||
@@ -190,7 +190,7 @@ struct Interval {
|
|||||||
/// When this represents multiple contiguous live-in intervals, this is the end point of the
|
/// When this represents multiple contiguous live-in intervals, this is the end point of the
|
||||||
/// last interval. The other intervals end at the terminator instructions of their respective
|
/// last interval. The other intervals end at the terminator instructions of their respective
|
||||||
/// EBB.
|
/// EBB.
|
||||||
end: Inst,
|
pub end: Inst,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interval {
|
impl Interval {
|
||||||
@@ -368,6 +368,11 @@ impl LiveRange {
|
|||||||
.ok()
|
.ok()
|
||||||
.map(|n| self.liveins[n].end)
|
.map(|n| self.liveins[n].end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all the live-in intervals.
|
||||||
|
pub fn liveins(&self) -> &[Interval] {
|
||||||
|
&self.liveins
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values.
|
/// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values.
|
||||||
|
|||||||
179
lib/cretonne/src/verifier/liveness.rs
Normal file
179
lib/cretonne/src/verifier/liveness.rs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
//! Liveness verifier.
|
||||||
|
|
||||||
|
use flowgraph::ControlFlowGraph;
|
||||||
|
use ir::{Function, Inst, Value, ProgramOrder, ProgramPoint, ExpandedProgramPoint};
|
||||||
|
use ir::entities::AnyEntity;
|
||||||
|
use isa::TargetIsa;
|
||||||
|
use regalloc::liveness::Liveness;
|
||||||
|
use regalloc::liverange::LiveRange;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use verifier::Result;
|
||||||
|
|
||||||
|
/// Verify liveness information for `func`.
|
||||||
|
///
|
||||||
|
/// The provided control flow graph is assumed to be sound.
|
||||||
|
///
|
||||||
|
/// - All values in the program must have a live range.
|
||||||
|
/// - The live range def point must match where the value is defined.
|
||||||
|
/// - The live range must reach all uses.
|
||||||
|
/// - When a live range is live-in to an EBB, it must be live at all the predecessors.
|
||||||
|
/// - The live range affinity must be compatible with encoding constraints.
|
||||||
|
///
|
||||||
|
/// We don't verify that live ranges are minimal. This would require recomputing live ranges for
|
||||||
|
/// all values.
|
||||||
|
pub fn verify_liveness(_isa: &TargetIsa,
|
||||||
|
func: &Function,
|
||||||
|
cfg: &ControlFlowGraph,
|
||||||
|
liveness: &Liveness)
|
||||||
|
-> Result {
|
||||||
|
let verifier = LivenessVerifier {
|
||||||
|
func: func,
|
||||||
|
cfg: cfg,
|
||||||
|
liveness: liveness,
|
||||||
|
};
|
||||||
|
verifier.check_ebbs()?;
|
||||||
|
verifier.check_insts()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LivenessVerifier<'a> {
|
||||||
|
func: &'a Function,
|
||||||
|
cfg: &'a ControlFlowGraph,
|
||||||
|
liveness: &'a Liveness,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LivenessVerifier<'a> {
|
||||||
|
/// Check all EBB arguments.
|
||||||
|
fn check_ebbs(&self) -> Result {
|
||||||
|
for ebb in self.func.layout.ebbs() {
|
||||||
|
for &val in self.func.dfg.ebb_args(ebb) {
|
||||||
|
let lr = match self.liveness.get(val) {
|
||||||
|
Some(lr) => lr,
|
||||||
|
None => return err!(ebb, "EBB arg {} has no live range", val),
|
||||||
|
};
|
||||||
|
self.check_lr(ebb.into(), val, lr)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check all instructions.
|
||||||
|
fn check_insts(&self) -> Result {
|
||||||
|
for ebb in self.func.layout.ebbs() {
|
||||||
|
for inst in self.func.layout.ebb_insts(ebb) {
|
||||||
|
// Check the defs.
|
||||||
|
for &val in self.func.dfg.inst_results(inst) {
|
||||||
|
let lr = match self.liveness.get(val) {
|
||||||
|
Some(lr) => lr,
|
||||||
|
None => return err!(inst, "{} has no live range", val),
|
||||||
|
};
|
||||||
|
self.check_lr(inst.into(), val, lr)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the uses.
|
||||||
|
for &val in self.func.dfg.inst_args(inst) {
|
||||||
|
let lr = match self.liveness.get(val) {
|
||||||
|
Some(lr) => lr,
|
||||||
|
None => return err!(inst, "{} has no live range", val),
|
||||||
|
};
|
||||||
|
if !self.live_at_use(lr, inst) {
|
||||||
|
return err!(inst, "{} is not live at this use", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is `lr` live at the use `inst`?
|
||||||
|
fn live_at_use(&self, lr: &LiveRange, inst: Inst) -> bool {
|
||||||
|
let l = &self.func.layout;
|
||||||
|
|
||||||
|
// Check if `inst` is in the def range, not including the def itself.
|
||||||
|
if l.cmp(lr.def(), inst) == Ordering::Less &&
|
||||||
|
l.cmp(inst, lr.def_local_end()) != Ordering::Greater {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise see if `inst` is in one of the live-in ranges.
|
||||||
|
match lr.livein_local_end(l.inst_ebb(inst).unwrap(), l) {
|
||||||
|
Some(end) => l.cmp(inst, end) != Ordering::Greater,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the integrity of the live range `lr`.
|
||||||
|
fn check_lr(&self, def: ProgramPoint, val: Value, lr: &LiveRange) -> Result {
|
||||||
|
let l = &self.func.layout;
|
||||||
|
|
||||||
|
let loc: AnyEntity = match def.into() {
|
||||||
|
ExpandedProgramPoint::Ebb(e) => e.into(),
|
||||||
|
ExpandedProgramPoint::Inst(i) => i.into(),
|
||||||
|
};
|
||||||
|
if lr.def() != def {
|
||||||
|
return err!(loc, "Wrong live range def ({}) for {}", lr.def(), val);
|
||||||
|
}
|
||||||
|
if lr.is_dead() {
|
||||||
|
if !lr.is_local() {
|
||||||
|
return err!(loc, "Dead live range {} should be local", val);
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let def_ebb = match def.into() {
|
||||||
|
ExpandedProgramPoint::Ebb(e) => e,
|
||||||
|
ExpandedProgramPoint::Inst(i) => l.inst_ebb(i).unwrap(),
|
||||||
|
};
|
||||||
|
match lr.def_local_end().into() {
|
||||||
|
ExpandedProgramPoint::Ebb(e) => {
|
||||||
|
return err!(loc, "Def local range for {} can't end at {}", val, e)
|
||||||
|
}
|
||||||
|
ExpandedProgramPoint::Inst(i) => {
|
||||||
|
if self.func.layout.inst_ebb(i) != Some(def_ebb) {
|
||||||
|
return err!(loc, "Def local end for {} in wrong ebb", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check the live-in intervals against the CFG.
|
||||||
|
for &livein in lr.liveins() {
|
||||||
|
let mut ebb = livein.begin;
|
||||||
|
if !l.is_ebb_inserted(ebb) {
|
||||||
|
return err!(loc, "{} livein at {} which is not in the layout", val, ebb);
|
||||||
|
}
|
||||||
|
let end_ebb = match l.inst_ebb(livein.end) {
|
||||||
|
Some(e) => e,
|
||||||
|
None => {
|
||||||
|
return err!(loc,
|
||||||
|
"{} livein for {} ends at {} which is not in the layout",
|
||||||
|
val,
|
||||||
|
ebb,
|
||||||
|
livein.end)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check all the EBBs in the interval independently.
|
||||||
|
loop {
|
||||||
|
// If `val` is live-in at `ebb`, it must be live at all the predecessors.
|
||||||
|
for &(_, pred) in self.cfg.get_predecessors(ebb) {
|
||||||
|
if !self.live_at_use(lr, pred) {
|
||||||
|
return err!(pred,
|
||||||
|
"{} is live in to {} but not live at predecessor",
|
||||||
|
val,
|
||||||
|
ebb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ebb == end_ebb {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ebb = match l.next_ebb(ebb) {
|
||||||
|
Some(e) => e,
|
||||||
|
None => return err!(loc, "end of {} livein ({}) never reached", val, end_ebb),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,27 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
use std::result;
|
use std::result;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
pub use self::liveness::verify_liveness;
|
||||||
|
|
||||||
|
// Create an `Err` variant of `Result<X>` from a location and `format!` arguments.
|
||||||
|
macro_rules! err {
|
||||||
|
( $loc:expr, $msg:expr ) => {
|
||||||
|
Err(::verifier::Error {
|
||||||
|
location: $loc.into(),
|
||||||
|
message: String::from($msg),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => {
|
||||||
|
Err(::verifier::Error {
|
||||||
|
location: $loc.into(),
|
||||||
|
message: format!( $fmt, $( $arg ),+ ),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod liveness;
|
||||||
|
|
||||||
/// A verifier error.
|
/// A verifier error.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
@@ -88,23 +109,6 @@ impl std_error::Error for Error {
|
|||||||
/// Verifier result.
|
/// Verifier result.
|
||||||
pub type Result = result::Result<(), Error>;
|
pub type Result = result::Result<(), Error>;
|
||||||
|
|
||||||
// Create an `Err` variant of `Result<X>` from a location and `format!` arguments.
|
|
||||||
macro_rules! err {
|
|
||||||
( $loc:expr, $msg:expr ) => {
|
|
||||||
Err(Error {
|
|
||||||
location: $loc.into(),
|
|
||||||
message: String::from($msg),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => {
|
|
||||||
Err(Error {
|
|
||||||
location: $loc.into(),
|
|
||||||
message: format!( $fmt, $( $arg ),+ ),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify `func`.
|
/// Verify `func`.
|
||||||
pub fn verify_function(func: &Function) -> Result {
|
pub fn verify_function(func: &Function) -> Result {
|
||||||
Verifier::new(func).run()
|
Verifier::new(func).run()
|
||||||
|
|||||||
Reference in New Issue
Block a user