↩️ Exposing host (imported) functions
A Wasm module can import entities, like functions, memories, globals and tables. This example illustrates how to expose functions from the host.
Up until now, our WebAssembly program has only been able to do pure computation, that is, take arguments and return values. Most interesting use cases require more than just computation though. In this section we'll go over how to give the Wasm modules we run extra abilities in the form of host functions.
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.
- 1.There will be a
get_counter
function that will return ani32
ofthe current global counter. - 2.There will be an
add_to_counter
function that will add the passedi32
value to the counter, and return ani32
of the currentglobal counter.
First we are going to want to initialize a new project. To do this we can navigate to our project folder, or create one. In this example, we will create a new project. Lets create it and navigate to it:
Rust
cargo new imported-function-env
cd imported-function-env
We have to modify
Cargo.toml
to add the Wasmer dependencies as shown below:[dependencies]
# The Wasmer API
wasmer = "3.0"
Now that we have everything set up, let's go ahead and try it out!
Because we want to store data outside of the Wasm module and have host functions use this data, we need to do some preparation. We'll need to declare the data we want to use and the container to hold it.
Rust
let shared_counter: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
#[derive(Clone)]
struct Env {
counter: Arc<Mutex<i32>>,
}
Here we use a combination of
Arc
and Mutex
to guarantee thread safety while allowing mutability.Now that our data is available we'll declare the functions.
Rust
fn get_counter(env: FunctionEnvMut<Env>) -> i32 {
*env.data().counter.lock().unwrap()
}
fn add_to_counter(env: &FunctionEnvMut<Env>, add: i32) -> i32 {
let mut counter_ref = env.data().counter.lock().unwrap();
*counter_ref += add;
*counter_ref
}
As you can see here, both functions take an extra parameter in the form of a mutable reference to an
Env
which is the container we created to hold our data.The last thing we need to do now is to imports the function in the Wasm module.
Rust
let get_counter_func = Function::new_typed_with_env(
&mut store,
Env { counter: shared_counter.clone() },
get_counter
);
let add_to_counter_func = Function::new_typed_with_env(
&mut store,
Env { counter: shared_counter.clone() },
add_to_counter
);
let import_object = imports! {
"env" => {
"get_counter" => get_counter_func,
"add_to_counter" => add_to_counter_func,
}
};
We use
Function::new_typed_with_env
here to tell Wasmer our host functions need our Env
to be passed in addition to other arguments.If the host function does not need external data (it is pure) we use the
new_typed
function instead, which has the same signature except that it doesn't take the env
parameter.Now each time the
add_to_counter
will be run from the Wasm module it will alter the data on the host side.We now have everything we need to run the Wasm module, let's do it!
Rust
You should be able to run it using the
cargo run
command. The output should look like this:Compiling module...
Instantiating module...
Initial ounter value: 0
Calling `increment_counter_loop` function...
New counter value (host): 5
New counter value (guest): 5
git clone https://github.com/wasmerio/wasmer.git
cd wasmer
cargo run --example imported-function-env --release --features "cranelift"
Last modified 7mo ago