wasmtime/runtime/vm/
table.rs

1//! Memory management for tables.
2//!
3//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
4
5use crate::prelude::*;
6use crate::runtime::vm::stack_switching::VMContObj;
7use crate::runtime::vm::vmcontext::{VMFuncRef, VMTableDefinition};
8use crate::runtime::vm::{GcStore, SendSyncPtr, VMGcRef, VMStore, VmPtr};
9use core::alloc::Layout;
10use core::mem;
11use core::ops::Range;
12use core::ptr::{self, NonNull};
13use core::slice;
14use core::{cmp, usize};
15use wasmtime_environ::{
16    FUNCREF_INIT_BIT, FUNCREF_MASK, IndexType, Trap, Tunables, WasmHeapTopType, WasmRefType,
17};
18
19/// An element going into or coming out of a table.
20///
21/// Table elements are stored as pointers and are default-initialized with
22/// `ptr::null_mut`.
23pub enum TableElement {
24    /// A `funcref`.
25    FuncRef(Option<NonNull<VMFuncRef>>),
26
27    /// A GC reference.
28    GcRef(Option<VMGcRef>),
29
30    /// An uninitialized funcref value. This should never be exposed
31    /// beyond the `wasmtime` crate boundary; the upper-level code
32    /// (which has access to the info needed for lazy initialization)
33    /// will replace it when fetched.
34    UninitFunc,
35
36    /// A `contref`
37    ContRef(Option<VMContObj>),
38}
39
40#[derive(Copy, Clone, PartialEq, Eq, Debug)]
41pub enum TableElementType {
42    Func,
43    GcRef,
44    Cont,
45}
46
47impl TableElementType {
48    fn matches(&self, val: &TableElement) -> bool {
49        match (val, self) {
50            (TableElement::FuncRef(_), TableElementType::Func) => true,
51            (TableElement::GcRef(_), TableElementType::GcRef) => true,
52            (TableElement::ContRef(_), TableElementType::Cont) => true,
53            _ => false,
54        }
55    }
56
57    /// Returns the size required to actually store an element of this particular type
58    pub fn element_size(&self) -> usize {
59        match self {
60            TableElementType::Func => core::mem::size_of::<FuncTableElem>(),
61            TableElementType::GcRef => core::mem::size_of::<Option<VMGcRef>>(),
62            TableElementType::Cont => core::mem::size_of::<ContTableElem>(),
63        }
64    }
65}
66
67// The usage of `*mut VMFuncRef` is safe w.r.t. thread safety, this just relies
68// on thread-safety of `VMGcRef` itself.
69unsafe impl Send for TableElement where VMGcRef: Send {}
70unsafe impl Sync for TableElement where VMGcRef: Sync {}
71
72impl TableElement {
73    /// Consumes a table element into a pointer/reference, as it
74    /// exists outside the table itself. This strips off any tag bits
75    /// or other information that only lives inside the table.
76    ///
77    /// Can only be done to an initialized table element; lazy init
78    /// must occur first. (In other words, lazy values do not survive
79    /// beyond the table, as every table read path initializes them.)
80    ///
81    /// # Safety
82    ///
83    /// The same warnings as for `into_table_values()` apply.
84    pub(crate) unsafe fn into_func_ref_asserting_initialized(self) -> Option<NonNull<VMFuncRef>> {
85        match self {
86            Self::FuncRef(e) => e,
87            Self::UninitFunc => panic!("Uninitialized table element value outside of table slot"),
88            Self::GcRef(_) => panic!("GC reference is not a function reference"),
89            Self::ContRef(_) => panic!("Continuation reference is not a function reference"),
90        }
91    }
92
93    /// Indicates whether this value is the "uninitialized element"
94    /// value.
95    pub(crate) fn is_uninit(&self) -> bool {
96        match self {
97            Self::UninitFunc => true,
98            _ => false,
99        }
100    }
101}
102
103impl From<Option<NonNull<VMFuncRef>>> for TableElement {
104    fn from(f: Option<NonNull<VMFuncRef>>) -> TableElement {
105        TableElement::FuncRef(f)
106    }
107}
108
109impl From<Option<VMGcRef>> for TableElement {
110    fn from(x: Option<VMGcRef>) -> TableElement {
111        TableElement::GcRef(x)
112    }
113}
114
115impl From<VMGcRef> for TableElement {
116    fn from(x: VMGcRef) -> TableElement {
117        TableElement::GcRef(Some(x))
118    }
119}
120
121impl From<Option<VMContObj>> for TableElement {
122    fn from(c: Option<VMContObj>) -> TableElement {
123        TableElement::ContRef(c)
124    }
125}
126
127impl From<VMContObj> for TableElement {
128    fn from(c: VMContObj) -> TableElement {
129        TableElement::ContRef(Some(c))
130    }
131}
132
133/// At-rest representation of a function in a funcref table.
134///
135/// Note that whether or not these pointers are tagged is a property of `Engine`
136/// configuration. Also note that this specifically uses `VmPtr<T>` to handle
137/// provenance here when loading/storing values to a table.
138///
139/// The possible values here are:
140///
141/// * `None` for untagged tables - a null function element
142/// * `Some(_)` for untagged tables - a non-null function element
143/// * `None` for tagged tables - an uninitialized element
144/// * `Some(1)` for tagged tables - a null function element
145/// * `Some(addr | 1)` for tagged tables - a non-null function element
146#[derive(Copy, Clone)]
147#[repr(transparent)]
148struct MaybeTaggedFuncRef(Option<VmPtr<VMFuncRef>>);
149
150impl MaybeTaggedFuncRef {
151    const UNINIT: MaybeTaggedFuncRef = MaybeTaggedFuncRef(None);
152
153    /// Converts the given `ptr`, a valid funcref pointer, into a tagged pointer
154    /// by adding in the `FUNCREF_INIT_BIT`.
155    fn from(ptr: Option<NonNull<VMFuncRef>>, lazy_init: bool) -> Self {
156        let maybe_tagged = if lazy_init {
157            Some(match ptr {
158                Some(ptr) => ptr.map_addr(|a| a | FUNCREF_INIT_BIT),
159                None => NonNull::new(core::ptr::without_provenance_mut(FUNCREF_INIT_BIT)).unwrap(),
160            })
161        } else {
162            ptr
163        };
164        MaybeTaggedFuncRef(maybe_tagged.map(Into::into))
165    }
166
167    /// Converts a tagged pointer into a `TableElement`, returning `UninitFunc`
168    /// for null (not a tagged value) or `FuncRef` for otherwise tagged values.
169    fn into_table_element(self, lazy_init: bool) -> TableElement {
170        let ptr = self.0;
171        if lazy_init && ptr.is_none() {
172            TableElement::UninitFunc
173        } else {
174            // Masking off the tag bit is harmless whether the table uses lazy
175            // init or not.
176            let unmasked =
177                ptr.and_then(|ptr| NonNull::new(ptr.as_ptr().map_addr(|a| a & FUNCREF_MASK)));
178            TableElement::FuncRef(unmasked)
179        }
180    }
181}
182
183pub type FuncTableElem = Option<SendSyncPtr<VMFuncRef>>;
184pub type ContTableElem = Option<VMContObj>;
185
186/// The maximum of the sizes of any of the table element types
187#[cfg(feature = "pooling-allocator")]
188pub const NOMINAL_MAX_TABLE_ELEM_SIZE: usize = {
189    // ContTableElem intentionally excluded for "nominal" calculation.
190    let sizes = [
191        core::mem::size_of::<FuncTableElem>(),
192        core::mem::size_of::<Option<VMGcRef>>(),
193    ];
194
195    // This is equivalent to `|data| {data.iter().reduce(std::cmp::max).unwrap()}`,
196    // but as a `const` function, so we can use it to define a constant.
197    const fn slice_max(data: &[usize]) -> usize {
198        match data {
199            [] => 0,
200            [head, tail @ ..] => {
201                let tail_max = slice_max(tail);
202                if *head >= tail_max { *head } else { tail_max }
203            }
204        }
205    }
206
207    slice_max(&sizes)
208};
209
210pub enum StaticTable {
211    Func(StaticFuncTable),
212    GcRef(StaticGcRefTable),
213    Cont(StaticContTable),
214}
215
216impl From<StaticFuncTable> for StaticTable {
217    fn from(value: StaticFuncTable) -> Self {
218        Self::Func(value)
219    }
220}
221
222impl From<StaticGcRefTable> for StaticTable {
223    fn from(value: StaticGcRefTable) -> Self {
224        Self::GcRef(value)
225    }
226}
227
228impl From<StaticContTable> for StaticTable {
229    fn from(value: StaticContTable) -> Self {
230        Self::Cont(value)
231    }
232}
233
234pub struct StaticFuncTable {
235    /// Where data for this table is stored. The length of this list is the
236    /// maximum size of the table.
237    data: SendSyncPtr<[FuncTableElem]>,
238    /// The current size of the table.
239    size: usize,
240    /// Whether elements of this table are initialized lazily.
241    lazy_init: bool,
242}
243
244pub struct StaticGcRefTable {
245    /// Where data for this table is stored. The length of this list is the
246    /// maximum size of the table.
247    data: SendSyncPtr<[Option<VMGcRef>]>,
248    /// The current size of the table.
249    size: usize,
250}
251
252pub struct StaticContTable {
253    /// Where data for this table is stored. The length of this list is the
254    /// maximum size of the table.
255    data: SendSyncPtr<[ContTableElem]>,
256    /// The current size of the table.
257    size: usize,
258}
259
260pub enum DynamicTable {
261    Func(DynamicFuncTable),
262    GcRef(DynamicGcRefTable),
263    Cont(DynamicContTable),
264}
265
266impl From<DynamicFuncTable> for DynamicTable {
267    fn from(value: DynamicFuncTable) -> Self {
268        Self::Func(value)
269    }
270}
271
272impl From<DynamicGcRefTable> for DynamicTable {
273    fn from(value: DynamicGcRefTable) -> Self {
274        Self::GcRef(value)
275    }
276}
277
278impl From<DynamicContTable> for DynamicTable {
279    fn from(value: DynamicContTable) -> Self {
280        Self::Cont(value)
281    }
282}
283
284pub struct DynamicFuncTable {
285    /// Dynamically managed storage space for this table. The length of this
286    /// vector is the current size of the table.
287    elements: Vec<FuncTableElem>,
288    /// Maximum size that `elements` can grow to.
289    maximum: Option<usize>,
290    /// Whether elements of this table are initialized lazily.
291    lazy_init: bool,
292}
293
294pub struct DynamicGcRefTable {
295    /// Dynamically managed storage space for this table. The length of this
296    /// vector is the current size of the table.
297    elements: Vec<Option<VMGcRef>>,
298    /// Maximum size that `elements` can grow to.
299    maximum: Option<usize>,
300}
301
302pub struct DynamicContTable {
303    /// Dynamically managed storage space for this table. The length of this
304    /// vector is the current size of the table.
305    elements: Vec<ContTableElem>,
306    /// Maximum size that `elements` can grow to.
307    maximum: Option<usize>,
308}
309
310/// Represents an instance's table.
311pub enum Table {
312    /// A "static" table where storage space is managed externally, currently
313    /// used with the pooling allocator.
314    Static(StaticTable),
315    /// A "dynamic" table where table storage space is dynamically allocated via
316    /// `malloc` (aka Rust's `Vec`).
317    Dynamic(DynamicTable),
318}
319
320impl From<StaticTable> for Table {
321    fn from(value: StaticTable) -> Self {
322        Self::Static(value)
323    }
324}
325
326impl From<StaticFuncTable> for Table {
327    fn from(value: StaticFuncTable) -> Self {
328        let t: StaticTable = value.into();
329        t.into()
330    }
331}
332
333impl From<StaticGcRefTable> for Table {
334    fn from(value: StaticGcRefTable) -> Self {
335        let t: StaticTable = value.into();
336        t.into()
337    }
338}
339
340impl From<StaticContTable> for Table {
341    fn from(value: StaticContTable) -> Self {
342        let t: StaticTable = value.into();
343        t.into()
344    }
345}
346
347impl From<DynamicTable> for Table {
348    fn from(value: DynamicTable) -> Self {
349        Self::Dynamic(value)
350    }
351}
352
353impl From<DynamicFuncTable> for Table {
354    fn from(value: DynamicFuncTable) -> Self {
355        let t: DynamicTable = value.into();
356        t.into()
357    }
358}
359
360impl From<DynamicGcRefTable> for Table {
361    fn from(value: DynamicGcRefTable) -> Self {
362        let t: DynamicTable = value.into();
363        t.into()
364    }
365}
366
367impl From<DynamicContTable> for Table {
368    fn from(value: DynamicContTable) -> Self {
369        let t: DynamicTable = value.into();
370        t.into()
371    }
372}
373
374pub(crate) fn wasm_to_table_type(ty: WasmRefType) -> TableElementType {
375    match ty.heap_type.top() {
376        WasmHeapTopType::Func => TableElementType::Func,
377        WasmHeapTopType::Any | WasmHeapTopType::Extern => TableElementType::GcRef,
378        WasmHeapTopType::Cont => TableElementType::Cont,
379    }
380}
381
382/// Allocate dynamic table elements of the given length.
383///
384/// Relies on the fact that our tables' elements are initialized to `None`,
385/// which is represented by zero, to allocate pre-zeroed memory from the global
386/// allocator and avoid manual zero-initialization.
387///
388/// # Safety
389///
390/// Should only ever be called with a `T` that is a table element type and where
391/// `Option<T>`'s `None` variant is represented with zero.
392unsafe fn alloc_dynamic_table_elements<T>(len: usize) -> Result<Vec<Option<T>>> {
393    debug_assert!(
394        core::mem::MaybeUninit::<Option<T>>::zeroed()
395            .assume_init()
396            .is_none(),
397        "null table elements are represented with zeroed memory"
398    );
399
400    if len == 0 {
401        return Ok(vec![]);
402    }
403
404    let align = mem::align_of::<Option<T>>();
405
406    let size = mem::size_of::<Option<T>>();
407    let size = size.next_multiple_of(align);
408    let size = size.checked_mul(len).unwrap();
409
410    let layout = Layout::from_size_align(size, align)?;
411
412    let ptr = alloc::alloc::alloc_zeroed(layout);
413    ensure!(!ptr.is_null(), "failed to allocate memory for table");
414
415    let elems = Vec::<Option<T>>::from_raw_parts(ptr.cast(), len, len);
416    debug_assert!(elems.iter().all(|e| e.is_none()));
417
418    Ok(elems)
419}
420
421impl Table {
422    /// Create a new dynamic (movable) table instance for the specified table plan.
423    pub fn new_dynamic(
424        ty: &wasmtime_environ::Table,
425        tunables: &Tunables,
426        store: &mut dyn VMStore,
427    ) -> Result<Self> {
428        let (minimum, maximum) = Self::limit_new(ty, store)?;
429        match wasm_to_table_type(ty.ref_type) {
430            TableElementType::Func => Ok(Self::from(DynamicFuncTable {
431                elements: unsafe { alloc_dynamic_table_elements(minimum)? },
432                maximum,
433                lazy_init: tunables.table_lazy_init,
434            })),
435            TableElementType::GcRef => Ok(Self::from(DynamicGcRefTable {
436                elements: unsafe { alloc_dynamic_table_elements(minimum)? },
437                maximum,
438            })),
439            TableElementType::Cont => Ok(Self::from(DynamicContTable {
440                elements: vec![None; minimum],
441                maximum,
442            })),
443        }
444    }
445
446    /// Create a new static (immovable) table instance for the specified table plan.
447    pub unsafe fn new_static(
448        ty: &wasmtime_environ::Table,
449        tunables: &Tunables,
450        data: SendSyncPtr<[u8]>,
451        store: &mut dyn VMStore,
452    ) -> Result<Self> {
453        let (minimum, maximum) = Self::limit_new(ty, store)?;
454        let size = minimum;
455        let max = maximum.unwrap_or(usize::MAX);
456
457        match wasm_to_table_type(ty.ref_type) {
458            TableElementType::Func => {
459                let len = {
460                    let data = data.as_non_null().as_ref();
461                    let (before, data, after) = data.align_to::<FuncTableElem>();
462                    assert!(before.is_empty());
463                    assert!(after.is_empty());
464                    data.len()
465                };
466                ensure!(
467                    usize::try_from(ty.limits.min).unwrap() <= len,
468                    "initial table size of {} exceeds the pooling allocator's \
469                     configured maximum table size of {len} elements",
470                    ty.limits.min,
471                );
472                let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
473                    data.as_non_null().cast::<FuncTableElem>(),
474                    cmp::min(len, max),
475                ));
476                Ok(Self::from(StaticFuncTable {
477                    data,
478                    size,
479                    lazy_init: tunables.table_lazy_init,
480                }))
481            }
482            TableElementType::GcRef => {
483                let len = {
484                    let data = data.as_non_null().as_ref();
485                    let (before, data, after) = data.align_to::<Option<VMGcRef>>();
486                    assert!(before.is_empty());
487                    assert!(after.is_empty());
488                    data.len()
489                };
490                ensure!(
491                    usize::try_from(ty.limits.min).unwrap() <= len,
492                    "initial table size of {} exceeds the pooling allocator's \
493                     configured maximum table size of {len} elements",
494                    ty.limits.min,
495                );
496                let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
497                    data.as_non_null().cast::<Option<VMGcRef>>(),
498                    cmp::min(len, max),
499                ));
500                Ok(Self::from(StaticGcRefTable { data, size }))
501            }
502            TableElementType::Cont => {
503                let len = {
504                    let data = data.as_non_null().as_ref();
505                    let (before, data, after) = data.align_to::<ContTableElem>();
506                    assert!(before.is_empty());
507                    assert!(after.is_empty());
508                    data.len()
509                };
510                ensure!(
511                    usize::try_from(ty.limits.min).unwrap() <= len,
512                    "initial table size of {} exceeds the pooling allocator's \
513                     configured maximum table size of {len} elements",
514                    ty.limits.min,
515                );
516                let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
517                    data.as_non_null().cast::<ContTableElem>(),
518                    cmp::min(len, max),
519                ));
520                Ok(Self::from(StaticContTable { data, size }))
521            }
522        }
523    }
524
525    // Calls the `store`'s limiter to optionally prevent the table from being created.
526    //
527    // Returns the minimum and maximum size of the table if the table can be created.
528    fn limit_new(
529        ty: &wasmtime_environ::Table,
530        store: &mut dyn VMStore,
531    ) -> Result<(usize, Option<usize>)> {
532        // No matter how the table limits are specified
533        // The table size is limited by the host's pointer size
534        let absolute_max = usize::MAX;
535
536        // If the minimum overflows the host's pointer size, then we can't satisfy this request.
537        // We defer the error to later so the `store` can be informed.
538        let minimum = usize::try_from(ty.limits.min).ok();
539
540        // The maximum size of the table is limited by:
541        // * the host's pointer size.
542        // * the table's maximum size if defined.
543        // * if the table is 64-bit.
544        let maximum = match (ty.limits.max, ty.idx_type) {
545            (Some(max), _) => usize::try_from(max).ok(),
546            (None, IndexType::I64) => usize::try_from(u64::MAX).ok(),
547            (None, IndexType::I32) => usize::try_from(u32::MAX).ok(),
548        };
549
550        // Inform the store's limiter what's about to happen.
551        if !store.table_growing(0, minimum.unwrap_or(absolute_max), maximum)? {
552            bail!(
553                "table minimum size of {} elements exceeds table limits",
554                ty.limits.min
555            );
556        }
557
558        // At this point we need to actually handle overflows, so bail out with
559        // an error if we made it this far.
560        let minimum = minimum.ok_or_else(|| {
561            format_err!(
562                "table minimum size of {} elements exceeds table limits",
563                ty.limits.min
564            )
565        })?;
566        Ok((minimum, maximum))
567    }
568
569    /// Returns the type of the elements in this table.
570    pub fn element_type(&self) -> TableElementType {
571        match self {
572            Table::Static(StaticTable::Func(_)) | Table::Dynamic(DynamicTable::Func(_)) => {
573                TableElementType::Func
574            }
575            Table::Static(StaticTable::GcRef(_)) | Table::Dynamic(DynamicTable::GcRef(_)) => {
576                TableElementType::GcRef
577            }
578            Table::Static(StaticTable::Cont(_)) | Table::Dynamic(DynamicTable::Cont(_)) => {
579                TableElementType::Cont
580            }
581        }
582    }
583
584    /// Returns whether or not the underlying storage of the table is "static".
585    #[cfg(feature = "pooling-allocator")]
586    pub(crate) fn is_static(&self) -> bool {
587        matches!(self, Table::Static(_))
588    }
589
590    /// Returns the number of allocated elements.
591    pub fn size(&self) -> usize {
592        match self {
593            Table::Static(StaticTable::Func(StaticFuncTable { size, .. })) => *size,
594            Table::Static(StaticTable::GcRef(StaticGcRefTable { size, .. })) => *size,
595            Table::Static(StaticTable::Cont(StaticContTable { size, .. })) => *size,
596            Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => elements.len(),
597            Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
598                elements.len()
599            }
600            Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => elements.len(),
601        }
602    }
603
604    /// Returns the maximum number of elements at runtime.
605    ///
606    /// Returns `None` if the table is unbounded.
607    ///
608    /// The runtime maximum may not be equal to the maximum from the table's Wasm type
609    /// when it is being constrained by an instance allocator.
610    pub fn maximum(&self) -> Option<usize> {
611        match self {
612            Table::Static(StaticTable::Cont(StaticContTable { data, .. })) => Some(data.len()),
613            Table::Static(StaticTable::Func(StaticFuncTable { data, .. })) => Some(data.len()),
614            Table::Static(StaticTable::GcRef(StaticGcRefTable { data, .. })) => Some(data.len()),
615            Table::Dynamic(DynamicTable::Func(DynamicFuncTable { maximum, .. })) => *maximum,
616            Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { maximum, .. })) => *maximum,
617            Table::Dynamic(DynamicTable::Cont(DynamicContTable { maximum, .. })) => *maximum,
618        }
619    }
620
621    /// Initializes the contents of this table to the specified function
622    ///
623    /// # Panics
624    ///
625    /// Panics if the table is not a function table.
626    pub fn init_func(
627        &mut self,
628        dst: u64,
629        items: impl ExactSizeIterator<Item = Option<NonNull<VMFuncRef>>>,
630    ) -> Result<(), Trap> {
631        let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
632
633        let (funcrefs, lazy_init) = self.funcrefs_mut();
634        let elements = funcrefs
635            .get_mut(dst..)
636            .and_then(|s| s.get_mut(..items.len()))
637            .ok_or(Trap::TableOutOfBounds)?;
638
639        for (item, slot) in items.zip(elements) {
640            *slot = MaybeTaggedFuncRef::from(item, lazy_init);
641        }
642        Ok(())
643    }
644
645    /// Fill `table[dst..]` with values from `items`
646    ///
647    /// Returns a trap error on out-of-bounds accesses.
648    pub fn init_gc_refs(
649        &mut self,
650        dst: u64,
651        items: impl ExactSizeIterator<Item = Option<VMGcRef>>,
652    ) -> Result<(), Trap> {
653        let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
654
655        let elements = self
656            .gc_refs_mut()
657            .get_mut(dst..)
658            .and_then(|s| s.get_mut(..items.len()))
659            .ok_or(Trap::TableOutOfBounds)?;
660
661        for (item, slot) in items.zip(elements) {
662            *slot = item;
663        }
664        Ok(())
665    }
666
667    /// Fill `table[dst..dst + len]` with `val`.
668    ///
669    /// Returns a trap error on out-of-bounds accesses.
670    ///
671    /// # Panics
672    ///
673    /// Panics if `val` does not have a type that matches this table, or if
674    /// `gc_store.is_none()` and this is a table of GC references.
675    pub fn fill(
676        &mut self,
677        mut gc_store: Option<&mut GcStore>,
678        dst: u64,
679        val: TableElement,
680        len: u64,
681    ) -> Result<(), Trap> {
682        let start = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
683        let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
684        let end = start
685            .checked_add(len)
686            .ok_or_else(|| Trap::TableOutOfBounds)?;
687
688        if end > self.size() {
689            return Err(Trap::TableOutOfBounds);
690        }
691
692        match val {
693            TableElement::FuncRef(f) => {
694                let (funcrefs, lazy_init) = self.funcrefs_mut();
695                funcrefs[start..end].fill(MaybeTaggedFuncRef::from(f, lazy_init));
696            }
697            TableElement::GcRef(r) => {
698                // Clone the init GC reference into each table slot.
699                for slot in &mut self.gc_refs_mut()[start..end] {
700                    match gc_store.as_deref_mut() {
701                        Some(s) => s.write_gc_ref(slot, r.as_ref()),
702                        None => {
703                            debug_assert!(slot.as_ref().is_none_or(|x| x.is_i31()));
704                            debug_assert!(r.as_ref().is_none_or(|r| r.is_i31()));
705                            *slot = r.as_ref().map(|r| r.copy_i31());
706                        }
707                    }
708                }
709
710                // Drop the init GC reference, since we aren't holding onto this
711                // reference anymore, only the clones in the table.
712                if let Some(r) = r {
713                    match gc_store {
714                        Some(s) => s.drop_gc_ref(r),
715                        None => debug_assert!(r.is_i31()),
716                    }
717                }
718            }
719            TableElement::UninitFunc => {
720                let (funcrefs, _lazy_init) = self.funcrefs_mut();
721                funcrefs[start..end].fill(MaybeTaggedFuncRef::UNINIT);
722            }
723            TableElement::ContRef(c) => {
724                let contrefs = self.contrefs_mut();
725                contrefs[start..end].fill(c);
726            }
727        }
728
729        Ok(())
730    }
731
732    /// Grow table by the specified amount of elements.
733    ///
734    /// Returns the previous size of the table if growth is successful.
735    ///
736    /// Returns `None` if table can't be grown by the specified amount of
737    /// elements, or if the `init_value` is the wrong kind of table element.
738    ///
739    /// # Panics
740    ///
741    /// Panics if `init_value` does not have a type that matches this table.
742    ///
743    /// # Unsafety
744    ///
745    /// Resizing the table can reallocate its internal elements buffer. This
746    /// table's instance's `VMContext` has raw pointers to the elements buffer
747    /// that are used by Wasm, and they need to be fixed up before we call into
748    /// Wasm again. Failure to do so will result in use-after-free inside Wasm.
749    ///
750    /// Generally, prefer using `InstanceHandle::table_grow`, which encapsulates
751    /// this unsafety.
752    pub unsafe fn grow(
753        &mut self,
754        delta: u64,
755        init_value: TableElement,
756        store: &mut dyn VMStore,
757    ) -> Result<Option<usize>, Error> {
758        let old_size = self.size();
759
760        // Don't try to resize the table if its size isn't changing, just return
761        // success.
762        if delta == 0 {
763            return Ok(Some(old_size));
764        }
765        let delta = usize::try_from(delta).map_err(|_| Trap::TableOutOfBounds)?;
766
767        let new_size = match old_size.checked_add(delta) {
768            Some(s) => s,
769            None => {
770                store.table_grow_failed(format_err!("overflow calculating new table size"))?;
771                return Ok(None);
772            }
773        };
774
775        if !store.table_growing(old_size, new_size, self.maximum())? {
776            return Ok(None);
777        }
778
779        // The WebAssembly spec requires failing a `table.grow` request if
780        // it exceeds the declared limits of the table. We may have set lower
781        // limits in the instance allocator as well.
782        if let Some(max) = self.maximum() {
783            if new_size > max {
784                store.table_grow_failed(format_err!("Table maximum size exceeded"))?;
785                return Ok(None);
786            }
787        }
788
789        debug_assert!(self.type_matches(&init_value));
790
791        // First resize the storage and then fill with the init value
792        match self {
793            Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => {
794                unsafe {
795                    debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
796                }
797                *size = new_size;
798            }
799            Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
800                unsafe {
801                    debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
802                }
803                *size = new_size;
804            }
805            Table::Static(StaticTable::Cont(StaticContTable { data, size })) => {
806                unsafe {
807                    debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
808                }
809                *size = new_size;
810            }
811
812            // These calls to `resize` could move the base address of
813            // `elements`. If this table's limits declare it to be fixed-size,
814            // then during AOT compilation we may have promised Cranelift that
815            // the table base address won't change, so it is allowed to optimize
816            // loading the base address. However, in that case the above checks
817            // that delta is non-zero and the new size doesn't exceed the
818            // maximum mean we can't get here.
819            Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
820                elements.resize(new_size, None);
821            }
822            Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
823                elements.resize_with(new_size, || None);
824            }
825            Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => {
826                elements.resize(new_size, None);
827            }
828        }
829
830        self.fill(
831            store.store_opaque_mut().optional_gc_store_mut(),
832            u64::try_from(old_size).unwrap(),
833            init_value,
834            u64::try_from(delta).unwrap(),
835        )
836        .expect("table should not be out of bounds");
837
838        Ok(Some(old_size))
839    }
840
841    /// Get reference to the specified element.
842    ///
843    /// Returns `None` if the index is out of bounds.
844    ///
845    /// Panics if this is a table of GC references and `gc_store` is `None`.
846    pub fn get(&self, gc_store: Option<&mut GcStore>, index: u64) -> Option<TableElement> {
847        let index = usize::try_from(index).ok()?;
848        match self.element_type() {
849            TableElementType::Func => {
850                let (funcrefs, lazy_init) = self.funcrefs();
851                funcrefs
852                    .get(index)
853                    .copied()
854                    .map(|e| e.into_table_element(lazy_init))
855            }
856            TableElementType::GcRef => self.gc_refs().get(index).map(|r| {
857                let r = r.as_ref().map(|r| match gc_store {
858                    Some(s) => s.clone_gc_ref(r),
859                    None => r.copy_i31(),
860                });
861
862                TableElement::GcRef(r)
863            }),
864            TableElementType::Cont => self
865                .contrefs()
866                .get(index)
867                .copied()
868                .map(|e| TableElement::ContRef(e)),
869        }
870    }
871
872    /// Set reference to the specified element.
873    ///
874    /// # Errors
875    ///
876    /// Returns an error if `index` is out of bounds or if this table type does
877    /// not match the element type.
878    ///
879    /// # Panics
880    ///
881    /// Panics if `elem` is not of the right type for this table.
882    pub fn set(&mut self, index: u64, elem: TableElement) -> Result<(), ()> {
883        let index: usize = index.try_into().map_err(|_| ())?;
884        match elem {
885            TableElement::FuncRef(f) => {
886                let (funcrefs, lazy_init) = self.funcrefs_mut();
887                *funcrefs.get_mut(index).ok_or(())? = MaybeTaggedFuncRef::from(f, lazy_init);
888            }
889            TableElement::UninitFunc => {
890                let (funcrefs, _lazy_init) = self.funcrefs_mut();
891                *funcrefs.get_mut(index).ok_or(())? = MaybeTaggedFuncRef::UNINIT;
892            }
893            TableElement::GcRef(e) => {
894                *self.gc_refs_mut().get_mut(index).ok_or(())? = e;
895            }
896            TableElement::ContRef(c) => {
897                *self.contrefs_mut().get_mut(index).ok_or(())? = c;
898            }
899        }
900        Ok(())
901    }
902
903    /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`.
904    ///
905    /// # Errors
906    ///
907    /// Returns an error if the range is out of bounds of either the source or
908    /// destination tables.
909    pub unsafe fn copy(
910        gc_store: Option<&mut GcStore>,
911        dst_table: *mut Self,
912        src_table: *mut Self,
913        dst_index: u64,
914        src_index: u64,
915        len: u64,
916    ) -> Result<(), Trap> {
917        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
918
919        let src_index = usize::try_from(src_index).map_err(|_| Trap::TableOutOfBounds)?;
920        let dst_index = usize::try_from(dst_index).map_err(|_| Trap::TableOutOfBounds)?;
921        let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
922
923        if src_index
924            .checked_add(len)
925            .map_or(true, |n| n > (*src_table).size())
926            || dst_index
927                .checked_add(len)
928                .map_or(true, |m| m > (*dst_table).size())
929        {
930            return Err(Trap::TableOutOfBounds);
931        }
932
933        debug_assert!(
934            (*dst_table).element_type() == (*src_table).element_type(),
935            "table element type mismatch"
936        );
937
938        let src_range = src_index..src_index + len;
939        let dst_range = dst_index..dst_index + len;
940
941        // Check if the tables are the same as we cannot mutably borrow and also borrow the same `RefCell`
942        if ptr::eq(dst_table, src_table) {
943            (*dst_table).copy_elements_within(gc_store, dst_range, src_range);
944        } else {
945            Self::copy_elements(gc_store, &mut *dst_table, &*src_table, dst_range, src_range);
946        }
947
948        Ok(())
949    }
950
951    /// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
952    pub fn vmtable(&mut self) -> VMTableDefinition {
953        match self {
954            Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => {
955                VMTableDefinition {
956                    base: data.cast().into(),
957                    current_elements: *size,
958                }
959            }
960            Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
961                VMTableDefinition {
962                    base: data.cast().into(),
963                    current_elements: *size,
964                }
965            }
966            Table::Static(StaticTable::Cont(StaticContTable { data, size })) => VMTableDefinition {
967                base: data.cast().into(),
968                current_elements: *size,
969            },
970            Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
971                VMTableDefinition {
972                    base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
973                    current_elements: elements.len(),
974                }
975            }
976            Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
977                VMTableDefinition {
978                    base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
979                    current_elements: elements.len(),
980                }
981            }
982            Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => {
983                VMTableDefinition {
984                    base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
985                    current_elements: elements.len(),
986                }
987            }
988        }
989    }
990
991    fn type_matches(&self, val: &TableElement) -> bool {
992        self.element_type().matches(val)
993    }
994
995    fn funcrefs(&self) -> (&[MaybeTaggedFuncRef], bool) {
996        assert_eq!(self.element_type(), TableElementType::Func);
997        match self {
998            Self::Dynamic(DynamicTable::Func(DynamicFuncTable {
999                elements,
1000                lazy_init,
1001                ..
1002            })) => (
1003                unsafe { slice::from_raw_parts(elements.as_ptr().cast(), elements.len()) },
1004                *lazy_init,
1005            ),
1006            Self::Static(StaticTable::Func(StaticFuncTable {
1007                data,
1008                size,
1009                lazy_init,
1010            })) => (
1011                unsafe { slice::from_raw_parts(data.as_ptr().cast(), *size) },
1012                *lazy_init,
1013            ),
1014            _ => unreachable!(),
1015        }
1016    }
1017
1018    fn funcrefs_mut(&mut self) -> (&mut [MaybeTaggedFuncRef], bool) {
1019        assert_eq!(self.element_type(), TableElementType::Func);
1020        match self {
1021            Self::Dynamic(DynamicTable::Func(DynamicFuncTable {
1022                elements,
1023                lazy_init,
1024                ..
1025            })) => (
1026                unsafe { slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len()) },
1027                *lazy_init,
1028            ),
1029            Self::Static(StaticTable::Func(StaticFuncTable {
1030                data,
1031                size,
1032                lazy_init,
1033            })) => (
1034                unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), *size) },
1035                *lazy_init,
1036            ),
1037            _ => unreachable!(),
1038        }
1039    }
1040
1041    fn gc_refs(&self) -> &[Option<VMGcRef>] {
1042        assert_eq!(self.element_type(), TableElementType::GcRef);
1043        match self {
1044            Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements,
1045            Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe {
1046                &data.as_non_null().as_ref()[..*size]
1047            },
1048            _ => unreachable!(),
1049        }
1050    }
1051
1052    fn contrefs(&self) -> &[Option<VMContObj>] {
1053        assert_eq!(self.element_type(), TableElementType::Cont);
1054        match self {
1055            Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe {
1056                slice::from_raw_parts(elements.as_ptr().cast(), elements.len())
1057            },
1058            Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe {
1059                slice::from_raw_parts(data.as_ptr().cast(), *size)
1060            },
1061            _ => unreachable!(),
1062        }
1063    }
1064
1065    fn contrefs_mut(&mut self) -> &mut [Option<VMContObj>] {
1066        assert_eq!(self.element_type(), TableElementType::Cont);
1067        match self {
1068            Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe {
1069                slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len())
1070            },
1071            Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe {
1072                slice::from_raw_parts_mut(data.as_ptr().cast(), *size)
1073            },
1074            _ => unreachable!(),
1075        }
1076    }
1077
1078    /// Get this table's GC references as a slice.
1079    ///
1080    /// Panics if this is not a table of GC references.
1081    pub fn gc_refs_mut(&mut self) -> &mut [Option<VMGcRef>] {
1082        assert_eq!(self.element_type(), TableElementType::GcRef);
1083        match self {
1084            Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements,
1085            Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe {
1086                &mut data.as_non_null().as_mut()[..*size]
1087            },
1088            _ => unreachable!(),
1089        }
1090    }
1091
1092    fn copy_elements(
1093        gc_store: Option<&mut GcStore>,
1094        dst_table: &mut Self,
1095        src_table: &Self,
1096        dst_range: Range<usize>,
1097        src_range: Range<usize>,
1098    ) {
1099        // This can only be used when copying between different tables
1100        debug_assert!(!ptr::eq(dst_table, src_table));
1101
1102        let ty = dst_table.element_type();
1103
1104        match ty {
1105            TableElementType::Func => {
1106                // `funcref` are `Copy`, so just do a mempcy
1107                let (dst_funcrefs, _lazy_init) = dst_table.funcrefs_mut();
1108                let (src_funcrefs, _lazy_init) = src_table.funcrefs();
1109                dst_funcrefs[dst_range].copy_from_slice(&src_funcrefs[src_range]);
1110            }
1111            TableElementType::GcRef => {
1112                assert_eq!(
1113                    dst_range.end - dst_range.start,
1114                    src_range.end - src_range.start
1115                );
1116                assert!(dst_range.end <= dst_table.gc_refs().len());
1117                assert!(src_range.end <= src_table.gc_refs().len());
1118                let gc_store = gc_store.unwrap();
1119                for (dst, src) in dst_range.zip(src_range) {
1120                    gc_store.write_gc_ref(
1121                        &mut dst_table.gc_refs_mut()[dst],
1122                        src_table.gc_refs()[src].as_ref(),
1123                    );
1124                }
1125            }
1126            TableElementType::Cont => {
1127                // `contref` are `Copy`, so just do a mempcy
1128                dst_table.contrefs_mut()[dst_range]
1129                    .copy_from_slice(&src_table.contrefs()[src_range]);
1130            }
1131        }
1132    }
1133
1134    fn copy_elements_within(
1135        &mut self,
1136        gc_store: Option<&mut GcStore>,
1137        dst_range: Range<usize>,
1138        src_range: Range<usize>,
1139    ) {
1140        assert_eq!(
1141            dst_range.end - dst_range.start,
1142            src_range.end - src_range.start
1143        );
1144
1145        // This is a no-op.
1146        if src_range.start == dst_range.start {
1147            return;
1148        }
1149
1150        let ty = self.element_type();
1151        match ty {
1152            TableElementType::Func => {
1153                // `funcref` are `Copy`, so just do a memmove
1154                let (funcrefs, _lazy_init) = self.funcrefs_mut();
1155                funcrefs.copy_within(src_range, dst_range.start);
1156            }
1157            TableElementType::GcRef => {
1158                let gc_store = gc_store.unwrap();
1159
1160                // We need to clone each `externref` while handling overlapping
1161                // ranges
1162                let elements = self.gc_refs_mut();
1163                if dst_range.start < src_range.start {
1164                    for (d, s) in dst_range.zip(src_range) {
1165                        let (ds, ss) = elements.split_at_mut(s);
1166                        let dst = &mut ds[d];
1167                        let src = ss[0].as_ref();
1168                        gc_store.write_gc_ref(dst, src);
1169                    }
1170                } else {
1171                    for (s, d) in src_range.rev().zip(dst_range.rev()) {
1172                        let (ss, ds) = elements.split_at_mut(d);
1173                        let dst = &mut ds[0];
1174                        let src = ss[s].as_ref();
1175                        gc_store.write_gc_ref(dst, src);
1176                    }
1177                }
1178            }
1179            TableElementType::Cont => {
1180                // `contref` are `Copy`, so just do a memmove
1181                self.contrefs_mut().copy_within(src_range, dst_range.start);
1182            }
1183        }
1184    }
1185}
1186
1187// The default table representation is an empty funcref table that cannot grow.
1188impl Default for Table {
1189    fn default() -> Self {
1190        Self::from(StaticFuncTable {
1191            data: SendSyncPtr::new(NonNull::from(&mut [])),
1192            size: 0,
1193            lazy_init: false,
1194        })
1195    }
1196}