-
Notifications
You must be signed in to change notification settings - Fork 54
ion: reject a fixed def that conflicts with a clobber #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is changing the fuzzer toplevel in a somewhat nontrivial way -- it's inventing a notion of (what I'd call) "differential feasibility" and doing allocation on two different versions of a function. I'm not sure I like introducing this complexity; I'd rather that we keep the simple logic of "create an arbitrary function, run the algorithm, check for expected result". In the case of an explicitly introduced fixed-def + clobber, I'd expect that our fuzzing oracle would say "not allocatable", and we'd assert that we get that back out. I also don't like that we spread logic between the toplevel driver here, which is supposed to be simple, and the function generator itself. Perhaps have a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My reason for checking the copy without the conflicting clobbers was to keep exercising successful allocation and output checking alongside rejection of the conflicting input. I see your point about the extra complexity in the driver. Your expected_fail() approach makes sense to me. With that change, I want to make sure the generator still exercises both valid fixed-register constraints and fixed-def/clobber collisions, so we retain coverage of successful allocation as well as the expected error. |
||
| 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<ion::Ctx> = 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 | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only operand 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used operand 0 because the generator starts each instruction with a def and appends uses after it. The additional defs in the callsite-like path are created with any_def, so they don't have fixed-register constraints.
That makes operand 0 the candidate here, but iterating over all operands and selecting late fixed defs would express the intent more directly. I'll change it to do that.