Implement module imports into components (#4208)

* Implement module imports into components

As a step towards implementing function imports into a component this
commit implements importing modules into a component. This fills out
missing pieces of functionality such as exporting modules as well. The
previous translation code had initial support for translating imported
modules but some of the AST type information was restructured with
feedback from this implementation, namely splitting the
`InstantiateModule` initializer into separate upvar/import variants to
clarify that the item orderings for imports are resolved differently at
runtime.

Much of this commit is also adding infrastructure for any imports at all
into a component. For example a `Linker` type (analagous to
`wasmtime::Linker`) was added here as well. For now this type is quite
limited due to the inability to define host functions (it can only work
with instances and instances-of-modules) but it's enough to start
writing `*.wast` tests which exercise lots of module-related functionality.

* Fix a warning
This commit is contained in:
Alex Crichton
2022-06-03 09:33:18 -05:00
committed by GitHub
parent 816aae6aca
commit b49c5c878e
12 changed files with 1254 additions and 250 deletions

View File

@@ -108,6 +108,10 @@ pub struct Component {
/// The number of lowered host functions (maximum `LoweredIndex`) needed to /// The number of lowered host functions (maximum `LoweredIndex`) needed to
/// instantiate this component. /// instantiate this component.
pub num_lowerings: u32, pub num_lowerings: u32,
/// The number of modules that are required to be saved within an instance
/// at runtime, or effectively the number of exported modules.
pub num_runtime_modules: u32,
} }
/// Initializer instructions to get processed when instantiating a component /// Initializer instructions to get processed when instantiating a component
@@ -126,36 +130,13 @@ pub struct Component {
// all of these instructions. // all of these instructions.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum Initializer { pub enum Initializer {
/// A core was module is being instantiated. /// A core wasm module is being instantiated.
/// ///
/// This will result in a new core wasm instance being created, which may /// This will result in a new core wasm instance being created, which may
/// involve running the `start` function of the instance as well if it's /// involve running the `start` function of the instance as well if it's
/// specified. This largely delegates to the same standard instantiation /// specified. This largely delegates to the same standard instantiation
/// process as the rest of the core wasm machinery already uses. /// process as the rest of the core wasm machinery already uses.
InstantiateModule { InstantiateModule(InstantiateModule),
/// The instance of the index that's being created.
///
/// This is guaranteed to be the `n`th `InstantiateModule` instruction
/// if the index is `n`.
instance: RuntimeInstanceIndex,
/// The module that's being instantiated, either an "upvar" or an
/// imported module.
module: ModuleToInstantiate,
/// The arguments to instantiation and where they're loaded from.
///
/// Note that this is a flat list. For "upvars" this list is sorted by
/// the actual concrete imports needed by the upvar so the items can be
/// passed directly to instantiation. For imports this list is sorted
/// by the order of the import names on the type of the module
/// declaration in this component.
///
/// Each argument is a `CoreDef` which represents that it's either, at
/// this time, a lowered imported function or a core wasm item from
/// another previously instantiated instance.
args: Box<[CoreDef]>,
},
/// A host function is being lowered, creating a core wasm function. /// A host function is being lowered, creating a core wasm function.
/// ///
@@ -173,43 +154,40 @@ pub enum Initializer {
/// previously created module instance, and stored into the /// previously created module instance, and stored into the
/// `VMComponentContext` at the `index` specified. This lowering is then /// `VMComponentContext` at the `index` specified. This lowering is then
/// used in the future by pointers from `CanonicalOptions`. /// used in the future by pointers from `CanonicalOptions`.
ExtractMemory { ExtractMemory(CoreExport<MemoryIndex>),
/// The index of the memory we're storing.
///
/// This is guaranteed to be the `n`th `ExtractMemory` instruction
/// if the index is `n`.
index: RuntimeMemoryIndex,
/// The source of the memory that is stored.
export: CoreExport<MemoryIndex>,
},
/// Same as `ExtractMemory`, except it's extracting a function pointer to be /// Same as `ExtractMemory`, except it's extracting a function pointer to be
/// used as a `realloc` function. /// used as a `realloc` function.
ExtractRealloc { ExtractRealloc(CoreDef),
/// The index of the realloc function we're storing.
/// /// The `module` specified is saved into the runtime state at the next
/// This is guaranteed to be the `n`th `ExtractRealloc` instruction /// `RuntimeModuleIndex`, referred to later by `Export` definitions.
/// if the index is `n`. SaveModuleUpvar(ModuleUpvarIndex),
index: RuntimeReallocIndex,
/// The source of the function pointer that is stored. /// Same as `SaveModuleUpvar`, but for imports.
def: CoreDef, SaveModuleImport(RuntimeImportIndex),
},
} }
/// Indicator used to refer to what module is being instantiated when /// Different methods of instantiating a core wasm module.
/// `Initializer::InstantiateModule` is used.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum ModuleToInstantiate { pub enum InstantiateModule {
/// An "upvar", or a module defined within a component, is being used. /// A module defined within this component is being instantiated.
/// ///
/// The index here is correlated with the `Translation::upvars` map that's /// Note that this is distinct from the case of imported modules because the
/// created during translation of a component. /// order of imports required is statically known and can be pre-calculated
Upvar(ModuleUpvarIndex), /// to avoid string lookups related to names at runtime, represented by the
/// flat list of arguments here.
Upvar(ModuleUpvarIndex, Box<[CoreDef]>),
/// An imported core wasm module is being instantiated. /// An imported module is being instantiated.
/// ///
/// It's guaranteed that this `RuntimeImportIndex` points to a module. /// This is similar to `Upvar` but notably the imports are provided as a
Import(RuntimeImportIndex), /// two-level named map since import resolution order needs to happen at
/// runtime.
Import(
RuntimeImportIndex,
IndexMap<String, IndexMap<String, CoreDef>>,
),
} }
/// Description of a lowered import used in conjunction with /// Description of a lowered import used in conjunction with
@@ -309,6 +287,11 @@ pub enum Export {
/// Any options, if present, associated with this lifting. /// Any options, if present, associated with this lifting.
options: CanonicalOptions, options: CanonicalOptions,
}, },
/// A module defined within this component is exported.
///
/// The module index here indexes a module recorded with
/// `Initializer::SaveModule` above.
Module(RuntimeModuleIndex),
} }
/// Canonical ABI options associated with a lifted or lowered function. /// Canonical ABI options associated with a lifted or lowered function.

View File

@@ -4,6 +4,7 @@ use crate::{
Tunables, Tunables,
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use indexmap::IndexMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem; use std::mem;
use wasmparser::{Chunk, Encoding, Parser, Payload, Validator}; use wasmparser::{Chunk, Encoding, Parser, Payload, Validator};
@@ -483,7 +484,7 @@ impl<'a, 'data> Translator<'a, 'data> {
fn module_instance( fn module_instance(
&mut self, &mut self,
module_idx: ModuleIndex, module: ModuleIndex,
args: &[wasmparser::ModuleArg<'data>], args: &[wasmparser::ModuleArg<'data>],
) -> InstanceDef<'data> { ) -> InstanceDef<'data> {
// Map the flat list of `args` to instead a name-to-instance index. // Map the flat list of `args` to instead a name-to-instance index.
@@ -496,7 +497,7 @@ impl<'a, 'data> Translator<'a, 'data> {
} }
} }
let (imports, module) = match self.result.modules[module_idx].clone() { let instantiate = match self.result.modules[module].clone() {
// A module defined within this component is being instantiated // A module defined within this component is being instantiated
// which means we statically know the structure of the module. The // which means we statically know the structure of the module. The
// list of imports required is ordered by the actual list of imports // list of imports required is ordered by the actual list of imports
@@ -507,8 +508,13 @@ impl<'a, 'data> Translator<'a, 'data> {
.module .module
.imports() .imports()
.map(|(m, n, _)| (m.to_string(), n.to_string())) .map(|(m, n, _)| (m.to_string(), n.to_string()))
.collect::<Vec<_>>(); .collect::<Vec<_>>()
(args, ModuleToInstantiate::Upvar(upvar_idx)) .iter()
.map(|(module, name)| {
self.lookup_core_def(instance_by_name[module.as_str()], name)
})
.collect();
InstantiateModule::Upvar(upvar_idx, args)
} }
// For imported modules the list of arguments is built to match the // For imported modules the list of arguments is built to match the
@@ -518,36 +524,27 @@ impl<'a, 'data> Translator<'a, 'data> {
// imports in a different order. // imports in a different order.
ModuleDef::Import { ty, import } => { ModuleDef::Import { ty, import } => {
let import = self.runtime_import_index(import); let import = self.runtime_import_index(import);
let args = self.types[ty].imports.keys().cloned().collect(); let mut args = IndexMap::new();
(args, ModuleToInstantiate::Import(import)) let imports = self.types[ty].imports.keys().cloned().collect::<Vec<_>>();
for (module, name) in imports {
let def = self.lookup_core_def(instance_by_name[module.as_str()], &name);
let prev = args
.entry(module)
.or_insert(IndexMap::new())
.insert(name, def);
assert!(prev.is_none());
}
InstantiateModule::Import(import, args)
} }
}; };
// Translate the desired order of import strings to a `CoreDef` used to
// instantiate each module. Of the two-level namespace the `module` name
// is indicated by the `args` argument to this function and the `name`
// is the export of the instance found that's used.
let args = imports
.iter()
.map(|(module, name)| self.lookup_core_def(instance_by_name[module.as_str()], name))
.collect();
// Record initializer information related to this instantiation now that
// we've figure out all the arguments.
let instance = RuntimeInstanceIndex::from_u32(self.result.component.num_runtime_instances);
self.result.component.num_runtime_instances += 1;
self.result self.result
.component .component
.initializers .initializers
.push(Initializer::InstantiateModule { .push(Initializer::InstantiateModule(instantiate));
instance,
module, let instance = RuntimeInstanceIndex::from_u32(self.result.component.num_runtime_instances);
args, self.result.component.num_runtime_instances += 1;
}); InstanceDef::Module { instance, module }
InstanceDef::Module {
instance,
module: module_idx,
}
} }
/// Calculate the `CoreDef`, a definition of a core wasm item, corresponding /// Calculate the `CoreDef`, a definition of a core wasm item, corresponding
@@ -693,8 +690,17 @@ impl<'a, 'data> Translator<'a, 'data> {
let export = match export.kind { let export = match export.kind {
wasmparser::ComponentExportKind::Module(i) => { wasmparser::ComponentExportKind::Module(i) => {
let idx = ModuleIndex::from_u32(i); let idx = ModuleIndex::from_u32(i);
drop(idx); let init = match self.result.modules[idx].clone() {
unimplemented!("exporting a module"); ModuleDef::Upvar(idx) => Initializer::SaveModuleUpvar(idx),
ModuleDef::Import { import, .. } => {
Initializer::SaveModuleImport(self.runtime_import_index(import))
}
};
self.result.component.initializers.push(init);
let runtime_index =
RuntimeModuleIndex::from_u32(self.result.component.num_runtime_modules);
self.result.component.num_runtime_modules += 1;
Export::Module(runtime_index)
} }
wasmparser::ComponentExportKind::Component(i) => { wasmparser::ComponentExportKind::Component(i) => {
let idx = ComponentIndex::from_u32(i); let idx = ComponentIndex::from_u32(i);
@@ -1021,7 +1027,7 @@ impl<'a, 'data> Translator<'a, 'data> {
self.result self.result
.component .component
.initializers .initializers
.push(Initializer::ExtractMemory { index, export }); .push(Initializer::ExtractMemory(export));
index index
} }
@@ -1035,7 +1041,7 @@ impl<'a, 'data> Translator<'a, 'data> {
self.result self.result
.component .component
.initializers .initializers
.push(Initializer::ExtractRealloc { index, def }); .push(Initializer::ExtractRealloc(def));
index index
} }
} }

View File

@@ -130,6 +130,10 @@ indices! {
/// Same as `RuntimeMemoryIndex` except for the `realloc` function. /// Same as `RuntimeMemoryIndex` except for the `realloc` function.
pub struct RuntimeReallocIndex(u32); pub struct RuntimeReallocIndex(u32);
/// Index that represents an exported module from a component since that's
/// currently the only use for saving the entire module state at runtime.
pub struct RuntimeModuleIndex(u32);
} }
// Reexport for convenience some core-wasm indices which are also used in the // Reexport for convenience some core-wasm indices which are also used in the

View File

@@ -1,12 +1,14 @@
use crate::component::{Component, ComponentParams, ComponentValue, Func, TypedFunc}; use crate::component::{Component, ComponentParams, ComponentValue, Func, TypedFunc};
use crate::instance::OwnedImports; use crate::instance::OwnedImports;
use crate::store::{StoreOpaque, Stored}; use crate::store::{StoreOpaque, Stored};
use crate::{AsContextMut, Module, StoreContextMut}; use crate::{AsContextMut, Module, StoreContext, StoreContextMut};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use std::marker;
use std::sync::Arc; use std::sync::Arc;
use wasmtime_environ::component::{ use wasmtime_environ::component::{
ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, ModuleToInstantiate, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, InstantiateModule,
RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeReallocIndex, RuntimeImportIndex, RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeModuleIndex,
RuntimeReallocIndex,
}; };
use wasmtime_environ::{EntityIndex, PrimaryMap}; use wasmtime_environ::{EntityIndex, PrimaryMap};
@@ -26,6 +28,7 @@ pub(crate) struct InstanceData {
// alive and things like that, instead only the bare minimum necessary // alive and things like that, instead only the bare minimum necessary
// should be kept alive here (mostly just `wasmtime_environ::Component`. // should be kept alive here (mostly just `wasmtime_environ::Component`.
component: Component, component: Component,
exported_modules: PrimaryMap<RuntimeModuleIndex, Module>,
// TODO: move these to `VMComponentContext` // TODO: move these to `VMComponentContext`
memories: PrimaryMap<RuntimeMemoryIndex, wasmtime_runtime::ExportMemory>, memories: PrimaryMap<RuntimeMemoryIndex, wasmtime_runtime::ExportMemory>,
@@ -33,21 +36,6 @@ pub(crate) struct InstanceData {
} }
impl Instance { impl Instance {
/// Instantiates the `component` provided within the given `store`.
///
/// Does not support components which have imports at this time.
//
// FIXME: need to write more docs here.
pub fn new(mut store: impl AsContextMut, component: &Component) -> Result<Instance> {
let mut store = store.as_context_mut();
let mut instantiator = Instantiator::new(component);
instantiator.run(&mut store)?;
let data = Box::new(instantiator.data);
Ok(Instance(store.0.store_data_mut().insert(Some(data))))
}
/// Looks up a function by name within this [`Instance`]. /// Looks up a function by name within this [`Instance`].
/// ///
/// The `store` specified must be the store that this instance lives within /// The `store` specified must be the store that this instance lives within
@@ -101,6 +89,41 @@ impl Instance {
Ok(f.typed::<Params, Results, _>(store) Ok(f.typed::<Params, Results, _>(store)
.with_context(|| format!("failed to convert function `{}` to given type", name))?) .with_context(|| format!("failed to convert function `{}` to given type", name))?)
} }
/// Returns an iterator of all of the exported modules that this instance
/// contains.
//
// FIXME: this should probably be generalized in some form to something else
// that either looks like:
//
// * an iterator over all exports
// * an iterator for a `Component` with type information followed by a
// `get_module` function here
//
// For now this is just quick-and-dirty to get wast support for iterating
// over exported modules to work.
pub fn modules<'a, T: 'a>(
&'a self,
store: impl Into<StoreContext<'a, T>>,
) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
let store = store.into();
self._modules(store.0)
}
fn _modules<'a>(
&'a self,
store: &'a StoreOpaque,
) -> impl Iterator<Item = (&'a str, &'a Module)> + '_ {
let data = store.store_data()[self.0].as_ref().unwrap();
data.component
.env_component()
.exports
.iter()
.filter_map(|(name, export)| match *export {
Export::Module(idx) => Some((name.as_str(), &data.exported_modules[idx])),
_ => None,
})
}
} }
impl InstanceData { impl InstanceData {
@@ -109,6 +132,7 @@ impl InstanceData {
Export::LiftedFunction { ty, func, options } => { Export::LiftedFunction { ty, func, options } => {
Some(Func::from_lifted_func(store, self, *ty, func, options)) Some(Func::from_lifted_func(store, self, *ty, func, options))
} }
Export::Module(_) => None,
} }
} }
@@ -156,21 +180,30 @@ impl InstanceData {
struct Instantiator<'a> { struct Instantiator<'a> {
component: &'a Component, component: &'a Component,
data: InstanceData, data: InstanceData,
imports: OwnedImports, core_imports: OwnedImports,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
}
pub enum RuntimeImport {
Module(Module),
} }
impl<'a> Instantiator<'a> { impl<'a> Instantiator<'a> {
fn new(component: &'a Component) -> Instantiator<'a> { fn new(
component: &'a Component,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> Instantiator<'a> {
let env_component = component.env_component(); let env_component = component.env_component();
if env_component.imports.len() > 0 {
unimplemented!("component imports");
}
Instantiator { Instantiator {
component, component,
imports: OwnedImports::empty(), imports,
core_imports: OwnedImports::empty(),
data: InstanceData { data: InstanceData {
instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize), instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize),
component: component.clone(), component: component.clone(),
exported_modules: PrimaryMap::with_capacity(
env_component.num_runtime_modules as usize,
),
memories: Default::default(), memories: Default::default(),
reallocs: Default::default(), reallocs: Default::default(),
}, },
@@ -181,16 +214,27 @@ impl<'a> Instantiator<'a> {
let env_component = self.component.env_component(); let env_component = self.component.env_component();
for initializer in env_component.initializers.iter() { for initializer in env_component.initializers.iter() {
match initializer { match initializer {
Initializer::InstantiateModule { Initializer::InstantiateModule(m) => {
instance, let module;
module, let imports = match m {
args, // Since upvars are statically know we know that the
} => { // `args` list is already in the right order.
let module = match module { InstantiateModule::Upvar(idx, args) => {
ModuleToInstantiate::Upvar(module) => self.component.upvar(*module), module = self.component.upvar(*idx);
ModuleToInstantiate::Import(idx) => { self.build_imports(store.0, module, args.iter())
drop(idx); }
unimplemented!("component module imports"); // With imports, unlike upvars, we need to do runtime
// lookups with strings to determine the order of the
// imports since it's whatever the actual module
// requires.
InstantiateModule::Import(idx, args) => {
module = match &self.imports[*idx] {
RuntimeImport::Module(m) => m,
};
let args = module
.imports()
.map(|import| &args[import.module()][import.name()]);
self.build_imports(store.0, module, args)
} }
}; };
@@ -198,42 +242,51 @@ impl<'a> Instantiator<'a> {
// validity of the component means that type-checks have // validity of the component means that type-checks have
// already been performed. This maens that the unsafety due // already been performed. This maens that the unsafety due
// to imports having the wrong type should not happen here. // to imports having the wrong type should not happen here.
let imports = self.build_imports(store.0, module, args);
let i = let i =
unsafe { crate::Instance::new_started(store, module, imports.as_ref())? }; unsafe { crate::Instance::new_started(store, module, imports.as_ref())? };
let idx = self.data.instances.push(i); self.data.instances.push(i);
assert_eq!(idx, *instance);
} }
Initializer::LowerImport(_) => unimplemented!(), Initializer::LowerImport(_) => unimplemented!(),
Initializer::ExtractMemory { index, export } => { Initializer::ExtractMemory(export) => {
let memory = match self.data.lookup_export(store.0, export) { let memory = match self.data.lookup_export(store.0, export) {
wasmtime_runtime::Export::Memory(m) => m, wasmtime_runtime::Export::Memory(m) => m,
_ => unreachable!(), _ => unreachable!(),
}; };
assert_eq!(*index, self.data.memories.push(memory)); self.data.memories.push(memory);
} }
Initializer::ExtractRealloc { index, def } => { Initializer::ExtractRealloc(def) => {
let func = match self.data.lookup_def(store.0, def) { let func = match self.data.lookup_def(store.0, def) {
wasmtime_runtime::Export::Function(f) => f, wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), _ => unreachable!(),
}; };
assert_eq!(*index, self.data.reallocs.push(func)); self.data.reallocs.push(func);
}
Initializer::SaveModuleUpvar(idx) => {
self.data
.exported_modules
.push(self.component.upvar(*idx).clone());
}
Initializer::SaveModuleImport(idx) => {
self.data.exported_modules.push(match &self.imports[*idx] {
RuntimeImport::Module(m) => m.clone(),
});
} }
} }
} }
Ok(()) Ok(())
} }
fn build_imports( fn build_imports<'b>(
&mut self, &mut self,
store: &mut StoreOpaque, store: &mut StoreOpaque,
module: &Module, module: &Module,
args: &[CoreDef], args: impl Iterator<Item = &'b CoreDef>,
) -> &OwnedImports { ) -> &OwnedImports {
self.imports.clear(); self.core_imports.clear();
self.imports.reserve(module); self.core_imports.reserve(module);
for arg in args { for arg in args {
let export = self.data.lookup_def(store, arg); let export = self.data.lookup_def(store, arg);
@@ -242,10 +295,58 @@ impl<'a> Instantiator<'a> {
// directly from an instance which should only give us valid export // directly from an instance which should only give us valid export
// items. // items.
unsafe { unsafe {
self.imports.push_export(&export); self.core_imports.push_export(&export);
} }
} }
&self.imports &self.core_imports
}
}
/// A "pre-instantiated" [`Instance`] which has all of its arguments already
/// supplied and is ready to instantiate.
///
/// This structure represents an efficient form of instantiation where import
/// type-checking and import lookup has all been resolved by the time that this
/// type is created. This type is primarily created through the
/// [`Linker::instance_pre`](crate::component::Linker::instance_pre) method.
pub struct InstancePre<T> {
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T> InstancePre<T> {
/// This function is `unsafe` since there's no guarantee that the
/// `RuntimeImport` items provided are guaranteed to work with the `T` of
/// the store.
///
/// Additionally there is no static guarantee that the `imports` provided
/// satisfy the imports of the `component` provided.
pub(crate) unsafe fn new_unchecked(
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> InstancePre<T> {
InstancePre {
component,
imports,
_marker: marker::PhantomData,
}
}
/// Returns the underlying component that will be instantiated.
pub fn component(&self) -> &Component {
&self.component
}
/// Performs the instantiation process into the store specified.
//
// TODO: needs more docs
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
let mut store = store.as_context_mut();
let mut i = Instantiator::new(&self.component, &self.imports);
i.run(&mut store)?;
let data = Box::new(i.data);
Ok(Instance(store.0.store_data_mut().insert(Some(data))))
} }
} }

View File

@@ -0,0 +1,272 @@
use crate::component::instance::RuntimeImport;
use crate::component::matching::TypeChecker;
use crate::component::{Component, Instance, InstancePre};
use crate::{AsContextMut, Engine, Module};
use anyhow::{anyhow, bail, Context, Result};
use std::collections::hash_map::{Entry, HashMap};
use std::marker;
use std::sync::Arc;
use wasmtime_environ::PrimaryMap;
/// A type used to instantiate [`Component`]s.
///
/// This type is used to both link components together as well as supply host
/// functionality to components. Values are defined in a [`Linker`] by their
/// import name and then components are instantiated with a [`Linker`] using the
/// names provided for name resolution of the component's imports.
pub struct Linker<T> {
engine: Engine,
strings: Strings,
map: NameMap,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
#[derive(Default)]
pub struct Strings {
string2idx: HashMap<Arc<str>, usize>,
strings: Vec<Arc<str>>,
}
/// Structure representing an "instance" being defined within a linker.
///
/// Instances do not need to be actual [`Instance`]s and instead are defined by
/// a "bag of named items", so each [`LinkerInstance`] can further define items
/// internally.
pub struct LinkerInstance<'a, T> {
strings: &'a mut Strings,
map: &'a mut NameMap,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
pub type NameMap = HashMap<usize, Definition>;
#[derive(Clone)]
pub enum Definition {
Instance(NameMap),
Module(Module),
}
impl<T> Linker<T> {
/// Creates a new linker for the [`Engine`] specified with no items defined
/// within it.
pub fn new(engine: &Engine) -> Linker<T> {
Linker {
engine: engine.clone(),
strings: Strings::default(),
map: NameMap::default(),
allow_shadowing: false,
_marker: marker::PhantomData,
}
}
/// Returns the [`Engine`] this is connected to.
pub fn engine(&self) -> &Engine {
&self.engine
}
/// Configures whether or not name-shadowing is allowed.
///
/// By default name shadowing is not allowed and it's an error to redefine
/// the same name within a linker.
pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self {
self.allow_shadowing = allow;
self
}
/// Returns the "root instance" of this linker, used to define names into
/// the root namespace.
pub fn root(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
strings: &mut self.strings,
map: &mut self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Returns a builder for the named instance specified.
///
/// # Errors
///
/// Returns an error if `name` is already defined within the linker.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.root().into_instance(name)
}
/// Performs a "pre-instantiation" to resolve the imports of the
/// [`Component`] specified with the items defined within this linker.
///
/// This method will perform as much work as possible short of actually
/// instnatiating an instance. Internally this will use the names defined
/// within this linker to satisfy the imports of the [`Component`] provided.
/// Additionally this will perform type-checks against the component's
/// imports against all items defined within this linker.
///
/// Note that unlike internally in components where subtyping at the
/// interface-types layer is supported this is not supported here. Items
/// defined in this linker must match the component's imports precisely.
///
/// # Errors
///
/// Returns an error if this linker doesn't define a name that the
/// `component` imports or if a name defined doesn't match the type of the
/// item imported by the `component` provided.
pub fn instantiate_pre(&self, component: &Component) -> Result<InstancePre<T>> {
let cx = TypeChecker {
types: component.types(),
strings: &self.strings,
};
// Walk over the component's list of import names and use that to lookup
// the definition within this linker that it corresponds to. When found
// perform a typecheck against the component's expected type.
let env_component = component.env_component();
for (_idx, (name, ty)) in env_component.import_types.iter() {
let import = self
.strings
.lookup(name)
.and_then(|name| self.map.get(&name))
.ok_or_else(|| anyhow!("import `{name}` not defined"))?;
cx.definition(ty, import)
.with_context(|| format!("import `{name}` has the wrong type"))?;
}
// Now that all imports are known to be defined and satisfied by this
// linker a list of "flat" import items (aka no instances) is created
// using the import map within the component created at
// component-compile-time.
let mut imports = PrimaryMap::with_capacity(env_component.imports.len());
for (idx, (import, names)) in env_component.imports.iter() {
let (root, _) = &env_component.import_types[*import];
let root = self.strings.lookup(root).unwrap();
// This is the flattening process where we go from a definition
// optionally through a list of exported names to get to the final
// item.
let mut cur = &self.map[&root];
for name in names {
let name = self.strings.lookup(name).unwrap();
cur = match cur {
Definition::Instance(map) => &map[&name],
_ => unreachable!(),
};
}
let import = match cur {
Definition::Module(m) => RuntimeImport::Module(m.clone()),
// This is guaranteed by the compilation process that "leaf"
// runtime imports are never instances.
Definition::Instance(_) => unreachable!(),
};
let i = imports.push(import);
assert_eq!(i, idx);
}
Ok(unsafe { InstancePre::new_unchecked(component.clone(), imports) })
}
/// Instantiates the [`Component`] provided into the `store` specified.
///
/// This function will use the items defined within this [`Linker`] to
/// satisfy the imports of the [`Component`] provided as necessary. For more
/// information about this see [`Linker::instantiate_pre`] as well.
///
/// # Errors
///
/// Returns an error if this [`Linker`] doesn't define an import that
/// `component` requires or if it is of the wrong type. Additionally this
/// can return an error if something goes wrong during instantiation such as
/// a runtime trap or a runtime limit being exceeded.
pub fn instantiate(
&self,
store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance> {
self.instantiate_pre(component)?.instantiate(store)
}
}
impl<T> LinkerInstance<'_, T> {
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
strings: self.strings,
map: self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Defines a [`Module`] within this instance.
///
/// This can be used to provide a core wasm [`Module`] as an import to a
/// component. The [`Module`] provided is saved within the linker for the
/// specified `name` in this instance.
pub fn module(&mut self, name: &str, module: &Module) -> Result<()> {
let name = self.strings.intern(name);
self.insert(name, Definition::Module(module.clone()))
}
/// Defines a nested instance within this instance.
///
/// This can be used to describe arbitrarily nested levels of instances
/// within a linker to satisfy nested instance exports of components.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.as_mut().into_instance(name)
}
/// Same as [`LinkerInstance::instance`] except with different liftime
/// parameters.
pub fn into_instance(mut self, name: &str) -> Result<Self> {
let name = self.strings.intern(name);
let item = Definition::Instance(NameMap::default());
let slot = match self.map.entry(name) {
Entry::Occupied(_) if !self.allow_shadowing => {
bail!("import of `{}` defined twice", self.strings.strings[name])
}
Entry::Occupied(o) => {
let slot = o.into_mut();
*slot = item;
slot
}
Entry::Vacant(v) => v.insert(item),
};
self.map = match slot {
Definition::Instance(map) => map,
_ => unreachable!(),
};
Ok(self)
}
fn insert(&mut self, key: usize, item: Definition) -> Result<()> {
match self.map.entry(key) {
Entry::Occupied(_) if !self.allow_shadowing => {
bail!("import of `{}` defined twice", self.strings.strings[key])
}
Entry::Occupied(mut e) => {
e.insert(item);
}
Entry::Vacant(v) => {
v.insert(item);
}
}
Ok(())
}
}
impl Strings {
fn intern(&mut self, string: &str) -> usize {
if let Some(idx) = self.string2idx.get(string) {
return *idx;
}
let string: Arc<str> = string.into();
let idx = self.strings.len();
self.strings.push(string.clone());
self.string2idx.insert(string, idx);
idx
}
pub fn lookup(&self, string: &str) -> Option<usize> {
self.string2idx.get(string).cloned()
}
}

View File

@@ -0,0 +1,84 @@
use crate::component::linker::{Definition, NameMap, Strings};
use crate::types::matching;
use crate::Module;
use anyhow::{anyhow, bail, Context, Result};
use wasmtime_environ::component::{ComponentInstanceType, ComponentTypes, ModuleType, TypeDef};
pub struct TypeChecker<'a> {
pub types: &'a ComponentTypes,
pub strings: &'a Strings,
}
impl TypeChecker<'_> {
pub fn definition(&self, expected: &TypeDef, actual: &Definition) -> Result<()> {
match *expected {
TypeDef::Module(t) => match actual {
Definition::Module(actual) => self.module(&self.types[t], actual),
_ => bail!("expected module found {}", actual.desc()),
},
TypeDef::ComponentInstance(t) => match actual {
Definition::Instance(actual) => self.instance(&self.types[t], actual),
_ => bail!("expected instance found {}", actual.desc()),
},
TypeDef::Func(_) => bail!("expected func found {}", actual.desc()),
TypeDef::Component(_) => bail!("expected component found {}", actual.desc()),
TypeDef::Interface(_) => bail!("expected type found {}", actual.desc()),
}
}
fn module(&self, expected: &ModuleType, actual: &Module) -> Result<()> {
let actual_types = actual.types();
let actual = actual.env_module();
// Every export that is expected should be in the actual module we have
for (name, expected) in expected.exports.iter() {
let idx = actual
.exports
.get(name)
.ok_or_else(|| anyhow!("module export `{name}` not defined"))?;
let actual = actual.type_of(*idx);
matching::entity_ty(expected, self.types.module_types(), &actual, actual_types)
.with_context(|| format!("module export `{name}` has the wrong type"))?;
}
// Note the opposite order of checks here. Every import that the actual
// module expects should be imported by the expected module since the
// expected module has the set of items given to the actual module.
// Additionally the "matches" check is inverted here.
for (module, name, actual) in actual.imports() {
// TODO: shouldn't need a `.to_string()` here ideally
let expected = expected
.imports
.get(&(module.to_string(), name.to_string()))
.ok_or_else(|| anyhow!("module import `{module}::{name}` not defined"))?;
matching::entity_ty(&actual, actual_types, expected, self.types.module_types())
.with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
}
Ok(())
}
fn instance(&self, expected: &ComponentInstanceType, actual: &NameMap) -> Result<()> {
// Like modules, every export in the expected type must be present in
// the actual type. It's ok, though, to have extra exports in the actual
// type.
for (name, expected) in expected.exports.iter() {
let actual = self
.strings
.lookup(name)
.and_then(|name| actual.get(&name))
.ok_or_else(|| anyhow!("instance export `{name}` not defined"))?;
self.definition(expected, actual)
.with_context(|| format!("instance export `{name}` has the wrong type"))?;
}
Ok(())
}
}
impl Definition {
fn desc(&self) -> &'static str {
match self {
Definition::Module(_) => "module",
Definition::Instance(_) => "instance",
}
}
}

View File

@@ -6,10 +6,13 @@
mod component; mod component;
mod func; mod func;
mod instance; mod instance;
mod linker;
mod matching;
mod store; mod store;
pub use self::component::Component; pub use self::component::Component;
pub use self::func::{ComponentParams, ComponentValue, Func, Op, TypedFunc, WasmList, WasmStr}; pub use self::func::{ComponentParams, ComponentValue, Func, Op, TypedFunc, WasmList, WasmStr};
pub use self::instance::Instance; pub use self::instance::{Instance, InstancePre};
pub use self::linker::Linker;
// These items are expected to be used by an eventual // These items are expected to be used by an eventual
// `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability // `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability

View File

@@ -1,7 +1,7 @@
use crate::linker::Definition; use crate::linker::Definition;
use crate::store::StoreOpaque; use crate::store::StoreOpaque;
use crate::{signatures::SignatureCollection, Engine, Extern}; use crate::{signatures::SignatureCollection, Engine, Extern};
use anyhow::{bail, Result}; use anyhow::{anyhow, bail, Result};
use wasmtime_environ::{ use wasmtime_environ::{
EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType, EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType,
}; };
@@ -16,84 +16,25 @@ pub struct MatchCx<'a> {
impl MatchCx<'_> { impl MatchCx<'_> {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> { pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> {
self.global_ty(expected, actual.wasmtime_ty(self.store.store_data())) global_ty(expected, actual.wasmtime_ty(self.store.store_data()))
}
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
match_bool(
expected.mutability,
actual.mutability,
"global",
"mutable",
"immutable",
)?;
Ok(())
} }
pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> { pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> {
self.table_ty( table_ty(
expected, expected,
actual.wasmtime_ty(self.store.store_data()), actual.wasmtime_ty(self.store.store_data()),
Some(actual.internal_size(self.store)), Some(actual.internal_size(self.store)),
) )
} }
fn table_ty(
&self,
expected: &Table,
actual: &Table,
actual_runtime_size: Option<u32>,
) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "table")?;
match_limits(
expected.minimum.into(),
expected.maximum.map(|i| i.into()),
actual_runtime_size.unwrap_or(actual.minimum).into(),
actual.maximum.map(|i| i.into()),
"table",
)?;
Ok(())
}
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> { pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> {
self.memory_ty( memory_ty(
expected, expected,
actual.wasmtime_ty(self.store.store_data()), actual.wasmtime_ty(self.store.store_data()),
Some(actual.internal_size(self.store)), Some(actual.internal_size(self.store)),
) )
} }
fn memory_ty(
&self,
expected: &Memory,
actual: &Memory,
actual_runtime_size: Option<u64>,
) -> Result<()> {
match_bool(
expected.shared,
actual.shared,
"memory",
"shared",
"non-shared",
)?;
match_bool(
expected.memory64,
actual.memory64,
"memory",
"64-bit",
"32-bit",
)?;
match_limits(
expected.minimum,
expected.maximum,
actual_runtime_size.unwrap_or(actual.minimum),
actual.maximum,
"memory",
)?;
Ok(())
}
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> { pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> {
self.vmshared_signature_index(expected, actual.sig_index(self.store.store_data())) self.vmshared_signature_index(expected, actual.sig_index(self.store.store_data()))
} }
@@ -130,27 +71,7 @@ impl MatchCx<'_> {
} }
}; };
let render = |ty: &WasmFuncType| { Err(func_ty_mismatch(msg, expected, &actual))
let params = ty
.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
};
bail!(
"{}: expected func of type {}, found func of type {}",
msg,
render(expected),
render(&actual)
)
} }
/// Validates that the `expected` type matches the type of `actual` /// Validates that the `expected` type matches the type of `actual`
@@ -188,6 +109,118 @@ impl MatchCx<'_> {
} }
} }
#[cfg_attr(not(feature = "component-model"), allow(dead_code))]
pub fn entity_ty(
expected: &EntityType,
expected_types: &ModuleTypes,
actual: &EntityType,
actual_types: &ModuleTypes,
) -> Result<()> {
match expected {
EntityType::Memory(expected) => match actual {
EntityType::Memory(actual) => memory_ty(expected, actual, None),
_ => bail!("expected memory found {}", entity_desc(actual)),
},
EntityType::Global(expected) => match actual {
EntityType::Global(actual) => global_ty(expected, actual),
_ => bail!("expected global found {}", entity_desc(actual)),
},
EntityType::Table(expected) => match actual {
EntityType::Table(actual) => table_ty(expected, actual, None),
_ => bail!("expected table found {}", entity_desc(actual)),
},
EntityType::Function(expected) => match actual {
EntityType::Function(actual) => {
let expected = &expected_types[*expected];
let actual = &actual_types[*actual];
if expected == actual {
Ok(())
} else {
Err(func_ty_mismatch(
"function types incompaible",
expected,
actual,
))
}
}
_ => bail!("expected func found {}", entity_desc(actual)),
},
EntityType::Tag(_) => unimplemented!(),
}
}
fn func_ty_mismatch(msg: &str, expected: &WasmFuncType, actual: &WasmFuncType) -> anyhow::Error {
let render = |ty: &WasmFuncType| {
let params = ty
.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
};
anyhow!(
"{msg}: expected func of type {}, found func of type {}",
render(expected),
render(actual)
)
}
fn global_ty(expected: &Global, actual: &Global) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
match_bool(
expected.mutability,
actual.mutability,
"global",
"mutable",
"immutable",
)?;
Ok(())
}
fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option<u32>) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "table")?;
match_limits(
expected.minimum.into(),
expected.maximum.map(|i| i.into()),
actual_runtime_size.unwrap_or(actual.minimum).into(),
actual.maximum.map(|i| i.into()),
"table",
)?;
Ok(())
}
fn memory_ty(expected: &Memory, actual: &Memory, actual_runtime_size: Option<u64>) -> Result<()> {
match_bool(
expected.shared,
actual.shared,
"memory",
"shared",
"non-shared",
)?;
match_bool(
expected.memory64,
actual.memory64,
"memory",
"64-bit",
"32-bit",
)?;
match_limits(
expected.minimum,
expected.maximum,
actual_runtime_size.unwrap_or(actual.minimum),
actual.maximum,
"memory",
)?;
Ok(())
}
fn match_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> { fn match_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> {
if expected == actual { if expected == actual {
return Ok(()); return Ok(());

View File

@@ -39,7 +39,9 @@ pub struct WastContext<T> {
/// Wast files have a concept of a "current" module, which is the most /// Wast files have a concept of a "current" module, which is the most
/// recently defined. /// recently defined.
current: Option<Instance>, current: Option<Instance>,
linker: Linker<T>, core_linker: Linker<T>,
#[cfg(feature = "component-model")]
component_linker: component::Linker<T>,
store: Store<T>, store: Store<T>,
} }
@@ -70,11 +72,17 @@ impl<T> WastContext<T> {
// Spec tests will redefine the same module/name sometimes, so we need // Spec tests will redefine the same module/name sometimes, so we need
// to allow shadowing in the linker which picks the most recent // to allow shadowing in the linker which picks the most recent
// definition as what to link when linking. // definition as what to link when linking.
let mut linker = Linker::new(store.engine()); let mut core_linker = Linker::new(store.engine());
linker.allow_shadowing(true); core_linker.allow_shadowing(true);
Self { Self {
current: None, current: None,
linker, core_linker,
#[cfg(feature = "component-model")]
component_linker: {
let mut linker = component::Linker::new(store.engine());
linker.allow_shadowing(true);
linker
},
store, store,
} }
} }
@@ -82,7 +90,7 @@ impl<T> WastContext<T> {
fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Extern> { fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Extern> {
match module { match module {
Some(module) => self Some(module) => self
.linker .core_linker
.get(&mut self.store, module, name) .get(&mut self.store, module, name)
.ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name)), .ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name)),
None => self None => self
@@ -96,7 +104,7 @@ impl<T> WastContext<T> {
fn instantiate_module(&mut self, module: &[u8]) -> Result<Outcome<Instance>> { fn instantiate_module(&mut self, module: &[u8]) -> Result<Outcome<Instance>> {
let module = Module::new(self.store.engine(), module)?; let module = Module::new(self.store.engine(), module)?;
let instance = match self.linker.instantiate(&mut self.store, &module) { let instance = match self.core_linker.instantiate(&mut self.store, &module) {
Ok(i) => i, Ok(i) => i,
Err(e) => return e.downcast::<Trap>().map(Outcome::Trap), Err(e) => return e.downcast::<Trap>().map(Outcome::Trap),
}; };
@@ -105,8 +113,9 @@ impl<T> WastContext<T> {
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
fn instantiate_component(&mut self, module: &[u8]) -> Result<Outcome<component::Instance>> { fn instantiate_component(&mut self, module: &[u8]) -> Result<Outcome<component::Instance>> {
let module = component::Component::new(self.store.engine(), module)?; let engine = self.store.engine();
let instance = match component::Instance::new(&mut self.store, &module) { let module = component::Component::new(engine, module)?;
let instance = match self.component_linker.instantiate(&mut self.store, &module) {
Ok(i) => i, Ok(i) => i,
Err(e) => return e.downcast::<Trap>().map(Outcome::Trap), Err(e) => return e.downcast::<Trap>().map(Outcome::Trap),
}; };
@@ -115,7 +124,7 @@ impl<T> WastContext<T> {
/// Register "spectest" which is used by the spec testsuite. /// Register "spectest" which is used by the spec testsuite.
pub fn register_spectest(&mut self) -> Result<()> { pub fn register_spectest(&mut self) -> Result<()> {
link_spectest(&mut self.linker, &mut self.store)?; link_spectest(&mut self.core_linker, &mut self.store)?;
Ok(()) Ok(())
} }
@@ -164,15 +173,28 @@ impl<T> WastContext<T> {
Outcome::Trap(e) => return Err(e).context("instantiation failed"), Outcome::Trap(e) => return Err(e).context("instantiation failed"),
}; };
if let Some(name) = name { if let Some(name) = name {
self.linker self.core_linker
.instance(&mut self.store, name.name(), instance)?; .instance(&mut self.store, name.name(), instance)?;
} }
self.current = Some(instance); self.current = Some(instance);
} else { } else {
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
match self.instantiate_component(&bytes)? { {
Outcome::Ok(_) => {} let instance = match self.instantiate_component(&bytes)? {
Outcome::Trap(e) => return Err(e).context("instantiation failed"), Outcome::Ok(i) => i,
Outcome::Trap(e) => return Err(e).context("instantiation failed"),
};
if let Some(name) = name {
// TODO: should ideally reflect more than just modules into
// the linker's namespace but that's not easily supported
// today for host functions due to the inability to take a
// function from one instance and put it into the linker
// (must go through the host right now).
let mut linker = self.component_linker.instance(name.name())?;
for (name, module) in instance.modules(&self.store) {
linker.module(name, module)?;
}
}
} }
#[cfg(not(feature = "component-model"))] #[cfg(not(feature = "component-model"))]
bail!("component-model support not enabled"); bail!("component-model support not enabled");
@@ -183,13 +205,14 @@ impl<T> WastContext<T> {
/// Register an instance to make it available for performing actions. /// Register an instance to make it available for performing actions.
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> { fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
match name { match name {
Some(name) => self.linker.alias_module(name, as_name), Some(name) => self.core_linker.alias_module(name, as_name),
None => { None => {
let current = *self let current = *self
.current .current
.as_ref() .as_ref()
.ok_or(anyhow!("no previous instance"))?; .ok_or(anyhow!("no previous instance"))?;
self.linker.instance(&mut self.store, as_name, current)?; self.core_linker
.instance(&mut self.store, as_name, current)?;
Ok(()) Ok(())
} }
} }

View File

@@ -89,7 +89,7 @@ fn thunks() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
instance instance
.get_typed_func::<(), (), _>(&mut store, "thunk")? .get_typed_func::<(), (), _>(&mut store, "thunk")?
.call(&mut store, ())?; .call(&mut store, ())?;
@@ -146,7 +146,7 @@ fn typecheck() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let thunk = instance.get_func(&mut store, "thunk").unwrap(); let thunk = instance.get_func(&mut store, "thunk").unwrap();
let take_string = instance.get_func(&mut store, "take-string").unwrap(); let take_string = instance.get_func(&mut store, "take-string").unwrap();
let take_two_args = instance.get_func(&mut store, "take-two-args").unwrap(); let take_two_args = instance.get_func(&mut store, "take-two-args").unwrap();
@@ -252,7 +252,7 @@ fn integers() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
// Passing in 100 is valid for all primitives // Passing in 100 is valid for all primitives
instance instance
@@ -502,7 +502,7 @@ fn type_layers() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
instance instance
.get_typed_func::<(Box<u32>,), (), _>(&mut store, "take-u32")? .get_typed_func::<(Box<u32>,), (), _>(&mut store, "take-u32")?
@@ -565,7 +565,7 @@ fn floats() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let f32_to_u32 = instance.get_typed_func::<(f32,), u32, _>(&mut store, "f32-to-u32")?; let f32_to_u32 = instance.get_typed_func::<(f32,), u32, _>(&mut store, "f32-to-u32")?;
let f64_to_u64 = instance.get_typed_func::<(f64,), u64, _>(&mut store, "f64-to-u64")?; let f64_to_u64 = instance.get_typed_func::<(f64,), u64, _>(&mut store, "f64-to-u64")?;
let u32_to_f32 = instance.get_typed_func::<(u32,), f32, _>(&mut store, "u32-to-f32")?; let u32_to_f32 = instance.get_typed_func::<(u32,), f32, _>(&mut store, "u32-to-f32")?;
@@ -622,7 +622,7 @@ fn bools() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let u32_to_bool = instance.get_typed_func::<(u32,), bool, _>(&mut store, "u32-to-bool")?; let u32_to_bool = instance.get_typed_func::<(u32,), bool, _>(&mut store, "u32-to-bool")?;
let bool_to_u32 = instance.get_typed_func::<(bool,), u32, _>(&mut store, "bool-to-u32")?; let bool_to_u32 = instance.get_typed_func::<(bool,), u32, _>(&mut store, "bool-to-u32")?;
@@ -657,7 +657,7 @@ fn chars() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let u32_to_char = instance.get_typed_func::<(u32,), char, _>(&mut store, "u32-to-char")?; let u32_to_char = instance.get_typed_func::<(u32,), char, _>(&mut store, "u32-to-char")?;
let char_to_u32 = instance.get_typed_func::<(char,), u32, _>(&mut store, "char-to-u32")?; let char_to_u32 = instance.get_typed_func::<(char,), u32, _>(&mut store, "char-to-u32")?;
@@ -729,7 +729,7 @@ fn tuple_result() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let input = (-1, 100, 3.0, 100.0); let input = (-1, 100, 3.0, 100.0);
let output = instance let output = instance
@@ -812,7 +812,7 @@ fn strings() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let list8_to_str = let list8_to_str =
instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?; instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?;
let str_to_list8 = let str_to_list8 =
@@ -936,7 +936,7 @@ fn many_parameters() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_typed_func::<( let func = instance.get_typed_func::<(
i8, i8,
u64, u64,
@@ -1138,7 +1138,7 @@ fn some_traps() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
// This should fail when calling the allocator function for the argument // This should fail when calling the allocator function for the argument
let err = instance let err = instance
@@ -1304,7 +1304,7 @@ fn char_bool_memory() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_typed_func::<(u32, u32), (bool, char), _>(&mut store, "ret-tuple")?; let func = instance.get_typed_func::<(u32, u32), (bool, char), _>(&mut store, "ret-tuple")?;
let ret = func.call(&mut store, (0, 'a' as u32))?; let ret = func.call(&mut store, (0, 'a' as u32))?;
@@ -1362,7 +1362,7 @@ fn string_list_oob() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let ret_list_u8 = instance.get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?; let ret_list_u8 = instance.get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?;
let ret_string = instance.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?; let ret_string = instance.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?;
@@ -1426,7 +1426,7 @@ fn tuples() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let foo = instance.get_typed_func::<((i32, f64), (i8,)), (u16,), _>(&mut store, "foo")?; let foo = instance.get_typed_func::<((i32, f64), (i8,)), (u16,), _>(&mut store, "foo")?;
assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,)); assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,));
@@ -1546,7 +1546,7 @@ fn option() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let option_unit_to_u32 = let option_unit_to_u32 =
instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?; instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?;
assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0); assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0);
@@ -1711,7 +1711,7 @@ fn expected() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let take_expected_unit = let take_expected_unit =
instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?; instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?;
assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0); assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0);
@@ -1813,7 +1813,7 @@ fn fancy_list() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &component)?; let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance let func = instance
.get_typed_func::<(&[(Option<u8>, Result<(), &str>)],), (u32, u32, WasmList<u8>), _>( .get_typed_func::<(&[(Option<u8>, Result<(), &str>)],), (u32, u32, WasmList<u8>), _>(

View File

@@ -0,0 +1,18 @@
(assert_unlinkable
(component
(import "undefined-name" (module))
)
"import `undefined-name` not defined")
(component $i)
(component
(import "i" (instance))
)
(assert_unlinkable
(component (import "i" (module)))
"expected module found instance")
(assert_unlinkable
(component (import "i" (func)))
"expected func found instance")
(assert_unlinkable
(component (import "i" (instance (export "x" (func)))))
"export `x` not defined")

View File

@@ -0,0 +1,477 @@
(component $foo
(module (export "a-module"))
)
;; the above instance can be imported into this component
(component
(import "foo" (instance
(export "a-module" (module))
))
)
;; specifying extra imports is ok
(component
(import "foo" (instance
(export "a-module" (module
(import "foo" "bar" (func))
))
))
)
;; specifying extra exports is not ok
(assert_unlinkable
(component
(import "foo" (instance
(export "a-module" (module
(export "the-export" (func))
))
))
)
"module export `the-export` not defined")
(component $foo
(module (export "a-module")
(import "env" "something" (func))
)
)
;; imports must be specified
(assert_unlinkable
(component
(import "foo" (instance
(export "a-module" (module))
))
)
"module import `env::something` not defined")
(component
(import "foo" (instance
(export "a-module" (module
(import "env" "something" (func))
))
))
)
;; extra imports still ok
(component
(import "foo" (instance
(export "a-module" (module
(import "env" "something" (func))
(import "env" "other" (global i32))
))
))
)
(component $foo
(module (export "a-module")
(func (export "f"))
)
)
;; dropping exports is ok
(component
(import "foo" (instance
(export "a-module" (module))
))
)
(component
(import "foo" (instance
(export "a-module" (module
(export "f" (func))
))
))
)
(assert_unlinkable
(component
(import "foo" (instance
(export "a-module" (module
(export "f" (func (param i32)))
))
))
)
"expected func of type `(i32) -> ()`, found func of type `() -> ()`")
(assert_unlinkable
(component
(import "foo" (instance
(export "a-module" (module
(export "f" (global i32))
))
))
)
"expected global found func")
(component $foo
(module (export "m")
(func (export "f"))
(table (export "t") 1 funcref)
(memory (export "m") 1)
(global (export "g") i32 i32.const 0)
)
)
;; wrong class of item
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "f" (global i32))))
))
)
"expected global found func")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "t" (func))))
))
)
"expected func found table")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "m" (func))))
))
)
"expected func found memory")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "g" (func))))
))
)
"expected func found global")
;; wrong item type
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "f" (func (param i32)))))
))
)
"export `f` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "t" (table 1 externref))))
))
)
"export `t` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "t" (table 2 funcref))))
))
)
"export `t` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "m" (memory 2))))
))
)
"export `m` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "g" (global f32))))
))
)
"export `g` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (export "g" (global (mut i32)))))
))
)
"export `g` has the wrong type")
;; subtyping ok
(component
(import "foo" (instance
(export "m" (module
(export "t" (table 0 funcref))
(export "m" (memory 0))
))
))
)
(component $foo
(module (export "f") (func (import "" "")))
(module (export "t") (table (import "" "") 1 funcref))
(module (export "m") (memory (import "" "") 1))
(module (export "g") (global (import "" "") i32))
)
;; wrong class of item
(assert_unlinkable
(component
(import "foo" (instance
(export "f" (module (import "" "" (global i32))))
))
)
"expected func found global")
(assert_unlinkable
(component
(import "foo" (instance
(export "t" (module (import "" "" (func))))
))
)
"expected table found func")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (import "" "" (func))))
))
)
"expected memory found func")
(assert_unlinkable
(component
(import "foo" (instance
(export "g" (module (import "" "" (func))))
))
)
"expected global found func")
;; wrong item type
(assert_unlinkable
(component
(import "foo" (instance
(export "f" (module (import "" "" (func (param i32)))))
))
)
"module import `::` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "t" (module (import "" "" (table 1 externref))))
))
)
"module import `::` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "t" (module (import "" "" (table 0 funcref))))
))
)
"module import `::` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "m" (module (import "" "" (memory 0))))
))
)
"module import `::` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "g" (module (import "" "" (global f32))))
))
)
"module import `::` has the wrong type")
(assert_unlinkable
(component
(import "foo" (instance
(export "g" (module (import "" "" (global (mut i32)))))
))
)
"module import `::` has the wrong type")
;; subtyping ok, but in the opposite direction of imports
(component
(import "foo" (instance
(export "t" (module (import "" "" (table 2 funcref))))
(export "m" (module (import "" "" (memory 2))))
))
)
;; An instance can reexport a module, define a module, and everything can be
;; used by something else
(component $src
(module (export "m")
(global (export "g") i32 i32.const 2)
)
)
(component $reexport
(module $m1
(global (export "g") i32 i32.const 1)
)
(import "src" (instance $src
(export "m" (module (export "g" (global i32))))
))
(module $m3
(global (export "g") i32 i32.const 3)
)
(export "m1" (module $m1))
(export "m2" (module $src "m"))
(export "m3" (module $m3))
)
(component
(type $modulety (module (export "g" (global i32))))
(import "reexport" (instance $reexport
(export "m1" (module (type $modulety)))
(export "m2" (module (type $modulety)))
(export "m3" (module (type $modulety)))
))
(module $assert_ok
(import "m1" "g" (global $m1 i32))
(import "m2" "g" (global $m2 i32))
(import "m3" "g" (global $m3 i32))
(func $assert_ok
block
global.get $m1
i32.const 1
i32.eq
br_if 0
unreachable
end
block
global.get $m2
i32.const 2
i32.eq
br_if 0
unreachable
end
block
global.get $m3
i32.const 3
i32.eq
br_if 0
unreachable
end
)
(start $assert_ok)
)
(instance $m1 (instantiate (module $reexport "m1")))
(instance $m2 (instantiate (module $reexport "m2")))
(instance $m3 (instantiate (module $reexport "m3")))
(instance (instantiate (module $assert_ok)
(with "m1" (instance $m1))
(with "m2" (instance $m2))
(with "m3" (instance $m3))
))
)
;; order of imports and exports can be shuffled between definition site and
;; use-site
(component $provider
(module (export "m")
(import "" "1" (global $i1 i32))
(import "" "2" (global $i2 i32))
(import "" "3" (global $i3 i32))
(import "" "4" (global $i4 i32))
(global $g1 i32 i32.const 100)
(global $g2 i32 i32.const 101)
(global $g3 i32 i32.const 102)
(global $g4 i32 i32.const 103)
(func $assert_imports
(block
global.get $i1
i32.const 1
i32.eq
br_if 0
unreachable)
(block
global.get $i2
i32.const 2
i32.eq
br_if 0
unreachable)
(block
global.get $i3
i32.const 3
i32.eq
br_if 0
unreachable)
(block
global.get $i4
i32.const 4
i32.eq
br_if 0
unreachable)
)
(start $assert_imports)
(export "g1" (global $g1))
(export "g2" (global $g2))
(export "g3" (global $g3))
(export "g4" (global $g4))
)
)
(component
(import "provider" (instance $provider
(export "m" (module
(import "" "4" (global i32))
(import "" "3" (global i32))
(import "" "2" (global i32))
(import "" "1" (global i32))
(export "g4" (global i32))
(export "g3" (global i32))
(export "g2" (global i32))
(export "g1" (global i32))
))
))
(module $imports
(global (export "1") i32 (i32.const 1))
(global (export "3") i32 (i32.const 3))
(global (export "2") i32 (i32.const 2))
(global (export "4") i32 (i32.const 4))
)
(instance $imports (instantiate (module $imports)))
(instance $m (instantiate (module $provider "m")
(with "" (instance $imports))
))
(module $import_globals
(import "" "g4" (global $g4 i32))
(import "" "g3" (global $g3 i32))
(import "" "g2" (global $g2 i32))
(import "" "g1" (global $g1 i32))
(func $assert_imports
(block
global.get $g1
i32.const 100
i32.eq
br_if 0
unreachable)
(block
global.get $g2
i32.const 101
i32.eq
br_if 0
unreachable)
(block
global.get $g3
i32.const 102
i32.eq
br_if 0
unreachable)
(block
global.get $g4
i32.const 103
i32.eq
br_if 0
unreachable)
)
(start $assert_imports)
)
(instance (instantiate (module $import_globals) (with "" (instance $m))))
)