Skip to content

Commit

Permalink
wasmtime: Support reference types in the Rust API
Browse files Browse the repository at this point in the history
This is a mix of exposing new things (e.g. a `Table::fill` method) and extending
existing support to `externref`s (e.g. `Table::new`).

Part of bytecodealliance#929
  • Loading branch information
fitzgen committed Jul 6, 2020
1 parent cf5289c commit a7c6433
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 8 deletions.
25 changes: 25 additions & 0 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,16 @@ impl Instance {
}
}

pub(crate) fn defined_table_fill(
&self,
table_index: DefinedTableIndex,
dst: u32,
val: TableElement,
len: u32,
) -> Result<(), Trap> {
self.tables.get(table_index).unwrap().fill(dst, val, len)
}

// Get table element by index.
fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option<TableElement> {
self.tables
Expand Down Expand Up @@ -1115,6 +1125,21 @@ impl InstanceHandle {
self.instance().table_set(table_index, index, val)
}

/// Fill a region of the table.
///
/// Returns an error if the region is out of bounds or val is not of the
/// correct type.
pub fn defined_table_fill(
&self,
table_index: DefinedTableIndex,
dst: u32,
val: TableElement,
len: u32,
) -> Result<(), Trap> {
self.instance()
.defined_table_fill(table_index, dst, val, len)
}

/// Get a table defined locally within this module.
pub fn get_defined_table(&self, index: DefinedTableIndex) -> &Table {
self.instance().get_defined_table(index)
Expand Down
56 changes: 50 additions & 6 deletions crates/wasmtime/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,10 @@ fn set_table_item(
instance: &InstanceHandle,
table_index: wasm::DefinedTableIndex,
item_index: u32,
item: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
item: runtime::TableElement,
) -> Result<()> {
instance
.table_set(table_index, item_index, item.into())
.table_set(table_index, item_index, item)
.map_err(|()| anyhow!("table element index out of bounds"))
}

Expand All @@ -342,14 +342,25 @@ impl Table {
///
/// Returns an error if `init` does not match the element type of the table.
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
let item = into_checked_anyfunc(init, store)?;
let (instance, wasmtime_export) = generate_table_export(store, &ty)?;

let init: runtime::TableElement = match ty.element() {
ValType::FuncRef => into_checked_anyfunc(init, store)?.into(),
ValType::ExternRef => init
.externref()
.ok_or_else(|| {
anyhow!("table initialization value does not have expected type `externref`")
})?
.map(|x| x.inner)
.into(),
ty => bail!("unsupported table element type: {:?}", ty),
};

// Initialize entries with the init value.
let definition = unsafe { &*wasmtime_export.definition };
let index = instance.table_index(definition);
for i in 0..definition.current_elements {
set_table_item(&instance, index, i, item)?;
set_table_item(&instance, index, i, init.clone())?;
}

Ok(Table {
Expand Down Expand Up @@ -392,9 +403,16 @@ impl Table {
/// Returns an error if `index` is out of bounds or if `val` does not have
/// the right type to be stored in this table.
pub fn set(&self, index: u32, val: Val) -> Result<()> {
if !val.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` values are not supported in tables");
}
let table_index = self.wasmtime_table_index();
let item = into_checked_anyfunc(val, &self.instance.store)?;
set_table_item(&self.instance, table_index, index, item)
set_table_item(
&self.instance,
table_index,
index,
val.into_table_element()?,
)
}

/// Returns the current size of this table.
Expand Down Expand Up @@ -473,6 +491,32 @@ impl Table {
Ok(())
}

/// Fill `table[dst..(dst + len)]` with the given value.
///
/// # Errors
///
/// Returns an error if
///
/// * `val` is not of the same type as this table's
/// element type,
///
/// * the region to be filled is out of bounds, or
///
/// * `val` comes from a different `Store` from this table.
pub fn fill(&self, dst: u32, val: Val, len: u32) -> Result<()> {
if !val.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` table fills are not supported");
}

let table_index = self.wasmtime_table_index();
self.instance
.handle
.defined_table_fill(table_index, dst, val.into_table_element()?, len)
.map_err(Trap::from_runtime)?;

Ok(())
}

pub(crate) fn from_wasmtime_table(
wasmtime_export: wasmtime_runtime::ExportTable,
instance: StoreInstanceHandle,
Expand Down
1 change: 1 addition & 0 deletions crates/wasmtime/src/trampoline/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
maximum: table.limits().max(),
ty: match table.element() {
ValType::FuncRef => wasm::TableElementType::Func,
ValType::ExternRef => wasm::TableElementType::Val(wasmtime_runtime::ref_type()),
_ => bail!("cannot support {:?} as a table element", table.element()),
},
};
Expand Down
12 changes: 12 additions & 0 deletions crates/wasmtime/src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ impl Val {
self.externref().expect("expected externref")
}

pub(crate) fn into_table_element(self) -> Result<runtime::TableElement> {
match self {
Val::FuncRef(Some(f)) => Ok(runtime::TableElement::FuncRef(
f.caller_checked_anyfunc().as_ptr(),
)),
Val::FuncRef(None) => Ok(runtime::TableElement::FuncRef(ptr::null_mut())),
Val::ExternRef(Some(x)) => Ok(runtime::TableElement::ExternRef(Some(x.inner))),
Val::ExternRef(None) => Ok(runtime::TableElement::ExternRef(None)),
_ => bail!("value does not match table element type"),
}
}

pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
match self {
Val::FuncRef(Some(f)) => Store::same(store, f.store()),
Expand Down
163 changes: 161 additions & 2 deletions tests/all/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,24 @@ fn cross_store() -> anyhow::Result<()> {
let t1 = Table::new(&store2, ty.clone(), store2val.clone())?;
assert!(t1.set(0, store1val.clone()).is_err());
assert!(t1.grow(0, store1val.clone()).is_err());
assert!(t1.fill(0, store1val.clone(), 1).is_err());
let t2 = Table::new(&store1, ty.clone(), store1val.clone())?;
assert!(Table::copy(&t1, 0, &t2, 0, 0).is_err());

// ============ Cross-store funcs ==============

// TODO: need to actually fill this out once we support externref params/locals
// let module = Module::new(&engine, r#"(module (func (export "a") (param funcref)))"#)?;
let module = Module::new(&engine, r#"(module (func (export "f") (param funcref)))"#)?;
let s1_inst = Instance::new(&store1, &module, &[])?;
let s2_inst = Instance::new(&store2, &module, &[])?;
let s1_f = s1_inst.get_func("f").unwrap();
let s2_f = s2_inst.get_func("f").unwrap();

assert!(s1_f.call(&[Val::FuncRef(None)]).is_ok());
assert!(s2_f.call(&[Val::FuncRef(None)]).is_ok());
assert!(s1_f.call(&[Val::FuncRef(Some(s1_f.clone()))]).is_ok());
assert!(s1_f.call(&[Val::FuncRef(Some(s2_f.clone()))]).is_err());
assert!(s2_f.call(&[Val::FuncRef(Some(s1_f.clone()))]).is_err());
assert!(s2_f.call(&[Val::FuncRef(Some(s2_f.clone()))]).is_ok());

Ok(())
}
Expand Down Expand Up @@ -181,3 +192,151 @@ fn get_set_funcref_globals_via_api() -> anyhow::Result<()> {

Ok(())
}

#[test]
fn create_get_set_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
let table = Table::new(
&store,
table_ty,
Val::FuncRef(Some(Func::wrap(&store, || {}))),
)?;

assert!(table.get(5).unwrap().unwrap_funcref().is_some());
table.set(5, Val::FuncRef(None))?;
assert!(table.get(5).unwrap().unwrap_funcref().is_none());

Ok(())
}

#[test]
fn fill_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
let table = Table::new(&store, table_ty, Val::FuncRef(None))?;

for i in 0..10 {
assert!(table.get(i).unwrap().unwrap_funcref().is_none());
}

table.fill(2, Val::FuncRef(Some(Func::wrap(&store, || {}))), 4)?;

for i in (0..2).chain(7..10) {
assert!(table.get(i).unwrap().unwrap_funcref().is_none());
}
for i in 2..6 {
assert!(table.get(i).unwrap().unwrap_funcref().is_some());
}

Ok(())
}

#[test]
fn grow_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
let table = Table::new(&store, table_ty, Val::FuncRef(None))?;

assert_eq!(table.size(), 10);
table.grow(3, Val::FuncRef(None))?;
assert_eq!(table.size(), 13);

Ok(())
}

#[test]
fn create_get_set_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
let table = Table::new(
&store,
table_ty,
Val::ExternRef(Some(ExternRef::new(42_usize))),
)?;

assert_eq!(
*table
.get(5)
.unwrap()
.unwrap_externref()
.unwrap()
.data()
.downcast_ref::<usize>()
.unwrap(),
42
);
table.set(5, Val::ExternRef(None))?;
assert!(table.get(5).unwrap().unwrap_externref().is_none());

Ok(())
}

#[test]
fn fill_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
let table = Table::new(&store, table_ty, Val::ExternRef(None))?;

for i in 0..10 {
assert!(table.get(i).unwrap().unwrap_externref().is_none());
}

table.fill(2, Val::ExternRef(Some(ExternRef::new(42_usize))), 4)?;

for i in (0..2).chain(7..10) {
assert!(table.get(i).unwrap().unwrap_externref().is_none());
}
for i in 2..6 {
assert_eq!(
*table
.get(i)
.unwrap()
.unwrap_externref()
.unwrap()
.data()
.downcast_ref::<usize>()
.unwrap(),
42
);
}

Ok(())
}

#[test]
fn grow_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let store = Store::new(&engine);

let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
let table = Table::new(&store, table_ty, Val::ExternRef(None))?;

assert_eq!(table.size(), 10);
table.grow(3, Val::ExternRef(None))?;
assert_eq!(table.size(), 13);

Ok(())
}

0 comments on commit a7c6433

Please sign in to comment.