component model: async host function & embedding support (#5055)
* func_wrap_async typechecks
* func call async
* instantiate_async
* fixes
* async engine creation for tests
* start adding a component model test for async
* fix wrong check for async support, factor out Instance::new_started to an unchecked impl
* tests: wibbles
* component::Linker::func_wrap: replace IntoComponentFunc with directly accepting a closure
We find that this makes the Linker::func_wrap type signature much easier
to read. The IntoComponentFunc abstraction was adding a lot of weight to
"splat" a set of arguments from a tuple of types into individual
arguments to the closure. Additionally, making the StoreContextMut
argument optional, or the Result<return> optional, wasn't very
worthwhile.
* Fixes for the new style of closure required by component::Linker::func_wrap
* future of result of return
* add Linker::instantiate_async and {Typed}Func::post_return_async
* fix fuzzing generator
* note optimisation opportunity
* simplify test
This commit is contained in:
@@ -65,6 +65,12 @@ pub fn engine() -> Engine {
|
|||||||
Engine::new(&config()).unwrap()
|
Engine::new(&config()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn async_engine() -> Engine {
|
||||||
|
let mut config = config();
|
||||||
|
config.async_support(true);
|
||||||
|
Engine::new(&config).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Newtype wrapper for `f32` whose `PartialEq` impl considers NaNs equal to each other.
|
/// Newtype wrapper for `f32` whose `PartialEq` impl considers NaNs equal to each other.
|
||||||
#[derive(Copy, Clone, Debug, Arbitrary)]
|
#[derive(Copy, Clone, Debug, Arbitrary)]
|
||||||
pub struct Float32(pub f32);
|
pub struct Float32(pub f32);
|
||||||
|
|||||||
@@ -277,11 +277,59 @@ impl Func {
|
|||||||
/// The `params` here must match the type signature of this `Func`, or this will return an error. If a trap
|
/// The `params` here must match the type signature of this `Func`, or this will return an error. If a trap
|
||||||
/// occurs while executing this function, then an error will also be returned.
|
/// occurs while executing this function, then an error will also be returned.
|
||||||
// TODO: say more -- most of the docs for `TypedFunc::call` apply here, too
|
// TODO: say more -- most of the docs for `TypedFunc::call` apply here, too
|
||||||
|
//
|
||||||
|
// # Panics
|
||||||
|
//
|
||||||
|
// Panics if this is called on a function in an asyncronous store. This only works
|
||||||
|
// with functions defined within a synchronous store. Also panics if `store`
|
||||||
|
// does not own this function.
|
||||||
pub fn call(
|
pub fn call(
|
||||||
&self,
|
&self,
|
||||||
mut store: impl AsContextMut,
|
mut store: impl AsContextMut,
|
||||||
params: &[Val],
|
params: &[Val],
|
||||||
results: &mut [Val],
|
results: &mut [Val],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
!store.0.async_support(),
|
||||||
|
"must use `call_async` when async support is enabled on the config"
|
||||||
|
);
|
||||||
|
self.call_impl(&mut store.as_context_mut(), params, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exactly like [`Self::call`] except for use on async stores.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if this is called on a function in a synchronous store. This only works
|
||||||
|
/// with functions defined within an asynchronous store. Also panics if `store`
|
||||||
|
/// does not own this function.
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn call_async<T>(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
params: &[Val],
|
||||||
|
results: &mut [Val],
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `call_async` without enabling async support in the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| self.call_impl(store, params, results))
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_impl(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut,
|
||||||
|
params: &[Val],
|
||||||
|
results: &mut [Val],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let store = &mut store.as_context_mut();
|
let store = &mut store.as_context_mut();
|
||||||
|
|
||||||
@@ -490,7 +538,42 @@ impl Func {
|
|||||||
/// called, then it will panic. If a different [`Func`] for the same
|
/// called, then it will panic. If a different [`Func`] for the same
|
||||||
/// component instance was invoked then this function will also panic
|
/// component instance was invoked then this function will also panic
|
||||||
/// because the `post-return` needs to happen for the other function.
|
/// because the `post-return` needs to happen for the other function.
|
||||||
|
///
|
||||||
|
/// Panics if this is called on a function in an asynchronous store.
|
||||||
|
/// This only works with functions defined within a synchronous store.
|
||||||
pub fn post_return(&self, mut store: impl AsContextMut) -> Result<()> {
|
pub fn post_return(&self, mut store: impl AsContextMut) -> Result<()> {
|
||||||
|
let store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
!store.0.async_support(),
|
||||||
|
"must use `post_return_async` when async support is enabled on the config"
|
||||||
|
);
|
||||||
|
self.post_return_impl(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exactly like [`Self::post_return`] except for use on async stores.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if this is called on a function in a synchronous store. This
|
||||||
|
/// only works with functions defined within an asynchronous store.
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn post_return_async<T: Send>(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `call_async` without enabling async support in the config"
|
||||||
|
);
|
||||||
|
// Future optimization opportunity: conditionally use a fiber here since
|
||||||
|
// some func's post_return will not need the async context (i.e. end up
|
||||||
|
// calling async host functionality)
|
||||||
|
store.on_fiber(|store| self.post_return_impl(store)).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_return_impl(&self, mut store: impl AsContextMut) -> Result<()> {
|
||||||
let mut store = store.as_context_mut();
|
let mut store = store.as_context_mut();
|
||||||
let data = &mut store.0[self.0];
|
let data = &mut store.0[self.0];
|
||||||
let instance = data.instance;
|
let instance = data.instance;
|
||||||
|
|||||||
@@ -145,8 +145,47 @@ where
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This function will panic if `store` does not own this function.
|
/// Panics if this is called on a function in an asynchronous store. This
|
||||||
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Return> {
|
/// only works with functions defined within a synchonous store. Also
|
||||||
|
/// panics if `store` does not own this function.
|
||||||
|
pub fn call(&self, store: impl AsContextMut, params: Params) -> Result<Return> {
|
||||||
|
assert!(
|
||||||
|
!store.as_context().async_support(),
|
||||||
|
"must use `call_async` when async support is enabled on the config"
|
||||||
|
);
|
||||||
|
self.call_impl(store, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exactly like [`Self::call`], except for use on asynchronous stores.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if this is called on a function in a synchronous store. This
|
||||||
|
/// only works with functions defined within an asynchronous store. Also
|
||||||
|
/// panics if `store` does not own this function.
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn call_async<T>(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
params: Params,
|
||||||
|
) -> Result<Return>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
Params: Send + Sync,
|
||||||
|
Return: Send + Sync,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"cannot use `call_async` when async support is not enabled on the config"
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.on_fiber(|store| self.call_impl(store, params))
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_impl(&self, mut store: impl AsContextMut, params: Params) -> Result<Return> {
|
||||||
let store = &mut store.as_context_mut();
|
let store = &mut store.as_context_mut();
|
||||||
// Note that this is in theory simpler than it might read at this time.
|
// Note that this is in theory simpler than it might read at this time.
|
||||||
// Here we're doing a runtime dispatch on the `flatten_count` for the
|
// Here we're doing a runtime dispatch on the `flatten_count` for the
|
||||||
@@ -286,6 +325,16 @@ where
|
|||||||
pub fn post_return(&self, store: impl AsContextMut) -> Result<()> {
|
pub fn post_return(&self, store: impl AsContextMut) -> Result<()> {
|
||||||
self.func.post_return(store)
|
self.func.post_return(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [`Func::post_return_async`]
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn post_return_async<T: Send>(
|
||||||
|
&self,
|
||||||
|
store: impl AsContextMut<Data = T>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.func.post_return_async(store).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait representing a static list of named types that can be passed to or
|
/// A trait representing a static list of named types that can be passed to or
|
||||||
|
|||||||
@@ -259,10 +259,16 @@ impl<'a> Instantiator<'a> {
|
|||||||
|
|
||||||
// Note that the unsafety here should be ok because the
|
// Note that the unsafety here should be ok because the
|
||||||
// validity of the component means that type-checks have
|
// validity of the component means that type-checks have
|
||||||
// already been performed. This maens that the unsafety due
|
// already been performed. This means that the unsafety due
|
||||||
// to imports having the wrong type should not happen here.
|
// to imports having the wrong type should not happen here.
|
||||||
let i =
|
//
|
||||||
unsafe { crate::Instance::new_started(store, module, imports.as_ref())? };
|
// Also note we are calling new_started_impl because we have
|
||||||
|
// already checked for asyncness and are running on a fiber
|
||||||
|
// if required.
|
||||||
|
|
||||||
|
let i = unsafe {
|
||||||
|
crate::Instance::new_started_impl(store, module, imports.as_ref())?
|
||||||
|
};
|
||||||
self.data.instances.push(i);
|
self.data.instances.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +490,36 @@ impl<T> InstancePre<T> {
|
|||||||
/// Performs the instantiation process into the store specified.
|
/// Performs the instantiation process into the store specified.
|
||||||
//
|
//
|
||||||
// TODO: needs more docs
|
// TODO: needs more docs
|
||||||
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
|
pub fn instantiate(&self, store: impl AsContextMut<Data = T>) -> Result<Instance> {
|
||||||
|
assert!(
|
||||||
|
!store.as_context().async_support(),
|
||||||
|
"must use async instantiation when async support is enabled"
|
||||||
|
);
|
||||||
|
self.instantiate_impl(store)
|
||||||
|
}
|
||||||
|
/// Performs the instantiation process into the store specified.
|
||||||
|
///
|
||||||
|
/// Exactly like [`Self::instantiate`] except for use on async stores.
|
||||||
|
//
|
||||||
|
// TODO: needs more docs
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn instantiate_async(
|
||||||
|
&self,
|
||||||
|
mut store: impl AsContextMut<Data = T>,
|
||||||
|
) -> Result<Instance>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut store = store.as_context_mut();
|
||||||
|
assert!(
|
||||||
|
store.0.async_support(),
|
||||||
|
"must use sync instantiation when async support is disabled"
|
||||||
|
);
|
||||||
|
store.on_fiber(|store| self.instantiate_impl(store)).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate_impl(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
|
||||||
let mut store = store.as_context_mut();
|
let mut store = store.as_context_mut();
|
||||||
let mut i = Instantiator::new(&self.component, store.0, &self.imports);
|
let mut i = Instantiator::new(&self.component, store.0, &self.imports);
|
||||||
i.run(&mut store)?;
|
i.run(&mut store)?;
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use crate::component::{Component, ComponentNamedList, Instance, InstancePre, Lif
|
|||||||
use crate::{AsContextMut, Engine, Module, StoreContextMut};
|
use crate::{AsContextMut, Engine, Module, StoreContextMut};
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use std::collections::hash_map::{Entry, HashMap};
|
use std::collections::hash_map::{Entry, HashMap};
|
||||||
|
use std::future::Future;
|
||||||
use std::marker;
|
use std::marker;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::component::TypeDef;
|
use wasmtime_environ::component::TypeDef;
|
||||||
use wasmtime_environ::PrimaryMap;
|
use wasmtime_environ::PrimaryMap;
|
||||||
@@ -36,6 +38,7 @@ pub struct Strings {
|
|||||||
/// a "bag of named items", so each [`LinkerInstance`] can further define items
|
/// a "bag of named items", so each [`LinkerInstance`] can further define items
|
||||||
/// internally.
|
/// internally.
|
||||||
pub struct LinkerInstance<'a, T> {
|
pub struct LinkerInstance<'a, T> {
|
||||||
|
engine: Engine,
|
||||||
strings: &'a mut Strings,
|
strings: &'a mut Strings,
|
||||||
map: &'a mut NameMap,
|
map: &'a mut NameMap,
|
||||||
allow_shadowing: bool,
|
allow_shadowing: bool,
|
||||||
@@ -82,6 +85,7 @@ impl<T> Linker<T> {
|
|||||||
/// the root namespace.
|
/// the root namespace.
|
||||||
pub fn root(&mut self) -> LinkerInstance<'_, T> {
|
pub fn root(&mut self) -> LinkerInstance<'_, T> {
|
||||||
LinkerInstance {
|
LinkerInstance {
|
||||||
|
engine: self.engine.clone(),
|
||||||
strings: &mut self.strings,
|
strings: &mut self.strings,
|
||||||
map: &mut self.map,
|
map: &mut self.map,
|
||||||
allow_shadowing: self.allow_shadowing,
|
allow_shadowing: self.allow_shadowing,
|
||||||
@@ -187,13 +191,47 @@ impl<T> Linker<T> {
|
|||||||
store: impl AsContextMut<Data = T>,
|
store: impl AsContextMut<Data = T>,
|
||||||
component: &Component,
|
component: &Component,
|
||||||
) -> Result<Instance> {
|
) -> Result<Instance> {
|
||||||
|
assert!(
|
||||||
|
!store.as_context().async_support(),
|
||||||
|
"must use async instantiation when async support is enabled"
|
||||||
|
);
|
||||||
self.instantiate_pre(component)?.instantiate(store)
|
self.instantiate_pre(component)?.instantiate(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instantiates the [`Component`] provided into the `store` specified.
|
||||||
|
///
|
||||||
|
/// This is exactly like [`Linker::instantiate`] except for async stores.
|
||||||
|
///
|
||||||
|
/// # 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.
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub async fn instantiate_async(
|
||||||
|
&self,
|
||||||
|
store: impl AsContextMut<Data = T>,
|
||||||
|
component: &Component,
|
||||||
|
) -> Result<Instance>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
store.as_context().async_support(),
|
||||||
|
"must use sync instantiation when async support is disabled"
|
||||||
|
);
|
||||||
|
self.instantiate_pre(component)?
|
||||||
|
.instantiate_async(store)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> LinkerInstance<'_, T> {
|
impl<T> LinkerInstance<'_, T> {
|
||||||
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
|
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
|
||||||
LinkerInstance {
|
LinkerInstance {
|
||||||
|
engine: self.engine.clone(),
|
||||||
strings: self.strings,
|
strings: self.strings,
|
||||||
map: self.map,
|
map: self.map,
|
||||||
allow_shadowing: self.allow_shadowing,
|
allow_shadowing: self.allow_shadowing,
|
||||||
@@ -229,6 +267,36 @@ impl<T> LinkerInstance<'_, T> {
|
|||||||
self.insert(name, Definition::Func(HostFunc::from_closure(func)))
|
self.insert(name, Definition::Func(HostFunc::from_closure(func)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines a new host-provided async function into this [`Linker`].
|
||||||
|
///
|
||||||
|
/// This is exactly like [`Self::func_wrap`] except it takes an async
|
||||||
|
/// host function.
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||||
|
pub fn func_wrap_async<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: for<'a> Fn(
|
||||||
|
StoreContextMut<'a, T>,
|
||||||
|
Params,
|
||||||
|
) -> Box<dyn Future<Output = Result<Return>> + Send + 'a>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
Params: ComponentNamedList + Lift + 'static,
|
||||||
|
Return: ComponentNamedList + Lower + 'static,
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
self.engine.config().async_support,
|
||||||
|
"cannot use `func_wrap_async` without enabling async support in the config"
|
||||||
|
);
|
||||||
|
let ff = move |mut store: StoreContextMut<'_, T>, params: Params| -> Result<Return> {
|
||||||
|
let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
|
||||||
|
let mut future = Pin::from(f(store.as_context_mut(), params));
|
||||||
|
unsafe { async_cx.block_on(future.as_mut()) }?
|
||||||
|
};
|
||||||
|
self.func_wrap(name, ff)
|
||||||
|
}
|
||||||
|
|
||||||
/// Define a new host-provided function using dynamic types.
|
/// Define a new host-provided function using dynamic types.
|
||||||
///
|
///
|
||||||
/// `name` must refer to a function type import in `component`. If and when
|
/// `name` must refer to a function type import in `component`. If and when
|
||||||
@@ -260,6 +328,8 @@ impl<T> LinkerInstance<'_, T> {
|
|||||||
Err(anyhow!("import `{name}` not found"))
|
Err(anyhow!("import `{name}` not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: define func_new_async
|
||||||
|
|
||||||
/// Defines a [`Module`] within this instance.
|
/// Defines a [`Module`] within this instance.
|
||||||
///
|
///
|
||||||
/// This can be used to provide a core wasm [`Module`] as an import to a
|
/// This can be used to provide a core wasm [`Module`] as an import to a
|
||||||
|
|||||||
@@ -174,7 +174,18 @@ impl Instance {
|
|||||||
!store.0.async_support(),
|
!store.0.async_support(),
|
||||||
"must use async instantiation when async support is enabled",
|
"must use async instantiation when async support is enabled",
|
||||||
);
|
);
|
||||||
|
Self::new_started_impl(store, module, imports)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal function to create an instance and run the start function.
|
||||||
|
///
|
||||||
|
/// ONLY CALL THIS IF YOU HAVE ALREADY CHECKED FOR ASYNCNESS AND HANDLED
|
||||||
|
/// THE FIBER NONSENSE
|
||||||
|
pub(crate) unsafe fn new_started_impl<T>(
|
||||||
|
store: &mut StoreContextMut<'_, T>,
|
||||||
|
module: &Module,
|
||||||
|
imports: Imports<'_>,
|
||||||
|
) -> Result<Instance> {
|
||||||
let (instance, start) = Instance::new_raw(store.0, module, imports)?;
|
let (instance, start) = Instance::new_raw(store.0, module, imports)?;
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
instance.start_raw(store, start)?;
|
instance.start_raw(store, start)?;
|
||||||
@@ -194,22 +205,13 @@ impl Instance {
|
|||||||
where
|
where
|
||||||
T: Send,
|
T: Send,
|
||||||
{
|
{
|
||||||
// Note that the body of this function is intentionally quite similar
|
|
||||||
// to the `new_started` function, and it's intended that the two bodies
|
|
||||||
// here are small enough to be ok duplicating.
|
|
||||||
assert!(
|
assert!(
|
||||||
store.0.async_support(),
|
store.0.async_support(),
|
||||||
"must use sync instantiation when async support is disabled",
|
"must use sync instantiation when async support is disabled",
|
||||||
);
|
);
|
||||||
|
|
||||||
store
|
store
|
||||||
.on_fiber(|store| {
|
.on_fiber(|store| Self::new_started_impl(store, module, imports))
|
||||||
let (instance, start) = Instance::new_raw(store.0, module, imports)?;
|
|
||||||
if let Some(start) = start {
|
|
||||||
instance.start_raw(store, start)?;
|
|
||||||
}
|
|
||||||
Ok(instance)
|
|
||||||
})
|
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use component_test_util::{engine, TypedFuncExt};
|
use component_test_util::{async_engine, engine, TypedFuncExt};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use wasmtime::component::Component;
|
use wasmtime::component::Component;
|
||||||
use wasmtime_component_util::REALLOC_AND_FREE;
|
use wasmtime_component_util::REALLOC_AND_FREE;
|
||||||
|
|
||||||
|
mod r#async;
|
||||||
mod dynamic;
|
mod dynamic;
|
||||||
mod func;
|
mod func;
|
||||||
mod import;
|
mod import;
|
||||||
|
|||||||
88
tests/all/component_model/async.rs
Normal file
88
tests/all/component_model/async.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use wasmtime::component::*;
|
||||||
|
use wasmtime::{Store, StoreContextMut, Trap, TrapCode};
|
||||||
|
|
||||||
|
/// This is super::func::thunks, except with an async store.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn smoke() -> Result<()> {
|
||||||
|
let component = r#"
|
||||||
|
(component
|
||||||
|
(core module $m
|
||||||
|
(func (export "thunk"))
|
||||||
|
(func (export "thunk-trap") unreachable)
|
||||||
|
)
|
||||||
|
(core instance $i (instantiate $m))
|
||||||
|
(func (export "thunk")
|
||||||
|
(canon lift (core func $i "thunk"))
|
||||||
|
)
|
||||||
|
(func (export "thunk-trap")
|
||||||
|
(canon lift (core func $i "thunk-trap"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let engine = super::async_engine();
|
||||||
|
let component = Component::new(&engine, component)?;
|
||||||
|
let mut store = Store::new(&engine, ());
|
||||||
|
let instance = Linker::new(&engine)
|
||||||
|
.instantiate_async(&mut store, &component)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;
|
||||||
|
|
||||||
|
thunk.call_async(&mut store, ()).await?;
|
||||||
|
thunk.post_return_async(&mut store).await?;
|
||||||
|
|
||||||
|
let err = instance
|
||||||
|
.get_typed_func::<(), (), _>(&mut store, "thunk-trap")?
|
||||||
|
.call_async(&mut store, ())
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(err.downcast::<Trap>()?.trap_code() == Some(TrapCode::UnreachableCodeReached));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle an import function, created using component::Linker::func_wrap_async.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn smoke_func_wrap() -> Result<()> {
|
||||||
|
let component = r#"
|
||||||
|
(component
|
||||||
|
(type $f (func))
|
||||||
|
(import "i" (func $f))
|
||||||
|
|
||||||
|
(core module $m
|
||||||
|
(import "imports" "i" (func $i))
|
||||||
|
(func (export "thunk") call $i)
|
||||||
|
)
|
||||||
|
|
||||||
|
(core func $f (canon lower (func $f)))
|
||||||
|
(core instance $i (instantiate $m
|
||||||
|
(with "imports" (instance
|
||||||
|
(export "i" (func $f))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
(func (export "thunk")
|
||||||
|
(canon lift (core func $i "thunk"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let engine = super::async_engine();
|
||||||
|
let component = Component::new(&engine, component)?;
|
||||||
|
let mut store = Store::new(&engine, ());
|
||||||
|
let mut linker = Linker::new(&engine);
|
||||||
|
let mut root = linker.root();
|
||||||
|
root.func_wrap_async("i", |_: StoreContextMut<()>, _: ()| {
|
||||||
|
Box::new(async { Ok(()) })
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let instance = linker.instantiate_async(&mut store, &component).await?;
|
||||||
|
|
||||||
|
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;
|
||||||
|
|
||||||
|
thunk.call_async(&mut store, ()).await?;
|
||||||
|
thunk.post_return_async(&mut store).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user