Initial public commit of regalloc2.

This commit is contained in:
Chris Fallin
2021-04-13 16:40:21 -07:00
parent 41841996c8
commit 8e923b0ad9
27 changed files with 7814 additions and 0 deletions

45
src/bin/test.rs Normal file
View File

@@ -0,0 +1,45 @@
use arbitrary::{Arbitrary, Unstructured};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use regalloc2::fuzzing::func::{machine_env, Func};
use regalloc2::ion;
use regalloc2::Function;
fn create_random_func(seed: u64, size: usize) -> Func {
let mut bytes: Vec<u8> = vec![];
bytes.resize(size, 0);
let mut rng = ChaCha8Rng::seed_from_u64(seed);
rng.fill(&mut bytes[..]);
loop {
let mut u = Unstructured::new(&bytes[..]);
match Func::arbitrary(&mut u) {
Ok(f) => {
return f;
}
Err(arbitrary::Error::NotEnoughData) => {
let len = bytes.len();
bytes.resize(len + 1024, 0);
rng.fill(&mut bytes[len..]);
}
Err(e) => panic!("unexpected error: {:?}", e),
}
}
}
fn main() {
const SIZE: usize = 1000 * 1000;
env_logger::init();
let env = machine_env();
for iter in 0..3 {
let func = create_random_func(iter, SIZE);
eprintln!("==== {} instructions", func.insts());
let mut stats: ion::Stats = ion::Stats::default();
for i in 0..1000 {
let out = ion::run(&func, &env).expect("regalloc did not succeed");
if i == 0 {
stats = out.stats;
}
}
eprintln!("Stats: {:?}", stats);
}
}

139
src/bitvec.rs Normal file
View File

@@ -0,0 +1,139 @@
//! Bit vectors.
use smallvec::{smallvec, SmallVec};
/// A conceptually infinite-length bitvector that allows bitwise operations and
/// iteration over set bits efficiently.
#[derive(Clone, Debug)]
pub struct BitVec {
bits: SmallVec<[u64; 2]>,
}
const BITS_PER_WORD: usize = 64;
impl BitVec {
pub fn new() -> Self {
Self { bits: smallvec![] }
}
pub fn with_capacity(len: usize) -> Self {
let words = (len + BITS_PER_WORD - 1) / BITS_PER_WORD;
Self {
bits: SmallVec::with_capacity(words),
}
}
#[inline(never)]
fn ensure_idx(&mut self, word: usize) {
let mut target_len = std::cmp::max(2, self.bits.len());
while word >= target_len {
target_len *= 2;
}
self.bits.resize(target_len, 0);
}
#[inline(always)]
pub fn set(&mut self, idx: usize, val: bool) {
let word = idx / BITS_PER_WORD;
let bit = idx % BITS_PER_WORD;
if val {
if word >= self.bits.len() {
self.ensure_idx(word);
}
self.bits[word] |= 1 << bit;
} else {
if word < self.bits.len() {
self.bits[word] &= !(1 << bit);
}
}
}
#[inline(always)]
pub fn get(&mut self, idx: usize) -> bool {
let word = idx / BITS_PER_WORD;
let bit = idx % BITS_PER_WORD;
if word >= self.bits.len() {
false
} else {
(self.bits[word] & (1 << bit)) != 0
}
}
pub fn or(&mut self, other: &Self) {
if other.bits.is_empty() {
return;
}
let last_idx = other.bits.len() - 1;
self.ensure_idx(last_idx);
for (self_word, other_word) in self.bits.iter_mut().zip(other.bits.iter()) {
*self_word |= *other_word;
}
}
pub fn and(&mut self, other: &Self) {
if other.bits.len() < self.bits.len() {
self.bits.truncate(other.bits.len());
}
for (self_word, other_word) in self.bits.iter_mut().zip(other.bits.iter()) {
*self_word &= *other_word;
}
}
pub fn iter<'a>(&'a self) -> SetBitsIter<'a> {
let cur_word = if self.bits.len() > 0 { self.bits[0] } else { 0 };
SetBitsIter {
words: &self.bits[..],
word_idx: 0,
cur_word,
}
}
}
pub struct SetBitsIter<'a> {
words: &'a [u64],
word_idx: usize,
cur_word: u64,
}
impl<'a> Iterator for SetBitsIter<'a> {
type Item = usize;
fn next(&mut self) -> Option<usize> {
while self.cur_word == 0 {
if self.word_idx + 1 >= self.words.len() {
return None;
}
self.word_idx += 1;
self.cur_word = self.words[self.word_idx];
}
let bitidx = self.cur_word.trailing_zeros();
self.cur_word &= !(1 << bitidx);
Some(self.word_idx * BITS_PER_WORD + bitidx as usize)
}
}
#[cfg(test)]
mod test {
use super::BitVec;
#[test]
fn test_set_bits_iter() {
let mut vec = BitVec::new();
let mut sum = 0;
for i in 0..1024 {
if i % 17 == 0 {
vec.set(i, true);
sum += i;
}
}
let mut checksum = 0;
for bit in vec.iter() {
assert!(bit % 17 == 0);
checksum += bit;
}
assert_eq!(sum, checksum);
}
}

110
src/cfg.rs Normal file
View File

@@ -0,0 +1,110 @@
//! Lightweight CFG analyses.
use crate::{domtree, postorder, Block, Function, Inst, OperandKind, ProgPoint};
#[derive(Clone, Debug)]
pub struct CFGInfo {
/// Postorder traversal of blocks.
pub postorder: Vec<Block>,
/// Domtree parents, indexed by block.
pub domtree: Vec<Block>,
/// For each instruction, the block it belongs to.
pub insn_block: Vec<Block>,
/// For each vreg, the instruction that defines it, if any.
pub vreg_def_inst: Vec<Inst>,
/// For each vreg, the block that defines it as a blockparam, if
/// any. (Every vreg must have a valid entry in either
/// `vreg_def_inst` or `vreg_def_blockparam`.)
pub vreg_def_blockparam: Vec<(Block, u32)>,
/// For each block, the first instruction.
pub block_entry: Vec<ProgPoint>,
/// For each block, the last instruction.
pub block_exit: Vec<ProgPoint>,
/// For each block, what is its position in its successor's preds,
/// if it has a single successor?
///
/// (Because we require split critical edges, we always either have a single
/// successor (which itself may have multiple preds), or we have multiple
/// successors but each successor itself has only one pred; so we can store
/// just one value per block and always know any block's position in its
/// successors' preds lists.)
pub pred_pos: Vec<usize>,
}
impl CFGInfo {
pub fn new<F: Function>(f: &F) -> CFGInfo {
let postorder =
postorder::calculate(f.blocks(), f.entry_block(), |block| f.block_succs(block));
let domtree = domtree::calculate(
f.blocks(),
|block| f.block_preds(block),
&postorder[..],
f.entry_block(),
);
let mut insn_block = vec![Block::invalid(); f.insts()];
let mut vreg_def_inst = vec![Inst::invalid(); f.num_vregs()];
let mut vreg_def_blockparam = vec![(Block::invalid(), 0); f.num_vregs()];
let mut block_entry = vec![ProgPoint::before(Inst::invalid()); f.blocks()];
let mut block_exit = vec![ProgPoint::before(Inst::invalid()); f.blocks()];
let mut pred_pos = vec![0; f.blocks()];
for block in 0..f.blocks() {
let block = Block::new(block);
for (i, param) in f.block_params(block).iter().enumerate() {
vreg_def_blockparam[param.vreg()] = (block, i as u32);
}
for inst in f.block_insns(block).iter() {
insn_block[inst.index()] = block;
for operand in f.inst_operands(inst) {
match operand.kind() {
OperandKind::Def => {
vreg_def_inst[operand.vreg().vreg()] = inst;
}
_ => {}
}
}
}
block_entry[block.index()] = ProgPoint::before(f.block_insns(block).first());
block_exit[block.index()] = ProgPoint::after(f.block_insns(block).last());
if f.block_preds(block).len() > 1 {
for (i, &pred) in f.block_preds(block).iter().enumerate() {
// Assert critical edge condition.
assert_eq!(
f.block_succs(pred).len(),
1,
"Edge {} -> {} is critical",
pred.index(),
block.index(),
);
pred_pos[pred.index()] = i;
}
}
}
CFGInfo {
postorder,
domtree,
insn_block,
vreg_def_inst,
vreg_def_blockparam,
block_entry,
block_exit,
pred_pos,
}
}
pub fn dominates(&self, a: Block, b: Block) -> bool {
domtree::dominates(&self.domtree[..], a, b)
}
/// Return the position of this block in its successor's predecessor list.
///
/// Because the CFG must have split critical edges, we actually do not need
/// to know *which* successor: if there is more than one, then each
/// successor has only one predecessor (that's this block), so the answer is
/// `0` no matter which successor we are considering.
pub fn pred_position(&self, block: Block) -> usize {
self.pred_pos[block.index()]
}
}

615
src/checker.rs Normal file
View File

@@ -0,0 +1,615 @@
/*
* The following code is derived from `lib/src/checker.rs` in the
* regalloc.rs project
* (https://github.com/bytecodealliance/regalloc.rs). regalloc.rs is
* also licensed under Apache-2.0 with the LLVM exception, as the rest
* of regalloc2's non-Ion-derived code is.
*/
//! Checker: verifies that spills/reloads/moves retain equivalent
//! dataflow to original, VReg-based code.
//!
//! The basic idea is that we track symbolic values as they flow
//! through spills and reloads. The symbolic values represent
//! particular virtual registers in the original function body
//! presented to the register allocator. Any instruction in the
//! original function body (i.e., not added by the allocator)
//! conceptually generates a symbolic value "Vn" when storing to (or
//! modifying) a virtual register.
//!
//! Operand policies (fixed register, register, any) are also checked
//! at each operand.
//!
//! The dataflow analysis state at each program point is:
//!
//! - map of: Allocation -> lattice value (top > Vn symbols (unordered) > bottom)
//!
//! And the transfer functions for instructions are:
//!
//! - `Edit::Move` inserted by RA: [ alloc_d := alloc_s ]
//!
//! A[alloc_d] := A[alloc_s]
//!
//! - phi-node [ V_i := phi block_j:V_j, block_k:V_k, ... ]
//! with allocations [ A_i := phi block_j:A_j, block_k:A_k, ... ]
//! (N.B.: phi-nodes are not semantically present in the final
//! machine code, but we include their allocations so that this
//! checker can work)
//!
//! A[A_i] := meet(A_j, A_k, ...)
//!
//! - statement in pre-regalloc function [ V_i := op V_j, V_k, ... ]
//! with allocated form [ A_i := op A_j, A_k, ... ]
//!
//! A[A_i] := `V_i`
//!
//! In other words, a statement, even after allocation, generates
//! a symbol that corresponds to its original virtual-register
//! def.
//!
//! (N.B.: moves in pre-regalloc function fall into this last case
//! -- they are "just another operation" and generate a new
//! symbol)
//!
//! At control-flow join points, the symbols meet using a very simple
//! lattice meet-function: two different symbols in the same
//! allocation meet to "conflicted"; otherwise, the symbol meets with
//! itself to produce itself (reflexivity).
//!
//! To check correctness, we first find the dataflow fixpoint with the
//! above lattice and transfer/meet functions. Then, at each op, we
//! examine the dataflow solution at the preceding program point, and
//! check that the allocation for each op arg (input/use) contains the
//! symbol corresponding to the original virtual register specified
//! for this arg.
#![allow(dead_code)]
use crate::{
Allocation, AllocationKind, Block, Edit, Function, Inst, InstPosition, Operand, OperandKind,
OperandPolicy, OperandPos, Output, ProgPoint, VReg,
};
use std::collections::{HashMap, VecDeque};
use std::default::Default;
use std::hash::Hash;
use std::result::Result;
use log::debug;
/// A set of errors detected by the regalloc checker.
#[derive(Clone, Debug)]
pub struct CheckerErrors {
errors: Vec<CheckerError>,
}
/// A single error detected by the regalloc checker.
#[derive(Clone, Debug)]
pub enum CheckerError {
MissingAllocation {
inst: Inst,
op: Operand,
},
UnknownValueInAllocation {
inst: Inst,
op: Operand,
alloc: Allocation,
},
ConflictedValueInAllocation {
inst: Inst,
op: Operand,
alloc: Allocation,
},
IncorrectValueInAllocation {
inst: Inst,
op: Operand,
alloc: Allocation,
actual: VReg,
},
PolicyViolated {
inst: Inst,
op: Operand,
alloc: Allocation,
},
AllocationIsNotReg {
inst: Inst,
op: Operand,
alloc: Allocation,
},
AllocationIsNotFixedReg {
inst: Inst,
op: Operand,
alloc: Allocation,
},
AllocationIsNotReuse {
inst: Inst,
op: Operand,
alloc: Allocation,
expected_alloc: Allocation,
},
}
/// Abstract state for an allocation.
///
/// Forms a lattice with \top (`Unknown`), \bot (`Conflicted`), and a
/// number of mutually unordered value-points in between, one per real
/// or virtual register. Any two different registers meet to \bot.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CheckerValue {
/// "top" value: this storage slot has no known value.
Unknown,
/// "bottom" value: this storage slot has a conflicted value.
Conflicted,
/// Reg: this storage slot has a value that originated as a def
/// into the given virtual register.
///
/// The boolean flag indicates whether the value is
/// reference-typed.
Reg(VReg, bool),
}
impl Default for CheckerValue {
fn default() -> CheckerValue {
CheckerValue::Unknown
}
}
impl CheckerValue {
/// Meet function of the abstract-interpretation value lattice.
fn meet(&self, other: &CheckerValue) -> CheckerValue {
match (self, other) {
(&CheckerValue::Unknown, _) => *other,
(_, &CheckerValue::Unknown) => *self,
(&CheckerValue::Conflicted, _) => *self,
(_, &CheckerValue::Conflicted) => *other,
(&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2)) if r1 == r2 => {
CheckerValue::Reg(r1, ref1 || ref2)
}
_ => {
log::debug!("{:?} and {:?} meet to Conflicted", self, other);
CheckerValue::Conflicted
}
}
}
}
/// State that steps through program points as we scan over the instruction stream.
#[derive(Clone, Debug, PartialEq, Eq)]
struct CheckerState {
allocations: HashMap<Allocation, CheckerValue>,
}
impl Default for CheckerState {
fn default() -> CheckerState {
CheckerState {
allocations: HashMap::new(),
}
}
}
impl std::fmt::Display for CheckerValue {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CheckerValue::Unknown => write!(f, "?"),
CheckerValue::Conflicted => write!(f, "!"),
CheckerValue::Reg(r, _) => write!(f, "{}", r),
}
}
}
fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
into: &mut HashMap<K, CheckerValue>,
from: &HashMap<K, CheckerValue>,
) {
for (k, v) in from {
let into_v = into.entry(*k).or_insert(Default::default());
let merged = into_v.meet(v);
*into_v = merged;
}
}
impl CheckerState {
/// Create a new checker state.
fn new() -> CheckerState {
Default::default()
}
/// Merge this checker state with another at a CFG join-point.
fn meet_with(&mut self, other: &CheckerState) {
merge_map(&mut self.allocations, &other.allocations);
}
fn check_val(
&self,
inst: Inst,
op: Operand,
alloc: Allocation,
val: CheckerValue,
allocs: &[Allocation],
) -> Result<(), CheckerError> {
if alloc == Allocation::none() {
return Err(CheckerError::MissingAllocation { inst, op });
}
match val {
CheckerValue::Unknown => {
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
}
CheckerValue::Conflicted => {
return Err(CheckerError::ConflictedValueInAllocation { inst, op, alloc });
}
CheckerValue::Reg(r, _) if r != op.vreg() => {
return Err(CheckerError::IncorrectValueInAllocation {
inst,
op,
alloc,
actual: r,
});
}
_ => {}
}
self.check_policy(inst, op, alloc, allocs)?;
Ok(())
}
/// Check an instruction against this state. This must be called
/// twice: once with `InstPosition::Before`, and once with
/// `InstPosition::After` (after updating state with defs).
fn check(&self, pos: InstPosition, checkinst: &CheckerInst) -> Result<(), CheckerError> {
match checkinst {
&CheckerInst::Op {
inst,
ref operands,
ref allocs,
..
} => {
// Skip Use-checks at the After point if there are any
// reused inputs: the Def which reuses the input
// happens early.
let has_reused_input = operands
.iter()
.any(|op| matches!(op.policy(), OperandPolicy::Reuse(_)));
if has_reused_input && pos == InstPosition::After {
return Ok(());
}
// For each operand, check (i) that the allocation
// contains the expected vreg, and (ii) that it meets
// the requirements of the OperandPolicy.
for (op, alloc) in operands.iter().zip(allocs.iter()) {
let is_here = match (op.pos(), pos) {
(OperandPos::Before, InstPosition::Before)
| (OperandPos::Both, InstPosition::Before) => true,
(OperandPos::After, InstPosition::After)
| (OperandPos::Both, InstPosition::After) => true,
_ => false,
};
if !is_here {
continue;
}
if op.kind() == OperandKind::Def {
continue;
}
let val = self
.allocations
.get(alloc)
.cloned()
.unwrap_or(Default::default());
debug!(
"checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
checkinst, op, alloc, val
);
self.check_val(inst, *op, *alloc, val, allocs)?;
}
}
_ => {}
}
Ok(())
}
/// Update according to instruction.
fn update(&mut self, checkinst: &CheckerInst) {
match checkinst {
&CheckerInst::Move { into, from } => {
let val = self
.allocations
.get(&from)
.cloned()
.unwrap_or(Default::default());
debug!(
"checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
checkinst, from, into, val
);
self.allocations.insert(into, val);
}
&CheckerInst::Op {
ref operands,
ref allocs,
..
} => {
for (op, alloc) in operands.iter().zip(allocs.iter()) {
if op.kind() != OperandKind::Def {
continue;
}
self.allocations
.insert(*alloc, CheckerValue::Reg(op.vreg(), false));
}
}
&CheckerInst::BlockParams {
ref vregs,
ref allocs,
..
} => {
for (vreg, alloc) in vregs.iter().zip(allocs.iter()) {
self.allocations
.insert(*alloc, CheckerValue::Reg(*vreg, false));
}
}
}
}
fn check_policy(
&self,
inst: Inst,
op: Operand,
alloc: Allocation,
allocs: &[Allocation],
) -> Result<(), CheckerError> {
match op.policy() {
OperandPolicy::Any => {}
OperandPolicy::Reg => {
if alloc.kind() != AllocationKind::Reg {
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
}
}
OperandPolicy::FixedReg(preg) => {
if alloc != Allocation::reg(preg) {
return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
}
}
OperandPolicy::Reuse(idx) => {
if alloc.kind() != AllocationKind::Reg {
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
}
if alloc != allocs[idx] {
return Err(CheckerError::AllocationIsNotReuse {
inst,
op,
alloc,
expected_alloc: allocs[idx],
});
}
}
}
Ok(())
}
}
/// An instruction representation in the checker's BB summary.
#[derive(Clone, Debug)]
pub(crate) enum CheckerInst {
/// A move between allocations (these could be registers or
/// spillslots).
Move { into: Allocation, from: Allocation },
/// A regular instruction with fixed use and def slots. Contains
/// both the original operands (as given to the regalloc) and the
/// allocation results.
Op {
inst: Inst,
operands: Vec<Operand>,
allocs: Vec<Allocation>,
},
/// The top of a block with blockparams. We define the given vregs
/// into the given allocations.
BlockParams {
block: Block,
vregs: Vec<VReg>,
allocs: Vec<Allocation>,
},
}
#[derive(Debug)]
pub struct Checker<'a, F: Function> {
f: &'a F,
bb_in: HashMap<Block, CheckerState>,
bb_insts: HashMap<Block, Vec<CheckerInst>>,
}
impl<'a, F: Function> Checker<'a, F> {
/// Create a new checker for the given function, initializing CFG
/// info immediately. The client should call the `add_*()`
/// methods to add abstract instructions to each BB before
/// invoking `run()` to check for errors.
pub fn new(f: &'a F) -> Checker<'a, F> {
let mut bb_in = HashMap::new();
let mut bb_insts = HashMap::new();
for block in 0..f.blocks() {
let block = Block::new(block);
bb_in.insert(block, Default::default());
bb_insts.insert(block, vec![]);
}
Checker { f, bb_in, bb_insts }
}
/// Build the list of checker instructions based on the given func
/// and allocation results.
pub fn prepare(&mut self, out: &Output) {
debug!("checker: out = {:?}", out);
// For each original instruction, create an `Op`.
let mut last_inst = None;
let mut insert_idx = 0;
for block in 0..self.f.blocks() {
let block = Block::new(block);
for inst in self.f.block_insns(block).iter() {
assert!(last_inst.is_none() || inst > last_inst.unwrap());
last_inst = Some(inst);
// Any inserted edits before instruction.
self.handle_edits(block, out, &mut insert_idx, ProgPoint::before(inst));
// Instruction itself.
let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
let checkinst = CheckerInst::Op {
inst,
operands,
allocs,
};
debug!("checker: adding inst {:?}", checkinst);
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
// Any inserted edits after instruction.
self.handle_edits(block, out, &mut insert_idx, ProgPoint::after(inst));
}
}
}
fn handle_edits(&mut self, block: Block, out: &Output, idx: &mut usize, pos: ProgPoint) {
while *idx < out.edits.len() && out.edits[*idx].0 <= pos {
let &(edit_pos, ref edit) = &out.edits[*idx];
*idx += 1;
if edit_pos < pos {
continue;
}
debug!("checker: adding edit {:?} at pos {:?}", edit, pos);
match edit {
&Edit::Move { from, to, .. } => {
self.bb_insts
.get_mut(&block)
.unwrap()
.push(CheckerInst::Move { into: to, from });
}
&Edit::BlockParams {
ref vregs,
ref allocs,
} => {
let inst = CheckerInst::BlockParams {
block,
vregs: vregs.clone(),
allocs: allocs.clone(),
};
self.bb_insts.get_mut(&block).unwrap().push(inst);
}
}
}
}
/// Perform the dataflow analysis to compute checker state at each BB entry.
fn analyze(&mut self) {
let mut queue = VecDeque::new();
queue.push_back(self.f.entry_block());
while !queue.is_empty() {
let block = queue.pop_front().unwrap();
let mut state = self.bb_in.get(&block).cloned().unwrap();
debug!("analyze: block {} has state {:?}", block.index(), state);
for inst in self.bb_insts.get(&block).unwrap() {
state.update(inst);
debug!("analyze: inst {:?} -> state {:?}", inst, state);
}
for &succ in self.f.block_succs(block) {
let cur_succ_in = self.bb_in.get(&succ).unwrap();
let mut new_state = state.clone();
new_state.meet_with(cur_succ_in);
let changed = &new_state != cur_succ_in;
if changed {
debug!(
"analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
succ.index(),
cur_succ_in,
new_state
);
self.bb_in.insert(succ, new_state);
queue.push_back(succ);
}
}
}
}
/// Using BB-start state computed by `analyze()`, step the checker state
/// through each BB and check each instruction's register allocations
/// for errors.
fn find_errors(&self) -> Result<(), CheckerErrors> {
let mut errors = vec![];
for (block, input) in &self.bb_in {
let mut state = input.clone();
for inst in self.bb_insts.get(block).unwrap() {
if let Err(e) = state.check(InstPosition::Before, inst) {
debug!("Checker error: {:?}", e);
errors.push(e);
}
state.update(inst);
if let Err(e) = state.check(InstPosition::After, inst) {
debug!("Checker error: {:?}", e);
errors.push(e);
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(CheckerErrors { errors })
}
}
/// Find any errors, returning `Err(CheckerErrors)` with all errors found
/// or `Ok(())` otherwise.
pub fn run(mut self) -> Result<(), CheckerErrors> {
self.analyze();
let result = self.find_errors();
debug!("=== CHECKER RESULT ===");
fn print_state(state: &CheckerState) {
let mut s = vec![];
for (alloc, state) in &state.allocations {
s.push(format!("{} := {}", alloc, state));
}
debug!(" {{ {} }}", s.join(", "))
}
for bb in 0..self.f.blocks() {
let bb = Block::new(bb);
debug!("block{}:", bb.index());
let insts = self.bb_insts.get(&bb).unwrap();
let mut state = self.bb_in.get(&bb).unwrap().clone();
print_state(&state);
for inst in insts {
match inst {
&CheckerInst::Op {
inst,
ref operands,
ref allocs,
} => {
debug!(" inst{}: {:?} ({:?})", inst.index(), operands, allocs);
}
&CheckerInst::Move { from, into } => {
debug!(" {} -> {}", from, into);
}
&CheckerInst::BlockParams {
ref vregs,
ref allocs,
..
} => {
let mut args = vec![];
for (vreg, alloc) in vregs.iter().zip(allocs.iter()) {
args.push(format!("{}:{}", vreg, alloc));
}
debug!(" blockparams: {}", args.join(", "));
}
}
state.update(inst);
print_state(&state);
}
}
result
}
}

118
src/domtree.rs Normal file
View File

@@ -0,0 +1,118 @@
/*
* Derives from the dominator tree implementation in regalloc.rs, which is
* licensed under the Apache Public License 2.0 with LLVM Exception. See:
* https://github.com/bytecodealliance/regalloc.rs
*/
// This is an implementation of the algorithm described in
//
// A Simple, Fast Dominance Algorithm
// Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
// Department of Computer Science, Rice University, Houston, Texas, USA
// TR-06-33870
// https://www.cs.rice.edu/~keith/EMBED/dom.pdf
use crate::Block;
// Helper
fn merge_sets(
idom: &[Block], // map from Block to Block
block_to_rpo: &[Option<u32>],
mut node1: Block,
mut node2: Block,
) -> Block {
while node1 != node2 {
if node1.is_invalid() || node2.is_invalid() {
return Block::invalid();
}
let rpo1 = block_to_rpo[node1.index()].unwrap();
let rpo2 = block_to_rpo[node2.index()].unwrap();
if rpo1 > rpo2 {
node1 = idom[node1.index()];
} else if rpo2 > rpo1 {
node2 = idom[node2.index()];
}
}
assert!(node1 == node2);
node1
}
pub fn calculate<'a, PredFn: Fn(Block) -> &'a [Block]>(
num_blocks: usize,
preds: PredFn,
post_ord: &[Block],
start: Block,
) -> Vec<Block> {
// We have post_ord, which is the postorder sequence.
// Compute maps from RPO to block number and vice-versa.
let mut block_to_rpo = vec![None; num_blocks];
block_to_rpo.resize(num_blocks, None);
for (i, rpo_block) in post_ord.iter().rev().enumerate() {
block_to_rpo[rpo_block.index()] = Some(i as u32);
}
let mut idom = vec![Block::invalid(); num_blocks];
// The start node must have itself as a parent.
idom[start.index()] = start;
let mut changed = true;
while changed {
changed = false;
// Consider blocks in reverse postorder. Skip any that are unreachable.
for &node in post_ord.iter().rev() {
let rponum = block_to_rpo[node.index()].unwrap();
let mut parent = Block::invalid();
for &pred in preds(node).iter() {
let pred_rpo = match block_to_rpo[pred.index()] {
Some(r) => r,
None => {
// Skip unreachable preds.
continue;
}
};
if pred_rpo < rponum {
parent = pred;
break;
}
}
if parent.is_valid() {
for &pred in preds(node).iter() {
if pred == parent {
continue;
}
if idom[pred.index()].is_invalid() {
continue;
}
parent = merge_sets(&idom, &block_to_rpo[..], parent, pred);
}
}
if parent.is_valid() && parent != idom[node.index()] {
idom[node.index()] = parent;
changed = true;
}
}
}
idom
}
pub fn dominates(idom: &[Block], a: Block, mut b: Block) -> bool {
loop {
if a == b {
return true;
}
if b.is_invalid() {
return false;
}
let parent = idom[b.index()];
if b == parent {
return false;
}
b = idom[b.index()];
}
}

542
src/fuzzing/func.rs Normal file
View File

@@ -0,0 +1,542 @@
use crate::{
domtree, postorder, Allocation, Block, Function, Inst, InstRange, MachineEnv, Operand,
OperandKind, OperandPolicy, OperandPos, PReg, RegClass, VReg,
};
use arbitrary::Result as ArbitraryResult;
use arbitrary::{Arbitrary, Unstructured};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InstOpcode {
Phi,
Op,
Call,
Ret,
Branch,
}
#[derive(Clone, Debug)]
pub struct InstData {
op: InstOpcode,
operands: Vec<Operand>,
clobbers: Vec<PReg>,
}
impl InstData {
pub fn op(def: usize, uses: &[usize]) -> InstData {
let mut operands = vec![Operand::reg_def(VReg::new(def, RegClass::Int))];
for &u in uses {
operands.push(Operand::reg_use(VReg::new(u, RegClass::Int)));
}
InstData {
op: InstOpcode::Op,
operands,
clobbers: vec![],
}
}
pub fn branch(uses: &[usize]) -> InstData {
let mut operands = vec![];
for &u in uses {
operands.push(Operand::reg_use(VReg::new(u, RegClass::Int)));
}
InstData {
op: InstOpcode::Branch,
operands,
clobbers: vec![],
}
}
pub fn ret() -> InstData {
InstData {
op: InstOpcode::Ret,
operands: vec![],
clobbers: vec![],
}
}
}
#[derive(Clone)]
pub struct Func {
insts: Vec<InstData>,
blocks: Vec<InstRange>,
block_preds: Vec<Vec<Block>>,
block_succs: Vec<Vec<Block>>,
block_params: Vec<Vec<VReg>>,
num_vregs: usize,
}
impl Function for Func {
fn insts(&self) -> usize {
self.insts.len()
}
fn blocks(&self) -> usize {
self.blocks.len()
}
fn entry_block(&self) -> Block {
assert!(self.blocks.len() > 0);
Block::new(0)
}
fn block_insns(&self, block: Block) -> InstRange {
self.blocks[block.index()]
}
fn block_succs(&self, block: Block) -> &[Block] {
&self.block_succs[block.index()][..]
}
fn block_preds(&self, block: Block) -> &[Block] {
&self.block_preds[block.index()][..]
}
fn block_params(&self, block: Block) -> &[VReg] {
&self.block_params[block.index()][..]
}
fn is_call(&self, insn: Inst) -> bool {
self.insts[insn.index()].op == InstOpcode::Call
}
fn is_ret(&self, insn: Inst) -> bool {
self.insts[insn.index()].op == InstOpcode::Ret
}
fn is_branch(&self, insn: Inst) -> bool {
self.insts[insn.index()].op == InstOpcode::Branch
}
fn is_safepoint(&self, _: Inst) -> bool {
false
}
fn is_move(&self, _: Inst) -> Option<(VReg, VReg)> {
None
}
fn inst_operands(&self, insn: Inst) -> &[Operand] {
&self.insts[insn.index()].operands[..]
}
fn inst_clobbers(&self, insn: Inst) -> &[PReg] {
&self.insts[insn.index()].clobbers[..]
}
fn num_vregs(&self) -> usize {
self.num_vregs
}
fn spillslot_size(&self, regclass: RegClass, _: VReg) -> usize {
match regclass {
RegClass::Int => 1,
RegClass::Float => 2,
}
}
}
struct FuncBuilder {
postorder: Vec<Block>,
idom: Vec<Block>,
f: Func,
insts_per_block: Vec<Vec<InstData>>,
}
impl FuncBuilder {
fn new() -> Self {
FuncBuilder {
postorder: vec![],
idom: vec![],
f: Func {
block_preds: vec![],
block_succs: vec![],
block_params: vec![],
insts: vec![],
blocks: vec![],
num_vregs: 0,
},
insts_per_block: vec![],
}
}
pub fn add_block(&mut self) -> Block {
let b = Block::new(self.f.blocks.len());
self.f
.blocks
.push(InstRange::forward(Inst::new(0), Inst::new(0)));
self.f.block_preds.push(vec![]);
self.f.block_succs.push(vec![]);
self.f.block_params.push(vec![]);
self.insts_per_block.push(vec![]);
b
}
pub fn add_inst(&mut self, block: Block, data: InstData) {
self.insts_per_block[block.index()].push(data);
}
pub fn add_edge(&mut self, from: Block, to: Block) {
self.f.block_succs[from.index()].push(to);
self.f.block_preds[to.index()].push(from);
}
pub fn set_block_params(&mut self, block: Block, params: &[VReg]) {
self.f.block_params[block.index()] = params.iter().cloned().collect();
}
fn compute_doms(&mut self) {
self.postorder = postorder::calculate(self.f.blocks.len(), Block::new(0), |block| {
&self.f.block_succs[block.index()][..]
});
self.idom = domtree::calculate(
self.f.blocks.len(),
|block| &self.f.block_preds[block.index()][..],
&self.postorder[..],
Block::new(0),
);
}
fn finalize(mut self) -> Func {
for (blocknum, blockrange) in self.f.blocks.iter_mut().enumerate() {
let begin_inst = self.f.insts.len();
for inst in &self.insts_per_block[blocknum] {
self.f.insts.push(inst.clone());
}
let end_inst = self.f.insts.len();
*blockrange = InstRange::forward(Inst::new(begin_inst), Inst::new(end_inst));
}
self.f
}
}
impl Arbitrary for OperandPolicy {
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Self> {
Ok(*u.choose(&[OperandPolicy::Any, OperandPolicy::Reg])?)
}
}
fn choose_dominating_block(
idom: &[Block],
mut block: Block,
allow_self: bool,
u: &mut Unstructured,
) -> ArbitraryResult<Block> {
assert!(block.is_valid());
let orig_block = block;
loop {
if (allow_self || block != orig_block) && bool::arbitrary(u)? {
break;
}
if idom[block.index()] == block {
break;
}
block = idom[block.index()];
assert!(block.is_valid());
}
let block = if block != orig_block || allow_self {
block
} else {
Block::invalid()
};
Ok(block)
}
#[derive(Clone, Copy, Debug)]
pub struct Options {
pub reused_inputs: bool,
pub fixed_regs: bool,
pub clobbers: bool,
pub control_flow: bool,
pub reducible: bool,
pub block_params: bool,
pub always_local_uses: bool,
}
impl std::default::Default for Options {
fn default() -> Self {
Options {
reused_inputs: false,
fixed_regs: false,
clobbers: false,
control_flow: true,
reducible: false,
block_params: true,
always_local_uses: false,
}
}
}
impl Arbitrary for Func {
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Func> {
Func::arbitrary_with_options(u, &Options::default())
}
}
impl Func {
pub fn arbitrary_with_options(u: &mut Unstructured, opts: &Options) -> ArbitraryResult<Func> {
// General strategy:
// 1. Create an arbitrary CFG.
// 2. Create a list of vregs to define in each block.
// 3. Define some of those vregs in each block as blockparams.f.
// 4. Populate blocks with ops that define the rest of the vregs.
// - For each use, choose an available vreg: either one
// already defined (via blockparam or inst) in this block,
// or one defined in a dominating block.
let mut builder = FuncBuilder::new();
for _ in 0..u.int_in_range(1..=100)? {
builder.add_block();
}
let num_blocks = builder.f.blocks.len();
// Generate a CFG. Create a "spine" of either single blocks,
// with links to the next; or fork patterns, with the left
// fork linking to the next and the right fork in `out_blocks`
// to be connected below. This creates an arbitrary CFG with
// split critical edges, which is a property that we require
// for the regalloc.
let mut from = 0;
let mut out_blocks = vec![];
let mut in_blocks = vec![];
// For reducibility, if selected: enforce strict nesting of backedges
let mut max_backedge_src = 0;
let mut min_backedge_dest = num_blocks;
while from < num_blocks {
in_blocks.push(from);
if num_blocks > 3 && from < num_blocks - 3 && bool::arbitrary(u)? && opts.control_flow {
// To avoid critical edges, we use from+1 as an edge
// block, and advance `from` an extra block; `from+2`
// will be the next normal iteration.
builder.add_edge(Block::new(from), Block::new(from + 1));
builder.add_edge(Block::new(from), Block::new(from + 2));
builder.add_edge(Block::new(from + 2), Block::new(from + 3));
out_blocks.push(from + 1);
from += 2;
} else if from < num_blocks - 1 {
builder.add_edge(Block::new(from), Block::new(from + 1));
}
from += 1;
}
for pred in out_blocks {
let mut succ = *u.choose(&in_blocks[..])?;
if opts.reducible && (pred >= succ) {
if pred < max_backedge_src || succ > min_backedge_dest {
// If the chosen edge would result in an
// irreducible CFG, just make this a diamond
// instead.
succ = pred + 2;
} else {
max_backedge_src = pred;
min_backedge_dest = succ;
}
}
builder.add_edge(Block::new(pred), Block::new(succ));
}
builder.compute_doms();
for block in 0..num_blocks {
builder.f.block_preds[block].clear();
}
for block in 0..num_blocks {
for &succ in &builder.f.block_succs[block] {
builder.f.block_preds[succ.index()].push(Block::new(block));
}
}
builder.compute_doms();
let mut vregs_by_block = vec![];
let mut vregs_by_block_to_be_defined = vec![];
let mut block_params = vec![vec![]; num_blocks];
for block in 0..num_blocks {
let mut vregs = vec![];
for _ in 0..u.int_in_range(5..=15)? {
let vreg = VReg::new(builder.f.num_vregs, RegClass::Int);
builder.f.num_vregs += 1;
vregs.push(vreg);
}
vregs_by_block.push(vregs.clone());
vregs_by_block_to_be_defined.push(vec![]);
let mut max_block_params = u.int_in_range(0..=std::cmp::min(3, vregs.len() / 3))?;
for &vreg in &vregs {
if block > 0 && opts.block_params && bool::arbitrary(u)? && max_block_params > 0 {
block_params[block].push(vreg);
max_block_params -= 1;
} else {
vregs_by_block_to_be_defined.last_mut().unwrap().push(vreg);
}
}
vregs_by_block_to_be_defined.last_mut().unwrap().reverse();
builder.set_block_params(Block::new(block), &block_params[block][..]);
}
for block in 0..num_blocks {
let mut avail = block_params[block].clone();
let mut remaining_nonlocal_uses = u.int_in_range(0..=3)?;
while let Some(vreg) = vregs_by_block_to_be_defined[block].pop() {
let def_policy = OperandPolicy::arbitrary(u)?;
let def_pos = if bool::arbitrary(u)? {
OperandPos::Before
} else {
OperandPos::After
};
let mut operands = vec![Operand::new(vreg, def_policy, OperandKind::Def, def_pos)];
let mut allocations = vec![Allocation::none()];
for _ in 0..u.int_in_range(0..=3)? {
let vreg = if avail.len() > 0
&& (opts.always_local_uses
|| remaining_nonlocal_uses == 0
|| bool::arbitrary(u)?)
{
*u.choose(&avail[..])?
} else if !opts.always_local_uses {
let def_block = choose_dominating_block(
&builder.idom[..],
Block::new(block),
/* allow_self = */ false,
u,
)?;
if !def_block.is_valid() {
// No vregs already defined, and no pred blocks that dominate us
// (perhaps we are the entry block): just stop generating inputs.
break;
}
remaining_nonlocal_uses -= 1;
*u.choose(&vregs_by_block[def_block.index()])?
} else {
break;
};
let use_policy = OperandPolicy::arbitrary(u)?;
operands.push(Operand::new(
vreg,
use_policy,
OperandKind::Use,
OperandPos::Before,
));
allocations.push(Allocation::none());
}
let mut clobbers: Vec<PReg> = vec![];
if operands.len() > 1 && opts.reused_inputs && bool::arbitrary(u)? {
// Make the def a reused input.
let op = operands[0];
assert_eq!(op.kind(), OperandKind::Def);
let reused = u.int_in_range(1..=(operands.len() - 1))?;
operands[0] = Operand::new(
op.vreg(),
OperandPolicy::Reuse(reused),
op.kind(),
OperandPos::After,
);
} else if opts.fixed_regs && bool::arbitrary(u)? {
// Pick an operand and make it a fixed reg.
let fixed_reg = PReg::new(u.int_in_range(0..=30)?, RegClass::Int);
let i = u.int_in_range(0..=(operands.len() - 1))?;
let op = operands[i];
operands[i] = Operand::new(
op.vreg(),
OperandPolicy::FixedReg(fixed_reg),
op.kind(),
op.pos(),
);
} else if opts.clobbers && bool::arbitrary(u)? {
for _ in 0..u.int_in_range(0..=5)? {
let reg = u.int_in_range(0..=30)?;
if clobbers.iter().any(|r| r.hw_enc() == reg) {
break;
}
clobbers.push(PReg::new(reg, RegClass::Int));
}
}
let op = *u.choose(&[InstOpcode::Op, InstOpcode::Call])?;
builder.add_inst(
Block::new(block),
InstData {
op,
operands,
clobbers,
},
);
avail.push(vreg);
}
// Define the branch with blockparam args that must end
// the block.
if builder.f.block_succs[block].len() > 0 {
let mut args = vec![];
for &succ in &builder.f.block_succs[block] {
for _ in 0..builder.f.block_params[succ.index()].len() {
let dom_block = choose_dominating_block(
&builder.idom[..],
Block::new(block),
false,
u,
)?;
let vreg = if dom_block.is_valid() && bool::arbitrary(u)? {
u.choose(&vregs_by_block[dom_block.index()][..])?
} else {
u.choose(&avail[..])?
};
args.push(vreg.vreg());
}
}
builder.add_inst(Block::new(block), InstData::branch(&args[..]));
} else {
builder.add_inst(Block::new(block), InstData::ret());
}
}
Ok(builder.finalize())
}
}
impl std::fmt::Debug for Func {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{{\n")?;
for (i, blockrange) in self.blocks.iter().enumerate() {
let succs = self.block_succs[i]
.iter()
.map(|b| b.index())
.collect::<Vec<_>>();
let preds = self.block_preds[i]
.iter()
.map(|b| b.index())
.collect::<Vec<_>>();
let params = self.block_params[i]
.iter()
.map(|v| format!("v{}", v.vreg()))
.collect::<Vec<_>>()
.join(", ");
write!(
f,
" block{}({}): # succs:{:?} preds:{:?}\n",
i, params, succs, preds
)?;
for inst in blockrange.iter() {
write!(
f,
" inst{}: {:?} ops:{:?} clobber:{:?}\n",
inst.index(),
self.insts[inst.index()].op,
self.insts[inst.index()].operands,
self.insts[inst.index()].clobbers
)?;
}
}
write!(f, "}}\n")?;
Ok(())
}
}
pub fn machine_env() -> MachineEnv {
// Reg 31 is the scratch reg.
let regs: Vec<PReg> = (0..31).map(|i| PReg::new(i, RegClass::Int)).collect();
let regs_by_class: Vec<Vec<PReg>> = vec![regs.clone(), vec![]];
let scratch_by_class: Vec<PReg> =
vec![PReg::new(31, RegClass::Int), PReg::new(0, RegClass::Float)];
MachineEnv {
regs,
regs_by_class,
scratch_by_class,
}
}

3
src/fuzzing/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
//! Utilities for fuzzing.
pub mod func;

176
src/index.rs Normal file
View File

@@ -0,0 +1,176 @@
#[macro_export]
macro_rules! define_index {
($ix:ident) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $ix(pub u32);
impl $ix {
#[inline(always)]
pub fn new(i: usize) -> Self {
Self(i as u32)
}
#[inline(always)]
pub fn index(self) -> usize {
assert!(self.is_valid());
self.0 as usize
}
#[inline(always)]
pub fn invalid() -> Self {
Self(u32::MAX)
}
#[inline(always)]
pub fn is_invalid(self) -> bool {
self == Self::invalid()
}
#[inline(always)]
pub fn is_valid(self) -> bool {
self != Self::invalid()
}
#[inline(always)]
pub fn next(self) -> $ix {
assert!(self.is_valid());
Self(self.0 + 1)
}
#[inline(always)]
pub fn prev(self) -> $ix {
assert!(self.is_valid());
Self(self.0 - 1)
}
}
impl crate::index::ContainerIndex for $ix {}
};
}
pub trait ContainerIndex: Clone + Copy + std::fmt::Debug + PartialEq + Eq {}
pub trait ContainerComparator {
type Ix: ContainerIndex;
fn compare(&self, a: Self::Ix, b: Self::Ix) -> std::cmp::Ordering;
}
define_index!(Inst);
define_index!(Block);
#[derive(Clone, Copy, Debug)]
pub struct InstRange(Inst, Inst, bool);
impl InstRange {
#[inline(always)]
pub fn forward(from: Inst, to: Inst) -> Self {
assert!(from.index() <= to.index());
InstRange(from, to, true)
}
#[inline(always)]
pub fn backward(from: Inst, to: Inst) -> Self {
assert!(from.index() >= to.index());
InstRange(to, from, false)
}
#[inline(always)]
pub fn first(self) -> Inst {
assert!(self.len() > 0);
if self.is_forward() {
self.0
} else {
self.1.prev()
}
}
#[inline(always)]
pub fn last(self) -> Inst {
assert!(self.len() > 0);
if self.is_forward() {
self.1.prev()
} else {
self.0
}
}
#[inline(always)]
pub fn rest(self) -> InstRange {
assert!(self.len() > 0);
if self.is_forward() {
InstRange::forward(self.0.next(), self.1)
} else {
InstRange::backward(self.1.prev(), self.0)
}
}
#[inline(always)]
pub fn len(self) -> usize {
self.1.index() - self.0.index()
}
#[inline(always)]
pub fn is_forward(self) -> bool {
self.2
}
#[inline(always)]
pub fn rev(self) -> Self {
Self(self.0, self.1, !self.2)
}
#[inline(always)]
pub fn iter(self) -> InstRangeIter {
InstRangeIter(self)
}
}
#[derive(Clone, Copy, Debug)]
pub struct InstRangeIter(InstRange);
impl Iterator for InstRangeIter {
type Item = Inst;
#[inline(always)]
fn next(&mut self) -> Option<Inst> {
if self.0.len() == 0 {
None
} else {
let ret = self.0.first();
self.0 = self.0.rest();
Some(ret)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_inst_range() {
let range = InstRange::forward(Inst::new(0), Inst::new(0));
assert_eq!(range.len(), 0);
let range = InstRange::forward(Inst::new(0), Inst::new(5));
assert_eq!(range.first().index(), 0);
assert_eq!(range.last().index(), 4);
assert_eq!(range.len(), 5);
assert_eq!(
range.iter().collect::<Vec<_>>(),
vec![
Inst::new(0),
Inst::new(1),
Inst::new(2),
Inst::new(3),
Inst::new(4)
]
);
let range = range.rev();
assert_eq!(range.first().index(), 4);
assert_eq!(range.last().index(), 0);
assert_eq!(range.len(), 5);
assert_eq!(
range.iter().collect::<Vec<_>>(),
vec![
Inst::new(4),
Inst::new(3),
Inst::new(2),
Inst::new(1),
Inst::new(0)
]
);
}
}

373
src/ion/LICENSE Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

3763
src/ion/mod.rs Normal file

File diff suppressed because it is too large Load Diff

780
src/lib.rs Normal file
View File

@@ -0,0 +1,780 @@
/*
* 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;
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 31 bits. This allows a `Reg` to encode an
/// `Operand` or an `Allocation` in 32 bits.
///
/// op-or-alloc:1 pos:2 kind:1 policy: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::FixedReg(preg) => {
assert_eq!(preg.class(), vreg.class());
(preg.hw_enc() as u32, 2)
}
OperandPolicy::Reuse(which) => {
assert!(which <= PReg::MAX);
(which as u32, 3)
}
};
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)
| (policy_field << 26)
| (kind_field << 28)
| (pos_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 >> 29) & 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 >> 26) & 3;
let preg_field = ((self.bits >> 20) as usize) & PReg::MAX;
match policy_field {
0 => OperandPolicy::Any,
1 => OperandPolicy::Reg,
2 => OperandPolicy::FixedReg(PReg::new(preg_field, self.class())),
3 => OperandPolicy::Reuse(preg_field),
_ => unreachable!(),
}
}
#[inline(always)]
pub fn bits(self) -> u32 {
self.bits
}
#[inline(always)]
pub fn from_bits(bits: u32) -> Self {
Operand { bits }
}
}
impl std::fmt::Debug for Operand {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Operand(vreg = {:?}, class = {:?}, kind = {:?}, pos = {:?}, policy = {:?})",
self.vreg().vreg(),
self.class(),
self.kind(),
self.pos(),
self.policy()
)
}
}
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(),
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 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::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 31 bits:
///
/// op-or-alloc:1 kind:2 index:29
bits: u32,
}
impl std::fmt::Debug for Allocation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Allocation(kind = {:?}, index = {})",
self.kind(),
self.index()
)
}
}
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 {
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) & 3 {
0 => AllocationKind::None,
1 => AllocationKind::Reg,
2 => AllocationKind::Stack,
_ => unreachable!(),
}
}
#[inline(always)]
pub fn index(self) -> usize {
(self.bits & ((1 << 29) - 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 {
Self { bits }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum AllocationKind {
None = 0,
Reg = 1,
Stack = 2,
}
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 trait defined by the regalloc client to provide access to its
/// machine-instruction / CFG representation.
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 *must* be the block parameters for
/// each of its block's `block_succs` successor blocks, in order.
fn is_branch(&self, insn: Inst) -> bool;
/// Determine whether an instruction is a safepoint and requires a stackmap.
fn is_safepoint(&self, insn: Inst) -> bool;
/// 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 precise number of `VReg` in use in this function, to allow
/// preallocating data structures. This number *must* be a correct
/// lower-bound, otherwise invalid index failures may happen; it is of
/// course better if it is exact.
fn num_vregs(&self) -> usize;
// --------------
// 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.
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>,
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>,
/// 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)
}

199
src/moves.rs Normal file
View File

@@ -0,0 +1,199 @@
use crate::Allocation;
use smallvec::{smallvec, SmallVec};
pub type MoveVec = SmallVec<[(Allocation, Allocation); 16]>;
/// A `ParallelMoves` represents a list of alloc-to-alloc moves that
/// must happen in parallel -- i.e., all reads of sources semantically
/// happen before all writes of destinations, and destinations are
/// allowed to overwrite sources. It can compute a list of sequential
/// moves that will produce the equivalent data movement, possibly
/// using a scratch register if one is necessary.
pub struct ParallelMoves {
parallel_moves: MoveVec,
scratch: Allocation,
}
impl ParallelMoves {
pub fn new(scratch: Allocation) -> Self {
Self {
parallel_moves: smallvec![],
scratch,
}
}
pub fn add(&mut self, from: Allocation, to: Allocation) {
self.parallel_moves.push((from, to));
}
fn sources_overlap_dests(&self) -> bool {
// Assumes `parallel_moves` has already been sorted in `resolve()` below.
for &(_, dst) in &self.parallel_moves {
if self
.parallel_moves
.binary_search_by_key(&dst, |&(src, _)| src)
.is_ok()
{
return true;
}
}
false
}
pub fn resolve(mut self) -> MoveVec {
// Easy case: zero or one move. Just return our vec.
if self.parallel_moves.len() <= 1 {
return self.parallel_moves;
}
// Sort moves by source so that we can efficiently test for
// presence.
self.parallel_moves.sort();
// Do any dests overlap sources? If not, we can also just
// return the list.
if !self.sources_overlap_dests() {
return self.parallel_moves;
}
// General case: some moves overwrite dests that other moves
// read as sources. We'll use a general algorithm.
//
// *Important property*: because we expect that each register
// has only one writer (otherwise the effect of the parallel
// move is undefined), each move can only block one other move
// (with its one source corresponding to the one writer of
// that source). Thus, we *can only have simple cycles*: there
// are no SCCs that are more complex than that. We leverage
// this fact below to avoid having to do a full Tarjan SCC DFS
// (with lowest-index computation, etc.): instead, as soon as
// we find a cycle, we know we have the full cycle and we can
// do a cyclic move sequence and continue.
// Sort moves by destination and check that each destination
// has only one writer.
self.parallel_moves.sort_by_key(|&(_, dst)| dst);
if cfg!(debug) {
let mut last_dst = None;
for &(_, dst) in &self.parallel_moves {
if last_dst.is_some() {
assert!(last_dst.unwrap() != dst);
}
last_dst = Some(dst);
}
}
// Construct a mapping from move indices to moves they must
// come before. Any given move must come before a move that
// overwrites its destination; we have moves sorted by dest
// above so we can efficiently find such a move, if any.
let mut must_come_before: SmallVec<[Option<usize>; 16]> =
smallvec![None; self.parallel_moves.len()];
for (i, &(src, _)) in self.parallel_moves.iter().enumerate() {
if let Ok(move_to_dst_idx) = self
.parallel_moves
.binary_search_by_key(&src, |&(_, dst)| dst)
{
must_come_before[i] = Some(move_to_dst_idx);
}
}
// Do a simple stack-based DFS and emit moves in postorder,
// then reverse at the end for RPO. Unlike Tarjan's SCC
// algorithm, we can emit a cycle as soon as we find one, as
// noted above.
let mut ret: MoveVec = smallvec![];
let mut stack: SmallVec<[usize; 16]> = smallvec![];
let mut visited: SmallVec<[bool; 16]> = smallvec![false; self.parallel_moves.len()];
let mut onstack: SmallVec<[bool; 16]> = smallvec![false; self.parallel_moves.len()];
stack.push(0);
onstack[0] = true;
loop {
if stack.is_empty() {
if let Some(next) = visited.iter().position(|&flag| !flag) {
stack.push(next);
onstack[next] = true;
} else {
break;
}
}
let top = *stack.last().unwrap();
visited[top] = true;
match must_come_before[top] {
None => {
ret.push(self.parallel_moves[top]);
onstack[top] = false;
stack.pop();
while let Some(top) = stack.pop() {
ret.push(self.parallel_moves[top]);
onstack[top] = false;
}
}
Some(next) if visited[next] && !onstack[next] => {
ret.push(self.parallel_moves[top]);
onstack[top] = false;
stack.pop();
while let Some(top) = stack.pop() {
ret.push(self.parallel_moves[top]);
onstack[top] = false;
}
}
Some(next) if !visited[next] && !onstack[next] => {
stack.push(next);
onstack[next] = true;
continue;
}
Some(next) => {
// Found a cycle -- emit a cyclic-move sequence
// for the cycle on the top of stack, then normal
// moves below it. Recall that these moves will be
// reversed in sequence, so from the original
// parallel move set
//
// { B := A, C := B, A := B }
//
// we will generate something like:
//
// A := scratch
// B := A
// C := B
// scratch := C
//
// which will become:
//
// scratch := C
// C := B
// B := A
// A := scratch
let mut last_dst = None;
let mut scratch_src = None;
while let Some(move_idx) = stack.pop() {
onstack[move_idx] = false;
let (mut src, dst) = self.parallel_moves[move_idx];
if last_dst.is_none() {
scratch_src = Some(src);
src = self.scratch;
} else {
assert_eq!(last_dst.unwrap(), src);
}
ret.push((src, dst));
last_dst = Some(dst);
if move_idx == next {
break;
}
}
if let Some(src) = scratch_src {
ret.push((src, self.scratch));
}
}
}
}
ret.reverse();
ret
}
}

51
src/postorder.rs Normal file
View File

@@ -0,0 +1,51 @@
//! Fast postorder computation with no allocations (aside from result).
use crate::Block;
use smallvec::{smallvec, SmallVec};
pub fn calculate<'a, SuccFn: Fn(Block) -> &'a [Block]>(
num_blocks: usize,
entry: Block,
succ_blocks: SuccFn,
) -> Vec<Block> {
let mut ret = vec![];
// State: visited-block map, and explicit DFS stack.
let mut visited = vec![];
visited.resize(num_blocks, false);
struct State<'a> {
block: Block,
succs: &'a [Block],
next_succ: usize,
}
let mut stack: SmallVec<[State; 64]> = smallvec![];
visited[entry.index()] = true;
stack.push(State {
block: entry,
succs: succ_blocks(entry),
next_succ: 0,
});
while let Some(ref mut state) = stack.last_mut() {
// Perform one action: push to new succ, skip an already-visited succ, or pop.
if state.next_succ < state.succs.len() {
let succ = state.succs[state.next_succ];
state.next_succ += 1;
if !visited[succ.index()] {
visited[succ.index()] = true;
stack.push(State {
block: succ,
succs: succ_blocks(succ),
next_succ: 0,
});
}
} else {
ret.push(state.block);
stack.pop();
}
}
ret
}

87
src/ssa.rs Normal file
View File

@@ -0,0 +1,87 @@
//! SSA-related utilities.
use crate::cfg::CFGInfo;
use crate::{Block, Function, Inst, OperandKind, RegAllocError};
pub fn validate_ssa<F: Function>(f: &F, cfginfo: &CFGInfo) -> Result<(), RegAllocError> {
// Walk the blocks in arbitrary order. Check, for every use, that
// the def is either in the same block in an earlier inst, or is
// defined (by inst or blockparam) in some other block that
// dominates this one. Also check that for every block param and
// inst def, that this is the only def.
let mut defined = vec![false; f.num_vregs()];
for block in 0..f.blocks() {
let block = Block::new(block);
for blockparam in f.block_params(block) {
if defined[blockparam.vreg()] {
return Err(RegAllocError::SSA(*blockparam, Inst::invalid()));
}
defined[blockparam.vreg()] = true;
}
for iix in f.block_insns(block).iter() {
let operands = f.inst_operands(iix);
for operand in operands {
match operand.kind() {
OperandKind::Use => {
let def_block = if cfginfo.vreg_def_inst[operand.vreg().vreg()].is_valid() {
cfginfo.insn_block[cfginfo.vreg_def_inst[operand.vreg().vreg()].index()]
} else {
cfginfo.vreg_def_blockparam[operand.vreg().vreg()].0
};
if def_block.is_invalid() {
return Err(RegAllocError::SSA(operand.vreg(), iix));
}
if !cfginfo.dominates(def_block, block) {
return Err(RegAllocError::SSA(operand.vreg(), iix));
}
}
OperandKind::Def => {
if defined[operand.vreg().vreg()] {
return Err(RegAllocError::SSA(operand.vreg(), iix));
}
defined[operand.vreg().vreg()] = true;
}
}
}
}
}
// Check that the length of branch args matches the sum of the
// number of blockparams in their succs, and that the end of every
// block ends in this branch or in a ret, and that there are no
// other branches or rets in the middle of the block.
for block in 0..f.blocks() {
let block = Block::new(block);
let insns = f.block_insns(block);
for insn in insns.iter() {
if insn == insns.last() {
if !(f.is_branch(insn) || f.is_ret(insn)) {
return Err(RegAllocError::BB(block));
}
if f.is_branch(insn) {
let expected = f
.block_succs(block)
.iter()
.map(|&succ| f.block_params(succ).len())
.sum();
if f.inst_operands(insn).len() != expected {
return Err(RegAllocError::Branch(insn));
}
}
} else {
if f.is_branch(insn) || f.is_ret(insn) {
return Err(RegAllocError::BB(block));
}
}
}
}
// Check that the entry block has no block args: otherwise it is
// undefined what their value would be.
if f.block_params(f.entry_block()).len() > 0 {
return Err(RegAllocError::BB(f.entry_block()));
}
Ok(())
}