wasmtime/runtime/externals/
table.rs

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