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 {
|
&Options {
|
||||||
reused_inputs: true,
|
reused_inputs: true,
|
||||||
fixed_regs: true,
|
fixed_regs: true,
|
||||||
|
fixed_nonallocatable: true,
|
||||||
clobbers: true,
|
clobbers: true,
|
||||||
control_flow: true,
|
control_flow: true,
|
||||||
reducible: false,
|
reducible: false,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ impl Arbitrary<'_> for TestCase {
|
|||||||
&Options {
|
&Options {
|
||||||
reused_inputs: true,
|
reused_inputs: true,
|
||||||
fixed_regs: true,
|
fixed_regs: true,
|
||||||
|
fixed_nonallocatable: true,
|
||||||
clobbers: true,
|
clobbers: true,
|
||||||
control_flow: true,
|
control_flow: true,
|
||||||
reducible: false,
|
reducible: false,
|
||||||
|
|||||||
@@ -460,19 +460,21 @@ impl CheckerState {
|
|||||||
return Err(CheckerError::MissingAllocation { inst, op });
|
return Err(CheckerError::MissingAllocation { inst, op });
|
||||||
}
|
}
|
||||||
|
|
||||||
match val {
|
if op.as_fixed_nonallocatable().is_none() {
|
||||||
CheckerValue::Universe => {
|
match val {
|
||||||
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
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)?;
|
self.check_constraint(inst, op, alloc, allocs, checker)?;
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ fn choose_dominating_block(
|
|||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub reused_inputs: bool,
|
pub reused_inputs: bool,
|
||||||
pub fixed_regs: bool,
|
pub fixed_regs: bool,
|
||||||
|
pub fixed_nonallocatable: bool,
|
||||||
pub clobbers: bool,
|
pub clobbers: bool,
|
||||||
pub control_flow: bool,
|
pub control_flow: bool,
|
||||||
pub reducible: bool,
|
pub reducible: bool,
|
||||||
@@ -283,6 +284,7 @@ impl std::default::Default for Options {
|
|||||||
Options {
|
Options {
|
||||||
reused_inputs: false,
|
reused_inputs: false,
|
||||||
fixed_regs: false,
|
fixed_regs: false,
|
||||||
|
fixed_nonallocatable: false,
|
||||||
clobbers: false,
|
clobbers: false,
|
||||||
control_flow: true,
|
control_flow: true,
|
||||||
reducible: false,
|
reducible: false,
|
||||||
@@ -536,6 +538,8 @@ impl Func {
|
|||||||
}
|
}
|
||||||
clobbers.push(PReg::new(reg, RegClass::Int));
|
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
|
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 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 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 {
|
MachineEnv {
|
||||||
preferred_regs_by_class,
|
preferred_regs_by_class,
|
||||||
non_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 pos in &[OperandPos::Late, OperandPos::Early] {
|
||||||
for op in self.func.inst_operands(inst) {
|
for op in self.func.inst_operands(inst) {
|
||||||
|
if op.as_fixed_nonallocatable().is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if op.pos() == *pos {
|
if op.pos() == *pos {
|
||||||
let was_live = live.get(op.vreg().vreg());
|
let was_live = live.get(op.vreg().vreg());
|
||||||
trace!("op {:?} was_live = {}", op, was_live);
|
trace!("op {:?} was_live = {}", op, was_live);
|
||||||
@@ -511,6 +514,9 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
let mut reused_input = None;
|
let mut reused_input = None;
|
||||||
for op in self.func.inst_operands(inst) {
|
for op in self.func.inst_operands(inst) {
|
||||||
if let OperandConstraint::Reuse(i) = op.constraint() {
|
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());
|
reused_input = Some(self.func.inst_operands(inst)[i].vreg());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -931,6 +937,9 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
let mut operand_rewrites: FxHashMap<usize, Operand> = FxHashMap::default();
|
let mut operand_rewrites: FxHashMap<usize, Operand> = FxHashMap::default();
|
||||||
let mut late_def_fixed: SmallVec<[(PReg, Operand, usize); 2]> = smallvec![];
|
let mut late_def_fixed: SmallVec<[(PReg, Operand, usize); 2]> = smallvec![];
|
||||||
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
|
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() {
|
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
|
||||||
match operand.pos() {
|
match operand.pos() {
|
||||||
OperandPos::Late => {
|
OperandPos::Late => {
|
||||||
@@ -952,6 +961,9 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
|
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() {
|
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
|
||||||
match operand.pos() {
|
match operand.pos() {
|
||||||
OperandPos::Early => {
|
OperandPos::Early => {
|
||||||
@@ -1057,6 +1069,15 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
operand
|
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() {
|
match operand.kind() {
|
||||||
OperandKind::Def | OperandKind::Mod => {
|
OperandKind::Def | OperandKind::Mod => {
|
||||||
trace!("Def of {} at {:?}", operand.vreg(), pos);
|
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
|
/// Get the virtual register designated by an operand. Every
|
||||||
/// operand must name some virtual register, even if it constrains
|
/// operand must name some virtual register, even if it constrains
|
||||||
/// the operand to a fixed physical register as well; the vregs
|
/// 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.
|
/// Get the raw 32-bit encoding of this operand's fields.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn bits(self) -> u32 {
|
pub fn bits(self) -> u32 {
|
||||||
|
|||||||
Reference in New Issue
Block a user