Skip to content

Commit

Permalink
Merge #1687
Browse files Browse the repository at this point in the history
1687: Add Table example; fix local Table and Memory metadata ownership r=MarkMcCaskey a=MarkMcCaskey

Adds an example using the Table API.

Work in progress, debugging an issue with growing using the Table API...

Things I'd like to have before shipping:
- [x] Code / comments clear enough that what's happening is fully understandable with no extra context
- [ ] Test for dynamic memory (I don't know how to test this actually works with dynamic memory without editing code, this bug manifests most strongly with dynamic memory -- it may be possible to test memory without it though)

# Review

- [x] Add a short description of the the change to the CHANGELOG.md file


Co-authored-by: Mark McCaskey <[email protected]>
Co-authored-by: Mark McCaskey <[email protected]>
  • Loading branch information
3 people authored Oct 27, 2020
2 parents a8b9350 + adc40ae commit 04091d8
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 97 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [#1710](https://github.com/wasmerio/wasmer/pull/1710) Memory for function call trampolines is now owned by the Artifact.
### Added

- [#1687](https://github.com/wasmerio/wasmer/pull/1687) Add basic table example; fix ownership of local memory and local table metadata in the VM.
- [#1751](https://github.com/wasmerio/wasmer/pull/1751) Implement `wasm_trap_t` inside a function declared with `wasm_func_new_with_env` in the Wasm C API.
- [#1741](https://github.com/wasmerio/wasmer/pull/1741) Implement `wasm_memory_type` in the Wasm C API.
- [#1736](https://github.com/wasmerio/wasmer/pull/1736) Implement `wasm_global_type` in the Wasm C API.
Expand Down
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,13 @@ required-features = ["cranelift"]
name = "wasi"
path = "examples/wasi.rs"
required-features = ["cranelift", "wasi"]

[[example]]
name = "table"
path = "examples/table.rs"
required-features = ["cranelift"]

[[example]]
name = "memory"
path = "examples/memory.rs"
required-features = ["cranelift"]
34 changes: 33 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ example.

</details>


10. [**Exported memory**][exported-memory], explains how to read from
and write to exported memory.

Expand Down Expand Up @@ -226,9 +227,38 @@ example.

</details>

### Externs

13. [**Table**][table], explains how to use Wasm Tables from the Wasmer API.

_Keywords_: basic, table, call_indirect

<details>
<summary><em>Execute the example</em></summary>

```shell
$ cargo run --example table --release --features "cranelift"
```

</details>

14. [**Memory**][memory], explains how to use Wasm Memories from
the Wasmer API. Memory example is a work in progress.

_Keywords_: basic, memory

<details>
<summary><em>Execute the example</em></summary>

```shell
$ cargo run --example memory --release --features "cranelift"
```

</details>

### Integrations

13. [**WASI**][wasi], explains how to use the [WebAssembly System
15. [**WASI**][wasi], explains how to use the [WebAssembly System
Interface][WASI] (WASI), i.e. the [`wasmer-wasi`] crate.

_Keywords_: wasi, system, interface
Expand All @@ -255,6 +285,8 @@ example.
[imported-global]: ./imports_global.rs
[imported-function]: ./imports_function.rs
[wasi]: ./wasi.rs
[table]: ./table.rs
[memory]: ./memory.rs
[`wasmer-compiler-singlepass`]: https://github.com/wasmerio/wasmer/tree/master/lib/compiler-singlepass
[`wasmer-compiler-cranelift`]: https://github.com/wasmerio/wasmer/tree/master/lib/compiler-cranelift
[`wasmer-compiler-llvm`]: https://github.com/wasmerio/wasmer/tree/master/lib/compiler-llvm
Expand Down
81 changes: 81 additions & 0 deletions examples/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use wasmer::{
imports, wat2wasm, Extern, Function, Instance, Memory, MemoryType, Module, NativeFunc, Pages,
Store, Table, TableType, Type, Value,
};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;

// this example is a work in progress:
// TODO: clean it up and comment it https://github.com/wasmerio/wasmer/issues/1749

fn main() -> anyhow::Result<()> {
let wasm_bytes = wat2wasm(
r#"
(module
(type $mem_size_t (func (result i32)))
(type $get_at_t (func (param i32) (result i32)))
(type $set_at_t (func (param i32) (param i32)))
(memory $mem 1)
(func $get_at (type $get_at_t) (param $idx i32) (result i32)
(i32.load (local.get $idx)))
(func $set_at (type $set_at_t) (param $idx i32) (param $val i32)
(i32.store (local.get $idx) (local.get $val)))
(func $mem_size (type $mem_size_t) (result i32)
(memory.size))
(export "get_at" (func $get_at))
(export "set_at" (func $set_at))
(export "mem_size" (func $mem_size))
(export "memory" (memory $mem)))
"#
.as_bytes(),
)?;

// We set up our store with an engine and a compiler.
let store = Store::new(&JIT::new(&Cranelift::default()).engine());
// Then compile our Wasm.
let module = Module::new(&store, wasm_bytes)?;
let import_object = imports! {};
// And instantiate it with no imports.
let instance = Instance::new(&module, &import_object)?;

let mem_size: NativeFunc<(), i32> = instance.exports.get_native_function("mem_size")?;
let get_at: NativeFunc<i32, i32> = instance.exports.get_native_function("get_at")?;
let set_at: NativeFunc<(i32, i32), ()> = instance.exports.get_native_function("set_at")?;
let memory = instance.exports.get_memory("memory")?;

let mem_addr = 0x2220;
let val = 0xFEFEFFE;

assert_eq!(memory.size(), Pages::from(1));
memory.grow(2)?;
assert_eq!(memory.size(), Pages::from(3));
let result = mem_size.call()?;
assert_eq!(result, 3);

// -------------
set_at.call(mem_addr, val)?;
// -------------

let page_size = 0x1_0000;
let result = get_at.call(page_size * 3 - 4)?;
memory.grow(1025)?;
assert_eq!(memory.size(), Pages::from(1028));
set_at.call(page_size * 1027 - 4, 123456)?;
let result = get_at.call(page_size * 1027 - 4)?;
assert_eq!(result, 123456);
set_at.call(1024, 123456)?;
let result = get_at.call(1024)?;
assert_eq!(result, 123456);

// -------------
let result = get_at.call(mem_addr)?;
assert_eq!(result, val);
// -------------

Ok(())
}
154 changes: 154 additions & 0 deletions examples/table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use wasmer::{
imports, wat2wasm, Function, Instance, Module, NativeFunc, Store, TableType, Type, Value,
};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;

/// A function we'll call through a table.
fn host_callback(arg1: i32, arg2: i32) -> i32 {
arg1 + arg2
}

fn main() -> anyhow::Result<()> {
let wasm_bytes = wat2wasm(
r#"
(module
;; All our callbacks will take 2 i32s and return an i32.
;; Wasm tables are not limited to 1 type of function, but the code using the
;; table must have code to handle the type it finds.
(type $callback_t (func (param i32 i32) (result i32)))
;; We'll call a callback by passing a table index as an i32 and then the two
;; arguments that the function expects.
(type $call_callback_t (func (param i32 i32 i32) (result i32)))
;; Our table of functions that's exactly size 3 (min 3, max 3).
(table $t1 3 6 funcref)
;; Call the function at the given index with the two supplied arguments.
(func $call_callback (type $call_callback_t) (param $idx i32)
(param $arg1 i32) (param $arg2 i32)
(result i32)
(call_indirect (type $callback_t)
(local.get $arg1) (local.get $arg2)
(local.get $idx)))
;; A default function that we'll pad the table with.
;; This function doubles both its inputs and then sums them.
(func $default_fn (type $callback_t) (param $a i32) (param $b i32) (result i32)
(i32.add
(i32.mul (local.get $a) (i32.const 2))
(i32.mul (local.get $b) (i32.const 2))))
;; Fill our table with the default function.
(elem $t1 (i32.const 0) $default_fn $default_fn $default_fn)
;; Export things for the host to call.
(export "call_callback" (func $call_callback))
(export "__indirect_function_table" (table $t1)))
"#
.as_bytes(),
)?;

// We set up our store with an engine and a compiler.
let store = Store::new(&JIT::new(&Cranelift::default()).engine());
// Then compile our Wasm.
let module = Module::new(&store, wasm_bytes)?;
let import_object = imports! {};
// And instantiate it with no imports.
let instance = Instance::new(&module, &import_object)?;

// We get our function that calls (i32, i32) -> i32 functions via table.
// The first argument is the table index and the next 2 are the 2 arguments
// to be passed to the function found in the table.
let call_via_table: NativeFunc<(i32, i32, i32), i32> =
instance.exports.get_native_function("call_callback")?;

// And then call it with table index 1 and arguments 2 and 7.
let result = call_via_table.call(1, 2, 7)?;
// Because it's the default function, we expect it to double each number and
// then sum it, giving us 18.
assert_eq!(result, 18);

// We then get the table from the instance.
let guest_table = instance.exports.get_table("__indirect_function_table")?;
// And demonstrate that it has the properties that we set in the Wasm.
assert_eq!(guest_table.size(), 3);
assert_eq!(
guest_table.ty(),
&TableType {
ty: Type::FuncRef,
minimum: 3,
maximum: Some(6),
}
);

// == Setting elements in a table ==

// We first construct a `Function` over our host_callback.
let func = Function::new_native(&store, host_callback);

// And set table index 1 of that table to the host_callback `Function`.
guest_table.set(1, func.into())?;

// We then repeat the call from before but this time it will find the host function
// that we put at table index 1.
let result = call_via_table.call(1, 2, 7)?;
// And because our host function simply sums the numbers, we expect 9.
assert_eq!(result, 9);

// == Growing a table ==

// We again construct a `Function` over our host_callback.
let func = Function::new_native(&store, host_callback);

// And grow the table by 3 elements, filling in our host_callback in all the
// new elements of the table.
let previous_size = guest_table.grow(3, func.into())?;
assert_eq!(previous_size, 3);

assert_eq!(guest_table.size(), 6);
assert_eq!(
guest_table.ty(),
&TableType {
ty: Type::FuncRef,
minimum: 3,
maximum: Some(6),
}
);
// Now demonstarte that the function we grew the table with is actually in the table.
for table_index in 3..6 {
if let Value::FuncRef(f) = guest_table.get(table_index as _).unwrap() {
let result = f.call(&[Value::I32(1), Value::I32(9)])?;
assert_eq!(result[0], Value::I32(10));
} else {
panic!("expected to find funcref in table!");
}
}

// Call function at index 0 to show that it's still the same.
let result = call_via_table.call(0, 2, 7)?;
assert_eq!(result, 18);

// Now overwrite index 0 with our host_callback.
let func = Function::new_native(&store, host_callback);
guest_table.set(0, func.into())?;
// And verify that it does what we expect.
let result = call_via_table.call(0, 2, 7)?;
assert_eq!(result, 9);

// Now demonstrate that the host and guest see the same table and that both
// get the same result.
for table_index in 3..6 {
if let Value::FuncRef(f) = guest_table.get(table_index as _).unwrap() {
let result = f.call(&[Value::I32(1), Value::I32(9)])?;
assert_eq!(result[0], Value::I32(10));
} else {
panic!("expected to find funcref in table!");
}
let result = call_via_table.call(table_index, 1, 9)?;
assert_eq!(result, 10);
}

Ok(())
}
2 changes: 1 addition & 1 deletion lib/api/src/externals/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Memory {
pub fn new(store: &Store, ty: MemoryType) -> Result<Self, MemoryError> {
let tunables = store.tunables();
let style = tunables.memory_style(&ty);
let memory = tunables.create_memory(&ty, &style)?;
let memory = tunables.create_host_memory(&ty, &style)?;

Ok(Self {
store: store.clone(),
Expand Down
2 changes: 1 addition & 1 deletion lib/api/src/externals/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl Table {
let tunables = store.tunables();
let style = tunables.table_style(&ty);
let table = tunables
.create_table(&ty, &style)
.create_host_table(&ty, &style)
.map_err(RuntimeError::new)?;

let num_elements = table.size();
Expand Down
Loading

0 comments on commit 04091d8

Please sign in to comment.