Skip to main content

wasmtime/runtime/externals/
table.rs

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