wasmtime/runtime/externals/
table.rs

1use crate::prelude::*;
2use crate::runtime::vm::{self as runtime};
3use crate::store::{AutoAssertNoGc, StoreData, StoreOpaque, Stored};
4use crate::trampoline::generate_table_export;
5use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType};
6use core::iter;
7use core::ptr::NonNull;
8use runtime::{GcRootsList, SendSyncPtr};
9use wasmtime_environ::TypeTrace;
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<crate::runtime::vm::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    /// Async variant of [`Table::new`]. You must use this variant with
81    /// [`Store`](`crate::Store`)s which have a
82    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
83    ///
84    /// # Panics
85    ///
86    /// This function will panic when used with a non-async
87    /// [`Store`](`crate::Store`)
88    #[cfg(feature = "async")]
89    pub async fn new_async<T>(
90        mut store: impl AsContextMut<Data = T>,
91        ty: TableType,
92        init: Ref,
93    ) -> Result<Table>
94    where
95        T: Send,
96    {
97        let mut store = store.as_context_mut();
98        assert!(
99            store.0.async_support(),
100            "cannot use `new_async` without enabling async support on the config"
101        );
102        store
103            .on_fiber(|store| Table::_new(store.0, ty, init))
104            .await?
105    }
106
107    fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
108        let wasmtime_export = generate_table_export(store, &ty)?;
109        let init = init.into_table_element(store, ty.element())?;
110        unsafe {
111            let table = Table::from_wasmtime_table(wasmtime_export, store);
112            let wasmtime_table = table.wasmtime_table(store, iter::empty());
113            (*wasmtime_table)
114                .fill(store.gc_store_mut()?, 0, init, ty.minimum())
115                .err2anyhow()?;
116            Ok(table)
117        }
118    }
119
120    /// Returns the underlying type of this table, including its element type as
121    /// well as the maximum/minimum lower bounds.
122    ///
123    /// # Panics
124    ///
125    /// Panics if `store` does not own this table.
126    pub fn ty(&self, store: impl AsContext) -> TableType {
127        self._ty(store.as_context().0)
128    }
129
130    fn _ty(&self, store: &StoreOpaque) -> TableType {
131        let ty = &store[self.0].table.table;
132        TableType::from_wasmtime_table(store.engine(), ty)
133    }
134
135    fn wasmtime_table(
136        &self,
137        store: &mut StoreOpaque,
138        lazy_init_range: impl Iterator<Item = u32>,
139    ) -> *mut runtime::Table {
140        unsafe {
141            let export = &store[self.0];
142            crate::runtime::vm::Instance::from_vmctx(export.vmctx, |handle| {
143                let idx = handle.table_index(&*export.definition);
144                handle.get_defined_table_with_lazy_init(idx, lazy_init_range)
145            })
146        }
147    }
148
149    /// Returns the table element value at `index`.
150    ///
151    /// Returns `None` if `index` is out of bounds.
152    ///
153    /// # Panics
154    ///
155    /// Panics if `store` does not own this table.
156    pub fn get(&self, mut store: impl AsContextMut, index: u32) -> Option<Ref> {
157        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
158        let table = self.wasmtime_table(&mut store, iter::once(index));
159        unsafe {
160            match (*table).get(store.unwrap_gc_store_mut(), index)? {
161                runtime::TableElement::FuncRef(f) => {
162                    let func = Func::from_vm_func_ref(&mut store, f);
163                    Some(func.into())
164                }
165
166                runtime::TableElement::UninitFunc => {
167                    unreachable!("lazy init above should have converted UninitFunc")
168                }
169
170                runtime::TableElement::GcRef(None) => {
171                    Some(Ref::null(self._ty(&store).element().heap_type()))
172                }
173
174                #[cfg_attr(not(feature = "gc"), allow(unreachable_code, unused_variables))]
175                runtime::TableElement::GcRef(Some(x)) => {
176                    match self._ty(&store).element().heap_type().top() {
177                        HeapType::Any => {
178                            let x = AnyRef::from_cloned_gc_ref(&mut store, x);
179                            Some(x.into())
180                        }
181                        HeapType::Extern => {
182                            let x = ExternRef::from_cloned_gc_ref(&mut store, x);
183                            Some(x.into())
184                        }
185                        HeapType::Func => {
186                            unreachable!("never have TableElement::GcRef for func tables")
187                        }
188                        ty => unreachable!("not a top type: {ty:?}"),
189                    }
190                }
191            }
192        }
193    }
194
195    /// Writes the `val` provided into `index` within this table.
196    ///
197    /// # Errors
198    ///
199    /// Returns an error if `index` is out of bounds, if `val` does not have
200    /// the right type to be stored in this table, or if `val` belongs to a
201    /// different store.
202    ///
203    /// # Panics
204    ///
205    /// Panics if `store` does not own this table.
206    pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Ref) -> Result<()> {
207        let store = store.as_context_mut().0;
208        let ty = self.ty(&store);
209        let val = val.into_table_element(store, ty.element())?;
210        let table = self.wasmtime_table(store, iter::empty());
211        unsafe {
212            (*table)
213                .set(index, val)
214                .map_err(|()| anyhow!("table element index out of bounds"))
215        }
216    }
217
218    /// Returns the current size of this table.
219    ///
220    /// # Panics
221    ///
222    /// Panics if `store` does not own this table.
223    pub fn size(&self, store: impl AsContext) -> u32 {
224        self.internal_size(store.as_context().0)
225    }
226
227    pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u32 {
228        unsafe { (*store[self.0].definition).current_elements }
229    }
230
231    /// Grows the size of this table by `delta` more elements, initialization
232    /// all new elements to `init`.
233    ///
234    /// Returns the previous size of this table if successful.
235    ///
236    /// # Errors
237    ///
238    /// Returns an error if the table cannot be grown by `delta`, for example
239    /// if it would cause the table to exceed its maximum size. Also returns an
240    /// error if `init` is not of the right type or if `init` does not belong to
241    /// `store`.
242    ///
243    /// # Panics
244    ///
245    /// Panics if `store` does not own this table.
246    ///
247    /// This function will panic when used with a [`Store`](`crate::Store`)
248    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
249    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
250    /// When using an async resource limiter, use [`Table::grow_async`]
251    /// instead.
252    pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Ref) -> Result<u32> {
253        let store = store.as_context_mut().0;
254        let ty = self.ty(&store);
255        let init = init.into_table_element(store, ty.element())?;
256        let table = self.wasmtime_table(store, iter::empty());
257        unsafe {
258            match (*table).grow(delta, init, store)? {
259                Some(size) => {
260                    let vm = (*table).vmtable();
261                    *store[self.0].definition = vm;
262                    Ok(size)
263                }
264                None => bail!("failed to grow table by `{}`", delta),
265            }
266        }
267    }
268
269    /// Async variant of [`Table::grow`]. Required when using a
270    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
271    ///
272    /// # Panics
273    ///
274    /// This function will panic when used with a non-async
275    /// [`Store`](`crate::Store`).
276    #[cfg(feature = "async")]
277    pub async fn grow_async<T>(
278        &self,
279        mut store: impl AsContextMut<Data = T>,
280        delta: u32,
281        init: Ref,
282    ) -> Result<u32>
283    where
284        T: Send,
285    {
286        let mut store = store.as_context_mut();
287        assert!(
288            store.0.async_support(),
289            "cannot use `grow_async` without enabling async support on the config"
290        );
291        store
292            .on_fiber(|store| self.grow(store, delta, init))
293            .await?
294    }
295
296    /// Copy `len` elements from `src_table[src_index..]` into
297    /// `dst_table[dst_index..]`.
298    ///
299    /// # Errors
300    ///
301    /// Returns an error if the range is out of bounds of either the source or
302    /// destination tables, or if the source table's element type does not match
303    /// the destination table's element type.
304    ///
305    /// # Panics
306    ///
307    /// Panics if `store` does not own either `dst_table` or `src_table`.
308    pub fn copy(
309        mut store: impl AsContextMut,
310        dst_table: &Table,
311        dst_index: u32,
312        src_table: &Table,
313        src_index: u32,
314        len: u32,
315    ) -> Result<()> {
316        let store = store.as_context_mut().0;
317
318        let dst_ty = dst_table.ty(&store);
319        let src_ty = src_table.ty(&store);
320        src_ty
321            .element()
322            .ensure_matches(store.engine(), dst_ty.element())
323            .context(
324                "type mismatch: source table's element type does not match \
325                 destination table's element type",
326            )?;
327
328        let dst_table = dst_table.wasmtime_table(store, iter::empty());
329        let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX));
330        let src_table = src_table.wasmtime_table(store, src_range);
331        unsafe {
332            runtime::Table::copy(
333                store.gc_store_mut()?,
334                dst_table,
335                src_table,
336                dst_index,
337                src_index,
338                len,
339            )
340            .err2anyhow()?;
341        }
342        Ok(())
343    }
344
345    /// Fill `table[dst..(dst + len)]` with the given value.
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if
350    ///
351    /// * `val` is not of the same type as this table's
352    ///   element type,
353    ///
354    /// * the region to be filled is out of bounds, or
355    ///
356    /// * `val` comes from a different `Store` from this table.
357    ///
358    /// # Panics
359    ///
360    /// Panics if `store` does not own either `dst_table` or `src_table`.
361    pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Ref, len: u32) -> Result<()> {
362        let store = store.as_context_mut().0;
363        let ty = self.ty(&store);
364        let val = val.into_table_element(store, ty.element())?;
365
366        let table = self.wasmtime_table(store, iter::empty());
367        unsafe {
368            (*table)
369                .fill(store.gc_store_mut()?, dst, val, len)
370                .err2anyhow()?;
371        }
372
373        Ok(())
374    }
375
376    pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut GcRootsList) {
377        if !self
378            ._ty(store)
379            .element()
380            .is_vmgcref_type_and_points_to_object()
381        {
382            return;
383        }
384
385        let table = self.wasmtime_table(store, 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, "Wasm table element");
392                }
393            }
394        }
395    }
396
397    pub(crate) unsafe fn from_wasmtime_table(
398        mut wasmtime_export: crate::runtime::vm::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_for_runtime_usage(&mut |module_index| {
407                crate::runtime::vm::Instance::from_vmctx(wasmtime_export.vmctx, |instance| {
408                    instance.engine_type_index(module_index)
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) -> crate::runtime::vm::VMTableImport {
420        let export = &store[self.0];
421        crate::runtime::vm::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 core::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}