Handle fixed stack slots in the move resolver (#78)
Fixed stack slots are treated as `PReg`s by most of the register allocator, but need some additional handling the move resolver to avoid generating stack-to-stack moves.
This commit is contained in:
@@ -10,6 +10,15 @@ use regalloc2::fuzzing::moves::{MoveAndScratchResolver, ParallelMoves};
|
|||||||
use regalloc2::{Allocation, PReg, RegClass, SpillSlot};
|
use regalloc2::{Allocation, PReg, RegClass, SpillSlot};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
fn is_stack_alloc(alloc: Allocation) -> bool {
|
||||||
|
// Treat registers 20..=29 as fixed stack slots.
|
||||||
|
if let Some(reg) = alloc.as_reg() {
|
||||||
|
reg.index() > 20
|
||||||
|
} else {
|
||||||
|
alloc.is_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
moves: Vec<(Allocation, Allocation)>,
|
moves: Vec<(Allocation, Allocation)>,
|
||||||
@@ -82,7 +91,8 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
Allocation::stack(SpillSlot::new(slot, RegClass::Int))
|
Allocation::stack(SpillSlot::new(slot, RegClass::Int))
|
||||||
};
|
};
|
||||||
let preferred_victim = PReg::new(0, RegClass::Int);
|
let preferred_victim = PReg::new(0, RegClass::Int);
|
||||||
let scratch_resolver = MoveAndScratchResolver::new(get_reg, get_stackslot, preferred_victim);
|
let scratch_resolver =
|
||||||
|
MoveAndScratchResolver::new(get_reg, get_stackslot, is_stack_alloc, preferred_victim);
|
||||||
let moves = scratch_resolver.compute(moves);
|
let moves = scratch_resolver.compute(moves);
|
||||||
log::trace!("resolved moves: {:?}", moves);
|
log::trace!("resolved moves: {:?}", moves);
|
||||||
|
|
||||||
@@ -97,7 +107,7 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
// Simulate the sequence of moves.
|
// Simulate the sequence of moves.
|
||||||
let mut locations: HashMap<Allocation, Allocation> = HashMap::new();
|
let mut locations: HashMap<Allocation, Allocation> = HashMap::new();
|
||||||
for (src, dst, _) in moves {
|
for (src, dst, _) in moves {
|
||||||
if src.is_stack() && dst.is_stack() {
|
if is_stack_alloc(src) && is_stack_alloc(dst) {
|
||||||
panic!("Stack-to-stack move!");
|
panic!("Stack-to-stack move!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Allocation, AllocationKind, Block, Edit, Function, Inst, InstOrEdit, InstPosition, MachineEnv,
|
Allocation, AllocationKind, Block, Edit, Function, Inst, InstOrEdit, InstPosition, MachineEnv,
|
||||||
Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg, VReg,
|
Operand, OperandConstraint, OperandKind, OperandPos, Output, PReg, PRegSet, VReg,
|
||||||
};
|
};
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -169,6 +169,10 @@ pub enum CheckerError {
|
|||||||
alloc: Allocation,
|
alloc: Allocation,
|
||||||
vregs: FxHashSet<VReg>,
|
vregs: FxHashSet<VReg>,
|
||||||
},
|
},
|
||||||
|
StackToStackMove {
|
||||||
|
into: Allocation,
|
||||||
|
from: Allocation,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstract state for an allocation.
|
/// Abstract state for an allocation.
|
||||||
@@ -560,7 +564,20 @@ impl CheckerState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&CheckerInst::ParallelMove { .. } | &CheckerInst::Move { .. } => {
|
&CheckerInst::Move { into, from } => {
|
||||||
|
// Ensure that the allocator never returns stack-to-stack moves.
|
||||||
|
let is_stack = |alloc: Allocation| {
|
||||||
|
if let Some(reg) = alloc.as_reg() {
|
||||||
|
checker.stack_pregs.contains(reg)
|
||||||
|
} else {
|
||||||
|
alloc.is_stack()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if is_stack(into) && is_stack(from) {
|
||||||
|
return Err(CheckerError::StackToStackMove { into, from });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&CheckerInst::ParallelMove { .. } => {
|
||||||
// This doesn't need verification; we just update
|
// This doesn't need verification; we just update
|
||||||
// according to the move semantics in the step
|
// according to the move semantics in the step
|
||||||
// function below.
|
// function below.
|
||||||
@@ -813,6 +830,7 @@ pub struct Checker<'a, F: Function> {
|
|||||||
edge_insts: FxHashMap<(Block, Block), Vec<CheckerInst>>,
|
edge_insts: FxHashMap<(Block, Block), Vec<CheckerInst>>,
|
||||||
reftyped_vregs: FxHashSet<VReg>,
|
reftyped_vregs: FxHashSet<VReg>,
|
||||||
machine_env: &'a MachineEnv,
|
machine_env: &'a MachineEnv,
|
||||||
|
stack_pregs: PRegSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F: Function> Checker<'a, F> {
|
impl<'a, F: Function> Checker<'a, F> {
|
||||||
@@ -841,6 +859,11 @@ impl<'a, F: Function> Checker<'a, F> {
|
|||||||
|
|
||||||
bb_in.insert(f.entry_block(), CheckerState::initial_with_pinned_vregs(f));
|
bb_in.insert(f.entry_block(), CheckerState::initial_with_pinned_vregs(f));
|
||||||
|
|
||||||
|
let mut stack_pregs = PRegSet::empty();
|
||||||
|
for &preg in &machine_env.fixed_stack_slots {
|
||||||
|
stack_pregs.add(preg);
|
||||||
|
}
|
||||||
|
|
||||||
Checker {
|
Checker {
|
||||||
f,
|
f,
|
||||||
bb_in,
|
bb_in,
|
||||||
@@ -848,6 +871,7 @@ impl<'a, F: Function> Checker<'a, F> {
|
|||||||
edge_insts,
|
edge_insts,
|
||||||
reftyped_vregs,
|
reftyped_vregs,
|
||||||
machine_env,
|
machine_env,
|
||||||
|
stack_pregs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,16 +39,6 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
pos == self.cfginfo.block_exit[block.index()]
|
pos == self.cfginfo.block_exit[block.index()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocation_is_stack(&self, alloc: Allocation) -> bool {
|
|
||||||
if alloc.is_stack() {
|
|
||||||
true
|
|
||||||
} else if let Some(preg) = alloc.as_reg() {
|
|
||||||
self.pregs[preg.index()].is_stack
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_move(
|
pub fn insert_move(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: ProgPoint,
|
pos: ProgPoint,
|
||||||
@@ -1054,10 +1044,21 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
// below.
|
// below.
|
||||||
Allocation::stack(SpillSlot::new(SpillSlot::MAX - idx, regclass))
|
Allocation::stack(SpillSlot::new(SpillSlot::MAX - idx, regclass))
|
||||||
};
|
};
|
||||||
|
let is_stack_alloc = |alloc: Allocation| {
|
||||||
|
if let Some(preg) = alloc.as_reg() {
|
||||||
|
self.pregs[preg.index()].is_stack
|
||||||
|
} else {
|
||||||
|
alloc.is_stack()
|
||||||
|
}
|
||||||
|
};
|
||||||
let preferred_victim = self.preferred_victim_by_class[regclass as usize];
|
let preferred_victim = self.preferred_victim_by_class[regclass as usize];
|
||||||
|
|
||||||
let scratch_resolver =
|
let scratch_resolver = MoveAndScratchResolver::new(
|
||||||
MoveAndScratchResolver::new(get_reg, get_stackslot, preferred_victim);
|
get_reg,
|
||||||
|
get_stackslot,
|
||||||
|
is_stack_alloc,
|
||||||
|
preferred_victim,
|
||||||
|
);
|
||||||
|
|
||||||
let resolved = scratch_resolver.compute(resolved);
|
let resolved = scratch_resolver.compute(resolved);
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,13 @@ impl PRegSet {
|
|||||||
Self { bits: 0 }
|
Self { bits: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the given register is part of the set.
|
||||||
|
pub fn contains(&self, reg: PReg) -> bool {
|
||||||
|
let bit = reg.index();
|
||||||
|
debug_assert!(bit < 128);
|
||||||
|
self.bits & 1u128 << bit != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a physical register (PReg) to the set, returning the new value.
|
/// Add a physical register (PReg) to the set, returning the new value.
|
||||||
pub const fn with(self, reg: PReg) -> Self {
|
pub const fn with(self, reg: PReg) -> Self {
|
||||||
let bit = reg.index();
|
let bit = reg.index();
|
||||||
|
|||||||
24
src/moves.rs
24
src/moves.rs
@@ -285,11 +285,11 @@ impl<T> MoveVecWithScratch<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Do any moves go from stack to stack?
|
/// Do any moves go from stack to stack?
|
||||||
pub fn stack_to_stack(&self) -> bool {
|
pub fn stack_to_stack(&self, is_stack_alloc: impl Fn(Allocation) -> bool) -> bool {
|
||||||
match self {
|
match self {
|
||||||
MoveVecWithScratch::NoScratch(moves) | MoveVecWithScratch::Scratch(moves) => moves
|
MoveVecWithScratch::NoScratch(moves) | MoveVecWithScratch::Scratch(moves) => moves
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(src, dst, _)| src.is_stack() && dst.is_stack()),
|
.any(|&(src, dst, _)| is_stack_alloc(src) && is_stack_alloc(dst)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,10 +320,11 @@ impl<T> MoveVecWithScratch<T> {
|
|||||||
/// Sometimes move elision will be able to clean this up a bit. But,
|
/// Sometimes move elision will be able to clean this up a bit. But,
|
||||||
/// for simplicity reasons, let's keep the concerns separated! So we
|
/// for simplicity reasons, let's keep the concerns separated! So we
|
||||||
/// always do the full expansion above.
|
/// always do the full expansion above.
|
||||||
pub struct MoveAndScratchResolver<GetReg, GetStackSlot>
|
pub struct MoveAndScratchResolver<GetReg, GetStackSlot, IsStackAlloc>
|
||||||
where
|
where
|
||||||
GetReg: FnMut() -> Option<Allocation>,
|
GetReg: FnMut() -> Option<Allocation>,
|
||||||
GetStackSlot: FnMut() -> Allocation,
|
GetStackSlot: FnMut() -> Allocation,
|
||||||
|
IsStackAlloc: Fn(Allocation) -> bool,
|
||||||
{
|
{
|
||||||
/// Scratch register for stack-to-stack move expansion.
|
/// Scratch register for stack-to-stack move expansion.
|
||||||
stack_stack_scratch_reg: Option<Allocation>,
|
stack_stack_scratch_reg: Option<Allocation>,
|
||||||
@@ -335,6 +336,8 @@ where
|
|||||||
find_free_reg: GetReg,
|
find_free_reg: GetReg,
|
||||||
/// Closure that gets us a stackslot, if needed.
|
/// Closure that gets us a stackslot, if needed.
|
||||||
get_stackslot: GetStackSlot,
|
get_stackslot: GetStackSlot,
|
||||||
|
/// Closure to determine whether an `Allocation` refers to a stack slot.
|
||||||
|
is_stack_alloc: IsStackAlloc,
|
||||||
/// The victim PReg to evict to another stackslot at every
|
/// The victim PReg to evict to another stackslot at every
|
||||||
/// stack-to-stack move if a free PReg is not otherwise
|
/// stack-to-stack move if a free PReg is not otherwise
|
||||||
/// available. Provided by caller and statically chosen. This is a
|
/// available. Provided by caller and statically chosen. This is a
|
||||||
@@ -342,17 +345,24 @@ where
|
|||||||
victim: PReg,
|
victim: PReg,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<GetReg, GetStackSlot> MoveAndScratchResolver<GetReg, GetStackSlot>
|
impl<GetReg, GetStackSlot, IsStackAlloc> MoveAndScratchResolver<GetReg, GetStackSlot, IsStackAlloc>
|
||||||
where
|
where
|
||||||
GetReg: FnMut() -> Option<Allocation>,
|
GetReg: FnMut() -> Option<Allocation>,
|
||||||
GetStackSlot: FnMut() -> Allocation,
|
GetStackSlot: FnMut() -> Allocation,
|
||||||
|
IsStackAlloc: Fn(Allocation) -> bool,
|
||||||
{
|
{
|
||||||
pub fn new(find_free_reg: GetReg, get_stackslot: GetStackSlot, victim: PReg) -> Self {
|
pub fn new(
|
||||||
|
find_free_reg: GetReg,
|
||||||
|
get_stackslot: GetStackSlot,
|
||||||
|
is_stack_alloc: IsStackAlloc,
|
||||||
|
victim: PReg,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack_stack_scratch_reg: None,
|
stack_stack_scratch_reg: None,
|
||||||
stack_stack_scratch_reg_save: None,
|
stack_stack_scratch_reg_save: None,
|
||||||
find_free_reg,
|
find_free_reg,
|
||||||
get_stackslot,
|
get_stackslot,
|
||||||
|
is_stack_alloc,
|
||||||
victim,
|
victim,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,7 +370,7 @@ where
|
|||||||
pub fn compute<T: Debug + Copy>(mut self, moves: MoveVecWithScratch<T>) -> MoveVec<T> {
|
pub fn compute<T: Debug + Copy>(mut self, moves: MoveVecWithScratch<T>) -> MoveVec<T> {
|
||||||
// First, do we have a vec with no stack-to-stack moves or use
|
// First, do we have a vec with no stack-to-stack moves or use
|
||||||
// of a scratch register? Fast return if so.
|
// of a scratch register? Fast return if so.
|
||||||
if !moves.needs_scratch() && !moves.stack_to_stack() {
|
if !moves.needs_scratch() && !moves.stack_to_stack(&self.is_stack_alloc) {
|
||||||
return moves.without_scratch().unwrap();
|
return moves.without_scratch().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +383,7 @@ where
|
|||||||
let moves = moves.with_scratch(scratch);
|
let moves = moves.with_scratch(scratch);
|
||||||
for &(src, dst, data) in &moves {
|
for &(src, dst, data) in &moves {
|
||||||
// Do we have a stack-to-stack move? If so, resolve.
|
// Do we have a stack-to-stack move? If so, resolve.
|
||||||
if src.is_stack() && dst.is_stack() {
|
if (self.is_stack_alloc)(src) && (self.is_stack_alloc)(dst) {
|
||||||
trace!("scratch resolver: stack to stack: {:?} -> {:?}", src, dst);
|
trace!("scratch resolver: stack to stack: {:?} -> {:?}", src, dst);
|
||||||
// Lazily allocate a stack-to-stack scratch.
|
// Lazily allocate a stack-to-stack scratch.
|
||||||
if self.stack_stack_scratch_reg.is_none() {
|
if self.stack_stack_scratch_reg.is_none() {
|
||||||
|
|||||||
Reference in New Issue
Block a user