Files
regalloc2/src/lib.rs
Chris Fallin 49c54b6144 Misc usability and functionality enhancements:
- 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).
2021-04-30 21:14:09 -07:00

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)
}