Skip to content

Commit

Permalink
wasmtime-runtime: Allow tables to internally hold externrefs (#1882)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fitzgen authored Jun 15, 2020
1 parent 357fb11 commit 8d671c2
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 51 deletions.
2 changes: 1 addition & 1 deletion crates/environ/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
_table: ir::Table,
) -> WasmResult<ir::Value> {
Err(WasmError::Unsupported(
"bulk memory: `table.size`".to_string(),
"reference types: `table.size`".to_string(),
))
}

Expand Down
25 changes: 10 additions & 15 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -455,11 +455,7 @@ impl Instance {
}

// Get table element by index.
fn table_get(
&self,
table_index: DefinedTableIndex,
index: u32,
) -> Option<VMCallerCheckedAnyfunc> {
fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option<TableElement> {
self.tables
.get(table_index)
.unwrap_or_else(|| panic!("no table for index {}", table_index.index()))
Expand All @@ -470,7 +466,7 @@ impl Instance {
&self,
table_index: DefinedTableIndex,
index: u32,
val: VMCallerCheckedAnyfunc,
val: TableElement,
) -> Result<(), ()> {
self.tables
.get(table_index)
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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<VMCallerCheckedAnyfunc> {
pub fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option<TableElement> {
self.instance().table_get(table_index, index)
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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();
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
123 changes: 93 additions & 30 deletions crates/runtime/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,33 +12,57 @@ use wasmtime_environ::{ir, TablePlan, TableStyle};
/// A table instance.
#[derive(Debug)]
pub struct Table {
vec: RefCell<Vec<VMCallerCheckedAnyfunc>>,
elements: RefCell<TableElements>,
maximum: Option<u32>,
}

/// An element going into or coming out of a table.
#[derive(Clone, Debug)]
pub enum TableElement {
/// A `funcref`.
FuncRef(VMCallerCheckedAnyfunc),
/// An `exrernref`.
ExternRef(Option<VMExternRef>),
}

#[derive(Debug)]
enum TableElements {
FuncRefs(Vec<VMCallerCheckedAnyfunc>),
ExternRefs(Vec<Option<VMExternRef>>),
}

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,
},
}
}

/// 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.
Expand All @@ -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<VMCallerCheckedAnyfunc> {
self.vec.borrow().get(index as usize).cloned()
pub fn get(&self, index: u32) -> Option<TableElement> {
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..]`.
Expand Down Expand Up @@ -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<TableElement> for VMCallerCheckedAnyfunc {
type Error = TableElement;

fn try_from(e: TableElement) -> Result<Self, Self::Error> {
match e {
TableElement::FuncRef(f) => Ok(f),
_ => Err(e),
}
}
}

impl TryFrom<TableElement> for Option<VMExternRef> {
type Error = TableElement;

fn try_from(e: TableElement) -> Result<Self, Self::Error> {
match e {
TableElement::ExternRef(x) => Ok(x),
_ => Err(e),
}
}
}
23 changes: 19 additions & 4 deletions crates/wasmtime/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"))
}

Expand Down Expand Up @@ -348,7 +354,16 @@ impl Table {
pub fn get(&self, index: u32) -> Option<Val> {
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.
Expand Down

0 comments on commit 8d671c2

Please sign in to comment.