Skip to content

Commit 1495c1e

Browse files
authored
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.
1 parent aeef47a commit 1495c1e

6 files changed

Lines changed: 67 additions & 13 deletions

File tree

fuzz/fuzz_targets/ion_checker.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ impl Arbitrary<'_> for TestCase {
2222
&Options {
2323
reused_inputs: true,
2424
fixed_regs: true,
25+
fixed_nonallocatable: true,
2526
clobbers: true,
2627
control_flow: true,
2728
reducible: false,

fuzz/fuzz_targets/ssagen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ impl Arbitrary<'_> for TestCase {
2323
&Options {
2424
reused_inputs: true,
2525
fixed_regs: true,
26+
fixed_nonallocatable: true,
2627
clobbers: true,
2728
control_flow: true,
2829
reducible: false,

src/checker.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -460,19 +460,21 @@ impl CheckerState {
460460
return Err(CheckerError::MissingAllocation { inst, op });
461461
}
462462

463-
match val {
464-
CheckerValue::Universe => {
465-
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
466-
}
467-
CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
468-
return Err(CheckerError::IncorrectValuesInAllocation {
469-
inst,
470-
op,
471-
alloc,
472-
actual: vregs.clone(),
473-
});
463+
if op.as_fixed_nonallocatable().is_none() {
464+
match val {
465+
CheckerValue::Universe => {
466+
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
467+
}
468+
CheckerValue::VRegs(vregs) if !vregs.contains(&op.vreg()) => {
469+
return Err(CheckerError::IncorrectValuesInAllocation {
470+
inst,
471+
op,
472+
alloc,
473+
actual: vregs.clone(),
474+
});
475+
}
476+
_ => {}
474477
}
475-
_ => {}
476478
}
477479

478480
self.check_constraint(inst, op, alloc, allocs, checker)?;

src/fuzzing/func.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ fn choose_dominating_block(
270270
pub struct Options {
271271
pub reused_inputs: bool,
272272
pub fixed_regs: bool,
273+
pub fixed_nonallocatable: bool,
273274
pub clobbers: bool,
274275
pub control_flow: bool,
275276
pub reducible: bool,
@@ -283,6 +284,7 @@ impl std::default::Default for Options {
283284
Options {
284285
reused_inputs: false,
285286
fixed_regs: false,
287+
fixed_nonallocatable: false,
286288
clobbers: false,
287289
control_flow: true,
288290
reducible: false,
@@ -536,6 +538,8 @@ impl Func {
536538
}
537539
clobbers.push(PReg::new(reg, RegClass::Int));
538540
}
541+
} else if opts.fixed_nonallocatable && bool::arbitrary(u)? {
542+
operands.push(Operand::fixed_nonallocatable(PReg::new(63, RegClass::Int)));
539543
}
540544

541545
let is_safepoint = opts.reftypes
@@ -658,7 +662,8 @@ pub fn machine_env() -> MachineEnv {
658662
}
659663
let preferred_regs_by_class: [Vec<PReg>; 2] = [regs(0..24), vec![]];
660664
let non_preferred_regs_by_class: [Vec<PReg>; 2] = [regs(24..32), vec![]];
661-
let fixed_stack_slots = regs(32..64);
665+
let fixed_stack_slots = regs(32..63);
666+
// Register 63 is reserved for use as a fixed non-allocatable register.
662667
MachineEnv {
663668
preferred_regs_by_class,
664669
non_preferred_regs_by_class,

src/ion/liveranges.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ impl<'a, F: Function> Env<'a, F> {
366366

367367
for pos in &[OperandPos::Late, OperandPos::Early] {
368368
for op in self.func.inst_operands(inst) {
369+
if op.as_fixed_nonallocatable().is_some() {
370+
continue;
371+
}
369372
if op.pos() == *pos {
370373
let was_live = live.get(op.vreg().vreg());
371374
trace!("op {:?} was_live = {}", op, was_live);
@@ -511,6 +514,9 @@ impl<'a, F: Function> Env<'a, F> {
511514
let mut reused_input = None;
512515
for op in self.func.inst_operands(inst) {
513516
if let OperandConstraint::Reuse(i) = op.constraint() {
517+
debug_assert!(self.func.inst_operands(inst)[i]
518+
.as_fixed_nonallocatable()
519+
.is_none());
514520
reused_input = Some(self.func.inst_operands(inst)[i].vreg());
515521
break;
516522
}
@@ -931,6 +937,9 @@ impl<'a, F: Function> Env<'a, F> {
931937
let mut operand_rewrites: FxHashMap<usize, Operand> = FxHashMap::default();
932938
let mut late_def_fixed: SmallVec<[(PReg, Operand, usize); 2]> = smallvec![];
933939
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
940+
if operand.as_fixed_nonallocatable().is_some() {
941+
continue;
942+
}
934943
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
935944
match operand.pos() {
936945
OperandPos::Late => {
@@ -952,6 +961,9 @@ impl<'a, F: Function> Env<'a, F> {
952961
}
953962
}
954963
for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() {
964+
if operand.as_fixed_nonallocatable().is_some() {
965+
continue;
966+
}
955967
if let OperandConstraint::FixedReg(preg) = operand.constraint() {
956968
match operand.pos() {
957969
OperandPos::Early => {
@@ -1057,6 +1069,15 @@ impl<'a, F: Function> Env<'a, F> {
10571069
operand
10581070
);
10591071

1072+
// If this is a "fixed non-allocatable
1073+
// register" operand, set the alloc
1074+
// immediately and then ignore the operand
1075+
// hereafter.
1076+
if let Some(preg) = operand.as_fixed_nonallocatable() {
1077+
self.set_alloc(inst, i, Allocation::reg(preg));
1078+
continue;
1079+
}
1080+
10601081
match operand.kind() {
10611082
OperandKind::Def | OperandKind::Mod => {
10621083
trace!("Def of {} at {:?}", operand.vreg(), pos);

src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,19 @@ impl Operand {
677677
)
678678
}
679679

680+
/// Create an `Operand` that always results in an assignment to the
681+
/// given fixed `preg`, *without* tracking liveranges in that
682+
/// `preg`. Must only be used for non-allocatable registers.
683+
#[inline(always)]
684+
pub fn fixed_nonallocatable(preg: PReg) -> Self {
685+
Operand::new(
686+
VReg::new(VReg::MAX, preg.class()),
687+
OperandConstraint::FixedReg(preg),
688+
OperandKind::Use,
689+
OperandPos::Early,
690+
)
691+
}
692+
680693
/// Get the virtual register designated by an operand. Every
681694
/// operand must name some virtual register, even if it constrains
682695
/// the operand to a fixed physical register as well; the vregs
@@ -744,6 +757,17 @@ impl Operand {
744757
}
745758
}
746759

760+
/// If this operand is for a fixed non-allocatable register (see
761+
/// [`Operand::fixed`]), then returns the physical register that it will
762+
/// be assigned to.
763+
#[inline(always)]
764+
pub fn as_fixed_nonallocatable(self) -> Option<PReg> {
765+
match self.constraint() {
766+
OperandConstraint::FixedReg(preg) if self.vreg().vreg() == VReg::MAX => Some(preg),
767+
_ => None,
768+
}
769+
}
770+
747771
/// Get the raw 32-bit encoding of this operand's fields.
748772
#[inline(always)]
749773
pub fn bits(self) -> u32 {

0 commit comments

Comments
 (0)