Hosting wasm modules in Rust easily using ‘wasmi’

Reading Time: 4 minutes

In this series of WebAssembly, We are learning about WebAssembly and some of the cool CLI to play with wasm. In this blog, We are going to learn the hosting of our wasm modules in Rust. If you have not been following this series, you can check out the previous blogs related to WebAssembly, WABT and wasm-bindgen to give you an idea about wasm. Now let’s begin.

Introduction

Till now, you have seen that we have been hosting WebAssembly modules in web browsers, but that is not the only way to do so. We can host a wasm module outside a web browser like a Rust program.

We will see how to do it. To do so, we will need a crate called wasmi, but before that, let’s see the qualities of a good host.

Qualities of a Good Host

To be a good host, you need to do all the following things,

1. Load and Validate the wasm Binary

Any application acting as a host is responsible for loading the bytes from that file and validating that all of the preconditions for the raw format of the file are correct.

2. Expose Exports

A WebAssembly module must have at least one exported function otherwise the host cannot interact with the wasm module. The host access the wasm module through the exposed functions.

3. Satisfy Imports

Along with exporting functions, WebAssembly modules can also import functions satisfied by the host. For Example, In our previous blogs from this series, we have seen how the Javascript host satisfied the notify_piecemoved and notify_piececrowned functions for the wasm module to import.

4. Execute the module

Once asked to invoke a WebAssembly function, the host is responsible for traversing through the instructions in the wasm module and executing the required function as given in the module.

5. Module isolation

The host is responsible for isolating the module. A module should not be able to access or import other module’s memory unless authorised by the host via proper channels.

Hosting in Rust

Let us proceed step by step to create a host in Rust.

Creating project

Now we are done with learning the good qualities of a host, we are ready to host our multiplication wasm module with the name mul.wasm that we created in the previous blogs. First, create a binary project in Rust using the following command –

$ cargo new --bin wasmi_mul

This will create a Rust binary project with the name wasmi_mul. Now, add the wasmi crate dependency in the .toml file like given below.

[package]
name = "wasmi_mul"
version = "0.1.0"
authors = ["aman2909verma <aman.verma@knoldus.com>"]

[dependencies]
wasmi = "0.4.0"

This will add the wasmi dependency to your project.

Writing code for hosting

Till now we have successfully created a binary project and now it’s time to add a code for hosting the module. Add the following code to the main.rs file in the src folder of the project.

extern crate wasmi;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue};

fn main() -> Result<(), Box<Error>> {
    let mut buffer = Vec::new();
    {
        let mut data = File::open("./mul.wasm")?;
        data.read_to_end(&mut buffer)?;
    }
    let module = wasmi::Module::from_buffer(buffer)?;
    let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
                   .expect("Failed to instantiate WASM module")
                   .assert_no_start();
    let mut args = Vec::<RuntimeValue>::new();
    args.push(RuntimeValue::from(4));
    args.push(RuntimeValue::from(2));
    let result: Option<RuntimeValue> = instance.invoke_export("mul", &args, &mut NopExternals)?;
    match result {
        Some(RuntimeValue::I32(val)) => {
            println!("The answer to your multiplication was {}", val);
        }
        Some(_) => {
            println!("Got a value of an unexpected data type");
        }
        None => {
            println!("Failed to get a result from wasm invocation");
        }
    }
    Ok(())
}

Ok, this is a whole bunch of code. I will explain what it does.

Creating buffer and module instance

First, we create a vector named buffer which would store the WebAssembly module as a vector of bytes. Then we create a module from that buffer using the from_buffer method of the Module object of wasmi crate.

Then we create an instance of the module that we created earlier using the new method.

The assert_no_start() function gives us an executable module instance that will panic if the module has a start() function. If we knew our module needed initialization, we’d call the run_start() function instead. The expect method is another way of forcing a panic if we get a failing result.

Handling data types and function calls

If you forgot how our multiplication function looked like then here it is.

(module
	(func $mul (param $num1 i32) (param $num2 i32) (result i32)
		(i32.mul
			(get_local $num1)
			(get_local $num2)
		)
	)
	(export "mul" (func $mul))
)

Our function takes two i32 parameters and returns an i32 value as result. We call invoke_export() with the name of the exported function. This name must be the same as exported from the module as it is case-sensitive. The RuntimeValue is used as a way of converting from Rust-native data types into values that can be passed onto the
WebAssembly stack as function parameters. We declare a vector of RuntimeValue to store the arguments.

Printing Result

At last, we handle the result by matching the result variable and printing the output correspondingly.

To run the program simply write the following command in the terminal.

$ cargo run

It will show the following output

Compiling wasmi_mul v0.1.0
    (file:///home/knoldus/wasmi_mul)
Finished dev [unoptimized + debuginfo] target(s) in 1.48
Running `target/debug/wasmi_mul`
The answer to your multiplication was 8

The last line shows that we have successfully made a Rust host and our wasm module was successfully hosted by the rust program.


If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


hosting

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading