#![allow(dead_code)]
macro_rules! trace {
($($tt:tt)*) => {
if cfg!(feature = "trace-log") {
::log::trace!($($tt)*);
}
};
}
macro_rules! trace_enabled {
() => {
cfg!(feature = "trace-log")
};
}
pub(crate) mod cfg;
pub(crate) mod domtree;
pub mod indexset;
pub(crate) mod ion;
pub(crate) mod moves;
pub(crate) mod postorder;
#[macro_use]
mod index;
pub use index::{Block, Inst, InstRange, InstRangeIter};
pub mod checker;
#[cfg(feature = "fuzzing")]
pub mod fuzzing;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum RegClass {
Int = 0,
Float = 1,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct PReg {
bits: u8,
}
impl PReg {
pub const MAX_BITS: usize = 6;
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
pub const NUM_INDEX: usize = 1 << (Self::MAX_BITS + 1);
#[inline(always)]
pub const fn new(hw_enc: usize, class: RegClass) -> Self {
debug_assert!(hw_enc <= PReg::MAX);
PReg {
bits: ((class as u8) << Self::MAX_BITS) | (hw_enc as u8),
}
}
#[inline(always)]
pub const fn hw_enc(self) -> usize {
self.bits as usize & Self::MAX
}
#[inline(always)]
pub const fn class(self) -> RegClass {
if self.bits & (1 << Self::MAX_BITS) == 0 {
RegClass::Int
} else {
RegClass::Float
}
}
#[inline(always)]
pub const fn index(self) -> usize {
self.bits as usize
}
#[inline(always)]
pub const fn from_index(index: usize) -> Self {
PReg {
bits: (index & (Self::NUM_INDEX - 1)) as u8,
}
}
#[inline(always)]
pub const 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)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct PRegSet {
bits: u128,
}
impl PRegSet {
pub const fn empty() -> Self {
Self { bits: 0 }
}
pub fn contains(&self, reg: PReg) -> bool {
let bit = reg.index();
debug_assert!(bit < 128);
self.bits & 1u128 << bit != 0
}
pub const fn with(self, reg: PReg) -> Self {
let bit = reg.index();
debug_assert!(bit < 128);
Self {
bits: self.bits | (1u128 << bit),
}
}
pub fn add(&mut self, reg: PReg) {
let bit = reg.index();
debug_assert!(bit < 128);
self.bits |= 1u128 << bit;
}
pub fn remove(&mut self, reg: PReg) {
let bit = reg.index();
debug_assert!(bit < 128);
self.bits &= !(1u128 << bit);
}
pub fn union_from(&mut self, other: PRegSet) {
self.bits |= other.bits;
}
}
impl IntoIterator for PRegSet {
type Item = PReg;
type IntoIter = PRegSetIter;
fn into_iter(self) -> PRegSetIter {
PRegSetIter { bits: self.bits }
}
}
pub struct PRegSetIter {
bits: u128,
}
impl Iterator for PRegSetIter {
type Item = PReg;
fn next(&mut self) -> Option<PReg> {
if self.bits == 0 {
None
} else {
let index = self.bits.trailing_zeros();
self.bits &= !(1u128 << index);
Some(PReg::from_index(index as usize))
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct VReg {
bits: u32,
}
impl VReg {
pub const MAX_BITS: usize = 21;
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
#[inline(always)]
pub const fn new(virt_reg: usize, class: RegClass) -> Self {
debug_assert!(virt_reg <= VReg::MAX);
VReg {
bits: ((virt_reg as u32) << 1) | (class as u8 as u32),
}
}
#[inline(always)]
pub const fn vreg(self) -> usize {
let vreg = (self.bits >> 1) as usize;
vreg
}
#[inline(always)]
pub const fn class(self) -> RegClass {
match self.bits & 1 {
0 => RegClass::Int,
1 => RegClass::Float,
_ => unreachable!(),
}
}
#[inline(always)]
pub const 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)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct SpillSlot {
bits: u32,
}
impl SpillSlot {
pub const MAX: usize = (1 << 24) - 1;
#[inline(always)]
pub fn new(slot: usize) -> Self {
debug_assert!(slot <= Self::MAX);
SpillSlot { bits: slot as u32 }
}
#[inline(always)]
pub fn index(self) -> usize {
(self.bits & 0x00ffffff) as usize
}
#[inline(always)]
pub fn plus(self, offset: usize) -> Self {
SpillSlot::new(self.index() + offset)
}
#[inline(always)]
pub fn invalid() -> Self {
SpillSlot { bits: 0xffff_ffff }
}
#[inline(always)]
pub fn is_invalid(self) -> bool {
self == Self::invalid()
}
#[inline(always)]
pub fn is_valid(self) -> bool {
self != Self::invalid()
}
}
impl std::fmt::Display for SpillSlot {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "stack{}", self.index())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum OperandConstraint {
Any,
Reg,
Stack,
FixedReg(PReg),
Reuse(usize),
}
impl std::fmt::Display for OperandConstraint {
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)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum OperandKind {
Def = 0,
Mod = 1,
Use = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum OperandPos {
Early = 0,
Late = 1,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Operand {
bits: u32,
}
impl Operand {
#[inline(always)]
pub fn new(
vreg: VReg,
constraint: OperandConstraint,
kind: OperandKind,
pos: OperandPos,
) -> Self {
let constraint_field = match constraint {
OperandConstraint::Any => 0,
OperandConstraint::Reg => 1,
OperandConstraint::Stack => 2,
OperandConstraint::FixedReg(preg) => {
debug_assert_eq!(preg.class(), vreg.class());
0b1000000 | preg.hw_enc() as u32
}
OperandConstraint::Reuse(which) => {
debug_assert!(which <= 31);
0b0100000 | which as u32
}
};
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
| (class_field << 21)
| (pos_field << 22)
| (kind_field << 23)
| (constraint_field << 25),
}
}
#[inline(always)]
pub fn reg_use(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Reg,
OperandKind::Use,
OperandPos::Early,
)
}
#[inline(always)]
pub fn reg_use_at_end(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Reg,
OperandKind::Use,
OperandPos::Late,
)
}
#[inline(always)]
pub fn reg_def(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Reg,
OperandKind::Def,
OperandPos::Late,
)
}
#[inline(always)]
pub fn reg_def_at_start(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Reg,
OperandKind::Def,
OperandPos::Early,
)
}
#[inline(always)]
pub fn reg_temp(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Reg,
OperandKind::Def,
OperandPos::Early,
)
}
#[inline(always)]
pub fn reg_reuse_def(vreg: VReg, idx: usize) -> Self {
Operand::new(
vreg,
OperandConstraint::Reuse(idx),
OperandKind::Def,
OperandPos::Late,
)
}
#[inline(always)]
pub fn reg_fixed_use(vreg: VReg, preg: PReg) -> Self {
Operand::new(
vreg,
OperandConstraint::FixedReg(preg),
OperandKind::Use,
OperandPos::Early,
)
}
#[inline(always)]
pub fn reg_fixed_def(vreg: VReg, preg: PReg) -> Self {
Operand::new(
vreg,
OperandConstraint::FixedReg(preg),
OperandKind::Def,
OperandPos::Late,
)
}
#[inline(always)]
pub fn any_use(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Any,
OperandKind::Use,
OperandPos::Early,
)
}
#[inline(always)]
pub fn any_def(vreg: VReg) -> Self {
Operand::new(
vreg,
OperandConstraint::Any,
OperandKind::Def,
OperandPos::Late,
)
}
#[inline(always)]
pub fn fixed_nonallocatable(preg: PReg) -> Self {
Operand::new(
VReg::new(VReg::MAX, preg.class()),
OperandConstraint::FixedReg(preg),
OperandKind::Use,
OperandPos::Early,
)
}
#[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 >> 21) & 1;
match class_field {
0 => RegClass::Int,
1 => RegClass::Float,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn kind(self) -> OperandKind {
let kind_field = (self.bits >> 23) & 3;
match kind_field {
0 => OperandKind::Def,
1 => OperandKind::Mod,
2 => OperandKind::Use,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn pos(self) -> OperandPos {
let pos_field = (self.bits >> 22) & 1;
match pos_field {
0 => OperandPos::Early,
1 => OperandPos::Late,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn constraint(self) -> OperandConstraint {
let constraint_field = ((self.bits >> 25) as usize) & 127;
if constraint_field & 0b1000000 != 0 {
OperandConstraint::FixedReg(PReg::new(constraint_field & 0b0111111, self.class()))
} else if constraint_field & 0b0100000 != 0 {
OperandConstraint::Reuse(constraint_field & 0b0011111)
} else {
match constraint_field {
0 => OperandConstraint::Any,
1 => OperandConstraint::Reg,
2 => OperandConstraint::Stack,
_ => unreachable!(),
}
}
}
#[inline(always)]
pub fn as_fixed_nonallocatable(self) -> Option<PReg> {
match self.constraint() {
OperandConstraint::FixedReg(preg) if self.vreg().vreg() == VReg::MAX => Some(preg),
_ => None,
}
}
#[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 {
match (self.kind(), self.pos()) {
(OperandKind::Def, OperandPos::Late)
| (OperandKind::Mod | OperandKind::Use, OperandPos::Early) => {
write!(f, "{:?}", self.kind())?;
}
_ => {
write!(f, "{:?}@{:?}", self.kind(), self.pos())?;
}
}
write!(
f,
": {}{} {}",
self.vreg(),
match self.class() {
RegClass::Int => "i",
RegClass::Float => "f",
},
self.constraint()
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Allocation {
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 {
debug_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.bits as usize)
}
#[inline(always)]
pub fn kind(self) -> AllocationKind {
match (self.bits >> 29) & 7 {
0 => AllocationKind::None,
1 => AllocationKind::Reg,
2 => AllocationKind::Stack,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn is_none(self) -> bool {
self.kind() == AllocationKind::None
}
#[inline(always)]
pub fn is_some(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 {
bits: 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 }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum AllocationKind {
None = 0,
Reg = 1,
Stack = 2,
}
pub trait Function {
fn num_insts(&self) -> usize;
fn num_blocks(&self) -> usize;
fn entry_block(&self) -> Block;
fn block_insns(&self, block: Block) -> InstRange;
fn block_succs(&self, block: Block) -> &[Block];
fn block_preds(&self, block: Block) -> &[Block];
fn block_params(&self, block: Block) -> &[VReg];
fn is_ret(&self, insn: Inst) -> bool;
fn is_branch(&self, insn: Inst) -> bool;
fn branch_blockparams(&self, block: Block, insn: Inst, succ_idx: usize) -> &[VReg];
fn requires_refs_on_stack(&self, _: Inst) -> bool {
false
}
fn is_move(&self, insn: Inst) -> Option<(Operand, Operand)>;
fn inst_operands(&self, insn: Inst) -> &[Operand];
fn inst_clobbers(&self, insn: Inst) -> PRegSet;
fn num_vregs(&self) -> usize;
fn reftype_vregs(&self) -> &[VReg] {
&[]
}
fn debug_value_labels(&self) -> &[(VReg, Inst, Inst, u32)] {
&[]
}
fn is_pinned_vreg(&self, _: VReg) -> Option<PReg> {
None
}
fn spillslot_size(&self, regclass: RegClass) -> usize;
fn multi_spillslot_named_by_last_slot(&self) -> bool {
false
}
fn allow_multiple_vreg_defs(&self) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum InstPosition {
Before = 0,
After = 1,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct ProgPoint {
bits: u32,
}
impl std::fmt::Debug for ProgPoint {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"progpoint{}{}",
self.inst().index(),
match self.pos() {
InstPosition::Before => "-pre",
InstPosition::After => "-post",
}
)
}
}
impl ProgPoint {
#[inline(always)]
pub fn new(inst: Inst, pos: InstPosition) -> Self {
let bits = ((inst.0 as u32) << 1) | (pos as u8 as u32);
Self { bits }
}
#[inline(always)]
pub fn before(inst: Inst) -> Self {
Self::new(inst, InstPosition::Before)
}
#[inline(always)]
pub fn after(inst: Inst) -> Self {
Self::new(inst, InstPosition::After)
}
#[inline(always)]
pub fn inst(self) -> Inst {
Inst::new(((self.bits as i32) >> 1) as usize)
}
#[inline(always)]
pub fn pos(self) -> InstPosition {
match self.bits & 1 {
0 => InstPosition::Before,
1 => InstPosition::After,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn next(self) -> ProgPoint {
Self {
bits: self.bits + 1,
}
}
#[inline(always)]
pub fn prev(self) -> ProgPoint {
Self {
bits: self.bits - 1,
}
}
#[inline(always)]
pub fn to_index(self) -> u32 {
self.bits
}
#[inline(always)]
pub fn from_index(index: u32) -> Self {
Self { bits: index }
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum Edit {
Move { from: Allocation, to: Allocation },
}
#[derive(Clone, Debug)]
pub enum InstOrEdit<'a> {
Inst(Inst),
Edit(&'a Edit),
}
pub struct OutputIter<'a> {
edits: &'a [(ProgPoint, Edit)],
inst_range: InstRange,
}
impl<'a> Iterator for OutputIter<'a> {
type Item = InstOrEdit<'a>;
fn next(&mut self) -> Option<InstOrEdit<'a>> {
if self.inst_range.len() == 0 {
return None;
}
let next_inst = self.inst_range.first();
if let Some((edit, remaining_edits)) = self.edits.split_first() {
if edit.0 <= ProgPoint::before(next_inst) {
self.edits = remaining_edits;
return Some(InstOrEdit::Edit(&edit.1));
}
}
self.inst_range = self.inst_range.rest();
Some(InstOrEdit::Inst(next_inst))
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct MachineEnv {
pub preferred_regs_by_class: [Vec<PReg>; 2],
pub non_preferred_regs_by_class: [Vec<PReg>; 2],
pub fixed_stack_slots: Vec<PReg>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Output {
pub num_spillslots: usize,
pub edits: Vec<(ProgPoint, Edit)>,
pub allocs: Vec<Allocation>,
pub inst_alloc_offsets: Vec<u32>,
pub safepoint_slots: Vec<(ProgPoint, Allocation)>,
pub debug_locations: Vec<(u32, ProgPoint, ProgPoint, Allocation)>,
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]
}
pub fn block_insts_and_edits(&self, func: &impl Function, block: Block) -> OutputIter<'_> {
let inst_range = func.block_insns(block);
let edit_idx = self
.edits
.binary_search_by(|&(pos, _)| {
if pos < ProgPoint::before(inst_range.first()) {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
})
.unwrap_err();
let edits = &self.edits[edit_idx..];
OutputIter { inst_range, edits }
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum RegAllocError {
CritEdge(Block, Block),
SSA(VReg, Inst),
BB(Block),
Branch(Inst),
EntryLivein,
DisallowedBranchArg(Inst),
TooManyLiveRegs,
}
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,
options: &RegallocOptions,
) -> Result<Output, RegAllocError> {
ion::run(func, env, options.verbose_log)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct RegallocOptions {
pub verbose_log: bool,
}