diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 4f6501056b4a..0f9ae3809787 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -54,7 +54,8 @@ use self::error_contexts::GlobalErrorContextRefCount; use crate::bail_bug; use crate::component::func::{Func, call_post_return}; use crate::component::{ - HasData, HasSelf, Instance, Resource, ResourceTable, ResourceTableError, RuntimeInstance, + HasData, HasSelf, Instance, InstancePre, Resource, ResourceTable, ResourceTableError, + RuntimeInstance, }; use crate::fiber::{self, StoreFiber, StoreFiberYield}; use crate::hash_set::HashSet; @@ -470,6 +471,34 @@ where }) } + /// Instantiates a component into the current concurrent store. + /// + /// This is the [`Accessor`] equivalent of [`InstancePre::instantiate_async`]. + /// It is useful for embedders that need to lazily instantiate components + /// while running inside [`StoreContextMut::run_concurrent`], where the + /// current store is available through this accessor rather than as a + /// separate [`StoreContextMut`]. + /// + /// # Panics + /// + /// This method has the same contextual requirements as [`Accessor::with`]: + /// it must only be called while this accessor's store is available through + /// Wasmtime's concurrent runtime. + pub async fn instantiate_async(&self, pre: &InstancePre) -> Result + where + T: Send + 'static, + { + let store = tls::get(|vmstore| { + let store = self.token.as_context_mut(vmstore); + // The concurrent runtime makes the store available through TLS + // for every poll of the host future. `InstancePre::instantiate_async` + // needs to hold the store context across await points, but Rust + // cannot express that TLS-scoped lifetime directly. + unsafe { mem::transmute::, StoreContextMut<'static, T>>(store) } + }); + pre.instantiate_async(store).await + } + /// Returns the getter this accessor is using to project from `T` into /// `D::Data`. pub fn getter(&self) -> fn(&mut T) -> D::Data<'_> { diff --git a/tests/all/component_model/async.rs b/tests/all/component_model/async.rs index 7df03ca8df8a..83ddccb1bc56 100644 --- a/tests/all/component_model/async.rs +++ b/tests/all/component_model/async.rs @@ -46,6 +46,68 @@ async fn smoke() -> Result<()> { Ok(()) } +#[tokio::test] +#[cfg_attr(miri, ignore)] +async fn accessor_can_instantiate_pre() -> Result<()> { + let component = r#" + (component + (import "add-one" (func $add_one (param "x" u32) (result u32))) + (core func $add_one (canon lower (func $add_one))) + + (core module $m + (import "" "add-one" (func $add_one (param i32) (result i32))) + (func (export "answer") (param i32) (result i32) + local.get 0 + call $add_one + call $add_one + i32.const 40 + i32.add) + ) + + (core instance $i (instantiate $m + (with "" (instance + (export "add-one" (func $add_one)) + )) + )) + (func (export "answer") (param "x" u32) (result u32) + (canon lift (core func $i "answer"))) + ) + "#; + + let engine = super::async_engine(); + let component = Component::new(&engine, component)?; + let mut linker = Linker::new(&engine); + linker + .root() + .func_wrap("add-one", |_: StoreContextMut<'_, ()>, (x,): (u32,)| { + Ok((x + 1,)) + })?; + let pre = linker.instantiate_pre(&component)?; + let mut store = Store::new(&engine, ()); + + let (answer_a, answer_b) = store + .run_concurrent(async |accessor| -> Result<_> { + let instance_a = accessor.instantiate_async(&pre).await?; + let instance_b = accessor.instantiate_async(&pre).await?; + let (answer_a, answer_b) = accessor.with(|mut access| { + let answer_a = + instance_a.get_typed_func::<(u32,), (u32,)>(&mut access, "answer")?; + let answer_b = + instance_b.get_typed_func::<(u32,), (u32,)>(&mut access, "answer")?; + Ok::<_, wasmtime::Error>((answer_a, answer_b)) + })?; + let (answer_a,) = answer_a.call_concurrent(accessor, (0,)).await?; + let (answer_b,) = answer_b.call_concurrent(accessor, (10,)).await?; + Ok((answer_a, answer_b)) + }) + .await??; + + assert_eq!(answer_a, 42); + assert_eq!(answer_b, 52); + + Ok(()) +} + /// Handle an import function, created using component::Linker::func_wrap_async. #[tokio::test] #[cfg_attr(miri, ignore)]