Compare commits
10 Commits
376294e828
...
f0e9cde328
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0e9cde328 | ||
|
|
9c6d6dc9aa | ||
|
|
2bd03256b3 | ||
|
|
7354cfedde | ||
|
|
54f074e507 | ||
|
|
34a9ae7379 | ||
|
|
1e8da4f99b | ||
|
|
7bb83a3361 | ||
|
|
c3e513c4cb | ||
|
|
50b9cf8fe2 |
10
.github/workflows/rust.yml
vendored
10
.github/workflows/rust.yml
vendored
@@ -37,6 +37,16 @@ jobs:
|
||||
- name: Check with all features
|
||||
run: cargo check --all-features
|
||||
|
||||
# Make sure the code and its dependencies compile without std.
|
||||
no_std:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install thumbv6m-none-eabi target
|
||||
run: rustup target add thumbv6m-none-eabi
|
||||
- name: Check no_std build
|
||||
run: cargo check --target thumbv6m-none-eabi --no-default-features --features trace-log,checker,enable-serde
|
||||
|
||||
# Lint dependency graph for security advisories, duplicate versions, and
|
||||
# incompatible licences.
|
||||
cargo_deny:
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regalloc2"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
authors = [
|
||||
"Chris Fallin <chris@cfallin.org>",
|
||||
"Mozilla SpiderMonkey Developers",
|
||||
@@ -13,11 +13,15 @@ repository = "https://github.com/bytecodealliance/regalloc2"
|
||||
[dependencies]
|
||||
log = { version = "0.4.8", default-features = false }
|
||||
smallvec = { version = "1.6.1", features = ["union"] }
|
||||
fxhash = "0.2.1"
|
||||
slice-group-by = "0.3.0"
|
||||
rustc-hash = { version = "1.1.0", default-features = false }
|
||||
slice-group-by = { version = "0.3.0", default-features = false }
|
||||
hashbrown = "0.13.2"
|
||||
|
||||
# Optional serde support, enabled by feature below.
|
||||
serde = { version = "1.0.136", features = ["derive"], optional = true }
|
||||
serde = { version = "1.0.136", features = [
|
||||
"derive",
|
||||
"alloc",
|
||||
], default-features = false, optional = true }
|
||||
|
||||
# The below are only needed for fuzzing.
|
||||
libfuzzer-sys = { version = "0.4.2", optional = true }
|
||||
@@ -29,7 +33,10 @@ debug-assertions = true
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["std"]
|
||||
|
||||
# Enables std-specific features such as the Error trait for RegAllocError.
|
||||
std = []
|
||||
|
||||
# Enables generation of DefAlloc edits for the checker.
|
||||
checker = []
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
This is a register allocator that started life as, and is about 50%
|
||||
still, a port of IonMonkey's backtracking register allocator to
|
||||
Rust. In many regards, it has been generalized, optimized, and
|
||||
improved since the initial port, and now supports both SSA and non-SSA
|
||||
use-cases. (However, non-SSA should be considered deprecated; we want to
|
||||
move to SSA-only in the future, to enable some performance improvements.
|
||||
See #4.)
|
||||
improved since the initial port.
|
||||
|
||||
In addition, it contains substantial amounts of testing infrastructure
|
||||
(fuzzing harnesses and checkers) that does not exist in the original
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
//! Lightweight CFG analyses.
|
||||
|
||||
use crate::{domtree, postorder, Block, Function, Inst, ProgPoint, RegAllocError};
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
120
src/checker.rs
120
src/checker.rs
@@ -96,14 +96,16 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::{
|
||||
Allocation, AllocationKind, Block, Edit, Function, Inst, InstOrEdit, InstPosition, MachineEnv,
|
||||
Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg, PRegSet, VReg,
|
||||
Allocation, AllocationKind, Block, Edit, Function, FxHashMap, FxHashSet, Inst, InstOrEdit,
|
||||
InstPosition, MachineEnv, Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg,
|
||||
PRegSet, VReg,
|
||||
};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
use core::default::Default;
|
||||
use core::hash::Hash;
|
||||
use core::result::Result;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::default::Default;
|
||||
use std::hash::Hash;
|
||||
use std::result::Result;
|
||||
|
||||
/// A set of errors detected by the regalloc checker.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -230,7 +232,7 @@ impl CheckerValue {
|
||||
}
|
||||
|
||||
fn from_reg(reg: VReg) -> CheckerValue {
|
||||
CheckerValue::VRegs(std::iter::once(reg).collect())
|
||||
CheckerValue::VRegs(core::iter::once(reg).collect())
|
||||
}
|
||||
|
||||
fn remove_vreg(&mut self, reg: VReg) {
|
||||
@@ -269,10 +271,6 @@ fn visit_all_vregs<F: Function, V: FnMut(VReg)>(f: &F, mut v: V) {
|
||||
for op in f.inst_operands(inst) {
|
||||
v(op.vreg());
|
||||
}
|
||||
if let Some((src, dst)) = f.is_move(inst) {
|
||||
v(src.vreg());
|
||||
v(dst.vreg());
|
||||
}
|
||||
if f.is_branch(inst) {
|
||||
for succ_idx in 0..f.block_succs(block).len() {
|
||||
for ¶m in f.branch_blockparams(block, inst, succ_idx) {
|
||||
@@ -377,8 +375,8 @@ impl Default for CheckerState {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CheckerValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for CheckerValue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
CheckerValue::Universe => {
|
||||
write!(f, "top")
|
||||
@@ -565,25 +563,6 @@ impl CheckerState {
|
||||
// according to the move semantics in the step
|
||||
// function below.
|
||||
}
|
||||
&CheckerInst::ProgramMove { inst, src, dst: _ } => {
|
||||
// Validate that the fixed-reg constraint, if any, on
|
||||
// `src` is satisfied.
|
||||
if let OperandConstraint::FixedReg(preg) = src.constraint() {
|
||||
let alloc = Allocation::reg(preg);
|
||||
let val = self.get_value(&alloc).unwrap_or(&default_val);
|
||||
trace!(
|
||||
"checker: checkinst {:?}: cheker value in {:?} is {:?}",
|
||||
checkinst,
|
||||
alloc,
|
||||
val
|
||||
);
|
||||
self.check_val(inst, src, alloc, val, &[alloc], checker)?;
|
||||
}
|
||||
// Note that we don't do anything with `dst`
|
||||
// here. That is implicitly checked whenever `dst` is
|
||||
// used; the `update()` step below adds the symbolic
|
||||
// vreg for `dst` into wherever `src` may be stored.
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -686,15 +665,6 @@ impl CheckerState {
|
||||
}
|
||||
}
|
||||
}
|
||||
&CheckerInst::ProgramMove { inst: _, src, dst } => {
|
||||
// Remove all earlier instances of `dst`: this vreg is
|
||||
// now stale (it is being overwritten).
|
||||
self.remove_vreg(dst.vreg());
|
||||
// Define `dst` wherever `src` occurs.
|
||||
for (_, value) in self.get_mappings_mut() {
|
||||
value.copy_vreg(src.vreg(), dst.vreg());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,23 +756,6 @@ pub(crate) enum CheckerInst {
|
||||
/// A safepoint, with the given Allocations specified as containing
|
||||
/// reftyped values. All other reftyped values become invalid.
|
||||
Safepoint { inst: Inst, allocs: Vec<Allocation> },
|
||||
|
||||
/// An op with one source operand, and one dest operand, that
|
||||
/// copies any symbolic values from the source to the dest, in
|
||||
/// addition to adding the symbolic value of the dest vreg to the
|
||||
/// set. This "program move" is distinguished from the above
|
||||
/// `Move` by being semantically relevant in the original
|
||||
/// (pre-regalloc) program.
|
||||
///
|
||||
/// We transform checker values as follows: for any vreg-set that
|
||||
/// contains `dst`'s vreg, we first delete that vreg (because it
|
||||
/// is being redefined). Then, for any vreg-set with `src`
|
||||
/// present, we add `dst`.
|
||||
ProgramMove {
|
||||
inst: Inst,
|
||||
src: Operand,
|
||||
dst: Operand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -903,35 +856,10 @@ impl<'a, F: Function> Checker<'a, F> {
|
||||
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
|
||||
}
|
||||
|
||||
// If this is a move, handle specially. Note that the
|
||||
// regalloc2-inserted moves are not semantically present in
|
||||
// the original program and so do not modify the sets of
|
||||
// symbolic values at all, but rather just move them around;
|
||||
// but "program moves" *are* present, and have the following
|
||||
// semantics: they define the destination vreg, but also
|
||||
// retain any symbolic values in the source.
|
||||
//
|
||||
// regalloc2 reifies all moves into edits in its unified
|
||||
// move/edit framework, so we don't get allocs for these moves
|
||||
// in the post-regalloc output, and the embedder is not
|
||||
// supposed to emit the moves. But we *do* want to check the
|
||||
// semantic implications, namely definition of new vregs. So
|
||||
// we emit `ProgramMove` ops that do just this.
|
||||
if let Some((src, dst)) = self.f.is_move(inst) {
|
||||
let src_op = Operand::any_use(src.vreg());
|
||||
let dst_op = Operand::any_def(dst.vreg());
|
||||
let checkinst = CheckerInst::ProgramMove {
|
||||
inst,
|
||||
src: src_op,
|
||||
dst: dst_op,
|
||||
};
|
||||
trace!("checker: adding inst {:?}", checkinst);
|
||||
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
|
||||
}
|
||||
// Skip normal checks if this is a branch: the blockparams do
|
||||
// not exist in post-regalloc code, and the edge-moves have to
|
||||
// be inserted before the branch rather than after.
|
||||
else if !self.f.is_branch(inst) {
|
||||
if !self.f.is_branch(inst) {
|
||||
let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
|
||||
let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
|
||||
let clobbers: Vec<_> = self.f.inst_clobbers(inst).into_iter().collect();
|
||||
@@ -987,11 +915,21 @@ impl<'a, F: Function> Checker<'a, F> {
|
||||
let mut queue = Vec::new();
|
||||
let mut queue_set = FxHashSet::default();
|
||||
|
||||
queue.push(self.f.entry_block());
|
||||
queue_set.insert(self.f.entry_block());
|
||||
// Put every block in the queue to start with, to ensure
|
||||
// everything is visited even if the initial state remains
|
||||
// `Top` after preds update it.
|
||||
//
|
||||
// We add blocks in reverse order so that when we process
|
||||
// back-to-front below, we do our initial pass in input block
|
||||
// order, which is (usually) RPO order or at least a
|
||||
// reasonable visit order.
|
||||
for block in (0..self.f.num_blocks()).rev() {
|
||||
let block = Block::new(block);
|
||||
queue.push(block);
|
||||
queue_set.insert(block);
|
||||
}
|
||||
|
||||
while !queue.is_empty() {
|
||||
let block = queue.pop().unwrap();
|
||||
while let Some(block) = queue.pop() {
|
||||
queue_set.remove(&block);
|
||||
let mut state = self.bb_in.get(&block).cloned().unwrap();
|
||||
trace!("analyze: block {} has state {:?}", block.index(), state);
|
||||
@@ -1032,9 +970,8 @@ impl<'a, F: Function> Checker<'a, F> {
|
||||
new_state
|
||||
);
|
||||
self.bb_in.insert(succ, new_state);
|
||||
if !queue_set.contains(&succ) {
|
||||
if queue_set.insert(succ) {
|
||||
queue.push(succ);
|
||||
queue_set.insert(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1119,9 +1056,6 @@ impl<'a, F: Function> Checker<'a, F> {
|
||||
}
|
||||
trace!(" safepoint: {}", slotargs.join(", "));
|
||||
}
|
||||
&CheckerInst::ProgramMove { inst, src, dst } => {
|
||||
trace!(" inst{}: prog_move {} -> {}", inst.index(), src, dst);
|
||||
}
|
||||
&CheckerInst::ParallelMove { .. } => {
|
||||
panic!("unexpected parallel_move in body (non-edge)")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
// TR-06-33870
|
||||
// https://www.cs.rice.edu/~keith/EMBED/dom.pdf
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::Block;
|
||||
|
||||
// Helper
|
||||
|
||||
@@ -8,6 +8,9 @@ use crate::{
|
||||
OperandConstraint, OperandKind, OperandPos, PReg, PRegSet, RegClass, VReg,
|
||||
};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
|
||||
use super::arbitrary::Result as ArbitraryResult;
|
||||
use super::arbitrary::{Arbitrary, Unstructured};
|
||||
|
||||
@@ -124,10 +127,6 @@ impl Function for Func {
|
||||
&self.debug_value_labels[..]
|
||||
}
|
||||
|
||||
fn is_move(&self, _: Inst) -> Option<(Operand, Operand)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn inst_operands(&self, insn: Inst) -> &[Operand] {
|
||||
&self.insts[insn.index()].operands[..]
|
||||
}
|
||||
@@ -279,7 +278,7 @@ pub struct Options {
|
||||
pub reftypes: bool,
|
||||
}
|
||||
|
||||
impl std::default::Default for Options {
|
||||
impl core::default::Default for Options {
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
reused_inputs: false,
|
||||
@@ -408,7 +407,7 @@ impl Func {
|
||||
}
|
||||
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))?;
|
||||
let mut max_block_params = u.int_in_range(0..=core::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);
|
||||
@@ -595,8 +594,8 @@ impl Func {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Func {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Debug for Func {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "{{\n")?;
|
||||
for vreg in self.reftype_vregs() {
|
||||
write!(f, " REF: {}\n", vreg)?;
|
||||
@@ -657,16 +656,18 @@ impl std::fmt::Debug for Func {
|
||||
}
|
||||
|
||||
pub fn machine_env() -> MachineEnv {
|
||||
fn regs(r: std::ops::Range<usize>) -> Vec<PReg> {
|
||||
fn regs(r: core::ops::Range<usize>) -> Vec<PReg> {
|
||||
r.map(|i| PReg::new(i, RegClass::Int)).collect()
|
||||
}
|
||||
let preferred_regs_by_class: [Vec<PReg>; 2] = [regs(0..24), vec![]];
|
||||
let non_preferred_regs_by_class: [Vec<PReg>; 2] = [regs(24..32), vec![]];
|
||||
let scratch_by_class: [Option<PReg>; 2] = [None, None];
|
||||
let fixed_stack_slots = regs(32..63);
|
||||
// Register 63 is reserved for use as a fixed non-allocatable register.
|
||||
MachineEnv {
|
||||
preferred_regs_by_class,
|
||||
non_preferred_regs_by_class,
|
||||
scratch_by_class,
|
||||
fixed_stack_slots,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ macro_rules! define_index {
|
||||
};
|
||||
}
|
||||
|
||||
pub trait ContainerIndex: Clone + Copy + std::fmt::Debug + PartialEq + Eq {}
|
||||
pub trait ContainerIndex: Clone + Copy + core::fmt::Debug + PartialEq + Eq {}
|
||||
|
||||
pub trait ContainerComparator {
|
||||
type Ix: ContainerIndex;
|
||||
fn compare(&self, a: Self::Ix, b: Self::Ix) -> std::cmp::Ordering;
|
||||
fn compare(&self, a: Self::Ix, b: Self::Ix) -> core::cmp::Ordering;
|
||||
}
|
||||
|
||||
define_index!(Inst);
|
||||
@@ -146,6 +146,9 @@ impl Iterator for InstRangeIter {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
//! Index sets: sets of integers that represent indices into a space.
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use std::cell::Cell;
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::Cell;
|
||||
|
||||
use crate::FxHashMap;
|
||||
|
||||
const SMALL_ELEMS: usize = 12;
|
||||
|
||||
@@ -151,10 +153,10 @@ impl AdaptiveMap {
|
||||
|
||||
enum AdaptiveMapIter<'a> {
|
||||
Small(&'a [u32], &'a [u64]),
|
||||
Large(std::collections::hash_map::Iter<'a, u32, u64>),
|
||||
Large(hashbrown::hash_map::Iter<'a, u32, u64>),
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Iterator for AdaptiveMapIter<'a> {
|
||||
impl<'a> core::iter::Iterator for AdaptiveMapIter<'a> {
|
||||
type Item = (u32, u64);
|
||||
|
||||
#[inline]
|
||||
@@ -292,7 +294,7 @@ impl Iterator for SetBitsIter {
|
||||
// Build an `Option<NonZeroU64>` so that on the nonzero path,
|
||||
// the compiler can optimize the trailing-zeroes operator
|
||||
// using that knowledge.
|
||||
std::num::NonZeroU64::new(self.0).map(|nz| {
|
||||
core::num::NonZeroU64::new(self.0).map(|nz| {
|
||||
let bitidx = nz.trailing_zeros();
|
||||
self.0 &= self.0 - 1; // clear highest set bit
|
||||
bitidx as usize
|
||||
@@ -300,8 +302,8 @@ impl Iterator for SetBitsIter {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for IndexSet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Debug for IndexSet {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let vals = self.iter().collect::<Vec<_>>();
|
||||
write!(f, "{:?}", vals)
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@ use crate::cfg::CFGInfo;
|
||||
use crate::index::ContainerComparator;
|
||||
use crate::indexset::IndexSet;
|
||||
use crate::{
|
||||
define_index, Allocation, Block, Edit, Function, Inst, MachineEnv, Operand, PReg, ProgPoint,
|
||||
RegClass, VReg,
|
||||
define_index, Allocation, Block, Edit, Function, FxHashSet, Inst, MachineEnv, Operand, PReg,
|
||||
ProgPoint, RegClass, VReg,
|
||||
};
|
||||
use fxhash::FxHashSet;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt::Debug;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use smallvec::SmallVec;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A range from `from` (inclusive) to `to` (exclusive).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -64,13 +66,13 @@ impl CodeRange {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for CodeRange {
|
||||
impl core::cmp::PartialOrd for CodeRange {
|
||||
#[inline(always)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl std::cmp::Ord for CodeRange {
|
||||
impl core::cmp::Ord for CodeRange {
|
||||
#[inline(always)]
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.to <= other.from {
|
||||
@@ -278,7 +280,7 @@ const fn no_bloat_capacity<T>() -> usize {
|
||||
//
|
||||
// So if `size_of([T; N]) == size_of(pointer) + size_of(capacity)` then we
|
||||
// get the maximum inline capacity without bloat.
|
||||
std::mem::size_of::<usize>() * 2 / std::mem::size_of::<T>()
|
||||
core::mem::size_of::<usize>() * 2 / core::mem::size_of::<T>()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -405,21 +407,6 @@ pub struct Env<'a, F: Function> {
|
||||
pub extra_spillslots_by_class: [SmallVec<[Allocation; 2]>; 2],
|
||||
pub preferred_victim_by_class: [PReg; 2],
|
||||
|
||||
// Program moves: these are moves in the provided program that we
|
||||
// handle with our internal machinery, in order to avoid the
|
||||
// overhead of ordinary operand processing. We expect the client
|
||||
// to not generate any code for instructions that return
|
||||
// `Some(..)` for `.is_move()`, and instead use the edits that we
|
||||
// provide to implement those moves (or some simplified version of
|
||||
// them) post-regalloc.
|
||||
//
|
||||
// (from-vreg, inst, from-alloc), sorted by (from-vreg, inst)
|
||||
pub prog_move_srcs: Vec<((VRegIndex, Inst), Allocation)>,
|
||||
// (to-vreg, inst, to-alloc), sorted by (to-vreg, inst)
|
||||
pub prog_move_dsts: Vec<((VRegIndex, Inst), Allocation)>,
|
||||
// (from-vreg, to-vreg) for bundle-merging.
|
||||
pub prog_move_merges: Vec<(LiveRangeIndex, LiveRangeIndex)>,
|
||||
|
||||
// When multiple fixed-register constraints are present on a
|
||||
// single VReg at a single program point (this can happen for,
|
||||
// e.g., call args that use the same value multiple times), we
|
||||
@@ -446,7 +433,7 @@ pub struct Env<'a, F: Function> {
|
||||
|
||||
// For debug output only: a list of textual annotations at every
|
||||
// ProgPoint to insert into the final allocated program listing.
|
||||
pub debug_annotations: std::collections::HashMap<ProgPoint, Vec<String>>,
|
||||
pub debug_annotations: hashbrown::HashMap<ProgPoint, Vec<String>>,
|
||||
pub annotations_enabled: bool,
|
||||
|
||||
// Cached allocation for `try_to_allocate_bundle_to_reg` to avoid allocating
|
||||
@@ -507,7 +494,7 @@ impl SpillSlotList {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrioQueue {
|
||||
pub heap: std::collections::BinaryHeap<PrioQueueEntry>,
|
||||
pub heap: alloc::collections::BinaryHeap<PrioQueueEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -546,28 +533,28 @@ impl LiveRangeKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for LiveRangeKey {
|
||||
impl core::cmp::PartialEq for LiveRangeKey {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.to > other.from && self.from < other.to
|
||||
}
|
||||
}
|
||||
impl std::cmp::Eq for LiveRangeKey {}
|
||||
impl std::cmp::PartialOrd for LiveRangeKey {
|
||||
impl core::cmp::Eq for LiveRangeKey {}
|
||||
impl core::cmp::PartialOrd for LiveRangeKey {
|
||||
#[inline(always)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl std::cmp::Ord for LiveRangeKey {
|
||||
impl core::cmp::Ord for LiveRangeKey {
|
||||
#[inline(always)]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
if self.to <= other.from {
|
||||
std::cmp::Ordering::Less
|
||||
core::cmp::Ordering::Less
|
||||
} else if self.from >= other.to {
|
||||
std::cmp::Ordering::Greater
|
||||
core::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Equal
|
||||
core::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,7 +564,7 @@ pub struct PrioQueueComparator<'a> {
|
||||
}
|
||||
impl<'a> ContainerComparator for PrioQueueComparator<'a> {
|
||||
type Ix = LiveBundleIndex;
|
||||
fn compare(&self, a: Self::Ix, b: Self::Ix) -> std::cmp::Ordering {
|
||||
fn compare(&self, a: Self::Ix, b: Self::Ix) -> core::cmp::Ordering {
|
||||
self.prios[a.index()].cmp(&self.prios[b.index()])
|
||||
}
|
||||
}
|
||||
@@ -585,7 +572,7 @@ impl<'a> ContainerComparator for PrioQueueComparator<'a> {
|
||||
impl PrioQueue {
|
||||
pub fn new() -> Self {
|
||||
PrioQueue {
|
||||
heap: std::collections::BinaryHeap::new(),
|
||||
heap: alloc::collections::BinaryHeap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,9 +615,7 @@ pub struct InsertedMove {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum InsertMovePrio {
|
||||
InEdgeMoves,
|
||||
BlockParam,
|
||||
Regular,
|
||||
PostRegular,
|
||||
MultiFixedRegInitial,
|
||||
MultiFixedRegSecondary,
|
||||
ReusedInput,
|
||||
@@ -660,10 +645,6 @@ pub struct Stats {
|
||||
pub livein_iterations: usize,
|
||||
pub initial_liverange_count: usize,
|
||||
pub merged_bundle_count: usize,
|
||||
pub prog_moves: usize,
|
||||
pub prog_moves_dead_src: usize,
|
||||
pub prog_move_merge_attempt: usize,
|
||||
pub prog_move_merge_success: usize,
|
||||
pub process_bundle_count: usize,
|
||||
pub process_bundle_reg_probes_fixed: usize,
|
||||
pub process_bundle_reg_success_fixed: usize,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Debugging output.
|
||||
|
||||
use alloc::string::ToString;
|
||||
use alloc::{format, vec};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use super::Env;
|
||||
use crate::{Block, Function, ProgPoint};
|
||||
|
||||
|
||||
@@ -22,13 +22,15 @@ use crate::ion::data_structures::{
|
||||
BlockparamIn, BlockparamOut, FixedRegFixupLevel, MultiFixedRegFixup,
|
||||
};
|
||||
use crate::{
|
||||
Allocation, Block, Function, Inst, InstPosition, Operand, OperandConstraint, OperandKind,
|
||||
OperandPos, PReg, ProgPoint, RegAllocError, VReg,
|
||||
Allocation, Block, Function, FxHashMap, FxHashSet, Inst, InstPosition, Operand,
|
||||
OperandConstraint, OperandKind, OperandPos, PReg, ProgPoint, RegAllocError, VReg,
|
||||
};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use alloc::collections::VecDeque;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashSet;
|
||||
use slice_group_by::GroupByMut;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
|
||||
/// A spill weight computed for a certain Use.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -42,7 +44,7 @@ pub fn spill_weight_from_constraint(
|
||||
) -> SpillWeight {
|
||||
// A bonus of 1000 for one loop level, 4000 for two loop levels,
|
||||
// 16000 for three loop levels, etc. Avoids exponentiation.
|
||||
let loop_depth = std::cmp::min(10, loop_depth);
|
||||
let loop_depth = core::cmp::min(10, loop_depth);
|
||||
let hot_bonus: f32 = (0..loop_depth).fold(1000.0, |a, _| a * 4.0);
|
||||
let def_bonus: f32 = if is_def { 2000.0 } else { 0.0 };
|
||||
let constraint_bonus: f32 = match constraint {
|
||||
@@ -91,7 +93,7 @@ impl SpillWeight {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<SpillWeight> for SpillWeight {
|
||||
impl core::ops::Add<SpillWeight> for SpillWeight {
|
||||
type Output = SpillWeight;
|
||||
fn add(self, other: SpillWeight) -> Self {
|
||||
SpillWeight(self.0 + other.0)
|
||||
@@ -334,8 +336,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
workqueue_set.insert(block);
|
||||
}
|
||||
|
||||
while !workqueue.is_empty() {
|
||||
let block = workqueue.pop_front().unwrap();
|
||||
while let Some(block) = workqueue.pop_front() {
|
||||
workqueue_set.remove(&block);
|
||||
let insns = self.func.block_insns(block);
|
||||
|
||||
@@ -357,13 +358,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
|
||||
for inst in insns.rev().iter() {
|
||||
if let Some((src, dst)) = self.func.is_move(inst) {
|
||||
live.set(dst.vreg().vreg(), false);
|
||||
live.set(src.vreg().vreg(), true);
|
||||
self.observe_vreg_class(src.vreg());
|
||||
self.observe_vreg_class(dst.vreg());
|
||||
}
|
||||
|
||||
for pos in &[OperandPos::Late, OperandPos::Early] {
|
||||
for op in self.func.inst_operands(inst) {
|
||||
if op.as_fixed_nonallocatable().is_some() {
|
||||
@@ -520,147 +514,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a move, handle specially.
|
||||
if let Some((src, dst)) = self.func.is_move(inst) {
|
||||
// We can completely skip the move if it is
|
||||
// trivial (vreg to same vreg).
|
||||
if src.vreg() != dst.vreg() {
|
||||
trace!(" -> move inst{}: src {} -> dst {}", inst.index(), src, dst);
|
||||
|
||||
debug_assert_eq!(src.class(), dst.class());
|
||||
debug_assert_eq!(src.kind(), OperandKind::Use);
|
||||
debug_assert_eq!(src.pos(), OperandPos::Early);
|
||||
debug_assert_eq!(dst.kind(), OperandKind::Def);
|
||||
debug_assert_eq!(dst.pos(), OperandPos::Late);
|
||||
|
||||
// Redefine src and dst operands to have
|
||||
// positions of After and Before respectively
|
||||
// (see note below), and to have Any
|
||||
// constraints if they were originally Reg.
|
||||
let src_constraint = match src.constraint() {
|
||||
OperandConstraint::Reg => OperandConstraint::Any,
|
||||
x => x,
|
||||
};
|
||||
let dst_constraint = match dst.constraint() {
|
||||
OperandConstraint::Reg => OperandConstraint::Any,
|
||||
x => x,
|
||||
};
|
||||
let src = Operand::new(
|
||||
src.vreg(),
|
||||
src_constraint,
|
||||
OperandKind::Use,
|
||||
OperandPos::Late,
|
||||
);
|
||||
let dst = Operand::new(
|
||||
dst.vreg(),
|
||||
dst_constraint,
|
||||
OperandKind::Def,
|
||||
OperandPos::Early,
|
||||
);
|
||||
|
||||
if self.annotations_enabled {
|
||||
self.annotate(
|
||||
ProgPoint::after(inst),
|
||||
format!(
|
||||
" prog-move v{} ({:?}) -> v{} ({:?})",
|
||||
src.vreg().vreg(),
|
||||
src_constraint,
|
||||
dst.vreg().vreg(),
|
||||
dst_constraint,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// N.B.: in order to integrate with the move
|
||||
// resolution that joins LRs in general, we
|
||||
// conceptually treat the move as happening
|
||||
// between the move inst's After and the next
|
||||
// inst's Before. Thus the src LR goes up to
|
||||
// (exclusive) next-inst-pre, and the dst LR
|
||||
// starts at next-inst-pre. We have to take
|
||||
// care in our move insertion to handle this
|
||||
// like other inter-inst moves, i.e., at
|
||||
// `Regular` priority, so it properly happens
|
||||
// in parallel with other inter-LR moves.
|
||||
//
|
||||
// Why the progpoint between move and next
|
||||
// inst, and not the progpoint between prev
|
||||
// inst and move? Because a move can be the
|
||||
// first inst in a block, but cannot be the
|
||||
// last; so the following progpoint is always
|
||||
// within the same block, while the previous
|
||||
// one may be an inter-block point (and the
|
||||
// After of the prev inst in a different
|
||||
// block).
|
||||
|
||||
// Handle the def w.r.t. liveranges: trim the
|
||||
// start of the range and mark it dead at this
|
||||
// point in our backward scan.
|
||||
let pos = ProgPoint::before(inst.next());
|
||||
let mut dst_lr = vreg_ranges[dst.vreg().vreg()];
|
||||
if !live.get(dst.vreg().vreg()) {
|
||||
let from = pos;
|
||||
let to = pos.next();
|
||||
dst_lr = self.add_liverange_to_vreg(
|
||||
VRegIndex::new(dst.vreg().vreg()),
|
||||
CodeRange { from, to },
|
||||
);
|
||||
trace!(" -> invalid LR for def; created {:?}", dst_lr);
|
||||
}
|
||||
trace!(" -> has existing LR {:?}", dst_lr);
|
||||
// Trim the LR to start here.
|
||||
if self.ranges[dst_lr.index()].range.from
|
||||
== self.cfginfo.block_entry[block.index()]
|
||||
{
|
||||
trace!(" -> started at block start; trimming to {:?}", pos);
|
||||
self.ranges[dst_lr.index()].range.from = pos;
|
||||
}
|
||||
self.ranges[dst_lr.index()].set_flag(LiveRangeFlag::StartsAtDef);
|
||||
live.set(dst.vreg().vreg(), false);
|
||||
vreg_ranges[dst.vreg().vreg()] = LiveRangeIndex::invalid();
|
||||
|
||||
// Handle the use w.r.t. liveranges: make it live
|
||||
// and create an initial LR back to the start of
|
||||
// the block.
|
||||
let pos = ProgPoint::after(inst);
|
||||
let src_lr = if !live.get(src.vreg().vreg()) {
|
||||
let range = CodeRange {
|
||||
from: self.cfginfo.block_entry[block.index()],
|
||||
to: pos.next(),
|
||||
};
|
||||
let src_lr = self
|
||||
.add_liverange_to_vreg(VRegIndex::new(src.vreg().vreg()), range);
|
||||
vreg_ranges[src.vreg().vreg()] = src_lr;
|
||||
src_lr
|
||||
} else {
|
||||
vreg_ranges[src.vreg().vreg()]
|
||||
};
|
||||
|
||||
trace!(" -> src LR {:?}", src_lr);
|
||||
|
||||
// Add to live-set.
|
||||
let src_is_dead_after_move = !live.get(src.vreg().vreg());
|
||||
live.set(src.vreg().vreg(), true);
|
||||
|
||||
// Add to program-moves lists.
|
||||
self.prog_move_srcs.push((
|
||||
(VRegIndex::new(src.vreg().vreg()), inst),
|
||||
Allocation::none(),
|
||||
));
|
||||
self.prog_move_dsts.push((
|
||||
(VRegIndex::new(dst.vreg().vreg()), inst.next()),
|
||||
Allocation::none(),
|
||||
));
|
||||
self.stats.prog_moves += 1;
|
||||
if src_is_dead_after_move {
|
||||
self.stats.prog_moves_dead_src += 1;
|
||||
self.prog_move_merges.push((src_lr, dst_lr));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preprocess defs and uses. Specifically, if there
|
||||
// are any fixed-reg-constrained defs at Late position
|
||||
// and fixed-reg-constrained uses at Early position
|
||||
@@ -959,12 +812,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
|
||||
for range in 0..self.ranges.len() {
|
||||
self.ranges[range].uses.reverse();
|
||||
debug_assert!(self.ranges[range]
|
||||
.uses
|
||||
.windows(2)
|
||||
.all(|win| win[0].pos <= win[1].pos));
|
||||
for range in &mut self.ranges {
|
||||
range.uses.reverse();
|
||||
debug_assert!(range.uses.windows(2).all(|win| win[0].pos <= win[1].pos));
|
||||
}
|
||||
|
||||
// Insert safepoint virtual stack uses, if needed.
|
||||
@@ -1019,11 +869,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
self.blockparam_ins.sort_unstable_by_key(|x| x.key());
|
||||
self.blockparam_outs.sort_unstable_by_key(|x| x.key());
|
||||
self.prog_move_srcs.sort_unstable_by_key(|(pos, _)| *pos);
|
||||
self.prog_move_dsts.sort_unstable_by_key(|(pos, _)| *pos);
|
||||
|
||||
trace!("prog_move_srcs = {:?}", self.prog_move_srcs);
|
||||
trace!("prog_move_dsts = {:?}", self.prog_move_dsts);
|
||||
|
||||
self.stats.initial_liverange_count = self.ranges.len();
|
||||
self.stats.blockparam_ins_count = self.blockparam_ins.len();
|
||||
@@ -1032,7 +877,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
pub fn fixup_multi_fixed_vregs(&mut self) {
|
||||
// Do a fixed-reg cleanup pass: if there are any LiveRanges with
|
||||
// multiple uses (or defs) at the same ProgPoint and there is
|
||||
// multiple uses at the same ProgPoint and there is
|
||||
// more than one FixedReg constraint at that ProgPoint, we
|
||||
// need to record all but one of them in a special fixup list
|
||||
// and handle them later; otherwise, bundle-splitting to
|
||||
@@ -1154,15 +999,13 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
|
||||
for &(clobber, pos) in &extra_clobbers {
|
||||
for (clobber, pos) in extra_clobbers.drain(..) {
|
||||
let range = CodeRange {
|
||||
from: pos,
|
||||
to: pos.next(),
|
||||
};
|
||||
self.add_liverange_to_preg(range, clobber);
|
||||
}
|
||||
|
||||
extra_clobbers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use super::{
|
||||
use crate::{
|
||||
ion::data_structures::BlockparamOut, Function, Inst, OperandConstraint, OperandKind, PReg,
|
||||
};
|
||||
use alloc::format;
|
||||
use smallvec::smallvec;
|
||||
|
||||
impl<'a, F: Function> Env<'a, F> {
|
||||
@@ -132,7 +133,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
// `to` bundle is empty -- just move the list over from
|
||||
// `from` and set `bundle` up-link on all ranges.
|
||||
trace!(" -> to bundle{} is empty; trivial merge", to.index());
|
||||
let list = std::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||
let list = core::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||
for entry in &list {
|
||||
self.ranges[entry.index.index()].bundle = to;
|
||||
|
||||
@@ -170,7 +171,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
// Two non-empty lists of LiveRanges: concatenate and
|
||||
// sort. This is faster than a mergesort-like merge into a new
|
||||
// list, empirically.
|
||||
let from_list = std::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||
let from_list = core::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||
for entry in &from_list {
|
||||
self.ranges[entry.index.index()].bundle = to;
|
||||
}
|
||||
@@ -213,7 +214,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
|
||||
if self.bundles[from.index()].spillset != self.bundles[to.index()].spillset {
|
||||
let from_vregs = std::mem::replace(
|
||||
let from_vregs = core::mem::replace(
|
||||
&mut self.spillsets[self.bundles[from.index()].spillset.index()].vregs,
|
||||
smallvec![],
|
||||
);
|
||||
@@ -351,28 +352,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
self.merge_bundles(from_bundle, to_bundle);
|
||||
}
|
||||
|
||||
// Attempt to merge move srcs/dsts.
|
||||
for i in 0..self.prog_move_merges.len() {
|
||||
let (src, dst) = self.prog_move_merges[i];
|
||||
trace!("trying to merge move src LR {:?} to dst LR {:?}", src, dst);
|
||||
let src = self.resolve_merged_lr(src);
|
||||
let dst = self.resolve_merged_lr(dst);
|
||||
trace!(
|
||||
"resolved LR-construction merging chains: move-merge is now src LR {:?} to dst LR {:?}",
|
||||
src,
|
||||
dst
|
||||
);
|
||||
|
||||
let src_bundle = self.ranges[src.index()].bundle;
|
||||
debug_assert!(src_bundle.is_valid());
|
||||
let dest_bundle = self.ranges[dst.index()].bundle;
|
||||
debug_assert!(dest_bundle.is_valid());
|
||||
self.stats.prog_move_merge_attempt += 1;
|
||||
if self.merge_bundles(/* from */ dest_bundle, /* to */ src_bundle) {
|
||||
self.stats.prog_move_merge_success += 1;
|
||||
}
|
||||
}
|
||||
|
||||
trace!("done merging bundles");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
use crate::cfg::CFGInfo;
|
||||
use crate::ssa::validate_ssa;
|
||||
use crate::{Function, MachineEnv, Output, PReg, ProgPoint, RegAllocError, RegClass};
|
||||
use std::collections::HashMap;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
pub(crate) mod data_structures;
|
||||
pub use data_structures::Stats;
|
||||
@@ -71,10 +73,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
extra_spillslots_by_class: [smallvec![], smallvec![]],
|
||||
preferred_victim_by_class: [PReg::invalid(), PReg::invalid()],
|
||||
|
||||
prog_move_srcs: Vec::with_capacity(n / 2),
|
||||
prog_move_dsts: Vec::with_capacity(n / 2),
|
||||
prog_move_merges: Vec::with_capacity(n / 2),
|
||||
|
||||
multi_fixed_reg_fixups: vec![],
|
||||
inserted_moves: vec![],
|
||||
edits: Vec::with_capacity(n),
|
||||
@@ -86,7 +84,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
stats: Stats::default(),
|
||||
|
||||
debug_annotations: std::collections::HashMap::new(),
|
||||
debug_annotations: hashbrown::HashMap::new(),
|
||||
annotations_enabled,
|
||||
|
||||
conflict_set: Default::default(),
|
||||
|
||||
145
src/ion/moves.rs
145
src/ion/moves.rs
@@ -22,12 +22,13 @@ use crate::ion::data_structures::{
|
||||
use crate::ion::reg_traversal::RegTraversalIter;
|
||||
use crate::moves::{MoveAndScratchResolver, ParallelMoves};
|
||||
use crate::{
|
||||
Allocation, Block, Edit, Function, Inst, InstPosition, OperandConstraint, OperandKind,
|
||||
OperandPos, PReg, ProgPoint, RegClass, SpillSlot, VReg,
|
||||
Allocation, Block, Edit, Function, FxHashMap, Inst, InstPosition, OperandConstraint,
|
||||
OperandKind, OperandPos, PReg, ProgPoint, RegClass, SpillSlot, VReg,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
use core::fmt::Debug;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl<'a, F: Function> Env<'a, F> {
|
||||
pub fn is_start_of_block(&self, pos: ProgPoint) -> bool {
|
||||
@@ -179,8 +180,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
let mut blockparam_in_idx = 0;
|
||||
let mut blockparam_out_idx = 0;
|
||||
let mut prog_move_src_idx = 0;
|
||||
let mut prog_move_dst_idx = 0;
|
||||
for vreg in 0..self.vregs.len() {
|
||||
let vreg = VRegIndex::new(vreg);
|
||||
if !self.is_vreg_used(vreg) {
|
||||
@@ -190,7 +189,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
// For each range in each vreg, insert moves or
|
||||
// half-moves. We also scan over `blockparam_ins` and
|
||||
// `blockparam_outs`, which are sorted by (block, vreg),
|
||||
// and over program-move srcs/dsts to fill in allocations.
|
||||
// to fill in allocations.
|
||||
let mut prev = LiveRangeIndex::invalid();
|
||||
for range_idx in 0..self.vregs[vreg.index()].ranges.len() {
|
||||
let entry = self.vregs[vreg.index()].ranges[range_idx];
|
||||
@@ -496,9 +495,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
// this case returns the index of the first
|
||||
// entry that is greater as an `Err`.
|
||||
if label_vreg.vreg() < vreg.index() {
|
||||
std::cmp::Ordering::Less
|
||||
core::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
core::cmp::Ordering::Greater
|
||||
}
|
||||
})
|
||||
.unwrap_err();
|
||||
@@ -517,96 +516,13 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let from = std::cmp::max(label_from, range.from);
|
||||
let to = std::cmp::min(label_to, range.to);
|
||||
let from = core::cmp::max(label_from, range.from);
|
||||
let to = core::cmp::min(label_to, range.to);
|
||||
|
||||
self.debug_locations.push((label, from, to, alloc));
|
||||
}
|
||||
}
|
||||
|
||||
// Scan over program move srcs/dsts to fill in allocations.
|
||||
|
||||
// Move srcs happen at `After` of a given
|
||||
// inst. Compute [from, to) semi-inclusive range of
|
||||
// inst indices for which we should fill in the source
|
||||
// with this LR's allocation.
|
||||
//
|
||||
// range from inst-Before or inst-After covers cur
|
||||
// inst's After; so includes move srcs from inst.
|
||||
let move_src_start = (vreg, range.from.inst());
|
||||
// range to (exclusive) inst-Before or inst-After
|
||||
// covers only prev inst's After; so includes move
|
||||
// srcs to (exclusive) inst.
|
||||
let move_src_end = (vreg, range.to.inst());
|
||||
trace!(
|
||||
"vreg {:?} range {:?}: looking for program-move sources from {:?} to {:?}",
|
||||
vreg,
|
||||
range,
|
||||
move_src_start,
|
||||
move_src_end
|
||||
);
|
||||
while prog_move_src_idx < self.prog_move_srcs.len()
|
||||
&& self.prog_move_srcs[prog_move_src_idx].0 < move_src_start
|
||||
{
|
||||
trace!(" -> skipping idx {}", prog_move_src_idx);
|
||||
prog_move_src_idx += 1;
|
||||
}
|
||||
while prog_move_src_idx < self.prog_move_srcs.len()
|
||||
&& self.prog_move_srcs[prog_move_src_idx].0 < move_src_end
|
||||
{
|
||||
trace!(
|
||||
" -> setting idx {} ({:?}) to alloc {:?}",
|
||||
prog_move_src_idx,
|
||||
self.prog_move_srcs[prog_move_src_idx].0,
|
||||
alloc
|
||||
);
|
||||
self.prog_move_srcs[prog_move_src_idx].1 = alloc;
|
||||
prog_move_src_idx += 1;
|
||||
}
|
||||
|
||||
// move dsts happen at Before point.
|
||||
//
|
||||
// Range from inst-Before includes cur inst, while inst-After includes only next inst.
|
||||
let move_dst_start = if range.from.pos() == InstPosition::Before {
|
||||
(vreg, range.from.inst())
|
||||
} else {
|
||||
(vreg, range.from.inst().next())
|
||||
};
|
||||
// Range to (exclusive) inst-Before includes prev
|
||||
// inst, so to (exclusive) cur inst; range to
|
||||
// (exclusive) inst-After includes cur inst, so to
|
||||
// (exclusive) next inst.
|
||||
let move_dst_end = if range.to.pos() == InstPosition::Before {
|
||||
(vreg, range.to.inst())
|
||||
} else {
|
||||
(vreg, range.to.inst().next())
|
||||
};
|
||||
trace!(
|
||||
"vreg {:?} range {:?}: looking for program-move dests from {:?} to {:?}",
|
||||
vreg,
|
||||
range,
|
||||
move_dst_start,
|
||||
move_dst_end
|
||||
);
|
||||
while prog_move_dst_idx < self.prog_move_dsts.len()
|
||||
&& self.prog_move_dsts[prog_move_dst_idx].0 < move_dst_start
|
||||
{
|
||||
trace!(" -> skipping idx {}", prog_move_dst_idx);
|
||||
prog_move_dst_idx += 1;
|
||||
}
|
||||
while prog_move_dst_idx < self.prog_move_dsts.len()
|
||||
&& self.prog_move_dsts[prog_move_dst_idx].0 < move_dst_end
|
||||
{
|
||||
trace!(
|
||||
" -> setting idx {} ({:?}) to alloc {:?}",
|
||||
prog_move_dst_idx,
|
||||
self.prog_move_dsts[prog_move_dst_idx].0,
|
||||
alloc
|
||||
);
|
||||
self.prog_move_dsts[prog_move_dst_idx].1 = alloc;
|
||||
prog_move_dst_idx += 1;
|
||||
}
|
||||
|
||||
prev = entry.index;
|
||||
}
|
||||
}
|
||||
@@ -714,7 +630,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
|
||||
// Handle multi-fixed-reg constraints by copying.
|
||||
for fixup in std::mem::replace(&mut self.multi_fixed_reg_fixups, vec![]) {
|
||||
for fixup in core::mem::replace(&mut self.multi_fixed_reg_fixups, vec![]) {
|
||||
let from_alloc = self.get_alloc(fixup.pos.inst(), fixup.from_slot as usize);
|
||||
let to_alloc = Allocation::reg(PReg::from_index(fixup.to_preg.index()));
|
||||
trace!(
|
||||
@@ -820,42 +736,6 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the prog-moves lists and insert moves to reify the
|
||||
// input program's move operations.
|
||||
self.prog_move_srcs
|
||||
.sort_unstable_by_key(|((_, inst), _)| *inst);
|
||||
self.prog_move_dsts
|
||||
.sort_unstable_by_key(|((_, inst), _)| inst.prev());
|
||||
let prog_move_srcs = std::mem::replace(&mut self.prog_move_srcs, vec![]);
|
||||
let prog_move_dsts = std::mem::replace(&mut self.prog_move_dsts, vec![]);
|
||||
debug_assert_eq!(prog_move_srcs.len(), prog_move_dsts.len());
|
||||
for (&((_, from_inst), from_alloc), &((to_vreg, to_inst), to_alloc)) in
|
||||
prog_move_srcs.iter().zip(prog_move_dsts.iter())
|
||||
{
|
||||
trace!(
|
||||
"program move at inst {:?}: alloc {:?} -> {:?} (v{})",
|
||||
from_inst,
|
||||
from_alloc,
|
||||
to_alloc,
|
||||
to_vreg.index(),
|
||||
);
|
||||
debug_assert!(from_alloc.is_some());
|
||||
debug_assert!(to_alloc.is_some());
|
||||
debug_assert_eq!(from_inst, to_inst.prev());
|
||||
// N.B.: these moves happen with the *same* priority as
|
||||
// LR-to-LR moves, because they work just like them: they
|
||||
// connect a use at one progpoint (move-After) with a def
|
||||
// at an adjacent progpoint (move+1-Before), so they must
|
||||
// happen in parallel with all other LR-to-LR moves.
|
||||
self.insert_move(
|
||||
ProgPoint::before(to_inst),
|
||||
InsertMovePrio::Regular,
|
||||
from_alloc,
|
||||
to_alloc,
|
||||
self.vreg(to_vreg),
|
||||
);
|
||||
}
|
||||
|
||||
// Sort the debug-locations vector; we provide this
|
||||
// invariant to the client.
|
||||
self.debug_locations.sort_unstable();
|
||||
@@ -986,6 +866,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
to: pos_prio.pos.next(),
|
||||
});
|
||||
let get_reg = || {
|
||||
if let Some(reg) = self.env.scratch_by_class[regclass as usize] {
|
||||
return Some(Allocation::reg(reg));
|
||||
}
|
||||
while let Some(preg) = scratch_iter.next() {
|
||||
if !self.pregs[preg.index()]
|
||||
.allocations
|
||||
|
||||
@@ -22,12 +22,11 @@ use crate::{
|
||||
CodeRange, BUNDLE_MAX_NORMAL_SPILL_WEIGHT, MAX_SPLITS_PER_SPILLSET,
|
||||
MINIMAL_BUNDLE_SPILL_WEIGHT, MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT,
|
||||
},
|
||||
Allocation, Function, Inst, InstPosition, OperandConstraint, OperandKind, PReg, ProgPoint,
|
||||
RegAllocError,
|
||||
Allocation, Function, FxHashSet, Inst, InstPosition, OperandConstraint, OperandKind, PReg,
|
||||
ProgPoint, RegAllocError,
|
||||
};
|
||||
use fxhash::FxHashSet;
|
||||
use core::fmt::Debug;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AllocRegResult {
|
||||
@@ -159,7 +158,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
trace!(" -> conflict bundle {:?}", conflict_bundle);
|
||||
if self.conflict_set.insert(conflict_bundle) {
|
||||
conflicts.push(conflict_bundle);
|
||||
max_conflict_weight = std::cmp::max(
|
||||
max_conflict_weight = core::cmp::max(
|
||||
max_conflict_weight,
|
||||
self.bundles[conflict_bundle.index()].cached_spill_weight(),
|
||||
);
|
||||
@@ -172,7 +171,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
|
||||
if first_conflict.is_none() {
|
||||
first_conflict = Some(ProgPoint::from_index(std::cmp::max(
|
||||
first_conflict = Some(ProgPoint::from_index(core::cmp::max(
|
||||
preg_key.from,
|
||||
key.from,
|
||||
)));
|
||||
@@ -334,7 +333,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
self.bundles[bundle.index()].prio,
|
||||
final_weight
|
||||
);
|
||||
std::cmp::min(BUNDLE_MAX_NORMAL_SPILL_WEIGHT, final_weight)
|
||||
core::cmp::min(BUNDLE_MAX_NORMAL_SPILL_WEIGHT, final_weight)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -824,7 +823,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
// (up to the Before of the next inst), *unless*
|
||||
// the original LR was only over the Before (up to
|
||||
// the After) of this inst.
|
||||
let to = std::cmp::min(ProgPoint::before(u.pos.inst().next()), lr_to);
|
||||
let to = core::cmp::min(ProgPoint::before(u.pos.inst().next()), lr_to);
|
||||
|
||||
// If the last bundle was at the same inst, add a new
|
||||
// LR to the same bundle; otherwise, create a LR and a
|
||||
@@ -863,7 +862,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
// Otherwise, create a new LR.
|
||||
let pos = ProgPoint::before(u.pos.inst());
|
||||
let pos = std::cmp::max(lr_from, pos);
|
||||
let pos = core::cmp::max(lr_from, pos);
|
||||
let cr = CodeRange { from: pos, to };
|
||||
let lr = self.create_liverange(cr);
|
||||
new_lrs.push((vreg, lr));
|
||||
@@ -1036,7 +1035,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
self.get_or_create_spill_bundle(bundle, /* create_if_absent = */ false)
|
||||
{
|
||||
let mut list =
|
||||
std::mem::replace(&mut self.bundles[bundle.index()].ranges, smallvec![]);
|
||||
core::mem::replace(&mut self.bundles[bundle.index()].ranges, smallvec![]);
|
||||
for entry in &list {
|
||||
self.ranges[entry.index.index()].bundle = spill;
|
||||
}
|
||||
@@ -1107,7 +1106,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
lowest_cost_evict_conflict_cost,
|
||||
lowest_cost_split_conflict_cost,
|
||||
) {
|
||||
(Some(a), Some(b)) => Some(std::cmp::max(a, b)),
|
||||
(Some(a), Some(b)) => Some(core::cmp::max(a, b)),
|
||||
_ => None,
|
||||
};
|
||||
match self.try_to_allocate_bundle_to_reg(bundle, preg_idx, scan_limit_cost) {
|
||||
@@ -1291,7 +1290,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
);
|
||||
let bundle_start = self.bundles[bundle.index()].ranges[0].range.from;
|
||||
let mut split_at_point =
|
||||
std::cmp::max(lowest_cost_split_conflict_point, bundle_start);
|
||||
core::cmp::max(lowest_cost_split_conflict_point, bundle_start);
|
||||
let requeue_with_reg = lowest_cost_split_conflict_reg;
|
||||
|
||||
// Adjust `split_at_point` if it is within a deeper loop
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Redundant-move elimination.
|
||||
|
||||
use crate::{Allocation, VReg};
|
||||
use fxhash::FxHashMap;
|
||||
use crate::{Allocation, FxHashMap, VReg};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -112,9 +111,9 @@ impl RedundantMoveEliminator {
|
||||
pub fn clear_alloc(&mut self, alloc: Allocation) {
|
||||
trace!(" redundant move eliminator: clear {:?}", alloc);
|
||||
if let Some(ref mut existing_copies) = self.reverse_allocs.get_mut(&alloc) {
|
||||
for to_inval in existing_copies.iter() {
|
||||
for to_inval in existing_copies.drain(..) {
|
||||
trace!(" -> clear existing copy: {:?}", to_inval);
|
||||
if let Some(val) = self.allocs.get_mut(to_inval) {
|
||||
if let Some(val) = self.allocs.get_mut(&to_inval) {
|
||||
match val {
|
||||
RedundantMoveState::Copy(_, Some(vreg)) => {
|
||||
*val = RedundantMoveState::Orig(*vreg);
|
||||
@@ -122,9 +121,8 @@ impl RedundantMoveEliminator {
|
||||
_ => *val = RedundantMoveState::None,
|
||||
}
|
||||
}
|
||||
self.allocs.remove(to_inval);
|
||||
self.allocs.remove(&to_inval);
|
||||
}
|
||||
existing_copies.clear();
|
||||
}
|
||||
self.allocs.remove(&alloc);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ impl<'a> RegTraversalIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Iterator for RegTraversalIter<'a> {
|
||||
impl<'a> core::iter::Iterator for RegTraversalIter<'a> {
|
||||
type Item = PReg;
|
||||
|
||||
fn next(&mut self) -> Option<PReg> {
|
||||
|
||||
@@ -138,7 +138,7 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
let mut success = false;
|
||||
// Never probe the same element more than once: limit the
|
||||
// attempt count to the number of slots in existence.
|
||||
for _attempt in 0..std::cmp::min(self.slots_by_size[size].slots.len(), MAX_ATTEMPTS) {
|
||||
for _attempt in 0..core::cmp::min(self.slots_by_size[size].slots.len(), MAX_ATTEMPTS) {
|
||||
// Note: this indexing of `slots` is always valid
|
||||
// because either the `slots` list is empty and the
|
||||
// iteration limit above consequently means we don't
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
//! Stackmap computation.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{Env, ProgPoint, VRegIndex};
|
||||
use crate::{ion::data_structures::u64_key, Function};
|
||||
|
||||
|
||||
103
src/lib.rs
103
src/lib.rs
@@ -11,6 +11,12 @@
|
||||
*/
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// Even when trace logging is disabled, the trace macro has a significant
|
||||
// performance cost so we disable it in release builds.
|
||||
@@ -28,6 +34,11 @@ macro_rules! trace_enabled {
|
||||
};
|
||||
}
|
||||
|
||||
use core::hash::BuildHasherDefault;
|
||||
use rustc_hash::FxHasher;
|
||||
type FxHashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
type FxHashSet<V> = hashbrown::HashSet<V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
pub(crate) mod cfg;
|
||||
pub(crate) mod domtree;
|
||||
pub mod indexset;
|
||||
@@ -38,6 +49,8 @@ pub mod ssa;
|
||||
|
||||
#[macro_use]
|
||||
mod index;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
pub use index::{Block, Inst, InstRange, InstRangeIter};
|
||||
|
||||
pub mod checker;
|
||||
@@ -142,8 +155,8 @@ impl PReg {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PReg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Debug for PReg {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"PReg(hw = {}, class = {:?}, index = {})",
|
||||
@@ -154,8 +167,8 @@ impl std::fmt::Debug for PReg {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PReg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for PReg {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let class = match self.class() {
|
||||
RegClass::Int => "i",
|
||||
RegClass::Float => "f",
|
||||
@@ -266,8 +279,7 @@ impl From<&MachineEnv> for PRegSet {
|
||||
/// A virtual register. Contains a virtual register number and a
|
||||
/// class.
|
||||
///
|
||||
/// A virtual register ("vreg") corresponds to an SSA value for SSA
|
||||
/// input, or just a register when we allow for non-SSA input. All
|
||||
/// A virtual register ("vreg") corresponds to an SSA value. All
|
||||
/// dataflow in the input program is specified via flow through a
|
||||
/// virtual register; even uses of specially-constrained locations,
|
||||
/// such as fixed physical registers, are done by using vregs, because
|
||||
@@ -312,8 +324,8 @@ impl VReg {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VReg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Debug for VReg {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"VReg(vreg = {}, class = {:?})",
|
||||
@@ -323,8 +335,8 @@ impl std::fmt::Debug for VReg {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VReg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for VReg {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "v{}", self.vreg())
|
||||
}
|
||||
}
|
||||
@@ -383,8 +395,8 @@ impl SpillSlot {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SpillSlot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for SpillSlot {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "stack{}", self.index())
|
||||
}
|
||||
}
|
||||
@@ -414,8 +426,8 @@ pub enum OperandConstraint {
|
||||
Reuse(usize),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OperandConstraint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for OperandConstraint {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
Self::Reg => write!(f, "reg"),
|
||||
@@ -797,14 +809,14 @@ impl Operand {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Operand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
impl core::fmt::Debug for Operand {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Operand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for Operand {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match (self.kind(), self.pos()) {
|
||||
(OperandKind::Def, OperandPos::Late) | (OperandKind::Use, OperandPos::Early) => {
|
||||
write!(f, "{:?}", self.kind())?;
|
||||
@@ -837,14 +849,14 @@ 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 core::fmt::Debug for Allocation {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Allocation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for Allocation {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self.kind() {
|
||||
AllocationKind::None => write!(f, "none"),
|
||||
AllocationKind::Reg => write!(f, "{}", self.as_reg().unwrap()),
|
||||
@@ -1029,10 +1041,6 @@ pub trait Function {
|
||||
false
|
||||
}
|
||||
|
||||
/// Determine whether an instruction is a move; if so, return the
|
||||
/// Operands for (src, dst).
|
||||
fn is_move(&self, insn: Inst) -> Option<(Operand, Operand)>;
|
||||
|
||||
// --------------------------
|
||||
// Instruction register slots
|
||||
// --------------------------
|
||||
@@ -1178,8 +1186,8 @@ pub struct ProgPoint {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ProgPoint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Debug for ProgPoint {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"progpoint{}{}",
|
||||
@@ -1326,19 +1334,43 @@ impl<'a> Iterator for OutputIter<'a> {
|
||||
pub struct MachineEnv {
|
||||
/// Preferred physical registers for each class. These are the
|
||||
/// registers that will be allocated first, if free.
|
||||
///
|
||||
/// If an explicit scratch register is provided in `scratch_by_class` then
|
||||
/// it must not appear in this list.
|
||||
pub preferred_regs_by_class: [Vec<PReg>; 2],
|
||||
|
||||
/// Non-preferred physical registers for each class. These are the
|
||||
/// registers that will be allocated if a preferred register is
|
||||
/// not available; using one of these is considered suboptimal,
|
||||
/// but still better than spilling.
|
||||
///
|
||||
/// If an explicit scratch register is provided in `scratch_by_class` then
|
||||
/// it must not appear in this list.
|
||||
pub non_preferred_regs_by_class: [Vec<PReg>; 2],
|
||||
|
||||
/// Optional dedicated scratch register per class. This is needed to perform
|
||||
/// moves between registers when cyclic move patterns occur. The
|
||||
/// register should not be placed in either the preferred or
|
||||
/// non-preferred list (i.e., it is not otherwise allocatable).
|
||||
///
|
||||
/// Note that the register allocator will freely use this register
|
||||
/// between instructions, but *within* the machine code generated
|
||||
/// by a single (regalloc-level) instruction, the client is free
|
||||
/// to use the scratch register. E.g., if one "instruction" causes
|
||||
/// the emission of two machine-code instructions, this lowering
|
||||
/// can use the scratch register between them.
|
||||
///
|
||||
/// If a scratch register is not provided then the register allocator will
|
||||
/// automatically allocate one as needed, spilling a value to the stack if
|
||||
/// necessary.
|
||||
pub scratch_by_class: [Option<PReg>; 2],
|
||||
|
||||
/// Some `PReg`s can be designated as locations on the stack rather than
|
||||
/// actual registers. These can be used to tell the register allocator about
|
||||
/// pre-defined stack slots used for function arguments and return values.
|
||||
///
|
||||
/// `PReg`s in this list cannot be used as an allocatable register.
|
||||
/// `PReg`s in this list cannot be used as an allocatable or scratch
|
||||
/// register.
|
||||
pub fixed_stack_slots: Vec<PReg>,
|
||||
}
|
||||
|
||||
@@ -1403,9 +1435,9 @@ impl Output {
|
||||
// binary_search_by returns the index of where it would have
|
||||
// been inserted in Err.
|
||||
if pos < ProgPoint::before(inst_range.first()) {
|
||||
std::cmp::Ordering::Less
|
||||
core::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
core::cmp::Ordering::Greater
|
||||
}
|
||||
})
|
||||
.unwrap_err();
|
||||
@@ -1444,12 +1476,13 @@ pub enum RegAllocError {
|
||||
TooManyLiveRegs,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RegAllocError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl core::fmt::Display for RegAllocError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for RegAllocError {}
|
||||
|
||||
/// Run the allocator.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
use crate::{ion::data_structures::u64_key, Allocation, PReg};
|
||||
use core::fmt::Debug;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A list of moves to be performed in sequence, with auxiliary data
|
||||
/// attached to each.
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
//! Fast postorder computation.
|
||||
|
||||
use crate::Block;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
pub fn calculate<'a, SuccFn: Fn(Block) -> &'a [Block]>(
|
||||
@@ -16,8 +18,7 @@ pub fn calculate<'a, SuccFn: Fn(Block) -> &'a [Block]>(
|
||||
let mut ret = vec![];
|
||||
|
||||
// State: visited-block map, and explicit DFS stack.
|
||||
let mut visited = vec![];
|
||||
visited.resize(num_blocks, false);
|
||||
let mut visited = vec![false; num_blocks];
|
||||
|
||||
struct State<'a> {
|
||||
block: Block,
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
//! SSA-related utilities.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use alloc::vec;
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use crate::cfg::CFGInfo;
|
||||
use crate::{Block, Function, Inst, OperandKind, RegAllocError, VReg};
|
||||
|
||||
Reference in New Issue
Block a user