From 3e8c9f09abc2d381dc6572b550a0467c20138349 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Mon, 7 Apr 2025 14:13:06 -0700 Subject: [PATCH] Fuzzing: add "callsite-ish constraints". This is a followup from #214. We have use-cases in Cranelift now where we generate constraints on call instructions that have: - A number of fixed-reg uses at "early"; - A number of fixed-reg defs at "late"; - A number of register clobbers, potentially overlapping with uses; - A number of "any"-constrained defs at "late", to add register pressure. The existing fuzzing function generator would create at most 3 operands, and would add *either* fixed-reg constraints *or* clobbers, not both. This PR adds a "callsite-ish constraints" branch as a subcase of the fixed-reg choice that also adds the "any" defs to add register pressure, and adds new clobbers that don't interfere with existing constraints. It also loosens the logic around constructing fixed-reg constraints: if a conflict is found with some arbitrarily-chosen constraint, it keeps going to the next operand and tries to make it fixed, rather than giving up on further operands. --- fuzz/fuzz_targets/fastalloc_checker.rs | 1 + fuzz/fuzz_targets/ion_checker.rs | 1 + fuzz/fuzz_targets/ssagen.rs | 1 + src/fuzzing/func.rs | 61 ++++++++++++++++++++++---- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/fuzz/fuzz_targets/fastalloc_checker.rs b/fuzz/fuzz_targets/fastalloc_checker.rs index b099c27a..42988944 100644 --- a/fuzz/fuzz_targets/fastalloc_checker.rs +++ b/fuzz/fuzz_targets/fastalloc_checker.rs @@ -25,6 +25,7 @@ impl Arbitrary<'_> for TestCase { fixed_nonallocatable: true, clobbers: true, reftypes: false, + callsite_ish_constraints: true, }, )?, }) diff --git a/fuzz/fuzz_targets/ion_checker.rs b/fuzz/fuzz_targets/ion_checker.rs index af41b55e..46a4d02e 100644 --- a/fuzz/fuzz_targets/ion_checker.rs +++ b/fuzz/fuzz_targets/ion_checker.rs @@ -25,6 +25,7 @@ impl Arbitrary<'_> for TestCase { fixed_nonallocatable: true, clobbers: true, reftypes: true, + callsite_ish_constraints: true, }, )?, }) diff --git a/fuzz/fuzz_targets/ssagen.rs b/fuzz/fuzz_targets/ssagen.rs index 621c94b7..1acb804b 100644 --- a/fuzz/fuzz_targets/ssagen.rs +++ b/fuzz/fuzz_targets/ssagen.rs @@ -26,6 +26,7 @@ impl Arbitrary<'_> for TestCase { fixed_nonallocatable: true, clobbers: true, reftypes: true, + callsite_ish_constraints: true, }, )?, }) diff --git a/src/fuzzing/func.rs b/src/fuzzing/func.rs index 69ce7262..17e74dd0 100644 --- a/src/fuzzing/func.rs +++ b/src/fuzzing/func.rs @@ -264,6 +264,7 @@ pub struct Options { pub fixed_nonallocatable: bool, pub clobbers: bool, pub reftypes: bool, + pub callsite_ish_constraints: bool, } impl core::default::Default for Options { @@ -274,6 +275,7 @@ impl core::default::Default for Options { fixed_nonallocatable: false, clobbers: false, reftypes: false, + callsite_ish_constraints: false, } } } @@ -289,7 +291,7 @@ impl Func { // General strategy: // 1. Create an arbitrary CFG. // 2. Create a list of vregs to define in each block. - // 3. Define some of those vregs in each block as blockparams.f. + // 3. Define some of those vregs in each block as blockparams. // 4. Populate blocks with ops that define the rest of the vregs. // - For each use, choose an available vreg: either one // already defined (via blockparam or inst) in this block, @@ -344,14 +346,19 @@ impl Func { builder.compute_doms(); + let alloc_vreg = |builder: &mut FuncBuilder, u: &mut Unstructured| { + let vreg = VReg::new(builder.f.num_vregs, RegClass::arbitrary(u)?); + builder.f.num_vregs += 1; + Ok(vreg) + }; + let mut vregs_by_block = vec![]; let mut vregs_by_block_to_be_defined = vec![]; let mut block_params = vec![vec![]; num_blocks]; for block in 0..num_blocks { let mut vregs = vec![]; for _ in 0..u.int_in_range(5..=15)? { - let vreg = VReg::new(builder.f.num_vregs, RegClass::arbitrary(u)?); - builder.f.num_vregs += 1; + let vreg = alloc_vreg(&mut builder, u)?; vregs.push(vreg); if opts.reftypes && bool::arbitrary(u)? { builder.f.reftype_vregs.push(vreg); @@ -408,7 +415,7 @@ impl Func { def_pos, )]; let mut allocations = vec![Allocation::none()]; - for _ in 0..u.int_in_range(0..=3)? { + for _ in 0..u.int_in_range(0..=10)? { let vreg = if avail.len() > 0 && (remaining_nonlocal_uses == 0 || bool::arbitrary(u)?) { @@ -471,14 +478,14 @@ impl Func { // Early-defs with fixed constraints conflict with // any other fixed uses of the same preg. if fixed_late.contains(&fixed_reg) { - break; + 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) { - break; + continue; } } let fixed_list = match op.pos() { @@ -486,7 +493,7 @@ impl Func { OperandPos::Late => &mut fixed_late, }; if fixed_list.contains(&fixed_reg) { - break; + continue; } fixed_list.push(fixed_reg); operands[i] = Operand::new( @@ -496,11 +503,49 @@ impl Func { op.pos(), ); } + + if opts.callsite_ish_constraints && bool::arbitrary(u)? { + // Define some new vregs with `any` + // constraints. + for _ in 0..u.int_in_range(0..=20)? { + let vreg = alloc_vreg(&mut builder, u)?; + operands.push(Operand::new( + vreg, + OperandConstraint::Any, + OperandKind::Def, + OperandPos::Late, + )); + } + + // 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)? { + 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, + }) + { + continue; + } + clobbers.push(preg); + } + } } else if opts.clobbers && bool::arbitrary(u)? { for _ in 0..u.int_in_range(0..=5)? { let reg = u.int_in_range(0..=30)?; if clobbers.iter().any(|r| r.hw_enc() == reg) { - break; + continue; } clobbers.push(PReg::new(reg, RegClass::arbitrary(u)?)); }