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::{Asyncness, 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: crate::StoreHookState<'_, 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`].
41 ///
42 /// After configuring this method it's required that synchronous APIs in
43 /// Wasmtime are no longer used, such as [`Func::call`](crate::Func::call).
44 /// Instead APIs such as [`Func::call_async`](crate::Func::call_async) must
45 /// be used instead.
46 pub fn limiter_async(
47 &mut self,
48 mut limiter: impl (FnMut(&mut T) -> &mut dyn crate::ResourceLimiterAsync)
49 + Send
50 + Sync
51 + 'static,
52 ) {
53 // Apply the limits on instances, tables, and memory given by the limiter:
54 let inner = &mut self.inner;
55 let (instance_limit, table_limit, memory_limit) = {
56 let l = limiter(inner.data_mut());
57 (l.instances(), l.tables(), l.memories())
58 };
59 let innermost = &mut inner.inner;
60 innermost.instance_limit = instance_limit;
61 innermost.table_limit = table_limit;
62 innermost.memory_limit = memory_limit;
63
64 // Save the limiter accessor function:
65 inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
66 inner.set_async_required(Asyncness::Yes);
67 }
68
69 /// Configures an async function that runs on calls and returns between
70 /// WebAssembly and host code. For the non-async equivalent of this method,
71 /// see [`Store::call_hook`].
72 ///
73 /// The function is passed a [`CallHook`] argument, which indicates which
74 /// state transition the VM is making.
75 ///
76 /// This function's future may return a [`Trap`]. If a trap is returned
77 /// when an import was called, it is immediately raised as-if the host
78 /// import had returned the trap. If a trap is returned after wasm returns
79 /// to the host then the wasm function's result is ignored and this trap is
80 /// returned instead.
81 ///
82 /// After this function returns a trap, it may be called for subsequent
83 /// returns to host or wasm code as the trap propagates to the root call.
84 ///
85 /// [`Trap`]: crate::Trap
86 #[cfg(feature = "call-hook")]
87 pub fn call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static) {
88 self.inner.call_hook = Some(crate::store::CallHookInner::Async(Box::new(hook)));
89 self.inner.set_async_required(Asyncness::Yes);
90 }
91
92 /// Perform garbage collection asynchronously.
93 ///
94 /// Note that it is not required to actively call this function. GC will
95 /// automatically happen according to various internal heuristics. This is
96 /// provided if fine-grained control over the GC is desired.
97 ///
98 /// This method is only available when the `gc` Cargo feature is enabled.
99 #[cfg(feature = "gc")]
100 pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
101 where
102 T: Send,
103 {
104 StoreContextMut(&mut self.inner).gc_async(why).await
105 }
106
107 /// Manually grow the GC heap by at least `bytes` bytes.
108 ///
109 /// For more information, see the documentation of [`Store::gc_heap_grow`].
110 #[cfg(feature = "gc")]
111 pub async fn gc_heap_grow_async(&mut self, bytes: u64) -> Result<()> {
112 StoreContextMut(&mut self.inner)
113 .gc_heap_grow_async(bytes)
114 .await
115 }
116
117 /// Configures epoch-deadline expiration to yield to the async
118 /// caller and the update the deadline.
119 ///
120 /// When epoch-interruption-instrumented code is executed on this
121 /// store and the epoch deadline is reached before completion,
122 /// with the store configured in this way, execution will yield
123 /// (the future will return `Pending` but re-awake itself for
124 /// later execution) and, upon resuming, the store will be
125 /// configured with an epoch deadline equal to the current epoch
126 /// plus `delta` ticks.
127 ///
128 /// This setting is intended to allow for cooperative timeslicing
129 /// of multiple CPU-bound Wasm guests in different stores, all
130 /// executing under the control of an async executor. To drive
131 /// this, stores should be configured to "yield and update"
132 /// automatically with this function, and some external driver (a
133 /// thread that wakes up periodically, or a timer
134 /// signal/interrupt) should call
135 /// [`Engine::increment_epoch()`](crate::Engine::increment_epoch).
136 ///
137 /// See documentation on
138 /// [`Config::epoch_interruption()`](crate::Config::epoch_interruption)
139 /// for an introduction to epoch-based interruption.
140 #[cfg(target_has_atomic = "64")]
141 pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
142 self.inner.epoch_deadline_async_yield_and_update(delta);
143 }
144}
145
146impl<'a, T> StoreContextMut<'a, T> {
147 /// Perform garbage collection of `ExternRef`s.
148 ///
149 /// Same as [`Store::gc`].
150 ///
151 /// This method is only available when the `gc` Cargo feature is enabled.
152 #[cfg(feature = "gc")]
153 pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
154 where
155 T: Send + 'static,
156 {
157 let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
158 store
159 .gc(
160 limiter.as_mut(),
161 None,
162 why.map(|e| e.bytes_needed()),
163 crate::store::Asyncness::Yes,
164 )
165 .await?;
166 Ok(())
167 }
168
169 /// Manually grow the GC heap by at least `bytes` bytes.
170 ///
171 /// For more information, see the documentation of [`Store::gc_heap_grow`].
172 #[cfg(feature = "gc")]
173 pub async fn gc_heap_grow_async(&mut self, bytes: u64) -> Result<()> {
174 let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
175 store
176 .grow_gc_heap(limiter.as_mut(), bytes, crate::store::Asyncness::Yes)
177 .await
178 }
179
180 /// Configures epoch-deadline expiration to yield to the async
181 /// caller and the update the deadline.
182 ///
183 /// For more information see
184 /// [`Store::epoch_deadline_async_yield_and_update`].
185 #[cfg(target_has_atomic = "64")]
186 pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
187 self.0.epoch_deadline_async_yield_and_update(delta);
188 }
189}
190
191impl<T> StoreInner<T> {
192 #[cfg(target_has_atomic = "64")]
193 fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
194 // All future entrypoints must be async to handle the case that an epoch
195 // changes and a yield is required.
196 self.set_async_required(Asyncness::Yes);
197
198 self.epoch_deadline_behavior =
199 Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
200 }
201}
202
203#[doc(hidden)]
204impl StoreOpaque {
205 pub(crate) fn allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack> {
206 if let Some(stack) = self.async_state.last_fiber_stack().take() {
207 return Ok(stack);
208 }
209 self.engine().allocator().allocate_fiber_stack()
210 }
211
212 pub(crate) fn deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack) {
213 self.flush_fiber_stack();
214 *self.async_state.last_fiber_stack() = Some(stack);
215 }
216
217 /// Releases the last fiber stack to the underlying instance allocator, if
218 /// present.
219 pub fn flush_fiber_stack(&mut self) {
220 if let Some(stack) = self.async_state.last_fiber_stack().take() {
221 unsafe {
222 self.engine.allocator().deallocate_fiber_stack(stack);
223 }
224 }
225 }
226}
227
228impl<T> StoreContextMut<'_, T> {
229 /// Executes a synchronous computation `func` asynchronously on a new fiber.
230 pub(crate) async fn on_fiber<R: Send + Sync>(
231 &mut self,
232 func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
233 ) -> Result<R> {
234 fiber::on_fiber(self.0, |me| func(&mut StoreContextMut(me))).await
235 }
236}