cranelift: Add LibCalls to the interpreter (#4782)
* cranelift: Add libcall handlers to interpreter * cranelift: Fuzz IshlI64 libcall * cranelift: Revert back to fuzzing udivi64 * cranelift: Use sdiv as a fuzz libcall * cranelift: Register Sdiv in fuzzgen * cranelift: Add multiple libcalls to fuzzer * cranelift: Register a single libcall handler * cranelift: Simplify args checking in interpreter * cranelift: Remove unused LibCalls * cranelift: Cleanup interpreter libcall types * cranelift: Fix Interpreter Docs
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3605,6 +3605,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-fuzzing",
|
"wasmtime-fuzzing",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Naming well-known routines in the runtime library.
|
//! Naming well-known routines in the runtime library.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ir::{types, AbiParam, ExternalName, FuncRef, Function, Opcode, Signature, Type},
|
ir::{types, AbiParam, ExternalName, FuncRef, Function, Signature},
|
||||||
isa::CallConv,
|
isa::CallConv,
|
||||||
};
|
};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
@@ -23,20 +23,6 @@ pub enum LibCall {
|
|||||||
/// probe for stack overflow. These are emitted for functions which need
|
/// probe for stack overflow. These are emitted for functions which need
|
||||||
/// when the `enable_probestack` setting is true.
|
/// when the `enable_probestack` setting is true.
|
||||||
Probestack,
|
Probestack,
|
||||||
/// udiv.i64
|
|
||||||
UdivI64,
|
|
||||||
/// sdiv.i64
|
|
||||||
SdivI64,
|
|
||||||
/// urem.i64
|
|
||||||
UremI64,
|
|
||||||
/// srem.i64
|
|
||||||
SremI64,
|
|
||||||
/// ishl.i64
|
|
||||||
IshlI64,
|
|
||||||
/// ushr.i64
|
|
||||||
UshrI64,
|
|
||||||
/// sshr.i64
|
|
||||||
SshrI64,
|
|
||||||
/// ceil.f32
|
/// ceil.f32
|
||||||
CeilF32,
|
CeilF32,
|
||||||
/// ceil.f64
|
/// ceil.f64
|
||||||
@@ -85,13 +71,6 @@ impl FromStr for LibCall {
|
|||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"Probestack" => Ok(Self::Probestack),
|
"Probestack" => Ok(Self::Probestack),
|
||||||
"UdivI64" => Ok(Self::UdivI64),
|
|
||||||
"SdivI64" => Ok(Self::SdivI64),
|
|
||||||
"UremI64" => Ok(Self::UremI64),
|
|
||||||
"SremI64" => Ok(Self::SremI64),
|
|
||||||
"IshlI64" => Ok(Self::IshlI64),
|
|
||||||
"UshrI64" => Ok(Self::UshrI64),
|
|
||||||
"SshrI64" => Ok(Self::SshrI64),
|
|
||||||
"CeilF32" => Ok(Self::CeilF32),
|
"CeilF32" => Ok(Self::CeilF32),
|
||||||
"CeilF64" => Ok(Self::CeilF64),
|
"CeilF64" => Ok(Self::CeilF64),
|
||||||
"FloorF32" => Ok(Self::FloorF32),
|
"FloorF32" => Ok(Self::FloorF32),
|
||||||
@@ -115,54 +94,11 @@ impl FromStr for LibCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LibCall {
|
impl LibCall {
|
||||||
/// Get the well-known library call name to use as a replacement for an instruction with the
|
|
||||||
/// given opcode and controlling type variable.
|
|
||||||
///
|
|
||||||
/// Returns `None` if no well-known library routine name exists for that instruction.
|
|
||||||
pub fn for_inst(opcode: Opcode, ctrl_type: Type) -> Option<Self> {
|
|
||||||
Some(match ctrl_type {
|
|
||||||
types::I64 => match opcode {
|
|
||||||
Opcode::Udiv => Self::UdivI64,
|
|
||||||
Opcode::Sdiv => Self::SdivI64,
|
|
||||||
Opcode::Urem => Self::UremI64,
|
|
||||||
Opcode::Srem => Self::SremI64,
|
|
||||||
Opcode::Ishl => Self::IshlI64,
|
|
||||||
Opcode::Ushr => Self::UshrI64,
|
|
||||||
Opcode::Sshr => Self::SshrI64,
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
types::F32 => match opcode {
|
|
||||||
Opcode::Ceil => Self::CeilF32,
|
|
||||||
Opcode::Floor => Self::FloorF32,
|
|
||||||
Opcode::Trunc => Self::TruncF32,
|
|
||||||
Opcode::Nearest => Self::NearestF32,
|
|
||||||
Opcode::Fma => Self::FmaF32,
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
types::F64 => match opcode {
|
|
||||||
Opcode::Ceil => Self::CeilF64,
|
|
||||||
Opcode::Floor => Self::FloorF64,
|
|
||||||
Opcode::Trunc => Self::TruncF64,
|
|
||||||
Opcode::Nearest => Self::NearestF64,
|
|
||||||
Opcode::Fma => Self::FmaF64,
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of all known `LibCall`'s.
|
/// Get a list of all known `LibCall`'s.
|
||||||
pub fn all_libcalls() -> &'static [LibCall] {
|
pub fn all_libcalls() -> &'static [LibCall] {
|
||||||
use LibCall::*;
|
use LibCall::*;
|
||||||
&[
|
&[
|
||||||
Probestack,
|
Probestack,
|
||||||
UdivI64,
|
|
||||||
SdivI64,
|
|
||||||
UremI64,
|
|
||||||
SremI64,
|
|
||||||
IshlI64,
|
|
||||||
UshrI64,
|
|
||||||
SshrI64,
|
|
||||||
CeilF32,
|
CeilF32,
|
||||||
CeilF64,
|
CeilF64,
|
||||||
FloorF32,
|
FloorF32,
|
||||||
@@ -188,17 +124,6 @@ impl LibCall {
|
|||||||
let mut sig = Signature::new(call_conv);
|
let mut sig = Signature::new(call_conv);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
LibCall::UdivI64
|
|
||||||
| LibCall::SdivI64
|
|
||||||
| LibCall::UremI64
|
|
||||||
| LibCall::SremI64
|
|
||||||
| LibCall::IshlI64
|
|
||||||
| LibCall::UshrI64
|
|
||||||
| LibCall::SshrI64 => {
|
|
||||||
sig.params.push(AbiParam::new(I64));
|
|
||||||
sig.params.push(AbiParam::new(I64));
|
|
||||||
sig.returns.push(AbiParam::new(I64));
|
|
||||||
}
|
|
||||||
LibCall::CeilF32 | LibCall::FloorF32 | LibCall::TruncF32 | LibCall::NearestF32 => {
|
LibCall::CeilF32 | LibCall::FloorF32 | LibCall::TruncF32 | LibCall::NearestF32 => {
|
||||||
sig.params.push(AbiParam::new(F32));
|
sig.params.push(AbiParam::new(F32));
|
||||||
sig.returns.push(AbiParam::new(F32));
|
sig.returns.push(AbiParam::new(F32));
|
||||||
|
|||||||
@@ -465,6 +465,16 @@ const OPCODE_SIGNATURES: &'static [(
|
|||||||
(Opcode::Call, &[], &[], insert_call),
|
(Opcode::Call, &[], &[], insert_call),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// These libcalls need a interpreter implementation in `cranelift-fuzzgen.rs`
|
||||||
|
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
|
||||||
|
LibCall::CeilF32,
|
||||||
|
LibCall::CeilF64,
|
||||||
|
LibCall::FloorF32,
|
||||||
|
LibCall::FloorF64,
|
||||||
|
LibCall::TruncF32,
|
||||||
|
LibCall::TruncF64,
|
||||||
|
];
|
||||||
|
|
||||||
pub struct FunctionGenerator<'r, 'data>
|
pub struct FunctionGenerator<'r, 'data>
|
||||||
where
|
where
|
||||||
'data: 'r,
|
'data: 'r,
|
||||||
@@ -506,6 +516,12 @@ where
|
|||||||
Ok(CallConv::SystemV)
|
Ok(CallConv::SystemV)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn system_callconv(&mut self) -> CallConv {
|
||||||
|
// TODO: This currently only runs on linux, so this is the only choice
|
||||||
|
// We should improve this once we generate flags and targets
|
||||||
|
CallConv::SystemV
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_type(&mut self) -> Result<Type> {
|
fn generate_type(&mut self) -> Result<Type> {
|
||||||
// TODO: It would be nice if we could get these directly from cranelift
|
// TODO: It would be nice if we could get these directly from cranelift
|
||||||
let scalars = [
|
let scalars = [
|
||||||
@@ -833,12 +849,11 @@ where
|
|||||||
let signature = self.generate_signature()?;
|
let signature = self.generate_signature()?;
|
||||||
(name, signature)
|
(name, signature)
|
||||||
} else {
|
} else {
|
||||||
// Use udivi64 as an example of a libcall function.
|
let libcall = *self.u.choose(ALLOWED_LIBCALLS)?;
|
||||||
let mut signature = Signature::new(CallConv::Fast);
|
// TODO: Use [CallConv::for_libcall] once we generate flags.
|
||||||
signature.params.push(AbiParam::new(I64));
|
let callconv = self.system_callconv();
|
||||||
signature.params.push(AbiParam::new(I64));
|
let signature = libcall.signature(callconv);
|
||||||
signature.returns.push(AbiParam::new(I64));
|
(ExternalName::LibCall(libcall), signature)
|
||||||
(ExternalName::LibCall(LibCall::UdivI64), signature)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let sig_ref = builder.import_signature(sig.clone());
|
let sig_ref = builder.import_signature(sig.clone());
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ use crate::value::{Value, ValueError};
|
|||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
||||||
use cranelift_codegen::ir::{
|
use cranelift_codegen::ir::{
|
||||||
ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, StackSlot, Type,
|
ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, LibCall,
|
||||||
Value as ValueRef,
|
StackSlot, TrapCode, Type, Value as ValueRef,
|
||||||
};
|
};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -191,9 +192,13 @@ pub enum HeapInit {
|
|||||||
FromBacking(HeapBacking),
|
FromBacking(HeapBacking),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type LibCallValues<V> = SmallVec<[V; 1]>;
|
||||||
|
pub type LibCallHandler<V> = fn(LibCall, LibCallValues<V>) -> Result<LibCallValues<V>, TrapCode>;
|
||||||
|
|
||||||
/// Maintains the [Interpreter]'s state, implementing the [State] trait.
|
/// Maintains the [Interpreter]'s state, implementing the [State] trait.
|
||||||
pub struct InterpreterState<'a> {
|
pub struct InterpreterState<'a> {
|
||||||
pub functions: FunctionStore<'a>,
|
pub functions: FunctionStore<'a>,
|
||||||
|
pub libcall_handler: LibCallHandler<DataValue>,
|
||||||
pub frame_stack: Vec<Frame<'a>>,
|
pub frame_stack: Vec<Frame<'a>>,
|
||||||
/// Number of bytes from the bottom of the stack where the current frame's stack space is
|
/// Number of bytes from the bottom of the stack where the current frame's stack space is
|
||||||
pub frame_offset: usize,
|
pub frame_offset: usize,
|
||||||
@@ -208,6 +213,7 @@ impl Default for InterpreterState<'_> {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
functions: FunctionStore::default(),
|
functions: FunctionStore::default(),
|
||||||
|
libcall_handler: |_, _| Err(TrapCode::UnreachableCodeReached),
|
||||||
frame_stack: vec![],
|
frame_stack: vec![],
|
||||||
frame_offset: 0,
|
frame_offset: 0,
|
||||||
stack: Vec::with_capacity(1024),
|
stack: Vec::with_capacity(1024),
|
||||||
@@ -224,6 +230,12 @@ impl<'a> InterpreterState<'a> {
|
|||||||
Self { functions, ..self }
|
Self { functions, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a libcall handler
|
||||||
|
pub fn with_libcall_handler(mut self, handler: LibCallHandler<DataValue>) -> Self {
|
||||||
|
self.libcall_handler = handler;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers a static heap and returns a reference to it
|
/// Registers a static heap and returns a reference to it
|
||||||
///
|
///
|
||||||
/// This heap reference can be used to generate a heap pointer, which
|
/// This heap reference can be used to generate a heap pointer, which
|
||||||
@@ -301,6 +313,10 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
|||||||
self.current_frame().function
|
self.current_frame().function
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_libcall_handler(&self) -> LibCallHandler<DataValue> {
|
||||||
|
self.libcall_handler
|
||||||
|
}
|
||||||
|
|
||||||
fn push_frame(&mut self, function: &'a Function) {
|
fn push_frame(&mut self, function: &'a Function) {
|
||||||
if let Some(frame) = self.frame_stack.iter().last() {
|
if let Some(frame) = self.frame_stack.iter().last() {
|
||||||
self.frame_offset += frame.function.fixed_stack_size() as usize;
|
self.frame_offset += frame.function.fixed_stack_size() as usize;
|
||||||
@@ -612,9 +628,11 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::step::CraneliftTrap;
|
use crate::step::CraneliftTrap;
|
||||||
|
use cranelift_codegen::ir::immediates::Ieee32;
|
||||||
use cranelift_codegen::ir::types::I64;
|
use cranelift_codegen::ir::types::I64;
|
||||||
use cranelift_codegen::ir::TrapCode;
|
use cranelift_codegen::ir::TrapCode;
|
||||||
use cranelift_reader::parse_functions;
|
use cranelift_reader::parse_functions;
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
// Most interpreter tests should use the more ergonomic `test interpret` filetest but this
|
// Most interpreter tests should use the more ergonomic `test interpret` filetest but this
|
||||||
// unit test serves as a sanity check that the interpreter still works without all of the
|
// unit test serves as a sanity check that the interpreter still works without all of the
|
||||||
@@ -1045,4 +1063,34 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trap, CraneliftTrap::User(TrapCode::IntegerOverflow));
|
assert_eq!(trap, CraneliftTrap::User(TrapCode::IntegerOverflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn libcall() {
|
||||||
|
let code = "function %test() -> i64 {
|
||||||
|
fn0 = colocated %CeilF32 (f32) -> f32 fast
|
||||||
|
block0:
|
||||||
|
v1 = f32const 0x0.5
|
||||||
|
v2 = call fn0(v1)
|
||||||
|
return v2
|
||||||
|
}";
|
||||||
|
|
||||||
|
let func = parse_functions(code).unwrap().into_iter().next().unwrap();
|
||||||
|
let mut env = FunctionStore::default();
|
||||||
|
env.add(func.name.to_string(), &func);
|
||||||
|
let state = InterpreterState::default()
|
||||||
|
.with_function_store(env)
|
||||||
|
.with_libcall_handler(|libcall, args| {
|
||||||
|
Ok(smallvec![match (libcall, &args[..]) {
|
||||||
|
(LibCall::CeilF32, [DataValue::F32(a)]) => DataValue::F32(a.ceil()),
|
||||||
|
_ => panic!("Unexpected args"),
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = Interpreter::new(state)
|
||||||
|
.call_by_name("%test", &[])
|
||||||
|
.unwrap()
|
||||||
|
.unwrap_return();
|
||||||
|
|
||||||
|
assert_eq!(result, vec![DataValue::F32(Ieee32::with_float(1.0))])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Cranelift instructions modify the state of the machine; the [State] trait describes these
|
//! Cranelift instructions modify the state of the machine; the [State] trait describes these
|
||||||
//! ways this can happen.
|
//! ways this can happen.
|
||||||
use crate::address::{Address, AddressSize};
|
use crate::address::{Address, AddressSize};
|
||||||
|
use crate::interpreter::LibCallHandler;
|
||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
||||||
use cranelift_codegen::ir::{FuncRef, Function, GlobalValue, Heap, StackSlot, Type, Value};
|
use cranelift_codegen::ir::{FuncRef, Function, GlobalValue, Heap, StackSlot, Type, Value};
|
||||||
@@ -23,6 +24,8 @@ pub trait State<'a, V> {
|
|||||||
fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function>;
|
fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function>;
|
||||||
/// Retrieve a reference to the currently executing [Function].
|
/// Retrieve a reference to the currently executing [Function].
|
||||||
fn get_current_function(&self) -> &'a Function;
|
fn get_current_function(&self) -> &'a Function;
|
||||||
|
/// Retrieve the handler callback for a [LibCall](cranelift_codegen::ir::LibCall)
|
||||||
|
fn get_libcall_handler(&self) -> LibCallHandler<V>;
|
||||||
/// Record that an interpreter has called into a new [Function].
|
/// Record that an interpreter has called into a new [Function].
|
||||||
fn push_frame(&mut self, function: &'a Function);
|
fn push_frame(&mut self, function: &'a Function);
|
||||||
/// Record that an interpreter has returned from a called [Function].
|
/// Record that an interpreter has returned from a called [Function].
|
||||||
@@ -129,6 +132,10 @@ where
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_libcall_handler(&self) -> LibCallHandler<V> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn push_frame(&mut self, _function: &'a Function) {
|
fn push_frame(&mut self, _function: &'a Function) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
|
|||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
|
||||||
use cranelift_codegen::ir::{
|
use cranelift_codegen::ir::{
|
||||||
types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Type, Value as ValueRef,
|
types, AbiParam, Block, ExternalName, FuncRef, Function, InstructionData, Opcode, TrapCode,
|
||||||
|
Type, Value as ValueRef,
|
||||||
};
|
};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -16,6 +17,14 @@ use std::fmt::Debug;
|
|||||||
use std::ops::RangeFrom;
|
use std::ops::RangeFrom;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Ensures that all types in args are the same as expected by the signature
|
||||||
|
fn validate_signature_params(sig: &[AbiParam], args: &[impl Value]) -> bool {
|
||||||
|
args.iter()
|
||||||
|
.map(|r| r.ty())
|
||||||
|
.zip(sig.iter().map(|r| r.value_type))
|
||||||
|
.all(|(a, b)| a == b)
|
||||||
|
}
|
||||||
|
|
||||||
/// Interpret a single Cranelift instruction. Note that program traps and interpreter errors are
|
/// Interpret a single Cranelift instruction. Note that program traps and interpreter errors are
|
||||||
/// distinct: a program trap results in `Ok(Flow::Trap(...))` whereas an interpretation error (e.g.
|
/// distinct: a program trap results in `Ok(Flow::Trap(...))` whereas an interpretation error (e.g.
|
||||||
/// the types of two values are incompatible) results in `Err(...)`.
|
/// the types of two values are incompatible) results in `Err(...)`.
|
||||||
@@ -25,7 +34,7 @@ pub fn step<'a, V, I>(
|
|||||||
inst_context: I,
|
inst_context: I,
|
||||||
) -> Result<ControlFlow<'a, V>, StepError>
|
) -> Result<ControlFlow<'a, V>, StepError>
|
||||||
where
|
where
|
||||||
V: Value,
|
V: Value + Debug,
|
||||||
I: InstructionContext,
|
I: InstructionContext,
|
||||||
{
|
{
|
||||||
let inst = inst_context.data();
|
let inst = inst_context.data();
|
||||||
@@ -295,13 +304,65 @@ where
|
|||||||
),
|
),
|
||||||
Opcode::Return => ControlFlow::Return(args()?),
|
Opcode::Return => ControlFlow::Return(args()?),
|
||||||
Opcode::Call => {
|
Opcode::Call => {
|
||||||
if let InstructionData::Call { func_ref, .. } = inst {
|
let func_ref = if let InstructionData::Call { func_ref, .. } = inst {
|
||||||
let function = state
|
func_ref
|
||||||
.get_function(func_ref)
|
|
||||||
.ok_or(StepError::UnknownFunction(func_ref))?;
|
|
||||||
ControlFlow::Call(function, args()?)
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let curr_func = state.get_current_function();
|
||||||
|
let ext_data = curr_func
|
||||||
|
.dfg
|
||||||
|
.ext_funcs
|
||||||
|
.get(func_ref)
|
||||||
|
.ok_or(StepError::UnknownFunction(func_ref))?;
|
||||||
|
|
||||||
|
let signature = if let Some(sig) = curr_func.dfg.signatures.get(ext_data.signature) {
|
||||||
|
sig
|
||||||
|
} else {
|
||||||
|
return Ok(ControlFlow::Trap(CraneliftTrap::User(
|
||||||
|
TrapCode::BadSignature,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = args()?;
|
||||||
|
|
||||||
|
// Check the types of the arguments. This is usually done by the verifier, but nothing
|
||||||
|
// guarantees that the user has ran that.
|
||||||
|
let args_match = validate_signature_params(&signature.params[..], &args[..]);
|
||||||
|
if !args_match {
|
||||||
|
return Ok(ControlFlow::Trap(CraneliftTrap::User(
|
||||||
|
TrapCode::BadSignature,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ext_data.name {
|
||||||
|
// These functions should be registered in the regular function store
|
||||||
|
ExternalName::User(_) | ExternalName::TestCase(_) => {
|
||||||
|
let function = state
|
||||||
|
.get_function(func_ref)
|
||||||
|
.ok_or(StepError::UnknownFunction(func_ref))?;
|
||||||
|
|
||||||
|
ControlFlow::Call(function, args)
|
||||||
|
}
|
||||||
|
ExternalName::LibCall(libcall) => {
|
||||||
|
let libcall_handler = state.get_libcall_handler();
|
||||||
|
|
||||||
|
// We don't transfer control to a libcall, we just execute it and return the results
|
||||||
|
let res = libcall_handler(libcall, args);
|
||||||
|
let res = match res {
|
||||||
|
Err(trap) => return Ok(ControlFlow::Trap(CraneliftTrap::User(trap))),
|
||||||
|
Ok(rets) => rets,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that what the handler returned is what we expect.
|
||||||
|
if validate_signature_params(&signature.returns[..], &res[..]) {
|
||||||
|
ControlFlow::Assign(res)
|
||||||
|
} else {
|
||||||
|
ControlFlow::Trap(CraneliftTrap::User(TrapCode::BadSignature))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExternalName::KnownSymbol(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::CallIndirect => unimplemented!("CallIndirect"),
|
Opcode::CallIndirect => unimplemented!("CallIndirect"),
|
||||||
|
|||||||
@@ -55,13 +55,6 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|||||||
pub fn default_libcall_names() -> Box<dyn Fn(ir::LibCall) -> String + Send + Sync> {
|
pub fn default_libcall_names() -> Box<dyn Fn(ir::LibCall) -> String + Send + Sync> {
|
||||||
Box::new(move |libcall| match libcall {
|
Box::new(move |libcall| match libcall {
|
||||||
ir::LibCall::Probestack => "__cranelift_probestack".to_owned(),
|
ir::LibCall::Probestack => "__cranelift_probestack".to_owned(),
|
||||||
ir::LibCall::UdivI64 => "__udivdi3".to_owned(),
|
|
||||||
ir::LibCall::SdivI64 => "__divdi3".to_owned(),
|
|
||||||
ir::LibCall::UremI64 => "__umoddi3".to_owned(),
|
|
||||||
ir::LibCall::SremI64 => "__moddi3".to_owned(),
|
|
||||||
ir::LibCall::IshlI64 => "__ashldi3".to_owned(),
|
|
||||||
ir::LibCall::UshrI64 => "__lshrdi3".to_owned(),
|
|
||||||
ir::LibCall::SshrI64 => "__ashrdi3".to_owned(),
|
|
||||||
ir::LibCall::CeilF32 => "ceilf".to_owned(),
|
ir::LibCall::CeilF32 => "ceilf".to_owned(),
|
||||||
ir::LibCall::CeilF64 => "ceil".to_owned(),
|
ir::LibCall::CeilF64 => "ceil".to_owned(),
|
||||||
ir::LibCall::FloorF32 => "floorf".to_owned(),
|
ir::LibCall::FloorF32 => "floorf".to_owned(),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ cranelift-interpreter = { path = "../cranelift/interpreter" }
|
|||||||
cranelift-fuzzgen = { path = "../cranelift/fuzzgen" }
|
cranelift-fuzzgen = { path = "../cranelift/fuzzgen" }
|
||||||
libfuzzer-sys = "0.4.0"
|
libfuzzer-sys = "0.4.0"
|
||||||
target-lexicon = "0.12"
|
target-lexicon = "0.12"
|
||||||
|
smallvec = "1.6.1"
|
||||||
wasmtime = { path = "../crates/wasmtime" }
|
wasmtime = { path = "../crates/wasmtime" }
|
||||||
wasmtime-fuzzing = { path = "../crates/fuzzing" }
|
wasmtime-fuzzing = { path = "../crates/fuzzing" }
|
||||||
component-test-util = { path = "../crates/misc/component-test-util" }
|
component-test-util = { path = "../crates/misc/component-test-util" }
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
|
use cranelift_codegen::ir::LibCall;
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline};
|
use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline};
|
||||||
use cranelift_fuzzgen::*;
|
use cranelift_fuzzgen::*;
|
||||||
use cranelift_interpreter::environment::FuncIndex;
|
use cranelift_interpreter::environment::FuncIndex;
|
||||||
use cranelift_interpreter::environment::FunctionStore;
|
use cranelift_interpreter::environment::FunctionStore;
|
||||||
use cranelift_interpreter::interpreter::{Interpreter, InterpreterError, InterpreterState};
|
use cranelift_interpreter::interpreter::{
|
||||||
|
Interpreter, InterpreterError, InterpreterState, LibCallValues,
|
||||||
|
};
|
||||||
use cranelift_interpreter::step::ControlFlow;
|
use cranelift_interpreter::step::ControlFlow;
|
||||||
use cranelift_interpreter::step::CraneliftTrap;
|
use cranelift_interpreter::step::CraneliftTrap;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
const INTERPRETER_FUEL: u64 = 4096;
|
const INTERPRETER_FUEL: u64 = 4096;
|
||||||
|
|
||||||
@@ -51,16 +55,30 @@ fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult {
|
|||||||
RunResult::Success(res)
|
RunResult::Success(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_interpreter(testcase: &TestCase) -> Interpreter {
|
||||||
|
let mut env = FunctionStore::default();
|
||||||
|
env.add(testcase.func.name.to_string(), &testcase.func);
|
||||||
|
|
||||||
|
let state = InterpreterState::default()
|
||||||
|
.with_function_store(env)
|
||||||
|
.with_libcall_handler(|libcall: LibCall, args: LibCallValues<DataValue>| {
|
||||||
|
use LibCall::*;
|
||||||
|
Ok(smallvec![match (libcall, &args[..]) {
|
||||||
|
(CeilF32, [DataValue::F32(a)]) => DataValue::F32(a.ceil()),
|
||||||
|
(CeilF64, [DataValue::F64(a)]) => DataValue::F64(a.ceil()),
|
||||||
|
(FloorF32, [DataValue::F32(a)]) => DataValue::F32(a.floor()),
|
||||||
|
(FloorF64, [DataValue::F64(a)]) => DataValue::F64(a.floor()),
|
||||||
|
(TruncF32, [DataValue::F32(a)]) => DataValue::F32(a.trunc()),
|
||||||
|
(TruncF64, [DataValue::F64(a)]) => DataValue::F64(a.trunc()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
|
||||||
|
let interpreter = Interpreter::new(state).with_fuel(Some(INTERPRETER_FUEL));
|
||||||
|
interpreter
|
||||||
|
}
|
||||||
|
|
||||||
fuzz_target!(|testcase: TestCase| {
|
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
|
// Native fn
|
||||||
let flags = {
|
let flags = {
|
||||||
let mut builder = settings::builder();
|
let mut builder = settings::builder();
|
||||||
@@ -80,7 +98,7 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
for args in &testcase.inputs {
|
for args in &testcase.inputs {
|
||||||
// We rebuild the interpreter every run so that we don't accidentally carry over any state
|
// We rebuild the interpreter every run so that we don't accidentally carry over any state
|
||||||
// between runs, such as fuel remaining.
|
// between runs, such as fuel remaining.
|
||||||
let mut interpreter = build_interpreter();
|
let mut interpreter = build_interpreter(&testcase);
|
||||||
let int_res = run_in_interpreter(&mut interpreter, args);
|
let int_res = run_in_interpreter(&mut interpreter, args);
|
||||||
match int_res {
|
match int_res {
|
||||||
RunResult::Success(_) => {}
|
RunResult::Success(_) => {}
|
||||||
|
|||||||
Reference in New Issue
Block a user