Skip to content

Commit

Permalink
Redesign how component exports work (#8786)
Browse files Browse the repository at this point in the history
* Un-nest exports in a component

This commit flattens the representation of exports in a component to
make them more easily indexable without forcing traversal through the
hierarchy of instance imports/exports to get there.

* Guarantee type information on component exports

Don't have it optional in some cases and present in others, instead
ensure there's type information for all component exports immediately
available.

* Refactor how component instance exports are loaded

This commit is a change to Wasmtime's public API for
`wasmtime::component::Instance` that reorganizes how component exports
are loaded. Previously there was a system where `Instance::exports()`
was called that that was sort of "iterated over" in a builder-style
pattern to acquire the actual export desired. This required lifetime
trickery for nested instances and some unfortunate API bloat. The major
downside of this approach is that it requires unconditional string
lookups at runtime for exports and additionally does not serve as a
great place to implement the semver-compatible logic of #8395. The goal
of this refactoring is to pave the way to improving this.

The new APIs for loading exports now look a bit more similar to what's
available for core modules. Notably there's a new
`Component::export_index` method which enables performing a string
lookup and returning an index. This index can in turn be passed to
`Instance::get_*` to skip the string lookup when exports are loaded. The
`Instance::exports` API is then entirely removed and dismantled.

The only piece remaining is the ability to load nested exports which is
done through an `Option` parameter to `Component::export_index`. The
way to load a nested instance is now to first lookup the instance with
`None` as this parameter an then the instance itself is `Some` to look
up an export of that instance. This removes the need for a
recursive-style lifetime-juggling API from wasmtime and in theory helps
simplify the usage of loading exports.

* Update `bindgen!` generated structures for exports

This commit updates the output of `bindgen!` to have a different setup
for exports of worlds to handle the changes from the previous commit.
This introduces new `*Pre` structures which are generated alongside the
existing `Guest` structures for example. The `*Pre` versions contain
`ComponentExportIndex` from the previous commit and serve as a path to
accelerating instantiation because all name lookups are skipped.

* Update test expectations for `bindgen!`-generated output

* Review comments

* Fix doc link
  • Loading branch information
alexcrichton committed Jun 18, 2024
1 parent 7b43325 commit 3171ef6
Show file tree
Hide file tree
Showing 91 changed files with 8,321 additions and 3,917 deletions.
157 changes: 106 additions & 51 deletions crates/component-macro/tests/expanded/char.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,70 @@
/// Auto-generated bindings for a pre-instantiated version of a
/// copmonent which implements the world `the-world`.
///
/// This structure is created through [`TheWorldPre::new`] which
/// takes a [`InstancePre`](wasmtime::component::InstancePre) that
/// has been created through a [`Linker`](wasmtime::component::Linker).
pub struct TheWorldPre<T> {
instance_pre: wasmtime::component::InstancePre<T>,
interface0: exports::foo::foo::chars::GuestPre,
}
/// Auto-generated bindings for an instance a component which
/// implements the world `the-world`.
///
/// This structure is created through either
/// [`TheWorld::instantiate`] or by first creating
/// a [`TheWorldPre`] followed by using
/// [`TheWorldPre::instantiate`].
pub struct TheWorld {
interface0: exports::foo::foo::chars::Guest,
}
const _: () = {
#[allow(unused_imports)]
use wasmtime::component::__internal::anyhow;
impl<_T> TheWorldPre<_T> {
/// Creates a new copy of `TheWorldPre` bindings which can then
/// be used to instantiate into a particular store.
///
/// This method may fail if the compoennt behind `instance_pre`
/// does not have the required exports.
pub fn new(
instance_pre: wasmtime::component::InstancePre<_T>,
) -> wasmtime::Result<Self> {
let _component = instance_pre.component();
let interface0 = exports::foo::foo::chars::GuestPre::new(_component)?;
Ok(TheWorldPre {
instance_pre,
interface0,
})
}
/// Instantiates a new instance of [`TheWorld`] within the
/// `store` provided.
///
/// This function will use `self` as the pre-instantiated
/// instance to perform instantiation. Afterwards the preloaded
/// indices in `self` are used to lookup all exports on the
/// resulting instance.
pub fn instantiate(
&self,
mut store: impl wasmtime::AsContextMut<Data = _T>,
) -> wasmtime::Result<TheWorld> {
let mut store = store.as_context_mut();
let _instance = self.instance_pre.instantiate(&mut store)?;
let interface0 = self.interface0.load(&mut store, &_instance)?;
Ok(TheWorld { interface0 })
}
}
impl TheWorld {
/// Convenience wrapper around [`TheWorldPre::new`] and
/// [`TheWorldPre::instantiate`].
pub fn instantiate<_T>(
mut store: impl wasmtime::AsContextMut<Data = _T>,
component: &wasmtime::component::Component,
linker: &wasmtime::component::Linker<_T>,
) -> wasmtime::Result<TheWorld> {
let pre = linker.instantiate_pre(component)?;
TheWorldPre::new(pre)?.instantiate(store)
}
pub fn add_to_linker<T, U>(
linker: &mut wasmtime::component::Linker<T>,
get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
Expand All @@ -15,51 +75,6 @@ const _: () = {
foo::foo::chars::add_to_linker(linker, get)?;
Ok(())
}
/// Instantiates the provided `module` using the specified
/// parameters, wrapping up the result in a structure that
/// translates between wasm and the host.
pub fn instantiate<T>(
mut store: impl wasmtime::AsContextMut<Data = T>,
component: &wasmtime::component::Component,
linker: &wasmtime::component::Linker<T>,
) -> wasmtime::Result<(Self, wasmtime::component::Instance)> {
let instance = linker.instantiate(&mut store, component)?;
Ok((Self::new(store, &instance)?, instance))
}
/// Instantiates a pre-instantiated module using the specified
/// parameters, wrapping up the result in a structure that
/// translates between wasm and the host.
pub fn instantiate_pre<T>(
mut store: impl wasmtime::AsContextMut<Data = T>,
instance_pre: &wasmtime::component::InstancePre<T>,
) -> wasmtime::Result<(Self, wasmtime::component::Instance)> {
let instance = instance_pre.instantiate(&mut store)?;
Ok((Self::new(store, &instance)?, instance))
}
/// Low-level creation wrapper for wrapping up the exports
/// of the `instance` provided in this structure of wasm
/// exports.
///
/// This function will extract exports from the `instance`
/// defined within `store` and wrap them all up in the
/// returned structure which can be used to interact with
/// the wasm module.
pub fn new(
mut store: impl wasmtime::AsContextMut,
instance: &wasmtime::component::Instance,
) -> wasmtime::Result<Self> {
let mut store = store.as_context_mut();
let mut exports = instance.exports(&mut store);
let mut __exports = exports.root();
let interface0 = exports::foo::foo::chars::Guest::new(
&mut __exports
.instance("foo:foo/chars")
.ok_or_else(|| {
anyhow::anyhow!("exported instance `foo:foo/chars` not present")
})?,
)?;
Ok(TheWorld { interface0 })
}
pub fn foo_foo_chars(&self) -> &exports::foo::foo::chars::Guest {
&self.interface0
}
Expand Down Expand Up @@ -148,18 +163,58 @@ pub mod exports {
take_char: wasmtime::component::Func,
return_char: wasmtime::component::Func,
}
impl Guest {
pub struct GuestPre {
take_char: wasmtime::component::ComponentExportIndex,
return_char: wasmtime::component::ComponentExportIndex,
}
impl GuestPre {
pub fn new(
__exports: &mut wasmtime::component::ExportInstance<'_, '_>,
component: &wasmtime::component::Component,
) -> wasmtime::Result<GuestPre> {
let _component = component;
let (_, instance) = component
.export_index(None, "foo:foo/chars")
.ok_or_else(|| {
anyhow::anyhow!(
"no exported instance named `foo:foo/chars`"
)
})?;
let _lookup = |name: &str| {
_component
.export_index(Some(&instance), name)
.map(|p| p.1)
.ok_or_else(|| {
anyhow::anyhow!(
"instance export `foo:foo/chars` does \
not have export `{name}`"
)
})
};
let take_char = _lookup("take-char")?;
let return_char = _lookup("return-char")?;
Ok(GuestPre { take_char, return_char })
}
pub fn load(
&self,
mut store: impl wasmtime::AsContextMut,
instance: &wasmtime::component::Instance,
) -> wasmtime::Result<Guest> {
let take_char = *__exports
.typed_func::<(char,), ()>("take-char")?
let mut store = store.as_context_mut();
let _ = &mut store;
let _instance = instance;
let take_char = *_instance
.get_typed_func::<(char,), ()>(&mut store, &self.take_char)?
.func();
let return_char = *__exports
.typed_func::<(), (char,)>("return-char")?
let return_char = *_instance
.get_typed_func::<
(),
(char,),
>(&mut store, &self.return_char)?
.func();
Ok(Guest { take_char, return_char })
}
}
impl Guest {
/// A function that accepts a character
pub fn call_take_char<S: wasmtime::AsContextMut>(
&self,
Expand Down
163 changes: 112 additions & 51 deletions crates/component-macro/tests/expanded/char_async.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,76 @@
/// Auto-generated bindings for a pre-instantiated version of a
/// copmonent which implements the world `the-world`.
///
/// This structure is created through [`TheWorldPre::new`] which
/// takes a [`InstancePre`](wasmtime::component::InstancePre) that
/// has been created through a [`Linker`](wasmtime::component::Linker).
pub struct TheWorldPre<T> {
instance_pre: wasmtime::component::InstancePre<T>,
interface0: exports::foo::foo::chars::GuestPre,
}
/// Auto-generated bindings for an instance a component which
/// implements the world `the-world`.
///
/// This structure is created through either
/// [`TheWorld::instantiate_async`] or by first creating
/// a [`TheWorldPre`] followed by using
/// [`TheWorldPre::instantiate_async`].
pub struct TheWorld {
interface0: exports::foo::foo::chars::Guest,
}
const _: () = {
#[allow(unused_imports)]
use wasmtime::component::__internal::anyhow;
impl<_T> TheWorldPre<_T> {
/// Creates a new copy of `TheWorldPre` bindings which can then
/// be used to instantiate into a particular store.
///
/// This method may fail if the compoennt behind `instance_pre`
/// does not have the required exports.
pub fn new(
instance_pre: wasmtime::component::InstancePre<_T>,
) -> wasmtime::Result<Self> {
let _component = instance_pre.component();
let interface0 = exports::foo::foo::chars::GuestPre::new(_component)?;
Ok(TheWorldPre {
instance_pre,
interface0,
})
}
/// Instantiates a new instance of [`TheWorld`] within the
/// `store` provided.
///
/// This function will use `self` as the pre-instantiated
/// instance to perform instantiation. Afterwards the preloaded
/// indices in `self` are used to lookup all exports on the
/// resulting instance.
pub async fn instantiate_async(
&self,
mut store: impl wasmtime::AsContextMut<Data = _T>,
) -> wasmtime::Result<TheWorld>
where
_T: Send,
{
let mut store = store.as_context_mut();
let _instance = self.instance_pre.instantiate_async(&mut store).await?;
let interface0 = self.interface0.load(&mut store, &_instance)?;
Ok(TheWorld { interface0 })
}
}
impl TheWorld {
/// Convenience wrapper around [`TheWorldPre::new`] and
/// [`TheWorldPre::instantiate_async`].
pub async fn instantiate_async<_T>(
mut store: impl wasmtime::AsContextMut<Data = _T>,
component: &wasmtime::component::Component,
linker: &wasmtime::component::Linker<_T>,
) -> wasmtime::Result<TheWorld>
where
_T: Send,
{
let pre = linker.instantiate_pre(component)?;
TheWorldPre::new(pre)?.instantiate_async(store).await
}
pub fn add_to_linker<T, U>(
linker: &mut wasmtime::component::Linker<T>,
get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
Expand All @@ -16,51 +82,6 @@ const _: () = {
foo::foo::chars::add_to_linker(linker, get)?;
Ok(())
}
/// Instantiates the provided `module` using the specified
/// parameters, wrapping up the result in a structure that
/// translates between wasm and the host.
pub async fn instantiate_async<T: Send>(
mut store: impl wasmtime::AsContextMut<Data = T>,
component: &wasmtime::component::Component,
linker: &wasmtime::component::Linker<T>,
) -> wasmtime::Result<(Self, wasmtime::component::Instance)> {
let instance = linker.instantiate_async(&mut store, component).await?;
Ok((Self::new(store, &instance)?, instance))
}
/// Instantiates a pre-instantiated module using the specified
/// parameters, wrapping up the result in a structure that
/// translates between wasm and the host.
pub async fn instantiate_pre<T: Send>(
mut store: impl wasmtime::AsContextMut<Data = T>,
instance_pre: &wasmtime::component::InstancePre<T>,
) -> wasmtime::Result<(Self, wasmtime::component::Instance)> {
let instance = instance_pre.instantiate_async(&mut store).await?;
Ok((Self::new(store, &instance)?, instance))
}
/// Low-level creation wrapper for wrapping up the exports
/// of the `instance` provided in this structure of wasm
/// exports.
///
/// This function will extract exports from the `instance`
/// defined within `store` and wrap them all up in the
/// returned structure which can be used to interact with
/// the wasm module.
pub fn new(
mut store: impl wasmtime::AsContextMut,
instance: &wasmtime::component::Instance,
) -> wasmtime::Result<Self> {
let mut store = store.as_context_mut();
let mut exports = instance.exports(&mut store);
let mut __exports = exports.root();
let interface0 = exports::foo::foo::chars::Guest::new(
&mut __exports
.instance("foo:foo/chars")
.ok_or_else(|| {
anyhow::anyhow!("exported instance `foo:foo/chars` not present")
})?,
)?;
Ok(TheWorld { interface0 })
}
pub fn foo_foo_chars(&self) -> &exports::foo::foo::chars::Guest {
&self.interface0
}
Expand Down Expand Up @@ -155,18 +176,58 @@ pub mod exports {
take_char: wasmtime::component::Func,
return_char: wasmtime::component::Func,
}
impl Guest {
pub struct GuestPre {
take_char: wasmtime::component::ComponentExportIndex,
return_char: wasmtime::component::ComponentExportIndex,
}
impl GuestPre {
pub fn new(
__exports: &mut wasmtime::component::ExportInstance<'_, '_>,
component: &wasmtime::component::Component,
) -> wasmtime::Result<GuestPre> {
let _component = component;
let (_, instance) = component
.export_index(None, "foo:foo/chars")
.ok_or_else(|| {
anyhow::anyhow!(
"no exported instance named `foo:foo/chars`"
)
})?;
let _lookup = |name: &str| {
_component
.export_index(Some(&instance), name)
.map(|p| p.1)
.ok_or_else(|| {
anyhow::anyhow!(
"instance export `foo:foo/chars` does \
not have export `{name}`"
)
})
};
let take_char = _lookup("take-char")?;
let return_char = _lookup("return-char")?;
Ok(GuestPre { take_char, return_char })
}
pub fn load(
&self,
mut store: impl wasmtime::AsContextMut,
instance: &wasmtime::component::Instance,
) -> wasmtime::Result<Guest> {
let take_char = *__exports
.typed_func::<(char,), ()>("take-char")?
let mut store = store.as_context_mut();
let _ = &mut store;
let _instance = instance;
let take_char = *_instance
.get_typed_func::<(char,), ()>(&mut store, &self.take_char)?
.func();
let return_char = *__exports
.typed_func::<(), (char,)>("return-char")?
let return_char = *_instance
.get_typed_func::<
(),
(char,),
>(&mut store, &self.return_char)?
.func();
Ok(Guest { take_char, return_char })
}
}
impl Guest {
/// A function that accepts a character
pub async fn call_take_char<S: wasmtime::AsContextMut>(
&self,
Expand Down
Loading

0 comments on commit 3171ef6

Please sign in to comment.