diff --git a/Cargo.lock b/Cargo.lock index 917724f89e2..d15d45dd695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1538,6 +1538,17 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasmer-runtime-core-tests" +version = "0.8.0" +dependencies = [ + "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-backend 0.9.0", + "wasmer-llvm-backend 0.9.0", + "wasmer-runtime-core 0.9.0", + "wasmer-singlepass-backend 0.9.0", +] + [[package]] name = "wasmer-singlepass-backend" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 5c183b23538..2a47f943de4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "lib/singlepass-backend", "lib/runtime", "lib/runtime-core", + "lib/runtime-core-tests", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", diff --git a/Makefile b/Makefile index 14575999363..e8d11bed05e 100644 --- a/Makefile +++ b/Makefile @@ -89,12 +89,15 @@ wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llv # Backends singlepass: spectests-singlepass emtests-singlepass middleware-singlepass wasitests-singlepass cargo test -p wasmer-singlepass-backend --release + cargo test -p wasmer-runtime-core-tests --release --no-default-features --features backend-singlepass cranelift: spectests-cranelift emtests-cranelift middleware-cranelift wasitests-cranelift cargo test -p wasmer-clif-backend --release + cargo test -p wasmer-runtime-core-tests --release llvm: spectests-llvm emtests-llvm wasitests-llvm cargo test -p wasmer-llvm-backend --release + cargo test -p wasmer-runtime-core-tests --release --no-default-features --features backend-llvm # All tests @@ -108,7 +111,20 @@ test-capi: capi capi-test: test-capi test-rest: - cargo test --release --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests --exclude wasmer-wasi --exclude wasmer-middleware-common --exclude wasmer-middleware-common-tests --exclude wasmer-singlepass-backend --exclude wasmer-clif-backend --exclude wasmer-llvm-backend --exclude wasmer-wasi-tests --exclude wasmer-emscripten-tests + cargo test --release \ + --all \ + --exclude wasmer-runtime-c-api \ + --exclude wasmer-emscripten \ + --exclude wasmer-spectests \ + --exclude wasmer-wasi \ + --exclude wasmer-middleware-common \ + --exclude wasmer-middleware-common-tests \ + --exclude wasmer-singlepass-backend \ + --exclude wasmer-clif-backend \ + --exclude wasmer-llvm-backend \ + --exclude wasmer-wasi-tests \ + --exclude wasmer-emscripten-tests \ + --exclude wasmer-runtime-core-tests circleci-clean: @if [ ! -z "${CIRCLE_JOB}" ]; then rm -f /home/circleci/project/target/debug/deps/libcranelift_wasm* && rm -f /Users/distiller/project/target/debug/deps/libcranelift_wasm*; fi; diff --git a/lib/runtime-core-tests/Cargo.toml b/lib/runtime-core-tests/Cargo.toml new file mode 100644 index 00000000000..2c1b3daa1c0 --- /dev/null +++ b/lib/runtime-core-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "wasmer-runtime-core-tests" +version = "0.8.0" +description = "Tests for the Wasmer runtime core crate" +license = "MIT" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +publish = false + +[dependencies] +wabt = "0.9.1" +wasmer-runtime-core = { path = "../runtime-core", version = "0.9" } +wasmer-clif-backend = { path = "../clif-backend", version = "0.9", optional = true } +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.9", optional = true } +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.9", optional = true } + +[features] +default = ["backend-cranelift"] +backend-cranelift = ["wasmer-clif-backend"] +backend-singlepass = ["wasmer-singlepass-backend"] +backend-llvm = ["wasmer-llvm-backend"] \ No newline at end of file diff --git a/lib/runtime-core-tests/src/lib.rs b/lib/runtime-core-tests/src/lib.rs new file mode 100644 index 00000000000..96f7e6265b8 --- /dev/null +++ b/lib/runtime-core-tests/src/lib.rs @@ -0,0 +1,21 @@ +pub use wabt::wat2wasm; +use wasmer_runtime_core::backend::Compiler; + +#[cfg(feature = "backend-cranelift")] +pub fn get_compiler() -> impl Compiler { + use wasmer_clif_backend::CraneliftCompiler; + + CraneliftCompiler::new() +} + +#[cfg(feature = "backend-singlepass")] +pub fn get_compiler() -> impl Compiler { + use wasmer_singlepass_backend::SinglePassCompiler; + SinglePassCompiler::new() +} + +#[cfg(feature = "backend-llvm")] +pub fn get_compiler() -> impl Compiler { + use wasmer_llvm_backend::LLVMCompiler; + LLVMCompiler::new() +} diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs new file mode 100644 index 00000000000..69b9040cdc6 --- /dev/null +++ b/lib/runtime-core-tests/tests/imports.rs @@ -0,0 +1,137 @@ +use wasmer_runtime_core::{ + compile_with, error::RuntimeError, imports, memory::Memory, typed_func::Func, + types::MemoryDescriptor, units::Pages, vm, +}; +use wasmer_runtime_core_tests::{get_compiler, wat2wasm}; + +#[test] +fn imported_functions_forms() { + const MODULE: &str = r#" +(module + (type $type (func (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (import "env" "callback_fn" (func $callback_fn (type $type))) + (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) + (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) + (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) + (func (export "function_fn") (type $type) + get_local 0 + call $callback_fn) + (func (export "function_fn_with_vmctx") (type $type) + get_local 0 + call $callback_fn_with_vmctx) + (func (export "function_fn_trap") (type $type) + get_local 0 + call $callback_fn_trap) + (func (export "function_fn_trap_with_vmctx") (type $type) + get_local 0 + call $callback_fn_trap_with_vmctx)) +"#; + + let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); + let module = compile_with(&wasm_binary, &get_compiler()).unwrap(); + let memory_descriptor = MemoryDescriptor::new(Pages(1), Some(Pages(1)), false).unwrap(); + let memory = Memory::new(memory_descriptor).unwrap(); + + const SHIFT: i32 = 10; + memory.view()[0].set(SHIFT); + + let import_object = imports! { + "env" => { + "memory" => memory.clone(), + "callback_fn" => Func::new(callback_fn), + "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), + "callback_fn_trap" => Func::new(callback_fn_trap), + "callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx), + }, + }; + let instance = module.instantiate(&import_object).unwrap(); + + macro_rules! call_and_assert { + ($function:ident, $expected_value:expr) => { + let $function: Func = instance.func(stringify!($function)).unwrap(); + + let result = $function.call(1); + + match (result, $expected_value) { + (Ok(value), expected_value) => assert_eq!( + Ok(value), + expected_value, + concat!("Expected right when calling `", stringify!($function), "`.") + ), + ( + Err(RuntimeError::Error { data }), + Err(RuntimeError::Error { + data: expected_data, + }), + ) => { + if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::<&str>(), + expected_data.downcast_ref::<&str>(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::(), + expected_data.downcast_ref::(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else { + assert!(false, "Unexpected error, cannot compare it.") + } + } + (result, expected_value) => assert!( + false, + format!( + "Unexpected assertion for `{}`: left = `{:?}`, right = `{:?}`.", + stringify!($function), + result, + expected_value + ) + ), + } + }; + } + + call_and_assert!(function_fn, Ok(2)); + call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT)); + call_and_assert!( + function_fn_trap, + Err(RuntimeError::Error { + data: Box::new(format!("foo {}", 1)) + }) + ); + call_and_assert!( + function_fn_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("baz {}", 2 + SHIFT)) + }) + ); +} + +fn callback_fn(n: i32) -> Result { + Ok(n + 1) +} + +fn callback_fn_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { + let memory = vmctx.memory(0); + let shift: i32 = memory.view()[0].get(); + + Ok(shift + n + 1) +} + +fn callback_fn_trap(n: i32) -> Result { + Err(format!("foo {}", n)) +} + +fn callback_fn_trap_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { + let memory = vmctx.memory(0); + let shift: i32 = memory.view()[0].get(); + + Err(format!("baz {}", shift + n + 1)) +} diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 74318747c2f..f70f169297e 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -3,7 +3,7 @@ use crate::{ export::{Context, Export, FuncPointer}, import::IsExport, types::{FuncSig, NativeWasmType, Type, WasmExternType}, - vm::{self, Ctx}, + vm, }; use std::{ any::Any, @@ -52,10 +52,10 @@ impl fmt::Display for WasmTrapInfo { /// of the `Func` struct. pub trait Kind {} -pub type Trampoline = unsafe extern "C" fn(*mut Ctx, NonNull, *const u64, *mut u64); +pub type Trampoline = unsafe extern "C" fn(*mut vm::Ctx, NonNull, *const u64, *mut u64); pub type Invoke = unsafe extern "C" fn( Trampoline, - *mut Ctx, + *mut vm::Ctx, NonNull, *const u64, *mut u64, @@ -124,16 +124,49 @@ pub trait WasmTypeList { self, f: NonNull, wasm: Wasm, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, ) -> Result where Rets: WasmTypeList; } +/// Empty trait to specify the kind of `ExternalFunction`: With or +/// without a `vm::Ctx` argument. See the `ExplicitVmCtx` and the +/// `ImplicitVmCtx` structures. +/// +/// This type is never aimed to be used by a user. It is used by the +/// trait system to automatically generate an appropriate `wrap` +/// function. +pub trait ExternalFunctionKind {} + +/// This empty structure indicates that an external function must +/// contain an explicit `vm::Ctx` argument (at first position). +/// +/// ```rs,ignore +/// fn add_one(_: mut &vm::Ctx, x: i32) -> i32 { +/// x + 1 +/// } +/// ``` +pub struct ExplicitVmCtx {} + +/// This empty structure indicates that an external function has no +/// `vm::Ctx` argument (at first position). Its signature is: +/// +/// ```rs,ignore +/// fn add_one(x: i32) -> i32 { +/// x + 1 +/// } +/// ``` +pub struct ImplicitVmCtx {} + +impl ExternalFunctionKind for ExplicitVmCtx {} +impl ExternalFunctionKind for ImplicitVmCtx {} + /// Represents a function that can be converted to a `vm::Func` /// (function pointer) that can be called within WebAssembly. -pub trait ExternalFunction +pub trait ExternalFunction where + Kind: ExternalFunctionKind, Args: WasmTypeList, Rets: WasmTypeList, { @@ -173,7 +206,7 @@ where pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, f: NonNull, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, _phantom: PhantomData<(&'a (), Args, Rets)>, } @@ -188,7 +221,7 @@ where pub(crate) unsafe fn from_raw_parts( inner: Wasm, f: NonNull, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, ) -> Func<'a, Args, Rets, Wasm> { Func { inner, @@ -208,9 +241,10 @@ where Args: WasmTypeList, Rets: WasmTypeList, { - pub fn new(f: F) -> Func<'a, Args, Rets, Host> + pub fn new(f: F) -> Func<'a, Args, Rets, Host> where - F: ExternalFunction, + Kind: ExternalFunctionKind, + F: ExternalFunction, { Func { inner: Host(()), @@ -267,7 +301,7 @@ impl WasmTypeList for Infallible { self, _: NonNull, _: Wasm, - _: *mut Ctx, + _: *mut vm::Ctx, ) -> Result where Rets: WasmTypeList, @@ -313,7 +347,7 @@ where self, f: NonNull, wasm: Wasm, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, ) -> Result where Rets: WasmTypeList, @@ -405,7 +439,7 @@ macro_rules! impl_traits { self, f: NonNull, wasm: Wasm, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, ) -> Result where Rets: WasmTypeList @@ -438,12 +472,70 @@ macro_rules! impl_traits { } } - impl< $( $x, )* Rets, Trap, FN > ExternalFunction<( $( $x ),* ), Rets> for FN + impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap, + { + #[allow(non_snake_case)] + fn to_raw(&self) -> NonNull { + if mem::size_of::() == 0 { + /// This is required for the llvm backend to be able to unwind through this function. + #[cfg_attr(nightly, unwind(allowed))] + extern fn wrap<$( $x, )* Rets, Trap, FN>( + vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )* + ) -> Rets::CStruct + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, + { + let f: FN = unsafe { mem::transmute_copy(&()) }; + + let err = match panic::catch_unwind( + panic::AssertUnwindSafe( + || { + f(vmctx $( , WasmExternType::from_native($x) )* ).report() + } + ) + ) { + Ok(Ok(returns)) => return returns.into_c_struct(), + Ok(Err(err)) => { + let b: Box<_> = err.into(); + b as Box + }, + Err(err) => err, + }; + + unsafe { + (&*vmctx.module).runnable_module.do_early_trap(err) + } + } + + NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap() + } else { + assert_eq!( + mem::size_of::(), + mem::size_of::(), + "you cannot use a closure that captures state for `Func`." + ); + + NonNull::new(unsafe { + mem::transmute_copy::<_, *mut vm::Func>(self) + }).unwrap() + } + } + } + + impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN where $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, - FN: Fn(&mut Ctx $( , $x )*) -> Trap, + FN: Fn($( $x, )*) -> Trap, { #[allow(non_snake_case)] fn to_raw(&self) -> NonNull { @@ -451,20 +543,20 @@ macro_rules! impl_traits { /// This is required for the llvm backend to be able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( - ctx: &mut Ctx $( , $x: <$x as WasmExternType>::Native )* + vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct where $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, - FN: Fn(&mut Ctx $( , $x )*) -> Trap, + FN: Fn($( $x, )*) -> Trap, { let f: FN = unsafe { mem::transmute_copy(&()) }; let err = match panic::catch_unwind( panic::AssertUnwindSafe( || { - f(ctx $( , WasmExternType::from_native($x) )* ).report() + f($( WasmExternType::from_native($x), )* ).report() } ) ) { @@ -477,7 +569,7 @@ macro_rules! impl_traits { }; unsafe { - (&*ctx.module).runnable_module.do_early_trap(err) + (&*vmctx.module).runnable_module.do_early_trap(err) } } @@ -490,7 +582,7 @@ macro_rules! impl_traits { ); NonNull::new(unsafe { - ::std::mem::transmute_copy::<_, *mut vm::Func>(self) + mem::transmute_copy::<_, *mut vm::Func>(self) }).unwrap() } } @@ -562,9 +654,57 @@ where #[cfg(test)] mod tests { use super::*; + + macro_rules! test_func_arity_n { + ($test_name:ident, $($x:ident),*) => { + #[test] + fn $test_name() { + use crate::vm; + + fn with_vmctx(_: &mut vm::Ctx, $($x: i32),*) -> i32 { + vec![$($x),*].iter().sum() + } + + fn without_vmctx($($x: i32),*) -> i32 { + vec![$($x),*].iter().sum() + } + + let _func = Func::new(with_vmctx); + let _func = Func::new(without_vmctx); + } + } + } + + #[test] + fn test_func_arity_0() { + fn foo(_: &mut vm::Ctx) -> i32 { + 0 + } + + fn bar() -> i32 { + 0 + } + + let _ = Func::new(foo); + let _ = Func::new(bar); + } + + test_func_arity_n!(test_func_arity_1, a); + test_func_arity_n!(test_func_arity_2, a, b); + test_func_arity_n!(test_func_arity_3, a, b, c); + test_func_arity_n!(test_func_arity_4, a, b, c, d); + test_func_arity_n!(test_func_arity_5, a, b, c, d, e); + test_func_arity_n!(test_func_arity_6, a, b, c, d, e, f); + test_func_arity_n!(test_func_arity_7, a, b, c, d, e, f, g); + test_func_arity_n!(test_func_arity_8, a, b, c, d, e, f, g, h); + test_func_arity_n!(test_func_arity_9, a, b, c, d, e, f, g, h, i); + test_func_arity_n!(test_func_arity_10, a, b, c, d, e, f, g, h, i, j); + test_func_arity_n!(test_func_arity_11, a, b, c, d, e, f, g, h, i, j, k); + test_func_arity_n!(test_func_arity_12, a, b, c, d, e, f, g, h, i, j, k, l); + #[test] fn test_call() { - fn foo(_ctx: &mut Ctx, a: i32, b: i32) -> (i32, i32) { + fn foo(_ctx: &mut vm::Ctx, a: i32, b: i32) -> (i32, i32) { (a, b) } @@ -575,7 +715,7 @@ mod tests { fn test_imports() { use crate::{func, imports}; - fn foo(_ctx: &mut Ctx, a: i32) -> i32 { + fn foo(_ctx: &mut vm::Ctx, a: i32) -> i32 { a }