From 1495c1e3423a6027ad5aa1ce6b98e9063b6b506d Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 20 Sep 2022 03:23:53 +0800 Subject: [PATCH] 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. --- fuzz/fuzz_targets/ion_checker.rs | 1 + fuzz/fuzz_targets/ssagen.rs | 1 + src/checker.rs | 26 ++++++++++++++------------ src/fuzzing/func.rs | 7 ++++++- src/ion/liveranges.rs | 21 +++++++++++++++++++++ src/lib.rs | 24 ++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/fuzz/fuzz_targets/ion_checker.rs b/fuzz/fuzz_targets/ion_checker.rs index 795d530..1ac49e9 100644 --- a/fuzz/fuzz_targets/ion_checker.rs +++ b/fuzz/fuzz_targets/ion_checker.rs @@ -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, diff --git a/fuzz/fuzz_targets/ssagen.rs b/fuzz/fuzz_targets/ssagen.rs index 054275f..733d814 100644 --- a/fuzz/fuzz_targets/ssagen.rs +++ b/fuzz/fuzz_targets/ssagen.rs @@ -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, diff --git a/src/checker.rs b/src/checker.rs index 1d905ac..90bcf3e 100644 --- a/src/checker.rs +++ b/src/checker.rs @@ -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)?; diff --git a/src/fuzzing/func.rs b/src/fuzzing/func.rs index 9ccb94f..fda2742 100644 --- a/src/fuzzing/func.rs +++ b/src/fuzzing/func.rs @@ -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; 2] = [regs(0..24), vec![]]; let non_preferred_regs_by_class: [Vec; 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, diff --git a/src/ion/liveranges.rs b/src/ion/liveranges.rs index 02dfa33..77e842e 100644 --- a/src/ion/liveranges.rs +++ b/src/ion/liveranges.rs @@ -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 = 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); diff --git a/src/lib.rs b/src/lib.rs index 037a6a5..ce48778 100644 --- a/src/lib.rs +++ b/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 { + 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 {