Files
wasmtime/lib/codegen/src/regalloc/solver.rs

1375 lines
50 KiB
Rust

//! Constraint solver for register coloring.
//!
//! The coloring phase of SSA-based register allocation is very simple in theory, but in practice
//! it is complicated by the various constraints imposed by individual instructions:
//!
//! - Call and return instructions have to satisfy ABI requirements for arguments and return
//! values.
//! - Values live across a call must be in a callee-saved register.
//! - Some instructions have operand constraints such as register sub-classes, fixed registers, or
//! tied operands.
//!
//! # The instruction register coloring problem
//!
//! The constraint solver addresses the problem of satisfying the constraints of a single
//! instruction. We have:
//!
//! - A set of values that are live in registers before the instruction, with current register
//! assignments. Some are used by the instruction, some are not.
//! - A subset of the live register values that are killed by the instruction.
//! - A set of new register values that are defined by the instruction.
//!
//! We are not concerned with stack values at all. The reload pass ensures that all values required
//! to be in a register by the instruction are already in a register.
//!
//! A solution to the register coloring problem consists of:
//!
//! - Register reassignment prescriptions for a subset of the live register values.
//! - Register assignments for the instruction's defined values.
//!
//! The solution ensures that when live registers are reassigned as prescribed before the
//! instruction, all its operand constraints are satisfied, and the definition assignments won't
//! conflict.
//!
//! # Register diversions and global interference
//!
//! We can divert register values temporarily to satisfy constraints, but we need to put the
//! values back into their originally assigned register locations before leaving the EBB.
//! Otherwise, values won't be in the right register at the entry point of other EBBs.
//!
//! Some values are *local*, and we don't need to worry about putting those values back since they
//! are not used in any other EBBs.
//!
//! When we assign register locations to defines, we are assigning both the register used locally
//! immediately after the instruction and the register used globally when the defined value is used
//! in a different EBB. We need to avoid interference both locally at the instruction and globally.
//!
//! We have multiple mappings of values to registers:
//!
//! 1. The initial local mapping before the instruction. This includes any diversions from previous
//! instructions in the EBB, but not diversions for the current instruction.
//! 2. The local mapping after applying the additional reassignments required to satisfy the
//! constraints of the current instruction.
//! 3. The local mapping after the instruction. This excludes values killed by the instruction and
//! includes values defined by the instruction.
//! 4. The global mapping after the instruction. This mapping only contains values with global live
//! ranges, and it does not include any diversions.
//!
//! All four mappings must be kept free of interference.
//!
//! # Problems handled by previous passes.
//!
//! The constraint solver can only reassign registers, it can't create spill code, so some
//! constraints are handled by earlier passes:
//!
//! - There will be enough free registers available for the defines. Ensuring this is the primary
//! purpose of the spilling phase.
//! - When the same value is used for multiple operands, the intersection of operand constraints is
//! non-empty. The spilling phase will insert copies to handle mutually incompatible constraints,
//! such as when the same value is bound to two different function arguments.
//! - Values bound to tied operands must be killed by the instruction. Also enforced by the
//! spiller.
//! - Values used by register operands are in registers, and values used by stack operands are in
//! stack slots. This is enforced by the reload pass.
//!
//! # Solver algorithm
//!
//! The goal of the solver is to satisfy the instruction constraints with a minimal number of
//! register assignments before the instruction.
//!
//! 1. Compute the set of values used by operands with a fixed register constraint that isn't
//! already satisfied. These are mandatory predetermined reassignments.
//! 2. Compute the set of values that don't satisfy their register class constraint. These are
//! mandatory reassignments that we need to solve.
//! 3. Add the set of defines to the set of variables computed in 2. Exclude defines tied to an
//! input operand since their value is pre-determined.
//!
//! The set of values computed in 2. and 3. are the *variables* for the solver. Given a set of
//! variables, we can also compute a set of allocatable registers by removing the variables from
//! the set of assigned registers before the instruction.
//!
//! 1. For each variable, compute its domain as the intersection of the allocatable registers and
//! its register class constraint.
//! 2. Sort the variables in order of increasing domain size.
//! 3. Search for a solution that assigns each variable a register from its domain without
//! interference between variables.
//!
//! If the search fails to find a solution, we may need to reassign more registers. Find an
//! appropriate candidate among the set of live register values, add it as a variable and start
//! over.
use super::RegisterSet;
use dbg::DisplayList;
use entity::{SparseMap, SparseMapValue};
use ir::Value;
use isa::{RegClass, RegUnit};
use regalloc::register_set::RegSetIter;
use std::cmp;
use std::fmt;
use std::mem;
use std::u16;
use std::vec::Vec;
/// A variable in the constraint problem.
///
/// Variables represent register values that can be assigned to any register unit within the
/// constraint register class. This includes live register values that can be reassigned to a new
/// register and values defined by the instruction which must be assigned to a register.
///
/// Besides satisfying the register class constraint, variables must also be mutually
/// non-interfering in up to three contexts:
///
/// 1. Input side live registers, after applying all the reassignments.
/// 2. Output side live registers, considering all the local register diversions.
/// 3. Global live register, not considering any local diversions.
///
pub struct Variable {
/// The value whose register assignment we're looking for.
pub value: Value,
/// Original register unit holding this live value before the instruction, or `None` for a
/// value that is defined by the instruction.
from: Option<RegUnit>,
/// Avoid interference on the input side.
is_input: bool,
/// Avoid interference on the output side.
is_output: bool,
/// Avoid interference with the global registers.
is_global: bool,
/// Number of registers available in the domain of this variable.
domain: u16,
/// The assigned register unit after a full solution was found.
pub solution: RegUnit,
/// Any solution must belong to the constraint register class.
constraint: RegClass,
}
impl Variable {
fn new_live(value: Value, constraint: RegClass, from: RegUnit, is_output: bool) -> Self {
Self {
value,
constraint,
from: Some(from),
is_input: true,
is_output,
is_global: false,
domain: 0,
solution: !0,
}
}
fn new_def(value: Value, constraint: RegClass, is_global: bool) -> Self {
Self {
value,
constraint,
from: None,
is_input: false,
is_output: true,
is_global,
domain: 0,
solution: !0,
}
}
/// Does this variable represent a value defined by the current instruction?
pub fn is_define(&self) -> bool {
self.from.is_none()
}
/// Get an iterator over possible register choices, given the available registers on the input
/// and output sides as well as the available global register set.
fn iter(&self, iregs: &RegisterSet, oregs: &RegisterSet, gregs: &RegisterSet) -> RegSetIter {
if !self.is_output {
debug_assert!(!self.is_global, "Global implies output");
debug_assert!(self.is_input, "Missing interference set");
return iregs.iter(self.constraint);
}
let mut r = oregs.clone();
if self.is_input {
r.intersect(iregs);
}
if self.is_global {
r.intersect(gregs);
}
r.iter(self.constraint)
}
}
impl fmt::Display for Variable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}({}", self.value, self.constraint)?;
if let Some(reg) = self.from {
write!(f, ", from {}", self.constraint.info.display_regunit(reg))?;
}
if self.is_input {
write!(f, ", in")?;
}
if self.is_output {
write!(f, ", out")?;
}
if self.is_global {
write!(f, ", global")?;
}
if self.is_define() {
write!(f, ", def")?;
}
if self.domain > 0 {
write!(f, ", {}", self.domain)?;
}
write!(f, ")")
}
}
#[derive(Clone, Debug)]
pub struct Assignment {
pub value: Value,
pub from: RegUnit,
pub to: RegUnit,
pub rc: RegClass,
}
impl SparseMapValue<Value> for Assignment {
fn key(&self) -> Value {
self.value
}
}
impl fmt::Display for Assignment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ri = self.rc.info;
write!(
f,
"{}:{}({} -> {})",
self.value,
self.rc,
ri.display_regunit(self.from),
ri.display_regunit(self.to)
)
}
}
/// A move operation between two registers or between a register and an emergency spill slot.
#[derive(Clone, PartialEq)]
pub enum Move {
Reg {
value: Value,
rc: RegClass,
from: RegUnit,
to: RegUnit,
},
Spill {
value: Value,
rc: RegClass,
from: RegUnit,
to_slot: usize,
},
Fill {
value: Value,
rc: RegClass,
from_slot: usize,
to: RegUnit,
},
}
impl Move {
/// Create a register move from an assignment, but not for identity assignments.
fn with_assignment(a: &Assignment) -> Option<Self> {
if a.from != a.to {
Some(Move::Reg {
value: a.value,
from: a.from,
to: a.to,
rc: a.rc,
})
} else {
None
}
}
/// Get the "from" register and register class, if possible.
#[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))]
fn from_reg(&self) -> Option<(RegClass, RegUnit)> {
match *self {
Move::Reg { rc, from, .. } | Move::Spill { rc, from, .. } => Some((rc, from)),
Move::Fill { .. } => None,
}
}
/// Get the "to" register and register class, if possible.
fn to_reg(&self) -> Option<(RegClass, RegUnit)> {
match *self {
Move::Reg { rc, to, .. } | Move::Fill { rc, to, .. } => Some((rc, to)),
Move::Spill { .. } => None,
}
}
/// Replace the "to" register with `new` and return the old value.
fn replace_to_reg(&mut self, new: RegUnit) -> RegUnit {
mem::replace(
match *self {
Move::Reg { ref mut to, .. } | Move::Fill { ref mut to, .. } => to,
Move::Spill { .. } => panic!("No to register in a spill {}", self),
},
new,
)
}
/// Convert this `Reg` move to a spill to `slot` and return the old "to" register.
fn change_to_spill(&mut self, slot: usize) -> RegUnit {
match self.clone() {
Move::Reg {
value,
rc,
from,
to,
} => {
*self = Move::Spill {
value,
rc,
from,
to_slot: slot,
};
to
}
_ => panic!("Expected reg move: {}", self),
}
}
/// Get the value being moved.
fn value(&self) -> Value {
match *self {
Move::Reg { value, .. } | Move::Fill { value, .. } | Move::Spill { value, .. } => value,
}
}
/// Get the associated register class.
fn rc(&self) -> RegClass {
match *self {
Move::Reg { rc, .. } | Move::Fill { rc, .. } | Move::Spill { rc, .. } => rc,
}
}
}
impl fmt::Display for Move {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Move::Reg {
value,
from,
to,
rc,
} => write!(
f,
"{}:{}({} -> {})",
value,
rc,
rc.info.display_regunit(from),
rc.info.display_regunit(to)
),
Move::Spill {
value,
from,
to_slot,
rc,
} => write!(
f,
"{}:{}({} -> slot {})",
value,
rc,
rc.info.display_regunit(from),
to_slot
),
Move::Fill {
value,
from_slot,
to,
rc,
} => write!(
f,
"{}:{}(slot {} -> {})",
value,
rc,
from_slot,
rc.info.display_regunit(to)
),
}
}
}
impl fmt::Debug for Move {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let as_display: &fmt::Display = self;
as_display.fmt(f)
}
}
/// Constraint solver for register allocation around a single instruction.
///
/// Start by programming in the instruction constraints.
///
/// 1. Initialize the solver by calling `reset()` with the set of allocatable registers before the
/// instruction.
/// 2. Program the input side constraints: Call `reassign_in()` for all fixed register constraints,
/// and `add_var()` for any input operands whose constraints are not already satisfied.
/// 3. Check for conflicts between fixed input assignments and existing live values by calling
/// `has_fixed_input_conflicts()`. Resolve any conflicts by calling `add_var()` with the
/// conflicting values.
/// 4. Prepare for adding output side constraints by calling `inputs_done()`.
/// 5. Add any killed register values that no longer cause interference on the output side by
/// calling `add_kill()`.
/// 6. Program the output side constraints: Call `add_fixed_output()` for all fixed register
/// constraints and `add_def()` for free defines. Resolve fixed output conflicts by calling
/// `add_through_var()`.
///
pub struct Solver {
/// Register reassignments that are required or decided as part of a full solution.
/// This includes identity assignments for values that are already in the correct fixed
/// register.
assignments: SparseMap<Value, Assignment>,
/// Variables are the values that should be reassigned as part of a solution.
/// Values with fixed register constraints are not considered variables. They are represented
/// in the `assignments` vector if necessary.
vars: Vec<Variable>,
/// Are we finished adding input-side constraints? This changes the meaning of the `regs_in`
/// and `regs_out` register sets.
inputs_done: bool,
/// Available registers on the input side of the instruction.
///
/// While we're adding input constraints (`!inputs_done`):
///
/// - Live values on the input side are marked as unavailable.
/// - The 'from' registers of fixed input reassignments are marked as available as they are
/// added.
/// - Input-side variables are marked as available.
///
/// After finishing input constraints (`inputs_done`):
///
/// - Live values on the input side are marked as unavailable.
/// - The 'to' registers of fixed input reassignments are marked as unavailable.
/// - Input-side variables are marked as available.
///
regs_in: RegisterSet,
/// Available registers on the output side of the instruction / fixed input scratch space.
///
/// While we're adding input constraints (`!inputs_done`):
///
/// - The 'to' registers of fixed input reassignments are marked as unavailable.
///
/// After finishing input constraints (`inputs_done`):
///
/// - Live-through values are marked as unavailable.
/// - Fixed output assignments are marked as unavailable.
/// - Live-through variables are marked as available.
///
regs_out: RegisterSet,
/// List of register moves scheduled to avoid conflicts.
///
/// This is used as working space by the `schedule_moves()` function.
moves: Vec<Move>,
/// List of pending fill moves. This is only used during `schedule_moves()`.
fills: Vec<Move>,
}
/// Interface for programming the constraints into the solver.
impl Solver {
/// Create a new empty solver.
pub fn new() -> Self {
Self {
assignments: SparseMap::new(),
vars: Vec::new(),
inputs_done: false,
regs_in: RegisterSet::new(),
regs_out: RegisterSet::new(),
moves: Vec::new(),
fills: Vec::new(),
}
}
/// Clear all data structures in this coloring pass.
pub fn clear(&mut self) {
self.assignments.clear();
self.vars.clear();
self.inputs_done = false;
self.regs_in = RegisterSet::new();
self.regs_out = RegisterSet::new();
self.moves.clear();
self.fills.clear();
}
/// Reset the solver state and prepare solving for a new instruction with an initial set of
/// allocatable registers.
///
/// The `regs` set is the allocatable registers before any reassignments are applied.
pub fn reset(&mut self, regs: &RegisterSet) {
self.assignments.clear();
self.vars.clear();
self.inputs_done = false;
self.regs_in = regs.clone();
// Used for tracking fixed input assignments while `!inputs_done`:
self.regs_out = RegisterSet::new();
self.moves.clear();
self.fills.clear();
}
/// Add a fixed input reassignment of `value`.
///
/// This means that `value` must be assigned to `to` and can't become a variable. Call with
/// `from == to` to ensure that `value` is not reassigned from its existing register location.
///
/// In either case, `to` will not be available for variables on the input side of the
/// instruction.
pub fn reassign_in(&mut self, value: Value, rc: RegClass, from: RegUnit, to: RegUnit) {
dbg!(
"reassign_in({}:{}, {} -> {})",
value,
rc,
rc.info.display_regunit(from),
rc.info.display_regunit(to)
);
debug_assert!(!self.inputs_done);
if self.regs_in.is_avail(rc, from) {
// It looks like `value` was already removed from the register set. It must have been
// added as a variable previously. A fixed constraint beats a variable, so convert it.
if let Some(idx) = self.vars.iter().position(|v| v.value == value) {
let v = self.vars.remove(idx);
dbg!("-> converting variable {} to a fixed constraint", v);
// The spiller is responsible for ensuring that all constraints on the uses of a
// value are compatible.
debug_assert!(
v.constraint.contains(to),
"Incompatible constraints for {}",
value
);
} else {
panic!("Invalid from register for fixed {} constraint", value);
}
}
self.regs_in.free(rc, from);
self.regs_out.take(rc, to);
self.assignments.insert(Assignment {
value,
rc,
from,
to,
});
}
/// Add a variable representing an input side value with an existing register assignment.
///
/// A variable is a value that should be reassigned to something in the `constraint` register
/// class.
///
/// It is assumed initially that the value is also live on the output side of the instruction.
/// This can be changed by calling to `add_kill()`.
///
/// This function can only be used before calling `inputs_done()`. Afterwards, more input-side
/// variables can be added by calling `add_killed_var()` and `add_through_var()`
pub fn add_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) {
dbg!(
"add_var({}:{}, from={})",
value,
constraint,
constraint.info.display_regunit(from)
);
debug_assert!(!self.inputs_done);
self.add_live_var(value, constraint, from, true);
}
/// Add an extra input-side variable representing a value that is killed by the current
/// instruction.
///
/// This function should be called after `inputs_done()` only. Use `add_var()` before.
pub fn add_killed_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) {
dbg!(
"add_killed_var({}:{}, from={})",
value,
constraint,
constraint.info.display_regunit(from)
);
debug_assert!(self.inputs_done);
self.add_live_var(value, constraint, from, false);
}
/// Add an extra input-side variable representing a value that is live through the current
/// instruction.
///
/// This function should be called after `inputs_done()` only. Use `add_var()` before.
pub fn add_through_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) {
dbg!(
"add_through_var({}:{}, from={})",
value,
constraint,
constraint.info.display_regunit(from)
);
debug_assert!(self.inputs_done);
self.add_live_var(value, constraint, from, true);
}
/// Shared code for `add_var`, `add_killed_var`, and `add_through_var`.
///
/// Add a variable that is live before the instruction, and possibly live through. Merge
/// constraints if the value has already been added as a variable or fixed assignment.
fn add_live_var(
&mut self,
value: Value,
constraint: RegClass,
from: RegUnit,
live_through: bool,
) {
// Check for existing entries for this value.
if self.regs_in.is_avail(constraint, from) {
// There could be an existing variable entry.
if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) {
// We have an existing variable entry for `value`. Combine the constraints.
if let Some(rc) = v.constraint.intersect(constraint) {
dbg!("-> combining constraint with {} yields {}", v, rc);
v.constraint = rc;
return;
} else {
// The spiller should have made sure the same value is not used with disjoint
// constraints.
panic!("Incompatible constraints: {} + {}", constraint, v)
}
}
// No variable, then it must be a fixed reassignment.
if let Some(a) = self.assignments.get(value) {
dbg!("-> already fixed assignment {}", a);
debug_assert!(
constraint.contains(a.to),
"Incompatible constraints for {}",
value
);
return;
}
dbg!("{}", self);
panic!("Wrong from register for {}", value);
}
let new_var = Variable::new_live(value, constraint, from, live_through);
dbg!("-> new var: {}", new_var);
self.regs_in.free(constraint, from);
if self.inputs_done && live_through {
self.regs_out.free(constraint, from);
}
self.vars.push(new_var);
}
/// Check for conflicts between fixed input assignments and existing live values.
///
/// Returns true if one of the live values conflicts with a fixed input assignment. Such a
/// conflicting value must be turned into a variable.
pub fn has_fixed_input_conflicts(&self) -> bool {
debug_assert!(!self.inputs_done);
// The `from` side of the fixed input diversions are taken from `regs_out`.
self.regs_out.interferes_with(&self.regs_in)
}
/// Check if `rc, reg` specifically conflicts with the fixed input assignments.
pub fn is_fixed_input_conflict(&self, rc: RegClass, reg: RegUnit) -> bool {
debug_assert!(!self.inputs_done);
!self.regs_out.is_avail(rc, reg)
}
/// Finish adding input side constraints.
///
/// Call this method to indicate that there will be no more fixed input reassignments added
/// and prepare for the output side constraints.
pub fn inputs_done(&mut self) {
debug_assert!(!self.has_fixed_input_conflicts());
// At this point, `regs_out` contains the `to` side of the input reassignments, and the
// `from` side has already been marked as available in `regs_in`.
//
// Remove the `to` assignments from `regs_in` so it now indicates the registers available
// to variables at the input side.
self.regs_in.intersect(&self.regs_out);
// The meaning of `regs_out` now changes completely to indicate the registers available to
// variables on the output side.
// The initial mask will be modified by `add_kill()` and `add_fixed_output()`.
self.regs_out = self.regs_in.clone();
// Now we can't add more fixed input assignments, but `add_var()` is still allowed.
self.inputs_done = true;
}
/// Record that an input register value is killed by the instruction.
///
/// Even if a fixed reassignment has been added for the value, the `reg` argument should be the
/// original location before the reassignments.
///
/// This means that the register is available on the output side.
pub fn add_kill(&mut self, value: Value, rc: RegClass, reg: RegUnit) {
debug_assert!(self.inputs_done);
// If a fixed assignment is killed, the `to` register becomes available on the output side.
if let Some(a) = self.assignments.get(value) {
debug_assert_eq!(a.from, reg);
self.regs_out.free(a.rc, a.to);
return;
}
// It's also possible that a variable is killed. That means it doesn't need to satisfy
// interference constraints on the output side.
// Variables representing tied operands will get their `is_output` flag set again later.
if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) {
debug_assert!(v.is_input);
v.is_output = false;
return;
}
// Alright, this is just a boring value being killed by the instruction. Just reclaim
// the assigned register.
self.regs_out.free(rc, reg);
}
/// Record that an input register is tied to an output register.
///
/// It is assumed that `add_kill` was called previously with the same arguments.
///
/// The output value that must have the same register as the input value is not recorded in the
/// solver.
///
/// If the value has already been assigned to a fixed register, return that.
pub fn add_tied_input(
&mut self,
value: Value,
rc: RegClass,
reg: RegUnit,
is_global: bool,
) -> Option<RegUnit> {
debug_assert!(self.inputs_done);
// If a fixed assignment is tied, the `to` register is not available on the output side.
if let Some(a) = self.assignments.get(value) {
debug_assert_eq!(a.from, reg);
self.regs_out.take(a.rc, a.to);
return Some(a.to);
}
// Check if a variable was created.
if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) {
debug_assert!(v.is_input);
v.is_output = true;
v.is_global = is_global;
return None;
}
// No variable exists for `value` because its constraints are already satisfied.
// However, if the tied output value has a global live range, we must create a variable to
// avoid global interference too.
if is_global {
let mut new_var = Variable::new_live(value, rc, reg, true);
new_var.is_global = true;
dbg!("add_tied_input: new tied-global var: {}", new_var);
self.vars.push(new_var);
self.regs_in.free(rc, reg);
} else {
self.regs_out.take(rc, reg);
}
None
}
/// Add a fixed output assignment.
///
/// This means that `to` will not be available for variables on the output side of the
/// instruction.
///
/// Returns `false` if a live value conflicts with `to`, so it couldn't be added. Find the
/// conflicting live-through value and turn it into a variable before calling this method
/// again.
#[allow(dead_code)]
pub fn add_fixed_output(&mut self, rc: RegClass, reg: RegUnit) -> bool {
debug_assert!(self.inputs_done);
if self.regs_out.is_avail(rc, reg) {
self.regs_out.take(rc, reg);
true
} else {
false
}
}
/// Add a defined output value.
///
/// This is similar to `add_var`, except the value doesn't have a prior register assignment.
pub fn add_def(&mut self, value: Value, constraint: RegClass, is_global: bool) {
debug_assert!(self.inputs_done);
self.vars
.push(Variable::new_def(value, constraint, is_global));
}
/// Clear the `is_global` flag on all solver variables.
///
/// This is used when there are not enough global registers available, and global defines have
/// to be replaced with local defines followed by a copy.
pub fn clear_all_global_flags(&mut self) {
for v in &mut self.vars {
v.is_global = false;
}
}
}
/// Error reported when the solver fails to find a solution with the current constraints.
///
/// When no solution can be found, the error indicates how constraints could be loosened to help.
pub enum SolverError {
/// There are not available registers in the given register class.
///
/// This should be resolved by turning live-through values into variables so they can be moved
/// out of the way.
Divert(RegClass),
/// There are insufficient available registers in the global set to assign an `is_global`
/// variable with the given value.
///
/// This should be resolved by converting the variable to a local one.
Global(Value),
}
/// Interface for searching for a solution.
impl Solver {
/// Try a quick-and-dirty solution.
///
/// This is expected to succeed for most instructions since the constraint problem is almost
/// always trivial.
///
/// Returns `Ok(regs)` if a solution was found.
pub fn quick_solve(&mut self, global_regs: &RegisterSet) -> Result<RegisterSet, SolverError> {
self.find_solution(global_regs)
}
/// Try harder to find a solution.
///
/// Call this method after `quick_solve()` fails.
///
/// This may return an error with a register class that has run out of registers. If registers
/// can be freed up in the starving class, this method can be called again after adding
/// variables for the freed registers.
pub fn real_solve(&mut self, global_regs: &RegisterSet) -> Result<RegisterSet, SolverError> {
// Compute domain sizes for all the variables given the current register sets.
for v in &mut self.vars {
let d = v.iter(&self.regs_in, &self.regs_out, global_regs).len();
v.domain = cmp::min(d, u16::MAX as usize) as u16;
}
// Solve for vars with small domains first to increase the chance of finding a solution.
//
// Also consider this case:
//
// v0: out, global
// v1: in
// v2: in+out
//
// If only %r0 and %r1 are available, the global constraint may cause us to assign:
//
// v0 -> %r1
// v1 -> %r0
// v2 -> !
//
// Usually in+out variables will have a smaller domain, but in the above case the domain
// size is the same, so we also prioritize in+out variables.
//
// Include the reversed previous solution for this variable partly as a stable tie breaker,
// partly to shake things up on a second attempt.
//
// Use the `from` register and value number as a tie breaker to get a stable sort.
self.vars.sort_unstable_by_key(|v| {
(
v.domain,
!(v.is_input && v.is_output),
!v.solution,
v.from.unwrap_or(0),
v.value,
)
});
dbg!("real_solve for {}", self);
self.find_solution(global_regs)
}
/// Search for a solution with the current list of variables.
///
/// If a solution was found, returns `Ok(regs)` with the set of available registers on the
/// output side after the solution. If no solution could be found, returns `Err(rc)` with the
/// constraint register class that needs more available registers.
fn find_solution(&mut self, global_regs: &RegisterSet) -> Result<RegisterSet, SolverError> {
// Available registers on the input and output sides respectively.
let mut iregs = self.regs_in.clone();
let mut oregs = self.regs_out.clone();
let mut gregs = global_regs.clone();
for v in &mut self.vars {
let rc = v.constraint;
let reg = match v.iter(&iregs, &oregs, &gregs).next() {
Some(reg) => reg,
None => {
// If `v` must avoid global interference, there is not point in requesting
// live registers be diverted. We need to make it a non-global variable.
if v.is_global && gregs.iter(rc).next().is_none() {
return Err(SolverError::Global(v.value));
} else {
return Err(SolverError::Divert(rc));
}
}
};
v.solution = reg;
if v.is_input {
iregs.take(rc, reg);
}
if v.is_output {
oregs.take(rc, reg);
}
if v.is_global {
gregs.take(rc, reg);
}
}
Ok(oregs)
}
/// Get all the variables.
pub fn vars(&self) -> &[Variable] {
&self.vars
}
/// Check if `value` can be added as a variable to help find a solution.
pub fn can_add_var(&mut self, _value: Value, constraint: RegClass, from: RegUnit) -> bool {
!self.regs_in.is_avail(constraint, from)
}
}
/// Interface for working with parallel copies once a solution has been found.
impl Solver {
/// Collect all the register moves we need to execute.
fn collect_moves(&mut self) {
self.moves.clear();
// Collect moves from the chosen solution for all non-define variables.
for v in &self.vars {
if let Some(from) = v.from {
// Omit variable solutions that don't require the value to be moved.
if from != v.solution {
self.moves.push(Move::Reg {
value: v.value,
from,
to: v.solution,
rc: v.constraint,
});
}
}
}
// Convert all of the fixed register assignments into moves, but omit the ones that are
// already in the right register.
self.moves
.extend(self.assignments.values().filter_map(Move::with_assignment));
if !(self.moves.is_empty()) {
dbg!("collect_moves: {}", DisplayList(&self.moves));
}
}
/// Try to schedule a sequence of `regmove` instructions that will shuffle registers into
/// place.
///
/// This may require the use of additional available registers, and it can fail if no
/// additional registers are available.
///
/// TODO: Handle failure by generating a sequence of register swaps, or by temporarily spilling
/// a register.
///
/// Returns the number of spills that had to be emitted.
pub fn schedule_moves(&mut self, regs: &RegisterSet) -> usize {
self.collect_moves();
debug_assert!(self.fills.is_empty());
let mut num_spill_slots = 0;
let mut avail = regs.clone();
let mut i = 0;
while i < self.moves.len() + self.fills.len() {
// Don't even look at the fills until we've spent all the moves. Deferring these let's
// us potentially reuse the claimed registers to resolve multiple cycles.
if i >= self.moves.len() {
self.moves.append(&mut self.fills);
}
// Find the first move that can be executed now.
if let Some(j) = self.moves[i..].iter().position(|m| match m.to_reg() {
Some((rc, reg)) => avail.is_avail(rc, reg),
None => true,
}) {
// This move can be executed now.
self.moves.swap(i, i + j);
let m = &self.moves[i];
if let Some((rc, reg)) = m.to_reg() {
avail.take(rc, reg);
}
if let Some((rc, reg)) = m.from_reg() {
avail.free(rc, reg);
}
dbg!("move #{}: {}", i, m);
i += 1;
continue;
}
// When we get here, none of the `moves[i..]` can be executed. This means there are
// only cycles remaining. The cycles can be broken in a few ways:
//
// 1. Grab an available register and use it to break a cycle.
// 2. Move a value temporarily into a stack slot instead of a register.
// 3. Use swap instructions.
//
// TODO: So far we only implement 1 and 2.
// Pick an assignment with the largest possible width. This is more likely to break up
// a cycle than an assignment with fewer register units. For example, it may be
// necessary to move two arm32 S-registers out of the way before a D-register can move
// into place.
//
// We use `min_by_key` and `!` instead of `max_by_key` because it preserves the
// existing order of moves with the same width.
let j = self.moves[i..]
.iter()
.enumerate()
.min_by_key(|&(_, m)| !m.rc().width)
.unwrap()
.0;
self.moves.swap(i, i + j);
// Check the top-level register class for an available register. It is an axiom of the
// register allocator that we can move between all registers in the top-level RC.
let m = self.moves[i].clone();
let toprc = m.rc().toprc();
if let Some(reg) = avail.iter(toprc).next() {
dbg!(
"breaking cycle at {} with available {} register {}",
m,
toprc,
toprc.info.display_regunit(reg)
);
// Alter the move so it is guaranteed to be picked up when we loop. It is important
// that this move is scheduled immediately, otherwise we would have multiple moves
// of the same value, and they would not be commutable.
let old_to_reg = self.moves[i].replace_to_reg(reg);
// Append a fixup move so we end up in the right place. This move will be scheduled
// later. That's ok because it is the single remaining move of `m.value` after the
// next iteration.
self.moves.push(Move::Reg {
value: m.value(),
rc: toprc,
from: reg,
to: old_to_reg,
});
// TODO: What if allocating an extra register is not enough to break a cycle? This
// can happen when there are registers of different widths in a cycle. For ARM, we
// may have to move two S-registers out of the way before we can resolve a cycle
// involving a D-register.
continue;
}
// It was impossible to free up a register in toprc, so use an emergency spill slot as
// a last resort.
let slot = num_spill_slots;
num_spill_slots += 1;
dbg!("breaking cycle at {} with slot {}", m, slot);
let old_to_reg = self.moves[i].change_to_spill(slot);
self.fills.push(Move::Fill {
value: m.value(),
rc: toprc,
from_slot: slot,
to: old_to_reg,
});
}
num_spill_slots
}
/// Borrow the scheduled set of register moves that was computed by `schedule_moves()`.
pub fn moves(&self) -> &[Move] {
&self.moves
}
}
impl fmt::Display for Solver {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let reginfo = self.vars.first().map(|v| v.constraint.info);
writeln!(f, "Solver {{ inputs_done: {},", self.inputs_done)?;
writeln!(f, " in: {}", self.regs_in.display(reginfo))?;
writeln!(f, " out: {}", self.regs_out.display(reginfo))?;
writeln!(
f,
" assignments: {}",
DisplayList(self.assignments.as_slice())
)?;
writeln!(f, " vars: {}", DisplayList(&self.vars))?;
writeln!(f, " moves: {}", DisplayList(&self.moves))?;
writeln!(f, "}}")
}
}
#[cfg(test)]
#[cfg(build_arm32)]
mod tests {
use super::{Move, Solver};
use entity::EntityRef;
use ir::Value;
use isa::{RegClass, RegInfo, RegUnit, TargetIsa};
use regalloc::RegisterSet;
use std::boxed::Box;
// Make an arm32 `TargetIsa`, if possible.
fn arm32() -> Option<Box<TargetIsa>> {
use isa;
use settings;
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
isa::lookup("arm32").ok().map(|b| b.finish(shared_flags))
}
// Get a register class by name.
fn rc_by_name(reginfo: &RegInfo, name: &str) -> RegClass {
reginfo
.classes
.iter()
.find(|rc| rc.name == name)
.expect("Can't find named register class.")
}
// Construct a register move.
fn mov(value: Value, rc: RegClass, from: RegUnit, to: RegUnit) -> Move {
Move::Reg {
value,
rc,
from,
to,
}
}
fn spill(value: Value, rc: RegClass, from: RegUnit, to_slot: usize) -> Move {
Move::Spill {
value,
rc,
from,
to_slot,
}
}
fn fill(value: Value, rc: RegClass, from_slot: usize, to: RegUnit) -> Move {
Move::Fill {
value,
rc,
from_slot,
to,
}
}
#[test]
fn simple_moves() {
let isa = arm32().expect("This test requires arm32 support");
let reginfo = isa.register_info();
let gpr = rc_by_name(&reginfo, "GPR");
let r0 = gpr.unit(0);
let r1 = gpr.unit(1);
let r2 = gpr.unit(2);
let gregs = RegisterSet::new();
let mut regs = RegisterSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
let v11 = Value::new(11);
// As simple as it gets: Value is in r1, we want r0.
regs.take(gpr, r1);
solver.reset(&regs);
solver.reassign_in(v10, gpr, r1, r0);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(solver.moves(), &[mov(v10, gpr, r1, r0)]);
// A bit harder: r0, r1 need to go in r1, r2.
regs.take(gpr, r0);
solver.reset(&regs);
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r2);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
&[mov(v11, gpr, r1, r2), mov(v10, gpr, r0, r1)]
);
// Swap r0 and r1 in three moves using r2 as a scratch.
solver.reset(&regs);
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r0);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
&[
mov(v10, gpr, r0, r2),
mov(v11, gpr, r1, r0),
mov(v10, gpr, r2, r1),
]
);
}
#[test]
fn harder_move_cycles() {
let isa = arm32().expect("This test requires arm32 support");
let reginfo = isa.register_info();
let s = rc_by_name(&reginfo, "S");
let d = rc_by_name(&reginfo, "D");
let d0 = d.unit(0);
let d1 = d.unit(1);
let d2 = d.unit(2);
let s0 = s.unit(0);
let s1 = s.unit(1);
let s2 = s.unit(2);
let s3 = s.unit(3);
let gregs = RegisterSet::new();
let mut regs = RegisterSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
let v11 = Value::new(11);
let v12 = Value::new(12);
// Not a simple cycle: Swap d0 <-> (s2, s3)
regs.take(d, d0);
regs.take(d, d1);
solver.reset(&regs);
solver.reassign_in(v10, d, d0, d1);
solver.reassign_in(v11, s, s2, s0);
solver.reassign_in(v12, s, s3, s1);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
&[
mov(v10, d, d0, d2),
mov(v11, s, s2, s0),
mov(v12, s, s3, s1),
mov(v10, d, d2, d1),
]
);
// Same problem in the other direction: Swap (s0, s1) <-> d1.
//
// If we divert the moves in order, we will need to allocate *two* temporary S registers. A
// trivial algorithm might assume that allocating a single temp is enough.
solver.reset(&regs);
solver.reassign_in(v11, s, s0, s2);
solver.reassign_in(v12, s, s1, s3);
solver.reassign_in(v10, d, d1, d0);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
&[
mov(v10, d, d1, d2),
mov(v12, s, s1, s3),
mov(v11, s, s0, s2),
mov(v10, d, d2, d0),
]
);
}
#[test]
fn emergency_spill() {
let isa = arm32().expect("This test requires arm32 support");
let reginfo = isa.register_info();
let gpr = rc_by_name(&reginfo, "GPR");
let r0 = gpr.unit(0);
let r1 = gpr.unit(1);
let r2 = gpr.unit(2);
let r3 = gpr.unit(3);
let r4 = gpr.unit(4);
let r5 = gpr.unit(5);
let gregs = RegisterSet::new();
let mut regs = RegisterSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
let v11 = Value::new(11);
let v12 = Value::new(12);
let v13 = Value::new(13);
let v14 = Value::new(14);
let v15 = Value::new(15);
// Claim r0--r2 and r3--r15 for other values.
for i in 0..16 {
regs.take(gpr, gpr.unit(i));
}
// Request a permutation cycle.
solver.reset(&regs);
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r2);
solver.reassign_in(v12, gpr, r2, r0);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 1);
assert_eq!(
solver.moves(),
&[
spill(v10, gpr, r0, 0),
mov(v12, gpr, r2, r0),
mov(v11, gpr, r1, r2),
fill(v10, gpr, 0, r1),
]
);
// Two cycles should only require a single spill.
solver.reset(&regs);
// Cycle 1.
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r2);
solver.reassign_in(v12, gpr, r2, r0);
// Cycle 2.
solver.reassign_in(v13, gpr, r3, r4);
solver.reassign_in(v14, gpr, r4, r5);
solver.reassign_in(v15, gpr, r5, r3);
solver.inputs_done();
assert!(solver.quick_solve(&gregs).is_ok());
// We resolve two cycles with one spill.
assert_eq!(solver.schedule_moves(&regs), 1);
assert_eq!(
solver.moves(),
&[
spill(v10, gpr, r0, 0),
mov(v12, gpr, r2, r0),
mov(v11, gpr, r1, r2),
mov(v13, gpr, r3, r1), // Use available r1 to break cycle 2.
mov(v15, gpr, r5, r3),
mov(v14, gpr, r4, r5),
mov(v13, gpr, r1, r4),
fill(v10, gpr, 0, r1), // Finally complete cycle 1.
]
);
}
}