cranelift: Fuzz mid-end optimizations (#5998)
This commit is contained in:
@@ -4,6 +4,7 @@ use cranelift_codegen::ir::Function;
|
||||
use cranelift_codegen::ir::Signature;
|
||||
use cranelift_codegen::ir::UserExternalName;
|
||||
use cranelift_codegen::ir::UserFuncName;
|
||||
use cranelift_codegen::Context;
|
||||
use libfuzzer_sys::arbitrary;
|
||||
use libfuzzer_sys::arbitrary::Arbitrary;
|
||||
use libfuzzer_sys::arbitrary::Unstructured;
|
||||
@@ -148,10 +149,15 @@ pub struct TestCase {
|
||||
/// Generate multiple test inputs for each test case.
|
||||
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
|
||||
pub inputs: Vec<TestCaseInput>,
|
||||
/// Should this `TestCase` be tested after optimizations.
|
||||
pub compare_against_host: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TestCase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.compare_against_host {
|
||||
writeln!(f, ";; Testing against optimized version")?;
|
||||
}
|
||||
PrintableTestCase::run(&self.isa, &self.functions, &self.inputs).fmt(f)
|
||||
}
|
||||
}
|
||||
@@ -169,6 +175,8 @@ impl TestCase {
|
||||
pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
|
||||
let mut gen = FuzzGen::new(u);
|
||||
|
||||
let compare_against_host = gen.u.arbitrary()?;
|
||||
|
||||
// TestCase is meant to be consumed by a runner, so we make the assumption here that we're
|
||||
// generating a TargetIsa for the host.
|
||||
let builder =
|
||||
@@ -214,9 +222,29 @@ impl TestCase {
|
||||
isa,
|
||||
functions,
|
||||
inputs,
|
||||
compare_against_host,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_optimized(&self) -> Self {
|
||||
let optimized_functions: Vec<Function> = self
|
||||
.functions
|
||||
.iter()
|
||||
.map(|func| {
|
||||
let mut ctx = Context::for_function(func.clone());
|
||||
ctx.optimize(self.isa.as_ref()).unwrap();
|
||||
ctx.func
|
||||
})
|
||||
.collect();
|
||||
|
||||
TestCase {
|
||||
isa: self.isa.clone(),
|
||||
functions: optimized_functions,
|
||||
inputs: self.inputs.clone(),
|
||||
compare_against_host: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the main function of this test case.
|
||||
pub fn main(&self) -> &Function {
|
||||
&self.functions[0]
|
||||
@@ -279,21 +307,7 @@ fn build_interpreter(testcase: &TestCase) -> Interpreter {
|
||||
|
||||
static STATISTICS: Lazy<Statistics> = Lazy::new(Statistics::default);
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
// This is the default, but we should ensure that it wasn't accidentally turned off anywhere.
|
||||
assert!(testcase.isa.flags().enable_verifier());
|
||||
|
||||
// Periodically print statistics
|
||||
let valid_inputs = STATISTICS.valid_inputs.fetch_add(1, Ordering::SeqCst);
|
||||
if valid_inputs != 0 && valid_inputs % 10000 == 0 {
|
||||
STATISTICS.print(valid_inputs);
|
||||
}
|
||||
|
||||
let mut compiler = TestFileCompiler::new(testcase.isa.clone());
|
||||
compiler.add_functions(&testcase.functions[..]).unwrap();
|
||||
let compiled = compiler.compile().unwrap();
|
||||
let trampoline = compiled.get_trampoline(testcase.main()).unwrap();
|
||||
|
||||
fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult) {
|
||||
for args in &testcase.inputs {
|
||||
STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
@@ -325,12 +339,38 @@ fuzz_target!(|testcase: TestCase| {
|
||||
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
||||
}
|
||||
|
||||
let host_res = run_in_host(&trampoline, args);
|
||||
match host_res {
|
||||
RunResult::Success(_) => {}
|
||||
_ => panic!("host failed: {:?}", host_res),
|
||||
}
|
||||
let res = run(args);
|
||||
|
||||
assert_eq!(int_res, host_res);
|
||||
assert_eq!(int_res, res);
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
// This is the default, but we should ensure that it wasn't accidentally turned off anywhere.
|
||||
assert!(testcase.isa.flags().enable_verifier());
|
||||
|
||||
// Periodically print statistics
|
||||
let valid_inputs = STATISTICS.valid_inputs.fetch_add(1, Ordering::SeqCst);
|
||||
if valid_inputs != 0 && valid_inputs % 10000 == 0 {
|
||||
STATISTICS.print(valid_inputs);
|
||||
}
|
||||
|
||||
if !testcase.compare_against_host {
|
||||
let opt_testcase = testcase.to_optimized();
|
||||
|
||||
run_test_inputs(&testcase, |args| {
|
||||
// We rebuild the interpreter every run so that we don't accidentally carry over any state
|
||||
// between runs, such as fuel remaining.
|
||||
let mut interpreter = build_interpreter(&opt_testcase);
|
||||
|
||||
run_in_interpreter(&mut interpreter, args)
|
||||
});
|
||||
} else {
|
||||
let mut compiler = TestFileCompiler::new(testcase.isa.clone());
|
||||
compiler.add_functions(&testcase.functions[..]).unwrap();
|
||||
let compiled = compiler.compile().unwrap();
|
||||
let trampoline = compiled.get_trampoline(testcase.main()).unwrap();
|
||||
|
||||
run_test_inputs(&testcase, |args| run_in_host(&trampoline, args));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user