Add fixed-non-allocatable operand support (#77)
This allows a non-allocatable `PReg` to be passed on directly to the allocations vector without any liverange tracking from the register allocator. The main intended use case is to support ISA-specific special registers such as a fixed zero register.
This commit is contained in:
@@ -22,6 +22,7 @@ impl Arbitrary<'_> for TestCase {
|
||||
&Options {
|
||||
reused_inputs: true,
|
||||
fixed_regs: true,
|
||||
fixed_nonallocatable: true,
|
||||
clobbers: true,
|
||||
control_flow: true,
|
||||
reducible: false,
|
||||
|
||||
@@ -23,6 +23,7 @@ impl Arbitrary<'_> for TestCase {
|
||||
&Options {
|
||||
reused_inputs: true,
|
||||
fixed_regs: true,
|
||||
fixed_nonallocatable: true,
|
||||
clobbers: true,
|
||||
control_flow: true,
|
||||
reducible: false,
|
||||
|
||||
@@ -460,19 +460,21 @@ impl CheckerState {
|
||||
return Err(CheckerError::MissingAllocation { inst, op });
|
||||
}
|
||||
|
||||
match val {
|
||||
CheckerValue::Universe => {
|
||||
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
||||
if op.as_fixed_nonallocatable().is_none() {
|
||||
match val {
|
||||
CheckerValue::Universe => {
|
||||
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
||||
}
|
||||
CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
|
||||
return Err(CheckerError::IncorrectValuesInAllocation {
|
||||
inst,
|
||||
op,
|
||||
alloc,
|
||||
actual: vregs.clone(),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
|
||||
return Err(CheckerError::IncorrectValuesInAllocation {
|
||||
inst,
|
||||
op,
|
||||
alloc,
|
||||
actual: vregs.clone(),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.check_constraint(inst, op, alloc, allocs, checker)?;
|
||||
|
||||
@@ -270,6 +270,7 @@ fn choose_dominating_block(
|
||||
pub struct Options {
|
||||
pub reused_inputs: bool,
|
||||
pub fixed_regs: bool,
|
||||
pub fixed_nonallocatable: bool,
|
||||
pub clobbers: bool,
|
||||
pub control_flow: bool,
|
||||
pub reducible: bool,
|
||||
@@ -283,6 +284,7 @@ impl std::default::Default for Options {
|
||||
Options {
|
||||
reused_inputs: false,
|
||||
fixed_regs: false,
|
||||
fixed_nonallocatable: false,
|
||||
clobbers: false,
|
||||
control_flow: true,
|
||||
reducible: false,
|
||||
@@ -536,6 +538,8 @@ impl Func {
|
||||
}
|
||||
clobbers.push(PReg::new(reg, RegClass::Int));
|
||||
}
|
||||
} else if opts.fixed_nonallocatable && bool::arbitrary(u)? {
|
||||
operands.push(Operand::fixed_nonallocatable(PReg::new(63, RegClass::Int)));
|
||||
}
|
||||
|
||||
let is_safepoint = opts.reftypes
|
||||
@@ -658,7 +662,8 @@ pub fn machine_env() -> MachineEnv {
|
||||
}
|
||||
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 fixed_stack_slots = regs(32..64);
|
||||
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,
|
||||
|
||||
@@ -366,6 +366,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
|
||||
for pos in &[OperandPos::Late, OperandPos::Early] {
|
||||
for op in self.func.inst_operands(inst) {
|
||||
if op.as_fixed_nonallocatable().is_some() {
|
||||
continue;
|
||||
}
|
||||
if op.pos() == *pos {
|
||||
let was_live = live.get(op.vreg().vreg());
|
||||
trace!("op {:?} was_live = {}", op, was_live);
|
||||
@@ -511,6 +514,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
let mut reused_input = None;
|
||||
for op in self.func.inst_operands(inst) {
|
||||
if let OperandConstraint::Reuse(i) = op.constraint() {
|
||||
debug_assert!(self.func.inst_operands(inst)[i]
|
||||
.as_fixed_nonallocatable()
|
||||
.is_none());
|
||||
reused_input = Some(self.func.inst_operands(inst)[i].vreg());
|
||||
break;
|
||||
}
|
||||
@@ -931,6 +937,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
let mut operand_rewrites: FxHashMap<usize, Operand> = FxHashMap::default();
|
||||
let mut late_def_fixed: SmallVec<[(PReg, Operand, usize); 2]> = smallvec![];
|
||||
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
|
||||
if operand.as_fixed_nonallocatable().is_some() {
|
||||
continue;
|
||||
}
|
||||
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
|
||||
match operand.pos() {
|
||||
OperandPos::Late => {
|
||||
@@ -952,6 +961,9 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
|
||||
if operand.as_fixed_nonallocatable().is_some() {
|
||||
continue;
|
||||
}
|
||||
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
|
||||
match operand.pos() {
|
||||
OperandPos::Early => {
|
||||
@@ -1057,6 +1069,15 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
operand
|
||||
);
|
||||
|
||||
// If this is a "fixed non-allocatable
|
||||
// register" operand, set the alloc
|
||||
// immediately and then ignore the operand
|
||||
// hereafter.
|
||||
if let Some(preg) = operand.as_fixed_nonallocatable() {
|
||||
self.set_alloc(inst, i, Allocation::reg(preg));
|
||||
continue;
|
||||
}
|
||||
|
||||
match operand.kind() {
|
||||
OperandKind::Def | OperandKind::Mod => {
|
||||
trace!("Def of {} at {:?}", operand.vreg(), pos);
|
||||
|
||||
24
src/lib.rs
24
src/lib.rs
@@ -677,6 +677,19 @@ impl Operand {
|
||||
)
|
||||
}
|
||||
|
||||
/// Create an `Operand` that always results in an assignment to the
|
||||
/// given fixed `preg`, *without* tracking liveranges in that
|
||||
/// `preg`. Must only be used for non-allocatable registers.
|
||||
#[inline(always)]
|
||||
pub fn fixed_nonallocatable(preg: PReg) -> Self {
|
||||
Operand::new(
|
||||
VReg::new(VReg::MAX, preg.class()),
|
||||
OperandConstraint::FixedReg(preg),
|
||||
OperandKind::Use,
|
||||
OperandPos::Early,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the virtual register designated by an operand. Every
|
||||
/// operand must name some virtual register, even if it constrains
|
||||
/// the operand to a fixed physical register as well; the vregs
|
||||
@@ -744,6 +757,17 @@ impl Operand {
|
||||
}
|
||||
}
|
||||
|
||||
/// If this operand is for a fixed non-allocatable register (see
|
||||
/// [`Operand::fixed`]), then returns the physical register that it will
|
||||
/// be assigned to.
|
||||
#[inline(always)]
|
||||
pub fn as_fixed_nonallocatable(self) -> Option<PReg> {
|
||||
match self.constraint() {
|
||||
OperandConstraint::FixedReg(preg) if self.vreg().vreg() == VReg::MAX => Some(preg),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw 32-bit encoding of this operand's fields.
|
||||
#[inline(always)]
|
||||
pub fn bits(self) -> u32 {
|
||||
|
||||
Reference in New Issue
Block a user