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:
Alex Crichton
2022-07-05 11:04:54 -05:00
committed by GitHub
parent 2003ae99a0
commit 76a2545a7f
10 changed files with 431 additions and 146 deletions

View File

@@ -212,7 +212,18 @@ impl Func {
Return: Lift,
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)) }
}

View File

@@ -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<StoreContextMut<'a, T>>) -> 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<Func> {
self._get_func(store.as_context_mut().0, 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;
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<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,
})
/// 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<Module> {
self.exports(store.as_context_mut())
.root()
.module(name)
.cloned()
}
}
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 {
match def {
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))))
}
}
/// 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,
}
}
}

View File

@@ -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};