Skip to content

Commit 4a23344

Browse files
agourakis82claude
andauthored
ion: reject a fixed def that conflicts with a clobber (#266)
Fixes #222. A clobber is a fixed late def of a throwaway vreg. When it overlaps a fixed definition in a minimal bundle, allocation is impossible: neither constraint can be moved or evicted. Return `RegAllocError::TooManyLiveRegs` instead of panicking. Replaced the verbose regression test with coverage in the existing ion fuzzer, as requested. The generator can now emit late fixed-def/clobber collisions. The harness checks allocation and output on a copy with the collisions removed, then requires `TooManyLiveRegs` for the original input. Other fuzzers retain the default generator behavior. Validation: - All six CI checks passed at `1e77724`: build, features, lint, no_std, fuzz, and Cargo deny. - 14 tests passed with `cargo test --all --all-features`; all-features and no-default-features checks passed. - `cargo fmt --all -- --check` passed. - All four targets built with `cargo +nightly fuzz build`. - Targeted ion campaign on the 5860: eight workers, 24 hours, 61,027,823 executions, all workers exited zero, no new failure artifacts. - With the allocator fix reverted, mutation found the original panic. The same artifact passed with the fix and passed the campaign's preflight. Campaign provenance note: the delivered executable had a truncated non-loaded tail. Byte comparison against the full build confirmed that every file-backed program segment was present and identical. The source diff matches the recorded campaign payload. The execution count is not a count of unique programs or exhaustive coverage. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2fe490b commit 4a23344

3 files changed

Lines changed: 60 additions & 7 deletions

File tree

‎src/fuzzing/func.rs‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use crate::{
77
domtree, postorder, Allocation, Block, Function, Inst, InstRange, MachineEnv, Operand,
8-
OperandConstraint, OperandKind, OperandPos, PReg, PRegSet, RegClass, VReg,
8+
OperandConstraint, OperandKind, OperandPos, PReg, PRegSet, RegAllocError, RegClass, VReg,
99
};
1010

1111
use alloc::vec::Vec;
@@ -367,6 +367,7 @@ pub struct Options {
367367
pub fixed_regs: bool,
368368
pub fixed_nonallocatable: bool,
369369
pub clobbers: bool,
370+
pub fixed_def_clobbers: bool,
370371
pub reftypes: bool,
371372
pub callsite_ish_constraints: bool,
372373
pub num_blocks: RangeInclusive<usize>,
@@ -383,6 +384,7 @@ impl Options {
383384
fixed_regs: false,
384385
fixed_nonallocatable: false,
385386
clobbers: false,
387+
fixed_def_clobbers: false,
386388
reftypes: false,
387389
callsite_ish_constraints: false,
388390
num_blocks: 1..=100,
@@ -582,6 +584,22 @@ impl Func {
582584
)));
583585
}
584586

587+
if opts.fixed_def_clobbers && bool::arbitrary(u)? {
588+
// Exercise an impossible fixed output, not just allocatable functions.
589+
for operand in &operands {
590+
if let (
591+
OperandKind::Def,
592+
OperandPos::Late,
593+
OperandConstraint::FixedReg(preg),
594+
) = (operand.kind(), operand.pos(), operand.constraint())
595+
{
596+
if preg.hw_enc() < 32 {
597+
clobbers.push(preg);
598+
}
599+
}
600+
}
601+
}
602+
585603
builder.add_inst(
586604
Block::new(block),
587605
InstData {
@@ -638,6 +656,20 @@ impl Func {
638656

639657
Ok(builder.finalize())
640658
}
659+
660+
/// An allocation error implied by the generated function's constraints.
661+
pub fn expected_fail(&self) -> Option<RegAllocError> {
662+
let fixed_def_clobber = self.insts.iter().any(|inst| {
663+
inst.clobbers
664+
.iter()
665+
.any(|&preg| inst.operands.iter().any(has_fixed_def_with(preg)))
666+
});
667+
if fixed_def_clobber {
668+
Some(RegAllocError::TooManyLiveRegs)
669+
} else {
670+
None
671+
}
672+
}
641673
}
642674

643675
impl core::fmt::Debug for Func {

‎src/fuzzing/ion.rs‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const OPTIONS: func::Options = func::Options {
1111
fixed_regs: true,
1212
fixed_nonallocatable: true,
1313
clobbers: true,
14+
fixed_def_clobbers: true,
1415
reftypes: true,
1516
callsite_ish_constraints: true,
1617
..func::Options::DEFAULT
@@ -59,12 +60,22 @@ pub fn check(t: TestCase) {
5960
}
6061

6162
CTX.with(|ctx| {
62-
ion::run(func, &env, &mut *ctx.borrow_mut(), *annotate, *check_ssa)
63-
.expect("regalloc did not succeed");
64-
65-
let mut checker = checker::Checker::new(func, &env);
66-
checker.prepare(&ctx.borrow().output);
67-
checker.run().expect("checker failed");
63+
let mut ctx = ctx.borrow_mut();
64+
let result = ion::run(func, &env, &mut ctx, *annotate, *check_ssa);
65+
if let Some(expected) = func.expected_fail() {
66+
// The oracle currently returns only the payload-free TooManyLiveRegs variant.
67+
assert!(
68+
matches!(&result, Err(actual) if core::mem::discriminant(actual) == core::mem::discriminant(&expected)),
69+
"expected {:?}, got {:?}",
70+
expected,
71+
result
72+
);
73+
} else {
74+
result.expect("regalloc did not succeed");
75+
let mut checker = checker::Checker::new(func, &env);
76+
checker.prepare(&ctx.output);
77+
checker.run().expect("checker failed");
78+
}
6879
});
6980
}
7081

‎src/ion/process.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,16 @@ impl<'a, F: Function> Env<'a, F> {
12141214
|| lowest_cost_evict_conflict_cost.is_none()
12151215
|| lowest_cost_evict_conflict_cost.unwrap() >= our_spill_weight)
12161216
{
1217+
// A minimal bundle pinned to one physical register cannot
1218+
// move, and a fixed reservation on that register (a clobber
1219+
// is modeled as one) cannot be evicted. The overlap is
1220+
// illegal: a clobber must not collide with a fixed def or
1221+
// late use. Reject it instead of panicking.
1222+
if matches!(req, Requirement::FixedReg(_))
1223+
&& lowest_cost_evict_conflict_cost.is_none()
1224+
{
1225+
return Err(RegAllocError::TooManyLiveRegs);
1226+
}
12171227
if matches!(req, Requirement::Register | Requirement::Limit(_)) {
12181228
// Check if this is a too-many-live-registers situation.
12191229
let range = self.ctx.bundles[bundle].ranges[0].range;

0 commit comments

Comments
 (0)