diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index b5205215cd..c3e3a6ef1f 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -108,6 +108,10 @@ pub struct Component { /// The number of lowered host functions (maximum `LoweredIndex`) needed to /// instantiate this component. 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 @@ -126,36 +130,13 @@ pub struct Component { // all of these instructions. #[derive(Debug, Serialize, Deserialize)] 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 /// involve running the `start` function of the instance as well if it's /// specified. This largely delegates to the same standard instantiation /// process as the rest of the core wasm machinery already uses. - 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]>, - }, + InstantiateModule(InstantiateModule), /// 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 /// `VMComponentContext` at the `index` specified. This lowering is then /// used in the future by pointers from `CanonicalOptions`. - ExtractMemory { - /// 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, - }, + ExtractMemory(CoreExport), /// Same as `ExtractMemory`, except it's extracting a function pointer to be /// used as a `realloc` function. - ExtractRealloc { - /// The index of the realloc function we're storing. - /// - /// This is guaranteed to be the `n`th `ExtractRealloc` instruction - /// if the index is `n`. - index: RuntimeReallocIndex, - /// The source of the function pointer that is stored. - def: CoreDef, - }, + ExtractRealloc(CoreDef), + + /// The `module` specified is saved into the runtime state at the next + /// `RuntimeModuleIndex`, referred to later by `Export` definitions. + SaveModuleUpvar(ModuleUpvarIndex), + + /// Same as `SaveModuleUpvar`, but for imports. + SaveModuleImport(RuntimeImportIndex), } -/// Indicator used to refer to what module is being instantiated when -/// `Initializer::InstantiateModule` is used. +/// Different methods of instantiating a core wasm module. #[derive(Debug, Serialize, Deserialize)] -pub enum ModuleToInstantiate { - /// An "upvar", or a module defined within a component, is being used. +pub enum InstantiateModule { + /// A module defined within this component is being instantiated. /// - /// The index here is correlated with the `Translation::upvars` map that's - /// created during translation of a component. - Upvar(ModuleUpvarIndex), + /// Note that this is distinct from the case of imported modules because the + /// order of imports required is statically known and can be pre-calculated + /// 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. - Import(RuntimeImportIndex), + /// This is similar to `Upvar` but notably the imports are provided as a + /// two-level named map since import resolution order needs to happen at + /// runtime. + Import( + RuntimeImportIndex, + IndexMap>, + ), } /// Description of a lowered import used in conjunction with @@ -309,6 +287,11 @@ pub enum Export { /// Any options, if present, associated with this lifting. 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. diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index bf65b5067b..e2a56c74c8 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -4,6 +4,7 @@ use crate::{ Tunables, }; use anyhow::{bail, Result}; +use indexmap::IndexMap; use std::collections::HashMap; use std::mem; use wasmparser::{Chunk, Encoding, Parser, Payload, Validator}; @@ -483,7 +484,7 @@ impl<'a, 'data> Translator<'a, 'data> { fn module_instance( &mut self, - module_idx: ModuleIndex, + module: ModuleIndex, args: &[wasmparser::ModuleArg<'data>], ) -> InstanceDef<'data> { // 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 // which means we statically know the structure of the module. The // list of imports required is ordered by the actual list of imports @@ -507,8 +508,13 @@ impl<'a, 'data> Translator<'a, 'data> { .module .imports() .map(|(m, n, _)| (m.to_string(), n.to_string())) - .collect::>(); - (args, ModuleToInstantiate::Upvar(upvar_idx)) + .collect::>() + .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 @@ -518,36 +524,27 @@ impl<'a, 'data> Translator<'a, 'data> { // imports in a different order. ModuleDef::Import { ty, import } => { let import = self.runtime_import_index(import); - let args = self.types[ty].imports.keys().cloned().collect(); - (args, ModuleToInstantiate::Import(import)) + let mut args = IndexMap::new(); + let imports = self.types[ty].imports.keys().cloned().collect::>(); + 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 .component .initializers - .push(Initializer::InstantiateModule { - instance, - module, - args, - }); - InstanceDef::Module { - instance, - module: module_idx, - } + .push(Initializer::InstantiateModule(instantiate)); + + let instance = RuntimeInstanceIndex::from_u32(self.result.component.num_runtime_instances); + self.result.component.num_runtime_instances += 1; + InstanceDef::Module { instance, module } } /// 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 { wasmparser::ComponentExportKind::Module(i) => { let idx = ModuleIndex::from_u32(i); - drop(idx); - unimplemented!("exporting a module"); + let init = match self.result.modules[idx].clone() { + 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) => { let idx = ComponentIndex::from_u32(i); @@ -1021,7 +1027,7 @@ impl<'a, 'data> Translator<'a, 'data> { self.result .component .initializers - .push(Initializer::ExtractMemory { index, export }); + .push(Initializer::ExtractMemory(export)); index } @@ -1035,7 +1041,7 @@ impl<'a, 'data> Translator<'a, 'data> { self.result .component .initializers - .push(Initializer::ExtractRealloc { index, def }); + .push(Initializer::ExtractRealloc(def)); index } } diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 6f9f7d3776..f2fe67117d 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -130,6 +130,10 @@ indices! { /// Same as `RuntimeMemoryIndex` except for the `realloc` function. 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 diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 2ae0890d45..919e229adb 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -1,12 +1,14 @@ use crate::component::{Component, ComponentParams, ComponentValue, Func, TypedFunc}; use crate::instance::OwnedImports; use crate::store::{StoreOpaque, Stored}; -use crate::{AsContextMut, Module, StoreContextMut}; +use crate::{AsContextMut, Module, StoreContext, StoreContextMut}; use anyhow::{anyhow, Context, Result}; +use std::marker; use std::sync::Arc; use wasmtime_environ::component::{ - ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, ModuleToInstantiate, - RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeReallocIndex, + ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, InstantiateModule, + RuntimeImportIndex, RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeModuleIndex, + RuntimeReallocIndex, }; use wasmtime_environ::{EntityIndex, PrimaryMap}; @@ -26,6 +28,7 @@ pub(crate) struct InstanceData { // alive and things like that, instead only the bare minimum necessary // should be kept alive here (mostly just `wasmtime_environ::Component`. component: Component, + exported_modules: PrimaryMap, // TODO: move these to `VMComponentContext` memories: PrimaryMap, @@ -33,21 +36,6 @@ pub(crate) struct InstanceData { } 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 { - 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`]. /// /// The `store` specified must be the store that this instance lives within @@ -101,6 +89,41 @@ impl Instance { Ok(f.typed::(store) .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>, + ) -> impl Iterator + 'a { + let store = store.into(); + self._modules(store.0) + } + + fn _modules<'a>( + &'a self, + store: &'a StoreOpaque, + ) -> impl Iterator + '_ { + 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 { @@ -109,6 +132,7 @@ impl InstanceData { Export::LiftedFunction { 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> { component: &'a Component, data: InstanceData, - imports: OwnedImports, + core_imports: OwnedImports, + imports: &'a PrimaryMap, +} + +pub enum RuntimeImport { + Module(Module), } impl<'a> Instantiator<'a> { - fn new(component: &'a Component) -> Instantiator<'a> { + fn new( + component: &'a Component, + imports: &'a PrimaryMap, + ) -> Instantiator<'a> { let env_component = component.env_component(); - if env_component.imports.len() > 0 { - unimplemented!("component imports"); - } Instantiator { component, - imports: OwnedImports::empty(), + imports, + core_imports: OwnedImports::empty(), data: InstanceData { instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize), component: component.clone(), + exported_modules: PrimaryMap::with_capacity( + env_component.num_runtime_modules as usize, + ), memories: Default::default(), reallocs: Default::default(), }, @@ -181,16 +214,27 @@ impl<'a> Instantiator<'a> { let env_component = self.component.env_component(); for initializer in env_component.initializers.iter() { match initializer { - Initializer::InstantiateModule { - instance, - module, - args, - } => { - let module = match module { - ModuleToInstantiate::Upvar(module) => self.component.upvar(*module), - ModuleToInstantiate::Import(idx) => { - drop(idx); - unimplemented!("component module imports"); + Initializer::InstantiateModule(m) => { + let module; + let imports = match m { + // Since upvars are statically know we know that the + // `args` list is already in the right order. + InstantiateModule::Upvar(idx, args) => { + module = self.component.upvar(*idx); + self.build_imports(store.0, module, args.iter()) + } + // 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 // already been performed. This maens that the unsafety due // to imports having the wrong type should not happen here. - let imports = self.build_imports(store.0, module, args); let i = unsafe { crate::Instance::new_started(store, module, imports.as_ref())? }; - let idx = self.data.instances.push(i); - assert_eq!(idx, *instance); + self.data.instances.push(i); } Initializer::LowerImport(_) => unimplemented!(), - Initializer::ExtractMemory { index, export } => { + Initializer::ExtractMemory(export) => { let memory = match self.data.lookup_export(store.0, export) { wasmtime_runtime::Export::Memory(m) => m, _ => 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) { wasmtime_runtime::Export::Function(f) => f, _ => 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(()) } - fn build_imports( + fn build_imports<'b>( &mut self, store: &mut StoreOpaque, module: &Module, - args: &[CoreDef], + args: impl Iterator, ) -> &OwnedImports { - self.imports.clear(); - self.imports.reserve(module); + self.core_imports.clear(); + self.core_imports.reserve(module); for arg in args { 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 // items. 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 { + component: Component, + imports: PrimaryMap, + _marker: marker::PhantomData T>, +} + +impl InstancePre { + /// 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, + ) -> InstancePre { + 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) -> Result { + 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)))) } } diff --git a/crates/wasmtime/src/component/linker.rs b/crates/wasmtime/src/component/linker.rs new file mode 100644 index 0000000000..88e3c408cf --- /dev/null +++ b/crates/wasmtime/src/component/linker.rs @@ -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 { + engine: Engine, + strings: Strings, + map: NameMap, + allow_shadowing: bool, + _marker: marker::PhantomData T>, +} + +#[derive(Default)] +pub struct Strings { + string2idx: HashMap, usize>, + strings: Vec>, +} + +/// 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 T>, +} + +pub type NameMap = HashMap; + +#[derive(Clone)] +pub enum Definition { + Instance(NameMap), + Module(Module), +} + +impl Linker { + /// Creates a new linker for the [`Engine`] specified with no items defined + /// within it. + pub fn new(engine: &Engine) -> Linker { + 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> { + 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> { + 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, + component: &Component, + ) -> Result { + self.instantiate_pre(component)?.instantiate(store) + } +} + +impl 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> { + self.as_mut().into_instance(name) + } + + /// Same as [`LinkerInstance::instance`] except with different liftime + /// parameters. + pub fn into_instance(mut self, name: &str) -> Result { + 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 = 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 { + self.string2idx.get(string).cloned() + } +} diff --git a/crates/wasmtime/src/component/matching.rs b/crates/wasmtime/src/component/matching.rs new file mode 100644 index 0000000000..1dca9a3c0a --- /dev/null +++ b/crates/wasmtime/src/component/matching.rs @@ -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", + } + } +} diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index 0a9014f656..7467e3a9c4 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -6,10 +6,13 @@ mod component; mod func; mod instance; +mod linker; +mod matching; mod store; pub use self::component::Component; 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 // `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs index 076c6d9eeb..7261a420ad 100644 --- a/crates/wasmtime/src/types/matching.rs +++ b/crates/wasmtime/src/types/matching.rs @@ -1,7 +1,7 @@ use crate::linker::Definition; use crate::store::StoreOpaque; use crate::{signatures::SignatureCollection, Engine, Extern}; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use wasmtime_environ::{ EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType, }; @@ -16,84 +16,25 @@ pub struct MatchCx<'a> { impl MatchCx<'_> { pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> { - self.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(()) + global_ty(expected, actual.wasmtime_ty(self.store.store_data())) } pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> { - self.table_ty( + table_ty( expected, actual.wasmtime_ty(self.store.store_data()), Some(actual.internal_size(self.store)), ) } - fn table_ty( - &self, - expected: &Table, - actual: &Table, - actual_runtime_size: Option, - ) -> 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<()> { - self.memory_ty( + memory_ty( expected, actual.wasmtime_ty(self.store.store_data()), Some(actual.internal_size(self.store)), ) } - fn memory_ty( - &self, - expected: &Memory, - actual: &Memory, - actual_runtime_size: Option, - ) -> 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<()> { self.vmshared_signature_index(expected, actual.sig_index(self.store.store_data())) } @@ -130,27 +71,7 @@ impl MatchCx<'_> { } }; - let render = |ty: &WasmFuncType| { - let params = ty - .params() - .iter() - .map(|s| s.to_string()) - .collect::>() - .join(", "); - let returns = ty - .returns() - .iter() - .map(|s| s.to_string()) - .collect::>() - .join(", "); - format!("`({}) -> ({})`", params, returns) - }; - bail!( - "{}: expected func of type {}, found func of type {}", - msg, - render(expected), - render(&actual) - ) + Err(func_ty_mismatch(msg, expected, &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::>() + .join(", "); + let returns = ty + .returns() + .iter() + .map(|s| s.to_string()) + .collect::>() + .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) -> 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) -> 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<()> { if expected == actual { return Ok(()); diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 1c8f79b909..0f1203d37d 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -39,7 +39,9 @@ pub struct WastContext { /// Wast files have a concept of a "current" module, which is the most /// recently defined. current: Option, - linker: Linker, + core_linker: Linker, + #[cfg(feature = "component-model")] + component_linker: component::Linker, store: Store, } @@ -70,11 +72,17 @@ impl WastContext { // Spec tests will redefine the same module/name sometimes, so we need // to allow shadowing in the linker which picks the most recent // definition as what to link when linking. - let mut linker = Linker::new(store.engine()); - linker.allow_shadowing(true); + let mut core_linker = Linker::new(store.engine()); + core_linker.allow_shadowing(true); Self { 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, } } @@ -82,7 +90,7 @@ impl WastContext { fn get_export(&mut self, module: Option<&str>, name: &str) -> Result { match module { Some(module) => self - .linker + .core_linker .get(&mut self.store, module, name) .ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name)), None => self @@ -96,7 +104,7 @@ impl WastContext { fn instantiate_module(&mut self, module: &[u8]) -> Result> { 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, Err(e) => return e.downcast::().map(Outcome::Trap), }; @@ -105,8 +113,9 @@ impl WastContext { #[cfg(feature = "component-model")] fn instantiate_component(&mut self, module: &[u8]) -> Result> { - let module = component::Component::new(self.store.engine(), module)?; - let instance = match component::Instance::new(&mut self.store, &module) { + let engine = self.store.engine(); + let module = component::Component::new(engine, module)?; + let instance = match self.component_linker.instantiate(&mut self.store, &module) { Ok(i) => i, Err(e) => return e.downcast::().map(Outcome::Trap), }; @@ -115,7 +124,7 @@ impl WastContext { /// Register "spectest" which is used by the spec testsuite. 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(()) } @@ -164,15 +173,28 @@ impl WastContext { Outcome::Trap(e) => return Err(e).context("instantiation failed"), }; if let Some(name) = name { - self.linker + self.core_linker .instance(&mut self.store, name.name(), instance)?; } self.current = Some(instance); } else { #[cfg(feature = "component-model")] - match self.instantiate_component(&bytes)? { - Outcome::Ok(_) => {} - Outcome::Trap(e) => return Err(e).context("instantiation failed"), + { + let instance = match self.instantiate_component(&bytes)? { + 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"))] bail!("component-model support not enabled"); @@ -183,13 +205,14 @@ impl WastContext { /// Register an instance to make it available for performing actions. fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> { match name { - Some(name) => self.linker.alias_module(name, as_name), + Some(name) => self.core_linker.alias_module(name, as_name), None => { let current = *self .current .as_ref() .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(()) } } diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 40d6469cd3..e6d968d6b3 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -89,7 +89,7 @@ fn thunks() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; let mut store = Store::new(&engine, ()); - let instance = Instance::new(&mut store, &component)?; + let instance = Linker::new(&engine).instantiate(&mut store, &component)?; instance .get_typed_func::<(), (), _>(&mut store, "thunk")? .call(&mut store, ())?; @@ -146,7 +146,7 @@ fn typecheck() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 take_string = instance.get_func(&mut store, "take-string").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 component = Component::new(&engine, component)?; 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 instance @@ -502,7 +502,7 @@ fn type_layers() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; let mut store = Store::new(&engine, ()); - let instance = Instance::new(&mut store, &component)?; + let instance = Linker::new(&engine).instantiate(&mut store, &component)?; instance .get_typed_func::<(Box,), (), _>(&mut store, "take-u32")? @@ -565,7 +565,7 @@ fn floats() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 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")?; @@ -622,7 +622,7 @@ fn bools() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 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 component = Component::new(&engine, component)?; 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 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 component = Component::new(&engine, component)?; 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 output = instance @@ -812,7 +812,7 @@ fn strings() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 = instance.get_typed_func::<(&[u8],), WasmStr, _>(&mut store, "list8-to-str")?; let str_to_list8 = @@ -936,7 +936,7 @@ fn many_parameters() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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::<( i8, u64, @@ -1138,7 +1138,7 @@ fn some_traps() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 let err = instance @@ -1304,7 +1304,7 @@ fn char_bool_memory() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 ret = func.call(&mut store, (0, 'a' as u32))?; @@ -1362,7 +1362,7 @@ fn string_list_oob() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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, _>(&mut store, "ret-list-u8")?; let ret_string = instance.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?; @@ -1426,7 +1426,7 @@ fn tuples() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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")?; assert_eq!(foo.call(&mut store, ((0, 1.0), (2,)))?, (3,)); @@ -1546,7 +1546,7 @@ fn option() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 = instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?; assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0); @@ -1711,7 +1711,7 @@ fn expected() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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 = instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?; assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0); @@ -1813,7 +1813,7 @@ fn fancy_list() -> Result<()> { let engine = super::engine(); let component = Component::new(&engine, component)?; 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::<(&[(Option, Result<(), &str>)],), (u32, u32, WasmList), _>( diff --git a/tests/misc_testsuite/component-model/linking.wast b/tests/misc_testsuite/component-model/linking.wast new file mode 100644 index 0000000000..651961b5f7 --- /dev/null +++ b/tests/misc_testsuite/component-model/linking.wast @@ -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") diff --git a/tests/misc_testsuite/component-model/modules.wast b/tests/misc_testsuite/component-model/modules.wast new file mode 100644 index 0000000000..0485c75c8c --- /dev/null +++ b/tests/misc_testsuite/component-model/modules.wast @@ -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)))) +)