wasmi/store/mod.rs
1mod context;
2mod error;
3mod inner;
4mod pruned;
5mod typeid;
6
7use self::pruned::PrunedStoreVTable;
8pub use self::{
9 context::{AsContext, AsContextMut, StoreContext, StoreContextMut},
10 error::{InternalStoreError, StoreError},
11 inner::{StoreInner, Stored},
12 pruned::PrunedStore,
13};
14use crate::{
15 collections::arena::Arena,
16 core::{CoreMemory, ResourceLimiterRef},
17 func::{FuncInOut, HostFuncEntity, Trampoline, TrampolineEntity, TrampolineIdx},
18 Engine,
19 Error,
20 Instance,
21 Memory,
22 ResourceLimiter,
23};
24use alloc::boxed::Box;
25use core::{
26 any::{type_name, TypeId},
27 fmt::{self, Debug},
28};
29
30/// The store that owns all data associated to Wasm modules.
31#[derive(Debug)]
32pub struct Store<T> {
33 /// All data that is not associated to `T`.
34 ///
35 /// # Note
36 ///
37 /// This is re-exported to the rest of the crate since
38 /// it is used directly by the engine's executor.
39 pub(crate) inner: StoreInner,
40 /// The inner parts of the [`Store`] that are generic over a host provided `T`.
41 typed: TypedStoreInner<T>,
42 /// The [`TypeId`] of the `T` of the `store`.
43 ///
44 /// This is used in [`PrunedStore::restore`] to check if the
45 /// restored `T` matches the original `T` of the `store`.
46 id: TypeId,
47 /// Used to restore a [`PrunedStore`] to a [`Store<T>`].
48 restore_pruned: PrunedStoreVTable,
49}
50
51impl<T> Default for Store<T>
52where
53 T: Default,
54{
55 fn default() -> Self {
56 let engine = Engine::default();
57 Self::new(&engine, T::default())
58 }
59}
60
61impl<T> Store<T> {
62 /// Creates a new store.
63 pub fn new(engine: &Engine, data: T) -> Self {
64 Self {
65 inner: StoreInner::new(engine),
66 typed: TypedStoreInner::new(data),
67 id: typeid::of::<T>(),
68 restore_pruned: PrunedStoreVTable::new::<T>(),
69 }
70 }
71}
72
73impl<T> Store<T> {
74 /// Returns the [`Engine`] that this store is associated with.
75 pub fn engine(&self) -> &Engine {
76 self.inner.engine()
77 }
78
79 /// Returns a shared reference to the user provided data owned by this [`Store`].
80 pub fn data(&self) -> &T {
81 &self.typed.data
82 }
83
84 /// Returns an exclusive reference to the user provided data owned by this [`Store`].
85 pub fn data_mut(&mut self) -> &mut T {
86 &mut self.typed.data
87 }
88
89 /// Consumes `self` and returns its user provided data.
90 pub fn into_data(self) -> T {
91 *self.typed.data
92 }
93
94 /// Installs a function into the [`Store`] that will be called with the user
95 /// data type `T` to retrieve a [`ResourceLimiter`] any time a limited,
96 /// growable resource such as a linear memory or table is grown.
97 pub fn limiter(
98 &mut self,
99 limiter: impl (FnMut(&mut T) -> &mut dyn ResourceLimiter) + Send + Sync + 'static,
100 ) {
101 self.typed.limiter = Some(ResourceLimiterQuery(Box::new(limiter)))
102 }
103
104 /// Calls the given [`HostFuncEntity`] with the `params` and `results` on `instance`.
105 ///
106 /// # Errors
107 ///
108 /// If the called host function returned an error.
109 fn call_host_func(
110 &mut self,
111 func: &HostFuncEntity,
112 instance: Option<&Instance>,
113 params_results: FuncInOut,
114 ) -> Result<(), StoreError<Error>> {
115 let trampoline = self.resolve_trampoline(func.trampoline())?.clone();
116 trampoline
117 .call(self, instance, params_results)
118 .map_err(StoreError::external)?;
119 Ok(())
120 }
121
122 /// Returns `true` if it is possible to create `additional` more instances in the [`Store`].
123 pub(crate) fn can_create_more_instances(&mut self, additional: usize) -> bool {
124 let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
125 if let Some(limiter) = limiter.as_resource_limiter() {
126 if inner.len_instances().saturating_add(additional) > limiter.instances() {
127 return false;
128 }
129 }
130 true
131 }
132
133 /// Returns `true` if it is possible to create `additional` more linear memories in the [`Store`].
134 pub(crate) fn can_create_more_memories(&mut self, additional: usize) -> bool {
135 let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
136 if let Some(limiter) = limiter.as_resource_limiter() {
137 if inner.len_memories().saturating_add(additional) > limiter.memories() {
138 return false;
139 }
140 }
141 true
142 }
143
144 /// Returns `true` if it is possible to create `additional` more tables in the [`Store`].
145 pub(crate) fn can_create_more_tables(&mut self, additional: usize) -> bool {
146 let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
147 if let Some(limiter) = limiter.as_resource_limiter() {
148 if inner.len_tables().saturating_add(additional) > limiter.tables() {
149 return false;
150 }
151 }
152 true
153 }
154
155 /// Returns a pair of [`StoreInner`] and [`ResourceLimiterRef`].
156 ///
157 /// # Note
158 ///
159 /// This methods mostly exists to satisfy certain use cases that otherwise would conflict with the borrow checker.
160 pub(crate) fn store_inner_and_resource_limiter_ref(
161 &mut self,
162 ) -> (&mut StoreInner, ResourceLimiterRef<'_>) {
163 let resource_limiter = match &mut self.typed.limiter {
164 Some(query) => {
165 let limiter = query.0(&mut self.typed.data);
166 ResourceLimiterRef::from(limiter)
167 }
168 None => ResourceLimiterRef::default(),
169 };
170 (&mut self.inner, resource_limiter)
171 }
172
173 /// Returns the remaining fuel of the [`Store`] if fuel metering is enabled.
174 ///
175 /// # Note
176 ///
177 /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel).
178 ///
179 /// # Errors
180 ///
181 /// If fuel metering is disabled.
182 pub fn get_fuel(&self) -> Result<u64, Error> {
183 self.inner.get_fuel()
184 }
185
186 /// Sets the remaining fuel of the [`Store`] to `value` if fuel metering is enabled.
187 ///
188 /// # Note
189 ///
190 /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel).
191 ///
192 /// # Errors
193 ///
194 /// If fuel metering is disabled.
195 pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> {
196 self.inner.set_fuel(fuel)
197 }
198
199 /// Allocates a new [`TrampolineEntity`] and returns a [`Trampoline`] reference to it.
200 pub(super) fn alloc_trampoline(&mut self, func: TrampolineEntity<T>) -> Trampoline {
201 let idx = self.typed.trampolines.alloc(func);
202 Trampoline::from_inner(self.inner.wrap_stored(idx))
203 }
204
205 /// Returns an exclusive reference to the [`CoreMemory`] associated to the given [`Memory`]
206 /// and an exclusive reference to the user provided host state.
207 ///
208 /// # Note
209 ///
210 /// This method exists to properly handle use cases where
211 /// otherwise the Rust borrow-checker would not accept.
212 ///
213 /// # Panics
214 ///
215 /// - If the [`Memory`] does not originate from this [`Store`].
216 /// - If the [`Memory`] cannot be resolved to its entity.
217 pub(super) fn resolve_memory_and_state_mut(
218 &mut self,
219 memory: &Memory,
220 ) -> (&mut CoreMemory, &mut T) {
221 (self.inner.resolve_memory_mut(memory), &mut self.typed.data)
222 }
223
224 /// Returns a shared reference to the associated entity of the host function trampoline.
225 ///
226 /// # Panics
227 ///
228 /// - If the [`Trampoline`] does not originate from this [`Store`].
229 /// - If the [`Trampoline`] cannot be resolved to its entity.
230 fn resolve_trampoline(
231 &self,
232 func: &Trampoline,
233 ) -> Result<&TrampolineEntity<T>, InternalStoreError> {
234 let entity_index = self.inner.unwrap_stored(func.as_inner())?;
235 let Some(trampoline) = self.typed.trampolines.get(entity_index) else {
236 return Err(InternalStoreError::not_found());
237 };
238 Ok(trampoline)
239 }
240
241 /// Sets a callback function that is executed whenever a WebAssembly
242 /// function is called from the host or a host function is called from
243 /// WebAssembly, or these functions return.
244 ///
245 /// The function is passed a `&mut T` to the underlying store, and a
246 /// [`CallHook`]. [`CallHook`] can be used to find out what kind of function
247 /// is being called or returned from.
248 ///
249 /// The callback can either return `Ok(())` or an `Err` with an
250 /// [`Error`]. If an error is returned, it is returned to the host
251 /// caller. If there are nested calls, only the most recent host caller
252 /// receives the error and it is not propagated further automatically. The
253 /// hook may be invoked again as new functions are called and returned from.
254 pub fn call_hook(
255 &mut self,
256 hook: impl FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync + 'static,
257 ) {
258 self.typed.call_hook = Some(CallHookWrapper(Box::new(hook)));
259 }
260
261 /// Executes the callback set by [`Store::call_hook`] if any has been set.
262 ///
263 /// # Note
264 ///
265 /// - Returns the value returned by the call hook.
266 /// - Returns `Ok(())` if no call hook exists.
267 #[inline]
268 pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> {
269 match self.typed.call_hook.as_mut() {
270 None => Ok(()),
271 Some(call_hook) => {
272 Self::invoke_call_hook_impl(&mut self.typed.data, call_type, call_hook)
273 }
274 }
275 }
276
277 /// Utility function to invoke the [`Store::call_hook`] that is asserted to
278 /// be available in this case.
279 ///
280 /// This is kept as a separate `#[cold]` function to help the compiler speed
281 /// up the code path without any call hooks.
282 #[cold]
283 fn invoke_call_hook_impl(
284 data: &mut T,
285 call_type: CallHook,
286 call_hook: &mut CallHookWrapper<T>,
287 ) -> Result<(), Error> {
288 call_hook.0(data, call_type)
289 }
290}
291
292/// The inner parts of the [`Store`] which are generic over a host provided `T`.
293#[derive(Debug)]
294pub struct TypedStoreInner<T> {
295 /// Stored host function trampolines.
296 trampolines: Arena<TrampolineIdx, TrampolineEntity<T>>,
297 /// User provided hook to retrieve a [`ResourceLimiter`].
298 limiter: Option<ResourceLimiterQuery<T>>,
299 /// User provided callback called when a host calls a WebAssembly function
300 /// or a WebAssembly function calls a host function, or these functions
301 /// return.
302 call_hook: Option<CallHookWrapper<T>>,
303 /// User provided host data owned by the [`Store`].
304 data: Box<T>,
305}
306
307impl<T> TypedStoreInner<T> {
308 /// Creates a new [`TypedStoreInner`] from the given data of type `T`.
309 fn new(data: T) -> Self {
310 Self {
311 trampolines: Arena::new(),
312 data: Box::new(data),
313 limiter: None,
314 call_hook: None,
315 }
316 }
317}
318
319/// A wrapper around a boxed `dyn FnMut(&mut T)` returning a `&mut dyn`
320/// [`ResourceLimiter`]; in other words a function that one can call to retrieve
321/// a [`ResourceLimiter`] from the [`Store`] object's user data type `T`.
322///
323/// This wrapper exists both to make types a little easier to read and to
324/// provide a `Debug` impl so that `#[derive(Debug)]` works on structs that
325/// contain it.
326struct ResourceLimiterQuery<T>(Box<dyn (FnMut(&mut T) -> &mut dyn ResourceLimiter) + Send + Sync>);
327impl<T> Debug for ResourceLimiterQuery<T> {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "ResourceLimiterQuery<{}>(...)", type_name::<T>())
330 }
331}
332
333/// A wrapper used to store hooks added with [`Store::call_hook`], containing a
334/// boxed `FnMut(&mut T, CallHook) -> Result<(), Error>`.
335///
336/// This wrapper exists to provide a `Debug` impl so that `#[derive(Debug)]`
337/// works for [`Store`].
338#[allow(clippy::type_complexity)]
339struct CallHookWrapper<T>(Box<dyn FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync>);
340impl<T> Debug for CallHookWrapper<T> {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "CallHook<{}>", type_name::<T>())
343 }
344}
345
346/// Argument to the callback set by [`Store::call_hook`] to indicate why the
347/// callback was invoked.
348#[derive(Debug)]
349pub enum CallHook {
350 /// Indicates that a WebAssembly function is being called from the host.
351 CallingWasm,
352 /// Indicates that a WebAssembly function called from the host is returning.
353 ReturningFromWasm,
354 /// Indicates that a host function is being called from a WebAssembly function.
355 CallingHost,
356 /// Indicates that a host function called from a WebAssembly function is returning.
357 ReturningFromHost,
358}
359
360/// The call hook behavior when calling a host function.
361#[derive(Debug, Copy, Clone)]
362pub enum CallHooks {
363 /// Invoke the host call hooks.
364 Call,
365 /// Ignore the host call hooks.
366 Ignore,
367}
368
369#[test]
370fn test_store_is_send_sync() {
371 const _: () = {
372 #[allow(clippy::extra_unused_type_parameters)]
373 fn assert_send<T: Send>() {}
374 #[allow(clippy::extra_unused_type_parameters)]
375 fn assert_sync<T: Sync>() {}
376 let _ = assert_send::<Store<()>>;
377 let _ = assert_sync::<Store<()>>;
378 };
379}