Interpreting WebAssembly Modules with Rust

If we really enjoyed punishing ourselves with loads of tedious work, we could start with the binary interface and write a parser for it. We could then write code that maintains the stack state, executes the various WebAssembly core instructions, and manages linear memory and all the other things needed for the low-level interface.

Thankfully we don’t have to do that. The creators of the wasmi[30] crate have done us a huge favor in that regard. Originally designed as a way to help Parity Tech create an ethereum client for contracts implemented in WebAssembly, Parity pulled the core pieces out of their code and made separate crates for interpreting and manipulating WebAssembly modules.

You’ll get your first exposure to this crate by accomplishing the simplest task—executing a WebAssembly function in a module that has no import needs and exports nothing but a single function. To get started, create a new binary Rust project with the following command:

 $ ​​cargo​​ ​​new​​ ​​--bin​​ ​​wasmi_add

This creates a new Rust project that is a standalone binary (we’ve been creating dynamic libraries for WebAssembly modules so far) that can be executed from the command line. The first step is to add a dependency on the wasmi crate to the project:

 [package]
 name = ​"wasmi_add"
 version = ​"0.1.0"
 authors = [​"Your Address <[email protected]>"​]
 
 [dependencies]
 wasmi = ​"0.4.0"

Replace your default main.rs with the following code. We’ll be adding to it piece by piece as this code is probably new, even to many veteran Rust programmers. In the first section, our main function returns a Result type. This lets us use the ? operation that will either give us the good value inside the result or return an error from the function immediately.

The first thing you’ll do is load the WebAssembly module from the fundamentals chapter (add.wasm) into a vector of bytes (the u8 type) and create a Module from that buffer:

 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​ f = ​File​::​open​(​"../fundamentals/add.wasm"​)?;
  f​.read_to_end​(&​mut​ buffer)?;
  }
 let​ module = ​wasmi​::​Module​::​from_buffer​(buffer)?;

Next, we’ll create an instance of the module. You can think of this as a “running copy” of the module, which has its own state, memory, etc. As a host, the module instance is what you’ll interact with most of the time:

 let​ instance = ​ModuleInstance​::​new​(&module, &​ImportsBuilder​::​default​())
 .expect​(​"Failed to instantiate WASM module"​)
 .assert_no_start​();

This code creates a new module with a default set of imports, meaning we’re not satisfying any imports demanded by the module yet. 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 use of expect is just another way of forcing a panic if we get a failing result.

Now that we’ve got a module instance, we can invoke a function. As a refresher, here’s what our add function looked like:

 (module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
  (i32.add
  (get_local $lhs)
  (get_local $rhs)
  )
  )
  (export "add" (func $add))
 )

This code takes two i32 parameters and returns an i32 value. We execute that using the wasmi crate like so:

 let​ ​mut​ args = ​Vec​::<RuntimeValue>::​new​();
 args​.push​(​RuntimeValue​::​from​(42));
 args​.push​(​RuntimeValue​::​from​(1));
 
 let​ result: Option<RuntimeValue> =
  instance​.invoke_export​(​"add"​, &args, &​mut​ NopExternals)?;

Here you call invoke_export with the name of the exported function. This name must match and 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. It’s an enum, and as such, it’s incredibly easy to use pattern matching to extract results from, as shown in the rest of the code from main.rs:

 match​ result {
 Some​(​RuntimeValue​::​I32​(v)) ​=>​ {
  println!(​"The answer to your addition was {}"​, v);
  }
 Some​(_) ​=>​ {
  println!(​"Got a value of an unexpected data type"​);
  }
  None ​=>​ {
  println!(​"Failed to get a result from wasm invocation"​);
  }
  }
 Ok​(())
 }

There are a couple of places in this code that are more verbose than they needed to be, but it helps to see how everything works in long form before taking some shortcuts. When you run this code, you should see that it performs the addition just the way you’d expect:

 $ ​​cargo​​ ​​run
  Compiling wasmi_add v0.1.0
  (file:///home/kevin/Code/Rust/wasmbook/khrust/Book/code/wasmi_add)
  Finished dev [unoptimized + debuginfo] target(s) in 1.48s
  Running `target/debug/wasmi_add`
 The answer to your addition was 43

And just like that, you’ve created a Rust console application that hosts a WebAssembly module. Hopefully the real power of WebAssembly is starting to hit you. Because next, we’re going to build a Rust console application that can run the checkers game we wrote earlier in the book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.100.205