wasmtime/runtime/externals/
table.rs

1use crate::prelude::*;
2use crate::runtime::RootedGcRefImpl;
3use crate::runtime::vm::{
4    self, GcStore, SendSyncPtr, TableElementType, VMFuncRef, VMGcRef, VMStore,
5};
6use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque, StoreResourceLimiter};
7use crate::trampoline::generate_table_export;
8use crate::{
9    AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, HeapType, Ref, RefType,
10    StoreContextMut, TableType, Trap,
11};
12use core::iter;
13use core::ptr::NonNull;
14use wasmtime_environ::DefinedTableIndex;
15
16/// A WebAssembly `table`, or an array of values.
17///
18/// Like [`Memory`][crate::Memory] a table is an indexed array of values, but
19/// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type
20/// values rather than bytes. One of the most common usages of a table is a
21/// function table for wasm modules (a `funcref` table), where each element has
22/// the `ValType::FuncRef` type.
23///
24/// A [`Table`] "belongs" to the store that it was originally created within
25/// (either via [`Table::new`] or via instantiating a
26/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the
27/// store it belongs to, and if another store is passed in by accident then
28/// methods will panic.
29#[derive(Copy, Clone, Debug)]
30#[repr(C)] // here for the C API
31pub struct Table {
32    instance: StoreInstanceId,
33    index: DefinedTableIndex,
34}
35
36// Double-check that the C representation in `extern.h` matches our in-Rust
37// representation here in terms of size/alignment/etc.
38const _: () = {
39    #[repr(C)]
40    struct Tmp(u64, u32);
41    #[repr(C)]
42    struct C(Tmp, u32);
43    assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>());
44    assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>());
45    assert!(core::mem::offset_of!(Table, instance) == 0);
46};
47
48impl Table {
49    /// Creates a new [`Table`] with the given parameters.
50    ///
51    /// * `store` - the owner of the resulting [`Table`]
52    /// * `ty` - the type of this table, containing both the element type as
53    ///   well as the initial size and maximum size, if any.
54    /// * `init` - the initial value to fill all table entries with, if the
55    ///   table starts with an initial size.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if `init` does not match the element type of the table,
60    /// or if `init` does not belong to the `store` provided.
61    ///
62    /// # Panics
63    ///
64    /// This function will panic when used with a [`Store`](`crate::Store`)
65    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
66    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`).
67    /// When using an async resource limiter, use [`Table::new_async`]
68    /// instead.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// # use wasmtime::*;
74    /// # fn main() -> anyhow::Result<()> {
75    /// let engine = Engine::default();
76    /// let mut store = Store::new(&engine, ());
77    ///
78    /// let ty = TableType::new(RefType::FUNCREF, 2, None);
79    /// let table = Table::new(&mut store, ty, Ref::Func(None))?;
80    ///
81    /// let module = Module::new(
82    ///     &engine,
83    ///     "(module
84    ///         (table (import \"\" \"\") 2 funcref)
85    ///         (func $f (result i32)
86    ///             i32.const 10)
87    ///         (elem (i32.const 0) $f)
88    ///     )"
89    /// )?;
90    ///
91    /// let instance = Instance::new(&mut store, &module, &[table.into()])?;
92    /// // ...
93    /// # Ok(())
94    /// # }
95    /// ```
96    pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
97        let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
98        vm::one_poll(Table::_new(store, limiter.as_mut(), ty, init))
99            .expect("must use `new_async` when async resource limiters are in use")
100    }
101
102    /// Async variant of [`Table::new`]. You must use this variant with
103    /// [`Store`](`crate::Store`)s which have a
104    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
105    ///
106    /// # Panics
107    ///
108    /// This function will panic when used with a non-async
109    /// [`Store`](`crate::Store`)
110    #[cfg(feature = "async")]
111    pub async fn new_async(
112        mut store: impl AsContextMut,
113        ty: TableType,
114        init: Ref,
115    ) -> Result<Table> {
116        let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
117        Table::_new(store, limiter.as_mut(), ty, init).await
118    }
119
120    async fn _new(
121        store: &mut StoreOpaque,
122        limiter: Option<&mut StoreResourceLimiter<'_>>,
123        ty: TableType,
124        init: Ref,
125    ) -> Result<Table> {
126        let table = generate_table_export(store, limiter, &ty).await?;
127        table._fill(store, 0, init, ty.minimum())?;
128        Ok(table)
129    }
130
131    /// Returns the underlying type of this table, including its element type as
132    /// well as the maximum/minimum lower bounds.
133    ///
134    /// # Panics
135    ///
136    /// Panics if `store` does not own this table.
137    pub fn ty(&self, store: impl AsContext) -> TableType {
138        self._ty(store.as_context().0)
139    }
140
141    fn _ty(&self, store: &StoreOpaque) -> TableType {
142        TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store))
143    }
144
145    /// Returns the `vm::Table` within `store` as well as the optional
146    /// `GcStore` in use within `store`.
147    ///
148    /// # Panics
149    ///
150    /// Panics if this table does not belong to `store`.
151    fn wasmtime_table<'a>(
152        &self,
153        store: &'a mut StoreOpaque,
154        lazy_init_range: impl IntoIterator<Item = u64>,
155    ) -> (&'a mut vm::Table, Option<&'a mut GcStore>) {
156        self.instance.assert_belongs_to(store.id());
157        let (store, registry, instance) =
158            store.optional_gc_store_and_registry_and_instance_mut(self.instance.instance());
159
160        (
161            instance.get_defined_table_with_lazy_init(registry, self.index, lazy_init_range),
162            store,
163        )
164    }
165
166    /// Returns the table element value at `index`.
167    ///
168    /// Returns `None` if `index` is out of bounds.
169    ///
170    /// # Panics
171    ///
172    /// Panics if `store` does not own this table.
173    pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> {
174        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
175        let (table, _gc_store) = self.wasmtime_table(&mut store, [index]);
176        match table.element_type() {
177            TableElementType::Func => {
178                let ptr = table.get_func(index).ok()?;
179                Some(
180                    // SAFETY: `store` owns this table, so therefore it owns all
181                    // functions within the table too.
182                    ptr.map(|p| unsafe { Func::from_vm_func_ref(store.id(), p) })
183                        .into(),
184                )
185            }
186            TableElementType::GcRef => {
187                let gc_ref = table
188                    .get_gc_ref(index)
189                    .ok()?
190                    .map(|r| r.unchecked_copy())
191                    .map(|r| store.clone_gc_ref(&r));
192                Some(match self._ty(&store).element().heap_type().top() {
193                    HeapType::Extern => {
194                        Ref::Extern(gc_ref.map(|r| ExternRef::from_cloned_gc_ref(&mut store, r)))
195                    }
196                    HeapType::Any => {
197                        Ref::Any(gc_ref.map(|r| AnyRef::from_cloned_gc_ref(&mut store, r)))
198                    }
199                    HeapType::Exn => {
200                        Ref::Exn(gc_ref.map(|r| ExnRef::from_cloned_gc_ref(&mut store, r)))
201                    }
202                    _ => unreachable!(),
203                })
204            }
205            // TODO(#10248) Required to support stack switching in the embedder
206            // API.
207            TableElementType::Cont => panic!("unimplemented table for cont"),
208        }
209    }
210
211    /// Writes the `val` provided into `index` within this table.
212    ///
213    /// # Errors
214    ///
215    /// Returns an error if `index` is out of bounds, if `val` does not have
216    /// the right type to be stored in this table, or if `val` belongs to a
217    /// different store.
218    ///
219    /// # Panics
220    ///
221    /// Panics if `store` does not own this table.
222    pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
223        self.set_(store.as_context_mut().0, index, val)
224    }
225
226    pub(crate) fn set_(&self, store: &mut StoreOpaque, index: u64, val: Ref) -> Result<()> {
227        let ty = self._ty(store);
228        match element_type(&ty) {
229            TableElementType::Func => {
230                let element = val.into_table_func(store, ty.element())?;
231                let (table, _gc_store) = self.wasmtime_table(store, iter::empty());
232                table.set_func(index, element)?;
233            }
234            TableElementType::GcRef => {
235                let mut store = AutoAssertNoGc::new(store);
236                let element = val.into_table_gc_ref(&mut store, ty.element())?;
237                // Note that `unchecked_copy` should be ok as we're under an
238                // `AutoAssertNoGc` which means that despite this not being
239                // rooted we don't have to worry about it going away.
240                let element = element.map(|r| r.unchecked_copy());
241                let (table, gc_store) = self.wasmtime_table(&mut store, iter::empty());
242                table.set_gc_ref(gc_store, index, element.as_ref())?;
243            }
244            // TODO(#10248) Required to support stack switching in the embedder
245            // API.
246            TableElementType::Cont => bail!("unimplemented table for cont"),
247        }
248        Ok(())
249    }
250
251    /// Returns the current size of this table.
252    ///
253    /// # Panics
254    ///
255    /// Panics if `store` does not own this table.
256    pub fn size(&self, store: impl AsContext) -> u64 {
257        self._size(store.as_context().0)
258    }
259
260    pub(crate) fn _size(&self, store: &StoreOpaque) -> u64 {
261        // unwrap here should be ok because the runtime should always guarantee
262        // that we can fit the number of elements in a 64-bit integer.
263        u64::try_from(store[self.instance].table(self.index).current_elements).unwrap()
264    }
265
266    /// Grows the size of this table by `delta` more elements, initialization
267    /// all new elements to `init`.
268    ///
269    /// Returns the previous size of this table if successful.
270    ///
271    /// # Errors
272    ///
273    /// Returns an error if the table cannot be grown by `delta`, for example
274    /// if it would cause the table to exceed its maximum size. Also returns an
275    /// error if `init` is not of the right type or if `init` does not belong to
276    /// `store`.
277    ///
278    /// # Panics
279    ///
280    /// Panics if `store` does not own this table.
281    ///
282    /// This function will panic when used with a [`Store`](`crate::Store`)
283    /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
284    /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
285    /// When using an async resource limiter, use [`Table::grow_async`]
286    /// instead.
287    pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
288        vm::one_poll(self._grow(store.as_context_mut(), delta, init))
289            .expect("must use `grow_async` when async resource limiters are in use")
290    }
291
292    async fn _grow<T>(&self, store: StoreContextMut<'_, T>, delta: u64, init: Ref) -> Result<u64> {
293        let store = store.0;
294        let ty = self.ty(&store);
295        let (mut limiter, store) = store.resource_limiter_and_store_opaque();
296        let limiter = limiter.as_mut();
297        let result = match element_type(&ty) {
298            TableElementType::Func => {
299                let element = init
300                    .into_table_func(store, ty.element())?
301                    .map(SendSyncPtr::new);
302                self.instance
303                    .get_mut(store)
304                    .defined_table_grow(self.index, async |table| {
305                        // SAFETY: in the context of `defined_table_grow` this
306                        // is safe to call as it'll update the internal table
307                        // pointer in the instance.
308                        unsafe { table.grow_func(limiter, delta, element).await }
309                    })
310                    .await?
311            }
312            TableElementType::GcRef => {
313                let mut store = AutoAssertNoGc::new(store);
314                let element = init
315                    .into_table_gc_ref(&mut store, ty.element())?
316                    .map(|r| r.unchecked_copy());
317                let (gc_store, instance) = self.instance.get_with_gc_store_mut(&mut store);
318                instance
319                    .defined_table_grow(self.index, async |table| {
320                        // SAFETY: in the context of `defined_table_grow` this
321                        // is safe to call as it'll update the internal table
322                        // pointer in the instance.
323                        unsafe {
324                            table
325                                .grow_gc_ref(limiter, gc_store, delta, element.as_ref())
326                                .await
327                        }
328                    })
329                    .await?
330            }
331            // TODO(#10248) Required to support stack switching in the
332            // embedder API.
333            TableElementType::Cont => bail!("unimplemented table for cont"),
334        };
335        match result {
336            Some(size) => {
337                // unwrap here should be ok because the runtime should always
338                // guarantee that we can fit the table size in a 64-bit integer.
339                Ok(u64::try_from(size).unwrap())
340            }
341            None => bail!("failed to grow table by `{delta}`"),
342        }
343    }
344
345    /// Async variant of [`Table::grow`]. Required when using a
346    /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
347    ///
348    /// # Panics
349    ///
350    /// This function will panic when used with a non-async
351    /// [`Store`](`crate::Store`).
352    #[cfg(feature = "async")]
353    pub async fn grow_async(
354        &self,
355        mut store: impl AsContextMut,
356        delta: u64,
357        init: Ref,
358    ) -> Result<u64> {
359        self._grow(store.as_context_mut(), delta, init).await
360    }
361
362    /// Copy `len` elements from `src_table[src_index..]` into
363    /// `dst_table[dst_index..]`.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if the range is out of bounds of either the source or
368    /// destination tables, or if the source table's element type does not match
369    /// the destination table's element type.
370    ///
371    /// # Panics
372    ///
373    /// Panics if `store` does not own either `dst_table` or `src_table`.
374    pub fn copy(
375        mut store: impl AsContextMut,
376        dst_table: &Table,
377        dst_index: u64,
378        src_table: &Table,
379        src_index: u64,
380        len: u64,
381    ) -> Result<()> {
382        let store = store.as_context_mut().0;
383
384        let dst_ty = dst_table.ty(&store);
385        let src_ty = src_table.ty(&store);
386        src_ty
387            .element()
388            .ensure_matches(store.engine(), dst_ty.element())
389            .context(
390                "type mismatch: source table's element type does not match \
391                 destination table's element type",
392            )?;
393
394        // SAFETY: the the two tables have the same type, as type-checked above.
395        unsafe {
396            Self::copy_raw(store, dst_table, dst_index, src_table, src_index, len)?;
397        }
398        Ok(())
399    }
400
401    /// Copies the elements of `src_table` to `dst_table`.
402    ///
403    /// # Panics
404    ///
405    /// Panics if the either table doesn't belong to `store`.
406    ///
407    /// # Safety
408    ///
409    /// Requires that the two tables have previously been type-checked to have
410    /// the same type.
411    pub(crate) unsafe fn copy_raw(
412        store: &mut StoreOpaque,
413        dst_table: &Table,
414        dst_index: u64,
415        src_table: &Table,
416        src_index: u64,
417        len: u64,
418    ) -> Result<(), Trap> {
419        // Handle lazy initialization of the source table first before doing
420        // anything else.
421        let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX));
422        src_table.wasmtime_table(store, src_range);
423
424        // validate `dst_table` belongs to `store`.
425        dst_table.wasmtime_table(store, iter::empty());
426
427        // Figure out which of the three cases we're in:
428        //
429        // 1. Cross-instance table copy.
430        // 2. Intra-instance table copy.
431        // 3. Intra-table copy.
432        //
433        // We handle each of them slightly differently.
434        let src_instance = src_table.instance.instance();
435        let dst_instance = dst_table.instance.instance();
436        match (
437            src_instance == dst_instance,
438            src_table.index == dst_table.index,
439        ) {
440            // 1. Cross-instance table copy: split the mutable store borrow into
441            // two mutable instance borrows, get each instance's defined table,
442            // and do the copy.
443            (false, _) => {
444                // SAFETY: accessing two instances mutably at the same time
445                // requires only accessing defined entities on each instance
446                // which is done below with `get_defined_*` methods.
447                let (gc_store, [src_instance, dst_instance]) = unsafe {
448                    store.optional_gc_store_and_instances_mut([src_instance, dst_instance])
449                };
450                src_instance.get_defined_table(src_table.index).copy_to(
451                    dst_instance.get_defined_table(dst_table.index),
452                    gc_store,
453                    dst_index,
454                    src_index,
455                    len,
456                )
457            }
458
459            // 2. Intra-instance, distinct-tables copy: split the mutable
460            // instance borrow into two distinct mutable table borrows and do
461            // the copy.
462            (true, false) => {
463                let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
464                let [(_, src_table), (_, dst_table)] = instance
465                    .tables_mut()
466                    .get_disjoint_mut([src_table.index, dst_table.index])
467                    .unwrap();
468                src_table.copy_to(dst_table, gc_store, dst_index, src_index, len)
469            }
470
471            // 3. Intra-table copy: get the table and copy within it!
472            (true, true) => {
473                let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
474                instance
475                    .get_defined_table(src_table.index)
476                    .copy_within(gc_store, dst_index, src_index, len)
477            }
478        }
479    }
480
481    /// Fill `table[dst..(dst + len)]` with the given value.
482    ///
483    /// # Errors
484    ///
485    /// Returns an error if
486    ///
487    /// * `val` is not of the same type as this table's
488    ///   element type,
489    ///
490    /// * the region to be filled is out of bounds, or
491    ///
492    /// * `val` comes from a different `Store` from this table.
493    ///
494    /// # Panics
495    ///
496    /// Panics if `store` does not own either `dst_table` or `src_table`.
497    pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> {
498        self._fill(store.as_context_mut().0, dst, val, len)
499    }
500
501    pub(crate) fn _fill(
502        &self,
503        store: &mut StoreOpaque,
504        dst: u64,
505        val: Ref,
506        len: u64,
507    ) -> Result<()> {
508        let ty = self._ty(&store);
509        match element_type(&ty) {
510            TableElementType::Func => {
511                let val = val.into_table_func(store, ty.element())?;
512                let (table, _) = self.wasmtime_table(store, iter::empty());
513                table.fill_func(dst, val, len)?;
514            }
515            TableElementType::GcRef => {
516                // Note that `val` is a `VMGcRef` temporarily read from the
517                // store here, and blocking GC with `AutoAssertNoGc` should
518                // ensure that it's not collected while being worked on here.
519                let mut store = AutoAssertNoGc::new(store);
520                let val = val.into_table_gc_ref(&mut store, ty.element())?;
521                let val = val.map(|g| g.unchecked_copy());
522                let (table, gc_store) = self.wasmtime_table(&mut store, iter::empty());
523                table.fill_gc_ref(gc_store, dst, val.as_ref(), len)?;
524            }
525            // TODO(#10248) Required to support stack switching in the embedder
526            // API.
527            TableElementType::Cont => bail!("unimplemented table for cont"),
528        }
529
530        Ok(())
531    }
532
533    #[cfg(feature = "gc")]
534    pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut vm::GcRootsList) {
535        if !self
536            ._ty(store)
537            .element()
538            .is_vmgcref_type_and_points_to_object()
539        {
540            return;
541        }
542
543        let (table, _) = self.wasmtime_table(store, iter::empty());
544        for gc_ref in table.gc_refs_mut() {
545            if let Some(gc_ref) = gc_ref {
546                unsafe {
547                    gc_roots_list.add_root(gc_ref.into(), "Wasm table element");
548                }
549            }
550        }
551    }
552
553    pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table {
554        Table { instance, index }
555    }
556
557    pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table {
558        let module = store[self.instance].env_module();
559        let index = module.table_index(self.index);
560        &module.tables[index]
561    }
562
563    pub(crate) fn vmimport(&self, store: &StoreOpaque) -> vm::VMTableImport {
564        let instance = &store[self.instance];
565        vm::VMTableImport {
566            from: instance.table_ptr(self.index).into(),
567            vmctx: instance.vmctx().into(),
568            index: self.index,
569        }
570    }
571
572    pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
573        store.id() == self.instance.store_id()
574    }
575
576    /// Get a stable hash key for this table.
577    ///
578    /// Even if the same underlying table definition is added to the
579    /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s,
580    /// this hash key will be consistent across all of these tables.
581    #[cfg_attr(
582        not(test),
583        expect(dead_code, reason = "Not used yet, but added for consistency")
584    )]
585    pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> {
586        store[self.instance].table_ptr(self.index).as_ptr().addr()
587    }
588}
589
590fn element_type(ty: &TableType) -> TableElementType {
591    match ty.element().heap_type().top() {
592        HeapType::Func => TableElementType::Func,
593        HeapType::Exn | HeapType::Extern | HeapType::Any => TableElementType::GcRef,
594        HeapType::Cont => TableElementType::Cont,
595        _ => unreachable!(),
596    }
597}
598
599impl Ref {
600    fn into_table_func(
601        self,
602        store: &mut StoreOpaque,
603        ty: &RefType,
604    ) -> Result<Option<NonNull<VMFuncRef>>> {
605        self.ensure_matches_ty(store, &ty)
606            .context("type mismatch: value does not match table element type")?;
607
608        match (self, ty.heap_type().top()) {
609            (Ref::Func(None), HeapType::Func) => {
610                assert!(ty.is_nullable());
611                Ok(None)
612            }
613            (Ref::Func(Some(f)), HeapType::Func) => {
614                debug_assert!(
615                    f.comes_from_same_store(store),
616                    "checked in `ensure_matches_ty`"
617                );
618                Ok(Some(f.vm_func_ref(store)))
619            }
620
621            _ => unreachable!("checked that the value matches the type above"),
622        }
623    }
624
625    fn into_table_gc_ref<'a>(
626        self,
627        store: &'a mut AutoAssertNoGc<'_>,
628        ty: &RefType,
629    ) -> Result<Option<&'a VMGcRef>> {
630        self.ensure_matches_ty(store, &ty)
631            .context("type mismatch: value does not match table element type")?;
632
633        match (self, ty.heap_type().top()) {
634            (Ref::Extern(e), HeapType::Extern) => match e {
635                None => {
636                    assert!(ty.is_nullable());
637                    Ok(None)
638                }
639                Some(e) => Ok(Some(e.try_gc_ref(store)?)),
640            },
641
642            (Ref::Any(a), HeapType::Any) => match a {
643                None => {
644                    assert!(ty.is_nullable());
645                    Ok(None)
646                }
647                Some(a) => Ok(Some(a.try_gc_ref(store)?)),
648            },
649
650            (Ref::Exn(e), HeapType::Exn) => match e {
651                None => {
652                    assert!(ty.is_nullable());
653                    Ok(None)
654                }
655                Some(e) => Ok(Some(e.try_gc_ref(store)?)),
656            },
657
658            _ => unreachable!("checked that the value matches the type above"),
659        }
660    }
661}
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666    use crate::{Instance, Module, Store};
667
668    #[test]
669    fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
670        let mut store = Store::<()>::default();
671        let module = Module::new(
672            store.engine(),
673            r#"
674                (module
675                    (table (export "t") 1 1 externref)
676                )
677            "#,
678        )?;
679        let instance = Instance::new(&mut store, &module, &[])?;
680
681        // Each time we `get_table`, we call `Table::from_wasmtime` which adds
682        // a new entry to `StoreData`, so `t1` and `t2` will have different
683        // indices into `StoreData`.
684        let t1 = instance.get_table(&mut store, "t").unwrap();
685        let t2 = instance.get_table(&mut store, "t").unwrap();
686
687        // That said, they really point to the same table.
688        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
689        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
690        let e = ExternRef::new(&mut store, 42)?;
691        t1.set(&mut store, 0, e.into())?;
692        assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
693        assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
694
695        // And therefore their hash keys are the same.
696        assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
697
698        // But the hash keys are different from different tables.
699        let instance2 = Instance::new(&mut store, &module, &[])?;
700        let t3 = instance2.get_table(&mut store, "t").unwrap();
701        assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0));
702
703        Ok(())
704    }
705
706    #[test]
707    fn grow_is_send() {
708        fn _assert_send<T: Send>(_: T) {}
709        fn _grow(table: &Table, store: &mut Store<()>, init: Ref) {
710            _assert_send(table.grow(store, 0, init))
711        }
712    }
713}