diff --git a/src/fuzzing/func.rs b/src/fuzzing/func.rs index b2a4817f..c936d10f 100644 --- a/src/fuzzing/func.rs +++ b/src/fuzzing/func.rs @@ -367,6 +367,7 @@ pub struct Options { pub fixed_regs: bool, pub fixed_nonallocatable: bool, pub clobbers: bool, + pub fixed_def_clobbers: bool, pub reftypes: bool, pub callsite_ish_constraints: bool, pub num_blocks: RangeInclusive, @@ -383,6 +384,7 @@ impl Options { fixed_regs: false, fixed_nonallocatable: false, clobbers: false, + fixed_def_clobbers: false, reftypes: false, callsite_ish_constraints: false, num_blocks: 1..=100, @@ -582,6 +584,19 @@ impl Func { ))); } + if opts.fixed_def_clobbers && bool::arbitrary(u)? { + // Exercise an impossible fixed output, not just allocatable functions. + if let (OperandKind::Def, OperandPos::Late, OperandConstraint::FixedReg(preg)) = ( + operands[0].kind(), + operands[0].pos(), + operands[0].constraint(), + ) { + if preg.hw_enc() < 32 { + clobbers.push(preg); + } + } + } + builder.add_inst( Block::new(block), InstData { @@ -638,6 +653,22 @@ impl Func { Ok(builder.finalize()) } + + pub fn has_fixed_def_clobber(&self) -> bool { + self.insts.iter().any(|inst| { + inst.clobbers + .iter() + .any(|&preg| inst.operands.iter().any(has_fixed_def_with(preg))) + }) + } + + pub fn remove_fixed_def_clobbers(&mut self) { + for inst in &mut self.insts { + let operands = &inst.operands; + inst.clobbers + .retain(|&preg| !operands.iter().any(has_fixed_def_with(preg))); + } + } } impl core::fmt::Debug for Func { diff --git a/src/fuzzing/ion.rs b/src/fuzzing/ion.rs index e64b5a31..81167dc0 100644 --- a/src/fuzzing/ion.rs +++ b/src/fuzzing/ion.rs @@ -1,6 +1,6 @@ //! Fuzz the `ion` register allocator. -use crate::{checker, fuzzing::func, ion}; +use crate::{checker, fuzzing::func, ion, RegAllocError}; use arbitrary::{Arbitrary, Result, Unstructured}; use core::cell::RefCell; use std::thread_local; @@ -11,6 +11,7 @@ const OPTIONS: func::Options = func::Options { fixed_regs: true, fixed_nonallocatable: true, clobbers: true, + fixed_def_clobbers: true, reftypes: true, callsite_ish_constraints: true, ..func::Options::DEFAULT @@ -53,18 +54,38 @@ pub fn check(t: TestCase) { log::trace!("func:\n{func:?}"); let env = func::machine_env(); + let allocatable_func = if func.has_fixed_def_clobber() { + let mut allocatable_func = func.clone(); + allocatable_func.remove_fixed_def_clobbers(); + Some(allocatable_func) + } else { + None + }; + let valid_func = allocatable_func.as_ref().unwrap_or(func); thread_local! { // We test that ctx is cleared properly between runs. static CTX: RefCell = RefCell::default(); } CTX.with(|ctx| { - ion::run(func, &env, &mut *ctx.borrow_mut(), *annotate, *check_ssa) + let mut ctx = ctx.borrow_mut(); + ion::run(valid_func, &env, &mut ctx, *annotate, *check_ssa) .expect("regalloc did not succeed"); - let mut checker = checker::Checker::new(func, &env); - checker.prepare(&ctx.borrow().output); - checker.run().expect("checker failed"); + { + let mut checker = checker::Checker::new(valid_func, &env); + checker.prepare(&ctx.output); + checker.run().expect("checker failed"); + } + + if allocatable_func.is_some() { + let result = ion::run(func, &env, &mut ctx, *annotate, *check_ssa); + assert!( + matches!(result, Err(RegAllocError::TooManyLiveRegs)), + "expected TooManyLiveRegs for a fixed-def/clobber conflict, got {:?}", + result + ); + } }); } diff --git a/src/ion/process.rs b/src/ion/process.rs index a6e5db90..57b81e69 100644 --- a/src/ion/process.rs +++ b/src/ion/process.rs @@ -1214,6 +1214,16 @@ impl<'a, F: Function> Env<'a, F> { || lowest_cost_evict_conflict_cost.is_none() || lowest_cost_evict_conflict_cost.unwrap() >= our_spill_weight) { + // A minimal bundle pinned to one physical register cannot + // move, and a fixed reservation on that register (a clobber + // is modeled as one) cannot be evicted. The overlap is + // illegal: a clobber must not collide with a fixed def or + // late use. Reject it instead of panicking. + if matches!(req, Requirement::FixedReg(_)) + && lowest_cost_evict_conflict_cost.is_none() + { + return Err(RegAllocError::TooManyLiveRegs); + } if matches!(req, Requirement::Register | Requirement::Limit(_)) { // Check if this is a too-many-live-registers situation. let range = self.ctx.bundles[bundle].ranges[0].range;