diff --git a/Cargo.lock b/Cargo.lock index bf4830f6ebcf..936c14ade413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3605,6 +3605,7 @@ dependencies = [ "proc-macro2", "quote", "rand 0.8.5", + "smallvec", "target-lexicon", "wasmtime", "wasmtime-fuzzing", diff --git a/cranelift/codegen/src/ir/libcall.rs b/cranelift/codegen/src/ir/libcall.rs index e7ef344bb31f..6734258ed735 100644 --- a/cranelift/codegen/src/ir/libcall.rs +++ b/cranelift/codegen/src/ir/libcall.rs @@ -1,7 +1,7 @@ //! Naming well-known routines in the runtime library. use crate::{ - ir::{types, AbiParam, ExternalName, FuncRef, Function, Opcode, Signature, Type}, + ir::{types, AbiParam, ExternalName, FuncRef, Function, Signature}, isa::CallConv, }; use core::fmt; @@ -23,20 +23,6 @@ pub enum LibCall { /// probe for stack overflow. These are emitted for functions which need /// when the `enable_probestack` setting is true. Probestack, - /// udiv.i64 - UdivI64, - /// sdiv.i64 - SdivI64, - /// urem.i64 - UremI64, - /// srem.i64 - SremI64, - /// ishl.i64 - IshlI64, - /// ushr.i64 - UshrI64, - /// sshr.i64 - SshrI64, /// ceil.f32 CeilF32, /// ceil.f64 @@ -85,13 +71,6 @@ impl FromStr for LibCall { fn from_str(s: &str) -> Result { match s { "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), "CeilF64" => Ok(Self::CeilF64), "FloorF32" => Ok(Self::FloorF32), @@ -115,54 +94,11 @@ impl FromStr for 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 { - 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. pub fn all_libcalls() -> &'static [LibCall] { use LibCall::*; &[ Probestack, - UdivI64, - SdivI64, - UremI64, - SremI64, - IshlI64, - UshrI64, - SshrI64, CeilF32, CeilF64, FloorF32, @@ -188,17 +124,6 @@ impl LibCall { let mut sig = Signature::new(call_conv); 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 => { sig.params.push(AbiParam::new(F32)); sig.returns.push(AbiParam::new(F32)); diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index 6e9b80bccf8d..f454f912d67d 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -465,6 +465,16 @@ const OPCODE_SIGNATURES: &'static [( (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> where 'data: 'r, @@ -506,6 +516,12 @@ where 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 { // TODO: It would be nice if we could get these directly from cranelift let scalars = [ @@ -833,12 +849,11 @@ where let signature = self.generate_signature()?; (name, signature) } else { - // Use udivi64 as an example of a libcall function. - let mut signature = Signature::new(CallConv::Fast); - signature.params.push(AbiParam::new(I64)); - signature.params.push(AbiParam::new(I64)); - signature.returns.push(AbiParam::new(I64)); - (ExternalName::LibCall(LibCall::UdivI64), signature) + let libcall = *self.u.choose(ALLOWED_LIBCALLS)?; + // TODO: Use [CallConv::for_libcall] once we generate flags. + let callconv = self.system_callconv(); + let signature = libcall.signature(callconv); + (ExternalName::LibCall(libcall), signature) }; let sig_ref = builder.import_signature(sig.clone()); diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs index 3d682799f4c1..eba421426952 100644 --- a/cranelift/interpreter/src/interpreter.rs +++ b/cranelift/interpreter/src/interpreter.rs @@ -12,10 +12,11 @@ use crate::value::{Value, ValueError}; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; use cranelift_codegen::ir::{ - ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, StackSlot, Type, - Value as ValueRef, + ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, LibCall, + StackSlot, TrapCode, Type, Value as ValueRef, }; use log::trace; +use smallvec::SmallVec; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::fmt::Debug; @@ -191,9 +192,13 @@ pub enum HeapInit { FromBacking(HeapBacking), } +pub type LibCallValues = SmallVec<[V; 1]>; +pub type LibCallHandler = fn(LibCall, LibCallValues) -> Result, TrapCode>; + /// Maintains the [Interpreter]'s state, implementing the [State] trait. pub struct InterpreterState<'a> { pub functions: FunctionStore<'a>, + pub libcall_handler: LibCallHandler, pub frame_stack: Vec>, /// Number of bytes from the bottom of the stack where the current frame's stack space is pub frame_offset: usize, @@ -208,6 +213,7 @@ impl Default for InterpreterState<'_> { fn default() -> Self { Self { functions: FunctionStore::default(), + libcall_handler: |_, _| Err(TrapCode::UnreachableCodeReached), frame_stack: vec![], frame_offset: 0, stack: Vec::with_capacity(1024), @@ -224,6 +230,12 @@ impl<'a> InterpreterState<'a> { Self { functions, ..self } } + /// Registers a libcall handler + pub fn with_libcall_handler(mut self, handler: LibCallHandler) -> Self { + self.libcall_handler = handler; + self + } + /// Registers a static heap and returns a reference to it /// /// 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 } + fn get_libcall_handler(&self) -> LibCallHandler { + self.libcall_handler + } + fn push_frame(&mut self, function: &'a Function) { if let Some(frame) = self.frame_stack.iter().last() { self.frame_offset += frame.function.fixed_stack_size() as usize; @@ -612,9 +628,11 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> { mod tests { use super::*; use crate::step::CraneliftTrap; + use cranelift_codegen::ir::immediates::Ieee32; use cranelift_codegen::ir::types::I64; use cranelift_codegen::ir::TrapCode; use cranelift_reader::parse_functions; + use smallvec::smallvec; // 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 @@ -1045,4 +1063,34 @@ mod tests { 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))]) + } } diff --git a/cranelift/interpreter/src/state.rs b/cranelift/interpreter/src/state.rs index b45a3e039066..48fe3ba6f087 100644 --- a/cranelift/interpreter/src/state.rs +++ b/cranelift/interpreter/src/state.rs @@ -1,6 +1,7 @@ //! Cranelift instructions modify the state of the machine; the [State] trait describes these //! ways this can happen. use crate::address::{Address, AddressSize}; +use crate::interpreter::LibCallHandler; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; 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>; /// Retrieve a reference to the currently executing [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; /// Record that an interpreter has called into a new [Function]. fn push_frame(&mut self, function: &'a Function); /// Record that an interpreter has returned from a called [Function]. @@ -129,6 +132,10 @@ where unimplemented!() } + fn get_libcall_handler(&self) -> LibCallHandler { + unimplemented!() + } + fn push_frame(&mut self, _function: &'a Function) { unimplemented!() } diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs index c30dd92b6408..75ae1ce667fb 100644 --- a/cranelift/interpreter/src/step.rs +++ b/cranelift/interpreter/src/step.rs @@ -7,7 +7,8 @@ use crate::value::{Value, ValueConversionKind, ValueError, ValueResult}; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; 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 smallvec::{smallvec, SmallVec}; @@ -16,6 +17,14 @@ use std::fmt::Debug; use std::ops::RangeFrom; 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 /// 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(...)`. @@ -25,7 +34,7 @@ pub fn step<'a, V, I>( inst_context: I, ) -> Result, StepError> where - V: Value, + V: Value + Debug, I: InstructionContext, { let inst = inst_context.data(); @@ -295,13 +304,65 @@ where ), Opcode::Return => ControlFlow::Return(args()?), Opcode::Call => { - if let InstructionData::Call { func_ref, .. } = inst { - let function = state - .get_function(func_ref) - .ok_or(StepError::UnknownFunction(func_ref))?; - ControlFlow::Call(function, args()?) + let func_ref = if let InstructionData::Call { func_ref, .. } = inst { + func_ref } else { 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"), diff --git a/cranelift/module/src/lib.rs b/cranelift/module/src/lib.rs index 4f9bde29d532..b14bc4a6bc27 100644 --- a/cranelift/module/src/lib.rs +++ b/cranelift/module/src/lib.rs @@ -55,13 +55,6 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn default_libcall_names() -> Box String + Send + Sync> { Box::new(move |libcall| match libcall { 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::CeilF64 => "ceil".to_owned(), ir::LibCall::FloorF32 => "floorf".to_owned(), diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 33a0181e7166..21e00b0e9f91 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -19,6 +19,7 @@ cranelift-interpreter = { path = "../cranelift/interpreter" } cranelift-fuzzgen = { path = "../cranelift/fuzzgen" } libfuzzer-sys = "0.4.0" target-lexicon = "0.12" +smallvec = "1.6.1" wasmtime = { path = "../crates/wasmtime" } wasmtime-fuzzing = { path = "../crates/fuzzing" } component-test-util = { path = "../crates/misc/component-test-util" } diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs index 4df01044291b..ac7aef2b3a06 100644 --- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs +++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs @@ -3,15 +3,19 @@ use libfuzzer_sys::fuzz_target; use cranelift_codegen::data_value::DataValue; +use cranelift_codegen::ir::LibCall; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline}; use cranelift_fuzzgen::*; use cranelift_interpreter::environment::FuncIndex; 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::CraneliftTrap; +use smallvec::{smallvec, SmallVec}; const INTERPRETER_FUEL: u64 = 4096; @@ -51,16 +55,30 @@ fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult { RunResult::Success(res) } -fuzz_target!(|testcase: TestCase| { - let build_interpreter = || { - let mut env = FunctionStore::default(); - env.add(testcase.func.name.to_string(), &testcase.func); +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); - let interpreter = Interpreter::new(state).with_fuel(Some(INTERPRETER_FUEL)); - interpreter - }; + let state = InterpreterState::default() + .with_function_store(env) + .with_libcall_handler(|libcall: LibCall, args: LibCallValues| { + 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| { // Native fn let flags = { let mut builder = settings::builder(); @@ -80,7 +98,7 @@ fuzz_target!(|testcase: TestCase| { 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 mut interpreter = build_interpreter(&testcase); let int_res = run_in_interpreter(&mut interpreter, args); match int_res { RunResult::Success(_) => {}