mod runtime_core_tests; #[cfg(test)] pub mod runtime_core_imports { use super::runtime_core_tests::{get_compiler, wat2wasm}; use std::{convert::TryInto, sync::Arc}; use wasmer::compiler::compile_with; use wasmer::error::RuntimeError; use wasmer::units::Pages; use wasmer::wasm::{ DynFunc, Func, FuncSig, Global, Instance, Memory, MemoryDescriptor, Type, Value, }; use wasmer::{imports, vm, DynamicFunc}; #[test] fn runtime_core_new_api_works() { let wasm = r#" (module (type $type (func (param i32) (result i32))) (global (export "my_global") i32 (i32.const 45)) (func (export "add_one") (type $type) (i32.add (get_local 0) (i32.const 1))) (func (export "double") (type $type) (i32.mul (get_local 0) (i32.const 2))) )"#; let wasm_binary = wat2wasm(wasm.as_bytes()).expect("WAST not valid or malformed"); let module = compile_with(&wasm_binary, &get_compiler()).unwrap(); let import_object = imports! {}; let instance = module.instantiate(&import_object).unwrap(); let my_global: Global = instance.exports.get("my_global").unwrap(); assert_eq!(my_global.get(), Value::I32(45)); let double: Func = instance.exports.get("double").unwrap(); assert_eq!(double.call(5).unwrap(), 10); let add_one: DynFunc = instance.exports.get("add_one").unwrap(); assert_eq!(add_one.call(&[Value::I32(5)]).unwrap(), &[Value::I32(6)]); let add_one_memory: Result = instance.exports.get("my_global"); assert!(add_one_memory.is_err()); } macro_rules! call_and_assert { ($instance:ident, $function:ident( $( $inputs:ty ),* ) -> $output:ty, ( $( $arguments:expr ),* ) == $expected_value:expr) => { #[allow(unused_parens)] let $function: Func<( $( $inputs ),* ), $output> = $instance.exports.get(stringify!($function)).expect(concat!("Failed to get the `", stringify!($function), "` export function.")); let result = $function.call( $( $arguments ),* ); match (result, $expected_value) { (Ok(value), expected_value) => assert_eq!( Ok(value), expected_value, concat!("Expected right when calling `", stringify!($function), "`.") ), (Err(RuntimeError(data)), Err(RuntimeError(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 ) ), } }; } /// The shift that is set in the instance memory. The value is part of /// the result returned by the imported functions if the memory is /// read properly. const SHIFT: i32 = 10; /// The shift that is captured in the environment of a closure. The /// value is part of the result returned by the imported function if /// the closure captures its environment properly. #[allow(non_upper_case_globals)] const shift: i32 = 100; #[cfg(all(unix, target_arch = "x86_64"))] fn imported_functions_forms(test: &dyn Fn(&Instance)) { 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_closure" (func $callback_closure (type $type))) (import "env" "callback_fn_dynamic" (func $callback_fn_dynamic (type $type))) (import "env" "callback_fn_dynamic_panic" (func $callback_fn_dynamic_panic (type $type))) (import "env" "callback_closure_dynamic_0" (func $callback_closure_dynamic_0)) (import "env" "callback_closure_dynamic_1" (func $callback_closure_dynamic_1 (param i32) (result i32))) (import "env" "callback_closure_dynamic_2" (func $callback_closure_dynamic_2 (param i32 i64) (result i64))) (import "env" "callback_closure_dynamic_3" (func $callback_closure_dynamic_3 (param i32 i64 f32) (result f32))) (import "env" "callback_closure_dynamic_4" (func $callback_closure_dynamic_4 (param i32 i64 f32 f64) (result f64))) (import "env" "callback_closure_with_env" (func $callback_closure_with_env (type $type))) (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) (import "env" "callback_closure_with_vmctx" (func $callback_closure_with_vmctx (type $type))) (import "env" "callback_closure_with_vmctx_and_env" (func $callback_closure_with_vmctx_and_env (type $type))) (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) (import "env" "callback_closure_trap" (func $callback_closure_trap (type $type))) (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) (import "env" "callback_closure_trap_with_vmctx" (func $callback_closure_trap_with_vmctx (type $type))) (import "env" "callback_closure_trap_with_vmctx_and_env" (func $callback_closure_trap_with_vmctx_and_env (type $type))) (func (export "function_fn") (type $type) get_local 0 call $callback_fn) (func (export "function_closure") (type $type) get_local 0 call $callback_closure) (func (export "function_fn_dynamic") (type $type) get_local 0 call $callback_fn_dynamic) (func (export "function_fn_dynamic_panic") (type $type) get_local 0 call $callback_fn_dynamic_panic) (func (export "function_closure_dynamic_0") call $callback_closure_dynamic_0) (func (export "function_closure_dynamic_1") (param i32) (result i32) get_local 0 call $callback_closure_dynamic_1) (func (export "function_closure_dynamic_2") (param i32 i64) (result i64) get_local 0 get_local 1 call $callback_closure_dynamic_2) (func (export "function_closure_dynamic_3") (param i32 i64 f32) (result f32) get_local 0 get_local 1 get_local 2 call $callback_closure_dynamic_3) (func (export "function_closure_dynamic_4") (param i32 i64 f32 f64) (result f64) get_local 0 get_local 1 get_local 2 get_local 3 call $callback_closure_dynamic_4) (func (export "function_closure_with_env") (type $type) get_local 0 call $callback_closure_with_env) (func (export "function_fn_with_vmctx") (type $type) get_local 0 call $callback_fn_with_vmctx) (func (export "function_closure_with_vmctx") (type $type) get_local 0 call $callback_closure_with_vmctx) (func (export "function_closure_with_vmctx_and_env") (type $type) get_local 0 call $callback_closure_with_vmctx_and_env) (func (export "function_fn_trap") (type $type) get_local 0 call $callback_fn_trap) (func (export "function_closure_trap") (type $type) get_local 0 call $callback_closure_trap) (func (export "function_fn_trap_with_vmctx") (type $type) get_local 0 call $callback_fn_trap_with_vmctx) (func (export "function_closure_trap_with_vmctx") (type $type) get_local 0 call $callback_closure_trap_with_vmctx) (func (export "function_closure_trap_with_vmctx_and_env") (type $type) get_local 0 call $callback_closure_trap_with_vmctx_and_env)) "#; 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(); memory.view()[0].set(SHIFT); let import_object = imports! { "env" => { "memory" => memory.clone(), // Regular function. "callback_fn" => Func::new(callback_fn), // Closure without a captured environment. "callback_closure" => Func::new(|n: i32| -> Result { Ok(n + 1) }), // Regular polymorphic function. "callback_fn_dynamic" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), callback_fn_dynamic, ), // Polymorphic function that panics. "callback_fn_dynamic_panic" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), callback_fn_dynamic_panic, ), // Polymorphic closure “closures”. "callback_closure_dynamic_0" => DynamicFunc::new( Arc::new(FuncSig::new(vec![], vec![])), |_, inputs: &[Value]| -> Vec { assert!(inputs.is_empty()); vec![] } ), "callback_closure_dynamic_1" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), move |vmctx: &mut vm::Ctx, inputs: &[Value]| -> Vec { assert_eq!(inputs.len(), 1); let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); let n: i32 = (&inputs[0]).try_into().unwrap(); vec![Value::I32(shift_ + n)] } ), "callback_closure_dynamic_2" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32, Type::I64], vec![Type::I64])), move |vmctx: &mut vm::Ctx, inputs: &[Value]| -> Vec { assert_eq!(inputs.len(), 2); let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); let i: i32 = (&inputs[0]).try_into().unwrap(); let j: i64 = (&inputs[1]).try_into().unwrap(); vec![Value::I64(shift_ as i64 + i as i64 + j)] } ), "callback_closure_dynamic_3" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32, Type::I64, Type::F32], vec![Type::F32])), move |vmctx: &mut vm::Ctx, inputs: &[Value]| -> Vec { assert_eq!(inputs.len(), 3); let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); let i: i32 = (&inputs[0]).try_into().unwrap(); let j: i64 = (&inputs[1]).try_into().unwrap(); let k: f32 = (&inputs[2]).try_into().unwrap(); vec![Value::F32(shift_ as f32 + i as f32 + j as f32 + k)] } ), "callback_closure_dynamic_4" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![Type::F64])), move |vmctx: &mut vm::Ctx, inputs: &[Value]| -> Vec { assert_eq!(inputs.len(), 4); let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); let i: i32 = (&inputs[0]).try_into().unwrap(); let j: i64 = (&inputs[1]).try_into().unwrap(); let k: f32 = (&inputs[2]).try_into().unwrap(); let l: f64 = (&inputs[3]).try_into().unwrap(); vec![Value::F64(shift_ as f64 + i as f64 + j as f64 + k as f64 + l)] } ), // Closure with a captured environment (a single variable + an instance of `Memory`). "callback_closure_with_env" => Func::new(move |n: i32| -> Result { let shift_ = shift + memory.view::()[0].get(); Ok(shift_ + n + 1) }), // Regular function with an explicit `vmctx`. "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), // Closure without a captured environment but with an explicit `vmctx`. "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift_: i32 = memory.view()[0].get(); Ok(shift_ + n + 1) }), // Closure with a captured environment (a single variable) and with an explicit `vmctx`. "callback_closure_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); Ok(shift_ + n + 1) }), // Trap a regular function. "callback_fn_trap" => Func::new(callback_fn_trap), // Trap a closure without a captured environment. "callback_closure_trap" => Func::new(|n: i32| -> Result { Err(format!("bar {}", n + 1)) }), // Trap a regular function with an explicit `vmctx`. "callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx), // Trap a closure without a captured environment but with an explicit `vmctx`. "callback_closure_trap_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift_: i32 = memory.view()[0].get(); Err(format!("qux {}", shift_ + n + 1)) }), // Trap a closure with a captured environment (a single variable) and with an explicit `vmctx`. "callback_closure_trap_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift_ = shift + memory.view::()[0].get(); Err(format!("! {}", shift_ + n + 1)) }), }, }; let instance = module.instantiate(&import_object).unwrap(); test(&instance); } fn callback_fn(n: i32) -> Result { Ok(n + 1) } fn callback_fn_dynamic(_: &mut vm::Ctx, inputs: &[Value]) -> Vec { match inputs[0] { Value::I32(x) => vec![Value::I32(x + 1)], _ => unreachable!(), } } fn callback_fn_dynamic_panic(_: &mut vm::Ctx, _: &[Value]) -> Vec { panic!("test"); } 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 + 1)) } 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)) } macro_rules! test { ($test_name:ident, $function:ident( $( $inputs:ty ),* ) -> $output:ty, ( $( $arguments:expr ),* ) == $expected_value:expr) => { #[cfg(all(unix, target_arch = "x86_64"))] #[test] fn $test_name() { imported_functions_forms(&|instance| { call_and_assert!(instance, $function( $( $inputs ),* ) -> $output, ( $( $arguments ),* ) == $expected_value); }); } }; } test!(test_fn, function_fn(i32) -> i32, (1) == Ok(2)); test!(test_closure, function_closure(i32) -> i32, (1) == Ok(2)); test!(test_fn_dynamic, function_fn_dynamic(i32) -> i32, (1) == Ok(2)); test!(test_fn_dynamic_panic, function_fn_dynamic_panic(i32) -> i32, (1) == Err(RuntimeError(Box::new("test")))); test!( test_closure_dynamic_0, function_closure_dynamic_0(()) -> (), () == Ok(()) ); test!( test_closure_dynamic_1, function_closure_dynamic_1(i32) -> i32, (1) == Ok(1 + shift + SHIFT) ); test!( test_closure_dynamic_2, function_closure_dynamic_2(i32, i64) -> i64, (1, 2) == Ok(1 + 2 + shift as i64 + SHIFT as i64) ); test!( test_closure_dynamic_3, function_closure_dynamic_3(i32, i64, f32) -> f32, (1, 2, 3.) == Ok(1. + 2. + 3. + shift as f32 + SHIFT as f32) ); test!( test_closure_dynamic_4, function_closure_dynamic_4(i32, i64, f32, f64) -> f64, (1, 2, 3., 4.) == Ok(1. + 2. + 3. + 4. + shift as f64 + SHIFT as f64) ); test!( test_closure_with_env, function_closure_with_env(i32) -> i32, (1) == Ok(2 + shift + SHIFT) ); test!(test_fn_with_vmctx, function_fn_with_vmctx(i32) -> i32, (1) == Ok(2 + SHIFT)); test!( test_closure_with_vmctx, function_closure_with_vmctx(i32) -> i32, (1) == Ok(2 + SHIFT) ); test!( test_closure_with_vmctx_and_env, function_closure_with_vmctx_and_env(i32) -> i32, (1) == Ok(2 + shift + SHIFT) ); test!( test_fn_trap, function_fn_trap(i32) -> i32, (1) == Err(RuntimeError(Box::new(format!("foo {}", 2)))) ); test!( test_closure_trap, function_closure_trap(i32) -> i32, (1) == Err(RuntimeError(Box::new(format!("bar {}", 2)))) ); test!( test_fn_trap_with_vmctx, function_fn_trap_with_vmctx(i32) -> i32, (1) == Err(RuntimeError(Box::new(format!("baz {}", 2 + SHIFT)))) ); test!( test_closure_trap_with_vmctx, function_closure_trap_with_vmctx(i32) -> i32, (1) == Err(RuntimeError(Box::new(format!("qux {}", 2 + SHIFT)))) ); test!( test_closure_trap_with_vmctx_and_env, function_closure_trap_with_vmctx_and_env(i32) -> i32, (1) == Err(RuntimeError(Box::new(format!("! {}", 2 + shift + SHIFT)))) ); }