//! Memory management for tables. //! //! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition}; use crate::{Trap, VMExternRef}; use std::cell::RefCell; use std::convert::{TryFrom, TryInto}; use std::ptr; use wasmtime_environ::wasm::TableElementType; use wasmtime_environ::{ir, TablePlan, TableStyle}; /// A table instance. #[derive(Debug)] pub struct Table { elements: RefCell, maximum: Option, } /// An element going into or coming out of a table. #[derive(Clone, Debug)] pub enum TableElement { /// A `funcref`. FuncRef(*mut VMCallerCheckedAnyfunc), /// An `exrernref`. ExternRef(Option), } #[derive(Debug)] enum TableElements { FuncRefs(Vec<*mut VMCallerCheckedAnyfunc>), ExternRefs(Vec>), } impl Table { /// Create a new table instance with specified minimum and maximum number of elements. pub fn new(plan: &TablePlan) -> Self { let min = usize::try_from(plan.table.minimum).unwrap(); let elements = RefCell::new(match plan.table.ty { TableElementType::Func => TableElements::FuncRefs(vec![ptr::null_mut(); min]), TableElementType::Val(ty) => { debug_assert_eq!(ty, crate::ref_type()); TableElements::ExternRefs(vec![None; min]) } }); match plan.style { TableStyle::CallerChecksSignature => Self { elements, maximum: plan.table.maximum, }, } } /// Returns the type of the elements in this table. pub fn element_type(&self) -> TableElementType { match &*self.elements.borrow() { TableElements::FuncRefs(_) => TableElementType::Func, TableElements::ExternRefs(_) => TableElementType::Val(crate::ref_type()), } } /// Returns the number of allocated elements. pub fn size(&self) -> u32 { match &*self.elements.borrow() { TableElements::FuncRefs(x) => x.len().try_into().unwrap(), TableElements::ExternRefs(x) => x.len().try_into().unwrap(), } } /// Fill `table[dst..dst + len]` with `val`. /// /// Returns a trap error on out-of-bounds accesses. pub fn fill(&self, dst: u32, val: TableElement, len: u32) -> Result<(), Trap> { let start = dst; let end = start .checked_add(len) .ok_or_else(|| Trap::wasm(ir::TrapCode::TableOutOfBounds))?; if end > self.size() { return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds)); } for i in start..end { self.set(i, val.clone()).unwrap(); } Ok(()) } /// Grow table by the specified amount of elements. /// /// Returns the previous size of the table if growth is successful. /// /// Returns `None` if table can't be grown by the specified amount of /// elements, or if the `init_value` is the wrong kind of table element. /// /// # Unsafety /// /// Resizing the table can reallocate its internal elements buffer. This /// table's instance's `VMContext` has raw pointers to the elements buffer /// that are used by Wasm, and they need to be fixed up before we call into /// Wasm again. Failure to do so will result in use-after-free inside Wasm. /// /// Generally, prefer using `InstanceHandle::table_grow`, which encapsulates /// this unsafety. pub unsafe fn grow(&self, delta: u32, init_value: TableElement) -> Option { let size = self.size(); let new_len = size.checked_add(delta)?; if let Some(max) = self.maximum { if new_len > max { return None; } } let new_len = usize::try_from(new_len).unwrap(); match &mut *self.elements.borrow_mut() { TableElements::FuncRefs(x) => { let init_value = init_value.try_into().ok()?; x.resize(new_len, init_value) } TableElements::ExternRefs(x) => { let init_value = init_value.try_into().ok()?; x.resize(new_len, init_value) } } Some(size) } /// Get reference to the specified element. /// /// Returns `None` if the index is out of bounds. pub fn get(&self, index: u32) -> Option { match &*self.elements.borrow() { TableElements::FuncRefs(x) => x.get(index as usize).cloned().map(TableElement::FuncRef), TableElements::ExternRefs(x) => { x.get(index as usize).cloned().map(TableElement::ExternRef) } } } /// Set reference to the specified element. /// /// # Errors /// /// Returns an error if `index` is out of bounds or if this table type does /// not match the element type. pub fn set(&self, index: u32, elem: TableElement) -> Result<(), ()> { let mut elems = self.elements.borrow_mut(); match &mut *elems { TableElements::FuncRefs(x) => { let slot = x.get_mut(index as usize).ok_or(())?; *slot = elem.try_into().or(Err(()))?; } TableElements::ExternRefs(x) => { let slot = x.get_mut(index as usize).ok_or(())?; *slot = elem.try_into().or(Err(()))?; } } Ok(()) } /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`. /// /// # Errors /// /// Returns an error if the range is out of bounds of either the source or /// destination tables. pub fn copy( dst_table: &Self, src_table: &Self, dst_index: u32, src_index: u32, len: u32, ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy if src_index .checked_add(len) .map_or(true, |n| n > src_table.size()) || dst_index .checked_add(len) .map_or(true, |m| m > dst_table.size()) { return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds)); } let srcs = src_index..src_index + len; let dsts = dst_index..dst_index + len; // Note on the unwraps: the bounds check above means that these will // never panic. // // TODO(#983): investigate replacing this get/set loop with a `memcpy`. if dst_index <= src_index { for (s, d) in (srcs).zip(dsts) { dst_table.set(d, src_table.get(s).unwrap()).unwrap(); } } else { for (s, d) in srcs.rev().zip(dsts.rev()) { dst_table.set(d, src_table.get(s).unwrap()).unwrap(); } } Ok(()) } /// Return a `VMTableDefinition` for exposing the table to compiled wasm code. pub fn vmtable(&self) -> VMTableDefinition { match &*self.elements.borrow() { TableElements::FuncRefs(x) => VMTableDefinition { base: x.as_ptr() as *const u8 as *mut u8, current_elements: x.len().try_into().unwrap(), }, TableElements::ExternRefs(x) => VMTableDefinition { base: x.as_ptr() as *const u8 as *mut u8, current_elements: x.len().try_into().unwrap(), }, } } } impl TryFrom for *mut VMCallerCheckedAnyfunc { type Error = TableElement; fn try_from(e: TableElement) -> Result { match e { TableElement::FuncRef(f) => Ok(f), _ => Err(e), } } } impl TryFrom for Option { type Error = TableElement; fn try_from(e: TableElement) -> Result { match e { TableElement::ExternRef(x) => Ok(x), _ => Err(e), } } } impl From<*mut VMCallerCheckedAnyfunc> for TableElement { fn from(f: *mut VMCallerCheckedAnyfunc) -> TableElement { TableElement::FuncRef(f) } } impl From> for TableElement { fn from(x: Option) -> TableElement { TableElement::ExternRef(x) } } impl From for TableElement { fn from(x: VMExternRef) -> TableElement { TableElement::ExternRef(Some(x)) } }