diff --git a/CHANGELOG.md b/CHANGELOG.md index b63f91207a0..aeed049e7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ - [#1329](https://github.com/wasmerio/wasmer/pull/1329) New numbers and strings instructions for WIT - [#1332](https://github.com/wasmerio/wasmer/pull/1332) Add option to `CompilerConfig` to force compiler IR verification off even when `debug_assertions` are enabled. This can be used to make debug builds faster, which may be important if you're creating a library that wraps Wasmer and depend on the speed of debug builds. - [#1320](https://github.com/wasmerio/wasmer/pull/1320) Change `custom_sections` field in `ModuleInfo` to be more standards compliant by allowing multiple custom sections with the same name. To get the old behavior with the new API, you can add `.last().unwrap()` to accesses. For example, `module_info.custom_sections["custom_section_name"].last().unwrap()`. +- [#1313](https://github.com/wasmerio/wasmer/pull/1313) Add new high-level public API through `wasmer` crate. Includes many updates including: + - Minor improvement: `imports!` macro now handles no trailing comma as well as a trailing comma in namespaces and between namespaces. + - New methods on `Module`: `exports`, `imports`, and `custom_sections`. + - New way to get exports from an instance with `let func_name: Func = instance.exports.get("func_name");`. + - Improved `Table` APIs including `set` which now allows setting functions directly. TODO: update this more if `Table::get` gets made public in this PR + - TODO: finish the list of changes here - [#1303](https://github.com/wasmerio/wasmer/pull/1303) NaN canonicalization for singlepass backend. - [#1292](https://github.com/wasmerio/wasmer/pull/1292) Experimental Support for Android (x86_64 and AArch64) - [#1305](https://github.com/wasmerio/wasmer/pull/1305) Handle panics from DynamicFunc. diff --git a/Cargo.lock b/Cargo.lock index 2f845c1d468..cffbfbbe0c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,14 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +[[package]] +name = "api-tests" +version = "0.16.2" +dependencies = [ + "wabt", + "wasmer", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -2718,6 +2726,16 @@ dependencies = [ [[package]] name = "wasmer" version = "0.16.2" +dependencies = [ + "wasmer-clif-backend", + "wasmer-llvm-backend", + "wasmer-runtime-core", + "wasmer-singlepass-backend", +] + +[[package]] +name = "wasmer-bin" +version = "0.16.2" dependencies = [ "atty", "byteorder", @@ -2730,6 +2748,7 @@ dependencies = [ "structopt", "typetag", "wabt", + "wasmer", "wasmer-clif-backend", "wasmer-dev-utils", "wasmer-emscripten", diff --git a/Cargo.toml b/Cargo.toml index 0bb2e9a828f..ff1ae68894b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasmer" +name = "wasmer-bin" version = "0.16.2" authors = ["The Wasmer Engineering Team "] edition = "2018" @@ -27,6 +27,7 @@ fern = { version = "0.5", features = ["colored"], optional = true } log = "0.4" structopt = "0.3" wabt = { version = "0.9.1", optional = true } +wasmer = { path = "lib/api" } wasmer-clif-backend = { path = "lib/clif-backend", optional = true } wasmer-singlepass-backend = { path = "lib/singlepass-backend", optional = true } wasmer-middleware-common = { path = "lib/middleware-common" } @@ -44,6 +45,8 @@ wasmer-wasi-experimental-io-devices = { path = "lib/wasi-experimental-io-devices [workspace] members = [ + "lib/api", + "lib/api-tests", "lib/clif-backend", "lib/singlepass-backend", "lib/runtime", diff --git a/Makefile b/Makefile index 23c038514ab..ffeb9c8bf38 100644 --- a/Makefile +++ b/Makefile @@ -160,6 +160,7 @@ test-capi: test-capi-singlepass test-capi-cranelift test-capi-llvm test-capi-ems capi-test: test-capi test-rest: + cargo test --release -p api-tests cargo test --release -p wasmer-dev-utils cargo test --release -p wasmer-interface-types cargo test --release -p wasmer-kernel-loader diff --git a/examples/callback.rs b/examples/callback.rs index e5969fec971..1b30b69d16e 100644 --- a/examples/callback.rs +++ b/examples/callback.rs @@ -1,6 +1,6 @@ /// 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::{compile_with, compiler_for_backend, func, imports, Backend, Ctx, Func}; use wasmer_runtime_core::{structures::TypedIndex, types::TableIndex}; static WASM: &'static str = "examples/callback-guest/callback-guest.wasm"; @@ -40,7 +40,7 @@ fn main() { .instantiate(&imports) .expect("failed to instantiate wasm module"); - let entry_point = instance.func::<(u32, u32), u32>("main").unwrap(); + let entry_point: Func<(u32, u32), u32> = instance.exports.get("main").unwrap(); entry_point.call(0, 0).expect("START"); } diff --git a/examples/parallel/src/main.rs b/examples/parallel/src/main.rs index b68b0eaeba3..8d9ec16b991 100644 --- a/examples/parallel/src/main.rs +++ b/examples/parallel/src/main.rs @@ -1,5 +1,7 @@ use rayon::prelude::*; -use wasmer_runtime::{compile_with, compiler_for_backend, func, imports, instantiate, Backend}; +use wasmer_runtime::{ + compile_with, compiler_for_backend, func, imports, instantiate, Backend, Func, +}; use wasmer_runtime_core::{ memory::ptr::{Array, WasmPtr}, vm::Ctx, @@ -61,7 +63,8 @@ fn main() { .clone() .instantiate(&imports) .expect("failed to instantiate wasm module"); - let check_password = instance.func::<(u64, u64), u64>("check_password").unwrap(); + let check_password: Func<(u64, u64), u64> = + instance.exports.get("check_password").unwrap(); let j = i * 10000; let result = check_password.call(j, j + 10000).unwrap(); print!("."); @@ -101,7 +104,7 @@ fn main() { let instance = instantiate(&wasm_bytes[..], &imports).expect("failed to instantiate wasm module"); - let check_password = instance.func::<(u64, u64), u64>("check_password").unwrap(); + let check_password: Func<(u64, u64), u64> = instance.exports.get("check_password").unwrap(); let mut out: Option = None; for i in (0..=u64::max_value()).step_by(10000) { diff --git a/examples/plugin.rs b/examples/plugin.rs index 8256b27c66c..2695119ae67 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use wasmer_runtime::{compile, func, imports}; +use wasmer_runtime::{compile, func, imports, Func}; use wasmer_runtime_core::vm::Ctx; use wasmer_wasi::{ generate_import_object_for_version, @@ -159,7 +159,7 @@ fn main() { initialize(instance.context_mut()); // get a reference to the function "plugin_entrypoint" which takes an i32 and returns an i32 - let entry_point = instance.func::<(i32), i32>("plugin_entrypoint").unwrap(); + let entry_point: Func = instance.exports.get("plugin_entrypoint").unwrap(); // call the "entry_point" function in WebAssembly with the number "2" as the i32 argument let result = entry_point.call(2).expect("failed to execute plugin"); println!("result: {}", result); diff --git a/fuzz/fuzz_targets/validate_wasm.rs b/fuzz/fuzz_targets/validate_wasm.rs index f386105eccf..c17a87fafbd 100644 --- a/fuzz/fuzz_targets/validate_wasm.rs +++ b/fuzz/fuzz_targets/validate_wasm.rs @@ -2,13 +2,13 @@ #[macro_use] extern crate libfuzzer_sys; -extern crate wasmer; +extern crate wasmer_bin; extern crate wasmer_runtime_core; use wasmer_runtime_core::backend::Features; fuzz_target!(|data: &[u8]| { - let _ = wasmer::utils::is_wasm_binary(data); + let _ = wasmer_bin::utils::is_wasm_binary(data); let _ = wasmer_runtime_core::validate_and_report_errors_with_features( &data, Features { diff --git a/lib/api-tests/Cargo.toml b/lib/api-tests/Cargo.toml new file mode 100644 index 00000000000..03713b5c8aa --- /dev/null +++ b/lib/api-tests/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "api-tests" +version = "0.16.2" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +license = "MIT" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wasmer = { version = "0.16.2", path = "../api" } + +[dev-dependencies] +wabt = "0.9.1" \ No newline at end of file diff --git a/lib/api-tests/src/lib.rs b/lib/api-tests/src/lib.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/lib/api-tests/src/lib.rs @@ -0,0 +1 @@ + diff --git a/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs new file mode 100644 index 00000000000..5a078b78ed5 --- /dev/null +++ b/lib/api-tests/tests/high_level_api.rs @@ -0,0 +1,219 @@ +static TEST_WAT: &str = r#" +(module + (import "env" "outside-global" (global $outside-global (mut i32))) + (import "env" "update-func" (func $update-func (param i32) (result i32))) + (table $test-table (export "test-table") 2 anyfunc) + (global $test-global (export "test-global") (mut i32) (i32.const 3)) + (elem (;0;) (i32.const 0) $ret_2) + (func $ret_2 (export "ret_2") (result i32) + i32.const 2) + (func $ret_4 (export "ret_4") (result i32) + i32.const 4) + (func $set_test_global (export "set_test_global") (param i32) + (global.set $test-global + (local.get 0))) + (func $update-outside-global (export "update_outside_global") + (global.set $outside-global + (call $update-func (global.get $outside-global)))) +) +"#; + +fn append_custom_section( + wasm: &mut Vec, + custom_section_name: &str, + custom_section_contents: &[u8], +) { + wasm.reserve( + // 1 for custom section id, 5 for max length of custom section, 5 for max length of name + custom_section_name.len() + custom_section_contents.len() + 1 + 5 + 5, + ); + + wasm.push(0); + + let name_length = custom_section_name.as_bytes().len() as u32; + let mut name_length_as_leb128 = vec![]; + write_u32_leb128(&mut name_length_as_leb128, name_length); + + let custom_section_length = (custom_section_contents.len() + + custom_section_name.as_bytes().len() + + name_length_as_leb128.len()) as u32; + + let mut custom_section_length_as_leb128 = vec![]; + write_u32_leb128(&mut custom_section_length_as_leb128, custom_section_length); + + wasm.extend(&custom_section_length_as_leb128); + wasm.extend(&name_length_as_leb128); + wasm.extend(custom_section_name.as_bytes()); + wasm.extend(custom_section_contents); +} + +#[test] +fn custom_section_parsing_works() { + use wasmer::{CompiledModule, Module}; + let wasm = { + let mut wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + append_custom_section(&mut wasm, "test_custom_section", b"hello, world!"); + append_custom_section(&mut wasm, "test_custom_section", b"goodbye, world!"); + append_custom_section(&mut wasm, "different_name", b"different value"); + wasm + }; + + let module = Module::new(wasm).unwrap(); + + assert_eq!( + module.custom_sections("test_custom_section"), + Some(&[b"hello, world!".to_vec(), b"goodbye, world!".to_vec()][..]) + ); +} + +#[test] +fn module_exports_are_ordered() { + use wasmer::types::{ElementType, FuncSig, GlobalDescriptor, TableDescriptor, Type}; + use wasmer::{export, CompiledModule, Module}; + + let wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + // TODO: review error messages when `CompiledModule` is not in scope. My hypothesis is that they'll be + // misleading, if so we may want to do something about it. + let module = Module::new(wasm).unwrap(); + let exports = module.exports(); + assert_eq!( + exports, + vec![ + export::ExportDescriptor { + name: "test-table", + ty: export::ExternDescriptor::Table(TableDescriptor { + element: ElementType::Anyfunc, + minimum: 2, + maximum: None, + }), + }, + export::ExportDescriptor { + name: "test-global", + ty: export::ExternDescriptor::Global(GlobalDescriptor { + mutable: true, + ty: Type::I32, + }), + }, + export::ExportDescriptor { + name: "ret_2", + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![Type::I32])), + }, + export::ExportDescriptor { + name: "ret_4", + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![Type::I32])), + }, + export::ExportDescriptor { + name: "set_test_global", + ty: export::ExternDescriptor::Function(FuncSig::new(vec![Type::I32], vec![])), + }, + export::ExportDescriptor { + name: "update_outside_global", + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![])), + }, + ] + ); +} + +#[test] +fn module_imports_are_correct() { + use wasmer::{CompiledModule, Module}; + + let wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + // TODO: review error messages when `CompiledModule` is not in scope. My hypothesis is that they'll be + // misleading, if so we may want to do something about it. + let module = Module::new(wasm).unwrap(); + + // TODO: test this more later + let imports = module.imports(); + assert_eq!(imports.len(), 2); +} + +#[test] +fn module_can_be_instantiated() { + use wasmer::wasm::{Global, Value}; + use wasmer::{func, imports, CompiledModule, Module}; + + let wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + // TODO: review error messages when `CompiledModule` is not in scope. My hypothesis is that they'll be + // misleading, if so we may want to do something about it. + let module = Module::new(wasm).unwrap(); + + let outside_global = Global::new_mutable(Value::I32(15)); + let import_object = imports! { + "env" => { + "update-func" => func!(|x: i32| x * 2), + "outside-global" => outside_global.clone(), + } + }; + let instance = module.instantiate(&import_object); + + assert!(instance.is_ok()); +} + +#[test] +fn exports_work() { + use wasmer::wasm::{Global, Value}; + use wasmer::{func, imports, CompiledModule, Func, Module, Table}; + + let wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + // TODO: review error messages when `CompiledModule` is not in scope. My hypothesis is that they'll be + // misleading, if so we may want to do something about it. + let module = Module::new(wasm).unwrap(); + + let outside_global = Global::new_mutable(Value::I32(15)); + + let import_object = imports! { + "env" => { + "update-func" => func!(|x: i32| x * 2), + "outside-global" => outside_global.clone(), + } + }; + let instance = module.instantiate(&import_object).unwrap(); + + let ret_2: Func<(), i32> = instance.exports.get("ret_2").unwrap(); + let ret_4: Func<(), i32> = instance.exports.get("ret_4").unwrap(); + let set_test_global: Func = instance.exports.get("set_test_global").unwrap(); + let update_outside_global: Func = instance.exports.get("update_outside_global").unwrap(); + + assert_eq!(ret_2.call(), Ok(2)); + assert_eq!(ret_4.call(), Ok(4)); + + let _test_table: Table = instance.exports.get("test-table").unwrap(); + // TODO: when table get is stablized test this + + let test_global: Global = instance.exports.get("test-global").unwrap(); + // TODO: do we want to make global.get act like exports.get()? + assert_eq!(test_global.get(), Value::I32(3)); + set_test_global.call(17).unwrap(); + assert_eq!(test_global.get(), Value::I32(17)); + + assert_eq!(outside_global.get(), Value::I32(15)); + update_outside_global.call().unwrap(); + assert_eq!(outside_global.get(), Value::I32(15 * 2)); + update_outside_global.call().unwrap(); + assert_eq!(outside_global.get(), Value::I32(15 * 2 * 2)); +} + +// copied from Rust stdlib: https://doc.rust-lang.org/nightly/nightly-rustc/src/serialize/leb128.rs.html#4-14 +macro_rules! impl_write_unsigned_leb128 { + ($fn_name:ident, $int_ty:ident) => { + #[inline] + pub fn $fn_name(out: &mut Vec, mut value: $int_ty) { + loop { + if value < 0x80 { + out.push(value as u8); + break; + } else { + out.push(((value & 0x7f) | 0x80) as u8); + value >>= 7; + } + } + } + }; +} + +impl_write_unsigned_leb128!(write_u16_leb128, u16); +impl_write_unsigned_leb128!(write_u32_leb128, u32); +impl_write_unsigned_leb128!(write_u64_leb128, u64); +impl_write_unsigned_leb128!(write_u128_leb128, u128); +impl_write_unsigned_leb128!(write_usize_leb128, usize); diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml new file mode 100644 index 00000000000..85ed576b975 --- /dev/null +++ b/lib/api/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "wasmer" +version = "0.16.2" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +publish = true +description = "The high-level public API of the Wasmer WebAssembly runtime" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wasmer-runtime-core = { version = "0.16.2", path = "../runtime-core" } + +[dependencies.wasmer-singlepass-backend] +path = "../singlepass-backend" +version = "0.16.2" +optional = true + +[dependencies.wasmer-llvm-backend] +path = "../llvm-backend" +optional = true + +[dependencies.wasmer-clif-backend] +path = "../clif-backend" +version = "0.16.2" +optional = true + +[features] +default = ["cranelift", "default-backend-cranelift"] + +singlepass = ["wasmer-singlepass-backend"] +llvm = ["wasmer-llvm-backend"] +cranelift = ["wasmer-clif-backend"] + +default-backend-singlepass = ["singlepass"] +default-backend-llvm = ["llvm"] +default-backend-cranelift = ["cranelift"] + +deterministic-execution = ["wasmer-singlepass-backend/deterministic-execution", "wasmer-runtime-core/deterministic-execution"] \ No newline at end of file diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs new file mode 100644 index 00000000000..4968e949394 --- /dev/null +++ b/lib/api/src/lib.rs @@ -0,0 +1,219 @@ +#![deny( + dead_code, +// missing_docs, + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +// Aspirational. I hope to have no unsafe code in this crate. +#![forbid(unsafe_code)] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + +//! TODO: Write high value, high-level API intro docs here +//! Intro/background information +//! +//! quick links to places in this document/other crates/standards etc. +//! +//! example code, link to projects using it +//! +//! more info, what to do if you run into problems + +pub use crate::module::*; +pub use wasmer_runtime_core::instance::{DynFunc, Instance}; +pub use wasmer_runtime_core::memory::Memory; +pub use wasmer_runtime_core::table::Table; +pub use wasmer_runtime_core::Func; +pub use wasmer_runtime_core::{func, imports}; + +pub mod module { + //! Types and functions for WebAssembly modules. + //! + //! # Usage + //! ## Create a Module + //! + //! ``` + //! ``` + //! + //! ## Get the exports from a Module + //! ``` + //! # use wasmer::*; + //! # fn get_exports(module: &Module) { + //! let exports: Vec = module.exports().collect(); + //! # } + //! ``` + // TODO: verify that this is the type we want to export, with extra methods on it + pub use wasmer_runtime_core::module::Module; + // should this be in here? + pub use wasmer_runtime_core::types::{ExportDescriptor, ExternDescriptor, ImportDescriptor}; + // TODO: implement abstract module API +} + +pub mod memory { + //! Types and functions for Wasm linear memory. + pub use wasmer_runtime_core::memory::{Atomically, Memory, MemoryView}; +} + +pub mod wasm { + //! Various types exposed by the Wasmer Runtime. + //! + //! TODO: Add index with links to sub sections + //! + //! # Globals + //! + //! # Tables + pub use wasmer_runtime_core::global::Global; + pub use wasmer_runtime_core::table::Table; + pub use wasmer_runtime_core::types::{ExportDescriptor, ExternDescriptor, ImportDescriptor}; + pub use wasmer_runtime_core::types::{ + FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type, Value, + }; +} + +pub mod import { + //! Types and functions for Wasm imports. + pub use wasmer_runtime_core::types::{ExternDescriptor, ImportDescriptor}; + pub use wasmer_runtime_core::{func, imports}; +} + +pub mod export { + //! Types and functions for Wasm exports. + pub use wasmer_runtime_core::types::{ExportDescriptor, ExternDescriptor}; +} + +pub mod units { + //! Various unit types. + pub use wasmer_runtime_core::units::{Bytes, Pages}; +} + +pub mod types { + //! Types used in the Wasm runtime and conversion functions. + pub use wasmer_runtime_core::types::{ + ElementType, FuncDescriptor, FuncSig, GlobalDescriptor, GlobalInit, MemoryDescriptor, + TableDescriptor, Type, Value, ValueType, + }; +} + +pub mod error { + //! Various error types returned by Wasmer APIs. + pub use wasmer_runtime_core::error::{CompileError, CompileResult}; + + #[derive(Debug)] + pub enum CompileFromFileError { + CompileError(CompileError), + IoError(std::io::Error), + } + + impl std::fmt::Display for CompileFromFileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompileFromFileError::CompileError(ce) => write!(f, "{}", ce), + CompileFromFileError::IoError(ie) => write!(f, "{}", ie), + } + } + } + + impl std::error::Error for CompileFromFileError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + CompileFromFileError::CompileError(ce) => Some(ce), + CompileFromFileError::IoError(ie) => Some(ie), + } + } + } + + impl From for CompileFromFileError { + fn from(other: CompileError) -> Self { + CompileFromFileError::CompileError(other) + } + } + impl From for CompileFromFileError { + fn from(other: std::io::Error) -> Self { + CompileFromFileError::IoError(other) + } + } +} + +/// Idea for generic trait; consider rename; it will need to be moved somewhere else +pub trait CompiledModule { + fn new(bytes: impl AsRef<[u8]>) -> error::CompileResult; + fn from_binary(bytes: impl AsRef<[u8]>) -> error::CompileResult; + fn from_binary_unchecked(bytes: impl AsRef<[u8]>) -> error::CompileResult; + fn from_file(file: impl AsRef) -> Result; + + fn validate(bytes: impl AsRef<[u8]>) -> error::CompileResult<()>; +} + +use wasmer_runtime_core::backend::Compiler; + +/// Copied from runtime core; TODO: figure out what we want to do here +pub fn default_compiler() -> impl Compiler { + #[cfg(any( + all( + feature = "default-backend-llvm", + not(feature = "docs"), + any( + feature = "default-backend-cranelift", + feature = "default-backend-singlepass" + ) + ), + all( + not(feature = "docs"), + feature = "default-backend-cranelift", + feature = "default-backend-singlepass" + ) + ))] + compile_error!( + "The `default-backend-X` features are mutually exclusive. Please choose just one" + ); + + #[cfg(all(feature = "default-backend-llvm", not(feature = "docs")))] + use wasmer_llvm_backend::LLVMCompiler as DefaultCompiler; + + #[cfg(all(feature = "default-backend-singlepass", not(feature = "docs")))] + use wasmer_singlepass_backend::SinglePassCompiler as DefaultCompiler; + + #[cfg(any(feature = "default-backend-cranelift", feature = "docs"))] + use wasmer_clif_backend::CraneliftCompiler as DefaultCompiler; + + DefaultCompiler::new() +} + +// this implementation should be moved +impl CompiledModule for Module { + fn new(bytes: impl AsRef<[u8]>) -> error::CompileResult { + let bytes = bytes.as_ref(); + wasmer_runtime_core::compile_with(bytes, &default_compiler()) + } + + fn from_binary(bytes: impl AsRef<[u8]>) -> error::CompileResult { + let bytes = bytes.as_ref(); + wasmer_runtime_core::compile_with(bytes, &default_compiler()) + } + + fn from_binary_unchecked(bytes: impl AsRef<[u8]>) -> error::CompileResult { + // TODO: optimize this + Self::from_binary(bytes) + } + + fn from_file(file: impl AsRef) -> Result { + use std::fs; + use std::io::Read; + let path = file.as_ref(); + let mut f = fs::File::open(path)?; + // TODO: ideally we can support a streaming compilation API and not have to read in the entire file + let mut bytes = vec![]; + f.read_to_end(&mut bytes)?; + + Ok(Module::from_binary(bytes.as_slice())?) + } + + fn validate(bytes: impl AsRef<[u8]>) -> error::CompileResult<()> { + // TODO: optimize this + let _ = Self::from_binary(bytes)?; + Ok(()) + } +} diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index b19f751d377..cf607e1b3fc 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -33,7 +33,7 @@ use wasmer_runtime_core::{ types::{ElementType, FuncSig, MemoryDescriptor, TableDescriptor, Type, Value}, units::Pages, vm::Ctx, - Func, Instance, IsExport, Module, + DynFunc, Func, Instance, IsExport, Module, }; #[cfg(unix)] @@ -181,75 +181,89 @@ impl<'a> EmscriptenData<'a> { globals: &'a EmscriptenGlobalsData, mapped_dirs: HashMap, ) -> EmscriptenData<'a> { - let malloc = instance.func("_malloc").or(instance.func("malloc")).ok(); - let free = instance.func("_free").or(instance.func("free")).ok(); + let malloc = instance + .exports + .get("_malloc") + .or(instance.exports.get("malloc")) + .ok(); + let free = instance + .exports + .get("_free") + .or(instance.exports.get("free")) + .ok(); let memalign = instance - .func("_memalign") - .or(instance.func("memalign")) + .exports + .get("_memalign") + .or(instance.exports.get("memalign")) + .ok(); + let memset = instance + .exports + .get("_memset") + .or(instance.exports.get("memset")) .ok(); - let memset = instance.func("_memset").or(instance.func("memset")).ok(); - let stack_alloc = instance.func("stackAlloc").ok(); - - let dyn_call_i = instance.func("dynCall_i").ok(); - let dyn_call_ii = instance.func("dynCall_ii").ok(); - let dyn_call_iii = instance.func("dynCall_iii").ok(); - let dyn_call_iiii = instance.func("dynCall_iiii").ok(); - let dyn_call_iifi = instance.func("dynCall_iifi").ok(); - let dyn_call_v = instance.func("dynCall_v").ok(); - let dyn_call_vi = instance.func("dynCall_vi").ok(); - let dyn_call_vii = instance.func("dynCall_vii").ok(); - let dyn_call_viii = instance.func("dynCall_viii").ok(); - let dyn_call_viiii = instance.func("dynCall_viiii").ok(); + let stack_alloc = instance.exports.get("stackAlloc").ok(); + + let dyn_call_i = instance.exports.get("dynCall_i").ok(); + let dyn_call_ii = instance.exports.get("dynCall_ii").ok(); + let dyn_call_iii = instance.exports.get("dynCall_iii").ok(); + let dyn_call_iiii = instance.exports.get("dynCall_iiii").ok(); + let dyn_call_iifi = instance.exports.get("dynCall_iifi").ok(); + let dyn_call_v = instance.exports.get("dynCall_v").ok(); + let dyn_call_vi = instance.exports.get("dynCall_vi").ok(); + let dyn_call_vii = instance.exports.get("dynCall_vii").ok(); + let dyn_call_viii = instance.exports.get("dynCall_viii").ok(); + let dyn_call_viiii = instance.exports.get("dynCall_viiii").ok(); // round 2 - let dyn_call_dii = instance.func("dynCall_dii").ok(); - let dyn_call_diiii = instance.func("dynCall_diiii").ok(); - let dyn_call_iiiii = instance.func("dynCall_iiiii").ok(); - let dyn_call_iiiiii = instance.func("dynCall_iiiiii").ok(); - let dyn_call_iiiiiii = instance.func("dynCall_iiiiiii").ok(); - let dyn_call_iiiiiiii = instance.func("dynCall_iiiiiiii").ok(); - let dyn_call_iiiiiiiii = instance.func("dynCall_iiiiiiiii").ok(); - let dyn_call_iiiiiiiiii = instance.func("dynCall_iiiiiiiiii").ok(); - let dyn_call_iiiiiiiiiii = instance.func("dynCall_iiiiiiiiiii").ok(); - let dyn_call_vd = instance.func("dynCall_vd").ok(); - let dyn_call_viiiii = instance.func("dynCall_viiiii").ok(); - let dyn_call_viiiiii = instance.func("dynCall_viiiiii").ok(); - let dyn_call_viiiiiii = instance.func("dynCall_viiiiiii").ok(); - let dyn_call_viiiiiiii = instance.func("dynCall_viiiiiiii").ok(); - let dyn_call_viiiiiiiii = instance.func("dynCall_viiiiiiiii").ok(); - let dyn_call_viiiiiiiiii = instance.func("dynCall_viiiiiiiiii").ok(); - let dyn_call_iij = instance.func("dynCall_iij").ok(); - let dyn_call_iji = instance.func("dynCall_iji").ok(); - let dyn_call_iiji = instance.func("dynCall_iiji").ok(); - let dyn_call_iiijj = instance.func("dynCall_iiijj").ok(); - let dyn_call_j = instance.func("dynCall_j").ok(); - let dyn_call_ji = instance.func("dynCall_ji").ok(); - let dyn_call_jii = instance.func("dynCall_jii").ok(); - let dyn_call_jij = instance.func("dynCall_jij").ok(); - let dyn_call_jjj = instance.func("dynCall_jjj").ok(); - let dyn_call_viiij = instance.func("dynCall_viiij").ok(); - let dyn_call_viiijiiii = instance.func("dynCall_viiijiiii").ok(); - let dyn_call_viiijiiiiii = instance.func("dynCall_viiijiiiiii").ok(); - let dyn_call_viij = instance.func("dynCall_viij").ok(); - let dyn_call_viiji = instance.func("dynCall_viiji").ok(); - let dyn_call_viijiii = instance.func("dynCall_viijiii").ok(); - let dyn_call_viijj = instance.func("dynCall_viijj").ok(); - let dyn_call_vj = instance.func("dynCall_vj").ok(); - let dyn_call_vjji = instance.func("dynCall_vjji").ok(); - let dyn_call_vij = instance.func("dynCall_vij").ok(); - let dyn_call_viji = instance.func("dynCall_viji").ok(); - let dyn_call_vijiii = instance.func("dynCall_vijiii").ok(); - let dyn_call_vijj = instance.func("dynCall_vijj").ok(); - let dyn_call_viid = instance.func("dynCall_viid").ok(); - let dyn_call_vidd = instance.func("dynCall_vidd").ok(); - let dyn_call_viidii = instance.func("dynCall_viidii").ok(); - let dyn_call_viidddddddd = instance.func("dynCall_viidddddddd").ok(); - - let stack_save = instance.func("stackSave").ok(); - let stack_restore = instance.func("stackRestore").ok(); + let dyn_call_dii = instance.exports.get("dynCall_dii").ok(); + let dyn_call_diiii = instance.exports.get("dynCall_diiii").ok(); + let dyn_call_iiiii = instance.exports.get("dynCall_iiiii").ok(); + let dyn_call_iiiiii = instance.exports.get("dynCall_iiiiii").ok(); + let dyn_call_iiiiiii = instance.exports.get("dynCall_iiiiiii").ok(); + let dyn_call_iiiiiiii = instance.exports.get("dynCall_iiiiiiii").ok(); + let dyn_call_iiiiiiiii = instance.exports.get("dynCall_iiiiiiiii").ok(); + let dyn_call_iiiiiiiiii = instance.exports.get("dynCall_iiiiiiiiii").ok(); + let dyn_call_iiiiiiiiiii = instance.exports.get("dynCall_iiiiiiiiiii").ok(); + let dyn_call_vd = instance.exports.get("dynCall_vd").ok(); + let dyn_call_viiiii = instance.exports.get("dynCall_viiiii").ok(); + let dyn_call_viiiiii = instance.exports.get("dynCall_viiiiii").ok(); + let dyn_call_viiiiiii = instance.exports.get("dynCall_viiiiiii").ok(); + let dyn_call_viiiiiiii = instance.exports.get("dynCall_viiiiiiii").ok(); + let dyn_call_viiiiiiiii = instance.exports.get("dynCall_viiiiiiiii").ok(); + let dyn_call_viiiiiiiiii = instance.exports.get("dynCall_viiiiiiiiii").ok(); + let dyn_call_iij = instance.exports.get("dynCall_iij").ok(); + let dyn_call_iji = instance.exports.get("dynCall_iji").ok(); + let dyn_call_iiji = instance.exports.get("dynCall_iiji").ok(); + let dyn_call_iiijj = instance.exports.get("dynCall_iiijj").ok(); + let dyn_call_j = instance.exports.get("dynCall_j").ok(); + let dyn_call_ji = instance.exports.get("dynCall_ji").ok(); + let dyn_call_jii = instance.exports.get("dynCall_jii").ok(); + let dyn_call_jij = instance.exports.get("dynCall_jij").ok(); + let dyn_call_jjj = instance.exports.get("dynCall_jjj").ok(); + let dyn_call_viiij = instance.exports.get("dynCall_viiij").ok(); + let dyn_call_viiijiiii = instance.exports.get("dynCall_viiijiiii").ok(); + let dyn_call_viiijiiiiii = instance.exports.get("dynCall_viiijiiiiii").ok(); + let dyn_call_viij = instance.exports.get("dynCall_viij").ok(); + let dyn_call_viiji = instance.exports.get("dynCall_viiji").ok(); + let dyn_call_viijiii = instance.exports.get("dynCall_viijiii").ok(); + let dyn_call_viijj = instance.exports.get("dynCall_viijj").ok(); + let dyn_call_vj = instance.exports.get("dynCall_vj").ok(); + let dyn_call_vjji = instance.exports.get("dynCall_vjji").ok(); + let dyn_call_vij = instance.exports.get("dynCall_vij").ok(); + let dyn_call_viji = instance.exports.get("dynCall_viji").ok(); + let dyn_call_vijiii = instance.exports.get("dynCall_vijiii").ok(); + let dyn_call_vijj = instance.exports.get("dynCall_vijj").ok(); + let dyn_call_viid = instance.exports.get("dynCall_viid").ok(); + let dyn_call_vidd = instance.exports.get("dynCall_vidd").ok(); + let dyn_call_viidii = instance.exports.get("dynCall_viidii").ok(); + let dyn_call_viidddddddd = instance.exports.get("dynCall_viidddddddd").ok(); + + let stack_save = instance.exports.get("stackSave").ok(); + let stack_restore = instance.exports.get("stackRestore").ok(); let set_threw = instance - .func("_setThrew") - .or(instance.func("setThrew")) + .exports + .get("_setThrew") + .or(instance.exports.get("setThrew")) .ok(); EmscriptenData { @@ -335,12 +349,15 @@ impl<'a> EmscriptenData<'a> { pub fn set_up_emscripten(instance: &mut Instance) -> CallResult<()> { // ATINIT // (used by C++) - if let Ok(_func) = instance.dyn_func("globalCtors") { - instance.call("globalCtors", &[])?; + if let Ok(func) = instance.exports.get::("globalCtors") { + func.call(&[])?; } - if let Ok(_func) = instance.dyn_func("___emscripten_environ_constructor") { - instance.call("___emscripten_environ_constructor", &[])?; + if let Ok(func) = instance + .exports + .get::("___emscripten_environ_constructor") + { + func.call(&[])?; } Ok(()) } @@ -350,9 +367,9 @@ pub fn set_up_emscripten(instance: &mut Instance) -> CallResult<()> { /// /// If you don't want to set it up yourself, consider using [`run_emscripten_instance`]. pub fn emscripten_call_main(instance: &mut Instance, path: &str, args: &[&str]) -> CallResult<()> { - let (func_name, main_func) = match instance.dyn_func("_main") { + let (func_name, main_func) = match instance.exports.get::("_main") { Ok(func) => Ok(("_main", func)), - Err(_e) => match instance.dyn_func("main") { + Err(_e) => match instance.exports.get::("main") { Ok(func) => Ok(("main", func)), Err(e) => Err(e), }, @@ -363,13 +380,12 @@ pub fn emscripten_call_main(instance: &mut Instance, path: &str, args: &[&str]) let mut new_args = vec![path]; new_args.extend(args); let (argc, argv) = store_module_arguments(instance.context_mut(), new_args); - instance.call( - func_name, - &[Value::I32(argc as i32), Value::I32(argv as i32)], - )?; + let func: DynFunc = instance.exports.get(func_name)?; + func.call(&[Value::I32(argc as i32), Value::I32(argv as i32)])?; } 0 => { - instance.call(func_name, &[])?; + let func: DynFunc = instance.exports.get(func_name)?; + func.call(&[])?; } _ => { return Err(CallError::Resolve(ResolveError::ExportWrongType { @@ -403,7 +419,8 @@ pub fn run_emscripten_instance( debug!("Running entry point: {}", &ep); let arg = unsafe { allocate_cstr_on_stack(instance.context_mut(), args[0]).0 }; //let (argc, argv) = store_module_arguments(instance.context_mut(), args); - instance.call(&ep, &[Value::I32(arg as i32)])?; + let func: DynFunc = instance.exports.get(&ep)?; + func.call(&[Value::I32(arg as i32)])?; } else { emscripten_call_main(instance, path, &args)?; } diff --git a/lib/middleware-common-tests/benches/metering_benchmark.rs b/lib/middleware-common-tests/benches/metering_benchmark.rs index 7c425ce991e..501e0178f1a 100644 --- a/lib/middleware-common-tests/benches/metering_benchmark.rs +++ b/lib/middleware-common-tests/benches/metering_benchmark.rs @@ -190,7 +190,7 @@ fn bench_metering(c: &mut Criterion) { let module = compile_with(&wasm_binary, &compiler).unwrap(); let import_object = imports! {}; let instance = module.instantiate(&import_object).unwrap(); - let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + let add_to: Func<(i32, i32), i32> = instance.exports.get("add_to").unwrap(); b.iter(|| black_box(add_to.call(100, 4))) }) .with_function("Gas Metering", |b| { @@ -203,7 +203,7 @@ fn bench_metering(c: &mut Criterion) { }, }; let gas_instance = gas_module.instantiate(&gas_import_object).unwrap(); - let gas_add_to: Func<(i32, i32), i32> = gas_instance.func("add_to").unwrap(); + let gas_add_to: Func<(i32, i32), i32> = gas_instance.exports.get("add_to").unwrap(); b.iter(|| black_box(gas_add_to.call(100, 4))) }) .with_function("Built-in Metering", |b| { @@ -215,7 +215,8 @@ fn bench_metering(c: &mut Criterion) { .instantiate(&metering_import_object) .unwrap(); metering::set_points_used(&mut metering_instance, 0u64); - let metering_add_to: Func<(i32, i32), i32> = metering_instance.func("add_to").unwrap(); + let metering_add_to: Func<(i32, i32), i32> = + metering_instance.exports.get("add_to").unwrap(); b.iter(|| black_box(metering_add_to.call(100, 4))) }), ); diff --git a/lib/middleware-common-tests/src/lib.rs b/lib/middleware-common-tests/src/lib.rs index 02f953fc474..70b373f4dd5 100644 --- a/lib/middleware-common-tests/src/lib.rs +++ b/lib/middleware-common-tests/src/lib.rs @@ -103,7 +103,7 @@ mod tests { set_points_used(&mut instance, 0u64); - let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + let add_to: Func<(i32, i32), i32> = instance.exports.get("add_to").unwrap(); let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { push_code_version(CodeVersion { @@ -145,7 +145,7 @@ mod tests { set_points_used(&mut instance, 0u64); - let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + let add_to: Func<(i32, i32), i32> = instance.exports.get("add_to").unwrap(); let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { push_code_version(CodeVersion { diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index f9ac6df9ecf..ca9c0cdb907 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -2,19 +2,48 @@ use std::{convert::TryInto, sync::Arc}; use wasmer_runtime_core::{ compile_with, error::RuntimeError, + global::Global, imports, memory::Memory, typed_func::{DynamicFunc, Func}, types::{FuncSig, MemoryDescriptor, Type, Value}, units::Pages, - vm, Instance, + vm, DynFunc, Instance, }; use wasmer_runtime_core_tests::{get_compiler, wat2wasm}; +#[test] +fn 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.func(stringify!($function)).expect(concat!("Failed to get the `", stringify!($function), "` export function.")); + 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 ),* ); diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 8729d979752..959a73cc710 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -1,9 +1,15 @@ -//! The export module contains the implementation data structures and helper functions used to -//! manipulate and access a wasm module's exports including memories, tables, globals, and -//! functions. +//! This module contains types to manipulate and access a Wasm module's exports +//! including memories, tables, globals, and functions. use crate::{ - global::Global, instance::InstanceInner, memory::Memory, module::ExportIndex, - module::ModuleInner, table::Table, types::FuncSig, vm, + error, + global::Global, + instance::{Exports, InstanceInner}, + memory::Memory, + module::ExportIndex, + module::ModuleInner, + table::Table, + types::FuncSig, + vm, }; use indexmap::map::Iter as IndexMapIter; use std::{ptr::NonNull, sync::Arc}; @@ -92,3 +98,10 @@ impl<'a> Iterator for ExportIter<'a> { )) } } + +/// This trait is used to mark types as gettable from an [`Instance`]. +pub trait Exportable<'a>: Sized { + /// Implementation of how to get the export corresponding to the implementing type + /// from an [`Instance`] by name. + fn get_self(exports: &'a Exports, name: &str) -> error::ResolveResult; +} diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index fff1797d777..48e59d4ec65 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -1,10 +1,12 @@ -//! The instance module contains the implementation data structures and helper functions used to -//! manipulate and access wasm instances. +//! This module contains types for manipulating and accessing Wasm instances. +//! +//! An "instance", or "instantiated module", is a compiled WebAssembly [`Module`] with its +//! corresponding imports (via [`ImportObject`]) that is ready to execute. use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, - error::{CallError, CallResult, ResolveError, ResolveResult, Result, RuntimeError}, - export::{Context, Export, ExportIter, FuncPointer}, + error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError}, + export::{Context, Export, ExportIter, Exportable, FuncPointer}, global::Global, import::{ImportObject, LikeNamespace}, loader::Loader, @@ -19,6 +21,7 @@ use crate::{ }; use smallvec::{smallvec, SmallVec}; use std::{ + borrow::Borrow, mem, pin::Pin, ptr::{self, NonNull}, @@ -53,6 +56,8 @@ pub struct Instance { /// Reference to the module used to instantiate this instance. pub module: Arc, inner: Pin>, + /// The exports of this instance. + pub exports: Exports, #[allow(dead_code)] import_object: ImportObject, } @@ -89,9 +94,15 @@ impl Instance { }; Box::leak(vmctx); + let exports = Exports { + module: module.clone(), + instance_inner: &*inner as *const InstanceInner, + }; + let instance = Instance { module, inner, + exports, import_object: imports.clone_ref(), }; @@ -170,103 +181,21 @@ impl Instance { /// # Ok(()) /// # } /// ``` + #[deprecated( + since = "0.17.0", + note = "Please use `instance.exports.get(name)` instead" + )] pub fn func(&self, name: &str) -> ResolveResult> where Args: WasmTypeList, Rets: WasmTypeList, { - let export_index = - self.module - .info - .exports - .get(name) - .ok_or_else(|| ResolveError::ExportNotFound { - name: name.to_string(), - })?; - - if let ExportIndex::Func(func_index) = export_index { - let sig_index = *self - .module - .info - .func_assoc - .get(*func_index) - .expect("broken invariant, incorrect func index"); - let signature = - SigRegistry.lookup_signature_ref(&self.module.info.signatures[sig_index]); - - if signature.params() != Args::types() || signature.returns() != Rets::types() { - Err(ResolveError::Signature { - expected: (*signature).clone(), - found: Args::types().to_vec(), - })?; - } - - let ctx = match func_index.local_or_import(&self.module.info) { - LocalOrImport::Local(_) => self.inner.vmctx, - LocalOrImport::Import(imported_func_index) => unsafe { - self.inner.import_backing.vm_functions[imported_func_index] - .func_ctx - .as_ref() - } - .vmctx - .as_ptr(), - }; - - let func_wasm_inner = self - .module - .runnable_module - .get_trampoline(&self.module.info, sig_index) - .unwrap(); - - let (func_ptr, func_env) = match func_index.local_or_import(&self.module.info) { - LocalOrImport::Local(local_func_index) => ( - self.module - .runnable_module - .get_func(&self.module.info, local_func_index) - .unwrap(), - None, - ), - LocalOrImport::Import(import_func_index) => { - let imported_func = &self.inner.import_backing.vm_functions[import_func_index]; - - ( - NonNull::new(imported_func.func as *mut _).unwrap(), - unsafe { imported_func.func_ctx.as_ref() }.func_env, - ) - } - }; - - let typed_func: Func = - unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, func_env, ctx) }; - - Ok(typed_func) - } else { - Err(ResolveError::ExportWrongType { - name: name.to_string(), - } - .into()) - } + self.exports.get(name) } /// Resolve a function by name. pub fn resolve_func(&self, name: &str) -> ResolveResult { - let export_index = - self.module - .info - .exports - .get(name) - .ok_or_else(|| ResolveError::ExportNotFound { - name: name.to_string(), - })?; - - if let ExportIndex::Func(func_index) = export_index { - Ok(func_index.index()) - } else { - Err(ResolveError::ExportWrongType { - name: name.to_string(), - } - .into()) - } + resolve_func_index(&*self.module, name).map(|fi| fi.index()) } /// This returns the representation of a function that can be called @@ -283,38 +212,12 @@ impl Instance { /// # Ok(()) /// # } /// ``` + #[deprecated( + since = "0.17.0", + note = "Please use `instance.exports.get(name)` instead" + )] pub fn dyn_func(&self, name: &str) -> ResolveResult { - let export_index = - self.module - .info - .exports - .get(name) - .ok_or_else(|| ResolveError::ExportNotFound { - name: name.to_string(), - })?; - - if let ExportIndex::Func(func_index) = export_index { - let sig_index = *self - .module - .info - .func_assoc - .get(*func_index) - .expect("broken invariant, incorrect func index"); - let signature = - SigRegistry.lookup_signature_ref(&self.module.info.signatures[sig_index]); - - Ok(DynFunc { - signature, - module: &self.module, - instance_inner: &self.inner, - func_index: *func_index, - }) - } else { - Err(ResolveError::ExportWrongType { - name: name.to_string(), - } - .into()) - } + self.exports.get(name) } /// Call an exported WebAssembly function given the export name. @@ -327,6 +230,23 @@ impl Instance { /// This returns `CallResult>` in order to support /// the future multi-value returns WebAssembly feature. /// + /// Consider using the more explicit [`Exports::get`]` with [`DynFunc::call`] + /// instead. For example: + /// + /// ``` + /// # use wasmer_runtime_core::types::Value; + /// # use wasmer_runtime_core::error::Result; + /// # use wasmer_runtime_core::Instance; + /// # use wasmer_runtime_core::DynFunc; + /// # fn call_foo(instance: &mut Instance) -> Result<()> { + /// // ... + /// let foo: DynFunc = instance.exports.get("foo")?; + /// let results = foo.call(&[Value::I32(42)])?; + /// // ... + /// # Ok(()) + /// # } + /// ``` + /// /// # Usage: /// ``` /// # use wasmer_runtime_core::types::Value; @@ -340,37 +260,8 @@ impl Instance { /// # } /// ``` pub fn call(&self, name: &str, params: &[Value]) -> CallResult> { - let export_index = - self.module - .info - .exports - .get(name) - .ok_or_else(|| ResolveError::ExportNotFound { - name: name.to_string(), - })?; - - let func_index = if let ExportIndex::Func(func_index) = export_index { - *func_index - } else { - return Err(CallError::Resolve(ResolveError::ExportWrongType { - name: name.to_string(), - }) - .into()); - }; - - let mut results = Vec::new(); - - call_func_with_index( - &self.module.info, - &**self.module.runnable_module, - &self.inner.import_backing, - self.inner.vmctx, - func_index, - params, - &mut results, - )?; - - Ok(results) + let func: DynFunc = self.exports.get(name)?; + func.call(params) } /// Returns an immutable reference to the @@ -411,6 +302,27 @@ impl Instance { } } +/// Private function used to find the [`FuncIndex`] of a given export. +fn resolve_func_index(module: &ModuleInner, name: &str) -> ResolveResult { + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; + + if let ExportIndex::Func(func_index) = export_index { + Ok(*func_index) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + } + .into()) + } +} + impl InstanceInner { pub(crate) fn get_export_from_index( &self, @@ -814,6 +726,219 @@ impl<'a> DynFunc<'a> { } } +impl<'a> Exportable<'a> for Memory { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { + let (inst_inner, module) = exports.get_inner(); + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; + if let ExportIndex::Memory(idx) = export_index { + Ok(inst_inner.get_memory_from_index(module, *idx)) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) + } + } +} + +impl<'a> Exportable<'a> for Table { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { + let (inst_inner, module) = exports.get_inner(); + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; + if let ExportIndex::Table(idx) = export_index { + Ok(inst_inner.get_table_from_index(module, *idx)) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) + } + } +} + +impl<'a> Exportable<'a> for Global { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { + let (inst_inner, module) = exports.get_inner(); + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; + if let ExportIndex::Global(idx) = export_index { + Ok(inst_inner.get_global_from_index(module, *idx)) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) + } + } +} + +impl<'a> Exportable<'a> for DynFunc<'a> { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { + let (inst_inner, module) = exports.get_inner(); + let func_index = resolve_func_index(module, name)?; + + let sig_index = *module + .info + .func_assoc + .get(func_index) + .expect("broken invariant, incorrect func index"); + let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]); + + Ok(DynFunc { + signature, + module: &module, + instance_inner: &inst_inner, + func_index: func_index, + }) + } +} + +impl<'a, Args: WasmTypeList, Rets: WasmTypeList> Exportable<'a> for Func<'a, Args, Rets, Wasm> { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { + let (inst_inner, module) = exports.get_inner(); + + let func_index = resolve_func_index(module, name)?; + + let sig_index = *module + .info + .func_assoc + .get(func_index) + .expect("broken invariant, incorrect func index"); + let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]); + + if signature.params() != Args::types() || signature.returns() != Rets::types() { + Err(ResolveError::Signature { + expected: (*signature).clone(), + found: Args::types().to_vec(), + })?; + } + + let ctx = match func_index.local_or_import(&module.info) { + LocalOrImport::Local(_) => inst_inner.vmctx, + LocalOrImport::Import(imported_func_index) => unsafe { + inst_inner.import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() + } + .vmctx + .as_ptr(), + }; + + let func_wasm_inner = module + .runnable_module + .get_trampoline(&module.info, sig_index) + .unwrap(); + + let (func_ptr, func_env) = match func_index.local_or_import(&module.info) { + LocalOrImport::Local(local_func_index) => ( + module + .runnable_module + .get_func(&module.info, local_func_index) + .unwrap(), + None, + ), + LocalOrImport::Import(import_func_index) => { + let imported_func = &inst_inner.import_backing.vm_functions[import_func_index]; + + ( + NonNull::new(imported_func.func as *mut _).unwrap(), + unsafe { imported_func.func_ctx.as_ref() }.func_env, + ) + } + }; + + let typed_func: Func = + unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, func_env, ctx) }; + + Ok(typed_func) + } +} + +/// `Exports` is used to get exports like [`Func`]s, [`DynFunc`]s, [`Memory`]s, +/// [`Global`]s, and [`Table`]s from an [`Instance`]. +/// +/// Use `Instance.exports` to get an `Exports` from an [`Instance`]. +pub struct Exports { + // We want to avoid the borrow checker here. + // This is safe because + // 1. `Exports` can't be constructed, its fields inspected (directly or via methods), + // or copied outside of this module/in Instance, so it can't safely outlive `Instance`. + // 2. `InstanceInner` is `Pin>`, thus we know that it will not move. + instance_inner: *const InstanceInner, + module: Arc, +} + +// this is safe because the lifetime of `Exports` is tied to `Instance` and +// `*const InstanceInner` comes from a `Pin>` +unsafe impl Send for Exports {} + +impl Exports { + /// Get an export. + /// + /// ``` + /// # use wasmer_runtime_core::{DynFunc, Func, Instance}; + /// # use wasmer_runtime_core::global::Global; + /// # use wasmer_runtime_core::types::Value; + /// # use wasmer_runtime_core::error::ResolveResult; + /// # fn example_fn(instance: &Instance) -> ResolveResult<()> { + /// // We can get a function as a static `Func` + /// let func: Func = instance.exports.get("my_func")?; + /// let _result = func.call(42); + /// + /// // Or we can get it as a dynamic `DynFunc` + /// let dyn_func: DynFunc = instance.exports.get("my_func")?; + /// let _result= dyn_func.call(&[Value::I32(42)]); + /// + /// // We can also get other exports like `Global`s, `Memory`s, and `Table`s + /// let _counter: Global = instance.exports.get("counter")?; + /// + /// # Ok(()) + /// # } + /// ``` + pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> ResolveResult { + T::get_self(self, name) + } + + /// This method must remain private for `Exports` to be sound. + fn get_inner(&self) -> (&InstanceInner, &ModuleInner) { + let inst_inner = unsafe { &*self.instance_inner }; + let module = self.module.borrow(); + (inst_inner, module) + } + + /// Iterate the exports. + /// + /// ``` + /// # use wasmer_runtime_core::instance::Instance; + /// # fn iterate_exports_example(instance: &Instance) { + /// for (export_name, export_value) in instance.exports.into_iter() { + /// println!("Found export `{}` with value `{:?}`", export_name, export_value); + /// } + /// # } + /// ``` + pub fn into_iter(&self) -> ExportIter { + let (inst_inner, module) = self.get_inner(); + ExportIter::new(&module, &inst_inner) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/lib/runtime-core/src/macros.rs b/lib/runtime-core/src/macros.rs index 8251a4133c2..2c30ca8eadc 100644 --- a/lib/runtime-core/src/macros.rs +++ b/lib/runtime-core/src/macros.rs @@ -76,7 +76,7 @@ macro_rules! func { /// ``` #[macro_export] macro_rules! imports { - ( $( $ns_name:expr => $ns:tt, )* ) => {{ + ( $( $ns_name:expr => $ns:tt ),* $(,)? ) => {{ use $crate::{ import::{ImportObject, Namespace}, }; @@ -91,7 +91,7 @@ macro_rules! imports { import_object }}; - ($state_gen:expr, $( $ns_name:expr => $ns:tt, )* ) => {{ + ($state_gen:expr, $( $ns_name:expr => $ns:tt ),* $(,)? ) => {{ use $crate::{ import::{ImportObject, Namespace}, }; @@ -111,7 +111,7 @@ macro_rules! imports { #[macro_export] #[doc(hidden)] macro_rules! __imports_internal { - ( { $( $imp_name:expr => $import_item:expr, )* } ) => {{ + ( { $( $imp_name:expr => $import_item:expr ),* $(,)? } ) => {{ let mut ns = Namespace::new(); $( ns.insert($imp_name, $import_item); @@ -126,7 +126,7 @@ macro_rules! __imports_internal { #[macro_export] #[doc(hidden)] macro_rules! namespace { - ( $( $imp_name:expr => $import_item:expr, )* ) => {{ + ( $( $imp_name:expr => $import_item:expr ),* $(,)? ) => {{ let mut ns = $crate::import::Namespace::new(); $( ns.insert($imp_name, $import_item); @@ -134,3 +134,85 @@ macro_rules! namespace { ns }}; } + +#[cfg(test)] +mod test { + fn func(arg: i32) -> i32 { + arg + 1 + } + + #[test] + fn imports_macro_allows_trailing_comma_and_none() { + let _ = imports! { + "env" => { + "func" => func!(func), + }, + }; + let _ = imports! { + "env" => { + "func" => func!(func), + } + }; + let _ = imports! { + "env" => { + "func" => func!(func), + }, + "abc" => { + "def" => func!(func), + } + }; + let _ = imports! { + "env" => { + "func" => func!(func) + }, + }; + let _ = imports! { + "env" => { + "func" => func!(func) + } + }; + let _ = imports! { + "env" => { + "func1" => func!(func), + "func2" => func!(func) + } + }; + let _ = imports! { + "env" => { + "func1" => func!(func), + "func2" => func!(func), + } + }; + } + + #[test] + fn imports_macro_allows_trailing_comma_and_none_with_state() { + use std::{ffi, ptr}; + + fn dtor(_arg: *mut ffi::c_void) {} + fn state_creator() -> (*mut ffi::c_void, fn(*mut ffi::c_void)) { + (ptr::null_mut() as *mut ffi::c_void, dtor) + } + let _ = imports! { + state_creator, + "env" => { + "func1" => func!(func), + "func2" => func!(func), + } + }; + let _ = imports! { + state_creator, + "env" => { + "func1" => func!(func), + "func2" => func!(func) + }, + }; + let _ = imports! { + state_creator, + "env" => { + "func1" => func!(func), + "func2" => func!(func), + }, + }; + } +} diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 5814d9ff87e..9402d924daa 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -1,5 +1,7 @@ -//! The module module contains the implementation data structures and helper functions used to -//! manipulate and access wasm modules. +//! This module contains the types to manipulate and access Wasm modules. +//! +//! A Wasm module is the artifact of compiling WebAssembly. Wasm modules are not executable +//! until they're instantiated with imports (via [`ImportObject`]). use crate::{ backend::RunnableModule, cache::{Artifact, Error as CacheError}, @@ -7,10 +9,10 @@ use crate::{ import::ImportObject, structures::{Map, TypedIndex}, types::{ - FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex, - ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer, - LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, MemoryIndex, - SigIndex, TableDescriptor, TableIndex, + ExportDescriptor, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, + ImportDescriptor, ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, + ImportedTableIndex, Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, + MemoryDescriptor, MemoryIndex, SigIndex, TableDescriptor, TableIndex, }, Instance, }; @@ -52,6 +54,10 @@ pub struct ModuleInfo { pub imported_globals: Map, /// Map of string to export index. + // Implementation note: this should maintain the order that the exports appear in the + // Wasm module. Be careful not to use APIs that may break the order! + // Side note, because this is public we can't actually guarantee that it will remain + // in order. pub exports: IndexMap, /// Vector of data initializers. @@ -164,6 +170,130 @@ impl Module { pub fn info(&self) -> &ModuleInfo { &self.inner.info } + + /// Iterate over the [`ExportDescriptor`]s of the exports that this module provides. + pub(crate) fn exports_iter(&self) -> impl Iterator + '_ { + self.info() + .exports + .iter() + .map(move |(name, &ei)| ExportDescriptor { + name, + ty: match ei { + ExportIndex::Func(f_idx) => { + let sig_idx = self.info().func_assoc[f_idx].into(); + self.info().signatures[sig_idx].clone().into() + } + ExportIndex::Global(g_idx) => { + let info = self.info(); + let local_global_idx = + LocalGlobalIndex::new(g_idx.index() - info.imported_globals.len()); + info.globals[local_global_idx].desc.into() + } + ExportIndex::Memory(m_idx) => { + let info = self.info(); + let local_memory_idx = + LocalMemoryIndex::new(m_idx.index() - info.imported_memories.len()); + info.memories[local_memory_idx].into() + } + ExportIndex::Table(t_idx) => { + let info = self.info(); + let local_table_idx = + LocalTableIndex::new(t_idx.index() - info.imported_tables.len()); + info.tables[local_table_idx].into() + } + }, + }) + } + + /// Get the [`ExportDescriptor`]s of the exports this [`Module`] provides. + pub fn exports(&self) -> Vec { + self.exports_iter().collect() + } + + /// Get the [`ImportDescriptor`]s describing the imports this [`Module`] + /// requires to be instantiated. + pub fn imports(&self) -> Vec { + let mut out = Vec::with_capacity( + self.inner.info.imported_functions.len() + + self.inner.info.imported_memories.len() + + self.inner.info.imported_tables.len() + + self.inner.info.imported_globals.len(), + ); + + /// Lookup the (namespace, name) in the [`ModuleInfo`] by index. + fn get_import_name( + info: &ModuleInfo, + &ImportName { + namespace_index, + name_index, + }: &ImportName, + ) -> (String, String) { + let namespace = info.namespace_table.get(namespace_index).to_string(); + let name = info.name_table.get(name_index).to_string(); + + (namespace, name) + } + + let info = &self.inner.info; + + let imported_functions = info.imported_functions.iter().map(|(idx, import_name)| { + let (namespace, name) = get_import_name(info, import_name); + let sig = info + .signatures + .get(*info.func_assoc.get(FuncIndex::new(idx.index())).unwrap()) + .unwrap(); + ImportDescriptor { + namespace, + name, + ty: sig.into(), + } + }); + let imported_memories = + info.imported_memories + .values() + .map(|(import_name, memory_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + ImportDescriptor { + namespace, + name, + ty: memory_descriptor.into(), + } + }); + let imported_tables = + info.imported_tables + .values() + .map(|(import_name, table_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + ImportDescriptor { + namespace, + name, + ty: table_descriptor.into(), + } + }); + let imported_globals = + info.imported_globals + .values() + .map(|(import_name, global_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + ImportDescriptor { + namespace, + name, + ty: global_descriptor.into(), + } + }); + + out.extend(imported_functions); + out.extend(imported_memories); + out.extend(imported_tables); + out.extend(imported_globals); + out + } + + /// Get the custom sections matching the given name. + pub fn custom_sections(&self, key: impl AsRef) -> Option<&[Vec]> { + let key = key.as_ref(); + self.inner.info.custom_sections.get(key).map(|v| v.as_ref()) + } } impl Clone for Module { diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index 38cb578f27a..b702cdc4f8c 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -7,6 +7,7 @@ use crate::{ vm, }; +use std::convert::TryFrom; use std::{ptr, sync::Arc}; enum AnyfuncInner<'a> { @@ -45,6 +46,46 @@ impl<'a> From> for Anyfunc<'a> { } } +impl<'a> TryFrom> for DynFunc<'a> { + type Error = (); + + fn try_from(anyfunc: Anyfunc<'a>) -> Result { + match anyfunc.inner { + AnyfuncInner::Managed(df) => Ok(df), + _ => Err(()), + } + } +} + +/* +// TODO: implement this when `vm::Anyfunc` is updated (aka avoiding the linear scan in `wrap`) +impl<'a, Args: WasmTypeList, Rets: WasmTypeList> TryFrom> for Func<'a, Args, Rets> { + type Error = (); + + fn try_from(anyfunc: Anyfunc<'a>) -> Result { + match anyfunc.inner { + AnyfuncInner::Host { + ptr, + ctx, + signature, + } => { + // TODO: return more specific error + let ptr = NonNull::new(ptr as _).ok_or(())?; + if signature.params() != Args::types() || signature.returns() != Rets::types() { + // TODO: return more specific error + return Err(()); + } + let wasm = todo!("Figure out how to get typed_func::Wasm"); + // TODO: handle func_env + let func_env = None; + Ok(unsafe { Func::from_raw_parts(wasm, ptr, func_env, ctx) }) + } + _ => Err(()), + } + } +} +*/ + pub struct AnyfuncTable { pub(crate) backing: Vec, max: Option, @@ -98,6 +139,25 @@ impl AnyfuncTable { Some(starting_len) } + // hidden and `pub(crate)` due to incomplete implementation (blocked on `wrap` issue) + #[doc(hidden)] + /// Get The vm::AnyFunc at the given index. + pub(crate) fn get<'outer_table>(&self, index: u32) -> Option> { + let vm_any_func = self.backing.get(index as usize)?; + let signature = SigRegistry.lookup_signature(vm_any_func.sig_id.into()); + // TODO: this function should take a generic type param indicating what type of + // anyfunc we want `host` or `managed` (or perhaps we should just return DynFunc/Func directly here). + // + // The issue with the current implementation is that through `StorableInTable`, we'll call + // `TryFrom for Dynfunc` which will always fail because we always return a `Host` function here. + Some(Anyfunc { + inner: AnyfuncInner::Host { + ptr: vm_any_func.func, + signature, + }, + }) + } + pub fn set(&mut self, index: u32, element: Anyfunc) -> Result<(), ()> { if let Some(slot) = self.backing.get_mut(index as usize) { let anyfunc = match element.inner { diff --git a/lib/runtime-core/src/table/mod.rs b/lib/runtime-core/src/table/mod.rs index 997ce5eb1e4..4d148425840 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -8,6 +8,7 @@ use crate::{ vm, }; use std::{ + convert::TryFrom, fmt, ptr, sync::{Arc, Mutex}, }; @@ -18,12 +19,76 @@ pub use self::anyfunc::Anyfunc; pub(crate) use self::anyfunc::AnyfuncTable; use crate::error::GrowError; +/// Error type indicating why a table access failed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TableAccessError { + /// The index wasn't valid, so no element could be accessed. + IndexError, + + // we'll need this error when we support tables holding more types + #[allow(dead_code)] + /// The type of the table was incorrect, so no element could be accessed. + TypeError, +} + +/// Trait indicates types that can be stored in tables +pub trait StorableInTable: Sized { + /// Attempt to lookup self in the given table. + fn unwrap_self(storage: &TableStorage, index: u32) -> Result; + + /// Wrap value to be stored in a table. + fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError>; +} + +impl<'a, F: Into> + TryFrom>> StorableInTable for F { + fn unwrap_self(storage: &TableStorage, index: u32) -> Result { + match storage { + TableStorage::Anyfunc(ref anyfunc_table) => { + let anyfunc = anyfunc_table + .get(index) + .ok_or(TableAccessError::IndexError)?; + // Should this be a different error value because it's not a table type error? + F::try_from(anyfunc).map_err(|_| TableAccessError::TypeError) + } + } + } + + fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { + let anyfunc: Anyfunc = self.into(); + + match storage { + TableStorage::Anyfunc(ref mut anyfunc_table) => anyfunc_table + .set(index, anyfunc) + .map_err(|_| TableAccessError::IndexError), + } + } +} + /// Kind of table element. +// note to implementors: all types in `Element` should implement `StorableInTable`. pub enum Element<'a> { /// Anyfunc. Anyfunc(Anyfunc<'a>), } +// delegation implementation for `Element` +impl<'a> StorableInTable for Element<'a> { + fn unwrap_self(storage: &TableStorage, index: u32) -> Result { + match storage { + TableStorage::Anyfunc(ref anyfunc_table) => anyfunc_table + .get(index) + .map(Element::Anyfunc) + .ok_or(TableAccessError::IndexError), + } + } + + fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { + match self { + Element::Anyfunc(af) => af.wrap_self(storage, index), + } + } +} + /// Kind of table storage. // #[derive(Debug)] pub enum TableStorage { @@ -89,17 +154,22 @@ impl Table { self.desc } + // Made `pub(crate)` because this API is incomplete, see `anyfunc::AnyfuncTable::get` + // for more information. + #[allow(dead_code)] + /// Get the raw table value at index. A return value of `None` means either that + /// the index or the type wasn't valid. + pub(crate) fn get(&self, index: u32) -> Result { + let guard = self.storage.lock().unwrap(); + let (storage, _) = &*guard; + T::unwrap_self(storage, index) + } + /// Set the element at index. - pub fn set(&self, index: u32, element: Element) -> Result<(), ()> { - let mut storage = self.storage.lock().unwrap(); - match &mut *storage { - (TableStorage::Anyfunc(ref mut anyfunc_table), _) => { - match element { - Element::Anyfunc(anyfunc) => anyfunc_table.set(index, anyfunc), - // _ => panic!("wrong element type for anyfunc table"), - } - } - } + pub fn set(&self, index: u32, element: T) -> Result<(), TableAccessError> { + let mut guard = self.storage.lock().unwrap(); + let (storage, _) = &mut *guard; + T::wrap_self(element, storage, index) } pub(crate) fn anyfunc_direct_access_mut(&self, f: F) -> R diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index a34960134c2..3fa26604a07 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -257,6 +257,7 @@ where Args: WasmTypeList, Rets: WasmTypeList, { + // TODO: document the invariants `unsafe` requires here pub(crate) unsafe fn from_raw_parts( inner: Wasm, func: NonNull, diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index 122aa544a8b..ccf7be1fb13 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -254,7 +254,7 @@ pub enum ElementType { /// Describes the properties of a table including the element types, minimum and optional maximum, /// number of elements in the table. -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct TableDescriptor { /// Type of data stored in this table. pub element: ElementType, @@ -359,6 +359,9 @@ pub struct FuncSig { returns: Cow<'static, [Type]>, } +/// Information about a function. +pub type FuncDescriptor = FuncSig; + impl FuncSig { /// Creates a new function signatures with the given parameter and return types. pub fn new(params: Params, returns: Returns) -> Self @@ -549,6 +552,81 @@ where } } +/// Information about an import such as its type and metadata. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExternDescriptor { + /// The import is a function. + Function(FuncDescriptor), + /// The import is a global variable. + Global(GlobalDescriptor), + /// The import is a Wasm linear memory. + Memory(MemoryDescriptor), + /// The import is a Wasm table. + Table(TableDescriptor), +} + +impl From for ExternDescriptor { + fn from(other: FuncDescriptor) -> Self { + ExternDescriptor::Function(other) + } +} +impl From<&FuncDescriptor> for ExternDescriptor { + fn from(other: &FuncDescriptor) -> Self { + ExternDescriptor::Function(other.clone()) + } +} +impl From for ExternDescriptor { + fn from(other: MemoryDescriptor) -> Self { + ExternDescriptor::Memory(other) + } +} +impl From<&MemoryDescriptor> for ExternDescriptor { + fn from(other: &MemoryDescriptor) -> Self { + ExternDescriptor::Memory(*other) + } +} + +impl From for ExternDescriptor { + fn from(other: TableDescriptor) -> Self { + ExternDescriptor::Table(other) + } +} +impl From<&TableDescriptor> for ExternDescriptor { + fn from(other: &TableDescriptor) -> Self { + ExternDescriptor::Table(*other) + } +} +impl From for ExternDescriptor { + fn from(other: GlobalDescriptor) -> Self { + ExternDescriptor::Global(other) + } +} +impl From<&GlobalDescriptor> for ExternDescriptor { + fn from(other: &GlobalDescriptor) -> Self { + ExternDescriptor::Global(*other) + } +} + +/// A type describing an import that a [`Module`] needs to be instantiated. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImportDescriptor { + /// The namespace that this import is in. + pub namespace: String, + /// The name of the import. + pub name: String, + /// The type of the import and information about the import. + pub ty: ExternDescriptor, +} + +/// Type describing an export that the [`Module`] provides. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExportDescriptor<'a> { + /// The name identifying the export. + pub name: &'a str, + /// The type of the export. + pub ty: ExternDescriptor, +} + #[cfg(test)] mod tests { use crate::types::NativeWasmType; diff --git a/lib/runtime-core/src/units.rs b/lib/runtime-core/src/units.rs index 780ceb034dd..44eac20e01e 100644 --- a/lib/runtime-core/src/units.rs +++ b/lib/runtime-core/src/units.rs @@ -1,4 +1,4 @@ -//! The units module provides common WebAssembly units like [`Pages`] and conversion functions into +//! This module provides common WebAssembly units like [`Pages`] and conversion functions into //! other units. use crate::error::PageError; use std::{ @@ -45,6 +45,12 @@ impl fmt::Debug for Pages { } } +impl From for Pages { + fn from(other: u32) -> Self { + Pages(other) + } +} + /// Units of WebAssembly memory in terms of 8-bit bytes. #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Bytes(pub usize); @@ -61,6 +67,12 @@ impl From for Bytes { } } +impl From for Bytes { + fn from(other: usize) -> Self { + Bytes(other) + } +} + impl Sub for Pages where T: Into, diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 73b9b2e6113..a9a1a2ad9b0 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -716,10 +716,19 @@ impl LocalGlobal { } /// Identifier for a function signature. +/// +/// A transparent `SigIndex` #[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct SigId(pub u32); +use crate::types::SigIndex; +impl From for SigIndex { + fn from(other: SigId) -> SigIndex { + SigIndex::new(other.0 as _) + } +} + /// Caller-checked anyfunc #[derive(Debug, Clone, Copy)] #[repr(C)] diff --git a/lib/runtime/README.md b/lib/runtime/README.md index 08bec247f6e..2f0f98a0e0a 100644 --- a/lib/runtime/README.md +++ b/lib/runtime/README.md @@ -71,6 +71,7 @@ static WASM: &'static [u8] = &[ use wasmer_runtime::{ instantiate, + DynFunc, Value, imports, error, @@ -83,7 +84,8 @@ fn main() -> error::Result<()> { let instance = instantiate(WASM, &import_object)?; let values = instance - .dyn_func("add_one")? + .exports + .get::("add_one")? .call(&[Value::I32(42)])?; assert_eq!(values[0], Value::I32(43)); diff --git a/lib/runtime/benches/many_instances.rs b/lib/runtime/benches/many_instances.rs index c10cf34e96f..dca54624c9a 100644 --- a/lib/runtime/benches/many_instances.rs +++ b/lib/runtime/benches/many_instances.rs @@ -4,7 +4,7 @@ use criterion::Criterion; use tempfile::tempdir; use wasmer_runtime::{ cache::{Cache, FileSystemCache, WasmHash}, - compile, func, imports, instantiate, validate, + compile, func, imports, instantiate, validate, Func, }; use wasmer_runtime_core::vm::Ctx; @@ -71,7 +71,7 @@ fn calling_fn_benchmark(c: &mut Criterion) { ); let instance = instantiate(SIMPLE_WASM, &imports).unwrap(); c.bench_function("calling fn", move |b| { - let entry_point = instance.func::("plugin_entrypoint").unwrap(); + let entry_point: Func = instance.exports.get("plugin_entrypoint").unwrap(); b.iter(|| entry_point.call(2).unwrap()) }); } diff --git a/lib/runtime/examples/call.rs b/lib/runtime/examples/call.rs index e393354c52a..46ccc5aa693 100644 --- a/lib/runtime/examples/call.rs +++ b/lib/runtime/examples/call.rs @@ -64,7 +64,7 @@ fn main() -> Result<(), error::Error> { }, })?; - let foo: Func<(), i32> = instance.func("dbz")?; + let foo: Func<(), i32> = instance.exports.get("dbz")?; let result = foo.call(); diff --git a/lib/runtime/src/cache.rs b/lib/runtime/src/cache.rs index dbcda5b8781..02bcc0f91da 100644 --- a/lib/runtime/src/cache.rs +++ b/lib/runtime/src/cache.rs @@ -187,7 +187,7 @@ mod tests { let import_object = imports! {}; let instance = cached_module.instantiate(&import_object).unwrap(); - let add_one: Func = instance.func("add_one").unwrap(); + let add_one: Func = instance.exports.get("add_one").unwrap(); let value = add_one.call(42).unwrap(); diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 6a3ad1981f9..91d86dada54 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -66,7 +66,7 @@ //! //! let mut instance = instantiate(WASM, &import_object)?; //! -//! let add_one: Func = instance.func("add_one")?; +//! let add_one: Func = instance.exports.get("add_one")?; //! //! let value = add_one.call(42)?; //! diff --git a/lib/runtime/tests/error_propagation.rs b/lib/runtime/tests/error_propagation.rs index 2a725e82f90..c50344ba012 100644 --- a/lib/runtime/tests/error_propagation.rs +++ b/lib/runtime/tests/error_propagation.rs @@ -36,7 +36,7 @@ fn error_propagation() { }) .unwrap(); - let foo: Func<(), ()> = instance.func("call_err").unwrap(); + let foo: Func<(), ()> = instance.exports.get("call_err").unwrap(); let result = foo.call(); diff --git a/lib/wasi-tests/src/lib.rs b/lib/wasi-tests/src/lib.rs index b65021aeb04..447cd40fc6d 100644 --- a/lib/wasi-tests/src/lib.rs +++ b/lib/wasi-tests/src/lib.rs @@ -35,7 +35,7 @@ fn serializing_works() { let state_bytes = { let instance = module.instantiate(&import_object).unwrap(); - let start: Func<(), ()> = instance.func("_start").unwrap(); + let start: Func<(), ()> = instance.exports.get("_start").unwrap(); start.call().unwrap(); let state = get_wasi_state(instance.context()); @@ -52,7 +52,7 @@ fn serializing_works() { instance.context_mut().data = Box::into_raw(wasi_state) as *mut c_void; - let second_entry: Func<(), i32> = instance.func("second_entry").unwrap(); + let second_entry: Func<(), i32> = instance.exports.get("second_entry").unwrap(); let result = second_entry.call().unwrap(); assert_eq!(result, true as i32); } diff --git a/src/bin/kwasmd.rs b/src/bin/kwasmd.rs index 79949417b10..46492b416f9 100644 --- a/src/bin/kwasmd.rs +++ b/src/bin/kwasmd.rs @@ -50,7 +50,7 @@ fn handle_client(mut stream: UnixStream) { let mut wasm_binary: Vec = Vec::with_capacity(binary_size as usize); unsafe { wasm_binary.set_len(binary_size as usize) }; stream.read_exact(&mut wasm_binary).unwrap(); - use wasmer::webassembly; + use wasmer_bin::webassembly; use wasmer_runtime_core::{ backend::{CompilerConfig, MemoryBoundCheckMode}, loader::Instance, diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 0208e22705c..7c4b9a3466f 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -22,7 +22,7 @@ use std::str::FromStr; use structopt::{clap, StructOpt}; -use wasmer::*; +use wasmer_bin::*; #[cfg(feature = "backend-cranelift")] use wasmer_clif_backend::CraneliftCompiler; #[cfg(feature = "backend-llvm")] @@ -31,7 +31,7 @@ use wasmer_llvm_backend::{ }; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Backend, Value, VERSION, + Backend, DynFunc, Value, VERSION, }; #[cfg(feature = "managed")] use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; @@ -437,8 +437,10 @@ fn execute_wasi( .instantiate(&import_object) .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; - let start: wasmer_runtime::Func<(), ()> = - instance.func("_start").map_err(|e| format!("{:?}", e))?; + let start: wasmer_runtime::Func<(), ()> = instance + .exports + .get("_start") + .map_err(|e| format!("{:?}", e))?; #[cfg(feature = "managed")] { @@ -506,7 +508,8 @@ fn execute_wasi( eprintln!("WARNING: Invoking aribtrary functions with WASI is not officially supported in the WASI standard yet. Use this feature at your own risk!"); let args = options.parse_args(&module, invoke_fn)?; let invoke_result = instance - .dyn_func(invoke_fn) + .exports + .get::(invoke_fn) .map_err(|e| format!("Invoke failed: {:?}", e))? .call(&args) .map_err(|e| format!("Calling invoke fn failed: {:?}", e))?; @@ -900,7 +903,8 @@ fn execute_wasm(options: &Run) -> Result<(), String> { }; let result = instance - .dyn_func(&invoke_fn) + .exports + .get::(&invoke_fn) .map_err(|e| format!("{:?}", e))? .call(&args) .map_err(|e| format!("{:?}", e))?;