diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index ea5045ea83da..ad6092ba74ec 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -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 { 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) diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 03b4ef442262..3beec44cf90f 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -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 { - 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, diff --git a/crates/wasmtime/src/trampoline/table.rs b/crates/wasmtime/src/trampoline/table.rs index b42575e6532f..02ea14880e97 100644 --- a/crates/wasmtime/src/trampoline/table.rs +++ b/crates/wasmtime/src/trampoline/table.rs @@ -15,6 +15,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result wasm::TableElementType::Func, + ValType::ExternRef => wasm::TableElementType::Val(wasmtime_runtime::ref_type()), _ => bail!("cannot support {:?} as a table element", table.element()), }, }; diff --git a/crates/wasmtime/src/values.rs b/crates/wasmtime/src/values.rs index a123319a2651..053e539e538f 100644 --- a/crates/wasmtime/src/values.rs +++ b/crates/wasmtime/src/values.rs @@ -174,6 +174,18 @@ impl Val { self.externref().expect("expected externref") } + pub(crate) fn into_table_element(self) -> Result { + 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()), diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 01a364f18880..365d5b2611db 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -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::() + .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::() + .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(()) +}