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.
|
// Get table element by index.
|
||||||
fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option<TableElement> {
|
fn table_get(&self, table_index: DefinedTableIndex, index: u32) -> Option<TableElement> {
|
||||||
self.tables
|
self.tables
|
||||||
@@ -1115,6 +1125,21 @@ impl InstanceHandle {
|
|||||||
self.instance().table_set(table_index, index, val)
|
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.
|
/// Get a table defined locally within this module.
|
||||||
pub fn get_defined_table(&self, index: DefinedTableIndex) -> &Table {
|
pub fn get_defined_table(&self, index: DefinedTableIndex) -> &Table {
|
||||||
self.instance().get_defined_table(index)
|
self.instance().get_defined_table(index)
|
||||||
|
|||||||
@@ -322,10 +322,10 @@ fn set_table_item(
|
|||||||
instance: &InstanceHandle,
|
instance: &InstanceHandle,
|
||||||
table_index: wasm::DefinedTableIndex,
|
table_index: wasm::DefinedTableIndex,
|
||||||
item_index: u32,
|
item_index: u32,
|
||||||
item: *mut wasmtime_runtime::VMCallerCheckedAnyfunc,
|
item: runtime::TableElement,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
instance
|
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"))
|
.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.
|
/// 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> {
|
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 (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.
|
// Initialize entries with the init value.
|
||||||
let definition = unsafe { &*wasmtime_export.definition };
|
let definition = unsafe { &*wasmtime_export.definition };
|
||||||
let index = instance.table_index(definition);
|
let index = instance.table_index(definition);
|
||||||
for i in 0..definition.current_elements {
|
for i in 0..definition.current_elements {
|
||||||
set_table_item(&instance, index, i, item)?;
|
set_table_item(&instance, index, i, init.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Table {
|
Ok(Table {
|
||||||
@@ -392,9 +403,16 @@ impl Table {
|
|||||||
/// Returns an error if `index` is out of bounds or if `val` does not have
|
/// Returns an error if `index` is out of bounds or if `val` does not have
|
||||||
/// the right type to be stored in this table.
|
/// the right type to be stored in this table.
|
||||||
pub fn set(&self, index: u32, val: Val) -> Result<()> {
|
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 table_index = self.wasmtime_table_index();
|
||||||
let item = into_checked_anyfunc(val, &self.instance.store)?;
|
set_table_item(
|
||||||
set_table_item(&self.instance, table_index, index, item)
|
&self.instance,
|
||||||
|
table_index,
|
||||||
|
index,
|
||||||
|
val.into_table_element()?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current size of this table.
|
/// Returns the current size of this table.
|
||||||
@@ -473,6 +491,32 @@ impl Table {
|
|||||||
Ok(())
|
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(
|
pub(crate) fn from_wasmtime_table(
|
||||||
wasmtime_export: wasmtime_runtime::ExportTable,
|
wasmtime_export: wasmtime_runtime::ExportTable,
|
||||||
instance: StoreInstanceHandle,
|
instance: StoreInstanceHandle,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
|
|||||||
maximum: table.limits().max(),
|
maximum: table.limits().max(),
|
||||||
ty: match table.element() {
|
ty: match table.element() {
|
||||||
ValType::FuncRef => wasm::TableElementType::Func,
|
ValType::FuncRef => wasm::TableElementType::Func,
|
||||||
|
ValType::ExternRef => wasm::TableElementType::Val(wasmtime_runtime::ref_type()),
|
||||||
_ => bail!("cannot support {:?} as a table element", table.element()),
|
_ => bail!("cannot support {:?} as a table element", table.element()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -174,6 +174,18 @@ impl Val {
|
|||||||
self.externref().expect("expected externref")
|
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 {
|
pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Val::FuncRef(Some(f)) => Store::same(store, f.store()),
|
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())?;
|
let t1 = Table::new(&store2, ty.clone(), store2val.clone())?;
|
||||||
assert!(t1.set(0, store1val.clone()).is_err());
|
assert!(t1.set(0, store1val.clone()).is_err());
|
||||||
assert!(t1.grow(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())?;
|
let t2 = Table::new(&store1, ty.clone(), store1val.clone())?;
|
||||||
assert!(Table::copy(&t1, 0, &t2, 0, 0).is_err());
|
assert!(Table::copy(&t1, 0, &t2, 0, 0).is_err());
|
||||||
|
|
||||||
// ============ Cross-store funcs ==============
|
// ============ 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 "f") (param funcref)))"#)?;
|
||||||
// let module = Module::new(&engine, r#"(module (func (export "a") (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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -181,3 +192,151 @@ fn get_set_funcref_globals_via_api() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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