Compute register affinities during liveness analysis.
Each live range has an affinity hint containing the preferred register class (or stack slot). Compute the affinity by merging the constraints of the def and all uses.
This commit is contained in:
@@ -175,39 +175,87 @@
|
|||||||
//!
|
//!
|
||||||
//! There is some room for improvement.
|
//! There is some room for improvement.
|
||||||
|
|
||||||
use regalloc::liverange::LiveRange;
|
|
||||||
use ir::{Function, Value, Inst, Ebb, ProgramPoint};
|
|
||||||
use ir::dfg::{DataFlowGraph, ValueDef};
|
|
||||||
use cfg::ControlFlowGraph;
|
use cfg::ControlFlowGraph;
|
||||||
|
use ir::dfg::ValueDef;
|
||||||
|
use ir::{Function, Value, Inst, Ebb};
|
||||||
|
use isa::{TargetIsa, RecipeConstraints};
|
||||||
|
use regalloc::liverange::LiveRange;
|
||||||
|
use regalloc::affinity::Affinity;
|
||||||
use sparse_map::SparseMap;
|
use sparse_map::SparseMap;
|
||||||
|
|
||||||
/// A set of live ranges, indexed by value number.
|
/// A set of live ranges, indexed by value number.
|
||||||
struct LiveRangeSet(SparseMap<Value, LiveRange>);
|
type LiveRangeSet = SparseMap<Value, LiveRange>;
|
||||||
|
|
||||||
impl LiveRangeSet {
|
/// Get a mutable reference to the live range for `value`.
|
||||||
pub fn new() -> LiveRangeSet {
|
/// Create it if necessary.
|
||||||
LiveRangeSet(SparseMap::new())
|
fn get_or_create<'a>(lrset: &'a mut LiveRangeSet,
|
||||||
|
value: Value,
|
||||||
|
func: &Function,
|
||||||
|
recipe_constraints: &[RecipeConstraints])
|
||||||
|
-> &'a mut LiveRange {
|
||||||
|
// It would be better to use `get_mut()` here, but that leads to borrow checker fighting
|
||||||
|
// which can probably only be resolved by non-lexical lifetimes.
|
||||||
|
// https://github.com/rust-lang/rfcs/issues/811
|
||||||
|
if lrset.get(value).is_none() {
|
||||||
|
// Create a live range for value. We need the program point that defines it.
|
||||||
|
let def;
|
||||||
|
let affinity;
|
||||||
|
match func.dfg.value_def(value) {
|
||||||
|
ValueDef::Res(inst, rnum) => {
|
||||||
|
def = inst.into();
|
||||||
|
// Initialize the affinity from the defining instruction's result constraints.
|
||||||
|
// Don't do this for call return values which are always tied to a single register.
|
||||||
|
affinity = recipe_constraints.get(func.encodings[inst].recipe())
|
||||||
|
.and_then(|rc| rc.outs.get(rnum))
|
||||||
|
.map(Affinity::new)
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
ValueDef::Arg(ebb, _) => {
|
||||||
|
def = ebb.into();
|
||||||
|
// Don't apply any affinity to EBB arguments.
|
||||||
|
// They could be in a register or on the stack.
|
||||||
|
affinity = Default::default();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lrset.insert(LiveRange::new(value, def, affinity));
|
||||||
|
}
|
||||||
|
lrset.get_mut(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extend the live range for `value` so it reaches `to` which must live in `ebb`.
|
||||||
|
fn extend_to_use(lr: &mut LiveRange,
|
||||||
|
ebb: Ebb,
|
||||||
|
to: Inst,
|
||||||
|
worklist: &mut Vec<Ebb>,
|
||||||
|
func: &Function,
|
||||||
|
cfg: &ControlFlowGraph) {
|
||||||
|
// 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) {
|
||||||
|
worklist.push(ebb);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
// The work list contains those EBBs where we have learned that the value needs to be
|
||||||
self.0.clear();
|
// live-in.
|
||||||
}
|
//
|
||||||
|
// This algorithm becomes a depth-first traversal up the CFG, enumerating all paths through the
|
||||||
/// Get a mutable reference to the live range for `value`.
|
// CFG from the existing live range to `ebb`.
|
||||||
/// Create it if necessary.
|
//
|
||||||
pub fn get_or_create(&mut self, value: Value, dfg: &DataFlowGraph) -> &mut LiveRange {
|
// Extend the live range as we go. The live range itself also serves as a visited set since
|
||||||
// It would be better to use `get_mut()` here, but that leads to borrow checker fighting
|
// `extend_in_ebb` will never return true twice for the same EBB.
|
||||||
// which can probably only be resolved by non-lexical lifetimes.
|
//
|
||||||
// https://github.com/rust-lang/rfcs/issues/811
|
while let Some(livein) = worklist.pop() {
|
||||||
if self.0.get(value).is_none() {
|
// We've learned that the value needs to be live-in to the `livein` EBB.
|
||||||
// Create a live range for value. We need the program point that defines it.
|
// Make sure it is also live at all predecessor branches to `livein`.
|
||||||
let def: ProgramPoint = match dfg.value_def(value) {
|
for &(pred, branch) in cfg.get_predecessors(livein) {
|
||||||
ValueDef::Res(inst, _) => inst.into(),
|
if lr.extend_in_ebb(pred, branch, &func.layout) {
|
||||||
ValueDef::Arg(ebb, _) => ebb.into(),
|
// This predecessor EBB also became live-in. We need to process it later.
|
||||||
};
|
worklist.push(pred);
|
||||||
self.0.insert(LiveRange::new(value, def));
|
}
|
||||||
}
|
}
|
||||||
self.0.get_mut(value).unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,56 +286,42 @@ impl Liveness {
|
|||||||
|
|
||||||
/// Compute the live ranges of all SSA values used in `func`.
|
/// Compute the live ranges of all SSA values used in `func`.
|
||||||
/// This clears out any existing analysis stored in this data structure.
|
/// This clears out any existing analysis stored in this data structure.
|
||||||
pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) {
|
pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, isa: &TargetIsa) {
|
||||||
self.ranges.clear();
|
self.ranges.clear();
|
||||||
|
|
||||||
|
// Get ISA data structures used for computing live range affinities.
|
||||||
|
let recipe_constraints = isa.recipe_constraints();
|
||||||
|
let reg_info = isa.register_info();
|
||||||
|
|
||||||
// The liveness computation needs to visit all uses, but the order doesn't matter.
|
// The liveness computation needs to visit all uses, but the order doesn't matter.
|
||||||
// TODO: Perhaps this traversal of the function could be combined with a dead code
|
// TODO: Perhaps this traversal of the function could be combined with a dead code
|
||||||
// elimination pass if we visit a post-order of the dominator tree?
|
// elimination pass if we visit a post-order of the dominator tree?
|
||||||
// TODO: Resolve value aliases while we're visiting instructions?
|
// TODO: Resolve value aliases while we're visiting instructions?
|
||||||
for ebb in func.layout.ebbs() {
|
for ebb in func.layout.ebbs() {
|
||||||
for inst in func.layout.ebb_insts(ebb) {
|
for inst in func.layout.ebb_insts(ebb) {
|
||||||
func.dfg[inst].each_arg(|arg| self.extend_to_use(arg, ebb, inst, func, cfg));
|
// The instruction encoding is used to compute affinities.
|
||||||
}
|
let recipe = func.encodings[inst].recipe();
|
||||||
}
|
// Iterator of constraints, one per value operand.
|
||||||
}
|
// TODO: Should we fail here if the instruction doesn't have a valid encoding?
|
||||||
|
let mut operand_constraints =
|
||||||
|
recipe_constraints.get(recipe).map(|c| c.ins).unwrap_or(&[]).iter();
|
||||||
|
|
||||||
/// Extend the live range for `value` so it reaches `to` which must live in `ebb`.
|
func.dfg[inst].each_arg(|arg| {
|
||||||
fn extend_to_use(&mut self,
|
// Get the live range, create it as a dead range if necessary.
|
||||||
value: Value,
|
let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints);
|
||||||
ebb: Ebb,
|
|
||||||
to: Inst,
|
|
||||||
func: &Function,
|
|
||||||
cfg: &ControlFlowGraph) {
|
|
||||||
// Get the live range, create it as a dead range if necessary.
|
|
||||||
let lr = self.ranges.get_or_create(value, &func.dfg);
|
|
||||||
|
|
||||||
// This is our scratch working space, and we'll leave it empty when we return.
|
// Extend the live range to reach this use.
|
||||||
assert!(self.worklist.is_empty());
|
extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg);
|
||||||
|
|
||||||
// Extend the range locally in `ebb`.
|
// Apply operand constraint, ignoring any variable arguments after the fixed
|
||||||
// If there already was a live interval in that block, we're done.
|
// operands described by `operand_constraints`. Variable arguments are either
|
||||||
if lr.extend_in_ebb(ebb, to, &func.layout) {
|
// EBB arguments or call/return ABI arguments. EBB arguments need to be
|
||||||
self.worklist.push(ebb);
|
// resolved by the coloring algorithm, and ABI constraints require specific
|
||||||
}
|
// registers or stack slots which the affinities don't model anyway.
|
||||||
|
if let Some(constraint) = operand_constraints.next() {
|
||||||
// The work list contains those EBBs where we have learned that the value needs to be
|
lr.affinity.merge(constraint, reg_info);
|
||||||
// live-in.
|
}
|
||||||
//
|
});
|
||||||
// This algorithm becomes a depth-first traversal up the CFG, enumerating all paths through
|
|
||||||
// the CFG from the existing live range to `ebb`.
|
|
||||||
//
|
|
||||||
// Extend the live range as we go. The live range itself also serves as a visited set since
|
|
||||||
// `extend_in_ebb` will never return true twice for the same EBB.
|
|
||||||
//
|
|
||||||
while let Some(livein) = self.worklist.pop() {
|
|
||||||
// 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.get_predecessors(livein) {
|
|
||||||
if lr.extend_in_ebb(pred, branch, &func.layout) {
|
|
||||||
// This predecessor EBB also became live-in. We need to process it later.
|
|
||||||
self.worklist.push(pred);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,11 +205,10 @@ impl LiveRange {
|
|||||||
/// Create a new live range for `value` defined at `def`.
|
/// Create a new live range for `value` defined at `def`.
|
||||||
///
|
///
|
||||||
/// The live range will be created as dead, but it can be extended with `extend_in_ebb()`.
|
/// The live range will be created as dead, but it can be extended with `extend_in_ebb()`.
|
||||||
pub fn new<PP: Into<ProgramPoint>>(value: Value, def: PP) -> LiveRange {
|
pub fn new(value: Value, def: ProgramPoint, affinity: Affinity) -> LiveRange {
|
||||||
let def = def.into();
|
|
||||||
LiveRange {
|
LiveRange {
|
||||||
value: value,
|
value: value,
|
||||||
affinity: Default::default(),
|
affinity: affinity,
|
||||||
def_begin: def,
|
def_begin: def,
|
||||||
def_end: def,
|
def_end: def,
|
||||||
liveins: Vec::new(),
|
liveins: Vec::new(),
|
||||||
@@ -423,7 +422,7 @@ mod tests {
|
|||||||
let v0 = Value::new(0);
|
let v0 = Value::new(0);
|
||||||
let i1 = Inst::new(1);
|
let i1 = Inst::new(1);
|
||||||
let e2 = Ebb::new(2);
|
let e2 = Ebb::new(2);
|
||||||
let lr = LiveRange::new(v0, i1);
|
let lr = LiveRange::new(v0, i1.into(), Default::default());
|
||||||
assert!(lr.is_dead());
|
assert!(lr.is_dead());
|
||||||
assert!(lr.is_local());
|
assert!(lr.is_local());
|
||||||
assert_eq!(lr.def(), i1.into());
|
assert_eq!(lr.def(), i1.into());
|
||||||
@@ -436,7 +435,7 @@ mod tests {
|
|||||||
fn dead_arg_range() {
|
fn dead_arg_range() {
|
||||||
let v0 = Value::new(0);
|
let v0 = Value::new(0);
|
||||||
let e2 = Ebb::new(2);
|
let e2 = Ebb::new(2);
|
||||||
let lr = LiveRange::new(v0, e2);
|
let lr = LiveRange::new(v0, e2.into(), Default::default());
|
||||||
assert!(lr.is_dead());
|
assert!(lr.is_dead());
|
||||||
assert!(lr.is_local());
|
assert!(lr.is_local());
|
||||||
assert_eq!(lr.def(), e2.into());
|
assert_eq!(lr.def(), e2.into());
|
||||||
@@ -453,7 +452,7 @@ mod tests {
|
|||||||
let i11 = Inst::new(11);
|
let i11 = Inst::new(11);
|
||||||
let i12 = Inst::new(12);
|
let i12 = Inst::new(12);
|
||||||
let i13 = Inst::new(13);
|
let i13 = Inst::new(13);
|
||||||
let mut lr = LiveRange::new(v0, i11);
|
let mut lr = LiveRange::new(v0, i11.into(), Default::default());
|
||||||
|
|
||||||
assert_eq!(lr.extend_in_ebb(e10, i13, PO), false);
|
assert_eq!(lr.extend_in_ebb(e10, i13, PO), false);
|
||||||
PO.validate(&lr);
|
PO.validate(&lr);
|
||||||
@@ -476,7 +475,7 @@ mod tests {
|
|||||||
let i11 = Inst::new(11);
|
let i11 = Inst::new(11);
|
||||||
let i12 = Inst::new(12);
|
let i12 = Inst::new(12);
|
||||||
let i13 = Inst::new(13);
|
let i13 = Inst::new(13);
|
||||||
let mut lr = LiveRange::new(v0, e10);
|
let mut lr = LiveRange::new(v0, e10.into(), Default::default());
|
||||||
|
|
||||||
// Extending a dead EBB argument in its own block should not indicate that a live-in
|
// Extending a dead EBB argument in its own block should not indicate that a live-in
|
||||||
// interval was created.
|
// interval was created.
|
||||||
@@ -510,7 +509,7 @@ mod tests {
|
|||||||
let i21 = Inst::new(21);
|
let i21 = Inst::new(21);
|
||||||
let i22 = Inst::new(22);
|
let i22 = Inst::new(22);
|
||||||
let i23 = Inst::new(23);
|
let i23 = Inst::new(23);
|
||||||
let mut lr = LiveRange::new(v0, i11);
|
let mut lr = LiveRange::new(v0, i11.into(), Default::default());
|
||||||
|
|
||||||
assert_eq!(lr.extend_in_ebb(e10, i12, PO), false);
|
assert_eq!(lr.extend_in_ebb(e10, i12, PO), false);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user