wasmer_vm/
table.rs

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