dusk_wasmtime/runtime/externals/
table.rs

1use std::ptr::NonNull;
2
3use crate::store::{AutoAssertNoGc, StoreData, StoreOpaque, Stored};
4use crate::trampoline::generate_table_export;
5use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType};
6use anyhow::{anyhow, bail, Context, Result};
7use runtime::{GcRootsList, SendSyncPtr};
8use wasmtime_environ::TypeTrace;
9use wasmtime_runtime::{self as runtime};
10
11/// A WebAssembly `table`, or an array of values.
12///
13/// Like [`Memory`][crate::Memory] a table is an indexed array of values, but
14/// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type
15/// values rather than bytes. One of the most common usages of a table is a
16/// function table for wasm modules (a `funcref` table), where each element has
17/// the `ValType::FuncRef` type.
18///
19/// A [`Table`] "belongs" to the store that it was originally created within
20/// (either via [`Table::new`] or via instantiating a
21/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the
22/// store it belongs to, and if another store is passed in by accident then
23/// methods will panic.
24#[derive(Copy, Clone, Debug)]
25#[repr(transparent)] // here for the C API
26pub struct Table(pub(super) Stored<wasmtime_runtime::ExportTable>);
27
28impl Table {
29    /// Creates a new [`Table`] with the given parameters.
30    ///
31    /// * `store` - the owner of the resulting [`Table`]
32    /// * `ty` - the type of this table, containing both the element type as
33    ///   well as the initial size and maximum size, if any.
34    /// * `init` - the initial value to fill all table entries with, if the
35    ///   table starts with an initial size.
36    ///
37    /// # Errors
38    ///
39    /// Returns an error if `init` does not match the element type of the table,
40    /// or if `init` does not belong to the `store` provided.
41    ///
42    /// # Panics
43    ///
44    /// This function will panic when used with a [`Store`](`crate::Store`)
45    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
46    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`).
47    /// When using an async resource limiter, use [`Table::new_async`]
48    /// instead.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// # use wasmtime::*;
54    /// # fn main() -> anyhow::Result<()> {
55    /// let engine = Engine::default();
56    /// let mut store = Store::new(&engine, ());
57    ///
58    /// let ty = TableType::new(RefType::FUNCREF, 2, None);
59    /// let table = Table::new(&mut store, ty, Ref::Func(None))?;
60    ///
61    /// let module = Module::new(
62    ///     &engine,
63    ///     "(module
64    ///         (table (import \"\" \"\") 2 funcref)
65    ///         (func $f (result i32)
66    ///             i32.const 10)
67    ///         (elem (i32.const 0) $f)
68    ///     )"
69    /// )?;
70    ///
71    /// let instance = Instance::new(&mut store, &module, &[table.into()])?;
72    /// // ...
73    /// # Ok(())
74    /// # }
75    /// ```
76    pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
77        Table::_new(store.as_context_mut().0, ty, init)
78    }
79
80    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
81    /// Async variant of [`Table::new`]. You must use this variant with
82    /// [`Store`](`crate::Store`)s which have a
83    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
84    ///
85    /// # Panics
86    ///
87    /// This function will panic when used with a non-async
88    /// [`Store`](`crate::Store`)
89    #[cfg(feature = "async")]
90    pub async fn new_async<T>(
91        mut store: impl AsContextMut<Data = T>,
92        ty: TableType,
93        init: Ref,
94    ) -> Result<Table>
95    where
96        T: Send,
97    {
98        let mut store = store.as_context_mut();
99        assert!(
100            store.0.async_support(),
101            "cannot use `new_async` without enabling async support on the config"
102        );
103        store
104            .on_fiber(|store| Table::_new(store.0, ty, init))
105            .await?
106    }
107
108    fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
109        let wasmtime_export = generate_table_export(store, &ty)?;
110        let init = init.into_table_element(store, ty.element())?;
111        unsafe {
112            let table = Table::from_wasmtime_table(wasmtime_export, store);
113            let wasmtime_table = table.wasmtime_table(store, std::iter::empty());
114            (*wasmtime_table).fill(store.gc_store_mut()?, 0, init, ty.minimum())?;
115            Ok(table)
116        }
117    }
118
119    /// Returns the underlying type of this table, including its element type as
120    /// well as the maximum/minimum lower bounds.
121    ///
122    /// # Panics
123    ///
124    /// Panics if `store` does not own this table.
125    pub fn ty(&self, store: impl AsContext) -> TableType {
126        self._ty(store.as_context().0)
127    }
128
129    fn _ty(&self, store: &StoreOpaque) -> TableType {
130        let ty = &store[self.0].table.table;
131        TableType::from_wasmtime_table(store.engine(), ty)
132    }
133
134    fn wasmtime_table(
135        &self,
136        store: &mut StoreOpaque,
137        lazy_init_range: impl Iterator<Item = u32>,
138    ) -> *mut runtime::Table {
139        unsafe {
140            let export = &store[self.0];
141            wasmtime_runtime::Instance::from_vmctx(export.vmctx, |handle| {
142                let idx = handle.table_index(&*export.definition);
143                handle.get_defined_table_with_lazy_init(idx, lazy_init_range)
144            })
145        }
146    }
147
148    /// Returns the table element value at `index`.
149    ///
150    /// Returns `None` if `index` is out of bounds.
151    ///
152    /// # Panics
153    ///
154    /// Panics if `store` does not own this table.
155    pub fn get(&self, mut store: impl AsContextMut, index: u32) -> Option<Ref> {
156        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
157        let table = self.wasmtime_table(&mut store, std::iter::once(index));
158        unsafe {
159            match (*table).get(store.unwrap_gc_store_mut(), index)? {
160                runtime::TableElement::FuncRef(f) => {
161                    let func = Func::from_vm_func_ref(&mut store, f);
162                    Some(func.into())
163                }
164
165                runtime::TableElement::UninitFunc => {
166                    unreachable!("lazy init above should have converted UninitFunc")
167                }
168
169                runtime::TableElement::GcRef(None) => {
170                    match self._ty(&store).element().heap_type().top(store.engine()) {
171                        HeapType::Any => Some(Ref::Any(None)),
172                        HeapType::Extern => Some(Ref::Extern(None)),
173                        HeapType::Func => {
174                            unreachable!("never have TableElement::GcRef for func tables")
175                        }
176                        ty => unreachable!("not a top type: {ty:?}"),
177                    }
178                }
179
180                #[cfg_attr(not(feature = "gc"), allow(unreachable_code, unused_variables))]
181                runtime::TableElement::GcRef(Some(x)) => {
182                    match self._ty(&store).element().heap_type().top(store.engine()) {
183                        HeapType::Any => {
184                            let x = AnyRef::from_cloned_gc_ref(&mut store, x);
185                            Some(x.into())
186                        }
187                        HeapType::Extern => {
188                            let x = ExternRef::from_cloned_gc_ref(&mut store, x);
189                            Some(x.into())
190                        }
191                        HeapType::Func => {
192                            unreachable!("never have TableElement::GcRef for func tables")
193                        }
194                        ty => unreachable!("not a top type: {ty:?}"),
195                    }
196                }
197            }
198        }
199    }
200
201    /// Writes the `val` provided into `index` within this table.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if `index` is out of bounds, if `val` does not have
206    /// the right type to be stored in this table, or if `val` belongs to a
207    /// different store.
208    ///
209    /// # Panics
210    ///
211    /// Panics if `store` does not own this table.
212    pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Ref) -> Result<()> {
213        let store = store.as_context_mut().0;
214        let ty = self.ty(&store);
215        let val = val.into_table_element(store, ty.element())?;
216        let table = self.wasmtime_table(store, std::iter::empty());
217        unsafe {
218            (*table)
219                .set(index, val)
220                .map_err(|()| anyhow!("table element index out of bounds"))
221        }
222    }
223
224    /// Returns the current size of this table.
225    ///
226    /// # Panics
227    ///
228    /// Panics if `store` does not own this table.
229    pub fn size(&self, store: impl AsContext) -> u32 {
230        self.internal_size(store.as_context().0)
231    }
232
233    pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u32 {
234        unsafe { (*store[self.0].definition).current_elements }
235    }
236
237    /// Grows the size of this table by `delta` more elements, initialization
238    /// all new elements to `init`.
239    ///
240    /// Returns the previous size of this table if successful.
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if the table cannot be grown by `delta`, for example
245    /// if it would cause the table to exceed its maximum size. Also returns an
246    /// error if `init` is not of the right type or if `init` does not belong to
247    /// `store`.
248    ///
249    /// # Panics
250    ///
251    /// Panics if `store` does not own this table.
252    ///
253    /// This function will panic when used with a [`Store`](`crate::Store`)
254    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
255    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
256    /// When using an async resource limiter, use [`Table::grow_async`]
257    /// instead.
258    pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Ref) -> Result<u32> {
259        let store = store.as_context_mut().0;
260        let ty = self.ty(&store);
261        let init = init.into_table_element(store, ty.element())?;
262        let table = self.wasmtime_table(store, std::iter::empty());
263        unsafe {
264            match (*table).grow(delta, init, store)? {
265                Some(size) => {
266                    let vm = (*table).vmtable();
267                    *store[self.0].definition = vm;
268                    Ok(size)
269                }
270                None => bail!("failed to grow table by `{}`", delta),
271            }
272        }
273    }
274
275    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
276    /// Async variant of [`Table::grow`]. Required when using a
277    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
278    ///
279    /// # Panics
280    ///
281    /// This function will panic when used with a non-async
282    /// [`Store`](`crate::Store`).
283    #[cfg(feature = "async")]
284    pub async fn grow_async<T>(
285        &self,
286        mut store: impl AsContextMut<Data = T>,
287        delta: u32,
288        init: Ref,
289    ) -> Result<u32>
290    where
291        T: Send,
292    {
293        let mut store = store.as_context_mut();
294        assert!(
295            store.0.async_support(),
296            "cannot use `grow_async` without enabling async support on the config"
297        );
298        store
299            .on_fiber(|store| self.grow(store, delta, init))
300            .await?
301    }
302
303    /// Copy `len` elements from `src_table[src_index..]` into
304    /// `dst_table[dst_index..]`.
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if the range is out of bounds of either the source or
309    /// destination tables, or if the source table's element type does not match
310    /// the destination table's element type.
311    ///
312    /// # Panics
313    ///
314    /// Panics if `store` does not own either `dst_table` or `src_table`.
315    pub fn copy(
316        mut store: impl AsContextMut,
317        dst_table: &Table,
318        dst_index: u32,
319        src_table: &Table,
320        src_index: u32,
321        len: u32,
322    ) -> Result<()> {
323        let store = store.as_context_mut().0;
324
325        let dst_ty = dst_table.ty(&store);
326        let src_ty = src_table.ty(&store);
327        src_ty
328            .element()
329            .ensure_matches(store.engine(), dst_ty.element())
330            .context(
331                "type mismatch: source table's element type does not match \
332                 destination table's element type",
333            )?;
334
335        let dst_table = dst_table.wasmtime_table(store, std::iter::empty());
336        let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX));
337        let src_table = src_table.wasmtime_table(store, src_range);
338        unsafe {
339            runtime::Table::copy(
340                store.gc_store_mut()?,
341                dst_table,
342                src_table,
343                dst_index,
344                src_index,
345                len,
346            )?;
347        }
348        Ok(())
349    }
350
351    /// Fill `table[dst..(dst + len)]` with the given value.
352    ///
353    /// # Errors
354    ///
355    /// Returns an error if
356    ///
357    /// * `val` is not of the same type as this table's
358    ///   element type,
359    ///
360    /// * the region to be filled is out of bounds, or
361    ///
362    /// * `val` comes from a different `Store` from this table.
363    ///
364    /// # Panics
365    ///
366    /// Panics if `store` does not own either `dst_table` or `src_table`.
367    pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Ref, len: u32) -> Result<()> {
368        let store = store.as_context_mut().0;
369        let ty = self.ty(&store);
370        let val = val.into_table_element(store, ty.element())?;
371
372        let table = self.wasmtime_table(store, std::iter::empty());
373        unsafe {
374            (*table).fill(store.gc_store_mut()?, dst, val, len)?;
375        }
376
377        Ok(())
378    }
379
380    pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut GcRootsList) {
381        if !self._ty(store).element().is_gc_heap_type() {
382            return;
383        }
384
385        let table = self.wasmtime_table(store, std::iter::empty());
386        for gc_ref in unsafe { (*table).gc_refs_mut() } {
387            if let Some(gc_ref) = gc_ref {
388                let gc_ref = NonNull::from(gc_ref);
389                let gc_ref = SendSyncPtr::new(gc_ref);
390                unsafe {
391                    gc_roots_list.add_root(gc_ref);
392                }
393            }
394        }
395    }
396
397    pub(crate) unsafe fn from_wasmtime_table(
398        mut wasmtime_export: wasmtime_runtime::ExportTable,
399        store: &mut StoreOpaque,
400    ) -> Table {
401        // Ensure that the table's type is engine-level canonicalized.
402        wasmtime_export
403            .table
404            .table
405            .wasm_ty
406            .canonicalize(&mut |module_index| {
407                wasmtime_runtime::Instance::from_vmctx(wasmtime_export.vmctx, |instance| {
408                    instance.engine_type_index(module_index).bits()
409                })
410            });
411
412        Table(store.store_data_mut().insert(wasmtime_export))
413    }
414
415    pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Table {
416        &data[self.0].table.table
417    }
418
419    pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMTableImport {
420        let export = &store[self.0];
421        wasmtime_runtime::VMTableImport {
422            from: export.definition,
423            vmctx: export.vmctx,
424        }
425    }
426
427    /// Get a stable hash key for this table.
428    ///
429    /// Even if the same underlying table definition is added to the
430    /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s,
431    /// this hash key will be consistent across all of these tables.
432    #[allow(dead_code)] // Not used yet, but added for consistency.
433    pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl std::hash::Hash + Eq {
434        store[self.0].definition as usize
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441    use crate::{Instance, Module, Store};
442
443    #[test]
444    fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
445        let mut store = Store::<()>::default();
446        let module = Module::new(
447            store.engine(),
448            r#"
449                (module
450                    (table (export "t") 1 1 externref)
451                )
452            "#,
453        )?;
454        let instance = Instance::new(&mut store, &module, &[])?;
455
456        // Each time we `get_table`, we call `Table::from_wasmtime` which adds
457        // a new entry to `StoreData`, so `t1` and `t2` will have different
458        // indices into `StoreData`.
459        let t1 = instance.get_table(&mut store, "t").unwrap();
460        let t2 = instance.get_table(&mut store, "t").unwrap();
461
462        // That said, they really point to the same table.
463        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
464        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
465        let e = ExternRef::new(&mut store, 42)?;
466        t1.set(&mut store, 0, e.into())?;
467        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
468        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
469
470        // And therefore their hash keys are the same.
471        assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
472
473        // But the hash keys are different from different tables.
474        let instance2 = Instance::new(&mut store, &module, &[])?;
475        let t3 = instance2.get_table(&mut store, "t").unwrap();
476        assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0));
477
478        Ok(())
479    }
480}