Implement value affinities for register allocation.

An SSA value is usually biased towards a specific register class or a
stack slot, depending on the constraints of the instructions using it.

Represent this bias as an Affinity enum, and implement a merging
algorithm for updating an affinity to satisfy a new constraint.

Affinities will be computed as part of the liveness analysis. This is
not implemented yet.
This commit is contained in:
Jakob Stoklund Olesen
2017-01-26 14:51:49 -08:00
parent a395f01b3e
commit 3c4d54c4bd
7 changed files with 90 additions and 7 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
tags tags
target target
Cargo.lock Cargo.lock
.*.rustfmt

View File

@@ -21,6 +21,7 @@ pub struct OperandConstraint {
} }
/// The different kinds of operand constraints. /// The different kinds of operand constraints.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ConstraintKind { pub enum ConstraintKind {
/// This operand or result must be a register from the given register class. /// This operand or result must be a register from the given register class.
Reg, Reg,

View File

@@ -41,8 +41,8 @@
//! concurrent function compilations. //! concurrent function compilations.
pub use isa::encoding::Encoding; pub use isa::encoding::Encoding;
pub use isa::registers::{RegInfo, RegUnit, RegClass}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex};
pub use isa::constraints::RecipeConstraints; pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind};
use settings; use settings;
use ir::{InstructionData, DataFlowGraph}; use ir::{InstructionData, DataFlowGraph};

View File

@@ -133,6 +133,12 @@ impl RegClassData {
Some(RegClassIndex(mask.trailing_zeros() as u8)) Some(RegClassIndex(mask.trailing_zeros() as u8))
} }
} }
/// Returns true if `other` is a subclass of this register class.
/// A register class is considerd to be a subclass of itself.
pub fn has_subclass<RCI: Into<RegClassIndex>>(&self, other: RCI) -> bool {
self.subclasses & (1 << other.into().0) != 0
}
} }
/// A small reference to a register class. /// A small reference to a register class.

View File

@@ -0,0 +1,68 @@
//! Value affinity for register allocation.
//!
//! An SSA value's affinity is a hint used to guide the register allocator. It specifies the class
//! of allocation that is likely to cause the least amount of fixup moves in order to satisfy
//! instruction operand constraints.
//!
//! For values that want to be in registers, the affinity hint includes a register class or
//! subclass. This is just a hint, and the register allocator is allowed to pick a register from a
//! larger register class instead.
use isa::{RegInfo, RegClassIndex, OperandConstraint, ConstraintKind};
/// Preferred register allocation for an SSA value.
#[derive(Clone, Copy)]
pub enum Affinity {
/// Don't care. This value can go anywhere.
Any,
/// This value should be placed in a spill slot on the stack.
Stack,
/// This value prefers a register from the given register class.
Reg(RegClassIndex),
}
impl Default for Affinity {
fn default() -> Self {
Affinity::Any
}
}
impl Affinity {
/// Create an affinity that satisfies a single constraint.
///
/// This will never create the indifferent `Affinity::Any`.
/// Use the `Default` implementation for that.
pub fn new(constraint: &OperandConstraint) -> Affinity {
if constraint.kind == ConstraintKind::Stack {
Affinity::Stack
} else {
Affinity::Reg(constraint.regclass.into())
}
}
/// Merge an operand constraint into this affinity.
///
/// Note that this does not guarantee that the register allocator will pick a register that
/// satisfies the constraint.
pub fn merge(&mut self, constraint: &OperandConstraint, reg_info: &RegInfo) {
match *self {
Affinity::Any => *self = Affinity::new(constraint),
Affinity::Reg(rc) => {
// If the preferred register class is a subclass of the constraint, there's no need
// to change anything.
if constraint.kind != ConstraintKind::Stack &&
!constraint.regclass.has_subclass(rc) {
// If the register classes don't overlap, `intersect` returns `None`, and we
// just keep our previous affinity.
if let Some(subclass) = constraint.regclass.intersect(reg_info.rc(rc)) {
// This constraint shrinks our preferred register class.
*self = Affinity::Reg(subclass);
}
}
}
Affinity::Stack => {}
}
}
}

View File

@@ -73,9 +73,9 @@
//! 3. A numerical order by EBB number. Performant because it doesn't need to indirect through the //! 3. A numerical order by EBB number. Performant because it doesn't need to indirect through the
//! `ProgramOrder` for comparisons. //! `ProgramOrder` for comparisons.
//! //!
//! These orderings will cause small differences in coalescing opportinities, but all of them would //! These orderings will cause small differences in coalescing opportunities, but all of them would
//! do a decent job of compressing a long live range. The numerical order might be preferable //! do a decent job of compressing a long live range. The numerical order might be preferable
//! beacuse: //! because:
//! //!
//! - It has better performance because EBB numbers can be compared directly without any table //! - It has better performance because EBB numbers can be compared directly without any table
//! lookups. //! lookups.
@@ -92,7 +92,7 @@
//! //!
//! Coalescing is an important compression technique because some live ranges can span thousands of //! Coalescing is an important compression technique because some live ranges can span thousands of
//! EBBs. We can represent that by switching to a sorted `Vec<ProgramPoint>` representation where //! EBBs. We can represent that by switching to a sorted `Vec<ProgramPoint>` representation where
//! an `[Ebb, Inst]` pair represents a coalesced range, while an `Inst` entry without a preceeding //! an `[Ebb, Inst]` pair represents a coalesced range, while an `Inst` entry without a preceding
//! `Ebb` entry represents a single live-in interval. //! `Ebb` entry represents a single live-in interval.
//! //!
//! This representation is more compact for a live range with many uncoalesced live-in intervals. //! This representation is more compact for a live range with many uncoalesced live-in intervals.
@@ -109,6 +109,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use ir::{Inst, Ebb, Value, ProgramPoint, ProgramOrder}; use ir::{Inst, Ebb, Value, ProgramPoint, ProgramOrder};
use regalloc::affinity::Affinity;
use sparse_map::SparseMapValue; use sparse_map::SparseMapValue;
/// Global live range of a single SSA value. /// Global live range of a single SSA value.
@@ -143,6 +144,9 @@ pub struct LiveRange {
/// This member can't be modified in case the live range is stored in a `SparseMap`. /// This member can't be modified in case the live range is stored in a `SparseMap`.
value: Value, value: Value,
/// The preferred register allocation for this value.
pub affinity: Affinity,
/// The instruction or EBB header where this value is defined. /// The instruction or EBB header where this value is defined.
def_begin: ProgramPoint, def_begin: ProgramPoint,
@@ -167,7 +171,7 @@ pub struct LiveRange {
/// An additional contiguous interval of a global live range. /// An additional contiguous interval of a global live range.
/// ///
/// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals /// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals
/// for contiguous EBBs where all but the last live-in inteval covers the whole EBB. /// for contiguous EBBs where all but the last live-in interval covers the whole EBB.
/// ///
struct Interval { struct Interval {
/// Interval starting point. /// Interval starting point.
@@ -205,6 +209,7 @@ impl LiveRange {
let def = def.into(); let def = def.into();
LiveRange { LiveRange {
value: value, value: value,
affinity: Default::default(),
def_begin: def, def_begin: def,
def_end: def, def_end: def,
liveins: Vec::new(), liveins: Vec::new(),
@@ -238,7 +243,7 @@ impl LiveRange {
/// is live-in to `ebb`, extending to `to`. Return true. /// is live-in to `ebb`, extending to `to`. Return true.
/// ///
/// The return value can be used to detect if we just learned that the value is live-in to /// The return value can be used to detect if we just learned that the value is live-in to
/// `ebb`. This can trigger recursive extensions in `ebb`'s CFG precedessor blocks. /// `ebb`. This can trigger recursive extensions in `ebb`'s CFG predecessor blocks.
pub fn extend_in_ebb<PO: ProgramOrder>(&mut self, ebb: Ebb, to: Inst, order: &PO) -> bool { pub fn extend_in_ebb<PO: ProgramOrder>(&mut self, ebb: Ebb, to: Inst, order: &PO) -> bool {
// First check if we're extending the def interval. // First check if we're extending the def interval.
// //

View File

@@ -5,3 +5,5 @@
pub mod liverange; pub mod liverange;
pub mod liveness; pub mod liveness;
pub mod allocatable_set; pub mod allocatable_set;
mod affinity;