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:
Amanieu d'Antras
2022-09-20 03:23:53 +08:00
committed by GitHub
parent aeef47a06b
commit 1495c1e342
6 changed files with 67 additions and 13 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)?;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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 {