diff --git a/fuzz/fuzz_targets/fastalloc_checker.rs b/fuzz/fuzz_targets/fastalloc_checker.rs index 42988944..026c1608 100644 --- a/fuzz/fuzz_targets/fastalloc_checker.rs +++ b/fuzz/fuzz_targets/fastalloc_checker.rs @@ -26,6 +26,7 @@ impl Arbitrary<'_> for TestCase { clobbers: true, reftypes: false, callsite_ish_constraints: true, + ..Options::default() }, )?, }) @@ -37,8 +38,8 @@ fuzz_target!(|testcase: TestCase| { let _ = env_logger::try_init(); log::trace!("func:\n{:?}", func); let env = regalloc2::fuzzing::func::machine_env(); - let out = - regalloc2::fuzzing::fastalloc::run(&func, &env, true, false).expect("regalloc did not succeed"); + let out = regalloc2::fuzzing::fastalloc::run(&func, &env, true, false) + .expect("regalloc did not succeed"); let mut checker = Checker::new(&func, &env); checker.prepare(&out); diff --git a/fuzz/fuzz_targets/ion_checker.rs b/fuzz/fuzz_targets/ion_checker.rs index 46a4d02e..39b1f0e6 100644 --- a/fuzz/fuzz_targets/ion_checker.rs +++ b/fuzz/fuzz_targets/ion_checker.rs @@ -26,6 +26,7 @@ impl Arbitrary<'_> for TestCase { clobbers: true, reftypes: true, callsite_ish_constraints: true, + ..Options::default() }, )?, }) diff --git a/fuzz/fuzz_targets/ssagen.rs b/fuzz/fuzz_targets/ssagen.rs index 1acb804b..99bfde20 100644 --- a/fuzz/fuzz_targets/ssagen.rs +++ b/fuzz/fuzz_targets/ssagen.rs @@ -27,6 +27,7 @@ impl Arbitrary<'_> for TestCase { clobbers: true, reftypes: true, callsite_ish_constraints: true, + ..Options::default() }, )?, }) diff --git a/src/fuzzing/func.rs b/src/fuzzing/func.rs index 7466d90e..f115f9e8 100644 --- a/src/fuzzing/func.rs +++ b/src/fuzzing/func.rs @@ -10,6 +10,7 @@ use crate::{ use alloc::vec::Vec; use alloc::{format, vec}; +use core::ops::RangeInclusive; use super::arbitrary::Result as ArbitraryResult; use super::arbitrary::{Arbitrary, Unstructured}; @@ -206,6 +207,27 @@ impl FuncBuilder { ); } + fn add_arbitrary_debug_labels( + &mut self, + u: &mut Unstructured<'_>, + num_blocks: usize, + vreg: VReg, + ) -> ArbitraryResult<()> { + let assumed_end_inst = 10 * num_blocks; + let mut start = u.int_in_range::(0..=assumed_end_inst)?; + Ok(for _ in 0..10 { + if start >= assumed_end_inst { + break; + } + let end = u.int_in_range::(start..=assumed_end_inst)?; + let label = u.int_in_range::(0..=100)?; + self.f + .debug_value_labels + .push((vreg, Inst::new(start), Inst::new(end), label)); + start = end; + }) + } + fn finalize(mut self) -> Func { for (blocknum, blockrange) in self.f.blocks.iter_mut().enumerate() { let begin_inst = self.f.insts.len(); @@ -226,6 +248,12 @@ impl Arbitrary<'_> for RegClass { } } +impl Arbitrary<'_> for OperandPos { + fn arbitrary(u: &mut Unstructured) -> ArbitraryResult { + Ok(*u.choose(&[OperandPos::Early, OperandPos::Late])?) + } +} + impl Arbitrary<'_> for OperandConstraint { fn arbitrary(u: &mut Unstructured) -> ArbitraryResult { Ok(*u.choose(&[OperandConstraint::Any, OperandConstraint::Reg])?) @@ -257,7 +285,83 @@ fn choose_dominating_block( Ok(block) } -#[derive(Clone, Copy, Debug)] +fn convert_def_to_reuse(u: &mut Unstructured<'_>, operands: &mut [Operand]) -> ArbitraryResult<()> { + let op = operands[0]; + debug_assert_eq!(op.kind(), OperandKind::Def); + + let reused = u.int_in_range(1..=(operands.len() - 1))?; + Ok(if op.class() == operands[reused].class() { + // Replace the def with a reuse of an existing input. + operands[0] = Operand::new( + op.vreg(), + OperandConstraint::Reuse(reused), + op.kind(), + OperandPos::Late, + ); + + // Make sure reused input is a register. + let op = operands[reused]; + operands[reused] = Operand::new( + op.vreg(), + OperandConstraint::Reg, + op.kind(), + OperandPos::Early, + ); + }) +} + +fn convert_op_to_fixed( + u: &mut Unstructured<'_>, + op: &mut Operand, + fixed_early: &mut Vec, + fixed_late: &mut Vec, +) -> ArbitraryResult<()> { + let fixed_reg = PReg::new(u.int_in_range(0..=62)?, op.class()); + + if op.kind() == OperandKind::Def && op.pos() == OperandPos::Early { + // Early-defs with fixed constraints conflict with + // any other fixed uses of the same preg. + if fixed_late.contains(&fixed_reg) { + return Ok(()); + } + } + + if op.kind() == OperandKind::Use && op.pos() == OperandPos::Late { + // Late-use with fixed constraints conflict with + // any other fixed uses of the same preg. + if fixed_early.contains(&fixed_reg) { + return Ok(()); + } + } + + // Check that it is not already fixed. + let fixed_list = match op.pos() { + OperandPos::Early => fixed_early, + OperandPos::Late => fixed_late, + }; + if fixed_list.contains(&fixed_reg) { + return Ok(()); + } + + fixed_list.push(fixed_reg); + *op = Operand::new( + op.vreg(), + OperandConstraint::FixedReg(fixed_reg), + op.kind(), + op.pos(), + ); + + Ok(()) +} + +fn has_fixed_def_with(preg: PReg) -> impl Fn(&Operand) -> bool { + move |op| match (op.kind(), op.constraint()) { + (OperandKind::Def, OperandConstraint::FixedReg(fixed)) => fixed == preg, + _ => false, + } +} + +#[derive(Clone, Debug)] pub struct Options { pub reused_inputs: bool, pub fixed_regs: bool, @@ -265,6 +369,11 @@ pub struct Options { pub clobbers: bool, pub reftypes: bool, pub callsite_ish_constraints: bool, + pub num_blocks: RangeInclusive, + pub num_vregs_per_block: RangeInclusive, + pub num_uses_per_inst: RangeInclusive, + pub num_callsite_ish_vregs_per_inst: RangeInclusive, + pub num_clobbers_per_inst: RangeInclusive, } impl core::default::Default for Options { @@ -276,6 +385,11 @@ impl core::default::Default for Options { clobbers: false, reftypes: false, callsite_ish_constraints: false, + num_blocks: 1..=100, + num_vregs_per_block: 5..=15, + num_uses_per_inst: 0..=10, + num_callsite_ish_vregs_per_inst: 0..=20, + num_clobbers_per_inst: 0..=10, } } } @@ -298,7 +412,7 @@ impl Func { // or one defined in a dominating block. let mut builder = FuncBuilder::new(); - for _ in 0..u.int_in_range(1..=100)? { + for _ in 0..u.int_in_range(opts.num_blocks.clone())? { builder.add_block(); } let num_blocks = builder.f.blocks.len(); @@ -332,18 +446,14 @@ impl Func { let succ = *u.choose(&in_blocks[..])?; builder.add_edge(Block::new(pred), Block::new(succ)); } - builder.compute_doms(); for block in 0..num_blocks { builder.f.block_preds[block].clear(); - } - for block in 0..num_blocks { for &succ in &builder.f.block_succs[block] { builder.f.block_preds[succ.index()].push(Block::new(block)); } } - builder.compute_doms(); let alloc_vreg = |builder: &mut FuncBuilder, u: &mut Unstructured| { @@ -356,33 +466,21 @@ impl Func { let mut vregs_by_block_to_be_defined = vec![]; let mut block_params = vec![vec![]; num_blocks]; for block in 0..num_blocks { + // Calculate the available vregs for this block. let mut vregs = vec![]; - for _ in 0..u.int_in_range(5..=15)? { + for _ in 0..u.int_in_range(opts.num_vregs_per_block.clone())? { let vreg = alloc_vreg(&mut builder, u)?; vregs.push(vreg); if opts.reftypes && bool::arbitrary(u)? { builder.f.reftype_vregs.push(vreg); } if bool::arbitrary(u)? { - let assumed_end_inst = 10 * num_blocks; - let mut start = u.int_in_range::(0..=assumed_end_inst)?; - for _ in 0..10 { - if start >= assumed_end_inst { - break; - } - let end = u.int_in_range::(start..=assumed_end_inst)?; - let label = u.int_in_range::(0..=100)?; - builder.f.debug_value_labels.push(( - vreg, - Inst::new(start), - Inst::new(end), - label, - )); - start = end; - } + builder.add_arbitrary_debug_labels(u, num_blocks, vreg)?; } } vregs_by_block.push(vregs.clone()); + + // Choose some of the vregs to be block parameters. let mut vregs_to_be_defined = vec![]; let mut max_block_params = u.int_in_range(0..=core::cmp::min(3, vregs.len() / 3))?; for &vreg in &vregs { @@ -402,20 +500,17 @@ impl Func { let mut avail = block_params[block].clone(); let mut remaining_nonlocal_uses = u.int_in_range(0..=3)?; while let Some(vreg) = vregs_by_block_to_be_defined[block].pop() { - let def_constraint = OperandConstraint::arbitrary(u)?; - let def_pos = if bool::arbitrary(u)? { - OperandPos::Early - } else { - OperandPos::Late - }; + // Start with a written-to vreg (`def`). let mut operands = vec![Operand::new( vreg, - def_constraint, + OperandConstraint::arbitrary(u)?, OperandKind::Def, - def_pos, + OperandPos::arbitrary(u)?, )]; + + // Then add some read-from vregs (`uses`). let mut allocations = vec![Allocation::none()]; - for _ in 0..u.int_in_range(0..=10)? { + for _ in 0..u.int_in_range(opts.num_uses_per_inst.clone())? { let vreg = if avail.len() > 0 && (remaining_nonlocal_uses == 0 || bool::arbitrary(u)?) { @@ -435,114 +530,54 @@ impl Func { remaining_nonlocal_uses -= 1; *u.choose(&vregs_by_block[def_block.index()])? }; - let use_constraint = OperandConstraint::arbitrary(u)?; operands.push(Operand::new( vreg, - use_constraint, + OperandConstraint::arbitrary(u)?, OperandKind::Use, OperandPos::Early, )); allocations.push(Allocation::none()); } + + // Convert some of the operands to have special constraints: + // reuses, fixed, clobbers, etc. let mut clobbers: Vec = vec![]; if operands.len() > 1 && opts.reused_inputs && bool::arbitrary(u)? { - // Make the def a reused input. - let op = operands[0]; - debug_assert_eq!(op.kind(), OperandKind::Def); - let reused = u.int_in_range(1..=(operands.len() - 1))?; - if op.class() == operands[reused].class() { - operands[0] = Operand::new( - op.vreg(), - OperandConstraint::Reuse(reused), - op.kind(), - OperandPos::Late, - ); - // Make sure reused input is a Reg. - let op = operands[reused]; - operands[reused] = Operand::new( - op.vreg(), - OperandConstraint::Reg, - op.kind(), - OperandPos::Early, - ); - } + convert_def_to_reuse(u, &mut operands)?; } else if opts.fixed_regs && bool::arbitrary(u)? { let mut fixed_early = vec![]; let mut fixed_late = vec![]; for _ in 0..u.int_in_range(0..=operands.len() - 1)? { // Pick an operand and make it a fixed reg. let i = u.int_in_range(0..=(operands.len() - 1))?; - let op = operands[i]; - let fixed_reg = PReg::new(u.int_in_range(0..=62)?, op.class()); - if op.kind() == OperandKind::Def && op.pos() == OperandPos::Early { - // Early-defs with fixed constraints conflict with - // any other fixed uses of the same preg. - if fixed_late.contains(&fixed_reg) { - continue; - } - } - if op.kind() == OperandKind::Use && op.pos() == OperandPos::Late { - // Late-use with fixed constraints conflict with - // any other fixed uses of the same preg. - if fixed_early.contains(&fixed_reg) { - continue; - } - } - let fixed_list = match op.pos() { - OperandPos::Early => &mut fixed_early, - OperandPos::Late => &mut fixed_late, - }; - if fixed_list.contains(&fixed_reg) { - continue; - } - fixed_list.push(fixed_reg); - operands[i] = Operand::new( - op.vreg(), - OperandConstraint::FixedReg(fixed_reg), - op.kind(), - op.pos(), - ); + let op = &mut operands[i]; + convert_op_to_fixed(u, op, &mut fixed_early, &mut fixed_late)?; } if opts.callsite_ish_constraints && bool::arbitrary(u)? { - // Define some new vregs with `any` - // constraints. - for _ in 0..u.int_in_range(0..=20)? { + // Define some new vregs with `any` constraints. + for _ in 0..u.int_in_range(opts.num_callsite_ish_vregs_per_inst.clone())? { let vreg = alloc_vreg(&mut builder, u)?; - operands.push(Operand::new( - vreg, - OperandConstraint::Any, - OperandKind::Def, - OperandPos::Late, - )); + operands.push(Operand::any_def(vreg)); } - // Create some clobbers, avoiding regs named - // by operand constraints. Note that the sum - // of the maximum clobber count here (10) and - // maximum operand count above (10) is less - // than the number of registers in any single + // Create some clobbers, avoiding regs named by operand + // constraints. Note that the sum of the maximum clobber + // count here (10) and maximum operand count above (10) + // is less than the number of registers in any single // class, so the resulting problem is always // allocatable. - for _ in 0..u.int_in_range(0..=10)? { + for _ in 0..u.int_in_range(opts.num_clobbers_per_inst.clone())? { let reg = u.int_in_range(0..=30)?; let preg = PReg::new(reg, RegClass::arbitrary(u)?); - if operands - .iter() - .any(|op| match (op.kind(), op.constraint()) { - (OperandKind::Def, OperandConstraint::FixedReg(fixed)) => { - fixed == preg - } - _ => false, - }) - { + if operands.iter().any(has_fixed_def_with(preg)) { continue; } clobbers.push(preg); } } } else if opts.clobbers && bool::arbitrary(u)? { - for _ in 0..u.int_in_range(0..=5)? { + for _ in 0..u.int_in_range(opts.num_clobbers_per_inst.clone())? { let reg = u.int_in_range(0..=30)?; if clobbers.iter().any(|r| r.hw_enc() == reg) { continue;