wasmer_vm/
table.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/main/docs/ATTRIBUTIONS.md
3
4//! Memory management for tables.
5//!
6//! `Table` is to WebAssembly tables what `Memory` is to WebAssembly linear memories.
7
8use crate::Trap;
9use crate::VMExternRef;
10use crate::VMFuncRef;
11use crate::store::MaybeInstanceOwned;
12use crate::vmcontext::VMTableDefinition;
13use bytesize::ByteSize;
14use std::cell::UnsafeCell;
15use std::convert::TryFrom;
16use std::fmt;
17use std::ptr::NonNull;
18use wasmer_types::TableStyle;
19use wasmer_types::{TableType, TrapCode, Type as ValType};
20
21/// A reference stored in a table. Can be either an externref or a funcref.
22#[derive(Debug, Clone)]
23pub enum TableElement {
24    /// Opaque pointer to arbitrary hostdata.
25    ExternRef(Option<VMExternRef>),
26    /// Pointer to function: contains enough information to call it.
27    FuncRef(Option<VMFuncRef>),
28}
29
30impl From<TableElement> for RawTableElement {
31    fn from(other: TableElement) -> Self {
32        match other {
33            TableElement::ExternRef(extern_ref) => Self { extern_ref },
34            TableElement::FuncRef(func_ref) => Self { func_ref },
35        }
36    }
37}
38
39#[repr(C)]
40#[derive(Clone, Copy)]
41pub union RawTableElement {
42    pub(crate) extern_ref: Option<VMExternRef>,
43    pub(crate) func_ref: Option<VMFuncRef>,
44}
45
46#[cfg(test)]
47#[test]
48fn table_element_size_test() {
49    use std::mem::size_of;
50    assert_eq!(size_of::<RawTableElement>(), size_of::<VMExternRef>());
51    assert_eq!(size_of::<RawTableElement>(), size_of::<VMFuncRef>());
52}
53
54impl fmt::Debug for RawTableElement {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        f.debug_struct("RawTableElement").finish()
57    }
58}
59
60impl Default for RawTableElement {
61    fn default() -> Self {
62        Self { func_ref: None }
63    }
64}
65
66impl Default for TableElement {
67    fn default() -> Self {
68        Self::FuncRef(None)
69    }
70}
71
72const TABLE_MAX_SIZE: usize = ByteSize::mib(128).as_u64() as usize;
73
74/// A table instance.
75#[derive(Debug)]
76pub struct VMTable {
77    vec: Vec<RawTableElement>,
78    maximum: Option<u32>,
79    /// The WebAssembly table description.
80    table: TableType,
81    /// Our chosen implementation style.
82    style: TableStyle,
83    vm_table_definition: MaybeInstanceOwned<VMTableDefinition>,
84}
85
86impl VMTable {
87    /// Create a new linear table instance with specified minimum and maximum number of elements.
88    ///
89    /// This creates a `Table` with metadata owned by a VM, pointed to by
90    /// `vm_table_location`: this can be used to create a local table.
91    pub fn new(table: &TableType, style: &TableStyle) -> Result<Self, String> {
92        unsafe { Self::new_inner(table, style, None) }
93    }
94
95    /// Returns the size of the table
96    pub fn get_runtime_size(&self) -> u32 {
97        self.vec.len() as u32
98    }
99
100    /// Create a new linear table instance with specified minimum and maximum number of elements.
101    ///
102    /// This creates a `Table` with metadata owned by a VM, pointed to by
103    /// `vm_table_location`: this can be used to create a local table.
104    ///
105    /// # Safety
106    /// - `vm_table_location` must point to a valid location in VM memory.
107    pub unsafe fn from_definition(
108        table: &TableType,
109        style: &TableStyle,
110        vm_table_location: NonNull<VMTableDefinition>,
111    ) -> Result<Self, String> {
112        unsafe { Self::new_inner(table, style, Some(vm_table_location)) }
113    }
114
115    /// Create a new `Table` with either self-owned or VM owned metadata.
116    unsafe fn new_inner(
117        table: &TableType,
118        style: &TableStyle,
119        vm_table_location: Option<NonNull<VMTableDefinition>>,
120    ) -> Result<Self, String> {
121        unsafe {
122            match table.ty {
123                ValType::FuncRef | ValType::ExternRef => (),
124                ty => {
125                    return Err(format!(
126                        "tables of types other than funcref or externref ({ty})",
127                    ));
128                }
129            };
130            if let Some(max) = table.maximum
131                && max < table.minimum
132            {
133                return Err(format!(
134                    "Table minimum ({}) is larger than maximum ({})!",
135                    table.minimum, max
136                ));
137            }
138            if table.minimum as usize > TABLE_MAX_SIZE {
139                return Err(format!(
140                    "Table minimum ({}) is larger than maximum allowed size ({TABLE_MAX_SIZE})!",
141                    table.minimum
142                ));
143            }
144            if let Some(max) = table.maximum
145                && max as usize > TABLE_MAX_SIZE
146            {
147                return Err(format!(
148                    "Table maximum ({max}) is larger than maximum allowed size ({TABLE_MAX_SIZE})!",
149                ));
150            }
151            let table_minimum = usize::try_from(table.minimum)
152                .map_err(|_| "Table minimum is bigger than usize".to_string())?;
153            let mut vec = vec![RawTableElement::default(); table_minimum];
154            let base = vec.as_mut_ptr();
155            match style {
156                TableStyle::CallerChecksSignature => Ok(Self {
157                    vec,
158                    maximum: table.maximum,
159                    table: *table,
160                    style: style.clone(),
161                    vm_table_definition: if let Some(table_loc) = vm_table_location {
162                        {
163                            let mut ptr = table_loc;
164                            let td = ptr.as_mut();
165                            td.base = base as _;
166                            td.current_elements = table_minimum as _;
167                        }
168                        MaybeInstanceOwned::Instance(table_loc)
169                    } else {
170                        MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMTableDefinition {
171                            base: base as _,
172                            current_elements: table_minimum as _,
173                        })))
174                    },
175                }),
176            }
177        }
178    }
179
180    /// Get the `VMTableDefinition`.
181    fn get_vm_table_definition(&self) -> NonNull<VMTableDefinition> {
182        self.vm_table_definition.as_ptr()
183    }
184
185    /// Returns the type for this Table.
186    pub fn ty(&self) -> &TableType {
187        &self.table
188    }
189
190    /// Returns the style for this Table.
191    pub fn style(&self) -> &TableStyle {
192        &self.style
193    }
194
195    /// Returns the number of allocated elements.
196    pub fn size(&self) -> u32 {
197        // TODO: investigate this function for race conditions
198        unsafe {
199            let td_ptr = self.get_vm_table_definition();
200            let td = td_ptr.as_ref();
201            td.current_elements
202        }
203    }
204
205    /// Grow table by the specified amount of elements.
206    ///
207    /// Returns `None` if table can't be grown by the specified amount
208    /// of elements, otherwise returns the previous size of the table.
209    pub fn grow(&mut self, delta: u32, init_value: TableElement) -> Option<u32> {
210        if self.table.readonly {
211            // Cannot grow a readonly fixed table.
212            return None;
213        }
214        let size = self.size();
215        let new_len = size.checked_add(delta)?;
216        if self.maximum.is_some_and(|max| new_len > max) {
217            return None;
218        }
219        if new_len == size {
220            debug_assert_eq!(delta, 0);
221            return Some(size);
222        }
223
224        self.vec
225            .resize(usize::try_from(new_len).unwrap(), init_value.into());
226
227        // update table definition
228        unsafe {
229            let mut td_ptr = self.get_vm_table_definition();
230            let td = td_ptr.as_mut();
231            td.current_elements = new_len;
232            td.base = self.vec.as_mut_ptr() as _;
233        }
234        Some(size)
235    }
236
237    /// Get reference to the specified element.
238    ///
239    /// Returns `None` if the index is out of bounds.
240    pub fn get(&self, index: u32) -> Option<TableElement> {
241        let raw_data = self.vec.get(index as usize).cloned()?;
242        Some(match self.table.ty {
243            ValType::ExternRef => TableElement::ExternRef(unsafe { raw_data.extern_ref }),
244            ValType::FuncRef => TableElement::FuncRef(unsafe { raw_data.func_ref }),
245            _ => todo!("getting invalid type from table, handle this error"),
246        })
247    }
248
249    /// Set reference to the specified element.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if the index is out of bounds.
254    pub fn set(&mut self, index: u32, reference: TableElement) -> Result<(), Trap> {
255        self.set_with_construction(index, reference, false)
256    }
257
258    pub(crate) fn set_with_construction(
259        &mut self,
260        index: u32,
261        reference: TableElement,
262        in_construction: bool,
263    ) -> Result<(), Trap> {
264        if !in_construction && self.table.readonly {
265            return Err(Trap::lib(TrapCode::ReadonlyTableModified));
266        }
267        match self.vec.get_mut(index as usize) {
268            Some(slot) => {
269                match (self.table.ty, reference) {
270                    (ValType::ExternRef, r @ TableElement::ExternRef(_)) => {
271                        *slot = r.into();
272                    }
273                    (ValType::FuncRef, r @ TableElement::FuncRef(_)) => {
274                        *slot = r.into();
275                    }
276                    // This path should never be hit by the generated code due to Wasm
277                    // validation.
278                    (ty, v) => {
279                        panic!("Attempted to set a table of type {ty} with the value {v:?}")
280                    }
281                };
282
283                Ok(())
284            }
285            None => Err(Trap::lib(TrapCode::TableAccessOutOfBounds)),
286        }
287    }
288
289    /// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
290    pub fn vmtable(&self) -> NonNull<VMTableDefinition> {
291        self.get_vm_table_definition()
292    }
293
294    /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`.
295    ///
296    /// # Errors
297    ///
298    /// Returns an error if the range is out of bounds of either the source or
299    /// destination tables.
300    pub fn copy(
301        &mut self,
302        src_table: &Self,
303        dst_index: u32,
304        src_index: u32,
305        len: u32,
306    ) -> Result<(), Trap> {
307        if self.table.readonly {
308            return Err(Trap::lib(TrapCode::ReadonlyTableModified));
309        }
310
311        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
312
313        if src_index
314            .checked_add(len)
315            .is_none_or(|n| n > src_table.size())
316        {
317            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
318        }
319
320        if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
321            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
322        }
323
324        let srcs = src_index..src_index + len;
325        let dsts = dst_index..dst_index + len;
326
327        // Note on the unwraps: the bounds check above means that these will
328        // never panic.
329        //
330        // TODO: investigate replacing this get/set loop with a `memcpy`.
331        if dst_index <= src_index {
332            for (s, d) in (srcs).zip(dsts) {
333                self.set(d, src_table.get(s).unwrap())?;
334            }
335        } else {
336            for (s, d) in srcs.rev().zip(dsts.rev()) {
337                self.set(d, src_table.get(s).unwrap())?;
338            }
339        }
340
341        Ok(())
342    }
343
344    /// Copies the table into a new table
345    pub fn copy_on_write(&self) -> Result<Self, String> {
346        let mut ret = Self::new(&self.table, &self.style)?;
347        ret.copy(self, 0, 0, self.size())
348            .map_err(|trap| format!("failed to copy the table - {trap:?}"))?;
349        Ok(ret)
350    }
351
352    /// Copy `len` elements from `table[src_index..]` to `table[dst_index..]`.
353    ///
354    /// # Errors
355    ///
356    /// Returns an error if the range is out of bounds of either the source or
357    /// destination tables.
358    pub fn copy_within(&mut self, dst_index: u32, src_index: u32, len: u32) -> Result<(), Trap> {
359        if self.table.readonly {
360            return Err(Trap::lib(TrapCode::ReadonlyTableModified));
361        }
362
363        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
364
365        if src_index.checked_add(len).is_none_or(|n| n > self.size()) {
366            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
367        }
368
369        if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
370            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
371        }
372
373        let srcs = src_index..src_index + len;
374        let dsts = dst_index..dst_index + len;
375
376        // Note on the unwraps: the bounds check above means that these will
377        // never panic.
378        //
379        // TODO: investigate replacing this get/set loop with a `memcpy`.
380        if dst_index <= src_index {
381            for (s, d) in (srcs).zip(dsts) {
382                self.set(d, self.get(s).unwrap())?;
383            }
384        } else {
385            for (s, d) in srcs.rev().zip(dsts.rev()) {
386                self.set(d, self.get(s).unwrap())?;
387            }
388        }
389
390        Ok(())
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::{TableElement, VMTable};
397    use wasmer_types::{TableStyle, TableType, Type};
398
399    #[test]
400    fn readonly_table_rejects_grow() {
401        let mut ty = TableType::new(Type::FuncRef, 0, Some(0));
402        ty.readonly = true;
403        let mut table = VMTable::new(&ty, &TableStyle::CallerChecksSignature).unwrap();
404        assert_eq!(table.grow(0, TableElement::FuncRef(None)), None);
405    }
406}