diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index d75a03857f..b84bd3bebb 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -393,6 +393,9 @@ pub enum Export { /// The module index here indexes a module recorded with /// `GlobalInitializer::SaveModule` above. Module(RuntimeModuleIndex), + /// A nested instance is being exported which has recursively defined + /// `Export` items. + Instance(IndexMap), } /// 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 33ff141ab4..a9b42f0160 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -1,5 +1,7 @@ use crate::component::*; -use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables}; +use crate::{ + EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, Tunables, +}; use anyhow::{bail, Result}; use std::collections::HashMap; use std::mem; @@ -141,6 +143,9 @@ struct Translation<'data> { /// Type information from wasmparser about this component, available after /// the component has been completely translated. types: Option, + + /// The types of all core wasm functions defined within this component. + funcs: PrimaryMap, } #[allow(missing_docs)] @@ -304,7 +309,7 @@ impl<'a, 'data> Translator<'a, 'data> { // Wasmtime to process at runtime as well (e.g. no string lookups as // most everything is done through indices instead). let component = inline::run( - &mut self.types, + &self.types, &self.result, &self.static_modules, &self.static_components, @@ -338,9 +343,34 @@ impl<'a, 'data> Translator<'a, 'data> { } Payload::End(offset) => { + let types = self.validator.end(offset)?; + // Record type information for this component now that we'll // have it from wasmparser. - self.result.types = Some(self.validator.end(offset)?); + // + // Note that this uses type information from `wasmparser` to + // lookup signature of all core wasm functions in this + // component. This avoids us having to reimplement the + // translate-interface-types-to-the-canonical-abi logic. The + // type of the function is then intern'd to get a + // `SignatureIndex` which is later used at runtime for a + // `VMSharedSignatureIndex`. + for init in self.result.initializers.iter() { + match init { + LocalInitializer::Lower(..) | LocalInitializer::AliasExportFunc(..) => {} + _ => continue, + } + let idx = self.result.funcs.next_key(); + let lowered_function_type = types + .function_at(idx.as_u32()) + .expect("should be in-bounds"); + let ty = self + .types + .module_types_builder() + .wasm_func_type(lowered_function_type.clone().try_into()?); + self.result.funcs.push(ty); + } + self.result.types = Some(types); // When leaving a module be sure to pop the types scope to // ensure that when we go back to the previous module outer diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 75f2334bfe..290b22e134 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -50,7 +50,7 @@ use crate::{ModuleTranslation, PrimaryMap, SignatureIndex}; use indexmap::IndexMap; pub(super) fn run( - types: &mut ComponentTypesBuilder, + types: &ComponentTypesBuilder, result: &Translation<'_>, nested_modules: &PrimaryMap>, nested_components: &PrimaryMap>, @@ -82,23 +82,7 @@ pub(super) fn run( }; let index = inliner.result.import_types.push((name.to_string(), ty)); let path = ImportPath::root(index); - args.insert( - name, - match ty { - TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(path, ty)), - TypeDef::ComponentInstance(ty) => { - ComponentItemDef::Instance(ComponentInstanceDef::Import(path, ty)) - } - TypeDef::ComponentFunc(_ty) => { - ComponentItemDef::Func(ComponentFuncDef::Import(path)) - } - // FIXME(#4283) should commit one way or another to how this - // should be treated. - TypeDef::Component(_ty) => bail!("root-level component imports are not supported"), - TypeDef::Interface(_ty) => unimplemented!("import of a type"), - TypeDef::CoreFunc(_ty) => unreachable!(), - }, - ); + args.insert(name, ComponentItemDef::from_import(path, ty)?); } // This will run the inliner to completion after being seeded with the @@ -116,58 +100,18 @@ pub(super) fn run( let exports = inliner.run(&mut frames)?; assert!(frames.is_empty()); + let mut export_map = Default::default(); for (name, def) in exports { - let export = match def { - // Exported modules are currently saved in a `PrimaryMap`, at - // runtime, so an index (`RuntimeModuleIndex`) is assigned here and - // then an initializer is recorded about where the module comes - // from. - ComponentItemDef::Module(module) => { - let index = RuntimeModuleIndex::from_u32(inliner.result.num_runtime_modules); - inliner.result.num_runtime_modules += 1; - let init = match module { - ModuleDef::Static(idx) => GlobalInitializer::SaveStaticModule(idx), - ModuleDef::Import(path, _) => { - GlobalInitializer::SaveModuleImport(inliner.runtime_import(&path)) - } - }; - inliner.result.initializers.push(init); - Export::Module(index) - } - - // Currently only exported functions through liftings are supported - // which simply record the various lifting options here which get - // processed at runtime. - ComponentItemDef::Func(func) => match func { - ComponentFuncDef::Lifted { ty, func, options } => { - Export::LiftedFunction { ty, func, options } - } - ComponentFuncDef::Import(_) => { - bail!("component export `{name}` is a reexport of an imported function which is not implemented") - } - }, - - ComponentItemDef::Instance(_) => unimplemented!("exporting an instance to the host"), - - // FIXME(#4283) should make an official decision on whether this is - // the final treatment of this or not. - ComponentItemDef::Component(_) => { - bail!("exporting a component from the root component is not supported") - } - }; - - inliner.result.exports.insert(name.to_string(), export); + inliner.record_export(name, def, &mut export_map)?; } + inliner.result.exports = export_map; Ok(inliner.result) } struct Inliner<'a> { /// Global type information for the entire component. - /// - /// Note that the mutability is used here to register a `SignatureIndex` for - /// the wasm function signature of lowered imports. - types: &'a mut ComponentTypesBuilder, + types: &'a ComponentTypesBuilder, /// The list of static modules that were found during initial translation of /// the component. @@ -445,24 +389,7 @@ impl<'a> Inliner<'a> { // // NB: at this time only lowered imported functions are supported. Lower(func, options) => { - // Use the type information from `wasmparser` to lookup the core - // wasm function signature of the lowered function. This avoids - // us having to reimplement the - // translate-interface-types-to-the-canonical-abi logic. The - // type of the function is then intern'd to get a - // `SignatureIndex` which is later used at runtime for a - // `VMSharedSignatureIndex`. - let lowered_function_type = frame - .translation - .types - .as_ref() - .unwrap() - .function_at(frame.funcs.next_key().as_u32()) - .expect("should be in-bounds"); - let canonical_abi = self - .types - .module_types_builder() - .wasm_func_type(lowered_function_type.clone().try_into()?); + let canonical_abi = frame.translation.funcs[frame.funcs.next_key()]; let options_lower = self.canonical_options(frame, options); let func = match &frame.component_funcs[*func] { @@ -937,6 +864,89 @@ impl<'a> Inliner<'a> { post_return, } } + + fn record_export( + &mut self, + name: &str, + def: ComponentItemDef<'a>, + map: &mut IndexMap, + ) -> Result<()> { + let export = match def { + // Exported modules are currently saved in a `PrimaryMap`, at + // runtime, so an index (`RuntimeModuleIndex`) is assigned here and + // then an initializer is recorded about where the module comes + // from. + ComponentItemDef::Module(module) => { + let index = RuntimeModuleIndex::from_u32(self.result.num_runtime_modules); + self.result.num_runtime_modules += 1; + let init = match module { + ModuleDef::Static(idx) => GlobalInitializer::SaveStaticModule(idx), + ModuleDef::Import(path, _) => { + GlobalInitializer::SaveModuleImport(self.runtime_import(&path)) + } + }; + self.result.initializers.push(init); + Export::Module(index) + } + + ComponentItemDef::Func(func) => match func { + // If this is a lifted function from something lowered in this + // component then the configured options are plumbed through + // here. + ComponentFuncDef::Lifted { ty, func, options } => { + Export::LiftedFunction { ty, func, options } + } + + // Currently reexported functions from an import are not + // supported. Being able to actually call these functions is + // somewhat tricky and needs something like temporary scratch + // space that isn't implemented. + ComponentFuncDef::Import(_) => { + bail!("component export `{name}` is a reexport of an imported function which is not implemented") + } + }, + + ComponentItemDef::Instance(instance) => { + let mut result = IndexMap::new(); + match instance { + // If this instance is one that was originally imported by + // the component itself then the imports are translated here + // by converting to a `ComponentItemDef` and then + // recursively recording the export as a reexport. + // + // Note that for now this would only work with + // module-exporting instances. + ComponentInstanceDef::Import(path, ty) => { + for (name, ty) in self.types[ty].exports.iter() { + let mut path = path.clone(); + path.path.push(name); + let def = ComponentItemDef::from_import(path, *ty)?; + self.record_export(name, def, &mut result)?; + } + } + + // An exported instance which is itself a bag of items is + // translated recursively here to our `result` map which is + // the bag of items we're exporting. + ComponentInstanceDef::Items(map) => { + for (name, def) in map { + self.record_export(name, def, &mut result)?; + } + } + } + Export::Instance(result) + } + + // FIXME(#4283) should make an official decision on whether this is + // the final treatment of this or not. + ComponentItemDef::Component(_) => { + bail!("exporting a component from the root component is not supported") + } + }; + + map.insert(name.to_string(), export); + Ok(()) + } } impl<'a> InlinerFrame<'a> { @@ -1004,3 +1014,21 @@ impl<'a> ImportPath<'a> { } } } + +impl<'a> ComponentItemDef<'a> { + fn from_import(path: ImportPath<'a>, ty: TypeDef) -> Result> { + let item = match ty { + TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(path, ty)), + TypeDef::ComponentInstance(ty) => { + ComponentItemDef::Instance(ComponentInstanceDef::Import(path, ty)) + } + TypeDef::ComponentFunc(_ty) => ComponentItemDef::Func(ComponentFuncDef::Import(path)), + // FIXME(#4283) should commit one way or another to how this + // should be treated. + TypeDef::Component(_ty) => bail!("root-level component imports are not supported"), + TypeDef::Interface(_ty) => unimplemented!("import of a type"), + TypeDef::CoreFunc(_ty) => unreachable!(), + }; + Ok(item) + } +} diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs index cfcfd6890c..a5fe8de4b0 100644 --- a/crates/wasmtime/src/component/func.rs +++ b/crates/wasmtime/src/component/func.rs @@ -212,7 +212,18 @@ impl Func { Return: Lift, S: AsContext, { - self.typecheck::(store.as_context().0)?; + self._typed(store.as_context().0) + } + + pub(crate) fn _typed( + &self, + store: &StoreOpaque, + ) -> Result> + where + Params: ComponentParams + Lower, + Return: Lift, + { + self.typecheck::(store)?; unsafe { Ok(TypedFunc::new_unchecked(*self)) } } diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 6ceb04bc4c..f0711f75a6 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -2,8 +2,9 @@ use crate::component::func::HostFunc; use crate::component::{Component, ComponentParams, Func, Lift, Lower, TypedFunc}; use crate::instance::OwnedImports; use crate::store::{StoreOpaque, Stored}; -use crate::{AsContextMut, Module, StoreContext, StoreContextMut}; +use crate::{AsContextMut, Module, StoreContextMut}; use anyhow::{anyhow, Context, Result}; +use indexmap::IndexMap; use std::marker; use std::sync::Arc; use wasmtime_environ::component::{ @@ -43,30 +44,33 @@ pub(crate) struct InstanceData { } impl Instance { + /// Returns information about the exports of this instance. + /// + /// This method can be used to extract exported values from this component + /// instance. The argument to this method be a handle to the store that + /// this instance was instantiated into. + /// + /// The returned [`Exports`] value can be used to lookup exported items by + /// name. + /// + /// # Panics + /// + /// Panics if `store` does not own this instance. + pub fn exports<'a, T: 'a>(&self, store: impl Into>) -> Exports<'a> { + let store = store.into(); + Exports::new(store.0, self) + } + /// Looks up a function by name within this [`Instance`]. /// - /// The `store` specified must be the store that this instance lives within - /// and `name` is the name of the function to lookup. If the function is - /// found `Some` is returned otherwise `None` is returned. + /// This is a convenience method for calling [`Instance::exports`] followed + /// by [`ExportInstance::get_func`]. /// /// # Panics /// /// Panics if `store` does not own this instance. pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option { - self._get_func(store.as_context_mut().0, name) - } - - fn _get_func(&self, store: &mut StoreOpaque, name: &str) -> Option { - // FIXME: this movement in ownership is unfortunate and feels like there - // should be a better solution. The reason for this is that we need to - // simultaneously look at lots of pieces of `InstanceData` while also - // inserting into `store`, but `InstanceData` is stored within `store`. - // By moving it out we appease the borrow-checker but take a runtime - // hit. - let data = store[self.0].take().unwrap(); - let result = data.get_func(store, self, name); - store[self.0] = Some(data); - return result; + self.exports(store.as_context_mut()).root().func(name) } /// Looks up an exported [`Func`] value by name and with its type. @@ -97,52 +101,24 @@ impl Instance { .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, - }) + /// Looks up a module by name within this [`Instance`]. + /// + /// The `store` specified must be the store that this instance lives within + /// and `name` is the name of the function to lookup. If the function is + /// found `Some` is returned otherwise `None` is returned. + /// + /// # Panics + /// + /// Panics if `store` does not own this instance. + pub fn get_module(&self, mut store: impl AsContextMut, name: &str) -> Option { + self.exports(store.as_context_mut()) + .root() + .module(name) + .cloned() } } impl InstanceData { - fn get_func(&self, store: &mut StoreOpaque, instance: &Instance, name: &str) -> Option { - match self.component.env_component().exports.get(name)? { - Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func( - store, instance, self, *ty, func, options, - )), - Export::Module(_) => None, - } - } - pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export { match def { CoreDef::Export(e) => self.lookup_export(store, e), @@ -442,3 +418,165 @@ impl InstancePre { Ok(Instance(store.0.store_data_mut().insert(Some(data)))) } } + +/// Description of the exports of an [`Instance`]. +/// +/// This structure is created through the [`Instance::exports`] method and is +/// used lookup exports by name from within an instance. +pub struct Exports<'store> { + store: &'store mut StoreOpaque, + data: Option>, + instance: Instance, +} + +impl<'store> Exports<'store> { + fn new(store: &'store mut StoreOpaque, instance: &Instance) -> Exports<'store> { + // Note that the `InstanceData` is `take`n from the store here. That's + // to ease with the various liftimes in play here where we often need + // simultaneous borrows into the `store` and the `data`. + // + // To put the data back into the store the `Drop for Exports<'_>` will + // restore the state of the world. + Exports { + data: store[instance.0].take(), + store, + instance: *instance, + } + } + + /// Returns the "root" instance of this set of exports, or the items that + /// are directly exported from the instance that this was created from. + pub fn root(&mut self) -> ExportInstance<'_, '_> { + let data = self.data.as_ref().unwrap(); + ExportInstance { + exports: &data.component.env_component().exports, + instance: &self.instance, + data, + store: self.store, + } + } + + /// Returns the items that the named instance exports. + /// + /// This method will lookup the exported instance with the name `name` from + /// this list of exports and return a descriptin of that instance's + /// exports. + pub fn instance(&mut self, name: &str) -> Option> { + self.root().into_instance(name) + } + + // FIXME: should all the func/module/typed_func methods below be mirrored + // here as well? They're already mirrored on `Instance` and otherwise + // this is attempting to look like the `Linker` API "but in reverse" + // somewhat. +} + +impl Drop for Exports<'_> { + fn drop(&mut self) { + // See `Exports::new` for where this data was originally extracted, and + // this is just restoring the state of the world. + self.store[self.instance.0] = self.data.take(); + } +} + +/// Description of the exports of a single instance. +/// +/// This structure is created from [`Exports`] via the [`Exports::root`] or +/// [`Exports::instance`] methods. This type provides access to the first layer +/// of exports within an instance. The [`ExportInstance::instance`] method +/// can be used to provide nested access to sub-instances. +pub struct ExportInstance<'a, 'store> { + exports: &'a IndexMap, + instance: &'a Instance, + data: &'a InstanceData, + store: &'store mut StoreOpaque, +} + +impl<'a, 'store> ExportInstance<'a, 'store> { + /// Same as [`Instance::get_func`] + pub fn func(&mut self, name: &str) -> Option { + match self.exports.get(name)? { + Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func( + self.store, + self.instance, + self.data, + *ty, + func, + options, + )), + Export::Module(_) | Export::Instance(_) => None, + } + } + + /// Same as [`Instance::get_typed_func`] + pub fn typed_func(&mut self, name: &str) -> Result> + where + Params: ComponentParams + Lower, + Results: Lift, + { + let func = self + .func(name) + .ok_or_else(|| anyhow!("failed to find function export `{}`", name))?; + Ok(func + ._typed::(self.store) + .with_context(|| format!("failed to convert function `{}` to given type", name))?) + } + + /// Same as [`Instance::get_module`] + pub fn module(&mut self, name: &str) -> Option<&'a Module> { + match self.exports.get(name)? { + Export::Module(idx) => Some(&self.data.exported_modules[*idx]), + _ => None, + } + } + + /// 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(&self) -> impl Iterator + '_ { + self.exports + .iter() + .filter_map(|(name, export)| match *export { + Export::Module(idx) => Some((name.as_str(), &self.data.exported_modules[idx])), + _ => None, + }) + } + + fn as_mut(&mut self) -> ExportInstance<'a, '_> { + ExportInstance { + exports: self.exports, + instance: self.instance, + data: self.data, + store: self.store, + } + } + + /// Looks up the exported instance with the `name` specified and returns + /// a description of its exports. + pub fn instance(&mut self, name: &str) -> Option> { + self.as_mut().into_instance(name) + } + + /// Same as [`ExportInstance::instance`] but consumes self to yield a + /// return value with the same lifetimes. + pub fn into_instance(self, name: &str) -> Option> { + match self.exports.get(name)? { + Export::Instance(exports) => Some(ExportInstance { + exports, + instance: self.instance, + data: self.data, + store: self.store, + }), + _ => None, + } + } +} diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs index f2193afe8c..a96d3f1d90 100644 --- a/crates/wasmtime/src/component/mod.rs +++ b/crates/wasmtime/src/component/mod.rs @@ -14,7 +14,7 @@ pub use self::func::{ ComponentParams, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList, WasmStr, }; -pub use self::instance::{Instance, InstancePre}; +pub use self::instance::{ExportInstance, Exports, Instance, InstancePre}; pub use self::linker::{Linker, LinkerInstance}; pub use wasmtime_component_macro::{ComponentType, Lift, Lower}; diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index e318f87bbb..a88583c892 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -193,7 +193,7 @@ impl WastContext { // 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) { + for (name, module) in instance.exports(&mut self.store).root().modules() { linker.module(name, module)?; } } diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs index 9cb50554e9..4fee2a480f 100644 --- a/tests/all/component_model.rs +++ b/tests/all/component_model.rs @@ -4,6 +4,7 @@ use wasmtime::{AsContextMut, Config, Engine}; mod func; mod import; +mod instance; mod macros; mod nested; mod post_return; diff --git a/tests/all/component_model/instance.rs b/tests/all/component_model/instance.rs new file mode 100644 index 0000000000..8d57886a25 --- /dev/null +++ b/tests/all/component_model/instance.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use wasmtime::component::*; +use wasmtime::{Module, Store}; + +#[test] +fn instance_exports() -> Result<()> { + let engine = super::engine(); + let component = r#" + (component + (import "a" (instance $i)) + (import "b" (instance $i2 (export "m" (core module)))) + + (alias export $i2 "m" (core module $m)) + + (component $c + (component $c + (export "m" (core module $m)) + ) + (instance $c (instantiate $c)) + (export "i" (instance $c)) + ) + (instance $c (instantiate $c)) + (export "i" (instance $c)) + (export "r" (instance $i)) + (export "r2" (instance $i2)) + ) + "#; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let mut linker = Linker::new(&engine); + linker.instance("a")?; + linker + .instance("b")? + .module("m", &Module::new(&engine, "(module)")?)?; + let instance = linker.instantiate(&mut store, &component)?; + + let mut exports = instance.exports(&mut store); + assert!(exports.instance("not an instance").is_none()); + let mut i = exports.instance("r").unwrap(); + assert!(i.func("x").is_none()); + drop(i); + exports.root().instance("i").unwrap(); + let mut i2 = exports.instance("r2").unwrap(); + assert!(i2.func("m").is_none()); + assert!(i2.module("m").is_some()); + drop(i2); + + exports + .instance("i") + .unwrap() + .instance("i") + .unwrap() + .module("m") + .unwrap(); + + Ok(()) +} diff --git a/tests/misc_testsuite/component-model/instance.wast b/tests/misc_testsuite/component-model/instance.wast index 071523f0ac..3d68d272c7 100644 --- a/tests/misc_testsuite/component-model/instance.wast +++ b/tests/misc_testsuite/component-model/instance.wast @@ -235,3 +235,20 @@ (core instance (instantiate $verify (with "host" (instance $i)))) ) + +;; export an instance +(component + (core module $m) + (instance $i (export "m" (core module $m))) + (export "i" (instance $i)) +) +(component + (component $c) + (instance $i (instantiate $c)) + (export "i" (instance $i)) +) +(component + (import "host" (instance $i)) + (export "i" (instance $i)) +) +