diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs index c6a008bd76..b20755509d 100644 --- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs +++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs @@ -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, + /// 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 { 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 = 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 = 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)); } });