linera_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::store::MaybeInstanceOwned;
9use crate::vmcontext::VMTableDefinition;
10use crate::Trap;
11use crate::VMExternRef;
12use crate::VMFuncRef;
13use derivative::Derivative;
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 host data.
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
72/// A table instance.
73#[derive(Derivative)]
74#[derivative(Debug)]
75pub struct VMTable {
76    #[derivative(Debug = "ignore")]
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    #[derivative(Debug = "ignore")]
84    vm_table_definition: MaybeInstanceOwned<VMTableDefinition>,
85}
86
87impl VMTable {
88    /// Create a new linear table instance with specified minimum and maximum number of elements.
89    ///
90    /// This creates a `Table` with metadata owned by a VM, pointed to by
91    /// `vm_table_location`: this can be used to create a local table.
92    pub fn new(table: &TableType, style: &TableStyle) -> Result<Self, String> {
93        unsafe { Self::new_inner(table, style, None) }
94    }
95
96    /// Returns the size of the table
97    pub fn get_runtime_size(&self) -> u32 {
98        self.vec.len() as u32
99    }
100
101    /// Create a new linear table instance with specified minimum and maximum number of elements.
102    ///
103    /// This creates a `Table` with metadata owned by a VM, pointed to by
104    /// `vm_table_location`: this can be used to create a local table.
105    ///
106    /// # Safety
107    /// - `vm_table_location` must point to a valid location in VM memory.
108    pub unsafe fn from_definition(
109        table: &TableType,
110        style: &TableStyle,
111        vm_table_location: NonNull<VMTableDefinition>,
112    ) -> Result<Self, String> {
113        Self::new_inner(table, style, Some(vm_table_location))
114    }
115
116    /// Create a new `Table` with either self-owned or VM owned metadata.
117    unsafe fn new_inner(
118        table: &TableType,
119        style: &TableStyle,
120        vm_table_location: Option<NonNull<VMTableDefinition>>,
121    ) -> Result<Self, String> {
122        match table.ty {
123            ValType::FuncRef | ValType::ExternRef => (),
124            ty => {
125                return Err(format!(
126                    "tables of types other than funcref or externref ({})",
127                    ty
128                ))
129            }
130        };
131        if let Some(max) = table.maximum {
132            if max < table.minimum {
133                return Err(format!(
134                    "Table minimum ({}) is larger than maximum ({})!",
135                    table.minimum, max
136                ));
137            }
138        }
139        let table_minimum = usize::try_from(table.minimum)
140            .map_err(|_| "Table minimum is bigger than usize".to_string())?;
141        let mut vec = vec![RawTableElement::default(); table_minimum];
142        let base = vec.as_mut_ptr();
143        match style {
144            TableStyle::CallerChecksSignature => Ok(Self {
145                vec,
146                maximum: table.maximum,
147                table: *table,
148                style: style.clone(),
149                vm_table_definition: if let Some(table_loc) = vm_table_location {
150                    {
151                        let mut ptr = table_loc;
152                        let td = ptr.as_mut();
153                        td.base = base as _;
154                        td.current_elements = table_minimum as _;
155                    }
156                    MaybeInstanceOwned::Instance(table_loc)
157                } else {
158                    MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMTableDefinition {
159                        base: base as _,
160                        current_elements: table_minimum as _,
161                    })))
162                },
163            }),
164        }
165    }
166
167    /// Get the `VMTableDefinition`.
168    fn get_vm_table_definition(&self) -> NonNull<VMTableDefinition> {
169        self.vm_table_definition.as_ptr()
170    }
171
172    /// Returns the type for this Table.
173    pub fn ty(&self) -> &TableType {
174        &self.table
175    }
176
177    /// Returns the style for this Table.
178    pub fn style(&self) -> &TableStyle {
179        &self.style
180    }
181
182    /// Returns the number of allocated elements.
183    pub fn size(&self) -> u32 {
184        // TODO: investigate this function for race conditions
185        unsafe {
186            let td_ptr = self.get_vm_table_definition();
187            let td = td_ptr.as_ref();
188            td.current_elements
189        }
190    }
191
192    /// Grow table by the specified amount of elements.
193    ///
194    /// Returns `None` if table can't be grown by the specified amount
195    /// of elements, otherwise returns the previous size of the table.
196    pub fn grow(&mut self, delta: u32, init_value: TableElement) -> Option<u32> {
197        let size = self.size();
198        let new_len = size.checked_add(delta)?;
199        if self.maximum.map_or(false, |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        match self.vec.get_mut(index as usize) {
239            Some(slot) => {
240                match (self.table.ty, reference) {
241                    (ValType::ExternRef, r @ TableElement::ExternRef(_)) => {
242                        *slot = r.into();
243                    }
244                    (ValType::FuncRef, r @ TableElement::FuncRef(_)) => {
245                        *slot = r.into();
246                    }
247                    // This path should never be hit by the generated code due to Wasm
248                    // validation.
249                    (ty, v) => {
250                        panic!(
251                            "Attempted to set a table of type {} with the value {:?}",
252                            ty, v
253                        )
254                    }
255                };
256
257                Ok(())
258            }
259            None => Err(Trap::lib(TrapCode::TableAccessOutOfBounds)),
260        }
261    }
262
263    /// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
264    pub fn vmtable(&self) -> NonNull<VMTableDefinition> {
265        self.get_vm_table_definition()
266    }
267
268    /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`.
269    ///
270    /// # Errors
271    ///
272    /// Returns an error if the range is out of bounds of either the source or
273    /// destination tables.
274    pub fn copy(
275        &mut self,
276        src_table: &Self,
277        dst_index: u32,
278        src_index: u32,
279        len: u32,
280    ) -> Result<(), Trap> {
281        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
282
283        if src_index
284            .checked_add(len)
285            .map_or(true, |n| n > src_table.size())
286        {
287            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
288        }
289
290        if dst_index.checked_add(len).map_or(true, |m| m > self.size()) {
291            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
292        }
293
294        let srcs = src_index..src_index + len;
295        let dsts = dst_index..dst_index + len;
296
297        // Note on the unwraps: the bounds check above means that these will
298        // never panic.
299        //
300        // TODO: investigate replacing this get/set loop with a `memcpy`.
301        if dst_index <= src_index {
302            for (s, d) in (srcs).zip(dsts) {
303                self.set(d, src_table.get(s).unwrap())?;
304            }
305        } else {
306            for (s, d) in srcs.rev().zip(dsts.rev()) {
307                self.set(d, src_table.get(s).unwrap())?;
308            }
309        }
310
311        Ok(())
312    }
313
314    /// Copies the table into a new table
315    pub fn copy_on_write(&self) -> Result<Self, String> {
316        let mut ret = Self::new(&self.table, &self.style)?;
317        ret.copy(self, 0, 0, self.size())
318            .map_err(|trap| format!("failed to copy the table - {:?}", trap))?;
319        Ok(ret)
320    }
321
322    /// Copy `len` elements from `table[src_index..]` to `table[dst_index..]`.
323    ///
324    /// # Errors
325    ///
326    /// Returns an error if the range is out of bounds of either the source or
327    /// destination tables.
328    pub fn copy_within(&mut self, dst_index: u32, src_index: u32, len: u32) -> Result<(), Trap> {
329        // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
330
331        if src_index.checked_add(len).map_or(true, |n| n > self.size()) {
332            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
333        }
334
335        if dst_index.checked_add(len).map_or(true, |m| m > self.size()) {
336            return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
337        }
338
339        let srcs = src_index..src_index + len;
340        let dsts = dst_index..dst_index + len;
341
342        // Note on the unwraps: the bounds check above means that these will
343        // never panic.
344        //
345        // TODO: investigate replacing this get/set loop with a `memcpy`.
346        if dst_index <= src_index {
347            for (s, d) in (srcs).zip(dsts) {
348                self.set(d, self.get(s).unwrap())?;
349            }
350        } else {
351            for (s, d) in srcs.rev().zip(dsts.rev()) {
352                self.set(d, self.get(s).unwrap())?;
353            }
354        }
355
356        Ok(())
357    }
358}