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