Repository: bytecodealliance/wasmtime
Pull request #1984 (2020-07-06 21:22:20)

wasmtime: Support reference types in the Rust API
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 #929

<!--

Please ensure that the following steps are all taken care of before submitting
the PR.

- [ ] This has been discussed in issue #..., or if not, please tell us why
  here.
- [ ] A short description of what this does, why it is needed; if the
  description becomes long, the matter should probably be discussed in an issue
  first.
- [ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
  If you don't know who could review this, please indicate so. The list of
  suggested reviewers on the right can help you.

Please ensure all communication adheres to the [code of
conduct](https://github.com/bytecodealliance/wasmtime/blob/master/CODE_OF_CONDUCT.md).
-->



Patch:
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index ea5045ea8..ad6092ba7 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<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)
diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs
index 03b4ef442..3beec44cf 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<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,
diff --git a/crates/wasmtime/src/trampoline/table.rs b/crates/wasmtime/src/trampoline/table.rs
index b42575e65..02ea14880 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<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()),
         },
     };
diff --git a/crates/wasmtime/src/values.rs b/crates/wasmtime/src/values.rs
index a123319a2..053e539e5 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<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()),


Test patch:
diff --git a/tests/all/externals.rs b/tests/all/externals.rs
index 01a364f18..365d5b261 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::<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(())
+}