โ† Back to Guides

WebAssembly Complete Guide

๐Ÿ“– 17 min read | ๐Ÿ“… Updated: October 2025 | ๐Ÿท๏ธ Other Technologies

Introduction

WebAssembly (WASM) is a binary instruction format that runs at near-native speed in browsers. This guide covers WASM fundamentals, compilation from C/C++/Rust, JavaScript integration, memory management, threading, and practical use cases for performance-critical web applications.

1. WebAssembly Fundamentals

# What is WebAssembly?

- **Binary format** for stack-based virtual machine
- **Near-native performance** (typically 50-80% of native speed)
- **Language-agnostic** (C, C++, Rust, Go, etc.)
- **Runs alongside JavaScript** in browsers
- **Safe and sandboxed** execution environment

# Key Features

1. **Fast**: Compiled to optimized machine code
2. **Safe**: Memory-safe, sandboxed execution
3. **Open**: Designed as open standard
4. **Portable**: Runs on any platform with WASM runtime
5. **Compact**: Binary format is smaller than JavaScript

# Use Cases

- **Compute-intensive tasks**: Image/video processing, compression
- **Games**: 3D engines, physics simulations
- **Scientific computing**: Data analysis, machine learning
- **Cryptography**: Encryption, hashing algorithms
- **Legacy code**: Port existing C/C++ libraries to web
- **Performance-critical**: Audio/video codecs, parsers

# WASM vs JavaScript Performance

Task                    JavaScript    WebAssembly
Image processing        100ms         20ms (5x faster)
Matrix multiplication   500ms         80ms (6x faster)
Fibonacci (recursive)   200ms         150ms (1.3x faster)
Compression             1000ms        200ms (5x faster)

2. Getting Started with Emscripten (C/C++)

# Install Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

# Simple C program
// hello.c
#include <stdio.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

EMSCRIPTEN_KEEPALIVE
void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

int main() {
    printf("WebAssembly module loaded\n");
    return 0;
}

# Compile to WebAssembly
emcc hello.c -o hello.js -s EXPORTED_FUNCTIONS='["_add", "_greet"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'

# This generates:
# - hello.wasm (binary module)
# - hello.js (JavaScript glue code)

# Load in HTML
<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Demo</title>
</head>
<body>
    <script src="hello.js"></script>
    <script>
        Module.onRuntimeInitialized = () => {
            // Call exported functions
            const add = Module.cwrap('add', 'number', ['number', 'number']);
            const result = add(5, 3);
            console.log('5 + 3 =', result);
            
            // Call function with string
            Module.ccall('greet', null, ['string'], ['World']);
        };
    </script>
</body>
</html>

# Image processing example
// blur.c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
void blur(unsigned char* data, int width, int height) {
    unsigned char* temp = (unsigned char*)malloc(width * height * 4);
    
    for (int y = 1; y < height - 1; y++) {
        for (int x = 1; x < width - 1; x++) {
            int idx = (y * width + x) * 4;
            
            for (int c = 0; c < 3; c++) {
                int sum = 0;
                for (int dy = -1; dy <= 1; dy++) {
                    for (int dx = -1; dx <= 1; dx++) {
                        int i = ((y + dy) * width + (x + dx)) * 4 + c;
                        sum += data[i];
                    }
                }
                temp[idx + c] = sum / 9;
            }
            temp[idx + 3] = data[idx + 3]; // Alpha channel
        }
    }
    
    memcpy(data, temp, width * height * 4);
    free(temp);
}

# Compile
emcc blur.c -o blur.js -s EXPORTED_FUNCTIONS='["_blur"]' -s ALLOW_MEMORY_GROWTH=1

3. Rust and WebAssembly

# Install wasm-pack
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

# Create new Rust WASM project
cargo new --lib my_wasm_lib
cd my_wasm_lib

# Cargo.toml
[package]
name = "my_wasm_lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

# src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[wasm_bindgen]
pub struct Counter {
    value: i32,
}

#[wasm_bindgen]
impl Counter {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Counter {
        Counter { value: 0 }
    }
    
    pub fn increment(&mut self) {
        self.value += 1;
    }
    
    pub fn get_value(&self) -> i32 {
        self.value
    }
}

// Work with JavaScript types
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Build for web
wasm-pack build --target web

# This generates pkg/ directory with:
# - my_wasm_lib_bg.wasm
# - my_wasm_lib.js
# - my_wasm_lib.d.ts

# Use in HTML
<script type="module">
    import init, { add, fibonacci, Counter, greet } from './pkg/my_wasm_lib.js';
    
    async function run() {
        await init();
        
        console.log('5 + 3 =', add(5, 3));
        console.log('Fibonacci(10) =', fibonacci(10));
        console.log(greet('World'));
        
        const counter = new Counter();
        counter.increment();
        console.log('Counter:', counter.get_value());
    }
    
    run();
</script>

# Image manipulation example
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use web_sys::ImageData;

#[wasm_bindgen]
pub fn grayscale(data: &mut [u8]) {
    for chunk in data.chunks_exact_mut(4) {
        let avg = (chunk[0] as u32 + chunk[1] as u32 + chunk[2] as u32) / 3;
        chunk[0] = avg as u8;
        chunk[1] = avg as u8;
        chunk[2] = avg as u8;
    }
}

#[wasm_bindgen]
pub fn invert(data: &mut [u8]) {
    for i in (0..data.len()).step_by(4) {
        data[i] = 255 - data[i];       // R
        data[i + 1] = 255 - data[i + 1]; // G
        data[i + 2] = 255 - data[i + 2]; // B
    }
}

4. JavaScript Integration

// Load WASM module directly
async function loadWASM() {
    const response = await fetch('module.wasm');
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.compile(buffer);
    const instance = await WebAssembly.instantiate(module);
    
    return instance.exports;
}

// Or use shorthand
async function loadWASMShort() {
    const { instance } = await WebAssembly.instantiateStreaming(
        fetch('module.wasm')
    );
    return instance.exports;
}

// Use the module
const wasm = await loadWASM();
const result = wasm.add(5, 3);
console.log(result);

// Pass memory between JS and WASM
const memory = new WebAssembly.Memory({ initial: 1 }); // 1 page = 64KB

const { instance } = await WebAssembly.instantiate(wasmModule, {
    env: {
        memory: memory,
        abort: () => console.error('WASM aborted')
    }
});

// Write data to WASM memory
const data = new Uint8Array(memory.buffer);
data[0] = 42;
data[1] = 100;

// Read from WASM memory
const result = new Uint32Array(memory.buffer, 0, 1);
console.log('Result:', result[0]);

// React component with WASM
import { useEffect, useState } from 'react';

function ImageProcessor() {
    const [wasm, setWasm] = useState(null);
    const [processing, setProcessing] = useState(false);
    
    useEffect(() => {
        loadWASM().then(setWasm);
    }, []);
    
    const processImage = async (imageFile) => {
        if (!wasm) return;
        
        setProcessing(true);
        
        const img = await loadImage(imageFile);
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);
        
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        
        // Process with WASM
        wasm.grayscale(imageData.data);
        
        ctx.putImageData(imageData, 0, 0);
        
        setProcessing(false);
        return canvas.toDataURL();
    };
    
    return (
        <div>
            <input 
                type="file" 
                accept="image/*"
                onChange={(e) => processImage(e.target.files[0])}
                disabled={!wasm || processing}
            />
            {processing && <p>Processing...</p>}
        </div>
    );
}

// Performance comparison
async function benchmark() {
    const size = 1000000;
    const data = new Float32Array(size);
    for (let i = 0; i < size; i++) {
        data[i] = Math.random();
    }
    
    // JavaScript implementation
    console.time('JS');
    let jsSum = 0;
    for (let i = 0; i < data.length; i++) {
        jsSum += data[i] * data[i];
    }
    console.timeEnd('JS');
    
    // WebAssembly implementation
    console.time('WASM');
    const wasm = await loadWASM();
    const wasmSum = wasm.sumSquares(data.length);
    console.timeEnd('WASM');
    
    console.log('JS:', jsSum, 'WASM:', wasmSum);
}

5. Memory Management

// WASM memory is linear array of bytes
const memory = new WebAssembly.Memory({ 
    initial: 10,  // 10 pages = 640KB
    maximum: 100  // 100 pages = 6.4MB
});

// Grow memory dynamically
memory.grow(5); // Add 5 pages (320KB)

// Access memory from JavaScript
const buffer = memory.buffer;
const uint8View = new Uint8Array(buffer);
const uint32View = new Uint32Array(buffer);
const float64View = new Float64Array(buffer);

// Rust example with manual memory management
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn allocate(size: usize) -> *mut u8 {
    let mut buf = Vec::with_capacity(size);
    let ptr = buf.as_mut_ptr();
    std::mem::forget(buf);
    ptr
}

#[wasm_bindgen]
pub fn deallocate(ptr: *mut u8, size: usize) {
    unsafe {
        let _ = Vec::from_raw_parts(ptr, 0, size);
    }
}

// JavaScript usage
const size = 1024;
const ptr = wasm.allocate(size);

// Use memory at ptr
const view = new Uint8Array(wasm.memory.buffer, ptr, size);
view[0] = 42;

// Free memory when done
wasm.deallocate(ptr, size);

// Better approach: Use Vec and return ownership
#[wasm_bindgen]
pub fn process_data(input: Vec<u8>) -> Vec<u8> {
    // Process data
    let mut output = input;
    for byte in &mut output {
        *byte = byte.wrapping_add(1);
    }
    output
}

// JavaScript
const input = new Uint8Array([1, 2, 3, 4, 5]);
const output = wasm.process_data(input);
console.log(output); // [2, 3, 4, 5, 6]

6. Threading and SIMD

// WebAssembly threads (requires SharedArrayBuffer)
// Note: Requires specific headers for cross-origin isolation

// C code with pthreads
#include <pthread.h>
#include <emscripten.h>

void* worker_thread(void* arg) {
    int* value = (int*)arg;
    *value = *value * 2;
    return NULL;
}

EMSCRIPTEN_KEEPALIVE
int parallel_compute(int n) {
    pthread_t threads[4];
    int values[4] = {n, n, n, n};
    
    for (int i = 0; i < 4; i++) {
        pthread_create(&threads[i], NULL, worker_thread, &values[i]);
    }
    
    for (int i = 0; i < 4; i++) {
        pthread_join(threads[i], NULL);
    }
    
    int sum = 0;
    for (int i = 0; i < 4; i++) {
        sum += values[i];
    }
    return sum;
}

# Compile with threading
emcc code.c -o output.js -pthread -s PTHREAD_POOL_SIZE=4

// SIMD (Single Instruction Multiple Data)
// Rust with SIMD
use std::arch::wasm32::*;

#[wasm_bindgen]
pub fn add_arrays_simd(a: &[f32], b: &[f32]) -> Vec<f32> {
    let mut result = Vec::with_capacity(a.len());
    
    unsafe {
        for i in (0..a.len()).step_by(4) {
            let va = v128_load(a.as_ptr().add(i) as *const v128);
            let vb = v128_load(b.as_ptr().add(i) as *const v128);
            let vr = f32x4_add(va, vb);
            
            let mut temp = [0f32; 4];
            v128_store(temp.as_mut_ptr() as *mut v128, vr);
            result.extend_from_slice(&temp);
        }
    }
    
    result
}

# Compile with SIMD support
wasm-pack build --target web -- --features simd

// Web Workers for parallel WASM execution
// main.js
const worker = new Worker('wasm-worker.js');

worker.postMessage({ 
    type: 'process', 
    data: largeDataArray 
});

worker.onmessage = (e) => {
    console.log('Result:', e.data.result);
};

// wasm-worker.js
importScripts('module.js');

let wasm;

Module.onRuntimeInitialized = () => {
    wasm = Module;
    self.postMessage({ type: 'ready' });
};

self.onmessage = (e) => {
    if (e.data.type === 'process') {
        const result = wasm.process(e.data.data);
        self.postMessage({ type: 'result', result });
    }
};

7. Real-World Examples

// Example 1: Video compression
// Rust implementation
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct VideoEncoder {
    width: u32,
    height: u32,
}

#[wasm_bindgen]
impl VideoEncoder {
    #[wasm_bindgen(constructor)]
    pub fn new(width: u32, height: u32) -> VideoEncoder {
        VideoEncoder { width, height }
    }
    
    pub fn encode_frame(&self, frame: &[u8]) -> Vec<u8> {
        // H.264 encoding logic (simplified)
        let mut compressed = Vec::new();
        
        // Apply DCT transform
        // Quantization
        // Entropy coding
        
        compressed
    }
}

// JavaScript usage
import init, { VideoEncoder } from './pkg/video_encoder.js';

async function encodeVideo(videoElement) {
    await init();
    
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = videoElement.videoWidth;
    canvas.height = videoElement.videoHeight;
    
    const encoder = new VideoEncoder(canvas.width, canvas.height);
    const frames = [];
    
    const captureFrame = () => {
        ctx.drawImage(videoElement, 0, 0);
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const compressed = encoder.encode_frame(imageData.data);
        frames.push(compressed);
    };
    
    videoElement.addEventListener('timeupdate', captureFrame);
}

// Example 2: Cryptography
use wasm_bindgen::prelude::*;
use sha2::{Sha256, Digest};
use aes::Aes256;
use block_modes::{BlockMode, Cbc};
use block_modes::block_padding::Pkcs7;

type Aes256Cbc = Cbc<Aes256, Pkcs7>;

#[wasm_bindgen]
pub fn hash_sha256(data: &[u8]) -> Vec<u8> {
    let mut hasher = Sha256::new();
    hasher.update(data);
    hasher.finalize().to_vec()
}

#[wasm_bindgen]
pub fn encrypt_aes(key: &[u8], iv: &[u8], plaintext: &[u8]) -> Vec<u8> {
    let cipher = Aes256Cbc::new_from_slices(key, iv).unwrap();
    cipher.encrypt_vec(plaintext)
}

// Example 3: Game physics
#[wasm_bindgen]
pub struct PhysicsWorld {
    bodies: Vec<RigidBody>,
    gravity: f32,
}

#[wasm_bindgen]
impl PhysicsWorld {
    pub fn step(&mut self, dt: f32) {
        for body in &mut self.bodies {
            body.velocity.y += self.gravity * dt;
            body.position.x += body.velocity.x * dt;
            body.position.y += body.velocity.y * dt;
        }
        
        self.check_collisions();
    }
    
    fn check_collisions(&mut self) {
        // Collision detection and response
    }
}

8. Best Practices

โœ“ WebAssembly Best Practices:

Conclusion

WebAssembly brings near-native performance to web applications. Use it for compute-intensive tasks like image processing, games, cryptography, and scientific computing. Compile from C/C++/Rust using Emscripten or wasm-pack, integrate seamlessly with JavaScript, and manage memory carefully. WASM is production-ready and used by major applications like Figma, Google Earth, and AutoCAD Web.

๐Ÿ’ก Pro Tip: Start with Rust and wasm-bindgen for the best developer experienceโ€”it provides excellent type safety, memory management, and integration with JavaScript. Use wasm-pack for easy builds and npm publishing. For existing C/C++ codebases, Emscripten works great but requires more manual memory management. Always benchmarkโ€”WASM shines for computational tasks but has overhead for small operations. Consider using AssemblyScript (TypeScript-like syntax) for easier learning. Monitor performance with Chrome DevTools WASM profiler and optimize hot paths with SIMD instructions.