From f4dc419b5e2578576e8b12e450cd17b9859428b1 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 17 Mar 2020 15:27:11 -0700 Subject: [PATCH 01/20] Add types and methods to provide updated API --- lib/runtime-core-tests/tests/imports.rs | 31 ++- lib/runtime-core/src/export.rs | 22 ++- lib/runtime-core/src/instance.rs | 97 +++++++++- lib/runtime-core/src/module.rs | 245 +++++++++++++++++++++++- 4 files changed, 380 insertions(+), 15 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index f9ac6df9e..bdebc62f1 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -2,15 +2,44 @@ 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_new().get("my_global").unwrap(); + assert_eq!(my_global.get(), Value::I32(45)); + let double: Func = instance.exports_new().get("double").unwrap(); + assert_eq!(double.call(5).unwrap(), 10); + let add_one: DynFunc = instance.exports_new().get("add_one").unwrap(); + assert_eq!(add_one.call(&[Value::I32(5)]).unwrap(), &[Value::I32(6)]); + let add_one_memory: Option = instance.exports_new().get("my_global"); + assert!(add_one_memory.is_none()); +} + macro_rules! call_and_assert { ($instance:ident, $function:ident( $( $inputs:ty ),* ) -> $output:ty, ( $( $arguments:expr ),* ) == $expected_value:expr) => { #[allow(unused_parens)] diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 8729d9797..b97668d0a 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -1,9 +1,14 @@ -//! 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, + global::Global, + instance::{Instance, 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 +97,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(instance: &'a Instance, name: &str) -> Option; +} diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index fff1797d7..5261b60e8 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}, + 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}, @@ -58,6 +61,11 @@ pub struct Instance { } impl Instance { + /// Access the new exports API. + /// TODO: rename etc. + pub fn exports_new(&self) -> Exports { + Exports { instance: self } + } pub(crate) fn new(module: Arc, imports: &ImportObject) -> Result { // We need the backing and import_backing to create a vm::Ctx, but we need // a vm::Ctx to create a backing and an import_backing. The solution is to create an @@ -814,6 +822,89 @@ impl<'a> DynFunc<'a> { } } +impl<'a> Exportable<'a> for Memory { + fn get_self(instance: &'a Instance, name: &str) -> Option { + let export_index = instance.module.info.exports.get(name)?; + if let ExportIndex::Memory(idx) = export_index { + let module = instance.module.borrow(); + Some(instance.inner.get_memory_from_index(module, *idx)) + } else { + None + } + } +} + +impl<'a> Exportable<'a> for Table { + fn get_self(instance: &'a Instance, name: &str) -> Option { + let export_index = instance.module.info.exports.get(name)?; + if let ExportIndex::Table(idx) = export_index { + let module = instance.module.borrow(); + Some(instance.inner.get_table_from_index(module, *idx)) + } else { + None + } + } +} + +impl<'a> Exportable<'a> for Global { + fn get_self(instance: &'a Instance, name: &str) -> Option { + let export_index = instance.module.info.exports.get(name)?; + if let ExportIndex::Global(idx) = export_index { + let module = instance.module.borrow(); + Some(instance.inner.get_global_from_index(module, *idx)) + } else { + None + } + } +} + +impl<'a> Exportable<'a> for DynFunc<'a> { + fn get_self(instance: &'a Instance, name: &str) -> Option { + instance.dyn_func(name).ok() + } +} + +impl<'a, Args: WasmTypeList, Rets: WasmTypeList> Exportable<'a> for Func<'a, Args, Rets, Wasm> { + fn get_self(instance: &'a Instance, name: &str) -> Option { + instance.func(name).ok() + } +} + +/// `Exports` is used to get exports like [`Func`]s, [`DynFunc`]s, [`Memory`]s, +/// [`Global`]s, and [`Table`]s from an [`Instance`]. +/// +/// Use [`Instance::exports_new`] to get an `Exports` from an [`Instance`]. +pub struct Exports<'a> { + instance: &'a Instance, +} + +impl<'a> Exports<'a> { + /// Get an export. + /// + /// ``` + /// # use wasmer_runtime_core::{DynFunc, Func, Instance}; + /// # use wasmer_runtime_core::global::Global; + /// # use wasmer_runtime_core::types::Value; + /// # fn example_fn(instance: &Instance) -> Option<()> { + /// // We can get a function as a static `Func` + /// let func: Func = instance.exports_new().get("my_func")?; + /// let _result = func.call(42); + /// + /// // Or we can get it as a dynamic `DynFunc` + /// let dyn_func: DynFunc = instance.exports_new().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_new().get("counter")?; + /// + /// # Some(()) + /// # } + /// ``` + pub fn get>(&self, name: &str) -> Option { + T::get_self(self.instance, name) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index e7af6a7da..6fc90d223 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, + ElementType, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, + ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, + Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, + MemoryIndex, SigIndex, TableDescriptor, TableIndex, Type, }, Instance, }; @@ -163,6 +165,136 @@ impl Module { pub fn info(&self) -> &ModuleInfo { &self.inner.info } + + /// Iterate over the exports that this module provides. + /// + /// TODO: show example here + pub fn exports(&self) -> impl Iterator + '_ { + self.inner + .info + .exports + .iter() + .map(|(name, &ei)| ExportDescriptor { + name: name.clone(), + kind: ei.into(), + }) + } + + /// Get the [`Import`]s 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.values().map(|import_name| { + let (namespace, name) = get_import_name(info, import_name); + Import { + namespace, + name, + type_: ImportType::Function, + } + }); + let imported_memories = + info.imported_memories + .values() + .map(|(import_name, memory_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + Import { + namespace, + name, + type_: memory_descriptor.into(), + } + }); + let imported_tables = + info.imported_tables + .values() + .map(|(import_name, table_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + Import { + namespace, + name, + type_: table_descriptor.into(), + } + }); + let imported_globals = + info.imported_tables + .values() + .map(|(import_name, global_descriptor)| { + let (namespace, name) = get_import_name(info, import_name); + Import { + namespace, + name, + type_: global_descriptor.into(), + } + }); + + out.extend(imported_functions); + out.extend(imported_memories); + out.extend(imported_tables); + out.extend(imported_globals); + out + } + + /// Find the custom section(s?) matching the given name. + // TODO: JS API returns `Vec<&[u8]>` here + pub fn custom_section(&self, key: impl AsRef) -> Option<&[u8]> { + let key = key.as_ref(); + self.inner.info.custom_sections.get(key).map(|v| v.as_ref()) + } +} + +// TODO: review this vs `ExportIndex` +/// Type describing an export that the [`Module`] provides. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ExportDescriptor { + /// The name identifying the export. + pub name: String, + /// The type of the export. + pub kind: ExportKind, +} + +// TODO: kind vs type +/// Tag indicating the type of the export. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ExportKind { + /// The export is a function. + Function, + /// The export is a global variable. + Global, + /// The export is a linear memory. + Memory, + /// The export is a table. + Table, +} + +impl From for ExportKind { + fn from(other: ExportIndex) -> Self { + match other { + ExportIndex::Func(_) => Self::Function, + ExportIndex::Global(_) => Self::Global, + ExportIndex::Memory(_) => Self::Memory, + ExportIndex::Table(_) => Self::Table, + } + } } impl Clone for Module { @@ -173,6 +305,107 @@ impl Clone for Module { } } +/// The type of import. This indicates whether the import is a function, global, memory, or table. +// TODO: discuss and research Kind vs Type; +// `Kind` has meaning to me from Haskell as an incomplete type like +// `List` which is of kind `* -> *`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ImportType { + /// The import is a function. + // TODO: why does function have no data? + Function, + /// The import is a global variable. + Global { + /// Whether or not the variable can be mutated. + mutable: bool, + /// The Wasm type that the global variable holds. + // TODO: attempt to understand explanation about 128bit globals: + // https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md#webassembly-module-instatiation + type_: Type, + }, + /// A Wasm linear memory. + // TODO: discuss using `Pages` here vs u32 + Memory { + /// The minimum number of pages this memory must have. + minimum_pages: u32, + /// The maximum number of pages this memory can have. + maximum_pages: Option, + // TODO: missing fields, `shared`, `memory_type` + }, + /// A Wasm table. + Table { + /// The minimum number of elements this table must have. + minimum_elements: u32, + /// The maximum number of elements this table can have. + maximum_elements: Option, + /// The type that this table contains + element_type: ElementType, + }, +} + +impl From for ImportType { + fn from(other: MemoryDescriptor) -> Self { + ImportType::Memory { + minimum_pages: other.minimum.0, + maximum_pages: other.maximum.map(|inner| inner.0), + } + } +} +impl From<&MemoryDescriptor> for ImportType { + fn from(other: &MemoryDescriptor) -> Self { + ImportType::Memory { + minimum_pages: other.minimum.0, + maximum_pages: other.maximum.map(|inner| inner.0), + } + } +} + +impl From for ImportType { + fn from(other: TableDescriptor) -> Self { + ImportType::Table { + minimum_elements: other.minimum, + maximum_elements: other.maximum, + element_type: other.element, + } + } +} +impl From<&TableDescriptor> for ImportType { + fn from(other: &TableDescriptor) -> Self { + ImportType::Table { + minimum_elements: other.minimum, + maximum_elements: other.maximum, + element_type: other.element, + } + } +} +impl From for ImportType { + fn from(other: GlobalDescriptor) -> Self { + ImportType::Global { + mutable: other.mutable, + type_: other.ty, + } + } +} +impl From<&GlobalDescriptor> for ImportType { + fn from(other: &GlobalDescriptor) -> Self { + ImportType::Global { + mutable: other.mutable, + type_: other.ty, + } + } +} + +/// A type describing an import that a [`Module`] needs to be instantiated. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Import { + /// The namespace that this import is in. + pub namespace: String, + /// The name of the import. + pub name: String, + /// The type of the import. + pub type_: ImportType, +} + impl ModuleInner {} #[doc(hidden)] From f864765298a58ef34fd38549d078bf78baa1ba49 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 17 Mar 2020 16:17:03 -0700 Subject: [PATCH 02/20] Make trailing commas in `imports!` macro optional We now use `,*` on the outside of a `$()` expression to match on interspersed commas instead of trailing commas. To continue to handle the trailing comma case, we optionally match on an extra comma at the end with `$(,)?`. --- lib/runtime-core/src/macros.rs | 84 ++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/macros.rs b/lib/runtime-core/src/macros.rs index 8251a4133..17114a91d 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,79 @@ macro_rules! namespace { ns }}; } + +#[cfg(test)] +mod test { + #[test] + fn imports_macro_allows_trailing_comma_and_none() { + fn func(arg: i32) -> i32 { + arg + 1 + } + 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), + } + }; + 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), + }, + }; + } +} From 71be2c67635183597f21fddc5847590aada070b7 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 18 Mar 2020 15:29:29 -0700 Subject: [PATCH 03/20] Add getter to table and other misc changes --- lib/runtime-core/src/module.rs | 31 ++++++++++++++++++++++----- lib/runtime-core/src/table/anyfunc.rs | 12 +++++++++++ lib/runtime-core/src/table/mod.rs | 10 +++++++++ lib/runtime-core/src/units.rs | 14 +++++++++++- lib/runtime-core/src/vm.rs | 9 ++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 6fc90d223..41b0bafae 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -168,7 +168,24 @@ impl Module { /// Iterate over the exports that this module provides. /// - /// TODO: show example here + /// ``` + /// # use wasmer_runtime_core::module::*; + /// # fn example(module: &Module) { + /// // We can filter by `ExportKind` to get only certain types of exports. + /// // For example, here we get all the names of the functions exported by this module. + /// let function_names = + /// module.exports() + /// .filter(|ed| ed.kind == ExportKind::Function) + /// .map(|ed| ed.name) + /// .collect::>(); + /// + /// // And here we count the number of global variables exported by this module. + /// let num_globals = + /// module.exports() + /// .filter(|ed| ed.kind == ExportKind::Global) + /// .count(); + /// # } + /// ``` pub fn exports(&self) -> impl Iterator + '_ { self.inner .info @@ -254,11 +271,15 @@ impl Module { out } - /// Find the custom section(s?) matching the given name. - // TODO: JS API returns `Vec<&[u8]>` here - pub fn custom_section(&self, key: impl AsRef) -> Option<&[u8]> { + /// Get the custom sections matching the given name. + pub fn custom_sections(&self, key: impl AsRef) -> Option> { let key = key.as_ref(); - self.inner.info.custom_sections.get(key).map(|v| v.as_ref()) + // TODO: handle multiple better when our system does + self.inner + .info + .custom_sections + .get(key) + .map(|v| vec![v.as_ref()]) } } diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index 38cb578f2..0ead69961 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -98,6 +98,18 @@ impl AnyfuncTable { Some(starting_len) } + /// Get The vm::AnyFunc at the given index. + pub 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()); + 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 997ce5eb1..211afc4f4 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -89,6 +89,16 @@ impl Table { self.desc } + /// Get the `Element` at the given index in the table + pub fn get(&self, index: u32) -> Option { + let storage = self.storage.lock().unwrap(); + match &*storage { + (TableStorage::Anyfunc(ref anyfunc_table), _) => { + anyfunc_table.get(index).map(Element::Anyfunc) + } + } + } + /// Set the element at index. pub fn set(&self, index: u32, element: Element) -> Result<(), ()> { let mut storage = self.storage.lock().unwrap(); diff --git a/lib/runtime-core/src/units.rs b/lib/runtime-core/src/units.rs index 780ceb034..44eac20e0 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 73b9b2e61..a9a1a2ad9 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)] From 2dc3ea53eb175f90749213666e8337ea07896007 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 18 Mar 2020 18:06:53 -0700 Subject: [PATCH 04/20] Implement table getting and setting --- lib/runtime-core/src/table/anyfunc.rs | 13 +++ lib/runtime-core/src/table/mod.rs | 137 ++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 18 deletions(-) diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index 0ead69961..fdd78a657 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -7,9 +7,11 @@ use crate::{ vm, }; +use std::convert::TryFrom; use std::{ptr, sync::Arc}; enum AnyfuncInner<'a> { + // TODO: update this entry and impl Into/TryFrom Host { ptr: *const vm::Func, signature: Arc, @@ -45,6 +47,17 @@ 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(()), + } + } +} + pub struct AnyfuncTable { 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 211afc4f4..bc013e1ab 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,120 @@ 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>; +} + +/* +// this specific impelementation should be unnecessary now +// delete it after tests are written + +impl<'a> StorableInTable for Anyfunc<'a> { + fn unwrap_self(storage: &TableStorage, index: u32) -> Result { + match storage { + TableStorage::Anyfunc(ref anyfunc_table) => { + anyfunc_table.get(index).ok_or(TableAccessError::IndexError) + } + // TODO: return type error here when we support more than 1 type + // _ => Err(TableAccessError::TypeError), + } + } + + fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { + match storage { + TableStorage::Anyfunc(ref mut anyfunc_table) => anyfunc_table + .set(index, self) + .map_err(|_| TableAccessError::IndexError), + } + } +} +*/ + +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), + } + } +} + +/* +// this should be unnecessary if the above generic implementation worked +// TODO: remove this commented out code after writing a test +// TODO: update `AnyfuncInner` so that `StorableInTable` can be implemented on `Func`, too. + +impl<'a, Args: WasmTypeList, Rets: WasmTypeList> StorableInTable for Func<'a, Args, Rets> { + fn unwrap_self(storage: &TableStorage, index: u32) -> Result { + // TODO: + } + + fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { + let sig = FuncSig::new(self.params(), self.returns()); + let anyfunc = Anyfunc::new(self.func.as_ptr(), sig); + + anyfunc.wrap_self(storage, index) + } +} +*/ + /// 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,27 +198,19 @@ impl Table { self.desc } - /// Get the `Element` at the given index in the table - pub fn get(&self, index: u32) -> Option { - let storage = self.storage.lock().unwrap(); - match &*storage { - (TableStorage::Anyfunc(ref anyfunc_table), _) => { - anyfunc_table.get(index).map(Element::Anyfunc) - } - } + /// Get the raw table value at index. A return value of `None` means either that + /// the index or the type wasn't valid. + pub 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 From da949f47a1088222e04d0335aa69465b89782fb9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 20 Mar 2020 11:15:10 -0700 Subject: [PATCH 05/20] Improve Table implementation for API This commit also leaves comments explaining the current state of things so that when it's unblocked it can be finished and the API made public. --- lib/runtime-core/src/table/anyfunc.rs | 39 +++++++++++++++++++-- lib/runtime-core/src/table/mod.rs | 49 +++------------------------ lib/runtime-core/src/typed_func.rs | 1 + 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index fdd78a657..b702cdc4f 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -11,7 +11,6 @@ use std::convert::TryFrom; use std::{ptr, sync::Arc}; enum AnyfuncInner<'a> { - // TODO: update this entry and impl Into/TryFrom Host { ptr: *const vm::Func, signature: Arc, @@ -58,6 +57,35 @@ impl<'a> TryFrom> for DynFunc<'a> { } } +/* +// 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, @@ -111,10 +139,17 @@ 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 fn get<'outer_table>(&self, index: u32) -> Option> { + 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, diff --git a/lib/runtime-core/src/table/mod.rs b/lib/runtime-core/src/table/mod.rs index bc013e1ab..4d1484258 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -40,31 +40,6 @@ pub trait StorableInTable: Sized { fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError>; } -/* -// this specific impelementation should be unnecessary now -// delete it after tests are written - -impl<'a> StorableInTable for Anyfunc<'a> { - fn unwrap_self(storage: &TableStorage, index: u32) -> Result { - match storage { - TableStorage::Anyfunc(ref anyfunc_table) => { - anyfunc_table.get(index).ok_or(TableAccessError::IndexError) - } - // TODO: return type error here when we support more than 1 type - // _ => Err(TableAccessError::TypeError), - } - } - - fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { - match storage { - TableStorage::Anyfunc(ref mut anyfunc_table) => anyfunc_table - .set(index, self) - .map_err(|_| TableAccessError::IndexError), - } - } -} -*/ - impl<'a, F: Into> + TryFrom>> StorableInTable for F { fn unwrap_self(storage: &TableStorage, index: u32) -> Result { match storage { @@ -89,25 +64,6 @@ impl<'a, F: Into> + TryFrom>> StorableInTable for F { } } -/* -// this should be unnecessary if the above generic implementation worked -// TODO: remove this commented out code after writing a test -// TODO: update `AnyfuncInner` so that `StorableInTable` can be implemented on `Func`, too. - -impl<'a, Args: WasmTypeList, Rets: WasmTypeList> StorableInTable for Func<'a, Args, Rets> { - fn unwrap_self(storage: &TableStorage, index: u32) -> Result { - // TODO: - } - - fn wrap_self(self, storage: &mut TableStorage, index: u32) -> Result<(), TableAccessError> { - let sig = FuncSig::new(self.params(), self.returns()); - let anyfunc = Anyfunc::new(self.func.as_ptr(), sig); - - anyfunc.wrap_self(storage, index) - } -} -*/ - /// Kind of table element. // note to implementors: all types in `Element` should implement `StorableInTable`. pub enum Element<'a> { @@ -198,9 +154,12 @@ 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 fn get(&self, index: u32) -> Result { + pub(crate) fn get(&self, index: u32) -> Result { let guard = self.storage.lock().unwrap(); let (storage, _) = &*guard; T::unwrap_self(storage, index) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index a34960134..3fa26604a 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, From 844a572bcae7412bc31569a72bacca963af10cbf Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 20 Mar 2020 17:10:43 -0700 Subject: [PATCH 06/20] Rename new type fields from `type_` to `ty` --- lib/runtime-core/src/module.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 41b0bafae..ad82ef192 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -227,7 +227,7 @@ impl Module { Import { namespace, name, - type_: ImportType::Function, + ty: ImportType::Function, } }); let imported_memories = @@ -238,7 +238,7 @@ impl Module { Import { namespace, name, - type_: memory_descriptor.into(), + ty: memory_descriptor.into(), } }); let imported_tables = @@ -249,7 +249,7 @@ impl Module { Import { namespace, name, - type_: table_descriptor.into(), + ty: table_descriptor.into(), } }); let imported_globals = @@ -260,7 +260,7 @@ impl Module { Import { namespace, name, - type_: global_descriptor.into(), + ty: global_descriptor.into(), } }); @@ -342,7 +342,7 @@ pub enum ImportType { /// The Wasm type that the global variable holds. // TODO: attempt to understand explanation about 128bit globals: // https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md#webassembly-module-instatiation - type_: Type, + ty: Type, }, /// A Wasm linear memory. // TODO: discuss using `Pages` here vs u32 @@ -403,7 +403,7 @@ impl From for ImportType { fn from(other: GlobalDescriptor) -> Self { ImportType::Global { mutable: other.mutable, - type_: other.ty, + ty: other.ty, } } } @@ -411,7 +411,7 @@ impl From<&GlobalDescriptor> for ImportType { fn from(other: &GlobalDescriptor) -> Self { ImportType::Global { mutable: other.mutable, - type_: other.ty, + ty: other.ty, } } } @@ -424,7 +424,7 @@ pub struct Import { /// The name of the import. pub name: String, /// The type of the import. - pub type_: ImportType, + pub ty: ImportType, } impl ModuleInner {} From 9829d97d7d879dfe8aa26d886050c8695adc39ee Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 23 Mar 2020 14:12:07 -0700 Subject: [PATCH 07/20] Update `Module::custom_sections` to properly return multiples --- lib/runtime-core/src/module.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index f1d153b63..1a738c72b 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -273,14 +273,9 @@ impl Module { } /// Get the custom sections matching the given name. - pub fn custom_sections(&self, key: impl AsRef) -> Option> { + pub fn custom_sections(&self, key: impl AsRef) -> Option<&[Vec]> { let key = key.as_ref(); - // TODO: handle multiple better when our system does - self.inner - .info - .custom_sections - .get(key) - .map(|v| vec![v.as_ref()]) + self.inner.info.custom_sections.get(key).map(|v| v.as_ref()) } } From d8bd258ef2ed395a5c165b3cf74acc238d930bee Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 23 Mar 2020 17:53:01 -0700 Subject: [PATCH 08/20] Add skeleton of exported api --- Cargo.lock | 8 ++++++++ Cargo.toml | 4 +++- fuzz/fuzz_targets/validate_wasm.rs | 4 ++-- src/bin/kwasmd.rs | 2 +- src/bin/wasmer.rs | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1df090a28..8ab8b647b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1842,6 +1842,13 @@ dependencies = [ [[package]] name = "wasmer" version = "0.16.2" +dependencies = [ + "wasmer-runtime-core", +] + +[[package]] +name = "wasmer-bin" +version = "0.16.2" dependencies = [ "atty", "byteorder", @@ -1854,6 +1861,7 @@ dependencies = [ "structopt", "typetag", "wabt", + "wasmer", "wasmer-clif-backend", "wasmer-dev-utils", "wasmer-emscripten", diff --git a/Cargo.toml b/Cargo.toml index 0bb2e9a82..e2d955ba1 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,7 @@ wasmer-wasi-experimental-io-devices = { path = "lib/wasi-experimental-io-devices [workspace] members = [ + "lib/api", "lib/clif-backend", "lib/singlepass-backend", "lib/runtime", diff --git a/fuzz/fuzz_targets/validate_wasm.rs b/fuzz/fuzz_targets/validate_wasm.rs index f386105ec..c17a87faf 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/src/bin/kwasmd.rs b/src/bin/kwasmd.rs index 79949417b..46492b416 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 0208e2270..b0ae65ea4 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")] From cc13e452151253c3cc30434d508b0a4ece9a268e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 24 Mar 2020 18:59:09 -0700 Subject: [PATCH 09/20] Add skeleton of external API and tests --- Cargo.lock | 11 ++ Cargo.toml | 1 + lib/api-tests/Cargo.toml | 15 +++ lib/api-tests/src/lib.rs | 1 + lib/api-tests/tests/high_level_api.rs | 33 +++++ lib/api/Cargo.toml | 40 ++++++ lib/api/src/lib.rs | 185 ++++++++++++++++++++++++++ 7 files changed, 286 insertions(+) create mode 100644 lib/api-tests/Cargo.toml create mode 100644 lib/api-tests/src/lib.rs create mode 100644 lib/api-tests/tests/high_level_api.rs create mode 100644 lib/api/Cargo.toml create mode 100644 lib/api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8ab8b647b..886eb59e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,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" @@ -1843,7 +1851,10 @@ dependencies = [ name = "wasmer" version = "0.16.2" dependencies = [ + "wasmer-clif-backend", + "wasmer-llvm-backend", "wasmer-runtime-core", + "wasmer-singlepass-backend", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e2d955ba1..ff1ae6889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ 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/lib/api-tests/Cargo.toml b/lib/api-tests/Cargo.toml new file mode 100644 index 000000000..03713b5c8 --- /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 000000000..8b1378917 --- /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 000000000..db6cd46a0 --- /dev/null +++ b/lib/api-tests/tests/high_level_api.rs @@ -0,0 +1,33 @@ +static TEST_WAT: &str = r#" +(module + (table $test-table 2 anyfunc) + (export "test-table" (table $test-table)) + (export "ret_2" (func $ret_2)) + (export "ret_4" (func $ret_4)) + (elem (;0;) (i32.const 0) $ret_2) + (func $ret_2 (result i32) + i32.const 2) + (func $ret_4 (result i32) + i32.const 4) +) +"#; + +#[test] +fn it_works() { + use wasmer::{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 import_object = imports! {}; + let instance = module.instantiate(&import_object).unwrap(); + + let ret_2: Func<(), i32> = instance.exports_new().get("ret_2").unwrap(); + let ret_4: Func<(), i32> = instance.exports_new().get("ret_4").unwrap(); + + assert_eq!(ret_2.call(), Ok(2)); + assert_eq!(ret_4.call(), Ok(4)); + + let _test_table: Table = instance.exports_new().get("test-table").unwrap(); + // TODO: when table get is stablized test this +} diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml new file mode 100644 index 000000000..85ed576b9 --- /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 000000000..9ca45696c --- /dev/null +++ b/lib/api/src/lib.rs @@ -0,0 +1,185 @@ +#![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 + +/// Commonly used types and functions. +pub mod prelude { + 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::module::{ExportDescriptor, ExportKind, Import, ImportType}; + // 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::module::{ExportDescriptor, ExportKind, Import, ImportType}; + pub use wasmer_runtime_core::table::Table; + 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::module::{Import, ImportType}; + pub use wasmer_runtime_core::{func, imports}; +} + +pub mod export { + //! Types and functions for Wasm exports. + pub use wasmer_runtime_core::module::{ExportDescriptor, ExportKind}; +} + +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::*; +} + +pub mod error { + //! Various error types returned by Wasmer APIs. + pub use wasmer_runtime_core::error::*; +} + +pub use prelude::*; + +/// 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) -> error::CompileResult; + + 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 { + todo!("from_binary: how is this different from `new`?") + } + fn from_binary_unchecked(_bytes: impl AsRef<[u8]>) -> error::CompileResult { + todo!("from_binary_unchecked") + } + fn from_file(_file: impl AsRef) -> error::CompileResult { + todo!("from_file"); + /* + use std::fs; + use std::io::Read; + let path = file.as_ref(); + let mut f = + fs::File::open(path).map_err(|_| todo!("Current error enum can't handle this case"))?; + // 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) + .map_err(|_| todo!("Current error enum can't handle this case"))?; + + Module::from_binary(bytes.as_slice()) + */ + } + + fn validate(_bytes: impl AsRef<[u8]>) -> error::CompileResult<()> { + todo!("validate") + } +} From c14c88fb720414c1cbaef399a5e8966f2085c9c0 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 25 Mar 2020 16:14:04 -0700 Subject: [PATCH 10/20] Improve high level API test --- CHANGELOG.md | 6 + Makefile | 1 + lib/api-tests/tests/high_level_api.rs | 162 ++++++++++++++++++++++++-- lib/runtime-core/src/module.rs | 2 +- 4 files changed, 161 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 581789f4f..f1d7658b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## **[Unreleased]** - [#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`. + - TODO: update this when the method name changes. New way to get exports from an instance with `let func_name: Func = instance.exports_new().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. - [#1305](https://github.com/wasmerio/wasmer/pull/1305) Handle panics from DynamicFunc. - [#1301](https://github.com/wasmerio/wasmer/pull/1301) Update supported stable Rust version to 1.41.1. diff --git a/Makefile b/Makefile index 2a8625052..114ab6ca7 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/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs index db6cd46a0..88136c15b 100644 --- a/lib/api-tests/tests/high_level_api.rs +++ b/lib/api-tests/tests/high_level_api.rs @@ -1,33 +1,177 @@ static TEST_WAT: &str = r#" (module - (table $test-table 2 anyfunc) - (export "test-table" (table $test-table)) - (export "ret_2" (func $ret_2)) - (export "ret_4" (func $ret_4)) + (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 (result i32) + (func $ret_2 (export "ret_2") (result i32) i32.const 2) - (func $ret_4 (result i32) + (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: &[u8], + custom_section_name: &str, + custom_section_contents: &[u8], +) -> Vec { + let mut out = Vec::with_capacity( + // 1 for custom section id, 5 for max length of custom section, 5 for max length of name + wasm.len() + custom_section_name.len() + custom_section_contents.len() + 1 + 5 + 5, + ); + + out.extend(wasm); + out.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); + + out.extend(&custom_section_length_as_leb128); + out.extend(&name_length_as_leb128); + out.extend(custom_section_name.as_bytes()); + out.extend(custom_section_contents); + + out +} + #[test] fn it_works() { - use wasmer::{imports, CompiledModule, Func, Module, Table}; - let wasm = wabt::wat2wasm(TEST_WAT).unwrap(); + use wasmer::wasm::{Global, Value}; + use wasmer::{export, func, imports, CompiledModule, Func, Module, Table}; + let wasm_no_custom_section = wabt::wat2wasm(TEST_WAT).unwrap(); + let wasm = append_custom_section( + &wasm_no_custom_section, + "test_custom_section", + b"hello, world!", + ); + let wasm = append_custom_section(&wasm, "test_custom_section", b"goodbye, world!"); // 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 import_object = imports! {}; + let imports = module.imports(); + assert_eq!(imports.len(), 2); + let num_exports = module.exports().count(); + assert_eq!(num_exports, 6); + let ( + mut test_table_seen, + mut test_global_seen, + mut test_ret_2_seen, + mut test_ret_4_seen, + mut set_test_global_seen, + mut update_outside_global_seen, + ) = (false, false, false, false, false, false); + for export in module.exports() { + match (export.name.as_ref(), export.kind) { + ("test-table", export::ExportKind::Table) => { + assert!(!test_table_seen); + test_table_seen = true; + } + ("test-global", export::ExportKind::Global) => { + assert!(!test_global_seen); + test_global_seen = true; + } + ("ret_2", export::ExportKind::Function) => { + assert!(!test_ret_2_seen); + test_ret_2_seen = true; + } + ("ret_4", export::ExportKind::Function) => { + assert!(!test_ret_4_seen); + test_ret_4_seen = true; + } + ("set_test_global", export::ExportKind::Function) => { + assert!(!set_test_global_seen); + set_test_global_seen = true; + } + ("update_outside_global", export::ExportKind::Function) => { + assert!(!update_outside_global_seen); + update_outside_global_seen = true; + } + _ => { + assert!(false, "Unknown export found!"); + } + } + } + assert!(test_table_seen); + assert!(test_global_seen); + assert!(test_ret_2_seen); + assert!(test_ret_4_seen); + assert!(set_test_global_seen); + assert!(update_outside_global_seen); + + assert_eq!( + module.custom_sections("test_custom_section"), + Some(&[b"hello, world!".to_vec(), b"goodbye, world!".to_vec()][..]) + ); + 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_new().get("ret_2").unwrap(); let ret_4: Func<(), i32> = instance.exports_new().get("ret_4").unwrap(); + let set_test_global: Func = instance.exports_new().get("set_test_global").unwrap(); + let update_outside_global: Func = instance.exports_new().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_new().get("test-table").unwrap(); // TODO: when table get is stablized test this + + let test_global: Global = instance.exports_new().get("test-global").unwrap(); + // TODO: do we want to make global.get act like exports_new().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/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 1a738c72b..d31490e40 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -254,7 +254,7 @@ impl Module { } }); let imported_globals = - info.imported_tables + info.imported_globals .values() .map(|(import_name, global_descriptor)| { let (namespace, name) = get_import_name(info, import_name); From a18371eb9118644203669fe8bc813ab8aea44d37 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 26 Mar 2020 16:24:23 -0700 Subject: [PATCH 11/20] Implement `instance.exports` field syntax --- CHANGELOG.md | 2 +- lib/api-tests/tests/high_level_api.rs | 14 +- lib/runtime-core-tests/tests/imports.rs | 8 +- lib/runtime-core/src/export.rs | 4 +- lib/runtime-core/src/instance.rs | 299 +++++++++++++----------- 5 files changed, 181 insertions(+), 146 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d7658b9..6047f59c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - [#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`. - - TODO: update this when the method name changes. New way to get exports from an instance with `let func_name: Func = instance.exports_new().get("func_name");`. + - 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. diff --git a/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs index 88136c15b..22aaf93b3 100644 --- a/lib/api-tests/tests/high_level_api.rs +++ b/lib/api-tests/tests/high_level_api.rs @@ -128,19 +128,19 @@ fn it_works() { }; let instance = module.instantiate(&import_object).unwrap(); - let ret_2: Func<(), i32> = instance.exports_new().get("ret_2").unwrap(); - let ret_4: Func<(), i32> = instance.exports_new().get("ret_4").unwrap(); - let set_test_global: Func = instance.exports_new().get("set_test_global").unwrap(); - let update_outside_global: Func = instance.exports_new().get("update_outside_global").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_new().get("test-table").unwrap(); + let _test_table: Table = instance.exports.get("test-table").unwrap(); // TODO: when table get is stablized test this - let test_global: Global = instance.exports_new().get("test-global").unwrap(); - // TODO: do we want to make global.get act like exports_new().get()? + 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)); diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index bdebc62f1..77754ae81 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -30,13 +30,13 @@ fn new_api_works() { let import_object = imports! {}; let instance = module.instantiate(&import_object).unwrap(); - let my_global: Global = instance.exports_new().get("my_global").unwrap(); + let my_global: Global = instance.exports.get("my_global").unwrap(); assert_eq!(my_global.get(), Value::I32(45)); - let double: Func = instance.exports_new().get("double").unwrap(); + let double: Func = instance.exports.get("double").unwrap(); assert_eq!(double.call(5).unwrap(), 10); - let add_one: DynFunc = instance.exports_new().get("add_one").unwrap(); + 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: Option = instance.exports_new().get("my_global"); + let add_one_memory: Option = instance.exports.get("my_global"); assert!(add_one_memory.is_none()); } diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index b97668d0a..7eb322663 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -2,7 +2,7 @@ //! including memories, tables, globals, and functions. use crate::{ global::Global, - instance::{Instance, InstanceInner}, + instance::{Exports, InstanceInner}, memory::Memory, module::ExportIndex, module::ModuleInner, @@ -102,5 +102,5 @@ impl<'a> Iterator for ExportIter<'a> { 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(instance: &'a Instance, name: &str) -> Option; + fn get_self(exports: &'a Exports, name: &str) -> Option; } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 5261b60e8..af854a8a8 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -56,16 +56,13 @@ pub struct Instance { /// Reference to the module used to instantiate this instance. pub module: Arc, inner: Pin>, + /// The exports of this instance. TODO: document this + pub exports: Exports, #[allow(dead_code)] import_object: ImportObject, } impl Instance { - /// Access the new exports API. - /// TODO: rename etc. - pub fn exports_new(&self) -> Exports { - Exports { instance: self } - } pub(crate) fn new(module: Arc, imports: &ImportObject) -> Result { // We need the backing and import_backing to create a vm::Ctx, but we need // a vm::Ctx to create a backing and an import_backing. The solution is to create an @@ -97,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(), }; @@ -183,77 +186,7 @@ impl Instance { 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()) - } + func(&*self.module, &self.inner, name) } /// Resolve a function by name. @@ -292,37 +225,7 @@ impl Instance { /// # } /// ``` 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()) - } + dyn_func(&*self.module, &self.inner, name) } /// Call an exported WebAssembly function given the export name. @@ -419,6 +322,124 @@ impl Instance { } } +/// Private function implementing the inner logic of `Instance::func` +// TODO: reevaluate this lifetime +fn func<'a, Args, Rets>( + module: &'a ModuleInner, + inst_inner: &'a InstanceInner, + name: &str, +) -> ResolveResult> +where + Args: WasmTypeList, + Rets: WasmTypeList, +{ + 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 { + 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) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + } + .into()) + } +} + +/// Private function implementing the inner logic of `Instance::dyn_func` +fn dyn_func<'a>( + module: &'a ModuleInner, + inst_inner: &'a InstanceInner, + 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 { + 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, + }) + } else { + Err(ResolveError::ExportWrongType { + name: name.to_string(), + } + .into()) + } +} + impl InstanceInner { pub(crate) fn get_export_from_index( &self, @@ -823,11 +844,11 @@ impl<'a> DynFunc<'a> { } impl<'a> Exportable<'a> for Memory { - fn get_self(instance: &'a Instance, name: &str) -> Option { - let export_index = instance.module.info.exports.get(name)?; + fn get_self(exports: &'a Exports, name: &str) -> Option { + let (inst_inner, module) = exports.get_inner(); + let export_index = module.info.exports.get(name)?; if let ExportIndex::Memory(idx) = export_index { - let module = instance.module.borrow(); - Some(instance.inner.get_memory_from_index(module, *idx)) + Some(inst_inner.get_memory_from_index(module, *idx)) } else { None } @@ -835,11 +856,11 @@ impl<'a> Exportable<'a> for Memory { } impl<'a> Exportable<'a> for Table { - fn get_self(instance: &'a Instance, name: &str) -> Option { - let export_index = instance.module.info.exports.get(name)?; + fn get_self(exports: &'a Exports, name: &str) -> Option { + let (inst_inner, module) = exports.get_inner(); + let export_index = module.info.exports.get(name)?; if let ExportIndex::Table(idx) = export_index { - let module = instance.module.borrow(); - Some(instance.inner.get_table_from_index(module, *idx)) + Some(inst_inner.get_table_from_index(module, *idx)) } else { None } @@ -847,11 +868,11 @@ impl<'a> Exportable<'a> for Table { } impl<'a> Exportable<'a> for Global { - fn get_self(instance: &'a Instance, name: &str) -> Option { - let export_index = instance.module.info.exports.get(name)?; + fn get_self(exports: &'a Exports, name: &str) -> Option { + let (inst_inner, module) = exports.get_inner(); + let export_index = module.info.exports.get(name)?; if let ExportIndex::Global(idx) = export_index { - let module = instance.module.borrow(); - Some(instance.inner.get_global_from_index(module, *idx)) + Some(inst_inner.get_global_from_index(module, *idx)) } else { None } @@ -859,26 +880,33 @@ impl<'a> Exportable<'a> for Global { } impl<'a> Exportable<'a> for DynFunc<'a> { - fn get_self(instance: &'a Instance, name: &str) -> Option { - instance.dyn_func(name).ok() + fn get_self(exports: &'a Exports, name: &str) -> Option { + let (inst_inner, module) = exports.get_inner(); + dyn_func(module, inst_inner, name).ok() } } impl<'a, Args: WasmTypeList, Rets: WasmTypeList> Exportable<'a> for Func<'a, Args, Rets, Wasm> { - fn get_self(instance: &'a Instance, name: &str) -> Option { - instance.func(name).ok() + fn get_self(exports: &'a Exports, name: &str) -> Option { + let (inst_inner, module) = exports.get_inner(); + func(module, inst_inner, name).ok() } } /// `Exports` is used to get exports like [`Func`]s, [`DynFunc`]s, [`Memory`]s, /// [`Global`]s, and [`Table`]s from an [`Instance`]. /// -/// Use [`Instance::exports_new`] to get an `Exports` from an [`Instance`]. -pub struct Exports<'a> { - instance: &'a 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 or copied (so can't safely outlive `Instance`) + // 2. `InstanceInner` is `Pin>`, thus we know that it will not move. + instance_inner: *const InstanceInner, + module: Arc, } -impl<'a> Exports<'a> { +impl Exports { /// Get an export. /// /// ``` @@ -887,21 +915,28 @@ impl<'a> Exports<'a> { /// # use wasmer_runtime_core::types::Value; /// # fn example_fn(instance: &Instance) -> Option<()> { /// // We can get a function as a static `Func` - /// let func: Func = instance.exports_new().get("my_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_new().get("my_func")?; + /// 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_new().get("counter")?; + /// let _counter: Global = instance.exports.get("counter")?; /// /// # Some(()) /// # } /// ``` - pub fn get>(&self, name: &str) -> Option { - T::get_self(self.instance, name) + pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> Option { + T::get_self(self, name) + } + + /// This method must remain private. + fn get_inner(&self) -> (&InstanceInner, &ModuleInner) { + let inst_inner = unsafe { &*self.instance_inner }; + let module = self.module.borrow(); + (inst_inner, module) } } From 2fb8f8197b1e88d7971399e30e4aed05035a85e1 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 26 Mar 2020 17:11:37 -0700 Subject: [PATCH 12/20] Deprecate `Instance::func` and `Instance::dyn_func` --- examples/callback.rs | 4 +- examples/parallel/src/main.rs | 9 +- examples/plugin.rs | 4 +- lib/emscripten/src/lib.rs | 153 +++++++++++++++++-------------- lib/runtime-core/src/export.rs | 3 +- lib/runtime-core/src/instance.rs | 68 ++++++++++---- src/bin/wasmer.rs | 14 ++- 7 files changed, 154 insertions(+), 101 deletions(-) diff --git a/examples/callback.rs b/examples/callback.rs index e5969fec9..1b30b69d1 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 b68b0eaeb..8d9ec16b9 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 8256b27c6..2695119ae 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/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index b19f751d3..d18c57105 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 memalign = instance - .func("_memalign") - .or(instance.func("memalign")) + let malloc = instance + .exports + .get("_malloc") + .or(instance.exports.get("malloc")) .ok(); - let memset = instance.func("_memset").or(instance.func("memset")).ok(); - let stack_alloc = instance.func("stackAlloc").ok(); + let free = instance + .exports + .get("_free") + .or(instance.exports.get("free")) + .ok(); + let memalign = instance + .exports + .get("_memalign") + .or(instance.exports.get("memalign")) + .ok(); + let memset = instance + .exports + .get("_memset") + .or(instance.exports.get("memset")) + .ok(); + let stack_alloc = instance.exports.get("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 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 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.func("stackSave").ok(); - let stack_restore = instance.func("stackRestore").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,11 +349,14 @@ 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") { + if let Ok(_func) = instance.exports.get::("globalCtors") { instance.call("globalCtors", &[])?; } - if let Ok(_func) = instance.dyn_func("___emscripten_environ_constructor") { + if let Ok(_func) = instance + .exports + .get::("___emscripten_environ_constructor") + { instance.call("___emscripten_environ_constructor", &[])?; } 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), }, diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 7eb322663..959a73cc7 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -1,6 +1,7 @@ //! This module contains types to manipulate and access a Wasm module's exports //! including memories, tables, globals, and functions. use crate::{ + error, global::Global, instance::{Exports, InstanceInner}, memory::Memory, @@ -102,5 +103,5 @@ impl<'a> Iterator for ExportIter<'a> { 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) -> Option; + 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 af854a8a8..8d73232bf 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -181,12 +181,13 @@ impl Instance { /// # Ok(()) /// # } /// ``` + #[deprecated(since = "0.17.0", note = "Please use `instance.exports.get()` instead")] pub fn func(&self, name: &str) -> ResolveResult> where Args: WasmTypeList, Rets: WasmTypeList, { - func(&*self.module, &self.inner, name) + self.exports.get(name) } /// Resolve a function by name. @@ -224,8 +225,9 @@ impl Instance { /// # Ok(()) /// # } /// ``` + #[deprecated(since = "0.17.0", note = "Please use `instance.exports.get()` instead")] pub fn dyn_func(&self, name: &str) -> ResolveResult { - dyn_func(&*self.module, &self.inner, name) + self.exports.get(name) } /// Call an exported WebAssembly function given the export name. @@ -323,7 +325,6 @@ impl Instance { } /// Private function implementing the inner logic of `Instance::func` -// TODO: reevaluate this lifetime fn func<'a, Args, Rets>( module: &'a ModuleInner, inst_inner: &'a InstanceInner, @@ -844,52 +845,79 @@ impl<'a> DynFunc<'a> { } impl<'a> Exportable<'a> for Memory { - fn get_self(exports: &'a Exports, name: &str) -> Option { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - let export_index = module.info.exports.get(name)?; + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; if let ExportIndex::Memory(idx) = export_index { - Some(inst_inner.get_memory_from_index(module, *idx)) + Ok(inst_inner.get_memory_from_index(module, *idx)) } else { - None + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) } } } impl<'a> Exportable<'a> for Table { - fn get_self(exports: &'a Exports, name: &str) -> Option { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - let export_index = module.info.exports.get(name)?; + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; if let ExportIndex::Table(idx) = export_index { - Some(inst_inner.get_table_from_index(module, *idx)) + Ok(inst_inner.get_table_from_index(module, *idx)) } else { - None + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) } } } impl<'a> Exportable<'a> for Global { - fn get_self(exports: &'a Exports, name: &str) -> Option { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - let export_index = module.info.exports.get(name)?; + let export_index = + module + .info + .exports + .get(name) + .ok_or_else(|| ResolveError::ExportNotFound { + name: name.to_string(), + })?; if let ExportIndex::Global(idx) = export_index { - Some(inst_inner.get_global_from_index(module, *idx)) + Ok(inst_inner.get_global_from_index(module, *idx)) } else { - None + Err(ResolveError::ExportWrongType { + name: name.to_string(), + }) } } } impl<'a> Exportable<'a> for DynFunc<'a> { - fn get_self(exports: &'a Exports, name: &str) -> Option { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - dyn_func(module, inst_inner, name).ok() + dyn_func(module, inst_inner, name) } } impl<'a, Args: WasmTypeList, Rets: WasmTypeList> Exportable<'a> for Func<'a, Args, Rets, Wasm> { - fn get_self(exports: &'a Exports, name: &str) -> Option { + fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - func(module, inst_inner, name).ok() + func(module, inst_inner, name) } } @@ -928,7 +956,7 @@ impl Exports { /// # Some(()) /// # } /// ``` - pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> Option { + pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> ResolveResult { T::get_self(self, name) } diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index b0ae65ea4..6ba033a44 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -31,7 +31,7 @@ use wasmer_llvm_backend::{ }; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Backend, Value, VERSION, + Backend, DynFunc, Func, 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: 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))?; From 3eff8c1973936acdd5a23ecc1d09af2ebd85aff5 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 26 Mar 2020 18:13:45 -0700 Subject: [PATCH 13/20] Convert usages of `Instance::{func,dyn_func}` to exports.get --- lib/middleware-common-tests/benches/metering_benchmark.rs | 7 ++++--- lib/middleware-common-tests/src/lib.rs | 4 ++-- lib/runtime-core-tests/tests/imports.rs | 6 +++--- lib/runtime-core/src/instance.rs | 4 ++++ lib/runtime/README.md | 4 +++- lib/runtime/benches/many_instances.rs | 4 ++-- lib/runtime/examples/call.rs | 2 +- lib/runtime/src/cache.rs | 2 +- lib/runtime/src/lib.rs | 2 +- lib/runtime/tests/error_propagation.rs | 2 +- lib/wasi-tests/src/lib.rs | 4 ++-- 11 files changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/middleware-common-tests/benches/metering_benchmark.rs b/lib/middleware-common-tests/benches/metering_benchmark.rs index 7c425ce99..501e0178f 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 02f953fc4..70b373f4d 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 77754ae81..ca9c0cdb9 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -36,14 +36,14 @@ fn new_api_works() { 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: Option = instance.exports.get("my_global"); - assert!(add_one_memory.is_none()); + 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/instance.rs b/lib/runtime-core/src/instance.rs index 8d73232bf..6233c8777 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -934,6 +934,10 @@ pub struct Exports { 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. /// diff --git a/lib/runtime/README.md b/lib/runtime/README.md index 08bec247f..2f0f98a0e 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 c10cf34e9..dca54624c 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 e393354c5..46ccc5aa6 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 dbcda5b87..02bcc0f91 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 6a3ad1981..91d86dada 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 2a725e82f..c50344ba0 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 b65021aeb..447cd40fc 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); } From a0dca15fbc009e28495ceb606f4c7f946d22ddf7 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 27 Mar 2020 13:25:51 -0700 Subject: [PATCH 14/20] Add misc. fixes and updates from feedback --- lib/api-tests/tests/high_level_api.rs | 180 +++++++++++++---------- lib/runtime-core/src/instance.rs | 202 +++++++++++--------------- lib/runtime-core/src/macros.rs | 12 +- lib/runtime-core/src/module.rs | 10 +- src/bin/wasmer.rs | 4 +- 5 files changed, 207 insertions(+), 201 deletions(-) diff --git a/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs index 22aaf93b3..a469fae11 100644 --- a/lib/api-tests/tests/high_level_api.rs +++ b/lib/api-tests/tests/high_level_api.rs @@ -19,17 +19,16 @@ static TEST_WAT: &str = r#" "#; fn append_custom_section( - wasm: &[u8], + wasm: &mut Vec, custom_section_name: &str, custom_section_contents: &[u8], -) -> Vec { - let mut out = Vec::with_capacity( +) { + wasm.reserve( // 1 for custom section id, 5 for max length of custom section, 5 for max length of name - wasm.len() + custom_section_name.len() + custom_section_contents.len() + 1 + 5 + 5, + custom_section_name.len() + custom_section_contents.len() + 1 + 5 + 5, ); - out.extend(wasm); - out.push(0); + wasm.push(0); let name_length = custom_section_name.as_bytes().len() as u32; let mut name_length_as_leb128 = vec![]; @@ -42,82 +41,117 @@ fn append_custom_section( let mut custom_section_length_as_leb128 = vec![]; write_u32_leb128(&mut custom_section_length_as_leb128, custom_section_length); - out.extend(&custom_section_length_as_leb128); - out.extend(&name_length_as_leb128); - out.extend(custom_section_name.as_bytes()); - out.extend(custom_section_contents); - - out + 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 it_works() { - use wasmer::wasm::{Global, Value}; - use wasmer::{export, func, imports, CompiledModule, Func, Module, Table}; - let wasm_no_custom_section = wabt::wat2wasm(TEST_WAT).unwrap(); - let wasm = append_custom_section( - &wasm_no_custom_section, - "test_custom_section", - b"hello, world!", - ); - let wasm = append_custom_section(&wasm, "test_custom_section", b"goodbye, world!"); - // 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. +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(); - let imports = module.imports(); - assert_eq!(imports.len(), 2); - let num_exports = module.exports().count(); - assert_eq!(num_exports, 6); - let ( - mut test_table_seen, - mut test_global_seen, - mut test_ret_2_seen, - mut test_ret_4_seen, - mut set_test_global_seen, - mut update_outside_global_seen, - ) = (false, false, false, false, false, false); - for export in module.exports() { - match (export.name.as_ref(), export.kind) { - ("test-table", export::ExportKind::Table) => { - assert!(!test_table_seen); - test_table_seen = true; - } - ("test-global", export::ExportKind::Global) => { - assert!(!test_global_seen); - test_global_seen = true; - } - ("ret_2", export::ExportKind::Function) => { - assert!(!test_ret_2_seen); - test_ret_2_seen = true; - } - ("ret_4", export::ExportKind::Function) => { - assert!(!test_ret_4_seen); - test_ret_4_seen = true; - } - ("set_test_global", export::ExportKind::Function) => { - assert!(!set_test_global_seen); - set_test_global_seen = true; - } - ("update_outside_global", export::ExportKind::Function) => { - assert!(!update_outside_global_seen); - update_outside_global_seen = true; - } - _ => { - assert!(false, "Unknown export found!"); - } - } - } - assert!(test_table_seen); - assert!(test_global_seen); - assert!(test_ret_2_seen); - assert!(test_ret_4_seen); - assert!(set_test_global_seen); - assert!(update_outside_global_seen); 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::{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().collect::>(); + assert_eq!( + exports, + vec![ + export::ExportDescriptor { + name: "test-table", + kind: export::ExportKind::Table, + }, + export::ExportDescriptor { + name: "test-global", + kind: export::ExportKind::Global, + }, + export::ExportDescriptor { + name: "ret_2", + kind: export::ExportKind::Function, + }, + export::ExportDescriptor { + name: "ret_4", + kind: export::ExportKind::Function, + }, + export::ExportDescriptor { + name: "set_test_global", + kind: export::ExportKind::Function, + }, + export::ExportDescriptor { + name: "update_outside_global", + kind: export::ExportKind::Function, + }, + ] + ); +} + +#[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! { diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 6233c8777..a7d47ae40 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -181,7 +181,10 @@ impl Instance { /// # Ok(()) /// # } /// ``` - #[deprecated(since = "0.17.0", note = "Please use `instance.exports.get()` instead")] + #[deprecated( + since = "0.17.0", + note = "Please use `instance.exports.get(name)` instead" + )] pub fn func(&self, name: &str) -> ResolveResult> where Args: WasmTypeList, @@ -192,23 +195,7 @@ impl Instance { /// 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 @@ -225,7 +212,10 @@ impl Instance { /// # Ok(()) /// # } /// ``` - #[deprecated(since = "0.17.0", note = "Please use `instance.exports.get()` instead")] + #[deprecated( + since = "0.17.0", + note = "Please use `instance.exports.get(name)` instead" + )] pub fn dyn_func(&self, name: &str) -> ResolveResult { self.exports.get(name) } @@ -324,16 +314,8 @@ impl Instance { } } -/// Private function implementing the inner logic of `Instance::func` -fn func<'a, Args, Rets>( - module: &'a ModuleInner, - inst_inner: &'a InstanceInner, - name: &str, -) -> ResolveResult> -where - Args: WasmTypeList, - Rets: WasmTypeList, -{ +/// 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 @@ -344,95 +326,7 @@ where })?; if let ExportIndex::Func(func_index) = export_index { - 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) - } else { - Err(ResolveError::ExportWrongType { - name: name.to_string(), - } - .into()) - } -} - -/// Private function implementing the inner logic of `Instance::dyn_func` -fn dyn_func<'a>( - module: &'a ModuleInner, - inst_inner: &'a InstanceInner, - 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 { - 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, - }) + Ok(*func_index) } else { Err(ResolveError::ExportWrongType { name: name.to_string(), @@ -910,14 +804,82 @@ impl<'a> Exportable<'a> for Global { impl<'a> Exportable<'a> for DynFunc<'a> { fn get_self(exports: &'a Exports, name: &str) -> ResolveResult { let (inst_inner, module) = exports.get_inner(); - dyn_func(module, inst_inner, name) + 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(); - func(module, inst_inner, name) + + 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) } } diff --git a/lib/runtime-core/src/macros.rs b/lib/runtime-core/src/macros.rs index 17114a91d..2c30ca8ea 100644 --- a/lib/runtime-core/src/macros.rs +++ b/lib/runtime-core/src/macros.rs @@ -137,11 +137,12 @@ macro_rules! namespace { #[cfg(test)] mod test { + fn func(arg: i32) -> i32 { + arg + 1 + } + #[test] fn imports_macro_allows_trailing_comma_and_none() { - fn func(arg: i32) -> i32 { - arg + 1 - } let _ = imports! { "env" => { "func" => func!(func), @@ -182,7 +183,12 @@ mod test { "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) diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index d31490e40..1f99fa3c2 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -54,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. @@ -193,7 +197,7 @@ impl Module { .exports .iter() .map(|(name, &ei)| ExportDescriptor { - name: name.clone(), + name, kind: ei.into(), }) } @@ -282,9 +286,9 @@ impl Module { // TODO: review this vs `ExportIndex` /// Type describing an export that the [`Module`] provides. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExportDescriptor { +pub struct ExportDescriptor<'a> { /// The name identifying the export. - pub name: String, + pub name: &'a str, /// The type of the export. pub kind: ExportKind, } diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 6ba033a44..7c4b9a346 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -31,7 +31,7 @@ use wasmer_llvm_backend::{ }; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Backend, DynFunc, Func, Value, VERSION, + Backend, DynFunc, Value, VERSION, }; #[cfg(feature = "managed")] use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; @@ -437,7 +437,7 @@ fn execute_wasi( .instantiate(&import_object) .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; - let start: Func<(), ()> = instance + let start: wasmer_runtime::Func<(), ()> = instance .exports .get("_start") .map_err(|e| format!("{:?}", e))?; From 7cd9e820156f8d1419cad6d22d6b175d68c11b6e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 27 Mar 2020 14:00:02 -0700 Subject: [PATCH 15/20] Simplify `ImportType` --- lib/runtime-core/src/instance.rs | 5 ++- lib/runtime-core/src/module.rs | 69 +++++++------------------------- lib/runtime-core/src/types.rs | 2 +- 3 files changed, 18 insertions(+), 58 deletions(-) diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index a7d47ae40..c708ef150 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -907,7 +907,8 @@ impl Exports { /// # use wasmer_runtime_core::{DynFunc, Func, Instance}; /// # use wasmer_runtime_core::global::Global; /// # use wasmer_runtime_core::types::Value; - /// # fn example_fn(instance: &Instance) -> Option<()> { + /// # 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); @@ -919,7 +920,7 @@ impl Exports { /// // We can also get other exports like `Global`s, `Memory`s, and `Table`s /// let _counter: Global = instance.exports.get("counter")?; /// - /// # Some(()) + /// # Ok(()) /// # } /// ``` pub fn get<'a, T: Exportable<'a>>(&'a self, name: &str) -> ResolveResult { diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 1f99fa3c2..13fb64535 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -9,10 +9,10 @@ use crate::{ import::ImportObject, structures::{Map, TypedIndex}, types::{ - ElementType, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, - ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, - Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, - MemoryIndex, SigIndex, TableDescriptor, TableIndex, Type, + FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex, + ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer, + LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, MemoryIndex, + SigIndex, TableDescriptor, TableIndex, }, Instance, }; @@ -181,7 +181,7 @@ impl Module { /// let function_names = /// module.exports() /// .filter(|ed| ed.kind == ExportKind::Function) - /// .map(|ed| ed.name) + /// .map(|ed| ed.name.to_string()) /// .collect::>(); /// /// // And here we count the number of global variables exported by this module. @@ -336,83 +336,42 @@ pub enum ImportType { // TODO: why does function have no data? Function, /// The import is a global variable. - Global { - /// Whether or not the variable can be mutated. - mutable: bool, - /// The Wasm type that the global variable holds. - // TODO: attempt to understand explanation about 128bit globals: - // https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md#webassembly-module-instatiation - ty: Type, - }, + Global(GlobalDescriptor), /// A Wasm linear memory. - // TODO: discuss using `Pages` here vs u32 - Memory { - /// The minimum number of pages this memory must have. - minimum_pages: u32, - /// The maximum number of pages this memory can have. - maximum_pages: Option, - // TODO: missing fields, `shared`, `memory_type` - }, + Memory(MemoryDescriptor), /// A Wasm table. - Table { - /// The minimum number of elements this table must have. - minimum_elements: u32, - /// The maximum number of elements this table can have. - maximum_elements: Option, - /// The type that this table contains - element_type: ElementType, - }, + Table(TableDescriptor), } impl From for ImportType { fn from(other: MemoryDescriptor) -> Self { - ImportType::Memory { - minimum_pages: other.minimum.0, - maximum_pages: other.maximum.map(|inner| inner.0), - } + ImportType::Memory(other) } } impl From<&MemoryDescriptor> for ImportType { fn from(other: &MemoryDescriptor) -> Self { - ImportType::Memory { - minimum_pages: other.minimum.0, - maximum_pages: other.maximum.map(|inner| inner.0), - } + ImportType::Memory(*other) } } impl From for ImportType { fn from(other: TableDescriptor) -> Self { - ImportType::Table { - minimum_elements: other.minimum, - maximum_elements: other.maximum, - element_type: other.element, - } + ImportType::Table(other) } } impl From<&TableDescriptor> for ImportType { fn from(other: &TableDescriptor) -> Self { - ImportType::Table { - minimum_elements: other.minimum, - maximum_elements: other.maximum, - element_type: other.element, - } + ImportType::Table(*other) } } impl From for ImportType { fn from(other: GlobalDescriptor) -> Self { - ImportType::Global { - mutable: other.mutable, - ty: other.ty, - } + ImportType::Global(other) } } impl From<&GlobalDescriptor> for ImportType { fn from(other: &GlobalDescriptor) -> Self { - ImportType::Global { - mutable: other.mutable, - ty: other.ty, - } + ImportType::Global(*other) } } diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index 122aa544a..8a0a0d82f 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, From 7ca721bd83ef0bccb19dd83ab601c9360d94a3ea Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 30 Mar 2020 17:38:51 -0700 Subject: [PATCH 16/20] Update from feedback Deprecate more methods on `Instance`, add `into_iter` method on `Exports`, add FuncSig to ImportType and other updates. --- lib/api/src/lib.rs | 2 +- lib/emscripten/src/lib.rs | 20 ++++++++++---------- lib/runtime-core/src/instance.rs | 19 +++++++++++++++++++ lib/runtime-core/src/module.rs | 29 +++++++++++++++++++++-------- lib/runtime-core/src/types.rs | 3 +++ 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 9ca45696c..44b6a46e4 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -99,7 +99,7 @@ pub mod types { pub mod error { //! Various error types returned by Wasmer APIs. - pub use wasmer_runtime_core::error::*; + pub use wasmer_runtime_core::error::{CompileError, CompileResult}; } pub use prelude::*; diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index d18c57105..cf607e1b3 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -349,15 +349,15 @@ impl<'a> EmscriptenData<'a> { pub fn set_up_emscripten(instance: &mut Instance) -> CallResult<()> { // ATINIT // (used by C++) - if let Ok(_func) = instance.exports.get::("globalCtors") { - instance.call("globalCtors", &[])?; + if let Ok(func) = instance.exports.get::("globalCtors") { + func.call(&[])?; } - if let Ok(_func) = instance + if let Ok(func) = instance .exports .get::("___emscripten_environ_constructor") { - instance.call("___emscripten_environ_constructor", &[])?; + func.call(&[])?; } Ok(()) } @@ -380,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 { @@ -420,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/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index c708ef150..a3110bc04 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -242,6 +242,10 @@ impl Instance { /// # Ok(()) /// # } /// ``` + #[deprecated( + since = "0.17.0", + note = "Please use `let f: DynFunc = instance.exports.get(name)?; f.call(params)?;` instead" + )] pub fn call(&self, name: &str, params: &[Value]) -> CallResult> { let export_index = self.module @@ -933,6 +937,21 @@ impl Exports { 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)] diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 13fb64535..382d422fe 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -9,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, + FuncDescriptor, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, + ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, + Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, + MemoryIndex, SigIndex, TableDescriptor, TableIndex, }, Instance, }; @@ -227,12 +227,16 @@ impl Module { let info = &self.inner.info; - let imported_functions = info.imported_functions.values().map(|import_name| { + 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(); Import { namespace, name, - ty: ImportType::Function, + ty: sig.into(), } }); let imported_memories = @@ -333,8 +337,7 @@ impl Clone for Module { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportType { /// The import is a function. - // TODO: why does function have no data? - Function, + Function(FuncDescriptor), /// The import is a global variable. Global(GlobalDescriptor), /// A Wasm linear memory. @@ -343,6 +346,16 @@ pub enum ImportType { Table(TableDescriptor), } +impl From for ImportType { + fn from(other: FuncDescriptor) -> Self { + ImportType::Function(other) + } +} +impl From<&FuncDescriptor> for ImportType { + fn from(other: &FuncDescriptor) -> Self { + ImportType::Function(other.clone()) + } +} impl From for ImportType { fn from(other: MemoryDescriptor) -> Self { ImportType::Memory(other) diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index 8a0a0d82f..7c0eb4630 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -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 From 50fcd57e45e5c18d36f443a164151eef438cac94 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 31 Mar 2020 12:37:50 -0700 Subject: [PATCH 17/20] Improve consistency and add misc clean ups --- lib/api-tests/tests/high_level_api.rs | 12 ++-- lib/api/src/lib.rs | 93 ++++++++++++++++++--------- lib/runtime-core/src/instance.rs | 7 +- lib/runtime-core/src/module.rs | 65 +++++++++---------- 4 files changed, 102 insertions(+), 75 deletions(-) diff --git a/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs index a469fae11..59b536275 100644 --- a/lib/api-tests/tests/high_level_api.rs +++ b/lib/api-tests/tests/high_level_api.rs @@ -80,27 +80,27 @@ fn module_exports_are_ordered() { vec![ export::ExportDescriptor { name: "test-table", - kind: export::ExportKind::Table, + ty: export::ExportType::Table, }, export::ExportDescriptor { name: "test-global", - kind: export::ExportKind::Global, + ty: export::ExportType::Global, }, export::ExportDescriptor { name: "ret_2", - kind: export::ExportKind::Function, + ty: export::ExportType::Function, }, export::ExportDescriptor { name: "ret_4", - kind: export::ExportKind::Function, + ty: export::ExportType::Function, }, export::ExportDescriptor { name: "set_test_global", - kind: export::ExportKind::Function, + ty: export::ExportType::Function, }, export::ExportDescriptor { name: "update_outside_global", - kind: export::ExportKind::Function, + ty: export::ExportType::Function, }, ] ); diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 44b6a46e4..9e194ed79 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -22,15 +22,12 @@ //! //! more info, what to do if you run into problems -/// Commonly used types and functions. -pub mod prelude { - 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 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. @@ -51,7 +48,7 @@ pub mod module { // 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::module::{ExportDescriptor, ExportKind, Import, ImportType}; + pub use wasmer_runtime_core::module::{ExportDescriptor, ExportType, Import, ImportDescriptor}; // TODO: implement abstract module API } @@ -69,7 +66,7 @@ pub mod wasm { //! //! # Tables pub use wasmer_runtime_core::global::Global; - pub use wasmer_runtime_core::module::{ExportDescriptor, ExportKind, Import, ImportType}; + pub use wasmer_runtime_core::module::{ExportDescriptor, ExportType, Import, ImportDescriptor}; pub use wasmer_runtime_core::table::Table; pub use wasmer_runtime_core::types::{ FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type, Value, @@ -78,13 +75,13 @@ pub mod wasm { pub mod import { //! Types and functions for Wasm imports. - pub use wasmer_runtime_core::module::{Import, ImportType}; + pub use wasmer_runtime_core::module::{Import, ImportDescriptor}; pub use wasmer_runtime_core::{func, imports}; } pub mod export { //! Types and functions for Wasm exports. - pub use wasmer_runtime_core::module::{ExportDescriptor, ExportKind}; + pub use wasmer_runtime_core::module::{ExportDescriptor, ExportType}; } pub mod units { @@ -100,16 +97,49 @@ pub mod types { pub mod error { //! Various error types returned by Wasmer APIs. pub use wasmer_runtime_core::error::{CompileError, CompileResult}; -} -pub use prelude::*; + #[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) -> error::CompileResult; + fn from_file(file: impl AsRef) -> Result; fn validate(bytes: impl AsRef<[u8]>) -> error::CompileResult<()>; } @@ -156,30 +186,31 @@ impl CompiledModule for Module { wasmer_runtime_core::compile_with(bytes, &default_compiler()) } - fn from_binary(_bytes: impl AsRef<[u8]>) -> error::CompileResult { - todo!("from_binary: how is this different from `new`?") + 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!("from_binary_unchecked") + + fn from_binary_unchecked(bytes: impl AsRef<[u8]>) -> error::CompileResult { + // TODO: optimize this + Self::from_binary(bytes) } - fn from_file(_file: impl AsRef) -> error::CompileResult { - todo!("from_file"); - /* + + 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).map_err(|_| todo!("Current error enum can't handle this case"))?; + 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) - .map_err(|_| todo!("Current error enum can't handle this case"))?; + f.read_to_end(&mut bytes)?; - Module::from_binary(bytes.as_slice()) - */ + Ok(Module::from_binary(bytes.as_slice())?) } - fn validate(_bytes: impl AsRef<[u8]>) -> error::CompileResult<()> { - todo!("validate") + fn validate(bytes: impl AsRef<[u8]>) -> error::CompileResult<()> { + // TODO: optimize this + let _ = Self::from_binary(bytes)?; + Ok(()) } } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index a3110bc04..17a2f820e 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -56,7 +56,7 @@ pub struct Instance { /// Reference to the module used to instantiate this instance. pub module: Arc, inner: Pin>, - /// The exports of this instance. TODO: document this + /// The exports of this instance. pub exports: Exports, #[allow(dead_code)] import_object: ImportObject, @@ -894,7 +894,8 @@ impl<'a, Args: WasmTypeList, Rets: WasmTypeList> Exportable<'a> for Func<'a, Arg pub struct Exports { // We want to avoid the borrow checker here. // This is safe because - // 1. `Exports` can't be constructed or copied (so can't safely outlive `Instance`) + // 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, @@ -931,7 +932,7 @@ impl Exports { T::get_self(self, name) } - /// This method must remain private. + /// 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(); diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 382d422fe..3e9208efa 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -176,18 +176,18 @@ impl Module { /// ``` /// # use wasmer_runtime_core::module::*; /// # fn example(module: &Module) { - /// // We can filter by `ExportKind` to get only certain types of exports. + /// // We can filter by `ExportType` to get only certain types of exports. /// // For example, here we get all the names of the functions exported by this module. /// let function_names = /// module.exports() - /// .filter(|ed| ed.kind == ExportKind::Function) + /// .filter(|ed| ed.kind == ExportType::Function) /// .map(|ed| ed.name.to_string()) /// .collect::>(); /// /// // And here we count the number of global variables exported by this module. /// let num_globals = /// module.exports() - /// .filter(|ed| ed.kind == ExportKind::Global) + /// .filter(|ed| ed.kind == ExportType::Global) /// .count(); /// # } /// ``` @@ -198,7 +198,7 @@ impl Module { .iter() .map(|(name, &ei)| ExportDescriptor { name, - kind: ei.into(), + ty: ei.into(), }) } @@ -236,7 +236,7 @@ impl Module { Import { namespace, name, - ty: sig.into(), + descriptor: sig.into(), } }); let imported_memories = @@ -247,7 +247,7 @@ impl Module { Import { namespace, name, - ty: memory_descriptor.into(), + descriptor: memory_descriptor.into(), } }); let imported_tables = @@ -258,7 +258,7 @@ impl Module { Import { namespace, name, - ty: table_descriptor.into(), + descriptor: table_descriptor.into(), } }); let imported_globals = @@ -269,7 +269,7 @@ impl Module { Import { namespace, name, - ty: global_descriptor.into(), + descriptor: global_descriptor.into(), } }); @@ -287,20 +287,18 @@ impl Module { } } -// TODO: review this vs `ExportIndex` /// Type describing an export that the [`Module`] provides. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExportDescriptor<'a> { /// The name identifying the export. pub name: &'a str, /// The type of the export. - pub kind: ExportKind, + pub ty: ExportType, } -// TODO: kind vs type /// Tag indicating the type of the export. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ExportKind { +pub enum ExportType { /// The export is a function. Function, /// The export is a global variable. @@ -311,7 +309,7 @@ pub enum ExportKind { Table, } -impl From for ExportKind { +impl From for ExportType { fn from(other: ExportIndex) -> Self { match other { ExportIndex::Func(_) => Self::Function, @@ -330,12 +328,9 @@ impl Clone for Module { } } -/// The type of import. This indicates whether the import is a function, global, memory, or table. -// TODO: discuss and research Kind vs Type; -// `Kind` has meaning to me from Haskell as an incomplete type like -// `List` which is of kind `* -> *`. +/// Information about an import such as its type and metadata about the import. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ImportType { +pub enum ImportDescriptor { /// The import is a function. Function(FuncDescriptor), /// The import is a global variable. @@ -346,45 +341,45 @@ pub enum ImportType { Table(TableDescriptor), } -impl From for ImportType { +impl From for ImportDescriptor { fn from(other: FuncDescriptor) -> Self { - ImportType::Function(other) + ImportDescriptor::Function(other) } } -impl From<&FuncDescriptor> for ImportType { +impl From<&FuncDescriptor> for ImportDescriptor { fn from(other: &FuncDescriptor) -> Self { - ImportType::Function(other.clone()) + ImportDescriptor::Function(other.clone()) } } -impl From for ImportType { +impl From for ImportDescriptor { fn from(other: MemoryDescriptor) -> Self { - ImportType::Memory(other) + ImportDescriptor::Memory(other) } } -impl From<&MemoryDescriptor> for ImportType { +impl From<&MemoryDescriptor> for ImportDescriptor { fn from(other: &MemoryDescriptor) -> Self { - ImportType::Memory(*other) + ImportDescriptor::Memory(*other) } } -impl From for ImportType { +impl From for ImportDescriptor { fn from(other: TableDescriptor) -> Self { - ImportType::Table(other) + ImportDescriptor::Table(other) } } -impl From<&TableDescriptor> for ImportType { +impl From<&TableDescriptor> for ImportDescriptor { fn from(other: &TableDescriptor) -> Self { - ImportType::Table(*other) + ImportDescriptor::Table(*other) } } -impl From for ImportType { +impl From for ImportDescriptor { fn from(other: GlobalDescriptor) -> Self { - ImportType::Global(other) + ImportDescriptor::Global(other) } } -impl From<&GlobalDescriptor> for ImportType { +impl From<&GlobalDescriptor> for ImportDescriptor { fn from(other: &GlobalDescriptor) -> Self { - ImportType::Global(*other) + ImportDescriptor::Global(*other) } } @@ -396,7 +391,7 @@ pub struct Import { /// The name of the import. pub name: String, /// The type of the import. - pub ty: ImportType, + pub descriptor: ImportDescriptor, } impl ModuleInner {} From 836711f7cd88429c7d92e7708cde73118825916b Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 31 Mar 2020 12:40:55 -0700 Subject: [PATCH 18/20] Implement `Instance::call` in terms of the exports API --- lib/runtime-core/src/instance.rs | 35 +++----------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 17a2f820e..c7f2b9f52 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -5,7 +5,7 @@ use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, - error::{CallError, CallResult, ResolveError, ResolveResult, Result, RuntimeError}, + error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError}, export::{Context, Export, ExportIter, Exportable, FuncPointer}, global::Global, import::{ImportObject, LikeNamespace}, @@ -247,37 +247,8 @@ impl Instance { note = "Please use `let f: DynFunc = instance.exports.get(name)?; f.call(params)?;` instead" )] 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 From bde319d9fb5829b98dd4c99fc448f4b15a3c64ce Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 31 Mar 2020 14:06:30 -0700 Subject: [PATCH 19/20] Fix bug and un-deprecate `Instance::call` --- lib/runtime-core/src/instance.rs | 21 +++++++++++++++++---- lib/runtime-core/src/module.rs | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index c7f2b9f52..48e59d4ec 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -230,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; @@ -242,10 +259,6 @@ impl Instance { /// # Ok(()) /// # } /// ``` - #[deprecated( - since = "0.17.0", - note = "Please use `let f: DynFunc = instance.exports.get(name)?; f.call(params)?;` instead" - )] pub fn call(&self, name: &str, params: &[Value]) -> CallResult> { let func: DynFunc = self.exports.get(name)?; func.call(params) diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 3e9208efa..f9d82f6cb 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -180,14 +180,14 @@ impl Module { /// // For example, here we get all the names of the functions exported by this module. /// let function_names = /// module.exports() - /// .filter(|ed| ed.kind == ExportType::Function) + /// .filter(|ed| ed.ty == ExportType::Function) /// .map(|ed| ed.name.to_string()) /// .collect::>(); /// /// // And here we count the number of global variables exported by this module. /// let num_globals = /// module.exports() - /// .filter(|ed| ed.kind == ExportType::Global) + /// .filter(|ed| ed.ty == ExportType::Global) /// .count(); /// # } /// ``` From 0527b50af335097eb27cc2b0ba450e739947bedb Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 31 Mar 2020 16:00:52 -0700 Subject: [PATCH 20/20] Improve consistency of names, reuse more code, and reorganize a bit --- lib/api-tests/tests/high_level_api.rs | 22 ++- lib/api/src/lib.rs | 13 +- lib/runtime-core/src/module.rs | 186 +++++++------------------- lib/runtime-core/src/types.rs | 75 +++++++++++ 4 files changed, 146 insertions(+), 150 deletions(-) diff --git a/lib/api-tests/tests/high_level_api.rs b/lib/api-tests/tests/high_level_api.rs index 59b536275..5a078b78e 100644 --- a/lib/api-tests/tests/high_level_api.rs +++ b/lib/api-tests/tests/high_level_api.rs @@ -68,39 +68,47 @@ fn custom_section_parsing_works() { #[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().collect::>(); + let exports = module.exports(); assert_eq!( exports, vec![ export::ExportDescriptor { name: "test-table", - ty: export::ExportType::Table, + ty: export::ExternDescriptor::Table(TableDescriptor { + element: ElementType::Anyfunc, + minimum: 2, + maximum: None, + }), }, export::ExportDescriptor { name: "test-global", - ty: export::ExportType::Global, + ty: export::ExternDescriptor::Global(GlobalDescriptor { + mutable: true, + ty: Type::I32, + }), }, export::ExportDescriptor { name: "ret_2", - ty: export::ExportType::Function, + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![Type::I32])), }, export::ExportDescriptor { name: "ret_4", - ty: export::ExportType::Function, + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![Type::I32])), }, export::ExportDescriptor { name: "set_test_global", - ty: export::ExportType::Function, + ty: export::ExternDescriptor::Function(FuncSig::new(vec![Type::I32], vec![])), }, export::ExportDescriptor { name: "update_outside_global", - ty: export::ExportType::Function, + ty: export::ExternDescriptor::Function(FuncSig::new(vec![], vec![])), }, ] ); diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 9e194ed79..4968e9493 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -48,7 +48,7 @@ pub mod module { // 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::module::{ExportDescriptor, ExportType, Import, ImportDescriptor}; + pub use wasmer_runtime_core::types::{ExportDescriptor, ExternDescriptor, ImportDescriptor}; // TODO: implement abstract module API } @@ -66,8 +66,8 @@ pub mod wasm { //! //! # Tables pub use wasmer_runtime_core::global::Global; - pub use wasmer_runtime_core::module::{ExportDescriptor, ExportType, Import, ImportDescriptor}; 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, }; @@ -75,13 +75,13 @@ pub mod wasm { pub mod import { //! Types and functions for Wasm imports. - pub use wasmer_runtime_core::module::{Import, ImportDescriptor}; + 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::module::{ExportDescriptor, ExportType}; + pub use wasmer_runtime_core::types::{ExportDescriptor, ExternDescriptor}; } pub mod units { @@ -91,7 +91,10 @@ pub mod units { pub mod types { //! Types used in the Wasm runtime and conversion functions. - pub use wasmer_runtime_core::types::*; + pub use wasmer_runtime_core::types::{ + ElementType, FuncDescriptor, FuncSig, GlobalDescriptor, GlobalInit, MemoryDescriptor, + TableDescriptor, Type, Value, ValueType, + }; } pub mod error { diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index f9d82f6cb..9402d924d 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -9,10 +9,10 @@ use crate::{ import::ImportObject, structures::{Map, TypedIndex}, types::{ - FuncDescriptor, 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, }; @@ -171,39 +171,48 @@ impl Module { &self.inner.info } - /// Iterate over the exports that this module provides. - /// - /// ``` - /// # use wasmer_runtime_core::module::*; - /// # fn example(module: &Module) { - /// // We can filter by `ExportType` to get only certain types of exports. - /// // For example, here we get all the names of the functions exported by this module. - /// let function_names = - /// module.exports() - /// .filter(|ed| ed.ty == ExportType::Function) - /// .map(|ed| ed.name.to_string()) - /// .collect::>(); - /// - /// // And here we count the number of global variables exported by this module. - /// let num_globals = - /// module.exports() - /// .filter(|ed| ed.ty == ExportType::Global) - /// .count(); - /// # } - /// ``` - pub fn exports(&self) -> impl Iterator + '_ { - 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(|(name, &ei)| ExportDescriptor { + .map(move |(name, &ei)| ExportDescriptor { name, - ty: ei.into(), + 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 [`Import`]s this [`Module`] requires to be instantiated. - pub fn imports(&self) -> Vec { + /// 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() @@ -233,10 +242,10 @@ impl Module { .signatures .get(*info.func_assoc.get(FuncIndex::new(idx.index())).unwrap()) .unwrap(); - Import { + ImportDescriptor { namespace, name, - descriptor: sig.into(), + ty: sig.into(), } }); let imported_memories = @@ -244,10 +253,10 @@ impl Module { .values() .map(|(import_name, memory_descriptor)| { let (namespace, name) = get_import_name(info, import_name); - Import { + ImportDescriptor { namespace, name, - descriptor: memory_descriptor.into(), + ty: memory_descriptor.into(), } }); let imported_tables = @@ -255,10 +264,10 @@ impl Module { .values() .map(|(import_name, table_descriptor)| { let (namespace, name) = get_import_name(info, import_name); - Import { + ImportDescriptor { namespace, name, - descriptor: table_descriptor.into(), + ty: table_descriptor.into(), } }); let imported_globals = @@ -266,10 +275,10 @@ impl Module { .values() .map(|(import_name, global_descriptor)| { let (namespace, name) = get_import_name(info, import_name); - Import { + ImportDescriptor { namespace, name, - descriptor: global_descriptor.into(), + ty: global_descriptor.into(), } }); @@ -287,39 +296,6 @@ impl Module { } } -/// Type describing an export that the [`Module`] provides. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ExportDescriptor<'a> { - /// The name identifying the export. - pub name: &'a str, - /// The type of the export. - pub ty: ExportType, -} - -/// Tag indicating the type of the export. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ExportType { - /// The export is a function. - Function, - /// The export is a global variable. - Global, - /// The export is a linear memory. - Memory, - /// The export is a table. - Table, -} - -impl From for ExportType { - fn from(other: ExportIndex) -> Self { - match other { - ExportIndex::Func(_) => Self::Function, - ExportIndex::Global(_) => Self::Global, - ExportIndex::Memory(_) => Self::Memory, - ExportIndex::Table(_) => Self::Table, - } - } -} - impl Clone for Module { fn clone(&self) -> Self { Self { @@ -328,72 +304,6 @@ impl Clone for Module { } } -/// Information about an import such as its type and metadata about the import. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ImportDescriptor { - /// The import is a function. - Function(FuncDescriptor), - /// The import is a global variable. - Global(GlobalDescriptor), - /// A Wasm linear memory. - Memory(MemoryDescriptor), - /// A Wasm table. - Table(TableDescriptor), -} - -impl From for ImportDescriptor { - fn from(other: FuncDescriptor) -> Self { - ImportDescriptor::Function(other) - } -} -impl From<&FuncDescriptor> for ImportDescriptor { - fn from(other: &FuncDescriptor) -> Self { - ImportDescriptor::Function(other.clone()) - } -} -impl From for ImportDescriptor { - fn from(other: MemoryDescriptor) -> Self { - ImportDescriptor::Memory(other) - } -} -impl From<&MemoryDescriptor> for ImportDescriptor { - fn from(other: &MemoryDescriptor) -> Self { - ImportDescriptor::Memory(*other) - } -} - -impl From for ImportDescriptor { - fn from(other: TableDescriptor) -> Self { - ImportDescriptor::Table(other) - } -} -impl From<&TableDescriptor> for ImportDescriptor { - fn from(other: &TableDescriptor) -> Self { - ImportDescriptor::Table(*other) - } -} -impl From for ImportDescriptor { - fn from(other: GlobalDescriptor) -> Self { - ImportDescriptor::Global(other) - } -} -impl From<&GlobalDescriptor> for ImportDescriptor { - fn from(other: &GlobalDescriptor) -> Self { - ImportDescriptor::Global(*other) - } -} - -/// A type describing an import that a [`Module`] needs to be instantiated. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Import { - /// The namespace that this import is in. - pub namespace: String, - /// The name of the import. - pub name: String, - /// The type of the import. - pub descriptor: ImportDescriptor, -} - impl ModuleInner {} #[doc(hidden)] diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index 7c0eb4630..ccf7be1fb 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -552,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;