wasmtime/runtime/store/
async_.rs

1#[cfg(feature = "call-hook")]
2use crate::CallHook;
3use crate::fiber::{self};
4use crate::prelude::*;
5#[cfg(feature = "gc")]
6use crate::runtime::vm::VMStore;
7use crate::store::{ResourceLimiterInner, StoreInner, StoreOpaque};
8use crate::{Store, StoreContextMut, UpdateDeadline};
9
10/// An object that can take callbacks when the runtime enters or exits hostcalls.
11#[cfg(feature = "call-hook")]
12#[async_trait::async_trait]
13pub trait CallHookHandler<T>: Send {
14    /// A callback to run when wasmtime is about to enter a host call, or when about to
15    /// exit the hostcall.
16    async fn handle_call_event(&self, t: StoreContextMut<'_, T>, ch: CallHook) -> Result<()>;
17}
18
19impl<T> Store<T> {
20    /// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync)
21    /// used to limit resource creation within this [`Store`].
22    ///
23    /// This method is an asynchronous variant of the [`Store::limiter`] method
24    /// where the embedder can block the wasm request for more resources with
25    /// host `async` execution of futures.
26    ///
27    /// By using a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
28    /// with a [`Store`], you can no longer use
29    /// [`Memory::new`](`crate::Memory::new`),
30    /// [`Memory::grow`](`crate::Memory::grow`),
31    /// [`Table::new`](`crate::Table::new`), and
32    /// [`Table::grow`](`crate::Table::grow`). Instead, you must use their
33    /// `async` variants: [`Memory::new_async`](`crate::Memory::new_async`),
34    /// [`Memory::grow_async`](`crate::Memory::grow_async`),
35    /// [`Table::new_async`](`crate::Table::new_async`), and
36    /// [`Table::grow_async`](`crate::Table::grow_async`).
37    ///
38    /// Note that this limiter is only used to limit the creation/growth of
39    /// resources in the future, this does not retroactively attempt to apply
40    /// limits to the [`Store`]. Additionally this must be used with an async
41    /// [`Store`] configured via
42    /// [`Config::async_support`](crate::Config::async_support).
43    pub fn limiter_async(
44        &mut self,
45        mut limiter: impl (FnMut(&mut T) -> &mut dyn crate::ResourceLimiterAsync)
46        + Send
47        + Sync
48        + 'static,
49    ) {
50        debug_assert!(self.inner.async_support());
51        // Apply the limits on instances, tables, and memory given by the limiter:
52        let inner = &mut self.inner;
53        let (instance_limit, table_limit, memory_limit) = {
54            let l = limiter(&mut inner.data);
55            (l.instances(), l.tables(), l.memories())
56        };
57        let innermost = &mut inner.inner;
58        innermost.instance_limit = instance_limit;
59        innermost.table_limit = table_limit;
60        innermost.memory_limit = memory_limit;
61
62        // Save the limiter accessor function:
63        inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
64    }
65
66    /// Configures an async function that runs on calls and returns between
67    /// WebAssembly and host code. For the non-async equivalent of this method,
68    /// see [`Store::call_hook`].
69    ///
70    /// The function is passed a [`CallHook`] argument, which indicates which
71    /// state transition the VM is making.
72    ///
73    /// This function's future may return a [`Trap`]. If a trap is returned
74    /// when an import was called, it is immediately raised as-if the host
75    /// import had returned the trap. If a trap is returned after wasm returns
76    /// to the host then the wasm function's result is ignored and this trap is
77    /// returned instead.
78    ///
79    /// After this function returns a trap, it may be called for subsequent
80    /// returns to host or wasm code as the trap propagates to the root call.
81    #[cfg(feature = "call-hook")]
82    pub fn call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static) {
83        self.inner.call_hook = Some(crate::store::CallHookInner::Async(Box::new(hook)));
84    }
85
86    /// Perform garbage collection asynchronously.
87    ///
88    /// Note that it is not required to actively call this function. GC will
89    /// automatically happen according to various internal heuristics. This is
90    /// provided if fine-grained control over the GC is desired.
91    ///
92    /// This method is only available when the `gc` Cargo feature is enabled.
93    #[cfg(feature = "gc")]
94    pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
95    where
96        T: Send,
97    {
98        StoreContextMut(&mut self.inner).gc_async(why).await
99    }
100
101    /// Configures epoch-deadline expiration to yield to the async
102    /// caller and the update the deadline.
103    ///
104    /// When epoch-interruption-instrumented code is executed on this
105    /// store and the epoch deadline is reached before completion,
106    /// with the store configured in this way, execution will yield
107    /// (the future will return `Pending` but re-awake itself for
108    /// later execution) and, upon resuming, the store will be
109    /// configured with an epoch deadline equal to the current epoch
110    /// plus `delta` ticks.
111    ///
112    /// This setting is intended to allow for cooperative timeslicing
113    /// of multiple CPU-bound Wasm guests in different stores, all
114    /// executing under the control of an async executor. To drive
115    /// this, stores should be configured to "yield and update"
116    /// automatically with this function, and some external driver (a
117    /// thread that wakes up periodically, or a timer
118    /// signal/interrupt) should call
119    /// [`Engine::increment_epoch()`](crate::Engine::increment_epoch).
120    ///
121    /// See documentation on
122    /// [`Config::epoch_interruption()`](crate::Config::epoch_interruption)
123    /// for an introduction to epoch-based interruption.
124    #[cfg(target_has_atomic = "64")]
125    pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
126        self.inner.epoch_deadline_async_yield_and_update(delta);
127    }
128}
129
130impl<'a, T> StoreContextMut<'a, T> {
131    /// Perform garbage collection of `ExternRef`s.
132    ///
133    /// Same as [`Store::gc`].
134    ///
135    /// This method is only available when the `gc` Cargo feature is enabled.
136    #[cfg(feature = "gc")]
137    pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
138    where
139        T: Send + 'static,
140    {
141        let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
142        store
143            .gc(limiter.as_mut(), None, why.map(|e| e.bytes_needed()))
144            .await;
145    }
146
147    /// Configures epoch-deadline expiration to yield to the async
148    /// caller and the update the deadline.
149    ///
150    /// For more information see
151    /// [`Store::epoch_deadline_async_yield_and_update`].
152    #[cfg(target_has_atomic = "64")]
153    pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
154        self.0.epoch_deadline_async_yield_and_update(delta);
155    }
156}
157
158impl<T> StoreInner<T> {
159    #[cfg(target_has_atomic = "64")]
160    fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
161        assert!(
162            self.async_support(),
163            "cannot use `epoch_deadline_async_yield_and_update` without enabling async support in the config"
164        );
165        self.epoch_deadline_behavior =
166            Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
167    }
168}
169
170#[doc(hidden)]
171impl StoreOpaque {
172    pub(crate) fn allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack> {
173        if let Some(stack) = self.async_state.last_fiber_stack().take() {
174            return Ok(stack);
175        }
176        self.engine().allocator().allocate_fiber_stack()
177    }
178
179    pub(crate) fn deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack) {
180        self.flush_fiber_stack();
181        *self.async_state.last_fiber_stack() = Some(stack);
182    }
183
184    /// Releases the last fiber stack to the underlying instance allocator, if
185    /// present.
186    pub fn flush_fiber_stack(&mut self) {
187        if let Some(stack) = self.async_state.last_fiber_stack().take() {
188            unsafe {
189                self.engine.allocator().deallocate_fiber_stack(stack);
190            }
191        }
192    }
193}
194
195impl<T> StoreContextMut<'_, T> {
196    /// Executes a synchronous computation `func` asynchronously on a new fiber.
197    pub(crate) async fn on_fiber<R: Send + Sync>(
198        &mut self,
199        func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
200    ) -> Result<R> {
201        fiber::on_fiber(self.0, |me| func(&mut StoreContextMut(me))).await
202    }
203}