SSA register coloring pass.

This is a bare-bones outline of the SSA coloring pass. Many features are
missing, including:

- Handling instruction operand constraints beyond simple register
  classes.
- Handling ABI requirements for function arguments and return values.
- Generating shuffle code for EBB arguments.
This commit is contained in:
Jakob Stoklund Olesen
2017-02-15 13:26:52 -08:00
parent 2ec7412a81
commit 329e51ac4f
3 changed files with 339 additions and 0 deletions

View File

@@ -49,6 +49,7 @@ pub enum ConstraintKind {
}
/// Constraints for an encoding recipe.
#[derive(Clone)]
pub struct RecipeConstraints {
/// Constraints for the instruction's fixed value operands.
///

View File

@@ -0,0 +1,337 @@
//! Register allocator coloring pass.
//!
//! The coloring pass assigns a physical register to every SSA value with a register affinity,
//! under the assumption that the register pressure has been lowered sufficiently by spilling and
//! splitting.
//!
//! # Preconditions
//!
//! The coloring pass doesn't work on arbitrary code. Certain preconditions must be satisfied:
//!
//! 1. All instructions must be legalized and assigned an encoding. The encoding recipe guides the
//! register assignments and provides exact constraints.
//!
//! 2. Instructions with tied operands must be in a coloring-friendly state. Specifically, the
//! values used by the tied operands must be killed by the instruction. This can be achieved by
//! inserting a `copy` to a new value immediately before the two-address instruction.
//!
//! 3. The register pressure must be lowered sufficiently by inserting spill code. Register
//! operands are allowed to read spilled values, but each such instance must be counted as using
//! a register.
//!
//! # Iteration order
//!
//! The SSA property guarantees that whenever the live range of two values overlap, one of the
//! values will be live at the definition point of the other value. If we visit the instructions in
//! a topological order relative to the dominance relation, we can assign colors to the values
//! defined by the instruction and only consider the colors of other values that are live at the
//! instruction.
//!
//! The topological order of instructions inside an EBB is simply the layout order, starting from
//! the EBB header. A topological order of the EBBs can only visit an EBB once its immediate
//! dominator has been visited.
//!
//! There are many valid topological orders of the EBBs, and the specific order can affect which
//! coloring hints are satisfied and which are broken.
//!
use entity_map::EntityMap;
use dominator_tree::DominatorTree;
use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph};
use isa::{TargetIsa, RegInfo, Encoding, RecipeConstraints, ConstraintKind};
use regalloc::affinity::Affinity;
use regalloc::allocatable_set::AllocatableSet;
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
use regalloc::liveness::Liveness;
use sparse_map::SparseSet;
/// Data structures for the coloring pass.
///
/// These are scratch space data structures that can be reused between invocations.
pub struct Coloring {
/// Set of visited EBBs.
visited: SparseSet<Ebb>,
/// Stack of EBBs to be visited next.
stack: Vec<Ebb>,
}
/// Bundle of references that the coloring algorithm needs.
///
/// Some of the needed mutable references are passed around as explicit function arguments so we
/// can avoid many fights with the borrow checker over mutable borrows of `self`. This includes the
/// `Function` and `LiveValueTracker` references.
///
/// Immutable context information and mutable references that don't need to be borrowed across
/// method calls should go in this struct.
struct Context<'a> {
// Cached ISA information.
// We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object.
reginfo: RegInfo,
recipe_constraints: &'a [RecipeConstraints],
// References to contextual data structures we need.
domtree: &'a DominatorTree,
liveness: &'a mut Liveness,
// Pristine set of registers that the allocator can use.
// This set remains immutable, we make clones.
usable_regs: AllocatableSet,
}
impl Coloring {
/// Allocate scratch space data structures for the coloring pass.
pub fn new() -> Coloring {
Coloring {
visited: SparseSet::new(),
stack: Vec::new(),
}
}
/// Run the coloring algorithm over `func`.
pub fn run(&mut self,
isa: &TargetIsa,
func: &mut Function,
domtree: &DominatorTree,
liveness: &mut Liveness,
tracker: &mut LiveValueTracker) {
let mut ctx = Context {
reginfo: isa.register_info(),
recipe_constraints: isa.recipe_constraints(),
domtree: domtree,
liveness: liveness,
// TODO: Ask the target ISA about reserved registers etc.
usable_regs: AllocatableSet::new(),
};
ctx.run(self, func, tracker)
}
}
impl<'a> Context<'a> {
/// Run the coloring algorithm.
fn run(&mut self, data: &mut Coloring, func: &mut Function, tracker: &mut LiveValueTracker) {
// Just visit blocks in layout order, letting `process_ebb` enforce a topological ordering.
// TODO: Once we have a loop tree, we could visit hot blocks first.
let mut next = func.layout.entry_block();
while let Some(ebb) = next {
self.process_ebb(ebb, data, func, tracker);
next = func.layout.next_ebb(ebb);
}
}
/// Process `ebb`, but only after ensuring that the immediate dominator has been processed.
///
/// This method can be called with the most desired order of visiting the EBBs. It will convert
/// that order into a valid topological order by visiting dominators first.
fn process_ebb(&mut self,
mut ebb: Ebb,
data: &mut Coloring,
func: &mut Function,
tracker: &mut LiveValueTracker) {
// The stack is just a scratch space for this algorithm. We leave it empty when returning.
assert!(data.stack.is_empty());
// Trace up the dominator tree until we reach a dominator that has already been visited.
while data.visited.insert(ebb).is_none() {
data.stack.push(ebb);
match self.domtree.idom(ebb) {
Some(idom) => ebb = func.layout.inst_ebb(idom).expect("idom not in layout"),
None => break,
}
}
// Pop off blocks in topological order.
while let Some(ebb) = data.stack.pop() {
self.visit_ebb(ebb, func, tracker);
}
}
/// Visit `ebb`, assuming that the immediate dominator has already been visited.
fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) {
let mut regs = self.visit_ebb_header(ebb, func, tracker);
// Now go through the instructions in `ebb` and color the values they define.
let mut pos = Cursor::new(&mut func.layout);
pos.goto_top(ebb);
while let Some(inst) = pos.next_inst() {
let encoding = func.encodings[inst];
assert!(encoding.is_legal(), "Illegal: {}", func.dfg[inst].opcode());
self.visit_inst(inst,
encoding,
&mut pos,
&mut func.dfg,
tracker,
&mut regs,
&mut func.locations);
tracker.drop_dead(inst);
}
}
/// Visit the `ebb` header.
///
/// Initialize the set of live registers and color the arguments to `ebb`.
fn visit_ebb_header(&self,
ebb: Ebb,
func: &mut Function,
tracker: &mut LiveValueTracker)
-> AllocatableSet {
// Reposition the live value tracker and deal with the EBB arguments.
let (liveins, args) =
tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree);
// The live-ins have already been assigned a register. Reconstruct the allocatable set.
let mut regs = self.livein_regs(liveins, func);
// TODO: Arguments to the entry block are pre-colored by the ABI. We should probably call
// a whole other function for that case.
self.color_args(args, &mut regs, &mut func.locations);
regs
}
/// Initialize a set of allocatable registers from the values that are live-in to a block.
/// These values must already be colored when the dominating blocks were processed.
fn livein_regs(&self, liveins: &[LiveValue], func: &Function) -> AllocatableSet {
// Start from the registers that are actually usable. We don't want to include any reserved
// registers in the set.
let mut regs = self.usable_regs.clone();
for lv in liveins {
let value = lv.value;
let affinity = self.liveness.get(value).expect("No live range for live-in").affinity;
if let Affinity::Reg(rc_index) = affinity {
let regclass = self.reginfo.rc(rc_index);
match func.locations[value] {
ValueLoc::Reg(regunit) => regs.take(regclass, regunit),
ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value),
ValueLoc::Stack(ss) => {
panic!("Live-in {} is in {}, should be register", value, ss)
}
}
}
}
regs
}
/// Color the live arguments to the current block.
///
/// It is assumed that any live-in register values have already been taken out of the register
/// set.
fn color_args(&self,
args: &[LiveValue],
regs: &mut AllocatableSet,
locations: &mut EntityMap<Value, ValueLoc>) {
for lv in args {
// Only look at the register arguments.
if let Affinity::Reg(rc_index) = lv.affinity {
let regclass = self.reginfo.rc(rc_index);
// TODO: Fall back to a top-level super-class. Sub-classes are only hints.
let regunit = regs.iter(regclass).next().expect("Out of registers for arguments");
regs.take(regclass, regunit);
*locations.ensure(lv.value) = ValueLoc::Reg(regunit);
}
}
}
/// Color the values defined by `inst` and insert any necessary shuffle code to satisfy
/// instruction constraints.
///
/// Update `regs` to reflect the allocated registers after `inst`, including removing any dead
/// or killed values from the set.
fn visit_inst(&self,
inst: Inst,
encoding: Encoding,
_pos: &mut Cursor,
dfg: &mut DataFlowGraph,
tracker: &mut LiveValueTracker,
regs: &mut AllocatableSet,
locations: &mut EntityMap<Value, ValueLoc>) {
// First update the live value tracker with this instruction.
// Get lists of values that are killed and defined by `inst`.
let (kills, defs) = tracker.process_inst(inst, dfg, self.liveness);
// Get the operand constraints for `inst` that we are trying to satisfy.
let constraints = self.recipe_constraints[encoding.recipe()].clone();
// Get rid of the killed values.
for lv in kills {
if let Affinity::Reg(rc_index) = lv.affinity {
let regclass = self.reginfo.rc(rc_index);
if let ValueLoc::Reg(regunit) = locations[lv.value] {
regs.free(regclass, regunit);
}
}
}
// Process the defined values with fixed constraints.
// TODO: Handle constraints on call return values.
assert_eq!(defs.len(),
constraints.outs.len(),
"Can't handle variable results");
for (lv, opcst) in defs.iter().zip(constraints.outs) {
match lv.affinity {
// This value should go in a register.
Affinity::Reg(rc_index) => {
// The preferred register class is not a requirement.
let pref_rc = self.reginfo.rc(rc_index);
match opcst.kind {
ConstraintKind::Reg => {
// This is a standard register constraint. The preferred register class
// should have been computed as a subclass of the hard constraint of
// the def.
assert!(opcst.regclass.has_subclass(rc_index),
"{} preference {} is not compatible with the definition \
constraint {}",
lv.value,
pref_rc.name,
opcst.regclass.name);
// Try to grab a register from the preferred class, but fall back to
// the actual constraint if we have to.
let regunit = regs.iter(pref_rc)
.next()
.or_else(|| regs.iter(opcst.regclass).next())
.expect("Ran out of registers");
regs.take(opcst.regclass, regunit);
*locations.ensure(lv.value) = ValueLoc::Reg(regunit);
}
ConstraintKind::Tied(arg_index) => {
// This def must use the same register as a fixed instruction argument.
let loc = locations[dfg[inst].arguments()[0][arg_index as usize]];
*locations.ensure(lv.value) = loc;
// Mark the reused register. It's not really clear if we support tied
// stack operands. We could do that for some Intel read-modify-write
// encodings.
if let ValueLoc::Reg(regunit) = loc {
// This is going to assert out unless the incoming value at
// `arg_index` was killed. Tied operands must be fixed to
// ensure that before running the coloring pass.
regs.take(opcst.regclass, regunit);
}
}
ConstraintKind::FixedReg(_regunit) => unimplemented!(),
ConstraintKind::Stack => {
panic!("{}:{} should be a stack value", lv.value, pref_rc.name)
}
}
}
Affinity::Stack => unimplemented!(),
Affinity::Any => unimplemented!(),
}
}
// Get rid of the dead defs.
for lv in defs {
if lv.endpoint == inst {
if let Affinity::Reg(rc_index) = lv.affinity {
let regclass = self.reginfo.rc(rc_index);
if let ValueLoc::Reg(regunit) = locations[lv.value] {
regs.free(regclass, regunit);
}
}
}
}
}
}

View File

@@ -6,5 +6,6 @@ pub mod liverange;
pub mod liveness;
pub mod allocatable_set;
pub mod live_value_tracker;
pub mod coloring;
mod affinity;