Skip to main content

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