Implement nested instance exports for components (#4364)
This commit adds support to Wasmtime for components which themselves export instances. The support here adds new APIs for how instance exports are accessed in the embedding API. For now this is mostly just a first-pass where the API is somewhat confusing and has a lot of lifetimes. I'm hoping that over time we can figure out how to simplify this but for now it should at least be expressive enough for exploring the exports of an instance.
This commit is contained in:
@@ -393,6 +393,9 @@ pub enum Export {
|
|||||||
/// The module index here indexes a module recorded with
|
/// The module index here indexes a module recorded with
|
||||||
/// `GlobalInitializer::SaveModule` above.
|
/// `GlobalInitializer::SaveModule` above.
|
||||||
Module(RuntimeModuleIndex),
|
Module(RuntimeModuleIndex),
|
||||||
|
/// A nested instance is being exported which has recursively defined
|
||||||
|
/// `Export` items.
|
||||||
|
Instance(IndexMap<String, Export>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Canonical ABI options associated with a lifted or lowered function.
|
/// Canonical ABI options associated with a lifted or lowered function.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::component::*;
|
use crate::component::*;
|
||||||
use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables};
|
use crate::{
|
||||||
|
EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, Tunables,
|
||||||
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@@ -141,6 +143,9 @@ struct Translation<'data> {
|
|||||||
/// Type information from wasmparser about this component, available after
|
/// Type information from wasmparser about this component, available after
|
||||||
/// the component has been completely translated.
|
/// the component has been completely translated.
|
||||||
types: Option<wasmparser::types::Types>,
|
types: Option<wasmparser::types::Types>,
|
||||||
|
|
||||||
|
/// The types of all core wasm functions defined within this component.
|
||||||
|
funcs: PrimaryMap<FuncIndex, SignatureIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[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
|
// Wasmtime to process at runtime as well (e.g. no string lookups as
|
||||||
// most everything is done through indices instead).
|
// most everything is done through indices instead).
|
||||||
let component = inline::run(
|
let component = inline::run(
|
||||||
&mut self.types,
|
&self.types,
|
||||||
&self.result,
|
&self.result,
|
||||||
&self.static_modules,
|
&self.static_modules,
|
||||||
&self.static_components,
|
&self.static_components,
|
||||||
@@ -338,9 +343,34 @@ impl<'a, 'data> Translator<'a, 'data> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Payload::End(offset) => {
|
Payload::End(offset) => {
|
||||||
|
let types = self.validator.end(offset)?;
|
||||||
|
|
||||||
// Record type information for this component now that we'll
|
// Record type information for this component now that we'll
|
||||||
// have it from wasmparser.
|
// 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
|
// When leaving a module be sure to pop the types scope to
|
||||||
// ensure that when we go back to the previous module outer
|
// ensure that when we go back to the previous module outer
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ use crate::{ModuleTranslation, PrimaryMap, SignatureIndex};
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
pub(super) fn run(
|
pub(super) fn run(
|
||||||
types: &mut ComponentTypesBuilder,
|
types: &ComponentTypesBuilder,
|
||||||
result: &Translation<'_>,
|
result: &Translation<'_>,
|
||||||
nested_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
|
nested_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
|
||||||
nested_components: &PrimaryMap<StaticComponentIndex, Translation<'_>>,
|
nested_components: &PrimaryMap<StaticComponentIndex, Translation<'_>>,
|
||||||
@@ -82,23 +82,7 @@ pub(super) fn run(
|
|||||||
};
|
};
|
||||||
let index = inliner.result.import_types.push((name.to_string(), ty));
|
let index = inliner.result.import_types.push((name.to_string(), ty));
|
||||||
let path = ImportPath::root(index);
|
let path = ImportPath::root(index);
|
||||||
args.insert(
|
args.insert(name, ComponentItemDef::from_import(path, ty)?);
|
||||||
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!(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will run the inliner to completion after being seeded with the
|
// 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)?;
|
let exports = inliner.run(&mut frames)?;
|
||||||
assert!(frames.is_empty());
|
assert!(frames.is_empty());
|
||||||
|
|
||||||
|
let mut export_map = Default::default();
|
||||||
for (name, def) in exports {
|
for (name, def) in exports {
|
||||||
let export = match def {
|
inliner.record_export(name, def, &mut export_map)?;
|
||||||
// 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.result.exports = export_map;
|
||||||
|
|
||||||
Ok(inliner.result)
|
Ok(inliner.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Inliner<'a> {
|
struct Inliner<'a> {
|
||||||
/// Global type information for the entire component.
|
/// Global type information for the entire component.
|
||||||
///
|
types: &'a ComponentTypesBuilder,
|
||||||
/// Note that the mutability is used here to register a `SignatureIndex` for
|
|
||||||
/// the wasm function signature of lowered imports.
|
|
||||||
types: &'a mut ComponentTypesBuilder,
|
|
||||||
|
|
||||||
/// The list of static modules that were found during initial translation of
|
/// The list of static modules that were found during initial translation of
|
||||||
/// the component.
|
/// the component.
|
||||||
@@ -445,24 +389,7 @@ impl<'a> Inliner<'a> {
|
|||||||
//
|
//
|
||||||
// NB: at this time only lowered imported functions are supported.
|
// NB: at this time only lowered imported functions are supported.
|
||||||
Lower(func, options) => {
|
Lower(func, options) => {
|
||||||
// Use the type information from `wasmparser` to lookup the core
|
let canonical_abi = frame.translation.funcs[frame.funcs.next_key()];
|
||||||
// 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 options_lower = self.canonical_options(frame, options);
|
let options_lower = self.canonical_options(frame, options);
|
||||||
let func = match &frame.component_funcs[*func] {
|
let func = match &frame.component_funcs[*func] {
|
||||||
@@ -937,6 +864,89 @@ impl<'a> Inliner<'a> {
|
|||||||
post_return,
|
post_return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_export(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
def: ComponentItemDef<'a>,
|
||||||
|
map: &mut IndexMap<String, Export>,
|
||||||
|
) -> 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> {
|
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<ComponentItemDef<'a>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -212,7 +212,18 @@ impl Func {
|
|||||||
Return: Lift,
|
Return: Lift,
|
||||||
S: AsContext,
|
S: AsContext,
|
||||||
{
|
{
|
||||||
self.typecheck::<Params, Return>(store.as_context().0)?;
|
self._typed(store.as_context().0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _typed<Params, Return>(
|
||||||
|
&self,
|
||||||
|
store: &StoreOpaque,
|
||||||
|
) -> Result<TypedFunc<Params, Return>>
|
||||||
|
where
|
||||||
|
Params: ComponentParams + Lower,
|
||||||
|
Return: Lift,
|
||||||
|
{
|
||||||
|
self.typecheck::<Params, Return>(store)?;
|
||||||
unsafe { Ok(TypedFunc::new_unchecked(*self)) }
|
unsafe { Ok(TypedFunc::new_unchecked(*self)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use crate::component::func::HostFunc;
|
|||||||
use crate::component::{Component, ComponentParams, Func, Lift, Lower, TypedFunc};
|
use crate::component::{Component, ComponentParams, Func, Lift, Lower, TypedFunc};
|
||||||
use crate::instance::OwnedImports;
|
use crate::instance::OwnedImports;
|
||||||
use crate::store::{StoreOpaque, Stored};
|
use crate::store::{StoreOpaque, Stored};
|
||||||
use crate::{AsContextMut, Module, StoreContext, StoreContextMut};
|
use crate::{AsContextMut, Module, StoreContextMut};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use std::marker;
|
use std::marker;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::component::{
|
use wasmtime_environ::component::{
|
||||||
@@ -43,30 +44,33 @@ pub(crate) struct InstanceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
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<StoreContextMut<'a, T>>) -> Exports<'a> {
|
||||||
|
let store = store.into();
|
||||||
|
Exports::new(store.0, self)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
/// This is a convenience method for calling [`Instance::exports`] followed
|
||||||
/// and `name` is the name of the function to lookup. If the function is
|
/// by [`ExportInstance::get_func`].
|
||||||
/// found `Some` is returned otherwise `None` is returned.
|
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `store` does not own this instance.
|
/// Panics if `store` does not own this instance.
|
||||||
pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option<Func> {
|
pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option<Func> {
|
||||||
self._get_func(store.as_context_mut().0, name)
|
self.exports(store.as_context_mut()).root().func(name)
|
||||||
}
|
|
||||||
|
|
||||||
fn _get_func(&self, store: &mut StoreOpaque, name: &str) -> Option<Func> {
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks up an exported [`Func`] value by name and with its type.
|
/// 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))?)
|
.with_context(|| format!("failed to convert function `{}` to given type", name))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator of all of the exported modules that this instance
|
/// Looks up a module by name within this [`Instance`].
|
||||||
/// contains.
|
///
|
||||||
//
|
/// The `store` specified must be the store that this instance lives within
|
||||||
// FIXME: this should probably be generalized in some form to something else
|
/// and `name` is the name of the function to lookup. If the function is
|
||||||
// that either looks like:
|
/// found `Some` is returned otherwise `None` is returned.
|
||||||
//
|
///
|
||||||
// * an iterator over all exports
|
/// # Panics
|
||||||
// * an iterator for a `Component` with type information followed by a
|
///
|
||||||
// `get_module` function here
|
/// Panics if `store` does not own this instance.
|
||||||
//
|
pub fn get_module(&self, mut store: impl AsContextMut, name: &str) -> Option<Module> {
|
||||||
// For now this is just quick-and-dirty to get wast support for iterating
|
self.exports(store.as_context_mut())
|
||||||
// over exported modules to work.
|
.root()
|
||||||
pub fn modules<'a, T: 'a>(
|
.module(name)
|
||||||
&'a self,
|
.cloned()
|
||||||
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 {
|
||||||
fn get_func(&self, store: &mut StoreOpaque, instance: &Instance, name: &str) -> Option<Func> {
|
|
||||||
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 {
|
pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export {
|
||||||
match def {
|
match def {
|
||||||
CoreDef::Export(e) => self.lookup_export(store, e),
|
CoreDef::Export(e) => self.lookup_export(store, e),
|
||||||
@@ -442,3 +418,165 @@ impl<T> InstancePre<T> {
|
|||||||
Ok(Instance(store.0.store_data_mut().insert(Some(data))))
|
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<Box<InstanceData>>,
|
||||||
|
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<ExportInstance<'_, '_>> {
|
||||||
|
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<String, Export>,
|
||||||
|
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<Func> {
|
||||||
|
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<Params, Results>(&mut self, name: &str) -> Result<TypedFunc<Params, Results>>
|
||||||
|
where
|
||||||
|
Params: ComponentParams + Lower,
|
||||||
|
Results: Lift,
|
||||||
|
{
|
||||||
|
let func = self
|
||||||
|
.func(name)
|
||||||
|
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
|
||||||
|
Ok(func
|
||||||
|
._typed::<Params, Results>(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<Item = (&'a str, &'a Module)> + '_ {
|
||||||
|
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<ExportInstance<'a, '_>> {
|
||||||
|
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<ExportInstance<'a, 'store>> {
|
||||||
|
match self.exports.get(name)? {
|
||||||
|
Export::Instance(exports) => Some(ExportInstance {
|
||||||
|
exports,
|
||||||
|
instance: self.instance,
|
||||||
|
data: self.data,
|
||||||
|
store: self.store,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub use self::func::{
|
|||||||
ComponentParams, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList,
|
ComponentParams, ComponentType, Func, IntoComponentFunc, Lift, Lower, TypedFunc, WasmList,
|
||||||
WasmStr,
|
WasmStr,
|
||||||
};
|
};
|
||||||
pub use self::instance::{Instance, InstancePre};
|
pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
|
||||||
pub use self::linker::{Linker, LinkerInstance};
|
pub use self::linker::{Linker, LinkerInstance};
|
||||||
pub use wasmtime_component_macro::{ComponentType, Lift, Lower};
|
pub use wasmtime_component_macro::{ComponentType, Lift, Lower};
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ impl<T> WastContext<T> {
|
|||||||
// function from one instance and put it into the linker
|
// function from one instance and put it into the linker
|
||||||
// (must go through the host right now).
|
// (must go through the host right now).
|
||||||
let mut linker = self.component_linker.instance(name.name())?;
|
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)?;
|
linker.module(name, module)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use wasmtime::{AsContextMut, Config, Engine};
|
|||||||
|
|
||||||
mod func;
|
mod func;
|
||||||
mod import;
|
mod import;
|
||||||
|
mod instance;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod nested;
|
mod nested;
|
||||||
mod post_return;
|
mod post_return;
|
||||||
|
|||||||
57
tests/all/component_model/instance.rs
Normal file
57
tests/all/component_model/instance.rs
Normal file
@@ -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(())
|
||||||
|
}
|
||||||
@@ -235,3 +235,20 @@
|
|||||||
|
|
||||||
(core instance (instantiate $verify (with "host" (instance $i))))
|
(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))
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user