From c5da572ebbd0b88196ea790454c40fd86237bf16 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 13:35:20 -0700 Subject: [PATCH] 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. --- cranelift/src/utils.rs | 2 +- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/regalloc/context.rs | 11 +- lib/cretonne/src/regalloc/liverange.rs | 11 +- lib/cretonne/src/verifier/liveness.rs | 179 +++++++++++++++++++++++++ lib/cretonne/src/verifier/mod.rs | 38 +++--- 6 files changed, 220 insertions(+), 23 deletions(-) create mode 100644 lib/cretonne/src/verifier/liveness.rs diff --git a/cranelift/src/utils.rs b/cranelift/src/utils.rs index 3519c55627..1bcba4891a 100644 --- a/cranelift/src/utils.rs +++ b/cranelift/src/utils.rs @@ -40,7 +40,7 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin AnyEntity::Inst(inst) => { write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap() } - _ => {} + _ => msg.push('\n'), } write_function(&mut msg, func, None).unwrap(); msg diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 71dfa4539b..f8a207c6fc 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -81,7 +81,7 @@ impl Context { /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc - .run(isa, &mut self.func, &self.cfg, &self.domtree); + .run(isa, &mut self.func, &self.cfg, &self.domtree)?; self.verify_if(isa) } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 2c59ab9797..5d91a258fa 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -11,6 +11,8 @@ use isa::TargetIsa; use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; +use result::CtonResult; +use verifier::verify_liveness; /// Persistent memory allocations for register allocation. pub struct Context { @@ -40,7 +42,8 @@ impl Context { isa: &TargetIsa, func: &mut Function, cfg: &ControlFlowGraph, - domtree: &DominatorTree) { + domtree: &DominatorTree) + -> CtonResult { // `Liveness` and `Coloring` are self-clearing. // Tracker state (dominator live sets) is actually reused between the spilling and coloring // phases. @@ -49,10 +52,16 @@ impl Context { // First pass: Liveness analysis. self.liveness.compute(isa, func, cfg); + if isa.flags().enable_verifier() { + verify_liveness(isa, func, cfg, &self.liveness)?; + } + // TODO: Second pass: Spilling. // Third pass: Reload and coloring. self.coloring .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + + Ok(()) } } diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 94c16cb9ae..0594af19ed 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -174,12 +174,12 @@ pub struct LiveRange { /// for contiguous EBBs where all but the last live-in interval covers the whole EBB. /// #[derive(Copy, Clone)] -struct Interval { +pub struct Interval { /// Interval starting point. /// /// Since this interval does not represent the def of the value, it must begin at an EBB header /// where the value is live-in. - begin: Ebb, + pub begin: Ebb, /// Interval end point. /// @@ -190,7 +190,7 @@ struct Interval { /// 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 /// EBB. - end: Inst, + pub end: Inst, } impl Interval { @@ -368,6 +368,11 @@ impl LiveRange { .ok() .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. diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs new file mode 100644 index 0000000000..d80f18a8b3 --- /dev/null +++ b/lib/cretonne/src/verifier/liveness.rs @@ -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(()) + } +} diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index b6f8ba8710..f2e8a7fc50 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -64,6 +64,27 @@ use std::fmt::{self, Display, Formatter}; use std::result; use std::collections::BTreeSet; +pub use self::liveness::verify_liveness; + +// Create an `Err` variant of `Result` 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. #[derive(Debug, PartialEq, Eq)] pub struct Error { @@ -88,23 +109,6 @@ impl std_error::Error for Error { /// Verifier result. pub type Result = result::Result<(), Error>; -// Create an `Err` variant of `Result` 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`. pub fn verify_function(func: &Function) -> Result { Verifier::new(func).run()