1 #[cfg(feature = "call-hook")]
2 use crate::CallHook;
3 use crate::fiber::{self};
4 use crate::prelude::*;
5 #[cfg(feature = "gc")]
6 use crate::runtime::vm::VMStore;
7 use crate::store::{Asyncness, ResourceLimiterInner, StoreInner, StoreOpaque};
8 use 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]
13 pub 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.
handle_call_event(&self, t: StoreContextMut<'_, T>, ch: CallHook) -> Result<()>16     async fn handle_call_event(&self, t: StoreContextMut<'_, T>, ch: CallHook) -> Result<()>;
17 }
18 
19 impl<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.
limiter_async( &mut self, mut limiter: impl (FnMut(&mut T) -> &mut dyn crate::ResourceLimiterAsync) + Send + Sync + 'static, )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")]
call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static)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")]
gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) where T: Send,100     pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
101     where
102         T: Send,
103     {
104         StoreContextMut(&mut self.inner).gc_async(why).await
105     }
106 
107     /// Configures epoch-deadline expiration to yield to the async
108     /// caller and the update the deadline.
109     ///
110     /// When epoch-interruption-instrumented code is executed on this
111     /// store and the epoch deadline is reached before completion,
112     /// with the store configured in this way, execution will yield
113     /// (the future will return `Pending` but re-awake itself for
114     /// later execution) and, upon resuming, the store will be
115     /// configured with an epoch deadline equal to the current epoch
116     /// plus `delta` ticks.
117     ///
118     /// This setting is intended to allow for cooperative timeslicing
119     /// of multiple CPU-bound Wasm guests in different stores, all
120     /// executing under the control of an async executor. To drive
121     /// this, stores should be configured to "yield and update"
122     /// automatically with this function, and some external driver (a
123     /// thread that wakes up periodically, or a timer
124     /// signal/interrupt) should call
125     /// [`Engine::increment_epoch()`](crate::Engine::increment_epoch).
126     ///
127     /// See documentation on
128     /// [`Config::epoch_interruption()`](crate::Config::epoch_interruption)
129     /// for an introduction to epoch-based interruption.
130     #[cfg(target_has_atomic = "64")]
epoch_deadline_async_yield_and_update(&mut self, delta: u64)131     pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
132         self.inner.epoch_deadline_async_yield_and_update(delta);
133     }
134 }
135 
136 impl<'a, T> StoreContextMut<'a, T> {
137     /// Perform garbage collection of `ExternRef`s.
138     ///
139     /// Same as [`Store::gc`].
140     ///
141     /// This method is only available when the `gc` Cargo feature is enabled.
142     #[cfg(feature = "gc")]
gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) where T: Send + 'static,143     pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
144     where
145         T: Send + 'static,
146     {
147         let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
148         store
149             .gc(
150                 limiter.as_mut(),
151                 None,
152                 why.map(|e| e.bytes_needed()),
153                 crate::store::Asyncness::Yes,
154             )
155             .await;
156     }
157 
158     /// Configures epoch-deadline expiration to yield to the async
159     /// caller and the update the deadline.
160     ///
161     /// For more information see
162     /// [`Store::epoch_deadline_async_yield_and_update`].
163     #[cfg(target_has_atomic = "64")]
epoch_deadline_async_yield_and_update(&mut self, delta: u64)164     pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
165         self.0.epoch_deadline_async_yield_and_update(delta);
166     }
167 }
168 
169 impl<T> StoreInner<T> {
170     #[cfg(target_has_atomic = "64")]
epoch_deadline_async_yield_and_update(&mut self, delta: u64)171     fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
172         // All future entrypoints must be async to handle the case that an epoch
173         // changes and a yield is required.
174         self.set_async_required(Asyncness::Yes);
175 
176         self.epoch_deadline_behavior =
177             Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
178     }
179 }
180 
181 #[doc(hidden)]
182 impl StoreOpaque {
allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack>183     pub(crate) fn allocate_fiber_stack(&mut self) -> Result<wasmtime_fiber::FiberStack> {
184         if let Some(stack) = self.async_state.last_fiber_stack().take() {
185             return Ok(stack);
186         }
187         self.engine().allocator().allocate_fiber_stack()
188     }
189 
deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack)190     pub(crate) fn deallocate_fiber_stack(&mut self, stack: wasmtime_fiber::FiberStack) {
191         self.flush_fiber_stack();
192         *self.async_state.last_fiber_stack() = Some(stack);
193     }
194 
195     /// Releases the last fiber stack to the underlying instance allocator, if
196     /// present.
flush_fiber_stack(&mut self)197     pub fn flush_fiber_stack(&mut self) {
198         if let Some(stack) = self.async_state.last_fiber_stack().take() {
199             unsafe {
200                 self.engine.allocator().deallocate_fiber_stack(stack);
201             }
202         }
203     }
204 }
205 
206 impl<T> StoreContextMut<'_, T> {
207     /// Executes a synchronous computation `func` asynchronously on a new fiber.
on_fiber<R: Send + Sync>( &mut self, func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync, ) -> Result<R>208     pub(crate) async fn on_fiber<R: Send + Sync>(
209         &mut self,
210         func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
211     ) -> Result<R> {
212         fiber::on_fiber(self.0, |me| func(&mut StoreContextMut(me))).await
213     }
214 }
215