Rust on WASM

ubolonton

Created: 2019-09-23 Mon 16:11

About Me

WebAssembly 101

What Is It?

  • Portable binary instruction format
  • Like Assembly, but not really
  • For the Web, but not only

Why Is It Interesting?

  • Portable, vendor-neutral
  • Embedding and sandboxing
  • Simple compilation target
  • Good performance
  • JVM/.NET, but better this time

Current State

  • Web: Firefox, Chrome, Safari, Edge
    • DOM via JS, eventually direct
  • Non-web: Wasmer, Lucet, wasmtime, NodeJS
    • WIP: WASI for rich host APIs
  • System languages: C/C++, Rust
    • Also C#, Go, but GC is an obstacle

Technical Detalis

  • Structured stack machine
  • Core types: i32, i64, f32, f64
  • Linear memory, optional GC
  • Built-in modules
  • Text format: S-expresssions

Linear memory

  • Growable, contiguous addressing
  • Min (+max) pages pre-declared
  • Separate from stack & globals
  • Copied, shared is WIP

Modules

  • Similar to Mach-O, ELF, PE
  • Imports, exports
  • Functions, start function
  • Local & global variables
  • Data section

Text format

  • .wat
  • S-expressions

    (module
     (type (;0;) (func (param i32 i32 i32) (result i32)))
     (memory (export "mem") 1)
     (import "env" "_memcpy" (func (type 0)))
     (func $multiply (param $lhs i32) (param $rhs i32) (result i32)
       local.get $lhs
       local.get $rhs
       i32.mul)
     (export "multi" (func $multiply))
     (func (export "greeting_") (result i32 i32) (i32.const 0) (i32.const 5))
     (data (i32.const 0) "hello"))
    

Rust on WASM

Why Rust?

  • Tooling, traction
  • Greatly overlapped interest
  • No GC: Easy to target WASM
  • Highest-level among non-gc langs

Targets

  • wasm32-unknown-emscripten
  • wasm32-unknown-unknown

wasm32-unknown-emscripten

  • emscripten, emcc
  • C-on-wasm interop (stdlib and so on)
  • Focuses on whole programs

wasm32-unknown-unknown

  • LLVM, clang
  • Barebone, minimal
  • Focuses on (dynamic) libraries
  • Officially preferred

Tooling

stdweb & cargo-web

  • Tooling for building web apps
  • Higher-level APIs
  • Hand-written web bindings
  • Targets both emscripten and barebone
  • Embedded JS, with interpolation: js!

    let button = document().query_selector("#hide-button")?.unwrap();
    button.add_event_listener(move |_: ClickEvent| {
        for anchor in document().query_selector_all( "#main a" ) {
            js!( @{anchor}.style = "display: none;"; );
        }
    });
    
    #[js_export]
    fn hash(string: String) -> String {
        let mut hasher = Sha1::new();
        hasher.update(string.as_bytes());
        hasher.digest().to_string()
    }
    

wasm-bindgen & wasm-pack

  • Tooling for working with WASM
  • Lower-level building blocks
  • Auto-generated web bindings
  • Targets wasm32-unknown-unknown

    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen]
    extern "C" {
        fn alert(s: &str);
    }
    
    #[wasm_bindgen]
    pub fn greet(name: &str) {
        alert(&format!("Hello, {}!", name));
    }
    

web-sys & js-sys

  • Built on wasm-bindgen
  • Low-level, "standard library" for Web/JS
  • Eventually faster than JS DOM APIs

    let document = web_sys::window()?.document()?;
    let body = document.body()?;
    let val = document.create_element("p")?;
    val.set_inner_html("Hello from Rust!");
    body.append_child(&val)?;
    

Integration

JavaScript: Parcel

  • New JS bundler
  • Supports Rust (wasm-bindgen)

    // synchronous import
    import { add } from './add.rs'
    console.log(add(2, 3))
    // asynchronous import
    const { add } = await import('./add.rs')
    console.log(add(2, 3))
    

Native Hosts: WASI

  • WebAssembly System Interface
  • "POSIX for WASM": "syscalls", libc
  • Capability-oriented
  • Wasmer, Lucet, wasmtime

wasi-software-architecture.png

Other Langs: Interface Types

  • Data sharing for dynamic linking
  • Pre-defined interface types
  • Per-module concrete repr
  • Pre-defined adapter instructions
  • Direct-copy optimization

    (module
      (memory (export "mem") 1)
      (data (i32.const 0) "hello there")
      (func (export "greeting_") (result i32 i32)
        i32.const 0    ;; offset of string in memory
        i32.const 11   ;; length
        )
      (@interface func (export "greeting") (result string)
        call-export "greeting_"
        memory-to-string "mem"))