Merge pull request #1984 from fitzgen/ref-types-in-rust-api
wasmtime: Support reference types in the Rust API
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user