wasmtime/runtime/store/async_.rs
1#[cfg(feature = "call-hook")]
2use crate::CallHook;
3use crate::fiber::{self};
4use crate::prelude::*;
5use crate::store::{ResourceLimiterInner, StoreInner, StoreOpaque, StoreToken};
6use crate::{AsContextMut, Store, StoreContextMut, UpdateDeadline};
7
8/// An object that can take callbacks when the runtime enters or exits hostcalls.
9#[cfg(feature = "call-hook")]
10#[async_trait::async_trait]
11pub trait CallHookHandler<T>: Send {
12 /// A callback to run when wasmtime is about to enter a host call, or when about to
13 /// exit the hostcall.
14 async fn handle_call_event(&self, t: StoreContextMut<'_, T>, ch: CallHook) -> Result<()>;
15}
16
17impl<T> Store<T> {
18 /// Configures the [`ResourceLimiterAsync`](crate::ResourceLimiterAsync)
19 /// used to limit resource creation within this [`Store`].
20 ///
21 /// This method is an asynchronous variant of the [`Store::limiter`] method
22 /// where the embedder can block the wasm request for more resources with
23 /// host `async` execution of futures.
24 ///
25 /// By using a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
26 /// with a [`Store`], you can no longer use
27 /// [`Memory::new`](`crate::Memory::new`),
28 /// [`Memory::grow`](`crate::Memory::grow`),
29 /// [`Table::new`](`crate::Table::new`), and
30 /// [`Table::grow`](`crate::Table::grow`). Instead, you must use their
31 /// `async` variants: [`Memory::new_async`](`crate::Memory::new_async`),
32 /// [`Memory::grow_async`](`crate::Memory::grow_async`),
33 /// [`Table::new_async`](`crate::Table::new_async`), and
34 /// [`Table::grow_async`](`crate::Table::grow_async`).
35 ///
36 /// Note that this limiter is only used to limit the creation/growth of
37 /// resources in the future, this does not retroactively attempt to apply
38 /// limits to the [`Store`]. Additionally this must be used with an async
39 /// [`Store`] configured via
40 /// [`Config::async_support`](crate::Config::async_support).
41 pub fn limiter_async(
42 &mut self,
43 mut limiter: impl (FnMut(&mut T) -> &mut dyn crate::ResourceLimiterAsync)
44 + Send
45 + Sync
46 + 'static,
47 ) {
48 debug_assert!(self.inner.async_support());
49 // Apply the limits on instances, tables, and memory given by the limiter:
50 let inner = &mut self.inner;
51 let (instance_limit, table_limit, memory_limit) = {
52 let l = limiter(&mut inner.data);
53 (l.instances(), l.tables(), l.memories())
54 };
55 let innermost = &mut inner.inner;
56 innermost.instance_limit = instance_limit;
57 innermost.table_limit = table_limit;
58 innermost.memory_limit = memory_limit;
59
60 // Save the limiter accessor function:
61 inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
62 }
63
64 /// Configures an async function that runs on calls and returns between
65 /// WebAssembly and host code. For the non-async equivalent of this method,
66 /// see [`Store::call_hook`].
67 ///
68 /// The function is passed a [`CallHook`] argument, which indicates which
69 /// state transition the VM is making.
70 ///
71 /// This function's future may return a [`Trap`]. If a trap is returned
72 /// when an import was called, it is immediately raised as-if the host
73 /// import had returned the trap. If a trap is returned after wasm returns
74 /// to the host then the wasm function's result is ignored and this trap is
75 /// returned instead.
76 ///
77 /// After this function returns a trap, it may be called for subsequent
78 /// returns to host or wasm code as the trap propagates to the root call.
79 #[cfg(feature = "call-hook")]
80 pub fn call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static) {
81 self.inner.call_hook = Some(crate::store::CallHookInner::Async(Box::new(hook)));
82 }
83
84 /// Perform garbage collection asynchronously.
85 ///
86 /// Note that it is not required to actively call this function. GC will
87 /// automatically happen according to various internal heuristics. This is
88 /// provided if fine-grained control over the GC is desired.
89 ///
90 /// This method is only available when the `gc` Cargo feature is enabled.
91 #[cfg(feature = "gc")]
92 pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
93 where
94 T: Send,
95 {
96 self.inner.gc_async(why).await
97 }
98
99 /// Configures epoch-deadline expiration to yield to the async
100 /// caller and the update the deadline.
101 ///
102 /// When epoch-interruption-instrumented code is executed on this
103 /// store and the epoch deadline is reached before completion,
104 /// with the store configured in this way, execution will yield
105 /// (the future will return `Pending` but re-awake itself for
106 /// later execution) and, upon resuming, the store will be
107 /// configured with an epoch deadline equal to the current epoch
108 /// plus `delta` ticks.
109 ///
110 /// This setting is intended to allow for cooperative timeslicing
111 /// of multiple CPU-bound Wasm guests in different stores, all
112 /// executing under the control of an async executor. To drive
113 /// this, stores should be configured to "yield and update"
114 /// automatically with this function, and some external driver (a
115 /// thread that wakes up periodically, or a timer
116 /// signal/interrupt) should call
117 /// [`Engine::increment_epoch()`](crate::Engine::increment_epoch).
118 ///
119 /// See documentation on
120 /// [`Config::epoch_interruption()`](crate::Config::epoch_interruption)
121 /// for an introduction to epoch-based interruption.
122 #[cfg(target_has_atomic = "64")]
123 pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
124 self.inner.epoch_deadline_async_yield_and_update(delta);
125 }
126}
127
128impl<'a, T> StoreContextMut<'a, T> {
129 /// Perform garbage collection of `ExternRef`s.
130 ///
131 /// Same as [`Store::gc`].
132 ///
133 /// This method is only available when the `gc` Cargo feature is enabled.
134 #[cfg(feature = "gc")]
135 pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
136 where
137 T: Send + 'static,
138 {
139 self.0.gc_async(why).await
140 }
141
142 /// Configures epoch-deadline expiration to yield to the async
143 /// caller and the update the deadline.
144 ///
145 /// For more information see
146 /// [`Store::epoch_deadline_async_yield_and_update`].
147 #[cfg(target_has_atomic = "64")]
148 pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
149 self.0.epoch_deadline_async_yield_and_update(delta);
150 }
151}
152
153impl<T> StoreInner<T> {
154 #[cfg(target_has_atomic = "64")]
155 fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
156 assert!(
157 self.async_support(),
158 "cannot use `epoch_deadline_async_yield_and_update` without enabling async support in the config"
159 );
160 self.epoch_deadline_behavior =
161 Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
162 }
163}
164
165#[doc(hidden)]
166impl StoreOpaque {
167 /// Executes a synchronous computation `func` asynchronously on a new fiber.
168 ///
169 /// This function will convert the synchronous `func` into an asynchronous
170 /// future. This is done by running `func` in a fiber on a separate native
171 /// stack which can be suspended and resumed from.
172 pub(crate) async fn on_fiber<R: Send + Sync>(
173 &mut self,
174 func: impl FnOnce(&mut Self) -> R + Send + Sync,
175 ) -> Result<R> {
176 fiber::on_fiber(self, func).await
177 }
178
179 #[cfg(feature = "gc")]
180 pub(super) async fn do_gc_async(&mut self) {
181 assert!(
182 self.async_support(),
183 "cannot use `gc_async` without enabling async support in the config",
184 );
185
186 // If the GC heap hasn't been initialized, there is nothing to collect.
187 if self.gc_store.is_none() {
188 return;
189 }
190
191 log::trace!("============ Begin Async GC ===========");
192
193 // Take the GC roots out of `self` so we can borrow it mutably but still
194 // call mutable methods on `self`.
195 let mut roots = core::mem::take(&mut self.gc_roots_list);
196
197 self.trace_roots_async(&mut roots).await;
198 self.unwrap_gc_store_mut()
199 .gc_async(unsafe { roots.iter() })
200 .await;
201
202 // Restore the GC roots for the next GC.
203 roots.clear();
204 self.gc_roots_list = roots;
205
206 log::trace!("============ End Async GC ===========");
207 }
208
209 #[inline]
210 #[cfg(not(feature = "gc"))]
211 pub async fn gc_async(&mut self) {
212 // Nothing to collect.
213 //
214 // Note that this is *not* a public method, this is just defined for the
215 // crate-internal `StoreOpaque` type. This is a convenience so that we
216 // don't have to `cfg` every call site.
217 }
218
219 #[cfg(feature = "gc")]
220 async fn trace_roots_async(&mut self, gc_roots_list: &mut crate::runtime::vm::GcRootsList) {
221 use crate::runtime::vm::Yield;
222
223 log::trace!("Begin trace GC roots");
224
225 // We shouldn't have any leftover, stale GC roots.
226 assert!(gc_roots_list.is_empty());
227
228 self.trace_wasm_stack_roots(gc_roots_list);
229 Yield::new().await;
230 self.trace_vmctx_roots(gc_roots_list);
231 Yield::new().await;
232 self.trace_user_roots(gc_roots_list);
233
234 log::trace!("End trace GC roots")
235 }
236
237 /// Yields execution to the caller on out-of-gas or epoch interruption.
238 ///
239 /// This only works on async futures and stores, and assumes that we're
240 /// executing on a fiber. This will yield execution back to the caller once.
241 pub fn async_yield_impl(&mut self) -> Result<()> {
242 // When control returns, we have a `Result<()>` passed
243 // in from the host fiber. If this finished successfully then
244 // we were resumed normally via a `poll`, so keep going. If
245 // the future was dropped while we were yielded, then we need
246 // to clean up this fiber. Do so by raising a trap which will
247 // abort all wasm and get caught on the other side to clean
248 // things up.
249 self.block_on(|_| Box::pin(crate::runtime::vm::Yield::new()))
250 }
251
252 pub(crate) fn allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack> {
253 if let Some(stack) = self.async_state.last_fiber_stack().take() {
254 return Ok(stack);
255 }
256 self.engine().allocator().allocate_fiber_stack()
257 }
258
259 pub(crate) fn deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack) {
260 self.flush_fiber_stack();
261 *self.async_state.last_fiber_stack() = Some(stack);
262 }
263
264 /// Releases the last fiber stack to the underlying instance allocator, if
265 /// present.
266 pub fn flush_fiber_stack(&mut self) {
267 if let Some(stack) = self.async_state.last_fiber_stack().take() {
268 unsafe {
269 self.engine.allocator().deallocate_fiber_stack(stack);
270 }
271 }
272 }
273}
274
275impl<T> StoreContextMut<'_, T> {
276 /// Executes a synchronous computation `func` asynchronously on a new fiber.
277 pub(crate) async fn on_fiber<R: Send + Sync>(
278 &mut self,
279 func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
280 ) -> Result<R>
281 where
282 T: Send + 'static,
283 {
284 let token = StoreToken::new(self.as_context_mut());
285 self.0
286 .on_fiber(|opaque| func(&mut token.as_context_mut(opaque.traitobj_mut())))
287 .await
288 }
289}