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