wasmtime/runtime/externals/table.rs
1use crate::prelude::*;
2use crate::runtime::vm::{self as runtime, GcStore};
3use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque};
4use crate::trampoline::generate_table_export;
5use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType, Trap};
6use core::iter;
7use wasmtime_environ::DefinedTableIndex;
8
9/// A WebAssembly `table`, or an array of values.
10///
11/// Like [`Memory`][crate::Memory] a table is an indexed array of values, but
12/// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type
13/// values rather than bytes. One of the most common usages of a table is a
14/// function table for wasm modules (a `funcref` table), where each element has
15/// the `ValType::FuncRef` type.
16///
17/// A [`Table`] "belongs" to the store that it was originally created within
18/// (either via [`Table::new`] or via instantiating a
19/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the
20/// store it belongs to, and if another store is passed in by accident then
21/// methods will panic.
22#[derive(Copy, Clone, Debug)]
23#[repr(C)] // here for the C API
24pub struct Table {
25 instance: StoreInstanceId,
26 index: DefinedTableIndex,
27}
28
29// Double-check that the C representation in `extern.h` matches our in-Rust
30// representation here in terms of size/alignment/etc.
31const _: () = {
32 #[repr(C)]
33 struct Tmp(u64, u32);
34 #[repr(C)]
35 struct C(Tmp, u32);
36 assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>());
37 assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>());
38 assert!(core::mem::offset_of!(Table, instance) == 0);
39};
40
41impl Table {
42 /// Creates a new [`Table`] with the given parameters.
43 ///
44 /// * `store` - the owner of the resulting [`Table`]
45 /// * `ty` - the type of this table, containing both the element type as
46 /// well as the initial size and maximum size, if any.
47 /// * `init` - the initial value to fill all table entries with, if the
48 /// table starts with an initial size.
49 ///
50 /// # Errors
51 ///
52 /// Returns an error if `init` does not match the element type of the table,
53 /// or if `init` does not belong to the `store` provided.
54 ///
55 /// Returns an error if the element type of `ty` was not created with the
56 /// same [`Engine`](crate::Engine) as `store`.
57 ///
58 /// # Panics
59 ///
60 /// This function will panic when used with a [`Store`](`crate::Store`)
61 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
62 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`).
63 /// When using an async resource limiter, use [`Table::new_async`]
64 /// instead.
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// # use wasmtime::*;
70 /// # fn main() -> anyhow::Result<()> {
71 /// let engine = Engine::default();
72 /// let mut store = Store::new(&engine, ());
73 ///
74 /// let ty = TableType::new(RefType::FUNCREF, 2, None);
75 /// let table = Table::new(&mut store, ty, Ref::Func(None))?;
76 ///
77 /// let module = Module::new(
78 /// &engine,
79 /// "(module
80 /// (table (import \"\" \"\") 2 funcref)
81 /// (func $f (result i32)
82 /// i32.const 10)
83 /// (elem (i32.const 0) $f)
84 /// )"
85 /// )?;
86 ///
87 /// let instance = Instance::new(&mut store, &module, &[table.into()])?;
88 /// // ...
89 /// # Ok(())
90 /// # }
91 /// ```
92 pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
93 Table::_new(store.as_context_mut().0, ty, init)
94 }
95
96 /// Async variant of [`Table::new`]. You must use this variant with
97 /// [`Store`](`crate::Store`)s which have a
98 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
99 ///
100 /// # Panics
101 ///
102 /// This function will panic when used with a non-async
103 /// [`Store`](`crate::Store`)
104 ///
105 /// # Errors
106 ///
107 /// Returns an error if the element type of `ty` was not created with the
108 /// same [`Engine`](crate::Engine) as `store`.
109 #[cfg(feature = "async")]
110 pub async fn new_async(
111 mut store: impl AsContextMut<Data: Send>,
112 ty: TableType,
113 init: Ref,
114 ) -> Result<Table> {
115 let mut store = store.as_context_mut();
116 assert!(
117 store.0.async_support(),
118 "cannot use `new_async` without enabling async support on the config"
119 );
120 store
121 .on_fiber(|store| Table::_new(store.0, ty, init))
122 .await?
123 }
124
125 fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
126 let table = generate_table_export(store, &ty)?;
127 let init = init.into_table_element(store, ty.element())?;
128 let (wasmtime_table, gc_store) = table.wasmtime_table(store, iter::empty());
129 wasmtime_table.fill(gc_store, 0, init, ty.minimum())?;
130 Ok(table)
131 }
132
133 /// Returns the underlying type of this table, including its element type as
134 /// well as the maximum/minimum lower bounds.
135 ///
136 /// # Panics
137 ///
138 /// Panics if `store` does not own this table.
139 pub fn ty(&self, store: impl AsContext) -> TableType {
140 self._ty(store.as_context().0)
141 }
142
143 fn _ty(&self, store: &StoreOpaque) -> TableType {
144 TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store))
145 }
146
147 /// Returns the `runtime::Table` within `store` as well as the optional
148 /// `GcStore` in use within `store`.
149 ///
150 /// # Panics
151 ///
152 /// Panics if this table does not belong to `store`.
153 fn wasmtime_table<'a>(
154 &self,
155 store: &'a mut StoreOpaque,
156 lazy_init_range: impl Iterator<Item = u64>,
157 ) -> (&'a mut runtime::Table, Option<&'a mut GcStore>) {
158 self.instance.assert_belongs_to(store.id());
159 let (store, instance) = store.optional_gc_store_and_instance_mut(self.instance.instance());
160
161 (
162 instance.get_defined_table_with_lazy_init(self.index, lazy_init_range),
163 store,
164 )
165 }
166
167 /// Returns the table element value at `index`.
168 ///
169 /// Returns `None` if `index` is out of bounds.
170 ///
171 /// # Panics
172 ///
173 /// Panics if `store` does not own this table.
174 pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> {
175 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
176 let (table, gc_store) = self.wasmtime_table(&mut store, iter::once(index));
177 match table.get(gc_store, index)? {
178 runtime::TableElement::FuncRef(f) => {
179 // SAFETY: the `table` belongs to `store`, so elements within
180 // the table must also belong to the store.
181 let func = unsafe { f.map(|f| Func::from_vm_func_ref(store.id(), f)) };
182 Some(func.into())
183 }
184
185 runtime::TableElement::UninitFunc => {
186 unreachable!("lazy init above should have converted UninitFunc")
187 }
188
189 runtime::TableElement::GcRef(None) => {
190 Some(Ref::null(self._ty(&store).element().heap_type()))
191 }
192
193 #[cfg_attr(
194 not(feature = "gc"),
195 expect(unreachable_code, unused_variables, reason = "definitions cfg'd off")
196 )]
197 runtime::TableElement::GcRef(Some(x)) => {
198 match self._ty(&store).element().heap_type().top() {
199 HeapType::Any => {
200 let x = AnyRef::from_cloned_gc_ref(&mut store, x);
201 Some(x.into())
202 }
203 HeapType::Extern => {
204 let x = ExternRef::from_cloned_gc_ref(&mut store, x);
205 Some(x.into())
206 }
207 HeapType::Func => {
208 unreachable!("never have TableElement::GcRef for func tables")
209 }
210 ty => unreachable!("not a top type: {ty:?}"),
211 }
212 }
213
214 runtime::TableElement::ContRef(_c) => {
215 // TODO(#10248) Required to support stack switching in the embedder API.
216 unimplemented!()
217 }
218 }
219 }
220
221 /// Writes the `val` provided into `index` within this table.
222 ///
223 /// # Errors
224 ///
225 /// Returns an error if `index` is out of bounds, if `val` does not have
226 /// the right type to be stored in this table, or if `val` belongs to a
227 /// different store.
228 ///
229 /// # Panics
230 ///
231 /// Panics if `store` does not own this table.
232 pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
233 let store = store.as_context_mut().0;
234 let ty = self.ty(&store);
235 let val = val.into_table_element(store, ty.element())?;
236 let (table, _) = self.wasmtime_table(store, iter::empty());
237 table
238 .set(index, val)
239 .map_err(|()| anyhow!("table element index out of bounds"))
240 }
241
242 /// Returns the current size of this table.
243 ///
244 /// # Panics
245 ///
246 /// Panics if `store` does not own this table.
247 pub fn size(&self, store: impl AsContext) -> u64 {
248 self.internal_size(store.as_context().0)
249 }
250
251 pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u64 {
252 // unwrap here should be ok because the runtime should always guarantee
253 // that we can fit the number of elements in a 64-bit integer.
254 u64::try_from(store[self.instance].table(self.index).current_elements).unwrap()
255 }
256
257 /// Grows the size of this table by `delta` more elements, initialization
258 /// all new elements to `init`.
259 ///
260 /// Returns the previous size of this table if successful.
261 ///
262 /// # Errors
263 ///
264 /// Returns an error if the table cannot be grown by `delta`, for example
265 /// if it would cause the table to exceed its maximum size. Also returns an
266 /// error if `init` is not of the right type or if `init` does not belong to
267 /// `store`.
268 ///
269 /// # Panics
270 ///
271 /// Panics if `store` does not own this table.
272 ///
273 /// This function will panic when used with a [`Store`](`crate::Store`)
274 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`)
275 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)).
276 /// When using an async resource limiter, use [`Table::grow_async`]
277 /// instead.
278 pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
279 let store = store.as_context_mut().0;
280 let ty = self.ty(&store);
281 let init = init.into_table_element(store, ty.element())?;
282 let (table, _gc_store) = self.wasmtime_table(store, iter::empty());
283 // FIXME(#11179) shouldn't need to subvert the borrow checker
284 let table: *mut _ = table;
285 unsafe {
286 match (*table).grow(delta, init, store)? {
287 Some(size) => {
288 let vm = (*table).vmtable();
289 store[self.instance].table_ptr(self.index).write(vm);
290 // unwrap here should be ok because the runtime should always guarantee
291 // that we can fit the table size in a 64-bit integer.
292 Ok(u64::try_from(size).unwrap())
293 }
294 None => bail!("failed to grow table by `{}`", delta),
295 }
296 }
297 }
298
299 /// Async variant of [`Table::grow`]. Required when using a
300 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
301 ///
302 /// # Panics
303 ///
304 /// This function will panic when used with a non-async
305 /// [`Store`](`crate::Store`).
306 #[cfg(feature = "async")]
307 pub async fn grow_async(
308 &self,
309 mut store: impl AsContextMut<Data: Send>,
310 delta: u64,
311 init: Ref,
312 ) -> Result<u64> {
313 let mut store = store.as_context_mut();
314 assert!(
315 store.0.async_support(),
316 "cannot use `grow_async` without enabling async support on the config"
317 );
318 store
319 .on_fiber(|store| self.grow(store, delta, init))
320 .await?
321 }
322
323 /// Copy `len` elements from `src_table[src_index..]` into
324 /// `dst_table[dst_index..]`.
325 ///
326 /// # Errors
327 ///
328 /// Returns an error if the range is out of bounds of either the source or
329 /// destination tables, or if the source table's element type does not match
330 /// the destination table's element type.
331 ///
332 /// # Panics
333 ///
334 /// Panics if `store` does not own either `dst_table` or `src_table`.
335 pub fn copy(
336 mut store: impl AsContextMut,
337 dst_table: &Table,
338 dst_index: u64,
339 src_table: &Table,
340 src_index: u64,
341 len: u64,
342 ) -> Result<()> {
343 let store = store.as_context_mut().0;
344
345 let dst_ty = dst_table.ty(&store);
346 let src_ty = src_table.ty(&store);
347 src_ty
348 .element()
349 .ensure_matches(store.engine(), dst_ty.element())
350 .context(
351 "type mismatch: source table's element type does not match \
352 destination table's element type",
353 )?;
354
355 // SAFETY: the the two tables have the same type, as type-checked above.
356 unsafe {
357 Self::copy_raw(store, dst_table, dst_index, src_table, src_index, len)?;
358 }
359 Ok(())
360 }
361
362 /// Copies the elements of `src_table` to `dst_table`.
363 ///
364 /// # Panics
365 ///
366 /// Panics if the either table doesn't belong to `store`.
367 ///
368 /// # Safety
369 ///
370 /// Requires that the two tables have previously been type-checked to have
371 /// the same type.
372 pub(crate) unsafe fn copy_raw(
373 store: &mut StoreOpaque,
374 dst_table: &Table,
375 dst_index: u64,
376 src_table: &Table,
377 src_index: u64,
378 len: u64,
379 ) -> Result<(), Trap> {
380 // Handle lazy initialization of the source table first before doing
381 // anything else.
382 let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX));
383 src_table.wasmtime_table(store, src_range);
384
385 // validate `dst_table` belongs to `store`.
386 dst_table.wasmtime_table(store, iter::empty());
387
388 // Figure out which of the three cases we're in:
389 //
390 // 1. Cross-instance table copy.
391 // 2. Intra-instance table copy.
392 // 3. Intra-table copy.
393 //
394 // We handle each of them slightly differently.
395 let src_instance = src_table.instance.instance();
396 let dst_instance = dst_table.instance.instance();
397 match (
398 src_instance == dst_instance,
399 src_table.index == dst_table.index,
400 ) {
401 // 1. Cross-instance table copy: split the mutable store borrow into
402 // two mutable instance borrows, get each instance's defined table,
403 // and do the copy.
404 (false, _) => {
405 // SAFETY: accessing two instances mutably at the same time
406 // requires only accessing defined entities on each instance
407 // which is done below with `get_defined_*` methods.
408 let (gc_store, [src_instance, dst_instance]) = unsafe {
409 store.optional_gc_store_and_instances_mut([src_instance, dst_instance])
410 };
411 src_instance.get_defined_table(src_table.index).copy_to(
412 dst_instance.get_defined_table(dst_table.index),
413 gc_store,
414 dst_index,
415 src_index,
416 len,
417 )
418 }
419
420 // 2. Intra-instance, distinct-tables copy: split the mutable
421 // instance borrow into two distinct mutable table borrows and do
422 // the copy.
423 (true, false) => {
424 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
425 let [(_, src_table), (_, dst_table)] = instance
426 .tables_mut()
427 .get_disjoint_mut([src_table.index, dst_table.index])
428 .unwrap();
429 src_table.copy_to(dst_table, gc_store, dst_index, src_index, len)
430 }
431
432 // 3. Intra-table copy: get the table and copy within it!
433 (true, true) => {
434 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
435 instance
436 .get_defined_table(src_table.index)
437 .copy_within(gc_store, dst_index, src_index, len)
438 }
439 }
440 }
441
442 /// Fill `table[dst..(dst + len)]` with the given value.
443 ///
444 /// # Errors
445 ///
446 /// Returns an error if
447 ///
448 /// * `val` is not of the same type as this table's
449 /// element type,
450 ///
451 /// * the region to be filled is out of bounds, or
452 ///
453 /// * `val` comes from a different `Store` from this table.
454 ///
455 /// # Panics
456 ///
457 /// Panics if `store` does not own either `dst_table` or `src_table`.
458 pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> {
459 let store = store.as_context_mut().0;
460 let ty = self.ty(&store);
461 let val = val.into_table_element(store, ty.element())?;
462
463 let (table, gc_store) = self.wasmtime_table(store, iter::empty());
464 table.fill(gc_store, dst, val, len)?;
465
466 Ok(())
467 }
468
469 #[cfg(feature = "gc")]
470 pub(crate) fn trace_roots(
471 &self,
472 store: &mut StoreOpaque,
473 gc_roots_list: &mut crate::runtime::vm::GcRootsList,
474 ) {
475 if !self
476 ._ty(store)
477 .element()
478 .is_vmgcref_type_and_points_to_object()
479 {
480 return;
481 }
482
483 let (table, _) = self.wasmtime_table(store, iter::empty());
484 for gc_ref in table.gc_refs_mut() {
485 if let Some(gc_ref) = gc_ref {
486 unsafe {
487 gc_roots_list.add_root(gc_ref.into(), "Wasm table element");
488 }
489 }
490 }
491 }
492
493 pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table {
494 Table { instance, index }
495 }
496
497 pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table {
498 let module = store[self.instance].env_module();
499 let index = module.table_index(self.index);
500 &module.tables[index]
501 }
502
503 pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport {
504 let instance = &store[self.instance];
505 crate::runtime::vm::VMTableImport {
506 from: instance.table_ptr(self.index).into(),
507 vmctx: instance.vmctx().into(),
508 index: self.index,
509 }
510 }
511
512 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
513 store.id() == self.instance.store_id()
514 }
515
516 /// Get a stable hash key for this table.
517 ///
518 /// Even if the same underlying table definition is added to the
519 /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s,
520 /// this hash key will be consistent across all of these tables.
521 #[cfg_attr(
522 not(test),
523 expect(dead_code, reason = "Not used yet, but added for consistency")
524 )]
525 pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> {
526 store[self.instance].table_ptr(self.index).as_ptr().addr()
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533 use crate::{Instance, Module, Store};
534
535 #[test]
536 fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
537 let mut store = Store::<()>::default();
538 let module = Module::new(
539 store.engine(),
540 r#"
541 (module
542 (table (export "t") 1 1 externref)
543 )
544 "#,
545 )?;
546 let instance = Instance::new(&mut store, &module, &[])?;
547
548 // Each time we `get_table`, we call `Table::from_wasmtime` which adds
549 // a new entry to `StoreData`, so `t1` and `t2` will have different
550 // indices into `StoreData`.
551 let t1 = instance.get_table(&mut store, "t").unwrap();
552 let t2 = instance.get_table(&mut store, "t").unwrap();
553
554 // That said, they really point to the same table.
555 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
556 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
557 let e = ExternRef::new(&mut store, 42)?;
558 t1.set(&mut store, 0, e.into())?;
559 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
560 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
561
562 // And therefore their hash keys are the same.
563 assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
564
565 // But the hash keys are different from different tables.
566 let instance2 = Instance::new(&mut store, &module, &[])?;
567 let t3 = instance2.get_table(&mut store, "t").unwrap();
568 assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0));
569
570 Ok(())
571 }
572}