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