From 08575a8b5e3d466f3b7793391f50cb2ce4f1fbc6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 8 Jul 2021 11:05:00 -0700 Subject: [PATCH 1/2] Add setter for enabling multi-memory to the C API. This commit adds `wasmtime_config_wasm_multi_memory_set` to the C API. Fixes #3066. --- crates/c-api/include/wasmtime/config.h | 8 ++++++++ crates/c-api/src/config.rs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h index 6c9e051f9a1b..356764e42a43 100644 --- a/crates/c-api/include/wasmtime/config.h +++ b/crates/c-api/include/wasmtime/config.h @@ -176,6 +176,14 @@ WASMTIME_CONFIG_PROP(void, wasm_bulk_memory, bool) */ WASMTIME_CONFIG_PROP(void, wasm_multi_value, bool) +/** + * \brief Configures whether the WebAssembly multi-memory proposal is + * enabled. + * + * This setting is `false` by default. + */ +WASMTIME_CONFIG_PROP(void, wasm_multi_memory, bool) + /** * \brief Configures whether the WebAssembly module linking proposal is * enabled. diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index 3e6e313ba9d1..40def6148ff2 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -90,6 +90,11 @@ pub extern "C" fn wasmtime_config_wasm_multi_value_set(c: &mut wasm_config_t, en c.config.wasm_multi_value(enable); } +#[no_mangle] +pub extern "C" fn wasmtime_config_wasm_multi_memory_set(c: &mut wasm_config_t, enable: bool) { + c.config.wasm_multi_memory(enable); +} + #[no_mangle] pub extern "C" fn wasmtime_config_wasm_module_linking_set(c: &mut wasm_config_t, enable: bool) { c.config.wasm_module_linking(enable); From 5d92b75b8fcd8e5444787e66b76a79d29f8d17e7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 8 Jul 2021 11:49:06 -0700 Subject: [PATCH 2/2] Add multimemory example. Add an example that deals with multiple memories in a single Wasm module. --- examples/multimemory.c | 327 +++++++++++++++++++++++++++++++++++++++ examples/multimemory.rs | 121 +++++++++++++++ examples/multimemory.wat | 28 ++++ 3 files changed, 476 insertions(+) create mode 100644 examples/multimemory.c create mode 100644 examples/multimemory.rs create mode 100644 examples/multimemory.wat diff --git a/examples/multimemory.c b/examples/multimemory.c new file mode 100644 index 000000000000..5d4ea982e30a --- /dev/null +++ b/examples/multimemory.c @@ -0,0 +1,327 @@ +/* +An example of how to interact with multiple memories. + +You can compile and run this example on Linux with: + + cargo build --release -p wasmtime-c-api + cc examples/multimemory.c \ + -I crates/c-api/include \ + -I crates/c-api/wasm-c-api/include \ + target/release/libwasmtime.a \ + -lpthread -ldl -lm \ + -o multimemory + ./multimemory + +Note that on Windows and macOS the command will be similar, but you'll need +to tweak the `-lpthread` and such annotations. +*/ + +#include +#include +#include +#include +#include +#include + +static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap); + +void check(bool success) { + if (!success) { + printf("> Error, expected success\n"); + exit(1); + } +} + +void check_call(wasmtime_context_t *store, + wasmtime_func_t *func, + const wasmtime_val_t* args, + size_t nargs, + int32_t expected) { + wasmtime_val_t results[1]; + wasm_trap_t *trap = NULL; + wasmtime_error_t *error = wasmtime_func_call( + store, func, args, nargs, results, 1, &trap + ); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function", error, trap); + if (results[0].of.i32 != expected) { + printf("> Error on result\n"); + exit(1); + } +} + +void check_call0(wasmtime_context_t *store, wasmtime_func_t *func, int32_t expected) { + check_call(store, func, NULL, 0, expected); +} + +void check_call1(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg, int32_t expected) { + wasmtime_val_t args[1]; + args[0].kind = WASMTIME_I32; + args[0].of.i32 = arg; + check_call(store, func, args, 1, expected); +} + +void check_call2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2, int32_t expected) { + wasmtime_val_t args[2]; + args[0].kind = WASMTIME_I32; + args[0].of.i32 = arg1; + args[1].kind = WASMTIME_I32; + args[1].of.i32 = arg2; + check_call(store, func, args, 2, expected); +} + +void check_ok(wasmtime_context_t *store, wasmtime_func_t *func, const wasmtime_val_t* args, size_t nargs) { + wasm_trap_t *trap = NULL; + wasmtime_error_t *error = wasmtime_func_call(store, func, args, nargs, NULL, 0, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to call function", error, trap); +} + +void check_ok2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2) { + wasmtime_val_t args[2]; + args[0].kind = WASMTIME_I32; + args[0].of.i32 = arg1; + args[1].kind = WASMTIME_I32; + args[1].of.i32 = arg2; + check_ok(store, func, args, 2); +} + +void check_trap(wasmtime_context_t *store, + wasmtime_func_t *func, + const wasmtime_val_t *args, + size_t nargs, + size_t num_results) { + assert(num_results <= 1); + wasmtime_val_t results[1]; + wasm_trap_t *trap = NULL; + wasmtime_error_t *error = wasmtime_func_call(store, func, args, nargs, results, num_results, &trap); + if (error != NULL) + exit_with_error("failed to call function", error, NULL); + if (trap == NULL) { + printf("> Error on result, expected trap\n"); + exit(1); + } + wasm_trap_delete(trap); +} + +void check_trap1(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg) { + wasmtime_val_t args[1]; + args[0].kind = WASMTIME_I32; + args[0].of.i32 = arg; + check_trap(store, func, args, 1, 1); +} + +void check_trap2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2) { + wasmtime_val_t args[2]; + args[0].kind = WASMTIME_I32; + args[0].of.i32 = arg1; + args[1].kind = WASMTIME_I32; + args[1].of.i32 = arg2; + check_trap(store, func, args, 2, 0); +} + +int main(int argc, const char* argv[]) { + // Initialize. + printf("Initializing...\n"); + + wasm_config_t *config = wasm_config_new(); + assert(config != NULL); + wasmtime_config_wasm_multi_memory_set(config, true); + + wasm_engine_t *engine = wasm_engine_new_with_config(config); + assert(engine != NULL); + + wasmtime_store_t* store = wasmtime_store_new(engine, NULL, NULL); + wasmtime_context_t *context = wasmtime_store_context(store); + + // Load our input file to parse it next + FILE* file = fopen("examples/multimemory.wat", "r"); + if (!file) { + printf("> Error loading file!\n"); + return 1; + } + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + wasm_byte_vec_t wat; + wasm_byte_vec_new_uninitialized(&wat, file_size); + if (fread(wat.data, file_size, 1, file) != 1) { + printf("> Error loading module!\n"); + return 1; + } + fclose(file); + + // Parse the wat into the binary wasm format + wasm_byte_vec_t binary; + wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &binary); + if (error != NULL) + exit_with_error("failed to parse wat", error, NULL); + wasm_byte_vec_delete(&wat); + + // Compile. + printf("Compiling module...\n"); + wasmtime_module_t* module = NULL; + error = wasmtime_module_new(engine, (uint8_t*) binary.data, binary.size, &module); + if (error) + exit_with_error("failed to compile module", error, NULL); + wasm_byte_vec_delete(&binary); + + // Instantiate. + printf("Instantiating module...\n"); + wasmtime_instance_t instance; + wasm_trap_t *trap = NULL; + error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap); + if (error != NULL || trap != NULL) + exit_with_error("failed to instantiate", error, trap); + wasmtime_module_delete(module); + + // Extract export. + printf("Extracting exports...\n"); + wasmtime_memory_t memory0, memory1; + wasmtime_func_t size0, load0, store0, size1, load1, store1; + wasmtime_extern_t item; + bool ok; + ok = wasmtime_instance_export_get(context, &instance, "memory0", strlen("memory0"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_MEMORY); + memory0 = item.of.memory; + ok = wasmtime_instance_export_get(context, &instance, "size0", strlen("size0"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + size0 = item.of.func; + ok = wasmtime_instance_export_get(context, &instance, "load0", strlen("load0"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + load0 = item.of.func; + ok = wasmtime_instance_export_get(context, &instance, "store0", strlen("store0"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + store0 = item.of.func; + ok = wasmtime_instance_export_get(context, &instance, "memory1", strlen("memory1"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_MEMORY); + memory1 = item.of.memory; + ok = wasmtime_instance_export_get(context, &instance, "size1", strlen("size1"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + size1 = item.of.func; + ok = wasmtime_instance_export_get(context, &instance, "load1", strlen("load1"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + load1 = item.of.func; + ok = wasmtime_instance_export_get(context, &instance, "store1", strlen("store1"), &item); + assert(ok && item.kind == WASMTIME_EXTERN_FUNC); + store1 = item.of.func; + + // Check initial memory. + printf("Checking memory...\n"); + check(wasmtime_memory_size(context, &memory0) == 2); + check(wasmtime_memory_data_size(context, &memory0) == 0x20000); + check(wasmtime_memory_data(context, &memory0)[0] == 0); + check(wasmtime_memory_data(context, &memory0)[0x1000] == 1); + check(wasmtime_memory_data(context, &memory0)[0x1001] == 2); + check(wasmtime_memory_data(context, &memory0)[0x1002] == 3); + check(wasmtime_memory_data(context, &memory0)[0x1003] == 4); + + check_call0(context, &size0, 2); + check_call1(context, &load0, 0, 0); + check_call1(context, &load0, 0x1000, 1); + check_call1(context, &load0, 0x1001, 2); + check_call1(context, &load0, 0x1002, 3); + check_call1(context, &load0, 0x1003, 4); + check_call1(context, &load0, 0x1ffff, 0); + check_trap1(context, &load0, 0x20000); + + check(wasmtime_memory_size(context, &memory1) == 2); + check(wasmtime_memory_data_size(context, &memory1) == 0x20000); + check(wasmtime_memory_data(context, &memory1)[0] == 0); + check(wasmtime_memory_data(context, &memory1)[0x1000] == 4); + check(wasmtime_memory_data(context, &memory1)[0x1001] == 3); + check(wasmtime_memory_data(context, &memory1)[0x1002] == 2); + check(wasmtime_memory_data(context, &memory1)[0x1003] == 1); + + check_call0(context, &size1, 2); + check_call1(context, &load1, 0, 0); + check_call1(context, &load1, 0x1000, 4); + check_call1(context, &load1, 0x1001, 3); + check_call1(context, &load1, 0x1002, 2); + check_call1(context, &load1, 0x1003, 1); + check_call1(context, &load1, 0x1ffff, 0); + check_trap1(context, &load1, 0x20000); + + // Mutate memory. + printf("Mutating memory...\n"); + wasmtime_memory_data(context, &memory0)[0x1003] = 5; + check_ok2(context, &store0, 0x1002, 6); + check_trap2(context, &store0, 0x20000, 0); + + check(wasmtime_memory_data(context, &memory0)[0x1002] == 6); + check(wasmtime_memory_data(context, &memory0)[0x1003] == 5); + check_call1(context, &load0, 0x1002, 6); + check_call1(context, &load0, 0x1003, 5); + + wasmtime_memory_data(context, &memory1)[0x1003] = 7; + check_ok2(context, &store1, 0x1002, 8); + check_trap2(context, &store1, 0x20000, 0); + + check(wasmtime_memory_data(context, &memory1)[0x1002] == 8); + check(wasmtime_memory_data(context, &memory1)[0x1003] == 7); + check_call1(context, &load1, 0x1002, 8); + check_call1(context, &load1, 0x1003, 7); + + // Grow memory. + printf("Growing memory...\n"); + uint32_t old_size; + error = wasmtime_memory_grow(context, &memory0, 1, &old_size); + if (error != NULL) + exit_with_error("failed to grow memory", error, trap); + check(wasmtime_memory_size(context, &memory0) == 3); + check(wasmtime_memory_data_size(context, &memory0) == 0x30000); + + check_call1(context, &load0, 0x20000, 0); + check_ok2(context, &store0, 0x20000, 0); + check_trap1(context, &load0, 0x30000); + check_trap2(context, &store0, 0x30000, 0); + + error = wasmtime_memory_grow(context, &memory0, 1, &old_size); + assert(error != NULL); + wasmtime_error_delete(error); + error = wasmtime_memory_grow(context, &memory0, 0, &old_size); + if (error != NULL) + exit_with_error("failed to grow memory", error, trap); + + error = wasmtime_memory_grow(context, &memory1, 2, &old_size); + if (error != NULL) + exit_with_error("failed to grow memory", error, trap); + check(wasmtime_memory_size(context, &memory1) == 4); + check(wasmtime_memory_data_size(context, &memory1) == 0x40000); + + check_call1(context, &load1, 0x30000, 0); + check_ok2(context, &store1, 0x30000, 0); + check_trap1(context, &load1, 0x40000); + check_trap2(context, &store1, 0x40000, 0); + + error = wasmtime_memory_grow(context, &memory1, 1, &old_size); + assert(error != NULL); + wasmtime_error_delete(error); + error = wasmtime_memory_grow(context, &memory1, 0, &old_size); + if (error != NULL) + exit_with_error("failed to grow memory", error, trap); + + // Shut down. + printf("Shutting down...\n"); + wasmtime_store_delete(store); + wasm_engine_delete(engine); + + // All done. + printf("Done.\n"); + return 0; +} + +static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) { + fprintf(stderr, "error: %s\n", message); + wasm_byte_vec_t error_message; + if (error != NULL) { + wasmtime_error_message(error, &error_message); + wasmtime_error_delete(error); + } else { + wasm_trap_message(trap, &error_message); + wasm_trap_delete(trap); + } + fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data); + wasm_byte_vec_delete(&error_message); + exit(1); +} diff --git a/examples/multimemory.rs b/examples/multimemory.rs new file mode 100644 index 000000000000..9a1dd8e161e7 --- /dev/null +++ b/examples/multimemory.rs @@ -0,0 +1,121 @@ +//! An example of how to interact with multiple memories. +//! +//! Here a small wasm module with multiple memories is used to show how memory +//! is initialized, how to read and write memory through the `Memory` object, +//! and how wasm functions can trap when dealing with out-of-bounds addresses. + +// You can execute this example with `cargo run --example example` + +use anyhow::Result; +use wasmtime::*; + +fn main() -> Result<()> { + // Enable the multi-memory feature. + let mut config = Config::new(); + config.wasm_multi_memory(true); + + let engine = Engine::new(&config)?; + + // Create our `store_fn` context and then compile a module and create an + // instance from the compiled module all in one go. + let mut store = Store::new(&engine, ()); + let module = Module::from_file(store.engine(), "examples/multimemory.wat")?; + let instance = Instance::new(&mut store, &module, &[])?; + + let memory0 = instance + .get_memory(&mut store, "memory0") + .ok_or(anyhow::format_err!("failed to find `memory0` export"))?; + let size0 = instance.get_typed_func::<(), i32, _>(&mut store, "size0")?; + let load0 = instance.get_typed_func::(&mut store, "load0")?; + let store0 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store0")?; + + let memory1 = instance + .get_memory(&mut store, "memory1") + .ok_or(anyhow::format_err!("failed to find `memory1` export"))?; + let size1 = instance.get_typed_func::<(), i32, _>(&mut store, "size1")?; + let load1 = instance.get_typed_func::(&mut store, "load1")?; + let store1 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store1")?; + + println!("Checking memory..."); + assert_eq!(memory0.size(&store), 2); + assert_eq!(memory0.data_size(&store), 0x20000); + assert_eq!(memory0.data_mut(&mut store)[0], 0); + assert_eq!(memory0.data_mut(&mut store)[0x1000], 1); + assert_eq!(memory0.data_mut(&mut store)[0x1001], 2); + assert_eq!(memory0.data_mut(&mut store)[0x1002], 3); + assert_eq!(memory0.data_mut(&mut store)[0x1003], 4); + + assert_eq!(size0.call(&mut store, ())?, 2); + assert_eq!(load0.call(&mut store, 0)?, 0); + assert_eq!(load0.call(&mut store, 0x1000)?, 1); + assert_eq!(load0.call(&mut store, 0x1001)?, 2); + assert_eq!(load0.call(&mut store, 0x1002)?, 3); + assert_eq!(load0.call(&mut store, 0x1003)?, 4); + assert_eq!(load0.call(&mut store, 0x1ffff)?, 0); + assert!(load0.call(&mut store, 0x20000).is_err()); // out of bounds trap + + assert_eq!(memory1.size(&store), 2); + assert_eq!(memory1.data_size(&store), 0x20000); + assert_eq!(memory1.data_mut(&mut store)[0], 0); + assert_eq!(memory1.data_mut(&mut store)[0x1000], 4); + assert_eq!(memory1.data_mut(&mut store)[0x1001], 3); + assert_eq!(memory1.data_mut(&mut store)[0x1002], 2); + assert_eq!(memory1.data_mut(&mut store)[0x1003], 1); + + assert_eq!(size1.call(&mut store, ())?, 2); + assert_eq!(load1.call(&mut store, 0)?, 0); + assert_eq!(load1.call(&mut store, 0x1000)?, 4); + assert_eq!(load1.call(&mut store, 0x1001)?, 3); + assert_eq!(load1.call(&mut store, 0x1002)?, 2); + assert_eq!(load1.call(&mut store, 0x1003)?, 1); + assert_eq!(load1.call(&mut store, 0x1ffff)?, 0); + assert!(load0.call(&mut store, 0x20000).is_err()); // out of bounds trap + + println!("Mutating memory..."); + memory0.data_mut(&mut store)[0x1003] = 5; + + store0.call(&mut store, (0x1002, 6))?; + assert!(store0.call(&mut store, (0x20000, 0)).is_err()); // out of bounds trap + + assert_eq!(memory0.data(&store)[0x1002], 6); + assert_eq!(memory0.data(&store)[0x1003], 5); + assert_eq!(load0.call(&mut store, 0x1002)?, 6); + assert_eq!(load0.call(&mut store, 0x1003)?, 5); + + memory1.data_mut(&mut store)[0x1003] = 7; + + store1.call(&mut store, (0x1002, 8))?; + assert!(store1.call(&mut store, (0x20000, 0)).is_err()); // out of bounds trap + + assert_eq!(memory1.data(&store)[0x1002], 8); + assert_eq!(memory1.data(&store)[0x1003], 7); + assert_eq!(load1.call(&mut store, 0x1002)?, 8); + assert_eq!(load1.call(&mut store, 0x1003)?, 7); + + println!("Growing memory..."); + memory0.grow(&mut store, 1)?; + assert_eq!(memory0.size(&store), 3); + assert_eq!(memory0.data_size(&store), 0x30000); + + assert_eq!(load0.call(&mut store, 0x20000)?, 0); + store0.call(&mut store, (0x20000, 0))?; + assert!(load0.call(&mut store, 0x30000).is_err()); + assert!(store0.call(&mut store, (0x30000, 0)).is_err()); + + assert!(memory0.grow(&mut store, 1).is_err()); + assert!(memory0.grow(&mut store, 0).is_ok()); + + memory1.grow(&mut store, 2)?; + assert_eq!(memory1.size(&store), 4); + assert_eq!(memory1.data_size(&store), 0x40000); + + assert_eq!(load1.call(&mut store, 0x30000)?, 0); + store1.call(&mut store, (0x30000, 0))?; + assert!(load1.call(&mut store, 0x40000).is_err()); + assert!(store1.call(&mut store, (0x40000, 0)).is_err()); + + assert!(memory1.grow(&mut store, 1).is_err()); + assert!(memory1.grow(&mut store, 0).is_ok()); + + Ok(()) +} diff --git a/examples/multimemory.wat b/examples/multimemory.wat new file mode 100644 index 000000000000..d65f4898e2dc --- /dev/null +++ b/examples/multimemory.wat @@ -0,0 +1,28 @@ +(module + (memory (export "memory0") 2 3) + (memory (export "memory1") 2 4) + + (func (export "size0") (result i32) (memory.size 0)) + (func (export "load0") (param i32) (result i32) + local.get 0 + i32.load8_s (memory 0) + ) + (func (export "store0") (param i32 i32) + local.get 0 + local.get 1 + i32.store8 (memory 0) + ) + (func (export "size1") (result i32) (memory.size 1)) + (func (export "load1") (param i32) (result i32) + local.get 0 + i32.load8_s (memory 1) + ) + (func (export "store1") (param i32 i32) + local.get 0 + local.get 1 + i32.store8 (memory 1) + ) + + (data (memory 0) (i32.const 0x1000) "\01\02\03\04") + (data (memory 1) (i32.const 0x1000) "\04\03\02\01") +)