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        let size = self.size();
211        let new_len = size.checked_add(delta)?;
212        if self.maximum.is_some_and(|max| new_len > max) {
213            return None;
214        }
215        if new_len == size {
216            debug_assert_eq!(delta, 0);
217            return Some(size);
218        }
219
220        self.vec
221            .resize(usize::try_from(new_len).unwrap(), init_value.into());
222
223        // update table definition
224        unsafe {
225            let mut td_ptr = self.get_vm_table_definition();
226            let td = td_ptr.as_mut();
227            td.current_elements = new_len;
228            td.base = self.vec.as_mut_ptr() as _;
229        }
230        Some(size)
231    }
232
233    /// Get reference to the specified element.
234    ///
235    /// Returns `None` if the index is out of bounds.
236    pub fn get(&self, index: u32) -> Option<TableElement> {
237        let raw_data = self.vec.get(index as usize).cloned()?;
238        Some(match self.table.ty {
239            ValType::ExternRef => TableElement::ExternRef(unsafe { raw_data.extern_ref }),
240            ValType::FuncRef => TableElement::FuncRef(unsafe { raw_data.func_ref }),
241            _ => todo!("getting invalid type from table, handle this error"),
242        })
243    }
244
245    /// Set reference to the specified element.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if the index is out of bounds.
250    pub fn set(&mut self, index: u32, reference: TableElement) -> Result<(), Trap> {
251        match self.vec.get_mut(index as usize) {
252            Some(slot) => {
253                match (self.table.ty, reference) {
254                    (ValType::ExternRef, r @ TableElement::ExternRef(_)) => {
255                        *slot = r.into();
256                    }
257                    (ValType::FuncRef, r @ TableElement::FuncRef(_)) => {
258                        *slot = r.into();
259                    }
260                    // This path should never be hit by the generated code due to Wasm
261                    // validation.
262                    (ty, v) => {
263                        panic!("Attempted to set a table of type {ty} with the value {v:?}")
264                    }
265                };
266
267                Ok(())
268            }
269            None => Err(Trap::lib(TrapCode::TableAccessOutOfBounds)),
270        }
271    }
272
273    /// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
274    pub fn vmtable(&self) -> NonNull<VMTableDefinition> {
275        self.get_vm_table_definition()
276    }
277
278    /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`.
279    ///
280    /// # Errors
281    ///
282    /// Returns an error if the range is out of bounds of either the source or
283    /// destination tables.
284    pub fn copy(
285        &mut self,
286        src_table: &Self,
287        dst_index: u32,
288        src_index: u32,
289        len: u32,
290    ) -> Result<(), Trap> {
291        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
292
293        if src_index
294            .checked_add(len)
295            .is_none_or(|n| n > src_table.size())
296        {
297            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
298        }
299
300        if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
301            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
302        }
303
304        let srcs = src_index..src_index + len;
305        let dsts = dst_index..dst_index + len;
306
307        // Note on the unwraps: the bounds check above means that these will
308        // never panic.
309        //
310        // TODO: investigate replacing this get/set loop with a `memcpy`.
311        if dst_index <= src_index {
312            for (s, d) in (srcs).zip(dsts) {
313                self.set(d, src_table.get(s).unwrap())?;
314            }
315        } else {
316            for (s, d) in srcs.rev().zip(dsts.rev()) {
317                self.set(d, src_table.get(s).unwrap())?;
318            }
319        }
320
321        Ok(())
322    }
323
324    /// Copies the table into a new table
325    pub fn copy_on_write(&self) -> Result<Self, String> {
326        let mut ret = Self::new(&self.table, &self.style)?;
327        ret.copy(self, 0, 0, self.size())
328            .map_err(|trap| format!("failed to copy the table - {trap:?}"))?;
329        Ok(ret)
330    }
331
332    /// Copy `len` elements from `table[src_index..]` to `table[dst_index..]`.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if the range is out of bounds of either the source or
337    /// destination tables.
338    pub fn copy_within(&mut self, dst_index: u32, src_index: u32, len: u32) -> Result<(), Trap> {
339        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
340
341        if src_index.checked_add(len).is_none_or(|n| n > self.size()) {
342            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
343        }
344
345        if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
346            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
347        }
348
349        let srcs = src_index..src_index + len;
350        let dsts = dst_index..dst_index + len;
351
352        // Note on the unwraps: the bounds check above means that these will
353        // never panic.
354        //
355        // TODO: investigate replacing this get/set loop with a `memcpy`.
356        if dst_index <= src_index {
357            for (s, d) in (srcs).zip(dsts) {
358                self.set(d, self.get(s).unwrap())?;
359            }
360        } else {
361            for (s, d) in srcs.rev().zip(dsts.rev()) {
362                self.set(d, self.get(s).unwrap())?;
363            }
364        }
365
366        Ok(())
367    }
368}