api/js: Replace ImportObject with new type Imports

This commit is contained in:
Manos Pitsidianakis
2022-04-21 15:33:22 +03:00
parent 8aa225a192
commit 92b7cb01b5
10 changed files with 232 additions and 553 deletions

View File

@@ -192,32 +192,6 @@ impl Exports {
iter: self.map.iter(),
}
}
/// safa
pub fn get_namespace_export(&self, name: &str) -> Option<Export> {
self.map.get(name).map(|is_export| is_export.to_export())
}
/// safa
pub fn get_namespace_externs(&self) -> Vec<(String, Extern)> {
self.map
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
/// safa
pub fn get_namespace_exports(&self) -> Vec<(String, Export)> {
self.map
.iter()
.map(|(k, v)| (k.clone(), v.to_export()))
.collect()
}
/// safa
pub fn as_exports(&self) -> Option<Exports> {
Some(self.clone())
}
}
impl fmt::Debug for Exports {
@@ -300,20 +274,20 @@ impl FromIterator<(String, Extern)> for Exports {
}
impl IntoIterator for Exports {
type IntoIter = std::vec::IntoIter<(String, Extern)>;
type IntoIter = indexmap::map::IntoIter<String, Extern>;
type Item = (String, Extern);
fn into_iter(self) -> Self::IntoIter {
self.get_namespace_externs().into_iter()
self.map.clone().into_iter()
}
}
impl IntoIterator for &Exports {
type IntoIter = std::vec::IntoIter<(String, Extern)>;
type Item = (String, Extern);
impl<'a> IntoIterator for &'a Exports {
type IntoIter = indexmap::map::Iter<'a, String, Extern>;
type Item = (&'a String, &'a Extern);
fn into_iter(self) -> Self::IntoIter {
self.get_namespace_externs().into_iter()
self.map.iter()
}
}

View File

@@ -2,11 +2,11 @@
//! manipulate and access a wasm module's imports including memories, tables, globals, and
//! functions.
use crate::js::export::Export;
use crate::js::exports::Exportable;
use crate::js::resolver::NamedResolver;
use crate::js::exports::{Exportable, Exports};
use crate::js::instance::InstantiationError;
use crate::js::module::Module;
use crate::Extern;
use std::borrow::{Borrow, BorrowMut};
use std::collections::{hash_map::Entry, HashMap};
use std::collections::HashMap;
use std::fmt;
/// All of the import data used when instantiating.
@@ -17,18 +17,24 @@ use std::fmt;
/// [`imports!`]: macro.imports.html
///
/// # Usage:
/// ```ignore
/// use wasmer::{Exports, Imports, Function};
/// ```no_run
/// use wasmer::{Exports, Module, Store, Instance, imports, Imports, Function};
/// # fn foo_test(module: Module, store: Store) {
///
/// let mut import_object = Imports::new();
/// let mut env = Exports::new();
/// let host_fn = Function::new_native(foo);
/// let import_object: Imports = imports! {
/// "env" => {
/// "foo" => host_fn,
/// },
/// };
///
/// env.insert("foo", Function::new_native(foo));
/// import_object.register("env", env);
/// let instance = Instance::new(&module, &import_object).expect("Could not instantiate module.");
///
/// fn foo(n: i32) -> i32 {
/// n
/// }
///
/// # }
/// ```
#[derive(Clone, Default)]
pub struct Imports {
@@ -44,15 +50,15 @@ impl Imports {
/// Gets an export given a ns and a name
///
/// # Usage
/// ```ignore
/// # use wasmer::{Imports, Instance, Namespace};
/// ```no_run
/// # use wasmer::Imports;
/// let mut import_object = Imports::new();
/// import_object.get_export("ns", "name");
/// ```
pub fn get_export(&self, ns: &str, name: &str) -> Option<Export> {
pub fn get_export(&self, ns: &str, name: &str) -> Option<Extern> {
if self.map.contains_key(&(ns.to_string(), name.to_string())) {
let ext = &self.map[&(ns.to_string(), name.to_string())];
return Some(ext.to_export());
return Some(ext.clone());
}
None
}
@@ -62,7 +68,20 @@ impl Imports {
self.map.keys().any(|(k, _)| (k == name))
}
/// TODO: Add doc
/// Register a list of externs into a namespace.
///
/// # Usage:
/// ```no_run
/// # use wasmer::{Imports, Exports, Memory};
/// # fn foo_test(memory: Memory) {
/// let mut exports = Exports::new()
/// exports.insert("memory", memory);
///
/// let mut import_object = Imports::new();
/// import_object.register_namespace("env", exports);
/// // ...
/// # }
/// ```
pub fn register_namespace(
&mut self,
ns: &str,
@@ -73,47 +92,62 @@ impl Imports {
}
}
/// TODO: Add doc
pub fn define(&mut self, ns: &str, name: &str, extern_: Extern) {
self.map.insert((ns.to_string(), name.to_string()), extern_);
/// Add a single import with a namespace `ns` and name `name`.
///
/// # Usage
/// ```no_run
/// # let store = Default::default();
/// use wasmer::{Imports, Function};
/// fn foo(n: i32) -> i32 {
/// n
/// }
/// let mut import_object = Imports::new();
/// import_object.define("env", "foo", Function::new_native(foo));
/// ```
pub fn define(&mut self, ns: &str, name: &str, val: impl Into<Extern>) {
self.map
.insert((ns.to_string(), name.to_string()), val.into());
}
// /// Register anything that implements `LikeNamespace` as a namespace.
// ///
// /// # Usage:
// /// ```ignore
// /// # use wasmer::{Imports, Instance, Namespace};
// /// let mut import_object = Imports::new();
// ///
// /// import_object.register("namespace0", instance);
// /// import_object.register("namespace1", namespace);
// /// // ...
// /// ```
// pub fn register<S, N>(&mut self, name: S, namespace: N) -> Option<Box<dyn LikeNamespace>>
// where
// S: Into<String>,
// N: LikeNamespace + Send + Sync + 'static,
// {
// let mut guard = self.map.lock().unwrap();
// let map = guard.borrow_mut();
// match map.entry(name.into()) {
// Entry::Vacant(empty) => {
// empty.insert(Box::new(namespace));
// None
// }
// Entry::Occupied(mut occupied) => Some(occupied.insert(Box::new(namespace))),
// }
// }
/// asdfsa
pub fn resolve_by_name(&self, ns: &str, name: &str) -> Option<Export> {
self.get_export(ns, name)
/// Returns the contents of a namespace as an `Exports`.
///
/// Returns `None` if the namespace doesn't exist.
pub fn get_namespace_exports(&self, name: &str) -> Option<Exports> {
let ret: Exports = self
.map
.iter()
.filter(|((ns, _), _)| ns == name)
.map(|((_, name), e)| (name.clone(), e.clone()))
.collect();
if ret.is_empty() {
None
} else {
Some(ret)
}
}
//fn iter(&self) -> impl Iterator<(&str, &str, Extern)> {
// todo!()
//}
/// Resolve and return a vector of imports in the order they are defined in the `module`'s source code.
///
/// This means the returned `Vec<Extern>` might be a subset of the imports contained in `self`.
pub fn imports_for_module(&self, module: &Module) -> Result<Vec<Extern>, InstantiationError> {
let mut ret = vec![];
for import in module.imports() {
if let Some(imp) = self
.map
.get(&(import.module().to_string(), import.name().to_string()))
{
ret.push(imp.clone());
} else {
return Err(InstantiationError::Link(format!(
"Error while importing {0:?}.{1:?}: unknown import. Expected {2:?}",
import.module(),
import.name(),
import.ty()
)));
}
}
Ok(ret)
}
/// Returns the `Imports` as a Javascript `Object`
pub fn as_jsobject(&self) -> js_sys::Object {
@@ -151,12 +185,6 @@ impl Into<js_sys::Object> for Imports {
}
}
impl NamedResolver for Imports {
fn resolve_by_name(&self, ns: &str, name: &str) -> Option<Export> {
self.get_export(ns, name)
}
}
impl IntoIterator for &Imports {
type IntoIter = std::collections::hash_map::IntoIter<(String, String), Extern>;
type Item = ((String, String), Extern);
@@ -166,6 +194,14 @@ impl IntoIterator for &Imports {
}
}
impl Extend<((String, String), Extern)> for Imports {
fn extend<T: IntoIterator<Item = ((String, String), Extern)>>(&mut self, iter: T) {
for ((ns, name), ext) in iter.into_iter() {
self.define(&ns, &name, ext);
}
}
}
impl fmt::Debug for Imports {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
enum SecretMap {
@@ -267,96 +303,11 @@ macro_rules! import_namespace {
#[cfg(test)]
mod test {
use super::*;
use crate::js::ChainableNamedResolver;
use crate::js::exports::Exportable;
use crate::js::Type;
use crate::js::{Global, Store, Val};
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn chaining_works() {
let store = Store::default();
let g = Global::new(&store, Val::I32(0));
let imports1 = imports! {
"dog" => {
"happy" => g.clone()
}
};
let imports2 = imports! {
"dog" => {
"small" => g.clone()
},
"cat" => {
"small" => g.clone()
}
};
let resolver = imports1.chain_front(imports2);
let small_cat_export = resolver.resolve_by_name("cat", "small");
assert!(small_cat_export.is_some());
let happy = resolver.resolve_by_name("dog", "happy");
let small = resolver.resolve_by_name("dog", "small");
assert!(happy.is_some());
assert!(small.is_some());
}
#[wasm_bindgen_test]
fn extending_conflict_overwrites() {
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
let resolver = imports1.chain_front(imports2);
let happy_dog_entry = resolver.resolve_by_name("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::F32
} else {
false
});
// now test it in reverse
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
let resolver = imports1.chain_back(imports2);
let happy_dog_entry = resolver.resolve_by_name("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::I32
} else {
false
});
}
#[wasm_bindgen_test]
fn namespace() {
let store = Store::default();
@@ -368,13 +319,15 @@ mod test {
"dog" => namespace
};
let happy_dog_entry = imports1.resolve_by_name("dog", "happy").unwrap();
let happy_dog_entry = imports1.get_export("dog", "happy").unwrap();
assert!(if let Export::Global(happy_dog_global) = happy_dog_entry {
happy_dog_global.ty.ty == Type::I32
} else {
false
});
assert!(
if let Export::Global(happy_dog_global) = happy_dog_entry.to_export() {
happy_dog_global.ty.ty == Type::I32
} else {
false
}
);
}
#[wasm_bindgen_test]
@@ -428,4 +381,93 @@ mod test {
}
};
}
#[wasm_bindgen_test]
fn chaining_works() {
let store = Store::default();
let g = Global::new(&store, Val::I32(0));
let mut imports1 = imports! {
"dog" => {
"happy" => g.clone()
}
};
let imports2 = imports! {
"dog" => {
"small" => g.clone()
},
"cat" => {
"small" => g.clone()
}
};
imports1.extend(&imports2);
let small_cat_export = imports1.get_export("cat", "small");
assert!(small_cat_export.is_some());
let happy = imports1.get_export("dog", "happy");
let small = imports1.get_export("dog", "small");
assert!(happy.is_some());
assert!(small.is_some());
}
#[wasm_bindgen_test]
fn extending_conflict_overwrites() {
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let mut imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
imports1.extend(&imports2);
let happy_dog_entry = imports1.get_export("dog", "happy").unwrap();
assert!(
if let Export::Global(happy_dog_global) = happy_dog_entry.to_export() {
happy_dog_global.ty.ty == Type::F32
} else {
false
}
);
// now test it in reverse
let store = Store::default();
let g1 = Global::new(&store, Val::I32(0));
let g2 = Global::new(&store, Val::F32(0.));
let imports1 = imports! {
"dog" => {
"happy" => g1,
},
};
let mut imports2 = imports! {
"dog" => {
"happy" => g2,
},
};
imports2.extend(&imports1);
let happy_dog_entry = imports2.get_export("dog", "happy").unwrap();
assert!(
if let Export::Global(happy_dog_global) = happy_dog_entry.to_export() {
happy_dog_global.ty.ty == Type::I32
} else {
false
}
);
}
}

View File

@@ -1,9 +1,9 @@
use crate::js::env::HostEnvInitError;
use crate::js::export::Export;
use crate::js::exports::Exports;
use crate::js::exports::{Exportable, Exports};
use crate::js::externals::Extern;
use crate::js::imports::Imports;
use crate::js::module::Module;
use crate::js::resolver::Resolver;
use crate::js::store::Store;
use crate::js::trap::RuntimeError;
use js_sys::WebAssembly;
@@ -23,6 +23,8 @@ use thiserror::Error;
pub struct Instance {
instance: WebAssembly::Instance,
module: Module,
#[allow(dead_code)]
imports: Imports,
/// The exports for an instance.
pub exports: Exports,
}
@@ -92,16 +94,14 @@ impl Instance {
/// Those are, as defined by the spec:
/// * Link errors that happen when plugging the imports into the instance
/// * Runtime errors that happen when running the module `start` function.
pub fn new(
module: &Module,
resolver: &(dyn Resolver + Send + Sync),
) -> Result<Self, InstantiationError> {
let (instance, imports) = module
.instantiate(resolver)
pub fn new(module: &Module, imports: &Imports) -> Result<Self, InstantiationError> {
let import_copy = imports.clone();
let (instance, imports): (WebAssembly::Instance, Vec<Extern>) = module
.instantiate(imports)
.map_err(|e| InstantiationError::Start(e))?;
let self_instance = Self::from_module_and_instance(module, instance)?;
self_instance.init_envs(&imports)?;
let self_instance = Self::from_module_and_instance(module, instance, import_copy)?;
self_instance.init_envs(&imports.iter().map(Extern::to_export).collect::<Vec<_>>())?;
Ok(self_instance)
}
@@ -117,6 +117,7 @@ impl Instance {
pub fn from_module_and_instance(
module: &Module,
instance: WebAssembly::Instance,
imports: Imports,
) -> Result<Self, InstantiationError> {
let store = module.store();
let instance_exports = instance.exports();
@@ -141,6 +142,7 @@ impl Instance {
Ok(Self {
instance,
module: module.clone(),
imports,
exports,
})
}

View File

@@ -1,4 +1,4 @@
use crate::js::{Export, ExternType, Module, NamedResolver};
use crate::js::{Export, ExternType, Module};
use std::collections::HashMap;
/// This struct is used in case you want to create an `Instance`
@@ -67,9 +67,3 @@ impl Into<js_sys::Object> for JsImportObject {
self.object
}
}
impl NamedResolver for JsImportObject {
fn resolve_by_name(&self, module: &str, name: &str) -> Option<Export> {
self.get_export(module, name)
}
}

View File

@@ -37,7 +37,6 @@ mod module;
mod module_info_polyfill;
mod native;
mod ptr;
mod resolver;
mod store;
mod trap;
mod types;
@@ -63,9 +62,6 @@ pub use crate::js::mem_access::{MemoryAccessError, WasmRef, WasmSlice, WasmSlice
pub use crate::js::module::{Module, ModuleTypeHints};
pub use crate::js::native::NativeFunc;
pub use crate::js::ptr::{Memory32, Memory64, MemorySize, WasmPtr, WasmPtr64};
pub use crate::js::resolver::{
ChainableNamedResolver, NamedResolver, NamedResolverChain, Resolver,
};
pub use crate::js::trap::RuntimeError;
pub use crate::js::store::{Store, StoreObject};

View File

@@ -1,5 +1,6 @@
use crate::js::export::Export;
use crate::js::resolver::Resolver;
use crate::js::exports::Exportable;
use crate::js::externals::Extern;
use crate::js::imports::Imports;
use crate::js::store::Store;
use crate::js::types::{ExportType, ImportType};
// use crate::js::InstantiationError;
@@ -217,28 +218,31 @@ impl Module {
pub(crate) fn instantiate(
&self,
resolver: &dyn Resolver,
) -> Result<(WebAssembly::Instance, Vec<Export>), RuntimeError> {
let imports = js_sys::Object::new();
let mut import_externs: Vec<Export> = vec![];
for (i, import_type) in self.imports().enumerate() {
let resolved_import =
resolver.resolve(i as u32, import_type.module(), import_type.name());
imports: &Imports,
) -> Result<(WebAssembly::Instance, Vec<Extern>), RuntimeError> {
let imports_object = js_sys::Object::new();
let mut import_externs: Vec<Extern> = vec![];
for import_type in self.imports() {
let resolved_import = imports.get_export(import_type.module(), import_type.name());
if let Some(import) = resolved_import {
let val = js_sys::Reflect::get(&imports, &import_type.module().into())?;
let val = js_sys::Reflect::get(&imports_object, &import_type.module().into())?;
if !val.is_undefined() {
// If the namespace is already set
js_sys::Reflect::set(&val, &import_type.name().into(), import.as_jsvalue())?;
js_sys::Reflect::set(
&val,
&import_type.name().into(),
import.to_export().as_jsvalue(),
)?;
} else {
// If the namespace doesn't exist
let import_namespace = js_sys::Object::new();
js_sys::Reflect::set(
&import_namespace,
&import_type.name().into(),
import.as_jsvalue(),
import.to_export().as_jsvalue(),
)?;
js_sys::Reflect::set(
&imports,
&imports_object,
&import_type.module().into(),
&import_namespace.into(),
)?;
@@ -249,7 +253,7 @@ impl Module {
// the error for us, so we don't need to handle it
}
Ok((
WebAssembly::Instance::new(&self.module, &imports)
WebAssembly::Instance::new(&self.module, &imports_object)
.map_err(|e: JsValue| -> RuntimeError { e.into() })?,
import_externs,
))

View File

@@ -1,165 +0,0 @@
use crate::js::export::Export;
/// Import resolver connects imports with available exported values.
pub trait Resolver {
/// Resolves an import a WebAssembly module to an export it's hooked up to.
///
/// The `index` provided is the index of the import in the wasm module
/// that's being resolved. For example 1 means that it's the second import
/// listed in the wasm module.
///
/// The `module` and `field` arguments provided are the module/field names
/// listed on the import itself.
///
/// # Notes:
///
/// The index is useful because some WebAssembly modules may rely on that
/// for resolving ambiguity in their imports. Such as:
/// ```ignore
/// (module
/// (import "" "" (func))
/// (import "" "" (func (param i32) (result i32)))
/// )
/// ```
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export>;
}
/// Import resolver connects imports with available exported values.
///
/// This is a specific subtrait for [`Resolver`] for those users who don't
/// care about the `index`, but only about the `module` and `field` for
/// the resolution.
pub trait NamedResolver {
/// Resolves an import a WebAssembly module to an export it's hooked up to.
///
/// It receives the `module` and `field` names and return the [`Export`] in
/// case it's found.
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export>;
}
// All NamedResolvers should extend `Resolver`.
impl<T: NamedResolver> Resolver for T {
/// By default this method will be calling [`NamedResolver::resolve_by_name`],
/// dismissing the provided `index`.
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export> {
self.resolve_by_name(module, field)
}
}
impl<T: NamedResolver> NamedResolver for &T {
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
(**self).resolve_by_name(module, field)
}
}
impl NamedResolver for Box<dyn NamedResolver + Send + Sync> {
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
(**self).resolve_by_name(module, field)
}
}
impl NamedResolver for () {
/// Always returns `None`.
fn resolve_by_name(&self, _module: &str, _field: &str) -> Option<Export> {
None
}
}
/// `Resolver` implementation that always resolves to `None`. Equivalent to `()`.
pub struct NullResolver {}
impl Resolver for NullResolver {
fn resolve(&self, _idx: u32, _module: &str, _field: &str) -> Option<Export> {
None
}
}
/// A [`Resolver`] that links two resolvers together in a chain.
pub struct NamedResolverChain<A: NamedResolver + Send + Sync, B: NamedResolver + Send + Sync> {
a: A,
b: B,
}
/// A trait for chaining resolvers together.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized + Send + Sync,
/// # B: NamedResolver + Sized + Send + Sync,
/// # {
/// // override duplicates with imports from `imports2`
/// imports1.chain_front(imports2);
/// # }
/// ```
pub trait ChainableNamedResolver: NamedResolver + Sized + Send + Sync {
/// Chain a resolver in front of the current resolver.
///
/// This will cause the second resolver to override the first.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized + Send + Sync,
/// # B: NamedResolver + Sized + Send + Sync,
/// # {
/// // override duplicates with imports from `imports2`
/// imports1.chain_front(imports2);
/// # }
/// ```
fn chain_front<U>(self, other: U) -> NamedResolverChain<U, Self>
where
U: NamedResolver + Send + Sync,
{
NamedResolverChain { a: other, b: self }
}
/// Chain a resolver behind the current resolver.
///
/// This will cause the first resolver to override the second.
///
/// ```
/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
/// # fn chainable_test<A, B>(imports1: A, imports2: B)
/// # where A: NamedResolver + Sized + Send + Sync,
/// # B: NamedResolver + Sized + Send + Sync,
/// # {
/// // override duplicates with imports from `imports1`
/// imports1.chain_back(imports2);
/// # }
/// ```
fn chain_back<U>(self, other: U) -> NamedResolverChain<Self, U>
where
U: NamedResolver + Send + Sync,
{
NamedResolverChain { a: self, b: other }
}
}
// We give these chain methods to all types implementing NamedResolver
impl<T: NamedResolver + Send + Sync> ChainableNamedResolver for T {}
impl<A, B> NamedResolver for NamedResolverChain<A, B>
where
A: NamedResolver + Send + Sync,
B: NamedResolver + Send + Sync,
{
fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
self.a
.resolve_by_name(module, field)
.or_else(|| self.b.resolve_by_name(module, field))
}
}
impl<A, B> Clone for NamedResolverChain<A, B>
where
A: NamedResolver + Clone + Send + Sync,
B: NamedResolver + Clone + Send + Sync,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
}
}
}