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}