Skip to main content

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}