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