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