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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
tags
|
tags
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
.*.rustfmt
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
68
lib/cretonne/src/regalloc/affinity.rs
Normal file
68
lib/cretonne/src/regalloc/affinity.rs
Normal 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 => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user