From 6ade066c8bb276fb4a358583240973949ab637a7 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Jun 2020 11:13:20 -0700 Subject: [PATCH] wasmtime-runtime: Allow tables to internally hold `externref`s This commit enables `wasmtime_runtime::Table` to internally hold elements of either `funcref` (all that is currently supported) or `externref` (newly introduced in this commit). This commit updates `Table`'s API, but does NOT generally propagate those changes outwards all the way through the Wasmtime embedding API. It only does enough to get everything compiling and the current test suite passing. It is expected that as we implement more of the reference types spec, we will bubble these changes out and expose them to the embedding API. --- crates/environ/src/func_environ.rs | 2 +- crates/runtime/src/instance.rs | 25 +++--- crates/runtime/src/lib.rs | 2 +- crates/runtime/src/table.rs | 123 ++++++++++++++++++++++------- crates/wasmtime/src/externals.rs | 23 +++++- 5 files changed, 124 insertions(+), 51 deletions(-) diff --git a/crates/environ/src/func_environ.rs b/crates/environ/src/func_environ.rs index 52e4fbb64ae9..c4ee1aa16d97 100644 --- a/crates/environ/src/func_environ.rs +++ b/crates/environ/src/func_environ.rs @@ -1192,7 +1192,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m _table: ir::Table, ) -> WasmResult { Err(WasmError::Unsupported( - "bulk memory: `table.size`".to_string(), + "reference types: `table.size`".to_string(), )) } diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 8642da2aef45..88a09b20c820 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -5,7 +5,7 @@ use crate::export::Export; use crate::imports::Imports; use crate::memory::{DefaultMemoryCreator, RuntimeLinearMemory, RuntimeMemoryCreator}; -use crate::table::Table; +use crate::table::{Table, TableElement}; use crate::traphandlers::Trap; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, @@ -455,11 +455,7 @@ impl Instance { } // Get table element by index. - fn table_get( - &self, - table_index: DefinedTableIndex, - index: u32, - ) -> Option { + fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option { self.tables .get(table_index) .unwrap_or_else(|| panic!("no table for index {}", table_index.index())) @@ -470,7 +466,7 @@ impl Instance { &self, table_index: DefinedTableIndex, index: u32, - val: VMCallerCheckedAnyfunc, + val: TableElement, ) -> Result<(), ()> { self.tables .get(table_index) @@ -547,7 +543,7 @@ impl Instance { // TODO(#983): investigate replacing this get/set loop with a `memcpy`. for (dst, src) in (dst..dst + len).zip(src..src + len) { table - .set(dst, elem[src as usize].clone()) + .set(dst, TableElement::FuncRef(elem[src as usize].clone())) .expect("should never panic because we already did the bounds check above"); } @@ -993,11 +989,7 @@ impl InstanceHandle { /// Get table element reference. /// /// Returns `None` if index is out of bounds. - pub fn table_get( - &self, - table_index: DefinedTableIndex, - index: u32, - ) -> Option { + pub fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option { self.instance().table_get(table_index, index) } @@ -1008,7 +1000,7 @@ impl InstanceHandle { &self, table_index: DefinedTableIndex, index: u32, - val: VMCallerCheckedAnyfunc, + val: TableElement, ) -> Result<(), ()> { self.instance().table_set(table_index, index, val) } @@ -1174,7 +1166,10 @@ fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> { for (i, func_idx) in init.elements.iter().enumerate() { let anyfunc = instance.get_caller_checked_anyfunc(*func_idx); table - .set(u32::try_from(start + i).unwrap(), anyfunc) + .set( + u32::try_from(start + i).unwrap(), + TableElement::FuncRef(anyfunc), + ) .unwrap(); } } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 6f2896054a5f..3d295974ea0c 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -44,7 +44,7 @@ pub use crate::jit_int::GdbJitImageRegistration; pub use crate::memory::{RuntimeLinearMemory, RuntimeMemoryCreator}; pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; -pub use crate::table::Table; +pub use crate::table::{Table, TableElement}; pub use crate::traphandlers::{ catch_traps, init_traps, raise_lib_trap, raise_user_trap, resume_panic, SignalHandler, Trap, }; diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index 5439eec97e9e..aa2f793f8d6f 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -3,7 +3,7 @@ //! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition}; -use crate::Trap; +use crate::{Trap, VMExternRef}; use std::cell::RefCell; use std::convert::{TryFrom, TryInto}; use wasmtime_environ::wasm::TableElementType; @@ -12,25 +12,46 @@ use wasmtime_environ::{ir, TablePlan, TableStyle}; /// A table instance. #[derive(Debug)] pub struct Table { - vec: RefCell>, + elements: RefCell, maximum: Option, } +/// An element going into or coming out of a table. +#[derive(Clone, Debug)] +pub enum TableElement { + /// A `funcref`. + FuncRef(VMCallerCheckedAnyfunc), + /// An `exrernref`. + ExternRef(Option), +} + +#[derive(Debug)] +enum TableElements { + FuncRefs(Vec), + ExternRefs(Vec>), +} + impl Table { /// Create a new table instance with specified minimum and maximum number of elements. pub fn new(plan: &TablePlan) -> Self { - match plan.table.ty { - TableElementType::Func => (), - TableElementType::Val(ty) => { - unimplemented!("tables of types other than anyfunc ({})", ty) - } - }; - match plan.style { - TableStyle::CallerChecksSignature => Self { - vec: RefCell::new(vec![ + let elements = + RefCell::new(match plan.table.ty { + TableElementType::Func => TableElements::FuncRefs(vec![ VMCallerCheckedAnyfunc::default(); usize::try_from(plan.table.minimum).unwrap() ]), + TableElementType::Val(ty) + if (cfg!(target_pointer_width = "64") && ty == ir::types::R64) + || (cfg!(target_pointer_width = "32") && ty == ir::types::R32) => + { + let min = usize::try_from(plan.table.minimum).unwrap(); + TableElements::ExternRefs(vec![None; min]) + } + TableElementType::Val(ty) => unimplemented!("unsupported table type ({})", ty), + }); + match plan.style { + TableStyle::CallerChecksSignature => Self { + elements, maximum: plan.table.maximum, }, } @@ -38,7 +59,10 @@ impl Table { /// Returns the number of allocated elements. pub fn size(&self) -> u32 { - self.vec.borrow().len().try_into().unwrap() + match &*self.elements.borrow() { + TableElements::FuncRefs(x) => x.len().try_into().unwrap(), + TableElements::ExternRefs(x) => x.len().try_into().unwrap(), + } } /// Grow table by the specified amount of elements. @@ -61,33 +85,45 @@ impl Table { return None; } }; - self.vec.borrow_mut().resize( - usize::try_from(new_len).unwrap(), - VMCallerCheckedAnyfunc::default(), - ); + let new_len = usize::try_from(new_len).unwrap(); + match &mut *self.elements.borrow_mut() { + TableElements::FuncRefs(x) => x.resize(new_len, VMCallerCheckedAnyfunc::default()), + TableElements::ExternRefs(x) => x.resize(new_len, None), + } Some(size) } /// Get reference to the specified element. /// /// Returns `None` if the index is out of bounds. - pub fn get(&self, index: u32) -> Option { - self.vec.borrow().get(index as usize).cloned() + 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. /// - /// # Panics + /// # Errors /// - /// Panics if `index` is out of bounds. - pub fn set(&self, index: u32, func: VMCallerCheckedAnyfunc) -> Result<(), ()> { - match self.vec.borrow_mut().get_mut(index as usize) { - Some(slot) => { - *slot = func; - Ok(()) + /// 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(()))?; } - None => Err(()), } + Ok(()) } /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`. @@ -137,10 +173,37 @@ impl Table { /// Return a `VMTableDefinition` for exposing the table to compiled wasm code. pub fn vmtable(&self) -> VMTableDefinition { - let mut vec = self.vec.borrow_mut(); - VMTableDefinition { - base: vec.as_mut_ptr() as *mut u8, - current_elements: vec.len().try_into().unwrap(), + 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 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), } } } diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 5e6cf3acadb7..77e75f64f465 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -2,8 +2,10 @@ use crate::trampoline::{ generate_global_export, generate_memory_export, generate_table_export, StoreInstanceHandle, }; use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val}; -use crate::{ExternType, GlobalType, MemoryType, Mutability, TableType, ValType}; -use crate::{Func, Store, Trap}; +use crate::{ + ExternRef, ExternType, Func, GlobalType, MemoryType, Mutability, Store, TableType, Trap, + ValType, +}; use anyhow::{anyhow, bail, Result}; use std::slice; use wasmtime_environ::wasm; @@ -299,7 +301,11 @@ fn set_table_item( item: wasmtime_runtime::VMCallerCheckedAnyfunc, ) -> Result<()> { instance - .table_set(table_index, item_index, item) + .table_set( + table_index, + item_index, + runtime::TableElement::FuncRef(item), + ) .map_err(|()| anyhow!("table element index out of bounds")) } @@ -348,7 +354,16 @@ impl Table { pub fn get(&self, index: u32) -> Option { let table_index = self.wasmtime_table_index(); let item = self.instance.table_get(table_index, index)?; - Some(from_checked_anyfunc(item, &self.instance.store)) + match item { + runtime::TableElement::FuncRef(f) => { + Some(from_checked_anyfunc(f, &self.instance.store)) + } + runtime::TableElement::ExternRef(None) => Some(Val::ExternRef(None)), + runtime::TableElement::ExternRef(Some(x)) => Some(Val::ExternRef(Some(ExternRef { + inner: x, + store: self.instance.store.weak(), + }))), + } } /// Writes the `val` provided into `index` within this table.