WASM and Rust

By Casey Allred

@sbditto85

Who am i?

  • Wrote ~20k LOC targeting WASM in Rust
  • UVU MCS project ~12k LOC currently at https://programwith.us
  • Work for LiveView Technology on the Web Development team
  • Have a wife, three beautiful little girls, and love playing Minecraft

Agenda

  • What is WASM?
  • WAT
  • WASM Bindgen
  • Rust frontend frameworks

What is WASM?

“WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.”

 

- WebAssembly Working Group (https://www.w3.org/wasm/)

 

WAT - WebAssembly Text format

  • S-Expressions
  • Types
  • Functions
  • Local/Global
  • Imports/Exports
  • Memory
  • Table

WAT - S-Expressions

(module)

“Lisp like” expressions where everything is surrounded in ()

 

Most simple WASM module is:

which is a module that has nothing and does nothing.

WAT - Types

WebAssembly has the following value types:

  • i32: 32-bit integer
  • i64: 64-bit integer
  • f32: 32-bit floating point
  • f64: 64-bit floating point

 

The value types i32 and i64 are not inherently signed or unsigned. The interpretation of these types is determined by individual operators.

WAT - Types

  • Each parameter and local variable has exactly one value type.
  • Function signatures consist of a sequence of zero or more parameter types and a sequence of zero or more return types
    • In the MVP, a function can have at most one return type.

WAT - Functions

  • The signature declares what the function takes (parameters) and returns (return values).
  • The locals are like vars in JavaScript, but with explicit types declared.
  • The body is just a linear list of low-level instructions.
( func <signature> <locals> <body> )

WAT - Functions

(func $add (param $lhs i32) (param $rhs i32) (result i32)
    get_local $lhs
    get_local $rhs
    i32.add
    )
(func $add (param i32) (param i32) (result i32) 
    get_local 0
    get_local 1
    i32.add
    )

WAT - Local/Global

(module
  (global $glob (mut i32) (i32.const 100))
  (func $add (param $lhs i32) (param $rhs i32) (result i32) (local $loc i32) 
    i32.const 10
    set_local $loc
    get_local $lhs
    get_local $rhs
    i32.add
    get_local $loc
    i32.add
    get_global $glob
    i32.add
    )
  (export "add" (func $add))
)

WAT - Imports/Exports

(module
  (global $glob (import "js" "global")  i32)
  (import "console" "log" (func $log (param i32)))
  (func (export "add") (param $lhs i32) (param $rhs i32) (result i32)
    i32.const 100
    call $log
    get_local $lhs
    get_local $rhs
    i32.add
    )
)
var importObject = {
  console: {
    log: function(arg) {
      console.log(arg);
    }
  }
};

WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject)
  .then(obj => {
    console.log(obj.instance.exports.add(1,1));
  });

WAT - Memory

  • Memory is just a large array of bytes that can grow over time.
  • WebAssembly contains instructions for reading and writing from linear memory.
    • i32.load
    • i32.store
    • ...

WAT - Memory

(module
  (import "console" "log" (func $log (param i32 i32)))
  (import "js" "mem" (memory 1))
  (data (i32.const 0) "Hi")
  (func (export "writeHi")
    i32.const 0  ;; pass offset 0 to log
    i32.const 2  ;; pass length 2 to log
    call $log))
function consoleLogString(offset, length) {
  var bytes = new Uint8Array(memory.buffer, offset, length);
  var string = new TextDecoder('utf8').decode(bytes);
  console.log(string);
}

var memory = new WebAssembly.Memory({initial:1});

var importObject = { console: { log: consoleLogString }, js: { mem: memory } };

WebAssembly.instantiateStreaming(fetch('logger2.wasm'), importObject)
  .then(obj => {
    obj.instance.exports.writeHi();
  });

WAT - Table

(module
  (table 2 funcref)
  (func $f1 (result i32)
    i32.const 42)
  (func $f2 (result i32)
    i32.const 13)
  (elem (i32.const 0) $f1 $f2)
  (type $return_i32 (func (result i32)))
  (func (export "callByIndex") (param $i i32) (result i32)
    local.get $i
    call_indirect (type $return_i32))
)
WebAssembly.instantiateStreaming(fetch('wasm-table.wasm'))
  .then(obj => {
    // returns 42
    console.log(obj.instance.exports.callByIndex(0)); 
    // returns 13
    console.log(obj.instance.exports.callByIndex(1)); 
    // returns an error, because there is no index position 2 in the table
    console.log(obj.instance.exports.callByIndex(2)); 
  });

WASM-Bindgen

Let's get rust to do all that for us!

Demo - webassembly.studio

Useful Resources

  • Programming WebAssembly with Rust
    • https://pragprog.com/book/khrust/programming-webassembly-with-rust
  • crates.io
    • web-sys
    • js-sys
    • wasm-bindgen

Rust Frameworks

WASM and Rust

By sbditto85

WASM and Rust

  • 782