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.
|
||||
|
||||
use regalloc::liverange::LiveRange;
|
||||
use ir::{Function, Value, Inst, Ebb, ProgramPoint};
|
||||
use ir::dfg::{DataFlowGraph, ValueDef};
|
||||
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;
|
||||
|
||||
/// A set of live ranges, indexed by value number.
|
||||
struct LiveRangeSet(SparseMap<Value, LiveRange>);
|
||||
type LiveRangeSet = SparseMap<Value, LiveRange>;
|
||||
|
||||
impl LiveRangeSet {
|
||||
pub fn new() -> LiveRangeSet {
|
||||
LiveRangeSet(SparseMap::new())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the live range for `value`.
|
||||
/// Create it if necessary.
|
||||
pub fn get_or_create(&mut self, value: Value, dfg: &DataFlowGraph) -> &mut LiveRange {
|
||||
/// Get a mutable reference to the live range for `value`.
|
||||
/// Create it if necessary.
|
||||
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 self.0.get(value).is_none() {
|
||||
if lrset.get(value).is_none() {
|
||||
// Create a live range for value. We need the program point that defines it.
|
||||
let def: ProgramPoint = match dfg.value_def(value) {
|
||||
ValueDef::Res(inst, _) => inst.into(),
|
||||
ValueDef::Arg(ebb, _) => ebb.into(),
|
||||
};
|
||||
self.0.insert(LiveRange::new(value, def));
|
||||
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);
|
||||
}
|
||||
|
||||
// The work list contains those EBBs where we have learned that the value needs to be
|
||||
// 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) = 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.
|
||||
worklist.push(pred);
|
||||
}
|
||||
}
|
||||
self.0.get_mut(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,56 +286,42 @@ impl Liveness {
|
||||
|
||||
/// Compute the live ranges of all SSA values used in `func`.
|
||||
/// 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();
|
||||
|
||||
// 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.
|
||||
// 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?
|
||||
// TODO: Resolve value aliases while we're visiting instructions?
|
||||
for ebb in func.layout.ebbs() {
|
||||
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`.
|
||||
fn extend_to_use(&mut self,
|
||||
value: Value,
|
||||
ebb: Ebb,
|
||||
to: Inst,
|
||||
func: &Function,
|
||||
cfg: &ControlFlowGraph) {
|
||||
func.dfg[inst].each_arg(|arg| {
|
||||
// Get the live range, create it as a dead range if necessary.
|
||||
let lr = self.ranges.get_or_create(value, &func.dfg);
|
||||
let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints);
|
||||
|
||||
// This is our scratch working space, and we'll leave it empty when we return.
|
||||
assert!(self.worklist.is_empty());
|
||||
// Extend the live range to reach this use.
|
||||
extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg);
|
||||
|
||||
// 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) {
|
||||
self.worklist.push(ebb);
|
||||
}
|
||||
|
||||
// The work list contains those EBBs where we have learned that the value needs to be
|
||||
// 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);
|
||||
// Apply operand constraint, ignoring any variable arguments after the fixed
|
||||
// operands described by `operand_constraints`. Variable arguments are either
|
||||
// EBB arguments or call/return ABI arguments. EBB arguments need to be
|
||||
// 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() {
|
||||
lr.affinity.merge(constraint, reg_info);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,11 +205,10 @@ impl LiveRange {
|
||||
/// 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()`.
|
||||
pub fn new<PP: Into<ProgramPoint>>(value: Value, def: PP) -> LiveRange {
|
||||
let def = def.into();
|
||||
pub fn new(value: Value, def: ProgramPoint, affinity: Affinity) -> LiveRange {
|
||||
LiveRange {
|
||||
value: value,
|
||||
affinity: Default::default(),
|
||||
affinity: affinity,
|
||||
def_begin: def,
|
||||
def_end: def,
|
||||
liveins: Vec::new(),
|
||||
@@ -423,7 +422,7 @@ mod tests {
|
||||
let v0 = Value::new(0);
|
||||
let i1 = Inst::new(1);
|
||||
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_local());
|
||||
assert_eq!(lr.def(), i1.into());
|
||||
@@ -436,7 +435,7 @@ mod tests {
|
||||
fn dead_arg_range() {
|
||||
let v0 = Value::new(0);
|
||||
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_local());
|
||||
assert_eq!(lr.def(), e2.into());
|
||||
@@ -453,7 +452,7 @@ mod tests {
|
||||
let i11 = Inst::new(11);
|
||||
let i12 = Inst::new(12);
|
||||
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);
|
||||
PO.validate(&lr);
|
||||
@@ -476,7 +475,7 @@ mod tests {
|
||||
let i11 = Inst::new(11);
|
||||
let i12 = Inst::new(12);
|
||||
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
|
||||
// interval was created.
|
||||
@@ -510,7 +509,7 @@ mod tests {
|
||||
let i21 = Inst::new(21);
|
||||
let i22 = Inst::new(22);
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user