Webassembly

What is Webassembly?

  • Open Standard
  • Binary instruction format
  • For stack-based virtual machine
  • Interfaces for interactions between program and their environment

Why Webassembly?

  • To get better performance on the web
    • low level binary format
    • smaller size
    • near native speed
  • JavaScript was created without modern programming paradigm in mind
  • JavaScript has some quirks
  • It's a binary format, means that you can write in any language that supports compiling into that format (C, C++, Go, Rust, etc)

Webassembly Format

  • Webassembly Text (WAT)
  • Binary (WASM)

Webassembly Text (WAT)

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

Webassembly Binary (WASM)

┌────────┬──────────────────────────────────────────────────┬────────────────┐
│00000000│ 00 61 73 6d 01 00 00 00  01 07 01 60 02 7f 7f 01 │0asm•000•••`••••│
│00000010│ 7f 03 02 01 00 07 07 01  03 61 64 64 00 00 0a 09 │••••0••••add00__│
│00000020│ 01 07 00 20 00 20 01 6a  0b                      │••0 0 •j•       │
└────────┴──────────────────────────────────────────────────┴────────────────┘

Milestones

  • April 2015 - WebAssembly Community Group started
  • June 2015 - The first public announcement
  • March 2016 - Definition of core feature with multiple interoperable implementations
  • October 2016 - Browser Preview announced with multiple interoperable implementations
  • February 2017 - Official logo chosen
  • March 2017 - Cross-browser consensus and end of Browser Preview
  • November 2017 - Shipped in all major browsers

Browser Support

June 2018: 85.57% of installed browsers (87.21% of desktop browsers)

Older Machines?

Compile to asm.js by a JavaScript polyfill

Mobile browsers?

Yes

 

https://developer.mozilla.org/en-US/docs/WebAssembly

https://caniuse.com/#feat=wasm

Why it's a big deal?

Sample Demo

Sample Demo

Sample Demo

Sample Demo

Let's build our own!

Rust + Webassembly

Prerequisites

  1. Install Rust Toolchain
  2. Install wasm-pack
  3. Install cargo-generate
  4. Install npm
> cargo generate --git https://github.com/rustwasm/wasm-pack-template
🤷  Project Name: wasm-game-of-life
🔧   Creating project called `wasm-game-of-life`...
✨   Done! New project created /Users/karuna/job/wasm-game-of-life
> cd wasm-game-of-life
> wasm-pack build
...
> ls pkg
README.md                 package.json              wasm_game_of_life.d.ts    wasm_game_of_life.js      wasm_game_of_life_bg.d.ts wasm_game_of_life_bg.wasm
> npm init wasm-app www
> cd www
> npm install
{
  // ...
  "dependencies": {
    "wasm-game-of-life": "file:../pkg", // Add this line!
    // ...
  }
}

www/package.json

import * as wasm from "wasm-game-of-life";

wasm.greet();

www/index.js

> npm install
> npm run start

open http://localhost:8080/

Rules

  1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.

  2. Any live cell with two or three live neighbours lives on to the next generation.

  3. Any live cell with more than three live neighbours dies, as if by overpopulation.

  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

[dependencies]
...
js-sys = "0.3"
fixedbitset = "0.1.9"

Cargo.toml

mod utils;
extern crate fixedbitset;

use fixedbitset::FixedBitSet;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
    Dead = 0,
    Alive = 1,
}

#[wasm_bindgen]
#[derive(Default)]
pub struct Universe {
    width: u32,
    height: u32,
    cells: FixedBitSet,
}

impl Universe {
    fn get_index(&self, row: u32, column: u32) -> usize {
        (row * self.width + column) as usize
    }

    fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
        let mut count = 0;
        for delta_row in [self.height - 1, 0, 1].iter().cloned() {
            for delta_col in [self.width - 1, 0, 1].iter().cloned() {
                if delta_row == 0 && delta_col == 0 {
                    continue;
                }

                let neighbor_row = (row + delta_row) % self.height;
                let neighbor_col = (column + delta_col) % self.width;
                let idx = self.get_index(neighbor_row, neighbor_col);
                count += self.cells[idx] as u8;
            }
        }
        count
    }
}

#[wasm_bindgen]
impl Universe {
    pub fn new() -> Universe {
        let width = 64;
        let height = 64;

        let size = (width * height) as usize;
        let mut cells = FixedBitSet::with_capacity(size);

        for i in 0..size {
            cells.set(i, js_sys::Math::random() < 0.5);
        }

        Universe {
            width,
            height,
            cells,
        }
    }

    pub fn tick(&mut self) {
        let mut next = self.cells.clone();

        for row in 0..self.height {
            for col in 0..self.width {
                let idx = self.get_index(row, col);
                let cell = self.cells[idx];
                let live_neighbors = self.live_neighbor_count(row, col);

                next.set(
                    idx,
                    match (cell, live_neighbors) {
                        (true, x) if x < 2 => false,
                        (true, 2) | (true, 3) => true,
                        (true, x) if x > 3 => false,
                        (false, 3) => true,
                        (otherwise, _) => otherwise,
                    },
                );
            }
        }

        self.cells = next;
    }

    pub fn width(&self) -> u32 {
        self.width
    }

    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn cells(&self) -> *const u32 {
        self.cells.as_slice().as_ptr()
    }
}

src/lib.rs

import { Universe, Cell } from "wasm-game-of-life";
import { memory } from "wasm-game-of-life/wasm_game_of_life_bg";

const CELL_SIZE = 5; // px
const GRID_COLOR = "#CCCCCC";
const DEAD_COLOR = "#FFFFFF";
const ALIVE_COLOR = "#000000";

const universe = Universe.new();
const width = universe.width();
const height = universe.height();

const canvas = document.getElementById("game-of-life-canvas");
canvas.height = (CELL_SIZE + 1) * height + 1;
canvas.width = (CELL_SIZE + 1) * width + 1;

const ctx = canvas.getContext('2d');

const renderLoop = () => {
  universe.tick();

  drawGrid();
  drawCells();

  requestAnimationFrame(renderLoop);
};

const drawGrid = () => {
  ctx.beginPath();
  ctx.strokeStyle = GRID_COLOR;

  // Vertical lines.
  for (let i = 0; i <= width; i++) {
    ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
    ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
  }

  // Horizontal lines.
  for (let j = 0; j <= height; j++) {
    ctx.moveTo(0,                           j * (CELL_SIZE + 1) + 1);
    ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
  }

  ctx.stroke();
};

const getIndex = (row, column) => {
  return row * width + column;
};

const bitIsSet = (n, arr) => {
  const byte = Math.floor(n / 8);
  const mask = 1 << (n % 8);
  return (arr[byte] & mask) === mask;
};


const drawCells = () => {
  const cellsPtr = universe.cells();

  const cells = new Uint8Array(memory.buffer, cellsPtr, width * height / 8);

  ctx.beginPath();

  for (let row = 0; row < height; row++) {
    for (let col = 0; col < width; col++) {
      const idx = getIndex(row, col);

      ctx.fillStyle = bitIsSet(idx, cells)
        ? ALIVE_COLOR
        : DEAD_COLOR;

      ctx.fillRect(
        col * (CELL_SIZE + 1) + 1,
        row * (CELL_SIZE + 1) + 1,
        CELL_SIZE,
        CELL_SIZE
      );
    }
  }

  ctx.stroke();
};


drawGrid();
drawCells();
requestAnimationFrame(renderLoop);

www/index.js

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello wasm-pack!</title>
  </head>
  <body>
    <canvas id="game-of-life-canvas"></canvas>
    <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
    <script src="./bootstrap.js"></script>
  </body>
</html>

www/index.html

$> wasm-pack build
$> cd www
$> npm install
$> npm run start

open http://localhost:8080

Is it only on browser?

  • Virtual Machine
  • Spec does not limit where it can be run

other execution runtime: wasmer, wasmtime, lucet

Wasmer example 1

> wapm install php
> cat phpinfo.php
> wapm run php phpinfo.php

Wasmer example 2

Wasmer example 3


#[no_mangle]
pub extern fn sum(x: i32, y: i32) -> i32 {
    x + y
}

simple.rs

Wasmer example 3

simple.rs

package main

import (
	"fmt"
	wasm "github.com/wasmerio/go-ext-wasm/wasmer"
)

func main() {
	bytes, _ := wasm.ReadBytes("simple.wasm")
	
	instance, _ := wasm.NewInstance(bytes)
	defer instance.Close()
	sum := instance.Exports["sum"]

	result, _ := sum(5, 37)

	fmt.Println(result) // 42!
}

Wasmer example 3

Who is using Webassembly?

  • AutoCad
  • Figma
  • Google Earth

Who is using Webassembly?

  • AutoCad
  • Figma
  • Google Earth

Fin

The End

Tamat

Webassembly

By Karuna Murti

Webassembly

Webassembly

  • 786