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
- Install Rust Toolchain
- Install wasm-pack
- Install cargo-generate
- 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
-
Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
-
Any live cell with two or three live neighbours lives on to the next generation.
-
Any live cell with more than three live neighbours dies, as if by overpopulation.
-
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
- 787