This generalizes the allocator to accept multiple defs by making defs just another type of "use" (uses are now perhaps more properly called "mentions", but for now we abuse the terminology slightly). It turns out that this actually was not terribly hard, because we don't rely on the properties that a strict SSA requirement otherwise might allow us to: e.g., defs always at exactly the start of a vreg's ranges. Because we already accepted arbitrary block order and irreducible CFGs, and approximated live-ranges with the single-pass algorithm, we are robust in our "stitching" (move insertion) and so all we really care about is computing some superset of the actual live-ranges and then a non-interfering coloring of (split pieces of) those ranges. Multiple defs don't change that, as long as we compute the ranges properly. We still have blockparams in this design, so the client *can* provide SSA directly, and everything will work as before. But a client that produces non-SSA need not use them at all; it can just happily reassign to vregs and everything will Just Work. This is part of the effort to port Cranelift over to regalloc2; I have decided that it may be easier to build a compatibility shim that matches regalloc.rs's interface than to continue boiling the ocean and converting all of the lowering sequences to SSA. It then becomes a separable piece of work (and simply further performance improvements and simplifications) to remove the need for this shim.
939 lines
28 KiB
Rust
939 lines
28 KiB
Rust
/*
|
|
* The fellowing license applies to this file, which derives many
|
|
* details (register and constraint definitions, for example) from the
|
|
* files `BacktrackingAllocator.h`, `BacktrackingAllocator.cpp`,
|
|
* `LIR.h`, and possibly definitions in other related files in
|
|
* `js/src/jit/`:
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#![allow(dead_code)]
|
|
|
|
pub mod bitvec;
|
|
pub mod cfg;
|
|
pub mod domtree;
|
|
pub mod ion;
|
|
pub mod moves;
|
|
pub mod postorder;
|
|
pub mod ssa;
|
|
|
|
#[macro_use]
|
|
pub mod index;
|
|
pub use index::{Block, Inst, InstRange, InstRangeIter};
|
|
|
|
pub mod checker;
|
|
|
|
#[cfg(feature = "fuzzing")]
|
|
pub mod fuzzing;
|
|
|
|
/// Register classes.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum RegClass {
|
|
Int = 0,
|
|
Float = 1,
|
|
}
|
|
|
|
/// A physical register. Contains a physical register number and a class.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct PReg(u8, RegClass);
|
|
|
|
impl PReg {
|
|
pub const MAX_BITS: usize = 5;
|
|
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
|
|
pub const MAX_INDEX: usize = 2 * Self::MAX; // including RegClass bit
|
|
|
|
/// Create a new PReg. The `hw_enc` range is 6 bits.
|
|
#[inline(always)]
|
|
pub fn new(hw_enc: usize, class: RegClass) -> Self {
|
|
assert!(hw_enc <= Self::MAX);
|
|
PReg(hw_enc as u8, class)
|
|
}
|
|
|
|
/// The physical register number, as encoded by the ISA for the particular register class.
|
|
#[inline(always)]
|
|
pub fn hw_enc(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
|
|
/// The register class.
|
|
#[inline(always)]
|
|
pub fn class(self) -> RegClass {
|
|
self.1
|
|
}
|
|
|
|
/// Get an index into the (not necessarily contiguous) index space of
|
|
/// all physical registers. Allows one to maintain an array of data for
|
|
/// all PRegs and index it efficiently.
|
|
#[inline(always)]
|
|
pub fn index(self) -> usize {
|
|
((self.1 as u8 as usize) << 5) | (self.0 as usize)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn from_index(index: usize) -> Self {
|
|
let class = (index >> 5) & 1;
|
|
let class = match class {
|
|
0 => RegClass::Int,
|
|
1 => RegClass::Float,
|
|
_ => unreachable!(),
|
|
};
|
|
let index = index & Self::MAX;
|
|
PReg::new(index, class)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn invalid() -> Self {
|
|
PReg::new(Self::MAX, RegClass::Int)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for PReg {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"PReg(hw = {}, class = {:?}, index = {})",
|
|
self.hw_enc(),
|
|
self.class(),
|
|
self.index()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for PReg {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
let class = match self.class() {
|
|
RegClass::Int => "i",
|
|
RegClass::Float => "f",
|
|
};
|
|
write!(f, "p{}{}", self.hw_enc(), class)
|
|
}
|
|
}
|
|
|
|
/// A virtual register. Contains a virtual register number and a class.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct VReg(u32);
|
|
|
|
impl VReg {
|
|
pub const MAX_BITS: usize = 20;
|
|
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
|
|
|
|
#[inline(always)]
|
|
pub fn new(virt_reg: usize, class: RegClass) -> Self {
|
|
assert!(virt_reg <= Self::MAX);
|
|
VReg(((virt_reg as u32) << 1) | (class as u8 as u32))
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn vreg(self) -> usize {
|
|
(self.0 >> 1) as usize
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn class(self) -> RegClass {
|
|
match self.0 & 1 {
|
|
0 => RegClass::Int,
|
|
1 => RegClass::Float,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn invalid() -> Self {
|
|
VReg::new(Self::MAX, RegClass::Int)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for VReg {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"VReg(vreg = {}, class = {:?})",
|
|
self.vreg(),
|
|
self.class()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for VReg {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "v{}", self.vreg())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct SpillSlot(u32);
|
|
|
|
impl SpillSlot {
|
|
#[inline(always)]
|
|
pub fn new(slot: usize, class: RegClass) -> Self {
|
|
assert!(slot < (1 << 24));
|
|
SpillSlot((slot as u32) | (class as u8 as u32) << 24)
|
|
}
|
|
#[inline(always)]
|
|
pub fn index(self) -> usize {
|
|
(self.0 & 0x00ffffff) as usize
|
|
}
|
|
#[inline(always)]
|
|
pub fn class(self) -> RegClass {
|
|
match (self.0 >> 24) as u8 {
|
|
0 => RegClass::Int,
|
|
1 => RegClass::Float,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
pub fn plus(self, offset: usize) -> Self {
|
|
SpillSlot::new(self.index() + offset, self.class())
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for SpillSlot {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "stack{}", self.index())
|
|
}
|
|
}
|
|
|
|
/// An `Operand` encodes everything about a mention of a register in
|
|
/// an instruction: virtual register number, and any constraint/policy
|
|
/// that applies to the register at this program point.
|
|
///
|
|
/// An Operand may be a use or def (this corresponds to `LUse` and
|
|
/// `LAllocation` in Ion).
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
pub struct Operand {
|
|
/// Bit-pack into 32 bits. Note that `policy` overlaps with `kind`
|
|
/// in `Allocation` and we use mutually disjoint tag-value ranges
|
|
/// so that clients, if they wish, can track just one `u32` per
|
|
/// register slot and edit it in-place after allocation.
|
|
///
|
|
/// policy:3 kind:1 pos:2 class:1 preg:5 vreg:20
|
|
bits: u32,
|
|
}
|
|
|
|
impl Operand {
|
|
#[inline(always)]
|
|
pub fn new(vreg: VReg, policy: OperandPolicy, kind: OperandKind, pos: OperandPos) -> Self {
|
|
let (preg_field, policy_field): (u32, u32) = match policy {
|
|
OperandPolicy::Any => (0, 0),
|
|
OperandPolicy::Reg => (0, 1),
|
|
OperandPolicy::Stack => (0, 2),
|
|
OperandPolicy::FixedReg(preg) => {
|
|
assert_eq!(preg.class(), vreg.class());
|
|
(preg.hw_enc() as u32, 3)
|
|
}
|
|
OperandPolicy::Reuse(which) => {
|
|
assert!(which <= PReg::MAX);
|
|
(which as u32, 4)
|
|
}
|
|
};
|
|
let class_field = vreg.class() as u8 as u32;
|
|
let pos_field = pos as u8 as u32;
|
|
let kind_field = kind as u8 as u32;
|
|
Operand {
|
|
bits: vreg.vreg() as u32
|
|
| (preg_field << 20)
|
|
| (class_field << 25)
|
|
| (pos_field << 26)
|
|
| (kind_field << 28)
|
|
| (policy_field << 29),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn reg_use(vreg: VReg) -> Self {
|
|
Operand::new(
|
|
vreg,
|
|
OperandPolicy::Reg,
|
|
OperandKind::Use,
|
|
OperandPos::Before,
|
|
)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_use_at_end(vreg: VReg) -> Self {
|
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Use, OperandPos::Both)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_def(vreg: VReg) -> Self {
|
|
Operand::new(
|
|
vreg,
|
|
OperandPolicy::Reg,
|
|
OperandKind::Def,
|
|
OperandPos::After,
|
|
)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_def_at_start(vreg: VReg) -> Self {
|
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Def, OperandPos::Both)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_temp(vreg: VReg) -> Self {
|
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Def, OperandPos::Both)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_reuse_def(vreg: VReg, idx: usize) -> Self {
|
|
Operand::new(
|
|
vreg,
|
|
OperandPolicy::Reuse(idx),
|
|
OperandKind::Def,
|
|
OperandPos::Both,
|
|
)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_fixed_use(vreg: VReg, preg: PReg) -> Self {
|
|
Operand::new(
|
|
vreg,
|
|
OperandPolicy::FixedReg(preg),
|
|
OperandKind::Use,
|
|
OperandPos::Before,
|
|
)
|
|
}
|
|
#[inline(always)]
|
|
pub fn reg_fixed_def(vreg: VReg, preg: PReg) -> Self {
|
|
Operand::new(
|
|
vreg,
|
|
OperandPolicy::FixedReg(preg),
|
|
OperandKind::Def,
|
|
OperandPos::After,
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn vreg(self) -> VReg {
|
|
let vreg_idx = ((self.bits as usize) & VReg::MAX) as usize;
|
|
VReg::new(vreg_idx, self.class())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn class(self) -> RegClass {
|
|
let class_field = (self.bits >> 25) & 1;
|
|
match class_field {
|
|
0 => RegClass::Int,
|
|
1 => RegClass::Float,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn kind(self) -> OperandKind {
|
|
let kind_field = (self.bits >> 28) & 1;
|
|
match kind_field {
|
|
0 => OperandKind::Def,
|
|
1 => OperandKind::Use,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn pos(self) -> OperandPos {
|
|
let pos_field = (self.bits >> 26) & 3;
|
|
match pos_field {
|
|
0 => OperandPos::Before,
|
|
1 => OperandPos::After,
|
|
2 => OperandPos::Both,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn policy(self) -> OperandPolicy {
|
|
let policy_field = (self.bits >> 29) & 7;
|
|
let preg_field = ((self.bits >> 20) as usize) & PReg::MAX;
|
|
match policy_field {
|
|
0 => OperandPolicy::Any,
|
|
1 => OperandPolicy::Reg,
|
|
2 => OperandPolicy::Stack,
|
|
3 => OperandPolicy::FixedReg(PReg::new(preg_field, self.class())),
|
|
4 => OperandPolicy::Reuse(preg_field),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn bits(self) -> u32 {
|
|
self.bits
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn from_bits(bits: u32) -> Self {
|
|
debug_assert!(bits >> 29 <= 4);
|
|
Operand { bits }
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Operand {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Operand {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{:?}@{:?}: {}{} {}",
|
|
self.kind(),
|
|
self.pos(),
|
|
self.vreg(),
|
|
match self.class() {
|
|
RegClass::Int => "i",
|
|
RegClass::Float => "f",
|
|
},
|
|
self.policy()
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum OperandPolicy {
|
|
/// Any location is fine (register or stack slot).
|
|
Any,
|
|
/// Operand must be in a register. Register is read-only for Uses.
|
|
Reg,
|
|
/// Operand must be on the stack.
|
|
Stack,
|
|
/// Operand must be in a fixed register.
|
|
FixedReg(PReg),
|
|
/// On defs only: reuse a use's register. Which use is given by `preg` field.
|
|
Reuse(usize),
|
|
}
|
|
|
|
impl std::fmt::Display for OperandPolicy {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
Self::Any => write!(f, "any"),
|
|
Self::Reg => write!(f, "reg"),
|
|
Self::Stack => write!(f, "stack"),
|
|
Self::FixedReg(preg) => write!(f, "fixed({})", preg),
|
|
Self::Reuse(idx) => write!(f, "reuse({})", idx),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum OperandKind {
|
|
Def = 0,
|
|
Use = 1,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum OperandPos {
|
|
Before = 0,
|
|
After = 1,
|
|
Both = 2,
|
|
}
|
|
|
|
/// An Allocation represents the end result of regalloc for an
|
|
/// Operand.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Allocation {
|
|
/// Bit-pack in 32 bits. Note that `kind` overlaps with the
|
|
/// `policy` field in `Operand`, and we are careful to use
|
|
/// disjoint ranges of values in this field for each type. We also
|
|
/// leave the def-or-use bit (`kind` for `Operand`) unused here so
|
|
/// that we can use it below in `OperandOrAllocation` to record
|
|
/// whether `Allocation`s are defs or uses (which is often useful
|
|
/// to know).
|
|
///
|
|
/// kind:3 unused:1 index:28
|
|
bits: u32,
|
|
}
|
|
|
|
impl std::fmt::Debug for Allocation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Allocation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self.kind() {
|
|
AllocationKind::None => write!(f, "none"),
|
|
AllocationKind::Reg => write!(f, "{}", self.as_reg().unwrap()),
|
|
AllocationKind::Stack => write!(f, "{}", self.as_stack().unwrap()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Allocation {
|
|
#[inline(always)]
|
|
pub(crate) fn new(kind: AllocationKind, index: usize) -> Self {
|
|
assert!(index < (1 << 28));
|
|
Self {
|
|
bits: ((kind as u8 as u32) << 29) | (index as u32),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn none() -> Allocation {
|
|
Allocation::new(AllocationKind::None, 0)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn reg(preg: PReg) -> Allocation {
|
|
Allocation::new(AllocationKind::Reg, preg.index())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn stack(slot: SpillSlot) -> Allocation {
|
|
Allocation::new(AllocationKind::Stack, slot.0 as usize)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn kind(self) -> AllocationKind {
|
|
match (self.bits >> 29) & 7 {
|
|
5 => AllocationKind::None,
|
|
6 => AllocationKind::Reg,
|
|
7 => AllocationKind::Stack,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn is_none(self) -> bool {
|
|
self.kind() == AllocationKind::None
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn is_reg(self) -> bool {
|
|
self.kind() == AllocationKind::Reg
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn is_stack(self) -> bool {
|
|
self.kind() == AllocationKind::Stack
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn index(self) -> usize {
|
|
(self.bits & ((1 << 28) - 1)) as usize
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn as_reg(self) -> Option<PReg> {
|
|
if self.kind() == AllocationKind::Reg {
|
|
Some(PReg::from_index(self.index()))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn as_stack(self) -> Option<SpillSlot> {
|
|
if self.kind() == AllocationKind::Stack {
|
|
Some(SpillSlot(self.index() as u32))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn bits(self) -> u32 {
|
|
self.bits
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn from_bits(bits: u32) -> Self {
|
|
debug_assert!(bits >> 29 >= 5);
|
|
Self { bits }
|
|
}
|
|
}
|
|
|
|
// N.B.: These values must be *disjoint* with the values used to
|
|
// encode `OperandPolicy`, because they share a 3-bit field.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
#[repr(u8)]
|
|
pub enum AllocationKind {
|
|
None = 5,
|
|
Reg = 6,
|
|
Stack = 7,
|
|
}
|
|
|
|
impl Allocation {
|
|
#[inline(always)]
|
|
pub fn class(self) -> RegClass {
|
|
match self.kind() {
|
|
AllocationKind::None => panic!("Allocation::None has no class"),
|
|
AllocationKind::Reg => self.as_reg().unwrap().class(),
|
|
AllocationKind::Stack => self.as_stack().unwrap().class(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A helper that wraps either an `Operand` or an `Allocation` and is
|
|
/// able to tell which it is based on the tag bits.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct OperandOrAllocation {
|
|
bits: u32,
|
|
}
|
|
|
|
impl OperandOrAllocation {
|
|
pub fn from_operand(operand: Operand) -> Self {
|
|
debug_assert!(operand.bits() >> 29 <= 4);
|
|
Self {
|
|
bits: operand.bits(),
|
|
}
|
|
}
|
|
pub fn from_alloc(alloc: Allocation) -> Self {
|
|
debug_assert!(alloc.bits() >> 29 >= 5);
|
|
Self { bits: alloc.bits() }
|
|
}
|
|
pub fn from_alloc_and_kind(alloc: Allocation, kind: OperandKind) -> Self {
|
|
debug_assert!(alloc.bits() >> 29 >= 5);
|
|
let bits = alloc.bits()
|
|
| match kind {
|
|
OperandKind::Def => 0,
|
|
OperandKind::Use => 1 << 28,
|
|
};
|
|
Self { bits }
|
|
}
|
|
pub fn is_operand(&self) -> bool {
|
|
(self.bits >> 29) <= 4
|
|
}
|
|
pub fn is_allocation(&self) -> bool {
|
|
(self.bits >> 29) >= 5
|
|
}
|
|
pub fn as_operand(&self) -> Option<Operand> {
|
|
if self.is_operand() {
|
|
Some(Operand::from_bits(self.bits))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
pub fn as_allocation(&self) -> Option<Allocation> {
|
|
if self.is_allocation() {
|
|
// Remove the def/use bit -- the canonical `Allocation`
|
|
// does not have this, and we want allocs to continue to
|
|
// be comparable whether they are used for reads or
|
|
// writes.
|
|
Some(Allocation::from_bits(self.bits & !(1 << 28)))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn kind(&self) -> OperandKind {
|
|
let kind_field = (self.bits >> 28) & 1;
|
|
match kind_field {
|
|
0 => OperandKind::Def,
|
|
1 => OperandKind::Use,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// Replaces the Operand with an Allocation, keeping the def/use bit.
|
|
pub fn replace_with_alloc(&mut self, alloc: Allocation) {
|
|
self.bits &= 1 << 28;
|
|
self.bits |= alloc.bits;
|
|
}
|
|
}
|
|
|
|
/// A trait defined by the regalloc client to provide access to its
|
|
/// machine-instruction / CFG representation.
|
|
///
|
|
/// (This trait's design is inspired by, and derives heavily from, the
|
|
/// trait of the same name in regalloc.rs.)
|
|
pub trait Function {
|
|
// -------------
|
|
// CFG traversal
|
|
// -------------
|
|
|
|
/// How many instructions are there?
|
|
fn insts(&self) -> usize;
|
|
|
|
/// How many blocks are there?
|
|
fn blocks(&self) -> usize;
|
|
|
|
/// Get the index of the entry block.
|
|
fn entry_block(&self) -> Block;
|
|
|
|
/// Provide the range of instruction indices contained in each block.
|
|
fn block_insns(&self, block: Block) -> InstRange;
|
|
|
|
/// Get CFG successors for a given block.
|
|
fn block_succs(&self, block: Block) -> &[Block];
|
|
|
|
/// Get the CFG predecessors for a given block.
|
|
fn block_preds(&self, block: Block) -> &[Block];
|
|
|
|
/// Get the block parameters for a given block.
|
|
fn block_params(&self, block: Block) -> &[VReg];
|
|
|
|
/// Determine whether an instruction is a call instruction. This is used
|
|
/// only for splitting heuristics.
|
|
fn is_call(&self, insn: Inst) -> bool;
|
|
|
|
/// Determine whether an instruction is a return instruction.
|
|
fn is_ret(&self, insn: Inst) -> bool;
|
|
|
|
/// Determine whether an instruction is the end-of-block
|
|
/// branch. If so, its operands at the indices given by
|
|
/// `branch_blockparam_arg_offset()` below *must* be the block
|
|
/// parameters for each of its block's `block_succs` successor
|
|
/// blocks, in order.
|
|
fn is_branch(&self, insn: Inst) -> bool;
|
|
|
|
/// If `insn` is a branch at the end of `block`, returns the
|
|
/// operand index at which outgoing blockparam arguments are
|
|
/// found. Starting at this index, blockparam arguments for each
|
|
/// successor block's blockparams, in order, must be found.
|
|
///
|
|
/// It is an error if `self.inst_operands(insn).len() -
|
|
/// self.branch_blockparam_arg_offset(insn)` is not exactly equal
|
|
/// to the sum of blockparam counts for all successor blocks.
|
|
fn branch_blockparam_arg_offset(&self, block: Block, insn: Inst) -> usize;
|
|
|
|
/// Determine whether an instruction is a safepoint and requires a stackmap.
|
|
fn is_safepoint(&self, _: Inst) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Determine whether an instruction is a move; if so, return the
|
|
/// vregs for (src, dst).
|
|
fn is_move(&self, insn: Inst) -> Option<(VReg, VReg)>;
|
|
|
|
// --------------------------
|
|
// Instruction register slots
|
|
// --------------------------
|
|
|
|
/// Get the Operands for an instruction.
|
|
fn inst_operands(&self, insn: Inst) -> &[Operand];
|
|
|
|
/// Get the clobbers for an instruction.
|
|
fn inst_clobbers(&self, insn: Inst) -> &[PReg];
|
|
|
|
/// Get the number of `VReg` in use in this function.
|
|
fn num_vregs(&self) -> usize;
|
|
|
|
/// Get the VRegs that are pointer/reference types. This has the
|
|
/// following effects for each such vreg:
|
|
///
|
|
/// - At all safepoint instructions, the vreg will be in a
|
|
/// SpillSlot, not in a register.
|
|
/// - The vreg *may not* be used as a register operand on
|
|
/// safepoint instructions: this is because a vreg can only live
|
|
/// in one place at a time. The client should copy the value to an
|
|
/// integer-typed vreg and use this to pass a pointer as an input
|
|
/// to a safepoint instruction (such as a function call).
|
|
/// - At all safepoint instructions, all live vregs' locations
|
|
/// will be included in a list in the `Output` below, so that
|
|
/// pointer-inspecting/updating functionality (such as a moving
|
|
/// garbage collector) may observe and edit their values.
|
|
fn reftype_vregs(&self) -> &[VReg] {
|
|
&[]
|
|
}
|
|
|
|
/// Get the VRegs for which we should generate value-location
|
|
/// metadata for debugging purposes. This can be used to generate
|
|
/// e.g. DWARF with valid prgram-point ranges for each value
|
|
/// expression in a way that is more efficient than a post-hoc
|
|
/// analysis of the allocator's output.
|
|
///
|
|
/// Each tuple is (vreg, inclusive_start, exclusive_end,
|
|
/// label). In the `Output` there will be (label, inclusive_start,
|
|
/// exclusive_end, alloc)` tuples. The ranges may not exactly
|
|
/// match -- specifically, the returned metadata may cover only a
|
|
/// subset of the requested ranges -- if the value is not live for
|
|
/// the entire requested ranges.
|
|
fn debug_value_labels(&self) -> &[(Inst, Inst, VReg, u32)] {
|
|
&[]
|
|
}
|
|
|
|
// --------------
|
|
// Spills/reloads
|
|
// --------------
|
|
|
|
/// How many logical spill slots does the given regclass require? E.g., on
|
|
/// a 64-bit machine, spill slots may nominally be 64-bit words, but a
|
|
/// 128-bit vector value will require two slots. The regalloc will always
|
|
/// align on this size.
|
|
///
|
|
/// This passes the associated virtual register to the client as well,
|
|
/// because the way in which we spill a real register may depend on the
|
|
/// value that we are using it for. E.g., if a machine has V128 registers
|
|
/// but we also use them for F32 and F64 values, we may use a different
|
|
/// store-slot size and smaller-operand store/load instructions for an F64
|
|
/// than for a true V128.
|
|
///
|
|
/// (This trait method's design and doc text derives from
|
|
/// regalloc.rs' trait of the same name.)
|
|
fn spillslot_size(&self, regclass: RegClass, for_vreg: VReg) -> usize;
|
|
|
|
/// When providing a spillslot number for a multi-slot spillslot,
|
|
/// do we provide the first or the last? This is usually related
|
|
/// to which direction the stack grows and different clients may
|
|
/// have different preferences.
|
|
fn multi_spillslot_named_by_last_slot(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// A position before or after an instruction.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
#[repr(u8)]
|
|
pub enum InstPosition {
|
|
Before = 0,
|
|
After = 1,
|
|
}
|
|
|
|
/// A program point: a single point before or after a given instruction.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct ProgPoint {
|
|
pub inst: Inst,
|
|
pub pos: InstPosition,
|
|
}
|
|
|
|
impl ProgPoint {
|
|
pub fn before(inst: Inst) -> Self {
|
|
Self {
|
|
inst,
|
|
pos: InstPosition::Before,
|
|
}
|
|
}
|
|
|
|
pub fn after(inst: Inst) -> Self {
|
|
Self {
|
|
inst,
|
|
pos: InstPosition::After,
|
|
}
|
|
}
|
|
|
|
pub fn next(self) -> ProgPoint {
|
|
match self.pos {
|
|
InstPosition::Before => ProgPoint {
|
|
inst: self.inst,
|
|
pos: InstPosition::After,
|
|
},
|
|
InstPosition::After => ProgPoint {
|
|
inst: self.inst.next(),
|
|
pos: InstPosition::Before,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn prev(self) -> ProgPoint {
|
|
match self.pos {
|
|
InstPosition::Before => ProgPoint {
|
|
inst: self.inst.prev(),
|
|
pos: InstPosition::After,
|
|
},
|
|
InstPosition::After => ProgPoint {
|
|
inst: self.inst,
|
|
pos: InstPosition::Before,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn to_index(self) -> u32 {
|
|
debug_assert!(self.inst.index() <= ((1 << 31) - 1));
|
|
((self.inst.index() as u32) << 1) | (self.pos as u8 as u32)
|
|
}
|
|
|
|
pub fn from_index(index: u32) -> Self {
|
|
let inst = Inst::new((index >> 1) as usize);
|
|
let pos = match index & 1 {
|
|
0 => InstPosition::Before,
|
|
1 => InstPosition::After,
|
|
_ => unreachable!(),
|
|
};
|
|
Self { inst, pos }
|
|
}
|
|
}
|
|
|
|
/// An instruction to insert into the program to perform some data movement.
|
|
#[derive(Clone, Debug)]
|
|
pub enum Edit {
|
|
/// Move one allocation to another. Each allocation may be a
|
|
/// register or a stack slot (spillslot).
|
|
Move { from: Allocation, to: Allocation },
|
|
/// Define blockparams' locations. Note that this is not typically
|
|
/// turned into machine code, but can be useful metadata (e.g. for
|
|
/// the checker).
|
|
BlockParams {
|
|
vregs: Vec<VReg>,
|
|
allocs: Vec<Allocation>,
|
|
},
|
|
}
|
|
|
|
/// A machine envrionment tells the register allocator which registers
|
|
/// are available to allocate and what register may be used as a
|
|
/// scratch register for each class, and some other miscellaneous info
|
|
/// as well.
|
|
#[derive(Clone, Debug)]
|
|
pub struct MachineEnv {
|
|
regs: Vec<PReg>,
|
|
preferred_regs_by_class: Vec<Vec<PReg>>,
|
|
non_preferred_regs_by_class: Vec<Vec<PReg>>,
|
|
scratch_by_class: Vec<PReg>,
|
|
}
|
|
|
|
/// The output of the register allocator.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Output {
|
|
/// How many spillslots are needed in the frame?
|
|
pub num_spillslots: usize,
|
|
/// Edits (insertions or removals). Guaranteed to be sorted by
|
|
/// program point.
|
|
pub edits: Vec<(ProgPoint, Edit)>,
|
|
/// Allocations for each operand. Mapping from instruction to
|
|
/// allocations provided by `inst_alloc_offsets` below.
|
|
pub allocs: Vec<Allocation>,
|
|
/// Allocation offset in `allocs` for each instruction.
|
|
pub inst_alloc_offsets: Vec<u32>,
|
|
|
|
/// Safepoint records: at a given program point, a reference-typed value lives in the given SpillSlot.
|
|
pub safepoint_slots: Vec<(ProgPoint, SpillSlot)>,
|
|
|
|
/// Debug info: a labeled value (as applied to vregs by
|
|
/// `Function::debug_value_labels()` on the input side) is located
|
|
/// in the given allocation from the first program point
|
|
/// (inclusive) to the second (exclusive). Guaranteed to be sorted
|
|
/// by label and program point, and the ranges are guaranteed to
|
|
/// be disjoint.
|
|
pub debug_locations: Vec<(u32, ProgPoint, ProgPoint, Allocation)>,
|
|
|
|
/// Internal stats from the allocator.
|
|
pub stats: ion::Stats,
|
|
}
|
|
|
|
impl Output {
|
|
pub fn inst_allocs(&self, inst: Inst) -> &[Allocation] {
|
|
let start = self.inst_alloc_offsets[inst.index()] as usize;
|
|
let end = if inst.index() + 1 == self.inst_alloc_offsets.len() {
|
|
self.allocs.len()
|
|
} else {
|
|
self.inst_alloc_offsets[inst.index() + 1] as usize
|
|
};
|
|
&self.allocs[start..end]
|
|
}
|
|
}
|
|
|
|
/// An error that prevents allocation.
|
|
#[derive(Clone, Debug)]
|
|
pub enum RegAllocError {
|
|
/// Invalid SSA for given vreg at given inst: multiple defs or
|
|
/// illegal use. `inst` may be `Inst::invalid()` if this concerns
|
|
/// a block param.
|
|
SSA(VReg, Inst),
|
|
/// Invalid basic block: does not end in branch/ret, or contains a
|
|
/// branch/ret in the middle.
|
|
BB(Block),
|
|
/// Invalid branch: operand count does not match sum of block
|
|
/// params of successor blocks.
|
|
Branch(Inst),
|
|
}
|
|
|
|
impl std::fmt::Display for RegAllocError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for RegAllocError {}
|
|
|
|
pub fn run<F: Function>(func: &F, env: &MachineEnv) -> Result<Output, RegAllocError> {
|
|
ion::run(func, env)
|
|
}
|