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