wasmtime/runtime/component/concurrent/func.rs
1use crate::component::concurrent::{self, PreparedCall, TaskGroupId, TaskId};
2use crate::component::func::LowerContext;
3use crate::component::{AsAccessor, ComponentNamedList, Func, Lift, Lower, TypedFunc, Val};
4use crate::prelude::*;
5use crate::runtime::vm::SendSyncPtr;
6use crate::{AsContextMut, StoreContextMut, ValRaw};
7use core::marker;
8use core::mem::MaybeUninit;
9use core::ptr::NonNull;
10use wasmtime_environ::component::{InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
11
12/// Returned from [`Func::start_call_concurrent`] to represent a
13/// pending-but-not-yet-resolved call into wasm.
14pub struct FuncCallConcurrent<'a, T> {
15 pub(crate) call: concurrent::StagedCall<Vec<Val>>,
16 results: &'a mut [Val],
17 _marker: marker::PhantomData<fn(T)>,
18}
19
20impl Func {
21 /// Start a concurrent call to this function.
22 ///
23 /// Concurrency is achieved by relying on the [`Accessor`] argument, which
24 /// can be obtained by calling [`StoreContextMut::run_concurrent`].
25 ///
26 /// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
27 /// exclusive access to the store until the completion of the call), calls
28 /// made using this method may run concurrently with other calls to the same
29 /// instance. In addition, the runtime will call the `post-return` function
30 /// (if any) automatically when the guest task completes.
31 ///
32 /// # Progress
33 ///
34 /// For the wasm task being created in `call_concurrent` to make progress it
35 /// must be run within the scope of [`run_concurrent`]. If there are no
36 /// active calls to [`run_concurrent`] then the wasm task will appear as
37 /// stalled. This is typically not a concern as an [`Accessor`] is bound
38 /// by default to a scope of [`run_concurrent`].
39 ///
40 /// One situation in which this can arise, for example, is that if a
41 /// [`run_concurrent`] computation finishes its async closure before all
42 /// wasm tasks have completed, then there will be no scope of
43 /// [`run_concurrent`] anywhere. In this situation the wasm tasks that have
44 /// not yet completed will not make progress until [`run_concurrent`] is
45 /// called again.
46 ///
47 /// Embedders will need to ensure that this future is `await`'d within the
48 /// scope of [`run_concurrent`] to ensure that the value can be produced
49 /// during the `await` call.
50 ///
51 /// # Cancellation
52 ///
53 /// Cancelling an async task created via `call_concurrent`, at this time, is
54 /// only possible by dropping the store that the computation runs within.
55 /// With [#11833] implemented then it will be possible to request
56 /// cancellation of a task, but that is not yet implemented. Hard-cancelling
57 /// a task will only ever be possible by dropping the entire store and it is
58 /// not possible to remove just one task from a store.
59 ///
60 /// This async function behaves more like a "spawn" than a normal Rust async
61 /// function. When this function is invoked then metadata for the function
62 /// call is recorded in the store connected to the `accessor` argument and
63 /// the wasm invocation is from then on connected to the store. If the
64 /// future created by this function is dropped it does not cancel the
65 /// in-progress execution of the wasm task. Dropping the future
66 /// relinquishes the host's ability to learn about the result of the task
67 /// but the task will still progress and invoke callbacks and such until
68 /// completion.
69 ///
70 /// This function will return an error if [`Config::concurrency_support`] is
71 /// disabled.
72 ///
73 /// [`Config::concurrency_support`]: crate::Config::concurrency_support
74 /// [`run_concurrent`]: crate::Store::run_concurrent
75 /// [#11833]: https://github.com/bytecodealliance/wasmtime/issues/11833
76 /// [`Accessor`]: crate::component::Accessor
77 ///
78 /// # Panics
79 ///
80 /// Panics if the store that the [`Accessor`] is derived from does not own
81 /// this function.
82 ///
83 /// # Example
84 ///
85 /// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
86 ///
87 /// ```
88 /// # use {
89 /// # wasmtime::{
90 /// # error::{Result},
91 /// # component::{Component, Linker, ResourceTable},
92 /// # Config, Engine, Store
93 /// # },
94 /// # };
95 /// #
96 /// # struct Ctx { table: ResourceTable }
97 /// #
98 /// # async fn foo() -> Result<()> {
99 /// # let mut config = Config::new();
100 /// # let engine = Engine::new(&config)?;
101 /// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
102 /// # let mut linker = Linker::new(&engine);
103 /// # let component = Component::new(&engine, "")?;
104 /// # let instance = linker.instantiate_async(&mut store, &component).await?;
105 /// let my_func = instance.get_func(&mut store, "my_func").unwrap();
106 /// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
107 /// my_func.call_concurrent(accessor, &[], &mut Vec::new()).await?;
108 /// Ok(())
109 /// }).await??;
110 /// # Ok(())
111 /// # }
112 /// ```
113 pub async fn call_concurrent(
114 self,
115 accessor: impl AsAccessor<Data: Send>,
116 params: &[Val],
117 results: &mut [Val],
118 ) -> Result<()> {
119 let accessor = accessor.as_accessor();
120 let call = accessor.with(|store| self.start_call_concurrent(store, params, results))?;
121 self.finish_call_concurrent(accessor, call).await
122 }
123
124 /// Performs preparatory work for invoking this function with `params`,
125 /// returning a [`FuncCallConcurrent`]
126 /// which can be passed to [`Func::finish_call_concurrent`] to resolve
127 /// the call.
128 ///
129 /// For more information see [`Func::call_concurrent`].
130 pub fn start_call_concurrent<'a, T: Send + 'static>(
131 self,
132 mut store: impl AsContextMut<Data = T>,
133 params: &'a [Val],
134 results: &'a mut [Val],
135 ) -> Result<FuncCallConcurrent<'a, T>> {
136 let result = self.do_start_call_concurrent(&mut store, params, results);
137
138 if result.is_err() {
139 store.as_context_mut().0.set_trapped();
140 }
141
142 result
143 }
144
145 fn do_start_call_concurrent<'a, T: Send + 'static>(
146 self,
147 mut store: impl AsContextMut<Data = T>,
148 params: &'a [Val],
149 results: &'a mut [Val],
150 ) -> Result<FuncCallConcurrent<'a, T>> {
151 self.check_params_results(store.as_context_mut(), params, results)?;
152 let prepared = self.prepare_call_dynamic(store.as_context_mut(), params.to_vec())?;
153 let call = concurrent::StagedCall::new(store.as_context_mut(), prepared)?;
154 Ok(FuncCallConcurrent {
155 call,
156 results,
157 _marker: marker::PhantomData,
158 })
159 }
160
161 /// Completes a call that was initiated via
162 /// [`Func::start_call_concurrent`].
163 pub async fn finish_call_concurrent<T: Send + 'static>(
164 self,
165 accessor: impl AsAccessor<Data = T>,
166 call: FuncCallConcurrent<'_, T>,
167 ) -> Result<()> {
168 // Intentionally not used today, but left here for future API
169 // compatibility with using this.
170 let _ = accessor;
171 let FuncCallConcurrent { call, results, .. } = call;
172 let run_results = call.await?;
173 assert_eq!(run_results.len(), results.len());
174 for (result, slot) in run_results.into_iter().zip(results) {
175 *slot = result;
176 }
177 Ok(())
178 }
179
180 /// Calls `concurrent::prepare_call` with monomorphized functions for
181 /// lowering the parameters and lifting the result.
182 fn prepare_call_dynamic<'a, T: Send + 'static>(
183 self,
184 mut store: StoreContextMut<'a, T>,
185 params: Vec<Val>,
186 ) -> Result<PreparedCall<Vec<Val>>> {
187 let store = store.as_context_mut();
188 let (options, _, ty, raw_options) = self.abi_info(store.0);
189 let async_ = raw_options.async_;
190 let instance = self.instance();
191
192 concurrent::prepare_call(
193 store,
194 self,
195 MAX_FLAT_PARAMS,
196 move |store, params_out| {
197 Func::with_lower_context(instance, store, options, ty, |cx, ty| {
198 Self::lower_args(cx, ¶ms, ty, params_out)
199 })
200 },
201 move |store, results| {
202 let max_flat = if async_ {
203 MAX_FLAT_PARAMS
204 } else {
205 MAX_FLAT_RESULTS
206 };
207 let results = Func::with_lift_context(instance, store, options, ty, |cx, ty| {
208 Self::lift_results(cx, ty, results, max_flat)?.collect::<Result<Vec<_>>>()
209 })?;
210 Ok(Box::new(results))
211 },
212 )
213 }
214}
215
216/// Returned from [`TypedFunc::start_call_concurrent`] to represent a
217/// pending-but-not-yet-resolved call into wasm.
218pub struct TypedFuncCallConcurrent<T, P, R> {
219 pub(crate) call: concurrent::StagedCall<R>,
220 _marker: marker::PhantomData<fn(T, P)>,
221}
222
223impl<Params, Return> TypedFunc<Params, Return>
224where
225 Params: ComponentNamedList + Lower,
226 Return: ComponentNamedList + Lift,
227{
228 pub(crate) async fn call_async_concurrent(
229 &self,
230 mut store: impl AsContextMut<Data: Send>,
231 params: Params,
232 ) -> Result<Return>
233 where
234 Return: 'static,
235 {
236 let mut store = store.as_context_mut();
237 // See `Func::call_async` for why this is checked up front.
238 store.0.check_event_loop_not_running()?;
239 let ptr = SendSyncPtr::from(NonNull::from(¶ms).cast::<u8>());
240 let prepared = self.prepare_call(store.as_context_mut(), move |cx, ty, dst| {
241 // SAFETY: The goal here is to get `Params`, a non-`'static`
242 // value, to live long enough to the lowering of the
243 // parameters. We're guaranteed that `Params` lives in the
244 // future of the outer function (we're in an `async fn`) so it'll
245 // stay alive as long as the future itself. That is distinct,
246 // for example, from the signature of `call_concurrent` below.
247 //
248 // Here a pointer to `Params` is smuggled to this location
249 // through a `SendSyncPtr<u8>` to thwart the `'static` check
250 // of rustc and the signature of `prepare_call`.
251 //
252 // Note the use of `SignalOnDrop` in the code that follows
253 // this closure, which removes this closure from its task when
254 // the containing `Future` is dropped, so long as the parameters
255 // have not yet been lowered (see `TaskId::host_future_dropped`).
256 // Since this closure is also removed from the task when the
257 // parameters are lowered, it will never be called after the
258 // containing `Future` is dropped.
259 let params = unsafe { ptr.cast::<Params>().as_ref() };
260 Self::lower_args(cx, ty, dst, params)
261 })?;
262
263 struct SignalOnDrop<'a, T: 'static> {
264 store: StoreContextMut<'a, T>,
265 task: TaskId,
266 }
267
268 impl<'a, T> Drop for SignalOnDrop<'a, T> {
269 fn drop(&mut self) {
270 self.task.host_future_dropped(self.store.0).unwrap();
271 }
272 }
273
274 // This will increment the task's refcount, and the
275 // `TaskId::host_future_dropped` function called from
276 // `SignalOnDrop::drop` wiil decrement it.
277 let task = prepared.acquire_task_id(store.0)?;
278
279 let mut wrapper = SignalOnDrop { store, task };
280
281 let result = concurrent::StagedCall::new(wrapper.store.as_context_mut(), prepared)?;
282 wrapper
283 .store
284 .as_context_mut()
285 .run_concurrent_trap_on_idle(async |_| result.await)
286 .await?
287 }
288
289 /// Start a concurrent call to this function.
290 ///
291 /// Concurrency is achieved by relying on the [`Accessor`] argument, which
292 /// can be obtained by calling [`StoreContextMut::run_concurrent`].
293 ///
294 /// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
295 /// exclusive access to the store until the completion of the call), calls
296 /// made using this method may run concurrently with other calls to the same
297 /// instance. In addition, the runtime will call the `post-return` function
298 /// (if any) automatically when the guest task completes.
299 ///
300 /// This function will return an error if [`Config::concurrency_support`] is
301 /// disabled.
302 ///
303 /// [`Config::concurrency_support`]: crate::Config::concurrency_support
304 ///
305 /// # Progress and Cancellation
306 ///
307 /// For more information about how to make progress on the wasm task or how
308 /// to cancel the wasm task see the documentation for
309 /// [`Func::call_concurrent`].
310 ///
311 /// [`Func::call_concurrent`]: crate::component::Func::call_concurrent
312 ///
313 /// # Panics
314 ///
315 /// Panics if the store that the [`Accessor`] is derived from does not own
316 /// this function.
317 ///
318 /// [`Accessor`]: crate::component::Accessor
319 ///
320 /// # Example
321 ///
322 /// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
323 ///
324 /// ```
325 /// # use {
326 /// # wasmtime::{
327 /// # error::{Result},
328 /// # component::{Component, Linker, ResourceTable},
329 /// # Config, Engine, Store
330 /// # },
331 /// # };
332 /// #
333 /// # struct Ctx { table: ResourceTable }
334 /// #
335 /// # async fn foo() -> Result<()> {
336 /// # let mut config = Config::new();
337 /// # let engine = Engine::new(&config)?;
338 /// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
339 /// # let mut linker = Linker::new(&engine);
340 /// # let component = Component::new(&engine, "")?;
341 /// # let instance = linker.instantiate_async(&mut store, &component).await?;
342 /// let my_typed_func = instance.get_typed_func::<(), ()>(&mut store, "my_typed_func")?;
343 /// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
344 /// my_typed_func.call_concurrent(accessor, ()).await?;
345 /// Ok(())
346 /// }).await??;
347 /// # Ok(())
348 /// # }
349 /// ```
350 pub async fn call_concurrent(
351 self,
352 accessor: impl AsAccessor<Data: Send>,
353 params: Params,
354 ) -> Result<Return>
355 where
356 Params: 'static,
357 Return: 'static,
358 {
359 let call = accessor
360 .as_accessor()
361 .with(|store| self.start_call_concurrent(store, params))?;
362 self.finish_call_concurrent(accessor, call).await
363 }
364
365 /// Performs preparatory work for invoking this function with `params`,
366 /// returning a [`TypedFuncCallConcurrent`]
367 /// which can be passed to [`TypedFunc::finish_call_concurrent`] to resolve
368 /// the call.
369 ///
370 /// For more information see [`TypedFunc::call_concurrent`].
371 pub fn start_call_concurrent<T>(
372 self,
373 mut store: impl AsContextMut<Data = T>,
374 params: Params,
375 ) -> Result<TypedFuncCallConcurrent<T, Params, Return>>
376 where
377 T: Send + 'static,
378 Params: 'static,
379 Return: 'static,
380 {
381 let mut store = store.as_context_mut();
382 let mut store = store.as_context_mut();
383 ensure!(
384 store.0.concurrency_support(),
385 "cannot use `call_concurrent` Config::concurrency_support disabled",
386 );
387
388 let prepared = self.prepare_call(store.as_context_mut(), move |cx, ty, dst| {
389 Self::lower_args(cx, ty, dst, ¶ms)
390 })?;
391 let call = concurrent::StagedCall::new(store, prepared)?;
392 Ok(TypedFuncCallConcurrent {
393 call,
394 _marker: marker::PhantomData,
395 })
396 }
397
398 /// Completes a call that was initiated via
399 /// [`TypedFunc::start_call_concurrent`].
400 pub async fn finish_call_concurrent<T>(
401 self,
402 accessor: impl AsAccessor<Data = T>,
403 call: TypedFuncCallConcurrent<T, Params, Return>,
404 ) -> Result<Return>
405 where
406 T: Send + 'static,
407 Params: 'static,
408 Return: 'static,
409 {
410 // Intentionally not used today, but left here for future API
411 // compatibility with using this.
412 let _ = accessor;
413 call.call.await
414 }
415
416 /// Calls `concurrent::prepare_call` with monomorphized functions for
417 /// lowering the parameters and lifting the result according to the number
418 /// of core Wasm parameters and results in the signature of the function to
419 /// be called.
420 fn prepare_call<T>(
421 self,
422 store: StoreContextMut<'_, T>,
423 lower: impl FnOnce(
424 &mut LowerContext<T>,
425 InterfaceType,
426 &mut [MaybeUninit<ValRaw>],
427 ) -> Result<()>
428 + Send
429 + Sync
430 + 'static,
431 ) -> Result<PreparedCall<Return>>
432 where
433 Return: 'static,
434 {
435 use crate::component::storage::slice_to_storage;
436 debug_assert!(store.0.concurrency_support());
437
438 let param_count = if Params::flatten_count() <= MAX_FLAT_PARAMS {
439 Params::flatten_count()
440 } else {
441 1
442 };
443 let (options, _, ty, raw_options) = self.func().abi_info(store.0);
444 let instance = self.func().instance();
445 let max_results = if raw_options.async_ {
446 MAX_FLAT_PARAMS
447 } else {
448 MAX_FLAT_RESULTS
449 };
450
451 concurrent::prepare_call(
452 store,
453 *self.func(),
454 param_count,
455 move |store, params_out| {
456 Func::with_lower_context(instance, store, options, ty, |cx, ty| {
457 lower(cx, ty, params_out)
458 })
459 },
460 move |store, results| {
461 let result = if Return::flatten_count() <= max_results {
462 Func::with_lift_context(instance, store, options, ty, |cx, ty| {
463 // SAFETY: Per the safety requirements documented for the
464 // `ComponentType` trait, `Return::Lower` must be
465 // compatible at the binary level with a `[ValRaw; N]`,
466 // where `N` is `mem::size_of::<Return::Lower>() /
467 // mem::size_of::<ValRaw>()`. And since this function
468 // is only used when `Return::flatten_count() <=
469 // MAX_FLAT_RESULTS` and `MAX_FLAT_RESULTS == 1`, `N`
470 // can only either be 0 or 1.
471 //
472 // See `ComponentInstance::exit_call` for where we use
473 // the result count passed from
474 // `wasmtime_environ::fact::trampoline`-generated code
475 // to ensure the slice has the correct length, and also
476 // `concurrent::start_call` for where we conservatively
477 // use a slice length of 1 unconditionally. Also note
478 // that, as of this writing `slice_to_storage`
479 // double-checks the slice length is sufficient.
480 let results: &Return::Lower = unsafe { slice_to_storage(results) };
481 Self::lift_stack_result(cx, ty, results)
482 })?
483 } else {
484 Func::with_lift_context(instance, store, options, ty, |cx, ty| {
485 Self::lift_heap_result(cx, ty, &results[0])
486 })?
487 };
488 Ok(Box::new(result))
489 },
490 )
491 }
492}
493
494impl<T> FuncCallConcurrent<'_, T> {
495 /// Returns the task group that this invocation corresponds to.
496 ///
497 /// This can be later correlated with calls to `TaskGroupHook` if the
498 /// `task-group-hook` feature is enabled.
499 pub fn group(&self) -> TaskGroupId {
500 self.call.group
501 }
502}
503
504impl<T, P, R> TypedFuncCallConcurrent<T, P, R> {
505 /// Returns the task group that this invocation corresponds to.
506 ///
507 /// This can be later correlated with calls to `TaskGroupHook` if the
508 /// `task-group-hook` feature is enabled.
509 pub fn group(&self) -> TaskGroupId {
510 self.call.group
511 }
512}