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:
@@ -49,6 +49,7 @@ pub enum ConstraintKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constraints for an encoding recipe.
|
/// Constraints for an encoding recipe.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RecipeConstraints {
|
pub struct RecipeConstraints {
|
||||||
/// Constraints for the instruction's fixed value operands.
|
/// Constraints for the instruction's fixed value operands.
|
||||||
///
|
///
|
||||||
|
|||||||
337
lib/cretonne/src/regalloc/coloring.rs
Normal file
337
lib/cretonne/src/regalloc/coloring.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,5 +6,6 @@ pub mod liverange;
|
|||||||
pub mod liveness;
|
pub mod liveness;
|
||||||
pub mod allocatable_set;
|
pub mod allocatable_set;
|
||||||
pub mod live_value_tracker;
|
pub mod live_value_tracker;
|
||||||
|
pub mod coloring;
|
||||||
|
|
||||||
mod affinity;
|
mod affinity;
|
||||||
|
|||||||
Reference in New Issue
Block a user