cranelift: Fuzz mid-end optimizations (#5998)

This commit is contained in:
ghostway0
2023-03-17 02:08:00 +02:00
committed by GitHub
parent bf212b767b
commit a66b3e1ab6

View File

@@ -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));
}
});