From a7579c052eb22fb259cdf3f8db7dce14d9a04a24 Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Thu, 23 Feb 2023 15:38:36 +0100 Subject: [PATCH 1/4] Added FunctionEnvMut::data_end_store_mut (will help #3592) --- Cargo.toml | 5 + examples/README.md | 2 + examples/imports_function_env_global.rs | 156 ++++++++++++++++++++++++ lib/api/src/sys/function_env.rs | 11 ++ 4 files changed, 174 insertions(+) create mode 100644 examples/imports_function_env_global.rs diff --git a/Cargo.toml b/Cargo.toml index 0b693d68e89..969c57f2e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -241,6 +241,11 @@ name = "imported-function-env" path = "examples/imports_function_env.rs" required-features = ["cranelift"] +[[example]] +name = "imported-function-env-global" +path = "examples/imports_function_env_global.rs" +required-features = ["cranelift"] + [[example]] name = "hello-world" path = "examples/hello_world.rs" diff --git a/examples/README.md b/examples/README.md index e48b3f8ea6f..8502a7dc8b6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -368,6 +368,8 @@ example. [exported-memory]: ./exports_memory.rs [imported-global]: ./imports_global.rs [imported-function]: ./imports_function.rs +[imported-function-env]: ./imports_function_env.rs +[imported-function-env-global]: ./imports_function_env_global.rs [instance]: ./instance.rs [wasi]: ./wasi.rs [wasi-pipes]: ./wasi_pipes.rs diff --git a/examples/imports_function_env_global.rs b/examples/imports_function_env_global.rs new file mode 100644 index 00000000000..fb9cb41debd --- /dev/null +++ b/examples/imports_function_env_global.rs @@ -0,0 +1,156 @@ +//! A Wasm module can import entities, like functions, memories, +//! globals and tables. +//! +//! In this example, we'll create a system for getting and adjusting a counter value. However, host +//! functions are not limited to storing data outside of Wasm, they're normal host functions and +//! can do anything that the host can do. +//! we will also demonstrate how a Function can also get globals from within a wasm call +//! +//! 1. There will be a `get_counter` function that will return an i32 of +//! the current global counter, The function will also increment a global value +//! 2. There will be an `add_to_counter` function will add the passed +//! i32 value to the counter, and return an i32 of the current +//! global counter. +//! +//! You can run the example directly by executing in Wasmer root: +//! +//! ```shell +//! cargo run --example imported-function-env-global --release --features "cranelift" +//! ``` +//! +//! Ready? + +use std::sync::{Arc, Mutex}; +use wasmer::{ + imports, wat2wasm, Function, FunctionEnv, FunctionEnvMut, Global, Instance, Module, Store, + TypedFunction, Value, +}; +use wasmer_compiler_cranelift::Cranelift; + +fn main() -> Result<(), Box> { + // Let's declare the Wasm module with the text representation. + let wasm_bytes = wat2wasm( + br#" +(module + (global $g_counter (import "env" "g_counter") (mut i32)) + (func $get_counter (import "env" "get_counter") (result i32)) + (func $add_to_counter (import "env" "add_to_counter") (param i32) (result i32)) + + (type $increment_t (func (param i32) (result i32))) + (func $increment_f (type $increment_t) (param $x i32) (result i32) + (block + (loop + (call $add_to_counter (i32.const 1)) + (set_local $x (i32.sub (get_local $x) (i32.const 1))) + (br_if 1 (i32.eq (get_local $x) (i32.const 0))) + (br 0))) + call $get_counter) + (export "increment_counter_loop" (func $increment_f))) +"#, + )?; + + // Create a Store. + // Note that we don't need to specify the engine/compiler if we want to use + // the default provided by Wasmer. + // You can use `Store::default()` for that. + let mut store = Store::new(Cranelift::default()); + + println!("Compiling module..."); + // Let's compile the Wasm module. + let module = Module::new(&store, wasm_bytes)?; + + // Create the global + let g_counter = Global::new_mut(&mut store, Value::I32(5)); + + // We create some shared data here, `Arc` is required because we may + // move our WebAssembly instance to another thread to run it. Mutex + // lets us get shared mutabilty which is fine because we know we won't + // run host calls concurrently. If concurrency is a possibilty, we'd have + // to use a `Mutex`. + let shared_counter: Arc> = Arc::new(Mutex::new(0)); + + // Once we have our counter we'll wrap it inside en `Env` which we'll pass + // to our imported functionsvia the FunctionEnv. + // + // This struct may have been anything. The only constraint is it must be + // possible to know the size of the `Env` at compile time (i.e it has to + // implement the `Sized` trait). + // The Env is then accessed using `data()` or `data_mut()` method. + #[derive(Clone)] + struct Env { + counter: Arc>, + g_counter: Global, + } + + // Create the functions + fn get_counter(env: FunctionEnvMut) -> i32 { + *env.data().counter.lock().unwrap() + } + fn add_to_counter(mut env: FunctionEnvMut, add: i32) -> i32 { + let (data, mut storemut) = env.data_and_store_mut(); + let mut counter_ref = data.counter.lock().unwrap(); + + let global_count = data.g_counter.get(&mut storemut).unwrap_i32(); + data.g_counter + .set(&mut storemut, Value::I32(global_count + add)); + + *counter_ref += add; + *counter_ref + } + + let env = FunctionEnv::new( + &mut store, + Env { + counter: shared_counter.clone(), + g_counter: g_counter.clone(), + }, + ); + + // Create an import object. + let import_object = imports! { + "env" => { + "get_counter" => Function::new_typed_with_env(&mut store, &env, get_counter), + "add_to_counter" => Function::new_typed_with_env(&mut store, &env, add_to_counter), + "g_counter" => g_counter.clone(), + } + }; + + println!("Instantiating module..."); + // Let's instantiate the Wasm module. + let instance = Instance::new(&mut store, &module, &import_object)?; + + // Here we go. + // + // The Wasm module exports a function called `increment_counter_loop`. Let's get it. + let increment_counter_loop: TypedFunction = instance + .exports + .get_function("increment_counter_loop")? + .typed(&mut store)?; + + let counter_value: i32 = *shared_counter.lock().unwrap(); + println!("Initial ounter value: {:?}", counter_value); + + println!("Calling `increment_counter_loop` function..."); + // Let's call the `increment_counter_loop` exported function. + // + // It will loop five times thus incrementing our counter five times. + let result = increment_counter_loop.call(&mut store, 5)?; + + let counter_value: i32 = *shared_counter.lock().unwrap(); + println!("New counter value (host): {:?}", counter_value); + assert_eq!(counter_value, 5); + + println!("New counter value (guest): {:?}", result); + assert_eq!(result, 5); + + let global_counter = g_counter.get(&mut store); + println!("New global counter value: {:?}", global_counter); + assert_eq!(global_counter.unwrap_i32(), 10); + + Ok(()) +} + +#[test] +fn test_imported_function_env() -> Result<(), Box> { + main() +} diff --git a/lib/api/src/sys/function_env.rs b/lib/api/src/sys/function_env.rs index 6ef7b48a3c9..08a0b68ec50 100644 --- a/lib/api/src/sys/function_env.rs +++ b/lib/api/src/sys/function_env.rs @@ -117,6 +117,17 @@ impl FunctionEnvMut<'_, T> { func_env: self.func_env.clone(), } } + + /// Borrows a new mutable reference of both the attached Store and host state + pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut) { + let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; + // telling the borrow check to close his eyes here + // this is still relatively safe to do as func_env are + // stored in a specific vec of Store, separate from the other objects + // and not really directly accessible with the StoreMut + let data = unsafe { &mut *data }; + (data, self.store_mut.as_store_mut()) + } } impl AsStoreRef for FunctionEnvMut<'_, T> { From b4101c4777bd63d2a32488966cc6951afc8bcd9c Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Thu, 23 Feb 2023 15:48:55 +0100 Subject: [PATCH 2/4] Add FunctionEnMut::data_and_store_mut on api/js too --- lib/api/src/js/function_env.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/api/src/js/function_env.rs b/lib/api/src/js/function_env.rs index 524fc7a2dbe..8b3e0a926a7 100644 --- a/lib/api/src/js/function_env.rs +++ b/lib/api/src/js/function_env.rs @@ -124,6 +124,17 @@ impl FunctionEnvMut<'_, T> { func_env: self.func_env.clone(), } } + + /// Borrows a new mutable reference of both the attached Store and host state + pub fn data_and_store_mut(&mut self) -> (&mut T, StoreMut) { + let data = self.func_env.as_mut(&mut self.store_mut) as *mut T; + // telling the borrow check to close his eyes here + // this is still relatively safe to do as func_env are + // stored in a specific vec of Store, separate from the other objects + // and not really directly accessible with the StoreMut + let data = unsafe { &mut *data }; + (data, self.store_mut.as_store_mut()) + } } impl AsStoreRef for FunctionEnvMut<'_, T> { From 6f3253e561873a612ab9ca64771b036ca0830784 Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Fri, 24 Feb 2023 12:12:30 +0100 Subject: [PATCH 3/4] Added a universal_test on FunctionEnvMut::data_and_store_mut function --- lib/api/tests/function_env.rs | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/api/tests/function_env.rs diff --git a/lib/api/tests/function_env.rs b/lib/api/tests/function_env.rs new file mode 100644 index 00000000000..0fd5418a429 --- /dev/null +++ b/lib/api/tests/function_env.rs @@ -0,0 +1,41 @@ +use macro_wasmer_universal_test::universal_test; +#[cfg(feature = "js")] +use wasm_bindgen_test::*; + +use wasmer::*; + +#[universal_test] +fn data_and_store_mut() -> Result<(), String> { + let mut store = Store::default(); + let global_mut = Global::new_mut(&mut store, Value::I32(10)); + struct Env { + value: i32, + global: Global, + } + let env = FunctionEnv::new( + &mut store, + Env { + value: 0i32, + global: global_mut.clone(), + }, + ); + let mut envmut = env.into_mut(&mut store); + + let (mut data, mut storemut) = envmut.data_and_store_mut(); + + assert_eq!( + data.global.ty(&mut storemut), + GlobalType { + ty: Type::I32, + mutability: Mutability::Var + } + ); + assert_eq!(data.global.get(&mut storemut), Value::I32(10)); + data.value = data.global.get(&mut storemut).unwrap_i32() + 10; + + data.global.set(&mut storemut, Value::I32(data.value)).unwrap(); + + assert_eq!(data.global.get(&mut storemut), Value::I32(data.value)); + + Ok(()) +} From ec14a758ac58b1233494519037f1fae295e458be Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Mon, 27 Feb 2023 10:19:58 +0100 Subject: [PATCH 4/4] Fixed linting --- lib/api/tests/function_env.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/api/tests/function_env.rs b/lib/api/tests/function_env.rs index 0fd5418a429..afdae7a865d 100644 --- a/lib/api/tests/function_env.rs +++ b/lib/api/tests/function_env.rs @@ -33,7 +33,9 @@ fn data_and_store_mut() -> Result<(), String> { assert_eq!(data.global.get(&mut storemut), Value::I32(10)); data.value = data.global.get(&mut storemut).unwrap_i32() + 10; - data.global.set(&mut storemut, Value::I32(data.value)).unwrap(); + data.global + .set(&mut storemut, Value::I32(data.value)) + .unwrap(); assert_eq!(data.global.get(&mut storemut), Value::I32(data.value));