Skip to content

Commit 12e4a1b

Browse files
author
Pat Hickey
authored
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
1 parent 25bc12e commit 12e4a1b

8 files changed

Lines changed: 351 additions & 17 deletions

File tree

crates/misc/component-test-util/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ pub fn engine() -> Engine {
6565
Engine::new(&config()).unwrap()
6666
}
6767

68+
pub fn async_engine() -> Engine {
69+
let mut config = config();
70+
config.async_support(true);
71+
Engine::new(&config).unwrap()
72+
}
73+
6874
/// Newtype wrapper for `f32` whose `PartialEq` impl considers NaNs equal to each other.
6975
#[derive(Copy, Clone, Debug, Arbitrary)]
7076
pub struct Float32(pub f32);

crates/wasmtime/src/component/func.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,59 @@ impl Func {
277277
/// The `params` here must match the type signature of this `Func`, or this will return an error. If a trap
278278
/// occurs while executing this function, then an error will also be returned.
279279
// TODO: say more -- most of the docs for `TypedFunc::call` apply here, too
280+
//
281+
// # Panics
282+
//
283+
// Panics if this is called on a function in an asyncronous store. This only works
284+
// with functions defined within a synchronous store. Also panics if `store`
285+
// does not own this function.
280286
pub fn call(
281287
&self,
282288
mut store: impl AsContextMut,
283289
params: &[Val],
284290
results: &mut [Val],
291+
) -> Result<()> {
292+
let mut store = store.as_context_mut();
293+
assert!(
294+
!store.0.async_support(),
295+
"must use `call_async` when async support is enabled on the config"
296+
);
297+
self.call_impl(&mut store.as_context_mut(), params, results)
298+
}
299+
300+
/// Exactly like [`Self::call`] except for use on async stores.
301+
///
302+
/// # Panics
303+
///
304+
/// Panics if this is called on a function in a synchronous store. This only works
305+
/// with functions defined within an asynchronous store. Also panics if `store`
306+
/// does not own this function.
307+
#[cfg(feature = "async")]
308+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
309+
pub async fn call_async<T>(
310+
&self,
311+
mut store: impl AsContextMut<Data = T>,
312+
params: &[Val],
313+
results: &mut [Val],
314+
) -> Result<()>
315+
where
316+
T: Send,
317+
{
318+
let mut store = store.as_context_mut();
319+
assert!(
320+
store.0.async_support(),
321+
"cannot use `call_async` without enabling async support in the config"
322+
);
323+
store
324+
.on_fiber(|store| self.call_impl(store, params, results))
325+
.await?
326+
}
327+
328+
fn call_impl(
329+
&self,
330+
mut store: impl AsContextMut,
331+
params: &[Val],
332+
results: &mut [Val],
285333
) -> Result<()> {
286334
let store = &mut store.as_context_mut();
287335

@@ -490,7 +538,42 @@ impl Func {
490538
/// called, then it will panic. If a different [`Func`] for the same
491539
/// component instance was invoked then this function will also panic
492540
/// because the `post-return` needs to happen for the other function.
541+
///
542+
/// Panics if this is called on a function in an asynchronous store.
543+
/// This only works with functions defined within a synchronous store.
493544
pub fn post_return(&self, mut store: impl AsContextMut) -> Result<()> {
545+
let store = store.as_context_mut();
546+
assert!(
547+
!store.0.async_support(),
548+
"must use `post_return_async` when async support is enabled on the config"
549+
);
550+
self.post_return_impl(store)
551+
}
552+
553+
/// Exactly like [`Self::post_return`] except for use on async stores.
554+
///
555+
/// # Panics
556+
///
557+
/// Panics if this is called on a function in a synchronous store. This
558+
/// only works with functions defined within an asynchronous store.
559+
#[cfg(feature = "async")]
560+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
561+
pub async fn post_return_async<T: Send>(
562+
&self,
563+
mut store: impl AsContextMut<Data = T>,
564+
) -> Result<()> {
565+
let mut store = store.as_context_mut();
566+
assert!(
567+
store.0.async_support(),
568+
"cannot use `call_async` without enabling async support in the config"
569+
);
570+
// Future optimization opportunity: conditionally use a fiber here since
571+
// some func's post_return will not need the async context (i.e. end up
572+
// calling async host functionality)
573+
store.on_fiber(|store| self.post_return_impl(store)).await?
574+
}
575+
576+
fn post_return_impl(&self, mut store: impl AsContextMut) -> Result<()> {
494577
let mut store = store.as_context_mut();
495578
let data = &mut store.0[self.0];
496579
let instance = data.instance;

crates/wasmtime/src/component/func/typed.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,47 @@ where
145145
///
146146
/// # Panics
147147
///
148-
/// This function will panic if `store` does not own this function.
149-
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Return> {
148+
/// Panics if this is called on a function in an asynchronous store. This
149+
/// only works with functions defined within a synchonous store. Also
150+
/// panics if `store` does not own this function.
151+
pub fn call(&self, store: impl AsContextMut, params: Params) -> Result<Return> {
152+
assert!(
153+
!store.as_context().async_support(),
154+
"must use `call_async` when async support is enabled on the config"
155+
);
156+
self.call_impl(store, params)
157+
}
158+
159+
/// Exactly like [`Self::call`], except for use on asynchronous stores.
160+
///
161+
/// # Panics
162+
///
163+
/// Panics if this is called on a function in a synchronous store. This
164+
/// only works with functions defined within an asynchronous store. Also
165+
/// panics if `store` does not own this function.
166+
#[cfg(feature = "async")]
167+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
168+
pub async fn call_async<T>(
169+
&self,
170+
mut store: impl AsContextMut<Data = T>,
171+
params: Params,
172+
) -> Result<Return>
173+
where
174+
T: Send,
175+
Params: Send + Sync,
176+
Return: Send + Sync,
177+
{
178+
let mut store = store.as_context_mut();
179+
assert!(
180+
store.0.async_support(),
181+
"cannot use `call_async` when async support is not enabled on the config"
182+
);
183+
store
184+
.on_fiber(|store| self.call_impl(store, params))
185+
.await?
186+
}
187+
188+
fn call_impl(&self, mut store: impl AsContextMut, params: Params) -> Result<Return> {
150189
let store = &mut store.as_context_mut();
151190
// Note that this is in theory simpler than it might read at this time.
152191
// Here we're doing a runtime dispatch on the `flatten_count` for the
@@ -286,6 +325,16 @@ where
286325
pub fn post_return(&self, store: impl AsContextMut) -> Result<()> {
287326
self.func.post_return(store)
288327
}
328+
329+
/// See [`Func::post_return_async`]
330+
#[cfg(feature = "async")]
331+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
332+
pub async fn post_return_async<T: Send>(
333+
&self,
334+
store: impl AsContextMut<Data = T>,
335+
) -> Result<()> {
336+
self.func.post_return_async(store).await
337+
}
289338
}
290339

291340
/// A trait representing a static list of named types that can be passed to or

crates/wasmtime/src/component/instance.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,16 @@ impl<'a> Instantiator<'a> {
259259

260260
// Note that the unsafety here should be ok because the
261261
// validity of the component means that type-checks have
262-
// already been performed. This maens that the unsafety due
262+
// already been performed. This means that the unsafety due
263263
// to imports having the wrong type should not happen here.
264-
let i =
265-
unsafe { crate::Instance::new_started(store, module, imports.as_ref())? };
264+
//
265+
// Also note we are calling new_started_impl because we have
266+
// already checked for asyncness and are running on a fiber
267+
// if required.
268+
269+
let i = unsafe {
270+
crate::Instance::new_started_impl(store, module, imports.as_ref())?
271+
};
266272
self.data.instances.push(i);
267273
}
268274

@@ -484,7 +490,36 @@ impl<T> InstancePre<T> {
484490
/// Performs the instantiation process into the store specified.
485491
//
486492
// TODO: needs more docs
487-
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
493+
pub fn instantiate(&self, store: impl AsContextMut<Data = T>) -> Result<Instance> {
494+
assert!(
495+
!store.as_context().async_support(),
496+
"must use async instantiation when async support is enabled"
497+
);
498+
self.instantiate_impl(store)
499+
}
500+
/// Performs the instantiation process into the store specified.
501+
///
502+
/// Exactly like [`Self::instantiate`] except for use on async stores.
503+
//
504+
// TODO: needs more docs
505+
#[cfg(feature = "async")]
506+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
507+
pub async fn instantiate_async(
508+
&self,
509+
mut store: impl AsContextMut<Data = T>,
510+
) -> Result<Instance>
511+
where
512+
T: Send,
513+
{
514+
let mut store = store.as_context_mut();
515+
assert!(
516+
store.0.async_support(),
517+
"must use sync instantiation when async support is disabled"
518+
);
519+
store.on_fiber(|store| self.instantiate_impl(store)).await?
520+
}
521+
522+
fn instantiate_impl(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
488523
let mut store = store.as_context_mut();
489524
let mut i = Instantiator::new(&self.component, store.0, &self.imports);
490525
i.run(&mut store)?;

crates/wasmtime/src/component/linker.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use crate::component::{Component, ComponentNamedList, Instance, InstancePre, Lif
55
use crate::{AsContextMut, Engine, Module, StoreContextMut};
66
use anyhow::{anyhow, bail, Context, Result};
77
use std::collections::hash_map::{Entry, HashMap};
8+
use std::future::Future;
89
use std::marker;
10+
use std::pin::Pin;
911
use std::sync::Arc;
1012
use wasmtime_environ::component::TypeDef;
1113
use wasmtime_environ::PrimaryMap;
@@ -36,6 +38,7 @@ pub struct Strings {
3638
/// a "bag of named items", so each [`LinkerInstance`] can further define items
3739
/// internally.
3840
pub struct LinkerInstance<'a, T> {
41+
engine: Engine,
3942
strings: &'a mut Strings,
4043
map: &'a mut NameMap,
4144
allow_shadowing: bool,
@@ -82,6 +85,7 @@ impl<T> Linker<T> {
8285
/// the root namespace.
8386
pub fn root(&mut self) -> LinkerInstance<'_, T> {
8487
LinkerInstance {
88+
engine: self.engine.clone(),
8589
strings: &mut self.strings,
8690
map: &mut self.map,
8791
allow_shadowing: self.allow_shadowing,
@@ -187,13 +191,47 @@ impl<T> Linker<T> {
187191
store: impl AsContextMut<Data = T>,
188192
component: &Component,
189193
) -> Result<Instance> {
194+
assert!(
195+
!store.as_context().async_support(),
196+
"must use async instantiation when async support is enabled"
197+
);
190198
self.instantiate_pre(component)?.instantiate(store)
191199
}
200+
201+
/// Instantiates the [`Component`] provided into the `store` specified.
202+
///
203+
/// This is exactly like [`Linker::instantiate`] except for async stores.
204+
///
205+
/// # Errors
206+
///
207+
/// Returns an error if this [`Linker`] doesn't define an import that
208+
/// `component` requires or if it is of the wrong type. Additionally this
209+
/// can return an error if something goes wrong during instantiation such as
210+
/// a runtime trap or a runtime limit being exceeded.
211+
#[cfg(feature = "async")]
212+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
213+
pub async fn instantiate_async(
214+
&self,
215+
store: impl AsContextMut<Data = T>,
216+
component: &Component,
217+
) -> Result<Instance>
218+
where
219+
T: Send,
220+
{
221+
assert!(
222+
store.as_context().async_support(),
223+
"must use sync instantiation when async support is disabled"
224+
);
225+
self.instantiate_pre(component)?
226+
.instantiate_async(store)
227+
.await
228+
}
192229
}
193230

194231
impl<T> LinkerInstance<'_, T> {
195232
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
196233
LinkerInstance {
234+
engine: self.engine.clone(),
197235
strings: self.strings,
198236
map: self.map,
199237
allow_shadowing: self.allow_shadowing,
@@ -229,6 +267,36 @@ impl<T> LinkerInstance<'_, T> {
229267
self.insert(name, Definition::Func(HostFunc::from_closure(func)))
230268
}
231269

270+
/// Defines a new host-provided async function into this [`Linker`].
271+
///
272+
/// This is exactly like [`Self::func_wrap`] except it takes an async
273+
/// host function.
274+
#[cfg(feature = "async")]
275+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
276+
pub fn func_wrap_async<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
277+
where
278+
F: for<'a> Fn(
279+
StoreContextMut<'a, T>,
280+
Params,
281+
) -> Box<dyn Future<Output = Result<Return>> + Send + 'a>
282+
+ Send
283+
+ Sync
284+
+ 'static,
285+
Params: ComponentNamedList + Lift + 'static,
286+
Return: ComponentNamedList + Lower + 'static,
287+
{
288+
assert!(
289+
self.engine.config().async_support,
290+
"cannot use `func_wrap_async` without enabling async support in the config"
291+
);
292+
let ff = move |mut store: StoreContextMut<'_, T>, params: Params| -> Result<Return> {
293+
let async_cx = store.as_context_mut().0.async_cx().expect("async cx");
294+
let mut future = Pin::from(f(store.as_context_mut(), params));
295+
unsafe { async_cx.block_on(future.as_mut()) }?
296+
};
297+
self.func_wrap(name, ff)
298+
}
299+
232300
/// Define a new host-provided function using dynamic types.
233301
///
234302
/// `name` must refer to a function type import in `component`. If and when
@@ -260,6 +328,8 @@ impl<T> LinkerInstance<'_, T> {
260328
Err(anyhow!("import `{name}` not found"))
261329
}
262330

331+
// TODO: define func_new_async
332+
263333
/// Defines a [`Module`] within this instance.
264334
///
265335
/// This can be used to provide a core wasm [`Module`] as an import to a

0 commit comments

Comments
 (0)