Skip to content

Commit 09371f0

Browse files
authored
Run the fuzzer with all register classes (#152)
* Run the fuzzer with all register classes * Don't share spill slots between register classes The parallel move resolvers runs separately for each register class and can't handle parallel moves involving a single stack slot used by two register classes.
1 parent 21644c5 commit 09371f0

File tree

5 files changed

+94
-67
lines changed

5 files changed

+94
-67
lines changed

src/fuzzing/func.rs

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,6 @@ pub struct InstData {
3030
}
3131

3232
impl InstData {
33-
pub fn op(def: usize, uses: &[usize]) -> InstData {
34-
let mut operands = vec![Operand::reg_def(VReg::new(def, RegClass::Int))];
35-
for &u in uses {
36-
operands.push(Operand::reg_use(VReg::new(u, RegClass::Int)));
37-
}
38-
InstData {
39-
op: InstOpcode::Op,
40-
operands,
41-
clobbers: vec![],
42-
is_safepoint: false,
43-
}
44-
}
4533
pub fn branch() -> InstData {
4634
InstData {
4735
op: InstOpcode::Branch,
@@ -145,8 +133,10 @@ impl Function for Func {
145133

146134
fn spillslot_size(&self, regclass: RegClass) -> usize {
147135
match regclass {
136+
// Test the case where 2 classes share the same
148137
RegClass::Int => 1,
149-
RegClass::Float | RegClass::Vector => 2,
138+
RegClass::Float => 1,
139+
RegClass::Vector => 2,
150140
}
151141
}
152142
}
@@ -234,6 +224,12 @@ impl FuncBuilder {
234224
}
235225
}
236226

227+
impl Arbitrary<'_> for RegClass {
228+
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Self> {
229+
Ok(*u.choose(&[RegClass::Int, RegClass::Float, RegClass::Vector])?)
230+
}
231+
}
232+
237233
impl Arbitrary<'_> for OperandConstraint {
238234
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Self> {
239235
Ok(*u.choose(&[OperandConstraint::Any, OperandConstraint::Reg])?)
@@ -380,7 +376,7 @@ impl Func {
380376
for block in 0..num_blocks {
381377
let mut vregs = vec![];
382378
for _ in 0..u.int_in_range(5..=15)? {
383-
let vreg = VReg::new(builder.f.num_vregs, RegClass::Int);
379+
let vreg = VReg::new(builder.f.num_vregs, RegClass::arbitrary(u)?);
384380
builder.f.num_vregs += 1;
385381
vregs.push(vreg);
386382
if opts.reftypes && bool::arbitrary(u)? {
@@ -476,28 +472,30 @@ impl Func {
476472
let op = operands[0];
477473
debug_assert_eq!(op.kind(), OperandKind::Def);
478474
let reused = u.int_in_range(1..=(operands.len() - 1))?;
479-
operands[0] = Operand::new(
480-
op.vreg(),
481-
OperandConstraint::Reuse(reused),
482-
op.kind(),
483-
OperandPos::Late,
484-
);
485-
// Make sure reused input is a Reg.
486-
let op = operands[reused];
487-
operands[reused] = Operand::new(
488-
op.vreg(),
489-
OperandConstraint::Reg,
490-
op.kind(),
491-
OperandPos::Early,
492-
);
475+
if op.class() == operands[reused].class() {
476+
operands[0] = Operand::new(
477+
op.vreg(),
478+
OperandConstraint::Reuse(reused),
479+
op.kind(),
480+
OperandPos::Late,
481+
);
482+
// Make sure reused input is a Reg.
483+
let op = operands[reused];
484+
operands[reused] = Operand::new(
485+
op.vreg(),
486+
OperandConstraint::Reg,
487+
op.kind(),
488+
OperandPos::Early,
489+
);
490+
}
493491
} else if opts.fixed_regs && bool::arbitrary(u)? {
494492
let mut fixed_early = vec![];
495493
let mut fixed_late = vec![];
496494
for _ in 0..u.int_in_range(0..=operands.len() - 1)? {
497495
// Pick an operand and make it a fixed reg.
498496
let i = u.int_in_range(0..=(operands.len() - 1))?;
499497
let op = operands[i];
500-
let fixed_reg = PReg::new(u.int_in_range(0..=62)?, RegClass::Int);
498+
let fixed_reg = PReg::new(u.int_in_range(0..=62)?, op.class());
501499
let fixed_list = match op.pos() {
502500
OperandPos::Early => &mut fixed_early,
503501
OperandPos::Late => &mut fixed_late,
@@ -535,10 +533,13 @@ impl Func {
535533
if clobbers.iter().any(|r| r.hw_enc() == reg) {
536534
break;
537535
}
538-
clobbers.push(PReg::new(reg, RegClass::Int));
536+
clobbers.push(PReg::new(reg, RegClass::arbitrary(u)?));
539537
}
540538
} else if opts.fixed_nonallocatable && bool::arbitrary(u)? {
541-
operands.push(Operand::fixed_nonallocatable(PReg::new(63, RegClass::Int)));
539+
operands.push(Operand::fixed_nonallocatable(PReg::new(
540+
63,
541+
RegClass::arbitrary(u)?,
542+
)));
542543
}
543544

544545
let is_safepoint = opts.reftypes
@@ -565,18 +566,30 @@ impl Func {
565566
let mut params = vec![];
566567
for &succ in &builder.f.block_succs[block] {
567568
let mut args = vec![];
568-
for _ in 0..builder.f.block_params_in[succ.index()].len() {
569+
for i in 0..builder.f.block_params_in[succ.index()].len() {
569570
let dom_block = choose_dominating_block(
570571
&builder.idom[..],
571572
Block::new(block),
572573
false,
573574
u,
574575
)?;
575-
let vreg = if dom_block.is_valid() && bool::arbitrary(u)? {
576-
u.choose(&vregs_by_block[dom_block.index()][..])?
576+
577+
// Look for a vreg with a suitable class. If no
578+
// suitable vreg is available then we error out, which
579+
// causes the fuzzer to skip this function.
580+
let vregs = if dom_block.is_valid() && bool::arbitrary(u)? {
581+
&vregs_by_block[dom_block.index()][..]
577582
} else {
578-
u.choose(&avail[..])?
583+
&avail[..]
579584
};
585+
let suitable_vregs: Vec<_> = vregs
586+
.iter()
587+
.filter(|vreg| {
588+
vreg.class() == builder.f.block_params_in[succ.index()][i].class()
589+
})
590+
.copied()
591+
.collect();
592+
let vreg = u.choose(&suitable_vregs)?;
580593
args.push(*vreg);
581594
}
582595
params.push(args);
@@ -656,13 +669,29 @@ impl core::fmt::Debug for Func {
656669
}
657670

658671
pub fn machine_env() -> MachineEnv {
659-
fn regs(r: core::ops::Range<usize>) -> Vec<PReg> {
660-
r.map(|i| PReg::new(i, RegClass::Int)).collect()
661-
}
662-
let preferred_regs_by_class: [Vec<PReg>; 3] = [regs(0..24), vec![], vec![]];
663-
let non_preferred_regs_by_class: [Vec<PReg>; 3] = [regs(24..32), vec![], vec![]];
672+
fn regs(r: core::ops::Range<usize>, c: RegClass) -> Vec<PReg> {
673+
r.map(|i| PReg::new(i, c)).collect()
674+
}
675+
let preferred_regs_by_class: [Vec<PReg>; 3] = [
676+
regs(0..24, RegClass::Int),
677+
regs(0..24, RegClass::Float),
678+
regs(0..24, RegClass::Vector),
679+
];
680+
let non_preferred_regs_by_class: [Vec<PReg>; 3] = [
681+
regs(24..32, RegClass::Int),
682+
regs(24..32, RegClass::Float),
683+
regs(24..32, RegClass::Vector),
684+
];
664685
let scratch_by_class: [Option<PReg>; 3] = [None, None, None];
665-
let fixed_stack_slots = regs(32..63);
686+
let fixed_stack_slots = (32..63)
687+
.flat_map(|i| {
688+
[
689+
PReg::new(i, RegClass::Int),
690+
PReg::new(i, RegClass::Float),
691+
PReg::new(i, RegClass::Vector),
692+
]
693+
})
694+
.collect();
666695
// Register 63 is reserved for use as a fixed non-allocatable register.
667696
MachineEnv {
668697
preferred_regs_by_class,

src/ion/data_structures.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ pub struct SpillSet {
300300
pub class: RegClass,
301301
pub spill_bundle: LiveBundleIndex,
302302
pub required: bool,
303-
pub size: u8,
304303
pub splits: u8,
305304

306305
/// The aggregate [`CodeRange`] of all involved [`LiveRange`]s. The effect of this abstraction
@@ -466,7 +465,7 @@ pub struct Env<'a, F: Function> {
466465

467466
pub spilled_bundles: Vec<LiveBundleIndex>,
468467
pub spillslots: Vec<SpillSlotData>,
469-
pub slots_by_size: Vec<SpillSlotList>,
468+
pub slots_by_class: [SpillSlotList; 3],
470469

471470
pub extra_spillslots_by_class: [SmallVec<[Allocation; 2]>; 3],
472471
pub preferred_victim_by_class: [PReg; 3],
@@ -554,6 +553,13 @@ pub struct SpillSlotList {
554553
}
555554

556555
impl SpillSlotList {
556+
pub fn new() -> Self {
557+
SpillSlotList {
558+
slots: smallvec![],
559+
probe_start: 0,
560+
}
561+
}
562+
557563
/// Get the next spillslot index in probing order, wrapping around
558564
/// at the end of the slots list.
559565
pub(crate) fn next_index(&self, index: usize) -> usize {

src/ion/merge.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,8 @@ impl<'a, F: Function> Env<'a, F> {
277277

278278
// Create a spillslot for this bundle.
279279
let reg = self.vreg(vreg);
280-
let size = self.func.spillslot_size(reg.class()) as u8;
281280
let ssidx = self.spillsets.push(SpillSet {
282281
slot: SpillSlotIndex::invalid(),
283-
size,
284282
required: false,
285283
class: reg.class(),
286284
reg_hint: PReg::invalid(),

src/ion/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ impl<'a, F: Function> Env<'a, F> {
6767
safepoints_per_vreg: HashMap::new(),
6868
spilled_bundles: vec![],
6969
spillslots: vec![],
70-
slots_by_size: vec![],
70+
slots_by_class: [
71+
SpillSlotList::new(),
72+
SpillSlotList::new(),
73+
SpillSlotList::new(),
74+
],
7175
allocated_bundle_count: 0,
7276

7377
extra_spillslots_by_class: [smallvec![], smallvec![], smallvec![]],

src/ion/spill.rs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
1515
use super::{
1616
AllocRegResult, Env, LiveRangeKey, PReg, PRegIndex, RegTraversalIter, SpillSetIndex,
17-
SpillSlotData, SpillSlotIndex, SpillSlotList,
17+
SpillSlotData, SpillSlotIndex,
1818
};
1919
use crate::{ion::data_structures::SpillSetRanges, Allocation, Function, SpillSlot};
20-
use smallvec::smallvec;
2120

2221
impl<'a, F: Function> Env<'a, F> {
2322
pub fn try_allocating_regs_for_spilled_bundles(&mut self) {
@@ -99,40 +98,31 @@ impl<'a, F: Function> Env<'a, F> {
9998
if !self.spillsets[spillset].required {
10099
continue;
101100
}
102-
// Get or create the spillslot list for this size.
103-
let size = self.spillsets[spillset].size as usize;
104-
if size >= self.slots_by_size.len() {
105-
self.slots_by_size.resize(
106-
size + 1,
107-
SpillSlotList {
108-
slots: smallvec![],
109-
probe_start: 0,
110-
},
111-
);
112-
}
101+
let class = self.spillsets[spillset].class as usize;
113102
// Try a few existing spillslots.
114-
let mut i = self.slots_by_size[size].probe_start;
103+
let mut i = self.slots_by_class[class].probe_start;
115104
let mut success = false;
116105
// Never probe the same element more than once: limit the
117106
// attempt count to the number of slots in existence.
118-
for _attempt in 0..core::cmp::min(self.slots_by_size[size].slots.len(), MAX_ATTEMPTS) {
107+
for _attempt in 0..core::cmp::min(self.slots_by_class[class].slots.len(), MAX_ATTEMPTS)
108+
{
119109
// Note: this indexing of `slots` is always valid
120110
// because either the `slots` list is empty and the
121111
// iteration limit above consequently means we don't
122112
// run this loop at all, or else `probe_start` is
123113
// in-bounds (because it is made so below when we add
124114
// a slot, and it always takes on the last index `i`
125115
// after this loop).
126-
let spillslot = self.slots_by_size[size].slots[i];
116+
let spillslot = self.slots_by_class[class].slots[i];
127117

128118
if self.spillslot_can_fit_spillset(spillslot, spillset) {
129119
self.allocate_spillset_to_spillslot(spillset, spillslot);
130120
success = true;
131-
self.slots_by_size[size].probe_start = i;
121+
self.slots_by_class[class].probe_start = i;
132122
break;
133123
}
134124

135-
i = self.slots_by_size[size].next_index(i);
125+
i = self.slots_by_class[class].next_index(i);
136126
}
137127

138128
if !success {
@@ -141,10 +131,10 @@ impl<'a, F: Function> Env<'a, F> {
141131
self.spillslots.push(SpillSlotData {
142132
ranges: SpillSetRanges::new(),
143133
alloc: Allocation::none(),
144-
slots: size as u32,
134+
slots: self.func.spillslot_size(self.spillsets[spillset].class) as u32,
145135
});
146-
self.slots_by_size[size].slots.push(spillslot);
147-
self.slots_by_size[size].probe_start = self.slots_by_size[size].slots.len() - 1;
136+
self.slots_by_class[class].slots.push(spillslot);
137+
self.slots_by_class[class].probe_start = self.slots_by_class[class].slots.len() - 1;
148138

149139
self.allocate_spillset_to_spillslot(spillset, spillslot);
150140
}

0 commit comments

Comments
 (0)