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::Signature;
|
||||||
use cranelift_codegen::ir::UserExternalName;
|
use cranelift_codegen::ir::UserExternalName;
|
||||||
use cranelift_codegen::ir::UserFuncName;
|
use cranelift_codegen::ir::UserFuncName;
|
||||||
|
use cranelift_codegen::Context;
|
||||||
use libfuzzer_sys::arbitrary;
|
use libfuzzer_sys::arbitrary;
|
||||||
use libfuzzer_sys::arbitrary::Arbitrary;
|
use libfuzzer_sys::arbitrary::Arbitrary;
|
||||||
use libfuzzer_sys::arbitrary::Unstructured;
|
use libfuzzer_sys::arbitrary::Unstructured;
|
||||||
@@ -148,10 +149,15 @@ pub struct TestCase {
|
|||||||
/// Generate multiple test inputs for each test case.
|
/// Generate multiple test inputs for each test case.
|
||||||
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
|
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
|
||||||
pub inputs: Vec<TestCaseInput>,
|
pub inputs: Vec<TestCaseInput>,
|
||||||
|
/// Should this `TestCase` be tested after optimizations.
|
||||||
|
pub compare_against_host: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for TestCase {
|
impl fmt::Debug for TestCase {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
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)
|
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> {
|
pub fn generate(u: &mut Unstructured) -> anyhow::Result<Self> {
|
||||||
let mut gen = FuzzGen::new(u);
|
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
|
// TestCase is meant to be consumed by a runner, so we make the assumption here that we're
|
||||||
// generating a TargetIsa for the host.
|
// generating a TargetIsa for the host.
|
||||||
let builder =
|
let builder =
|
||||||
@@ -214,9 +222,29 @@ impl TestCase {
|
|||||||
isa,
|
isa,
|
||||||
functions,
|
functions,
|
||||||
inputs,
|
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.
|
/// Returns the main function of this test case.
|
||||||
pub fn main(&self) -> &Function {
|
pub fn main(&self) -> &Function {
|
||||||
&self.functions[0]
|
&self.functions[0]
|
||||||
@@ -279,21 +307,7 @@ fn build_interpreter(testcase: &TestCase) -> Interpreter {
|
|||||||
|
|
||||||
static STATISTICS: Lazy<Statistics> = Lazy::new(Statistics::default);
|
static STATISTICS: Lazy<Statistics> = Lazy::new(Statistics::default);
|
||||||
|
|
||||||
fuzz_target!(|testcase: TestCase| {
|
fn run_test_inputs(testcase: &TestCase, run: impl Fn(&[DataValue]) -> RunResult) {
|
||||||
// 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();
|
|
||||||
|
|
||||||
for args in &testcase.inputs {
|
for args in &testcase.inputs {
|
||||||
STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst);
|
STATISTICS.total_runs.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
@@ -325,12 +339,38 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_res = run_in_host(&trampoline, args);
|
let res = run(args);
|
||||||
match host_res {
|
|
||||||
RunResult::Success(_) => {}
|
|
||||||
_ => panic!("host failed: {:?}", host_res),
|
|
||||||
}
|
|
||||||
|
|
||||||
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