- Support preferred and non-preferred subsets of a register class. This allows allocating, e.g., caller-saved registers before callee-saved registers. - Allow branch blockparam args to start an a certain offset in branch operands; this allows branches to have other operands too (e.g., conditional-branch inputs). - Allow `OperandOrAllocation` to be constructed from an `Allocation` and `OperandKind` as well (i.e., an allocation with an use/def bit).
938 lines
28 KiB
Rust
938 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;
|
|
|
|
/// 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) << 6) | (self.0 as usize)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn from_index(index: usize) -> Self {
|
|
let class = (index >> 6) & 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)]
|
|
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)
|
|
}
|