diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 609e0c83f7c..b29669d33b3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,4 +12,4 @@ links to related issues, and the context of the PR. # Review -- [ ] Create a short description of the the change in the CHANGELOG.md file +- [ ] Add a short description of the the change to the CHANGELOG.md file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1651f7ffbeb..6273bc8ef46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Blocks of changes will separated by version increments. ## **[Unreleased]** +- [#803](https://github.com/wasmerio/wasmer/issues/803) Add method to `Ctx` to invoke functions by their `TableIndex` - [#790](https://github.com/wasmerio/wasmer/pull/790) Fix flaky test failure with LLVM, switch to large code model. - [#788](https://github.com/wasmerio/wasmer/pull/788) Use union merge on the changelog file. - [#785](https://github.com/wasmerio/wasmer/pull/785) Include Apache license file for spectests. diff --git a/Cargo.toml b/Cargo.toml index 31ba526bd0b..06b2c2854dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ members = [ "lib/wasi-tests", "lib/emscripten-tests", "lib/middleware-common-tests", - "examples/plugin-for-example" + "examples/plugin-for-example", ] [build-dependencies] @@ -105,3 +105,7 @@ managed = ["backend-singlepass", "wasmer-runtime-core/managed"] [[example]] name = "plugin" crate-type = ["bin"] + +[[example]] +name = "callback" +crate-type = ["bin"] diff --git a/Makefile b/Makefile index 007e89853b0..5eb9c9aa23e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: spectests emtests clean build install lint precommit docs +.PHONY: spectests emtests clean build install lint precommit docs examples # Generate files generate-spectests: @@ -111,12 +111,16 @@ test: spectests emtests middleware wasitests circleci-clean test-rest # Integration tests -integration-tests: release-clif +integration-tests: release-clif examples echo "Running Integration Tests" ./integration_tests/lua/test.sh ./integration_tests/nginx/test.sh ./integration_tests/cowsay/test.sh +examples: + cargo run --example plugin + cargo run --example callback + # Utils lint: diff --git a/examples/callback-guest/README.md b/examples/callback-guest/README.md new file mode 100644 index 00000000000..6439ef00dd4 --- /dev/null +++ b/examples/callback-guest/README.md @@ -0,0 +1,5 @@ +# Call back guest + +This is part of the `callback` example. This Wasm module passes host imports and its own functions to the Wasm host to execute. + +See `examples/callback.rs` for the host diff --git a/examples/callback-guest/callback-guest.rs b/examples/callback-guest/callback-guest.rs new file mode 100644 index 00000000000..129ad05c8ac --- /dev/null +++ b/examples/callback-guest/callback-guest.rs @@ -0,0 +1,24 @@ +extern "C" { + fn call_guest_fn(f: u32) -> u32; + fn call_guest_fn2(f: u32) -> u32; + fn host_callback() -> u32; +} + +#[no_mangle] +fn test_callback() -> u32 { + 42 +} + +#[no_mangle] +fn test_callback2() -> u32 { + 45 +} + +fn main() { + unsafe { call_guest_fn(test_callback as usize as u32) }; + unsafe { call_guest_fn(host_callback as usize as u32) }; + unsafe { call_guest_fn(test_callback2 as usize as u32) }; + unsafe { call_guest_fn2(test_callback2 as usize as u32) }; + unsafe { call_guest_fn2(test_callback as usize as u32) }; + unsafe { call_guest_fn2(host_callback as usize as u32) }; +} diff --git a/examples/callback-guest/callback-guest.wasm b/examples/callback-guest/callback-guest.wasm new file mode 100755 index 00000000000..05558297595 Binary files /dev/null and b/examples/callback-guest/callback-guest.wasm differ diff --git a/examples/callback.rs b/examples/callback.rs new file mode 100644 index 00000000000..e5969fec971 --- /dev/null +++ b/examples/callback.rs @@ -0,0 +1,46 @@ +/// This example demonstrates the use of callbacks: calling functions (Host and Wasm) +/// passed to us from the Wasm via hostcall +use wasmer_runtime::{compile_with, compiler_for_backend, func, imports, Backend, Ctx}; +use wasmer_runtime_core::{structures::TypedIndex, types::TableIndex}; + +static WASM: &'static str = "examples/callback-guest/callback-guest.wasm"; + +/// This function matches our arbitrarily decided callback signature +/// in this example we'll only call functions that take no arguments and return one value +fn host_callback(_ctx: &mut Ctx) -> u32 { + 55 +} + +fn call_guest_fn(ctx: &mut Ctx, guest_fn: u32) -> u32 { + // We get a TableIndex from our raw value passed in + let guest_fn_typed = TableIndex::new(guest_fn as usize); + // and use it to call the corresponding function + let result = ctx.call_with_table_index(guest_fn_typed, &[]).unwrap(); + + println!("Guest fn {} returned {:?}", guest_fn, result); + + 0 +} + +fn main() { + let wasm_bytes = + std::fs::read(WASM).expect(&format!("Could not read in WASM plugin at {}", WASM)); + + let imports = imports! { + "env" => { + "call_guest_fn" => func!(call_guest_fn), + "call_guest_fn2" => func!(call_guest_fn), + "host_callback" => func!(host_callback), + }, + }; + + let compiler = compiler_for_backend(Backend::default()).unwrap(); + let module = compile_with(&wasm_bytes[..], compiler.as_ref()).unwrap(); + let instance = module + .instantiate(&imports) + .expect("failed to instantiate wasm module"); + + let entry_point = instance.func::<(u32, u32), u32>("main").unwrap(); + + entry_point.call(0, 0).expect("START"); +} diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index e6ba365f6ef..f1ce0a19376 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -527,29 +527,12 @@ fn call_func_with_index( args: &[Value], rets: &mut Vec, ) -> CallResult<()> { - rets.clear(); - let sig_index = *info .func_assoc .get(func_index) .expect("broken invariant, incorrect func index"); let signature = &info.signatures[sig_index]; - let num_results = signature.returns().len(); - let num_results = num_results - + signature - .returns() - .iter() - .filter(|&&ty| ty == Type::V128) - .count(); - rets.reserve(num_results); - - if !signature.check_param_value_types(args) { - Err(ResolveError::Signature { - expected: signature.clone(), - found: args.iter().map(|val| val.ty()).collect(), - })? - } let func_ptr = match func_index.local_or_import(info) { LocalOrImport::Local(local_func_index) => { @@ -567,6 +550,39 @@ fn call_func_with_index( } }; + let wasm = runnable + .get_trampoline(info, sig_index) + .expect("wasm trampoline"); + + call_func_with_index_inner(ctx_ptr, func_ptr, signature, wasm, args, rets) +} + +pub(crate) fn call_func_with_index_inner( + ctx_ptr: *mut vm::Ctx, + func_ptr: NonNull, + signature: &FuncSig, + wasm: Wasm, + args: &[Value], + rets: &mut Vec, +) -> CallResult<()> { + rets.clear(); + + let num_results = signature.returns().len(); + let num_results = num_results + + signature + .returns() + .iter() + .filter(|&&ty| ty == Type::V128) + .count(); + rets.reserve(num_results); + + if !signature.check_param_value_types(args) { + Err(ResolveError::Signature { + expected: signature.clone(), + found: args.iter().map(|val| val.ty()).collect(), + })? + } + let mut raw_args: SmallVec<[u64; 8]> = SmallVec::new(); for v in args { match v { @@ -598,9 +614,7 @@ fn call_func_with_index( trampoline, invoke, invoke_env, - } = runnable - .get_trampoline(info, sig_index) - .expect("wasm trampoline"); + } = wasm; let run_wasm = |result_space: *mut u64| unsafe { let mut trap_info = WasmTrapInfo::Unknown; diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index 789f67ada2b..4a63c8f6249 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -44,7 +44,7 @@ impl<'a> From> for Anyfunc<'a> { } pub struct AnyfuncTable { - backing: Vec, + pub(crate) backing: Vec, max: Option, } diff --git a/lib/runtime-core/src/table/mod.rs b/lib/runtime-core/src/table/mod.rs index 2e11507b1de..1b61cd75438 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -10,7 +10,7 @@ use std::{cell::RefCell, fmt, ptr, rc::Rc}; mod anyfunc; pub use self::anyfunc::Anyfunc; -use self::anyfunc::AnyfuncTable; +pub(crate) use self::anyfunc::AnyfuncTable; use crate::error::GrowError; pub enum Element<'a> { diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 51f883318d1..e3084a9c5fa 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -1,15 +1,19 @@ pub use crate::backing::{ImportBacking, LocalBacking, INTERNALS_SIZE}; use crate::{ + error::CallResult, + instance::call_func_with_index_inner, memory::{Memory, MemoryType}, module::{ModuleInfo, ModuleInner}, + sig_registry::SigRegistry, structures::TypedIndex, - types::{LocalOrImport, MemoryIndex}, + types::{LocalOrImport, MemoryIndex, TableIndex, Value}, vmcalls, }; use std::{ cell::UnsafeCell, ffi::c_void, - mem, ptr, + mem, + ptr::{self, NonNull}, sync::atomic::{AtomicUsize, Ordering}, sync::Once, }; @@ -393,6 +397,41 @@ impl Ctx { (*self.internal.internals)[field.index()] = value; } } + + /// Calls a host or Wasm function at the given table index + pub fn call_with_table_index( + &mut self, + index: TableIndex, + args: &[Value], + ) -> CallResult> { + let anyfunc_table = + unsafe { &*((**self.internal.tables).table as *mut crate::table::AnyfuncTable) }; + let Anyfunc { func, ctx, sig_id } = anyfunc_table.backing[index.index()]; + + let signature = SigRegistry.lookup_signature(unsafe { std::mem::transmute(sig_id.0) }); + let mut rets = vec![]; + + let wasm = { + let module = unsafe { &*self.module }; + let runnable = &module.runnable_module; + + let sig_index = SigRegistry.lookup_sig_index(signature.clone()); + runnable + .get_trampoline(&module.info, sig_index) + .expect("wasm trampoline") + }; + + call_func_with_index_inner( + ctx, + NonNull::new(func as *mut _).unwrap(), + &signature, + wasm, + args, + &mut rets, + )?; + + Ok(rets) + } } #[doc(hidden)]