Use bforest::Map for representing live ranges.
Get rid of the per-value Vec in the LiveRange data type and use a bforest::Map instead to represent the live-in intervals for non-local live ranges. This has some advantages: - The memory footprint of a local live range is reduced from 40 to 20 bytes, and - Clearing the Liveness data structure is now a constant time operation which doesn't call free(). - The potentially quadratic behavior when computing large live ranges is controlled by the logarithmic B-tree operations.
This commit is contained in:
@@ -157,13 +157,11 @@ impl DomForest {
|
||||
layout,
|
||||
domtree,
|
||||
};
|
||||
let ctx = liveness.context(layout);
|
||||
for node in merged {
|
||||
if let Some(parent) = self.push_node(node, layout, domtree) {
|
||||
// Check if `parent` live range contains `node.def`.
|
||||
let lr = liveness.get(parent).expect(
|
||||
"No live range for parent value",
|
||||
);
|
||||
if lr.overlaps_def(node.def, layout.pp_ebb(node.def), layout) {
|
||||
if liveness[parent].overlaps_def(node.def, layout.pp_ebb(node.def), ctx) {
|
||||
// Interference detected. Get the `(a, b)` order right in the error.
|
||||
return Err(if node.set == 0 {
|
||||
(node.value, parent)
|
||||
@@ -447,7 +445,11 @@ impl<'a> Context<'a> {
|
||||
if self.liveness
|
||||
.get(a)
|
||||
.expect("No live range for interfering value")
|
||||
.reaches_use(pred_inst, pred_ebb, &self.func.layout)
|
||||
.reaches_use(
|
||||
pred_inst,
|
||||
pred_ebb,
|
||||
self.liveness.context(&self.func.layout),
|
||||
)
|
||||
{
|
||||
// Splitting at `pred_inst` wouldn't resolve the interference, so we need to
|
||||
// start over.
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
use cursor::{Cursor, EncCursor};
|
||||
use dominator_tree::DominatorTree;
|
||||
use ir::{Ebb, Inst, Value, Function, ValueLoc, SigRef};
|
||||
use ir::{Ebb, Inst, Value, Function, Layout, ValueLoc, SigRef};
|
||||
use ir::{InstBuilder, AbiParam, ArgumentLoc, ValueDef};
|
||||
use isa::{RegUnit, RegClass, RegInfo, regs_overlap};
|
||||
use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind};
|
||||
@@ -54,7 +54,7 @@ use regalloc::affinity::Affinity;
|
||||
use regalloc::allocatable_set::AllocatableSet;
|
||||
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
|
||||
use regalloc::liveness::Liveness;
|
||||
use regalloc::liverange::LiveRange;
|
||||
use regalloc::liverange::{LiveRange, LiveRangeContext};
|
||||
use regalloc::solver::{Solver, SolverError};
|
||||
use std::mem;
|
||||
|
||||
@@ -542,8 +542,8 @@ impl<'a> Context<'a> {
|
||||
if op.regclass.contains(cur_reg) {
|
||||
// This code runs after calling `solver.inputs_done()` so we must identify
|
||||
// the new variable as killed or live-through.
|
||||
let layout = &self.cur.func.layout;
|
||||
if self.liveness[value].killed_at(inst, layout.pp_ebb(inst), layout) {
|
||||
let ctx = self.liveness.context(&self.cur.func.layout);
|
||||
if self.liveness[value].killed_at(inst, ctx.order.pp_ebb(inst), ctx) {
|
||||
self.solver.add_killed_var(value, op.regclass, cur_reg);
|
||||
} else {
|
||||
self.solver.add_through_var(value, op.regclass, cur_reg);
|
||||
@@ -572,7 +572,7 @@ impl<'a> Context<'a> {
|
||||
//
|
||||
// Values with a global live range that are not live in to `dest` could appear as branch
|
||||
// arguments, so they can't always be un-diverted.
|
||||
self.undivert_regs(|lr, func| lr.is_livein(dest, &func.layout));
|
||||
self.undivert_regs(|lr, ctx| lr.is_livein(dest, ctx));
|
||||
|
||||
// Now handle the EBB arguments.
|
||||
let br_args = self.cur.func.dfg.inst_variable_args(inst);
|
||||
@@ -642,13 +642,13 @@ impl<'a> Context<'a> {
|
||||
/// are reallocated to their global register assignments.
|
||||
fn undivert_regs<Pred>(&mut self, mut pred: Pred)
|
||||
where
|
||||
Pred: FnMut(&LiveRange, &Function) -> bool,
|
||||
Pred: FnMut(&LiveRange, LiveRangeContext<Layout>) -> bool,
|
||||
{
|
||||
for rdiv in self.divert.all() {
|
||||
let lr = self.liveness.get(rdiv.value).expect(
|
||||
"Missing live range for diverted register",
|
||||
);
|
||||
if pred(lr, &self.cur.func) {
|
||||
if pred(lr, self.liveness.context(&self.cur.func.layout)) {
|
||||
if let Affinity::Reg(rci) = lr.affinity {
|
||||
let rc = self.reginfo.rc(rci);
|
||||
// Stack diversions should not be possible here. The only live transiently
|
||||
@@ -882,17 +882,18 @@ impl<'a> Context<'a> {
|
||||
use ir::instructions::BranchInfo::*;
|
||||
|
||||
let inst = self.cur.current_inst().expect("Not on an instruction");
|
||||
let ctx = self.liveness.context(&self.cur.func.layout);
|
||||
match self.cur.func.dfg[inst].analyze_branch(&self.cur.func.dfg.value_lists) {
|
||||
NotABranch => false,
|
||||
SingleDest(ebb, _) => {
|
||||
let lr = &self.liveness[value];
|
||||
lr.is_livein(ebb, &self.cur.func.layout)
|
||||
lr.is_livein(ebb, ctx)
|
||||
}
|
||||
Table(jt) => {
|
||||
let lr = &self.liveness[value];
|
||||
!lr.is_local() &&
|
||||
self.cur.func.jump_tables[jt].entries().any(|(_, ebb)| {
|
||||
lr.is_livein(ebb, &self.cur.func.layout)
|
||||
lr.is_livein(ebb, ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ impl LiveValueTracker {
|
||||
let idom_live_list = self.idom_sets.get(&idom).expect(
|
||||
"No stored live set for dominator",
|
||||
);
|
||||
let ctx = liveness.context(layout);
|
||||
// Get just the values that are live-in to `ebb`.
|
||||
for &value in idom_live_list.as_slice(&self.idom_pool) {
|
||||
let lr = liveness.get(value).expect(
|
||||
@@ -196,7 +197,7 @@ impl LiveValueTracker {
|
||||
);
|
||||
|
||||
// Check if this value is live-in here.
|
||||
if let Some(endpoint) = lr.livein_local_end(ebb, layout) {
|
||||
if let Some(endpoint) = lr.livein_local_end(ebb, ctx) {
|
||||
self.live.push(value, endpoint, lr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ use ir::dfg::ValueDef;
|
||||
use ir::{Function, Value, Inst, Ebb, Layout, ProgramPoint};
|
||||
use isa::{TargetIsa, EncInfo};
|
||||
use regalloc::affinity::Affinity;
|
||||
use regalloc::liverange::LiveRange;
|
||||
use regalloc::liverange::{LiveRange, LiveRangeForest, LiveRangeContext};
|
||||
use std::mem;
|
||||
use std::ops::Index;
|
||||
|
||||
@@ -247,13 +247,14 @@ fn extend_to_use(
|
||||
worklist: &mut Vec<Ebb>,
|
||||
func: &Function,
|
||||
cfg: &ControlFlowGraph,
|
||||
forest: &mut LiveRangeForest,
|
||||
) {
|
||||
// This is our scratch working space, and we'll leave it empty when we return.
|
||||
assert!(worklist.is_empty());
|
||||
|
||||
// Extend the range locally in `ebb`.
|
||||
// If there already was a live interval in that block, we're done.
|
||||
if lr.extend_in_ebb(ebb, to, &func.layout) {
|
||||
if lr.extend_in_ebb(ebb, to, &func.layout, forest) {
|
||||
worklist.push(ebb);
|
||||
}
|
||||
|
||||
@@ -270,7 +271,7 @@ fn extend_to_use(
|
||||
// We've learned that the value needs to be live-in to the `livein` EBB.
|
||||
// Make sure it is also live at all predecessor branches to `livein`.
|
||||
for (pred, branch) in cfg.pred_iter(livein) {
|
||||
if lr.extend_in_ebb(pred, branch, &func.layout) {
|
||||
if lr.extend_in_ebb(pred, branch, &func.layout, forest) {
|
||||
// This predecessor EBB also became live-in. We need to process it later.
|
||||
worklist.push(pred);
|
||||
}
|
||||
@@ -285,6 +286,9 @@ pub struct Liveness {
|
||||
/// The live ranges that have been computed so far.
|
||||
ranges: LiveRangeSet,
|
||||
|
||||
/// Memory pool for the live ranges.
|
||||
forest: LiveRangeForest,
|
||||
|
||||
/// Working space for the `extend_to_use` algorithm.
|
||||
/// This vector is always empty, except for inside that function.
|
||||
/// It lives here to avoid repeated allocation of scratch memory.
|
||||
@@ -302,14 +306,21 @@ impl Liveness {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ranges: LiveRangeSet::new(),
|
||||
forest: LiveRangeForest::new(),
|
||||
worklist: Vec::new(),
|
||||
ebb_params: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a context needed for working with a `LiveRange`.
|
||||
pub fn context<'a>(&'a self, layout: &'a Layout) -> LiveRangeContext<'a, Layout> {
|
||||
LiveRangeContext::new(layout, &self.forest)
|
||||
}
|
||||
|
||||
/// Clear all data structures in this liveness analysis.
|
||||
pub fn clear(&mut self) {
|
||||
self.ranges.clear();
|
||||
self.forest.clear();
|
||||
self.worklist.clear();
|
||||
self.ebb_params.clear();
|
||||
}
|
||||
@@ -359,7 +370,7 @@ impl Liveness {
|
||||
) -> &mut Affinity {
|
||||
debug_assert_eq!(Some(ebb), layout.inst_ebb(user));
|
||||
let lr = self.ranges.get_mut(value).expect("Value has no live range");
|
||||
let livein = lr.extend_in_ebb(ebb, user, layout);
|
||||
let livein = lr.extend_in_ebb(ebb, user, layout, &mut self.forest);
|
||||
assert!(!livein, "{} should already be live in {}", value, ebb);
|
||||
&mut lr.affinity
|
||||
}
|
||||
@@ -416,7 +427,15 @@ impl Liveness {
|
||||
let lr = get_or_create(&mut self.ranges, arg, isa, func, &enc_info);
|
||||
|
||||
// Extend the live range to reach this use.
|
||||
extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg);
|
||||
extend_to_use(
|
||||
lr,
|
||||
ebb,
|
||||
inst,
|
||||
&mut self.worklist,
|
||||
func,
|
||||
cfg,
|
||||
&mut self.forest,
|
||||
);
|
||||
|
||||
// Apply operand constraint, ignoring any variable arguments after the fixed
|
||||
// operands described by `operand_constraints`. Variable arguments are either
|
||||
|
||||
@@ -107,11 +107,11 @@
|
||||
//! of coalescing, so we would need to roll our own.
|
||||
//!
|
||||
|
||||
use bforest;
|
||||
use entity::SparseMapValue;
|
||||
use ir::{Inst, Ebb, Value, Layout, ProgramPoint, ExpandedProgramPoint, ProgramOrder};
|
||||
use regalloc::affinity::Affinity;
|
||||
use std::cmp::Ordering;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Global live range of a single SSA value.
|
||||
///
|
||||
@@ -165,50 +165,52 @@ pub struct GenLiveRange<PO: ProgramOrder> {
|
||||
|
||||
/// Additional live-in intervals sorted in program order.
|
||||
///
|
||||
/// This vector is empty for most values which are only used in one EBB.
|
||||
/// This map is empty for most values which are only used in one EBB.
|
||||
///
|
||||
/// Invariants:
|
||||
/// A map entry `ebb -> inst` means that the live range is live-in to `ebb`, continuing up to
|
||||
/// `inst` which may belong to a later EBB in the program order.
|
||||
///
|
||||
/// - Sorted, disjoint: For all `i < j`: `liveins[i].end < liveins[j].begin`.
|
||||
/// - Not overlapping defining EBB: For all `i`:
|
||||
/// `liveins[i].end < def_begin` or `liveins[i].begin > def_end`.
|
||||
liveins: Vec<Interval>,
|
||||
|
||||
unused: PhantomData<PO>,
|
||||
/// The entries are non-overlapping, and none of them overlap the EBB where the value is
|
||||
/// defined.
|
||||
liveins: bforest::Map<Ebb, Inst, PO>,
|
||||
}
|
||||
|
||||
/// An additional contiguous interval of a global live range.
|
||||
///
|
||||
/// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals
|
||||
/// for contiguous EBBs where all but the last live-in interval covers the whole EBB.
|
||||
///
|
||||
#[derive(Copy, Clone)]
|
||||
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.
|
||||
pub begin: Ebb,
|
||||
|
||||
/// Interval end point.
|
||||
///
|
||||
/// The only interval end point that can be an EBB header is `def_end` above in a dead def
|
||||
/// live range for an unused EBB argument. All other intervals must end at an instruction --
|
||||
/// either the last use in the EBB or the last branch/jump that can reach a use.
|
||||
///
|
||||
/// 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.
|
||||
pub end: Inst,
|
||||
/// Context information needed to query a `LiveRange`.
|
||||
pub struct LiveRangeContext<'a, PO: 'a + ProgramOrder> {
|
||||
/// Ordering of EBBs.
|
||||
pub order: &'a PO,
|
||||
/// Memory pool.
|
||||
pub forest: &'a bforest::MapForest<Ebb, Inst, PO>,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Extend the interval end point to reach `to`, but only if it would make the interval longer.
|
||||
fn extend_to<PO: ProgramOrder>(&mut self, to: Inst, order: &PO) {
|
||||
if order.cmp(to, self.end) == Ordering::Greater {
|
||||
self.end = to;
|
||||
impl<'a, PO: ProgramOrder> LiveRangeContext<'a, PO> {
|
||||
/// Make a new context.
|
||||
pub fn new(
|
||||
order: &'a PO,
|
||||
forest: &'a bforest::MapForest<Ebb, Inst, PO>,
|
||||
) -> LiveRangeContext<'a, PO> {
|
||||
LiveRangeContext { order, forest }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, PO: ProgramOrder> Clone for LiveRangeContext<'a, PO> {
|
||||
fn clone(&self) -> Self {
|
||||
LiveRangeContext {
|
||||
order: self.order,
|
||||
forest: self.forest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, PO: ProgramOrder> Copy for LiveRangeContext<'a, PO> {}
|
||||
|
||||
/// Forest of B-trees used for storing live ranges.
|
||||
pub type LiveRangeForest = bforest::MapForest<Ebb, Inst, Layout>;
|
||||
|
||||
impl<PO: ProgramOrder> bforest::Comparator<Ebb> for PO {
|
||||
fn cmp(&self, a: Ebb, b: Ebb) -> Ordering {
|
||||
self.cmp(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<PO: ProgramOrder> GenLiveRange<PO> {
|
||||
@@ -221,28 +223,10 @@ impl<PO: ProgramOrder> GenLiveRange<PO> {
|
||||
affinity,
|
||||
def_begin: def,
|
||||
def_end: def,
|
||||
liveins: Vec::new(),
|
||||
unused: PhantomData,
|
||||
liveins: bforest::Map::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the live-in interval containing `ebb`, if any.
|
||||
///
|
||||
/// Return `Ok(n)` if `liveins[n]` already contains `ebb`.
|
||||
/// Otherwise, return `Err(n)` with the index where such an interval should be inserted.
|
||||
fn find_ebb_interval(&self, ebb: Ebb, order: &PO) -> Result<usize, usize> {
|
||||
self.liveins
|
||||
.binary_search_by(|intv| order.cmp(intv.begin, ebb))
|
||||
.or_else(|n| {
|
||||
// The interval at `n-1` may cover `ebb`.
|
||||
if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater {
|
||||
Ok(n - 1)
|
||||
} else {
|
||||
Err(n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend the local interval for `ebb` so it reaches `to` which must belong to `ebb`.
|
||||
/// Create a live-in interval if necessary.
|
||||
///
|
||||
@@ -254,7 +238,13 @@ impl<PO: ProgramOrder> GenLiveRange<PO> {
|
||||
///
|
||||
/// The return value can be used to detect if we just learned that the value is live-in to
|
||||
/// `ebb`. This can trigger recursive extensions in `ebb`'s CFG predecessor blocks.
|
||||
pub fn extend_in_ebb(&mut self, ebb: Ebb, to: Inst, order: &PO) -> bool {
|
||||
pub fn extend_in_ebb(
|
||||
&mut self,
|
||||
ebb: Ebb,
|
||||
to: Inst,
|
||||
order: &PO,
|
||||
forest: &mut bforest::MapForest<Ebb, Inst, PO>,
|
||||
) -> bool {
|
||||
// First check if we're extending the def interval.
|
||||
//
|
||||
// We're assuming here that `to` never precedes `def_begin` in the same EBB, but we can't
|
||||
@@ -275,68 +265,60 @@ impl<PO: ProgramOrder> GenLiveRange<PO> {
|
||||
}
|
||||
|
||||
// Now check if we're extending any of the existing live-in intervals.
|
||||
match self.find_ebb_interval(ebb, order) {
|
||||
Ok(n) => {
|
||||
// We have an interval that contains `ebb`, so we can simply extend it.
|
||||
self.liveins[n].extend_to(to, order);
|
||||
let mut c = self.liveins.cursor(forest, order);
|
||||
let first_time_livein;
|
||||
|
||||
// If `to` is the terminator and the value lives in the successor EBB,
|
||||
// coalesce the two intervals.
|
||||
if let Some(next) = self.liveins.get(n + 1).cloned() {
|
||||
if order.is_ebb_gap(to, next.begin) {
|
||||
self.liveins[n].extend_to(next.end, order);
|
||||
self.liveins.remove(n + 1);
|
||||
if let Some(end) = c.goto(ebb) {
|
||||
// There's an interval beginning at `ebb`. See if it extends.
|
||||
first_time_livein = false;
|
||||
if order.cmp(end, to) == Ordering::Less {
|
||||
*c.value_mut().unwrap() = to;
|
||||
} else {
|
||||
return first_time_livein;
|
||||
}
|
||||
} else {
|
||||
// There's no interval beginning at `ebb`, but we could still be live-in at `ebb` with
|
||||
// a coalesced interval that begins before and ends after.
|
||||
if let Some((_, end)) = c.prev() {
|
||||
if order.cmp(end, ebb) == Ordering::Greater {
|
||||
// Yep, the previous interval overlaps `ebb`.
|
||||
first_time_livein = false;
|
||||
if order.cmp(end, to) == Ordering::Less {
|
||||
*c.value_mut().unwrap() = to;
|
||||
} else {
|
||||
return first_time_livein;
|
||||
}
|
||||
} else {
|
||||
first_time_livein = true;
|
||||
// The current interval does not overlap `ebb`, but it may still be possible to
|
||||
// coalesce with it.
|
||||
if order.is_ebb_gap(end, ebb) {
|
||||
*c.value_mut().unwrap() = to;
|
||||
} else {
|
||||
c.insert(ebb, to);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no existing interval before `ebb`.
|
||||
first_time_livein = true;
|
||||
c.insert(ebb, to);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Err(n) => {
|
||||
// Insert a new live-in interval at `n`, or coalesce to predecessor or successor
|
||||
// if possible.
|
||||
// Now `c` to left pointing at an interval that ends in `to`.
|
||||
debug_assert_eq!(c.value(), Some(to));
|
||||
|
||||
// Determine if the new live-in range touches the predecessor or successor range
|
||||
// and can therefore be coalesced to them.
|
||||
let (coalesce_prev, coalesce_next) = {
|
||||
let prev = n.checked_sub(1).and_then(|i| self.liveins.get(i));
|
||||
let next = self.liveins.get(n);
|
||||
|
||||
(
|
||||
prev.map_or(false, |prev| order.is_ebb_gap(prev.end, ebb)),
|
||||
next.map_or(false, |next| order.is_ebb_gap(to, next.begin)),
|
||||
)
|
||||
};
|
||||
|
||||
match (coalesce_prev, coalesce_next) {
|
||||
// Extend predecessor interval to cover new and successor intervals
|
||||
(true, true) => {
|
||||
let end = self.liveins[n].end;
|
||||
self.liveins[n - 1].extend_to(end, order);
|
||||
self.liveins.remove(n);
|
||||
}
|
||||
// Extend predecessor interval to cover new interval
|
||||
(true, false) => {
|
||||
self.liveins[n - 1].extend_to(to, order);
|
||||
}
|
||||
// Extend successor interval to cover new interval
|
||||
(false, true) => {
|
||||
self.liveins[n].begin = ebb;
|
||||
}
|
||||
// Cannot coalesce; insert new interval
|
||||
(false, false) => {
|
||||
self.liveins.insert(
|
||||
n,
|
||||
Interval {
|
||||
begin: ebb,
|
||||
end: to,
|
||||
},
|
||||
);
|
||||
// See if it can be coalesced with the following interval.
|
||||
if let Some((next_ebb, next_end)) = c.next() {
|
||||
if order.is_ebb_gap(to, next_ebb) {
|
||||
// Remove this interval and extend the previous end point to `next_end`.
|
||||
c.remove();
|
||||
c.prev();
|
||||
*c.value_mut().unwrap() = next_end;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
first_time_livein
|
||||
}
|
||||
|
||||
/// Is this the live range of a dead value?
|
||||
@@ -387,59 +369,77 @@ impl<PO: ProgramOrder> GenLiveRange<PO> {
|
||||
/// If the live range is live through all of `ebb`, the terminator of `ebb` is a correct
|
||||
/// answer, but it is also possible that an even later program point is returned. So don't
|
||||
/// depend on the returned `Inst` to belong to `ebb`.
|
||||
pub fn livein_local_end(&self, ebb: Ebb, order: &PO) -> Option<Inst> {
|
||||
self.find_ebb_interval(ebb, order).ok().map(|n| {
|
||||
self.liveins[n].end
|
||||
pub fn livein_local_end(&self, ebb: Ebb, ctx: LiveRangeContext<PO>) -> Option<Inst> {
|
||||
self.liveins
|
||||
.get_or_less(ebb, ctx.forest, ctx.order)
|
||||
.and_then(|(_, inst)| {
|
||||
// We have an entry that ends at `inst`.
|
||||
if ctx.order.cmp(inst, ebb) == Ordering::Greater {
|
||||
Some(inst)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Is this value live-in to `ebb`?
|
||||
///
|
||||
/// An EBB argument is not considered to be live in.
|
||||
pub fn is_livein(&self, ebb: Ebb, order: &PO) -> bool {
|
||||
self.livein_local_end(ebb, order).is_some()
|
||||
pub fn is_livein(&self, ebb: Ebb, ctx: LiveRangeContext<PO>) -> bool {
|
||||
self.livein_local_end(ebb, ctx).is_some()
|
||||
}
|
||||
|
||||
/// Get all the live-in intervals.
|
||||
pub fn liveins(&self) -> &[Interval] {
|
||||
&self.liveins
|
||||
///
|
||||
/// Note that the intervals are stored in a compressed form so each entry may span multiple
|
||||
/// EBBs where the value is live in.
|
||||
pub fn liveins<'a>(
|
||||
&'a self,
|
||||
ctx: LiveRangeContext<'a, PO>,
|
||||
) -> bforest::MapIter<'a, Ebb, Inst, PO> {
|
||||
self.liveins.iter(ctx.forest)
|
||||
}
|
||||
|
||||
/// Check if this live range overlaps a definition in `ebb`.
|
||||
pub fn overlaps_def(&self, def: ExpandedProgramPoint, ebb: Ebb, order: &PO) -> bool {
|
||||
pub fn overlaps_def(
|
||||
&self,
|
||||
def: ExpandedProgramPoint,
|
||||
ebb: Ebb,
|
||||
ctx: LiveRangeContext<PO>,
|
||||
) -> bool {
|
||||
// Check for an overlap with the local range.
|
||||
if order.cmp(def, self.def_begin) != Ordering::Less &&
|
||||
order.cmp(def, self.def_end) == Ordering::Less
|
||||
if ctx.order.cmp(def, self.def_begin) != Ordering::Less &&
|
||||
ctx.order.cmp(def, self.def_end) == Ordering::Less
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for an overlap with a live-in range.
|
||||
match self.livein_local_end(ebb, order) {
|
||||
Some(inst) => order.cmp(def, inst) == Ordering::Less,
|
||||
match self.livein_local_end(ebb, ctx) {
|
||||
Some(inst) => ctx.order.cmp(def, inst) == Ordering::Less,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this live range reaches a use at `user` in `ebb`.
|
||||
pub fn reaches_use(&self, user: Inst, ebb: Ebb, order: &PO) -> bool {
|
||||
pub fn reaches_use(&self, user: Inst, ebb: Ebb, ctx: LiveRangeContext<PO>) -> bool {
|
||||
// Check for an overlap with the local range.
|
||||
if order.cmp(user, self.def_begin) == Ordering::Greater &&
|
||||
order.cmp(user, self.def_end) != Ordering::Greater
|
||||
if ctx.order.cmp(user, self.def_begin) == Ordering::Greater &&
|
||||
ctx.order.cmp(user, self.def_end) != Ordering::Greater
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for an overlap with a live-in range.
|
||||
match self.livein_local_end(ebb, order) {
|
||||
Some(inst) => order.cmp(user, inst) != Ordering::Greater,
|
||||
match self.livein_local_end(ebb, ctx) {
|
||||
Some(inst) => ctx.order.cmp(user, inst) != Ordering::Greater,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this live range is killed at `user` in `ebb`.
|
||||
pub fn killed_at(&self, user: Inst, ebb: Ebb, order: &PO) -> bool {
|
||||
self.def_local_end() == user.into() || self.livein_local_end(ebb, order) == Some(user)
|
||||
pub fn killed_at(&self, user: Inst, ebb: Ebb, ctx: LiveRangeContext<PO>) -> bool {
|
||||
self.def_local_end() == user.into() || self.livein_local_end(ebb, ctx) == Some(user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +452,8 @@ impl<PO: ProgramOrder> SparseMapValue<Value> for GenLiveRange<PO> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GenLiveRange;
|
||||
use super::{GenLiveRange, LiveRangeContext};
|
||||
use bforest;
|
||||
use ir::{Inst, Ebb, Value};
|
||||
use entity::EntityRef;
|
||||
use ir::{ProgramOrder, ExpandedProgramPoint};
|
||||
@@ -503,7 +504,11 @@ mod tests {
|
||||
}
|
||||
|
||||
// Validate the live range invariants.
|
||||
fn validate(&self, lr: &GenLiveRange<ProgOrder>) {
|
||||
fn validate(
|
||||
&self,
|
||||
lr: &GenLiveRange<ProgOrder>,
|
||||
forest: &bforest::MapForest<Ebb, Inst, ProgOrder>,
|
||||
) {
|
||||
// The def interval must cover a single EBB.
|
||||
let def_ebb = self.pp_ebb(lr.def_begin);
|
||||
assert_eq!(def_ebb, self.pp_ebb(lr.def_end));
|
||||
@@ -519,20 +524,20 @@ mod tests {
|
||||
|
||||
// Check the live-in intervals.
|
||||
let mut prev_end = None;
|
||||
for li in &lr.liveins {
|
||||
assert_eq!(self.cmp(li.begin, li.end), Ordering::Less);
|
||||
for (begin, end) in lr.liveins.iter(forest) {
|
||||
assert_eq!(self.cmp(begin, end), Ordering::Less);
|
||||
if let Some(e) = prev_end {
|
||||
assert_eq!(self.cmp(e, li.begin), Ordering::Less);
|
||||
assert_eq!(self.cmp(e, begin), Ordering::Less);
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.cmp(lr.def_end, li.begin) == Ordering::Less ||
|
||||
self.cmp(lr.def_begin, li.end) == Ordering::Greater,
|
||||
self.cmp(lr.def_end, begin) == Ordering::Less ||
|
||||
self.cmp(lr.def_begin, end) == Ordering::Greater,
|
||||
"Interval can't overlap the def EBB"
|
||||
);
|
||||
|
||||
// Save for next round.
|
||||
prev_end = Some(li.end);
|
||||
prev_end = Some(end);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -547,12 +552,14 @@ mod tests {
|
||||
let i1 = Inst::new(1);
|
||||
let e2 = Ebb::new(2);
|
||||
let lr = GenLiveRange::new(v0, i1.into(), Default::default());
|
||||
let forest = &bforest::MapForest::new();
|
||||
let ctx = LiveRangeContext::new(PO, forest);
|
||||
assert!(lr.is_dead());
|
||||
assert!(lr.is_local());
|
||||
assert_eq!(lr.def(), i1.into());
|
||||
assert_eq!(lr.def_local_end(), i1.into());
|
||||
assert_eq!(lr.livein_local_end(e2, PO), None);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.livein_local_end(e2, ctx), None);
|
||||
PO.validate(&lr, ctx.forest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -560,13 +567,15 @@ mod tests {
|
||||
let v0 = Value::new(0);
|
||||
let e2 = Ebb::new(2);
|
||||
let lr = GenLiveRange::new(v0, e2.into(), Default::default());
|
||||
let forest = &bforest::MapForest::new();
|
||||
let ctx = LiveRangeContext::new(PO, forest);
|
||||
assert!(lr.is_dead());
|
||||
assert!(lr.is_local());
|
||||
assert_eq!(lr.def(), e2.into());
|
||||
assert_eq!(lr.def_local_end(), e2.into());
|
||||
// The def interval of an EBB argument does not count as live-in.
|
||||
assert_eq!(lr.livein_local_end(e2, PO), None);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.livein_local_end(e2, ctx), None);
|
||||
PO.validate(&lr, ctx.forest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -577,17 +586,18 @@ mod tests {
|
||||
let i12 = Inst::new(12);
|
||||
let i13 = Inst::new(13);
|
||||
let mut lr = GenLiveRange::new(v0, i11.into(), Default::default());
|
||||
let forest = &mut bforest::MapForest::new();
|
||||
|
||||
assert_eq!(lr.extend_in_ebb(e10, i13, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i13, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert!(!lr.is_dead());
|
||||
assert!(lr.is_local());
|
||||
assert_eq!(lr.def(), i11.into());
|
||||
assert_eq!(lr.def_local_end(), i13.into());
|
||||
|
||||
// Extending to an already covered inst should not change anything.
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert_eq!(lr.def(), i11.into());
|
||||
assert_eq!(lr.def_local_end(), i13.into());
|
||||
}
|
||||
@@ -600,25 +610,26 @@ mod tests {
|
||||
let i12 = Inst::new(12);
|
||||
let i13 = Inst::new(13);
|
||||
let mut lr = GenLiveRange::new(v0, e10.into(), Default::default());
|
||||
let forest = &mut bforest::MapForest::new();
|
||||
|
||||
// Extending a dead EBB argument in its own block should not indicate that a live-in
|
||||
// interval was created.
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert!(!lr.is_dead());
|
||||
assert!(lr.is_local());
|
||||
assert_eq!(lr.def(), e10.into());
|
||||
assert_eq!(lr.def_local_end(), i12.into());
|
||||
|
||||
// Extending to an already covered inst should not change anything.
|
||||
assert_eq!(lr.extend_in_ebb(e10, i11, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i11, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert_eq!(lr.def(), e10.into());
|
||||
assert_eq!(lr.def_local_end(), i12.into());
|
||||
|
||||
// Extending further.
|
||||
assert_eq!(lr.extend_in_ebb(e10, i13, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i13, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert_eq!(lr.def(), e10.into());
|
||||
assert_eq!(lr.def_local_end(), i13.into());
|
||||
}
|
||||
@@ -634,22 +645,32 @@ mod tests {
|
||||
let i22 = Inst::new(22);
|
||||
let i23 = Inst::new(23);
|
||||
let mut lr = GenLiveRange::new(v0, i11.into(), Default::default());
|
||||
let forest = &mut bforest::MapForest::new();
|
||||
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO), false);
|
||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO, forest), false);
|
||||
|
||||
// Adding a live-in interval.
|
||||
assert_eq!(lr.extend_in_ebb(e20, i22, PO), true);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.livein_local_end(e20, PO), Some(i22));
|
||||
assert_eq!(lr.extend_in_ebb(e20, i22, PO, forest), true);
|
||||
PO.validate(&lr, forest);
|
||||
assert_eq!(
|
||||
lr.livein_local_end(e20, LiveRangeContext::new(PO, forest)),
|
||||
Some(i22)
|
||||
);
|
||||
|
||||
// Non-extending the live-in.
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO), false);
|
||||
assert_eq!(lr.livein_local_end(e20, PO), Some(i22));
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO, forest), false);
|
||||
assert_eq!(
|
||||
lr.livein_local_end(e20, LiveRangeContext::new(PO, forest)),
|
||||
Some(i22)
|
||||
);
|
||||
|
||||
// Extending the existing live-in.
|
||||
assert_eq!(lr.extend_in_ebb(e20, i23, PO), false);
|
||||
PO.validate(&lr);
|
||||
assert_eq!(lr.livein_local_end(e20, PO), Some(i23));
|
||||
assert_eq!(lr.extend_in_ebb(e20, i23, PO, forest), false);
|
||||
PO.validate(&lr, forest);
|
||||
assert_eq!(
|
||||
lr.livein_local_end(e20, LiveRangeContext::new(PO, forest)),
|
||||
Some(i23)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -663,35 +684,54 @@ mod tests {
|
||||
let e40 = Ebb::new(40);
|
||||
let i41 = Inst::new(41);
|
||||
let mut lr = GenLiveRange::new(v0, i11.into(), Default::default());
|
||||
let forest = &mut bforest::MapForest::new();
|
||||
|
||||
assert_eq!(lr.extend_in_ebb(e30, i31, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 1);
|
||||
assert_eq!(lr.extend_in_ebb(e30, i31, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e30, i31)]
|
||||
);
|
||||
|
||||
// Coalesce to previous
|
||||
assert_eq!(lr.extend_in_ebb(e40, i41, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 1);
|
||||
assert_eq!(lr.liveins[0].begin, e30);
|
||||
assert_eq!(lr.liveins[0].end, i41);
|
||||
assert_eq!(lr.extend_in_ebb(e40, i41, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e30, i41)]
|
||||
);
|
||||
|
||||
// Coalesce to next
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 1);
|
||||
assert_eq!(lr.liveins[0].begin, e20);
|
||||
assert_eq!(lr.liveins[0].end, i41);
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e20, i41)]
|
||||
);
|
||||
|
||||
let mut lr = GenLiveRange::new(v0, i11.into(), Default::default());
|
||||
|
||||
assert_eq!(lr.extend_in_ebb(e40, i41, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 1);
|
||||
assert_eq!(lr.extend_in_ebb(e40, i41, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e40, i41)]
|
||||
);
|
||||
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 2);
|
||||
assert_eq!(lr.extend_in_ebb(e20, i21, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e20, i21), (e40, i41)]
|
||||
);
|
||||
|
||||
// Coalesce to previous and next
|
||||
assert_eq!(lr.extend_in_ebb(e30, i31, PO), true);
|
||||
assert_eq!(lr.liveins.len(), 1);
|
||||
assert_eq!(lr.liveins[0].begin, e20);
|
||||
assert_eq!(lr.liveins[0].end, i41);
|
||||
assert_eq!(lr.extend_in_ebb(e30, i31, PO, forest), true);
|
||||
assert_eq!(
|
||||
lr.liveins(LiveRangeContext::new(PO, forest))
|
||||
.collect::<Vec<_>>(),
|
||||
[(e20, i41)]
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Add more tests that exercise the binary search algorithm.
|
||||
|
||||
@@ -300,16 +300,17 @@ impl<'a> Context<'a> {
|
||||
for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
|
||||
let mut reguse = RegUse::new(arg, idx, op.regclass.into());
|
||||
let lr = &self.liveness[arg];
|
||||
let ctx = self.liveness.context(&self.cur.func.layout);
|
||||
match op.kind {
|
||||
ConstraintKind::Stack => continue,
|
||||
ConstraintKind::FixedReg(_) => reguse.fixed = true,
|
||||
ConstraintKind::Tied(_) => {
|
||||
// A tied operand must kill the used value.
|
||||
reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout);
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
}
|
||||
ConstraintKind::FixedTied(_) => {
|
||||
reguse.fixed = true;
|
||||
reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout);
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
}
|
||||
ConstraintKind::Reg => {}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ impl<'a> CssaVerifier<'a> {
|
||||
|
||||
// Knowing that values are in RPO order, we can check for interference this
|
||||
// way.
|
||||
if self.liveness[prev_val].overlaps_def(def, def_ebb, &self.func.layout) {
|
||||
let ctx = self.liveness.context(&self.func.layout);
|
||||
if self.liveness[prev_val].overlaps_def(def, def_ebb, ctx) {
|
||||
return err!(val, "Value def in {} interferes with {}", vreg, prev_val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,18 +129,18 @@ impl<'a> LivenessVerifier<'a> {
|
||||
|
||||
/// Is `lr` live at the use `inst`?
|
||||
fn live_at_use(&self, lr: &LiveRange, inst: Inst) -> bool {
|
||||
let l = &self.func.layout;
|
||||
let ctx = self.liveness.context(&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
|
||||
if ctx.order.cmp(lr.def(), inst) == Ordering::Less &&
|
||||
ctx.order.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,
|
||||
match lr.livein_local_end(ctx.order.inst_ebb(inst).unwrap(), ctx) {
|
||||
Some(end) => ctx.order.cmp(inst, end) != Ordering::Greater,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@@ -205,12 +205,11 @@ impl<'a> LivenessVerifier<'a> {
|
||||
}
|
||||
|
||||
// Now check the live-in intervals against the CFG.
|
||||
for &livein in lr.liveins() {
|
||||
let mut ebb = livein.begin;
|
||||
for (mut ebb, end) in lr.liveins(self.liveness.context(l)) {
|
||||
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) {
|
||||
let end_ebb = match l.inst_ebb(end) {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return err!(
|
||||
@@ -218,7 +217,7 @@ impl<'a> LivenessVerifier<'a> {
|
||||
"{} livein for {} ends at {} which is not in the layout",
|
||||
val,
|
||||
ebb,
|
||||
livein.end
|
||||
end
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -282,7 +282,7 @@ impl<'a> LocationVerifier<'a> {
|
||||
SingleDest(ebb, _) => {
|
||||
for d in divert.all() {
|
||||
let lr = &liveness[d.value];
|
||||
if lr.is_livein(ebb, &self.func.layout) {
|
||||
if lr.is_livein(ebb, liveness.context(&self.func.layout)) {
|
||||
return err!(
|
||||
inst,
|
||||
"{} is diverted to {} and live in to {}",
|
||||
@@ -297,7 +297,7 @@ impl<'a> LocationVerifier<'a> {
|
||||
for d in divert.all() {
|
||||
let lr = &liveness[d.value];
|
||||
for (_, ebb) in self.func.jump_tables[jt].entries() {
|
||||
if lr.is_livein(ebb, &self.func.layout) {
|
||||
if lr.is_livein(ebb, liveness.context(&self.func.layout)) {
|
||||
return err!(
|
||||
inst,
|
||||
"{} is diverted to {} and live in to {}",
|
||||
|
||||
Reference in New Issue
Block a user