WebAssembly
Run high-performance code in the browser
WebAssembly (Wasm) is a binary instruction format that enables high-performance applications to run in web browsers. It allows code written in languages like C, C++, and Rust to execute at near-native speed, making it ideal for compute-intensive tasks like video editing, gaming, CAD, and image processing.
What is WebAssembly?
Key Characteristics
WebAssembly has several defining characteristics: binary format (not text-based like JavaScript), stack-based virtual machine, linear memory model, portable across all modern browsers, and secure sandboxed execution environment.
Performance Comparison
Typical speedups include 10x to 20x faster for compute-heavy algorithms, 2x to 5x for general operations, and near-native performance for SIMD operations.
Basic Usage
Loading a WebAssembly Module
// Fetch and instantiate WASM
async function loadWasm() {
const response = await fetch('./module.wasm');
const bytes = await response.arrayBuffer();
// Instantiate with JavaScript imports
const { instance } = await WebAssembly.instantiate(bytes, {
env: {
memory: new WebAssembly.Memory({ initial: 256 }),
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
consoleLog: (value) => console.log(value)
}
});
return instance.exports;
}
// Usage
const wasm = await loadWasm();
const result = wasm.add(5, 3);
console.log(result);
Using wasm-bindgen (Rust)
// Rust code (lib.rs)
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
if n == 0 { return 0; }
if n == 1 { return 1; }
fibonacci(n.wrapping_sub(1)).wrapping_add(fibonacci(n.wrapping_sub(2)))
}
// JavaScript usage
import { fibonacci } from './pkg';
console.log(fibonacci(40));
Use Cases
1. Image/Video Processing
// Real-time image filters
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, width, height);
// Pass to WASM for processing
const processed = wasm.applyFilter(
imageData.data,
imageData.width,
imageData.height,
'blur'
);
ctx.putImageData(processed, 0, 0);
2. Gaming
// Game engine compiled to WASM
const game = await loadGame();
// Main loop
function gameLoop() {
wasm.update();
wasm.render();
requestAnimationFrame(gameLoop);
}
3. Cryptography
// Fast hashing
async function hashFile(file) {
const arrayBuffer = await file.arrayBuffer();
const hash = wasm.sha256(new Uint8Array(arrayBuffer));
return hash;
}
4. Audio Processing
// Web Audio API + WebAssembly
const audioContext = new AudioContext();
const processor = audioContext.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = (e) => {
const inputData = e.inputBuffer.getChannelData(0);
const outputData = e.outputBuffer.getChannelData(0);
wasm.processAudio(inputData, outputData, inputData.length);
};
Memory Management
Shared Linear Memory
// Create memory that both JS and WASM can access
const memory = new WebAssembly.Memory({
initial: 256, // Pages (64KB each)
maximum: 512,
shared: true // For multi-threading
});
// Access from JavaScript
const array = new Uint8Array(memory.buffer);
array[0] = 42;
// Access from WASM returns 42 at index 0
Passing Strings
// Helper functions for string conversion
function stringToPtr(str, wasm) {
const encoder = new TextEncoder();
const bytes = encoder.encode(str);
const ptr = wasm.alloc(bytes.length + 1);
const memory = new Uint8Array(wasm.memory.buffer);
memory.set(bytes, ptr);
memory[ptr + bytes.length] = 0; // Null terminator
return ptr;
}
function ptrToString(ptr, wasm) {
const memory = new Uint8Array(wasm.memory.buffer);
let end = ptr;
while (memory[end] !== 0) end++;
const bytes = memory.slice(ptr, end);
return new TextDecoder().decode(bytes);
}
SIMD (Single Instruction, Multiple Data)
Vector Operations
// 128-bit SIMD operations process 4 floats or 16 bytes at once
// Image processing example
function processPixelsSimd(data) {
const result = wasm.processSimd(data);
// Approximately 4x faster than scalar operations
return result;
}
Debugging
Source Maps
Compile with debug info using emcc -g4 source.c -o output.js
In DevTools you can see original C/C++ code, set breakpoints, and inspect variables.
Error Handling
try {
const result = wasm.riskyOperation();
} catch (e) {
if (e instanceof WebAssembly.RuntimeError) {
console.error('WASM Runtime Error:', e.message);
}
}
Best Practices
1. Optimize JavaScript/WASM Calls
// Avoid: Many small calls
for (let i = 0; i < 1000; i++) {
wasm.processOne(data[i]);
}
// Prefer: Batch operations
wasm.processMany(data, data.length);
2. Avoid Frequent Memory Resizing
// Pre-allocate sufficient memory
const memory = new WebAssembly.Memory({
initial: 256,
maximum: 256 // Fixed size
});
3. Use Workers for Heavy Computation
// main.js
const worker = new Worker('wasm-worker.js');
worker.postMessage({ imageData, filter: 'blur' });
worker.onmessage = (e) => {
displayResult(e.data);
};
// wasm-worker.js
importScripts('./module.js');
self.onmessage = async (e) => {
const wasm = await loadWasm();
const result = wasm.processImage(e.data.imageData);
self.postMessage(result);
};
Tools and Frameworks
Emscripten
Compile C/C++ to WASM:
emcc main.cpp -o index.html -s WASM=1 -s EXPORTED_FUNCTIONS='["_main","_calculate"]'
AssemblyScript
// TypeScript-like syntax
export function add(a: i32, b: i32): i32 {
return a + b;
}
// Compiles to WebAssembly
Rust + wasm-pack
Build Rust for web:
wasm-pack build --target web
Generates: pkg/package.json, pkg/module_bg.wasm, pkg/module.js (bindings)
WebAssembly enables near-native performance in browsers for compute-intensive tasks. Use it for image processing, games, cryptography, and audio/video manipulation. Minimize JS-WASM boundary crossings, manage memory carefully, and consider Web Workers for heavy computations. Modern tools like wasm-pack and Emscripten make integration straightforward.