* cranelift: Use JIT in runtests Using `cranelift-jit` in run tests allows us to preform relocations and libcalls. This is important since some instruction lowerings fallback to libcall's when an extension is missing, or when it's too complicated to implement manually. This is also a first step to being able to test `call`'s between functions in the runtest suite. It should also make it easier to eventually test TLS relocations, symbol resolution and ABI's. Another benefit of this is that we also get to test the JIT more, since it now runs the runtests, and gets some fuzzing via `fuzzgen` (which uses the `SingleFunctionCompiler`). This change causes regressions in terms of runtime for the filetests. I haven't done any serious benchmarking but what I've been seeing is that it now takes about ~3 seconds to run the testsuite while it previously took around 2 seconds. * Add FMA tests for X86
105 lines
3.7 KiB
Rust
105 lines
3.7 KiB
Rust
#![no_main]
|
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
use cranelift_codegen::data_value::DataValue;
|
|
use cranelift_codegen::settings;
|
|
use cranelift_codegen::settings::Configurable;
|
|
use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompiler};
|
|
use cranelift_fuzzgen::*;
|
|
use cranelift_interpreter::environment::FuncIndex;
|
|
use cranelift_interpreter::environment::FunctionStore;
|
|
use cranelift_interpreter::interpreter::{Interpreter, InterpreterError, InterpreterState};
|
|
use cranelift_interpreter::step::ControlFlow;
|
|
use cranelift_interpreter::step::CraneliftTrap;
|
|
|
|
const INTERPRETER_FUEL: u64 = 4096;
|
|
|
|
#[derive(Debug)]
|
|
enum RunResult {
|
|
Success(Vec<DataValue>),
|
|
Trap(CraneliftTrap),
|
|
Timeout,
|
|
Error(Box<dyn std::error::Error>),
|
|
}
|
|
|
|
impl RunResult {
|
|
pub fn unwrap(self) -> Vec<DataValue> {
|
|
match self {
|
|
RunResult::Success(d) => d,
|
|
_ => panic!("Expected RunResult::Success in unwrap but got: {:?}", self),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult {
|
|
// The entrypoint function is always 0
|
|
let index = FuncIndex::from_u32(0);
|
|
let res = interpreter.call_by_index(index, args);
|
|
|
|
match res {
|
|
Ok(ControlFlow::Return(results)) => RunResult::Success(results.to_vec()),
|
|
Ok(ControlFlow::Trap(trap)) => RunResult::Trap(trap),
|
|
Ok(cf) => RunResult::Error(format!("Unrecognized exit ControlFlow: {:?}", cf).into()),
|
|
Err(InterpreterError::FuelExhausted) => RunResult::Timeout,
|
|
Err(e) => RunResult::Error(e.into()),
|
|
}
|
|
}
|
|
|
|
fn run_in_host(compiled_fn: &CompiledFunction, args: &[DataValue]) -> RunResult {
|
|
let res = compiled_fn.call(args);
|
|
RunResult::Success(res)
|
|
}
|
|
|
|
fuzz_target!(|testcase: TestCase| {
|
|
let build_interpreter = || {
|
|
let mut env = FunctionStore::default();
|
|
env.add(testcase.func.name.to_string(), &testcase.func);
|
|
|
|
let state = InterpreterState::default().with_function_store(env);
|
|
let interpreter = Interpreter::new(state).with_fuel(Some(INTERPRETER_FUEL));
|
|
interpreter
|
|
};
|
|
|
|
// Native fn
|
|
let flags = {
|
|
let mut builder = settings::builder();
|
|
// We need llvm ABI extensions for i128 values on x86
|
|
builder.set("enable_llvm_abi_extensions", "true").unwrap();
|
|
settings::Flags::new(builder)
|
|
};
|
|
let host_compiler = SingleFunctionCompiler::with_host_isa(flags).unwrap();
|
|
let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap();
|
|
|
|
for args in &testcase.inputs {
|
|
// 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();
|
|
let int_res = run_in_interpreter(&mut interpreter, args);
|
|
match int_res {
|
|
RunResult::Success(_) => {}
|
|
RunResult::Trap(_) => {
|
|
// We currently ignore inputs that trap the interpreter
|
|
// We could catch traps in the host run and compare them to the
|
|
// interpreter traps, but since we already test trap cases with
|
|
// wasm tests and wasm-level fuzzing, the amount of effort does
|
|
// not justify implementing it again here.
|
|
return;
|
|
}
|
|
RunResult::Timeout => {
|
|
// We probably generated an infinite loop, we can ignore this
|
|
return;
|
|
}
|
|
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
|
}
|
|
|
|
let host_res = run_in_host(&compiled_fn, args);
|
|
match host_res {
|
|
RunResult::Success(_) => {}
|
|
_ => panic!("host failed: {:?}", host_res),
|
|
}
|
|
|
|
assert_eq!(int_res.unwrap(), host_res.unwrap());
|
|
}
|
|
});
|