WASM: Emscripten, Rust and Golang

Oleksii Vasyliev, Railsware

WASM: Emscripten, Rust and Golang

Brought to you by Alexey Vasiliev, Railsware

Oleksii Vasyliev

WebAssembly (WASM)

WASM goals

  • Be fast, efficient, and portable
  • Be readable and debuggable
  • Keep secure
  • Don't break the web
cat explorer

WebAssembly is a different language from JavaScript

WasmHowTo

Key concepts

Emscripten

Emscripten

The Emscripten tool is able to take just about any C/C++ source code and compile it into a .wasm module, plus the necessary JavaScript "glue" code for loading and running the module, and an HTML document to display the results of the code

Emscripten
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int fib(int n) {
  if(n <= 0){
    return 0;
  }
  int i, t, a = 0, b = 1;
  for (i = 1; i < n; i++) {
    t = a + b;
    a = b;
    b = t;
  }
  return b;
}

Emcc - Emscripten compiler command

emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.c
<script src="a.out.js"></script>
<script>
  Module.onRuntimeInitialized = _ => {
    const fib = Module.cwrap('fib', 'number', ['number']);
    console.log(fib(12));
  };
</script>
<script>
  (async function() {
    const imports = {
      env: {
        memory: new WebAssembly.Memory({initial: 1}),
        STACKTOP: 0,
      }
    };
    const {instance} = await WebAssembly.instantiateStreaming(
      fetch('/a.out.wasm'),
      imports
    );
    console.log(instance.exports._fib(12));
  })();
</script>

Compiling a C library

emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
    -I libwebp \
    webp.c \
    libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c

Webp Wasm

Rust

Wasm-pack

cargo install wasm-pack
npm install -g wasm-pack
yarn global add wasm-pack
wasm
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
$ wasm-pack build
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling proc-macro2 v1.0.32
Compiling unicode-xid v0.2.2
Compiling log v0.4.14
Compiling syn v1.0.82
Compiling wasm-bindgen-shared v0.2.78
...
Compiling js-sys v0.3.55
Compiling console_error_panic_hook v0.1.7
Compiling web-sys v0.3.55
Compiling wasm-game-of-life v0.1.0 (/Users/leo/Downloads/wasm_game_of_life)
Finished release [optimized] target(s) in 17.17s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary,
but recommended
[INFO]: ✨ Done in 21.55s

Using the package on the web

<script type="module">
import init, {greet} from "./pkg/hello_wasm.js";
init()
  .then(() => {
    greet("WebAssembly")
  });
</script>

Conway's Game of Life

Rust crates for WASM development

Rust crates for WASM development

Golang

Go + WASM is an Application, not a Library

package main
import (
  "fmt"
)

func main() {
  fmt.Println("Hello World")
  // Prevent the function from returning, which is required in a wasm module
  <-make(chan bool)
}

Go + WASM is an Application, not a Library

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
GOOS=js GOARCH=wasm go build -o main.wasm
const loadWasm = (path) => {
  const go = new Go()

  return new Promise((resolve, reject) => {
    WebAssembly.instantiateStreaming(fetch(path), go.importObject)
    .then(result => {
      go.run(result.instance)
      resolve(result.instance)
    })
    .catch(error => {
      reject(error)
    })
  })
}
// Load the wasm file
loadWasm("main.wasm").then(wasm => {
    console.log("main.wasm is loaded ")
}).catch(error => {
    console.error("ouch", error)
}) 

Work with DOM

package main
import (
    "syscall/js"
)

func main() {

  message := "Hello World"

  document := js.Global().Get("document")
  h2 := document.Call("createElement", "h2")
  h2.Set("innerHTML", message)
  document.Get("body").Call("appendChild", h2)

  <-make(chan bool)
}

Work with DOM

func MyGoFunc() js.Func {
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Get the current time as a Go time.Time object
		now := time.Now()
		// Get the Date object constructor from JavaScript
		dateConstructor := js.Global().Get("Date")
		// Return a new JS "Date" object with the time from the Go "now" variable
		// We're passing the UNIX timestamp to the "Date" constructor
		// Because JS uses milliseconds for UNIX timestamp, we need to multiply the timestamp by 1000
		return dateConstructor.New(now.Unix() * 1000)
	})
}

Go types <-> JavaScript types

| Go                     | JavaScript             |
| ---------------------- | ---------------------- |
| js.Value               | [its value]            |
| js.Func                | function               |
| nil                    | null                   |
| bool                   | boolean                |
| integers and floats    | number                 |
| string                 | string                 |
| []interface{}          | new array              |
| map[string]interface{} | new object             |

Unsupported in GO WASM

Vmail Wasm

Optimizations

Conclusion

<Thank You!> Questions?

Contact information

QuestionsSlide