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