wasmtime/runtime/externals/
table.rs

1use crate::prelude::*;
2use crate::runtime::vm::{self as runtime, GcStore};
3use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque};
4use crate::trampoline::generate_table_export;
5use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType, Trap};
6use core::iter;
7use wasmtime_environ::DefinedTableIndex;
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 table = generate_table_export(store, &ty)?;
119        let init = init.into_table_element(store, ty.element())?;
120        let (wasmtime_table, gc_store) = table.wasmtime_table(store, iter::empty());
121        wasmtime_table.fill(gc_store, 0, init, ty.minimum())?;
122        Ok(table)
123    }
124
125    /// Returns the underlying type of this table, including its element type as
126    /// well as the maximum/minimum lower bounds.
127    ///
128    /// # Panics
129    ///
130    /// Panics if `store` does not own this table.
131    pub fn ty(&self, store: impl AsContext) -> TableType {
132        self._ty(store.as_context().0)
133    }
134
135    fn _ty(&self, store: &StoreOpaque) -> TableType {
136        TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store))
137    }
138
139    /// Returns the `runtime::Table` within `store` as well as the optional
140    /// `GcStore` in use within `store`.
141    ///
142    /// # Panics
143    ///
144    /// Panics if this table does not belong to `store`.
145    fn wasmtime_table<'a>(
146        &self,
147        store: &'a mut StoreOpaque,
148        lazy_init_range: impl Iterator<Item = u64>,
149    ) -> (&'a mut runtime::Table, Option<&'a mut GcStore>) {
150        self.instance.assert_belongs_to(store.id());
151        let (store, instance) = store.optional_gc_store_and_instance_mut(self.instance.instance());
152
153        (
154            instance.get_defined_table_with_lazy_init(self.index, lazy_init_range),
155            store,
156        )
157    }
158
159    /// Returns the table element value at `index`.
160    ///
161    /// Returns `None` if `index` is out of bounds.
162    ///
163    /// # Panics
164    ///
165    /// Panics if `store` does not own this table.
166    pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> {
167        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
168        let (table, gc_store) = self.wasmtime_table(&mut store, iter::once(index));
169        match table.get(gc_store, index)? {
170            runtime::TableElement::FuncRef(f) => {
171                // SAFETY: the `table` belongs to `store`, so elements within
172                // the table must also belong to the store.
173                let func = unsafe { f.map(|f| Func::from_vm_func_ref(store.id(), f)) };
174                Some(func.into())
175            }
176
177            runtime::TableElement::UninitFunc => {
178                unreachable!("lazy init above should have converted UninitFunc")
179            }
180
181            runtime::TableElement::GcRef(None) => {
182                Some(Ref::null(self._ty(&store).element().heap_type()))
183            }
184
185            #[cfg_attr(
186                not(feature = "gc"),
187                expect(unreachable_code, unused_variables, reason = "definitions cfg'd off")
188            )]
189            runtime::TableElement::GcRef(Some(x)) => {
190                match self._ty(&store).element().heap_type().top() {
191                    HeapType::Any => {
192                        let x = AnyRef::from_cloned_gc_ref(&mut store, x);
193                        Some(x.into())
194                    }
195                    HeapType::Extern => {
196                        let x = ExternRef::from_cloned_gc_ref(&mut store, x);
197                        Some(x.into())
198                    }
199                    HeapType::Func => {
200                        unreachable!("never have TableElement::GcRef for func tables")
201                    }
202                    ty => unreachable!("not a top type: {ty:?}"),
203                }
204            }
205
206            runtime::TableElement::ContRef(_c) => {
207                // TODO(#10248) Required to support stack switching in the embedder API.
208                unimplemented!()
209            }
210        }
211    }
212
213    /// Writes the `val` provided into `index` within this table.
214    ///
215    /// # Errors
216    ///
217    /// Returns an error if `index` is out of bounds, if `val` does not have
218    /// the right type to be stored in this table, or if `val` belongs to a
219    /// different store.
220    ///
221    /// # Panics
222    ///
223    /// Panics if `store` does not own this table.
224    pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
225        let store = store.as_context_mut().0;
226        let ty = self.ty(&store);
227        let val = val.into_table_element(store, ty.element())?;
228        let (table, _) = self.wasmtime_table(store, iter::empty());
229        table
230            .set(index, val)
231            .map_err(|()| anyhow!("table element index out of bounds"))
232    }
233
234    /// Returns the current size of this table.
235    ///
236    /// # Panics
237    ///
238    /// Panics if `store` does not own this table.
239    pub fn size(&self, store: impl AsContext) -> u64 {
240        self.internal_size(store.as_context().0)
241    }
242
243    pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u64 {
244        // unwrap here should be ok because the runtime should always guarantee
245        // that we can fit the number of elements in a 64-bit integer.
246        u64::try_from(store[self.instance].table(self.index).current_elements).unwrap()
247    }
248
249    /// Grows the size of this table by `delta` more elements, initialization
250    /// all new elements to `init`.
251    ///
252    /// Returns the previous size of this table if successful.
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if the table cannot be grown by `delta`, for example
257    /// if it would cause the table to exceed its maximum size. Also returns an
258    /// error if `init` is not of the right type or if `init` does not belong to
259    /// `store`.
260    ///
261    /// # Panics
262    ///
263    /// Panics if `store` does not own this table.
264    ///
265    /// This function will panic when used with a [`Store`](`crate::Store`)
266    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
267    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
268    /// When using an async resource limiter, use [`Table::grow_async`]
269    /// instead.
270    pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
271        let store = store.as_context_mut().0;
272        let ty = self.ty(&store);
273        let init = init.into_table_element(store, ty.element())?;
274        let (table, _gc_store) = self.wasmtime_table(store, iter::empty());
275        // FIXME(#11179) shouldn't need to subvert the borrow checker
276        let table: *mut _ = table;
277        unsafe {
278            match (*table).grow(delta, init, store)? {
279                Some(size) => {
280                    let vm = (*table).vmtable();
281                    store[self.instance].table_ptr(self.index).write(vm);
282                    // unwrap here should be ok because the runtime should always guarantee
283                    // that we can fit the table size in a 64-bit integer.
284                    Ok(u64::try_from(size).unwrap())
285                }
286                None => bail!("failed to grow table by `{}`", delta),
287            }
288        }
289    }
290
291    /// Async variant of [`Table::grow`]. Required when using a
292    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
293    ///
294    /// # Panics
295    ///
296    /// This function will panic when used with a non-async
297    /// [`Store`](`crate::Store`).
298    #[cfg(feature = "async")]
299    pub async fn grow_async(
300        &self,
301        mut store: impl AsContextMut<Data: Send>,
302        delta: u64,
303        init: Ref,
304    ) -> Result<u64> {
305        let mut store = store.as_context_mut();
306        assert!(
307            store.0.async_support(),
308            "cannot use `grow_async` without enabling async support on the config"
309        );
310        store
311            .on_fiber(|store| self.grow(store, delta, init))
312            .await?
313    }
314
315    /// Copy `len` elements from `src_table[src_index..]` into
316    /// `dst_table[dst_index..]`.
317    ///
318    /// # Errors
319    ///
320    /// Returns an error if the range is out of bounds of either the source or
321    /// destination tables, or if the source table's element type does not match
322    /// the destination table's element type.
323    ///
324    /// # Panics
325    ///
326    /// Panics if `store` does not own either `dst_table` or `src_table`.
327    pub fn copy(
328        mut store: impl AsContextMut,
329        dst_table: &Table,
330        dst_index: u64,
331        src_table: &Table,
332        src_index: u64,
333        len: u64,
334    ) -> Result<()> {
335        let store = store.as_context_mut().0;
336
337        let dst_ty = dst_table.ty(&store);
338        let src_ty = src_table.ty(&store);
339        src_ty
340            .element()
341            .ensure_matches(store.engine(), dst_ty.element())
342            .context(
343                "type mismatch: source table's element type does not match \
344                 destination table's element type",
345            )?;
346
347        // SAFETY: the the two tables have the same type, as type-checked above.
348        unsafe {
349            Self::copy_raw(store, dst_table, dst_index, src_table, src_index, len)?;
350        }
351        Ok(())
352    }
353
354    /// Copies the elements of `src_table` to `dst_table`.
355    ///
356    /// # Panics
357    ///
358    /// Panics if the either table doesn't belong to `store`.
359    ///
360    /// # Safety
361    ///
362    /// Requires that the two tables have previously been type-checked to have
363    /// the same type.
364    pub(crate) unsafe fn copy_raw(
365        store: &mut StoreOpaque,
366        dst_table: &Table,
367        dst_index: u64,
368        src_table: &Table,
369        src_index: u64,
370        len: u64,
371    ) -> Result<(), Trap> {
372        // Handle lazy initialization of the source table first before doing
373        // anything else.
374        let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX));
375        src_table.wasmtime_table(store, src_range);
376
377        // validate `dst_table` belongs to `store`.
378        dst_table.wasmtime_table(store, iter::empty());
379
380        // Figure out which of the three cases we're in:
381        //
382        // 1. Cross-instance table copy.
383        // 2. Intra-instance table copy.
384        // 3. Intra-table copy.
385        //
386        // We handle each of them slightly differently.
387        let src_instance = src_table.instance.instance();
388        let dst_instance = dst_table.instance.instance();
389        match (
390            src_instance == dst_instance,
391            src_table.index == dst_table.index,
392        ) {
393            // 1. Cross-instance table copy: split the mutable store borrow into
394            // two mutable instance borrows, get each instance's defined table,
395            // and do the copy.
396            (false, _) => {
397                // SAFETY: accessing two instances mutably at the same time
398                // requires only accessing defined entities on each instance
399                // which is done below with `get_defined_*` methods.
400                let (gc_store, [src_instance, dst_instance]) = unsafe {
401                    store.optional_gc_store_and_instances_mut([src_instance, dst_instance])
402                };
403                src_instance.get_defined_table(src_table.index).copy_to(
404                    dst_instance.get_defined_table(dst_table.index),
405                    gc_store,
406                    dst_index,
407                    src_index,
408                    len,
409                )
410            }
411
412            // 2. Intra-instance, distinct-tables copy: split the mutable
413            // instance borrow into two distinct mutable table borrows and do
414            // the copy.
415            (true, false) => {
416                let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
417                let [(_, src_table), (_, dst_table)] = instance
418                    .tables_mut()
419                    .get_disjoint_mut([src_table.index, dst_table.index])
420                    .unwrap();
421                src_table.copy_to(dst_table, gc_store, dst_index, src_index, len)
422            }
423
424            // 3. Intra-table copy: get the table and copy within it!
425            (true, true) => {
426                let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
427                instance
428                    .get_defined_table(src_table.index)
429                    .copy_within(gc_store, dst_index, src_index, len)
430            }
431        }
432    }
433
434    /// Fill `table[dst..(dst + len)]` with the given value.
435    ///
436    /// # Errors
437    ///
438    /// Returns an error if
439    ///
440    /// * `val` is not of the same type as this table's
441    ///   element type,
442    ///
443    /// * the region to be filled is out of bounds, or
444    ///
445    /// * `val` comes from a different `Store` from this table.
446    ///
447    /// # Panics
448    ///
449    /// Panics if `store` does not own either `dst_table` or `src_table`.
450    pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> {
451        let store = store.as_context_mut().0;
452        let ty = self.ty(&store);
453        let val = val.into_table_element(store, ty.element())?;
454
455        let (table, gc_store) = self.wasmtime_table(store, iter::empty());
456        table.fill(gc_store, dst, val, len)?;
457
458        Ok(())
459    }
460
461    #[cfg(feature = "gc")]
462    pub(crate) fn trace_roots(
463        &self,
464        store: &mut StoreOpaque,
465        gc_roots_list: &mut crate::runtime::vm::GcRootsList,
466    ) {
467        if !self
468            ._ty(store)
469            .element()
470            .is_vmgcref_type_and_points_to_object()
471        {
472            return;
473        }
474
475        let (table, _) = self.wasmtime_table(store, iter::empty());
476        for gc_ref in table.gc_refs_mut() {
477            if let Some(gc_ref) = gc_ref {
478                unsafe {
479                    gc_roots_list.add_root(gc_ref.into(), "Wasm table element");
480                }
481            }
482        }
483    }
484
485    pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table {
486        Table { instance, index }
487    }
488
489    pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table {
490        let module = store[self.instance].env_module();
491        let index = module.table_index(self.index);
492        &module.tables[index]
493    }
494
495    pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport {
496        let instance = &store[self.instance];
497        crate::runtime::vm::VMTableImport {
498            from: instance.table_ptr(self.index).into(),
499            vmctx: instance.vmctx().into(),
500            index: self.index,
501        }
502    }
503
504    pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
505        store.id() == self.instance.store_id()
506    }
507
508    /// Get a stable hash key for this table.
509    ///
510    /// Even if the same underlying table definition is added to the
511    /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s,
512    /// this hash key will be consistent across all of these tables.
513    #[cfg_attr(
514        not(test),
515        expect(dead_code, reason = "Not used yet, but added for consistency")
516    )]
517    pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> {
518        store[self.instance].table_ptr(self.index).as_ptr().addr()
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525    use crate::{Instance, Module, Store};
526
527    #[test]
528    fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
529        let mut store = Store::<()>::default();
530        let module = Module::new(
531            store.engine(),
532            r#"
533                (module
534                    (table (export "t") 1 1 externref)
535                )
536            "#,
537        )?;
538        let instance = Instance::new(&mut store, &module, &[])?;
539
540        // Each time we `get_table`, we call `Table::from_wasmtime` which adds
541        // a new entry to `StoreData`, so `t1` and `t2` will have different
542        // indices into `StoreData`.
543        let t1 = instance.get_table(&mut store, "t").unwrap();
544        let t2 = instance.get_table(&mut store, "t").unwrap();
545
546        // That said, they really point to the same table.
547        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
548        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
549        let e = ExternRef::new(&mut store, 42)?;
550        t1.set(&mut store, 0, e.into())?;
551        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
552        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
553
554        // And therefore their hash keys are the same.
555        assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
556
557        // But the hash keys are different from different tables.
558        let instance2 = Instance::new(&mut store, &module, &[])?;
559        let t3 = instance2.get_table(&mut store, "t").unwrap();
560        assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0));
561
562        Ok(())
563    }
564}