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