Lesson 01-Basic Concepts of WebAssembly

Introduction to WebAssembly

What is WebAssembly

WebAssembly (commonly abbreviated as Wasm) is a portable, compact, and fast-loading low-level code format designed for web and network platforms. It is not a programming language but a virtual instruction set architecture (Virtual ISA) that allows code written in languages such as C, C++, and Rust to be compiled and executed in web browsers with near-native performance. The core goal of WebAssembly is to enable heavy applications and games, which are typically challenging or inefficient to run on the web, to operate in a browser environment while ensuring security and compatibility.

Advantages and Use Cases of WASM

Advantages of WASM

  • High Performance: WebAssembly’s binary format enables compiled languages like C++ and Rust to run in browsers with near-native execution speeds, making it ideal for compute-intensive applications such as 3D games, video editing, real-time communication, and cryptographic operations.
  • Compact Size: The optimized binary format is smaller than equivalent JavaScript code, speeding up resource loading and improving user experience.
  • Cross-Platform Compatibility: WASM code is independent of specific operating systems or hardware, ensuring consistent execution across any browser that supports WebAssembly, enhancing application portability.
  • Security: WASM runs in a sandboxed environment, adhering to the same-origin policy and other security measures as JavaScript, ensuring user safety.
  • Interoperability with JavaScript: WASM modules can directly call JavaScript functions and vice versa, allowing seamless integration into existing web applications to enhance functionality without disrupting the current technology stack.

Use Cases

  • Game Development: Leveraging WASM’s high performance, developers can create complex online games with smooth graphics rendering and physics simulations.
  • Image and Audio/Video Processing: For computationally intensive tasks like real-time image and video editing, filtering, and encoding/decoding, WASM offers performance comparable to desktop applications.
  • Cloud-Native Applications: On the server side, WASM serves as a lightweight sandboxed environment for running microservices and function-as-a-service, improving execution efficiency and security.
  • Data Science and Machine Learning: Due to high performance requirements, WASM can be used to run trained models for real-time predictions and data analysis.
  • Plugin Systems: As a secure sandbox, WASM can implement browser plugins or in-app plugin systems, allowing users to extend application functionality without security risks.
  • Cryptocurrency and Blockchain: For applications requiring high-performance cryptographic operations, such as crypto wallets and decentralized applications (DApps), WASM provides a secure and efficient execution environment.

How WASM Works

The workflow of WebAssembly (WASM) involves several key steps, from source code to execution in a browser, as outlined below:

  1. Source Code Writing: Developers write code in supported languages like C, C++, or Rust.
  2. Compilation to WASM: Using appropriate compilers (e.g., Emscripten, Clang/LLVM, or Rust’s compiler), the source code is compiled into WebAssembly bytecode (.wasm file). This process involves converting high-level code into an abstract syntax tree (AST), then into WASM’s intermediate representation (IR), and finally generating binary WASM code.
  3. Module Validation: Before loading in the browser, the generated WASM binary code undergoes a validation phase to ensure compliance with WASM specifications, free of malicious or non-compliant instructions, guaranteeing execution safety.
  4. Compilation and Execution:
    • Interpreted Execution: Early implementations may directly interpret WASM code.
    • Just-In-Time (JIT) Compilation: Modern browsers typically use JIT compilation, converting WASM bytecode to native machine code at runtime to enhance execution efficiency.
  5. Memory Management and Stack-Based Model: WASM operates on a stack-based virtual machine, using a stack to store and manipulate data. It has its own linear memory space accessed via indices, distinct from JavaScript’s garbage collection, offering more direct memory control.
  6. Interaction with JavaScript: WASM modules can interoperate with JavaScript code at runtime through imported and exported functions, enabling WASM to call JavaScript functions and JavaScript to invoke WASM functions, achieving seamless integration.
  7. API Access: WASM modules can interact with the browser environment via Web APIs, such as accessing the DOM, handling network requests, or using WebGL for graphics rendering.

Relationship Between WASM and JavaScript

WebAssembly (WASM) and JavaScript have a complementary rather than competitive relationship, forming the foundation of modern web development. Key aspects of their relationship include:

  1. Complementarity: WASM is primarily used to enhance performance-critical parts of web applications, such as graphics rendering, audio processing, and scientific computations, while JavaScript handles business logic, event handling, and DOM manipulation. Together, they enable web applications to combine high-performance computation with flexible scripting.
  2. Interoperability: WASM modules can be called from JavaScript in the web environment, and WASM can invoke JavaScript functions, facilitating data exchange and functional interaction. This interoperability allows developers to embed WASM modules in JavaScript code or call JavaScript libraries from WASM to access Web APIs.
  3. Performance and Security: WASM’s near-native execution model provides higher performance for compute-intensive tasks, while JavaScript’s interpreted nature suits rapid development and iteration. Security-wise, WASM runs in a sandboxed environment, ensuring code isolation and aligning with JavaScript’s security standards.
  4. Development and Deployment: Although WASM can be compiled from languages like C++ or Rust, its deployment and integration rely on the JavaScript environment, such as loading WASM modules via fetch or import(). JavaScript’s mature ecosystem, including tools like Webpack and Babel, increasingly supports WASM packaging and optimization, streamlining integration.
  5. Future Trends: As WebAssembly evolves, proposals like WASI (WebAssembly System Interface) aim to enable WASM to run outside browsers, such as on servers or non-web environments, broadening its scope. Meanwhile, JavaScript continues to advance, with projects like JWST (JavaScript-to-WASM static compilers) blurring the boundaries between the two, fostering closer collaboration.

Browser Support

Since its introduction, WebAssembly (WASM) has been supported by all major browsers, including:

  • Google Chrome: Supported since Chrome 57, with ongoing performance and feature optimizations in later versions.
  • Mozilla Firefox: Supported since Firefox 52, with continuous improvements in subsequent releases.
  • Apple Safari: Supported since Safari 11.
  • Microsoft Edge: Supported since EdgeHTML 16 (approximately Edge 41), with full support in the Chromium-based Edge from its initial release.
  • Opera: As a Chromium-based browser, Opera aligns with Chrome’s support timeline, supporting WebAssembly from corresponding Chromium versions.

Additionally, most mobile browsers, including those based on the above engines, support WebAssembly, ensuring compatibility on devices like smartphones and tablets.

While modern browsers offer mature WebAssembly support, developers should consider users’ browser version distributions, especially for applications targeting older browsers. Feature detection or fallback solutions can ensure broad compatibility. For example, the following JavaScript snippet detects WebAssembly support:

if ('WebAssembly' in window) {
  console.log('Your browser supports WebAssembly!');
} else {
  console.log('Your browser does not support WebAssembly!');
}

WebAssembly Architecture

WebAssembly Runtime Environment (Browser, Node.js)

WebAssembly Runtime in Browsers

Modern browsers provide a WebAssembly runtime environment through JavaScript APIs, comprising the following core components:

  1. WebAssembly JavaScript API:
    • WebAssembly.Module – Represents a compiled WebAssembly module
    • WebAssembly.Instance – Represents an instantiated WebAssembly module
    • WebAssembly.Memory – Shared memory object
    • WebAssembly.Table – Function table object
    • WebAssembly.CompileError/WebAssembly.LinkError/WebAssembly.RuntimeError – Error handling

Basic Process for Loading WASM Modules in Browsers:

// 1. Fetch WASM binary file
fetch('module.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => {
    // 2. Compile module
    return WebAssembly.compile(bytes);
  })
  .then(module => {
    // 3. Create instance
    return new WebAssembly.Instance(module);
  })
  .then(instance => {
    // 4. Call exported function
    instance.exports.exported_func();
  });

Modern browsers (Chrome, Firefox, Safari, Edge) implement the WebAssembly MVP (Minimum Viable Product) specification and progressively support additional proposed features.

WebAssembly Runtime in Node.js

Node.js supports WebAssembly through the vm module and dedicated WebAssembly APIs:

  1. Node.js WebAssembly API:
    • Largely aligns with browser APIs but includes Node.js-specific extensions
    • Supports direct loading of WASM modules from the file system
    • Offers finer-grained memory management

Example of Loading WASM in Node.js:

const fs = require('fs');
const wasmBuffer = fs.readFileSync('module.wasm');

WebAssembly.compile(wasmBuffer).then(module => {
  const instance = new WebAssembly.Instance(module);
  instance.exports.exported_func();
});

Node.js v12+ fully supports WebAssembly, including advanced features like streaming compilation.

Components of WebAssembly Modules (Binary Module, Text Format)

Binary Module Format

The WebAssembly binary format (.wasm) is a compact binary encoding, consisting of the following sections:

  1. Module Structure:
    • Type section – Function signature types
    • Import section – Imported items
    • Function section – Function definitions
    • Table section – Function tables
    • Memory section – Memory definitions
    • Global section – Global variables
    • Export section – Exported items
    • Start section – Startup function
    • Element section – Table elements
    • Code section – Function bodies
    • Data section – Data segments
  2. Binary Encoding Features:
    • Uses LEB128 variable-length integer encoding
    • Compact binary representation
    • Strict module validation

Text Format (WAT)

The WebAssembly text format (.wat) is a human-readable representation, similar to assembly language:

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add))
)

The text format can be converted to and from the binary format:

  • wat2wasm – Text to binary
  • wasm2wat – Binary to text

WebAssembly Virtual Machine (Stack-Based Execution, Memory Management)

Stack-Based Virtual Machine Design

WebAssembly employs a stack-based virtual machine design with the following characteristics:

  1. Execution Model:
    • Explicit operand stack
    • No register architecture
    • Sequential instruction execution
  2. Instruction Set:
    • Numeric instructions (i32/i64/f32/f64)
    • Parameterized instructions (varying widths)
    • Control flow instructions (block/loop/if/else/br/br_if/br_table/return/unreachable)
    • Memory instructions (load/store)
    • Function call instructions (call/call_indirect)

Example Instruction Sequence:

func $add (param $a i32) (param $b i32) (result i32)
  local.get $a  ;; Push parameter a onto stack
  local.get $b  ;; Push parameter b onto stack
  i32.add       ;; Pop two values, add them, push result
  return        ;; Return top of stack

Memory Management

WebAssembly provides a linear memory model:

  1. Memory Characteristics:
    • Single contiguous byte array
    • Dynamically growable (via memory.grow)
    • Minimum initial size (default 64KB)
    • Maximum size limit (configurable)
  2. Memory Access:
    • Explicit load/store instructions
    • Boundary checks (prevent out-of-bounds access)
    • Supports various data types (i8/u8/i16/u16/i32/u32/f32/f64)

Memory Operation Example:

// Create shared memory in JavaScript
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });

// Access memory in WASM module
(memory.load i32 (i32.const 0))  ;; Read i32 from offset 0
(memory.store i32 (i32.const 0) (i32.const 42))  ;; Write 42 to offset 0

WebAssembly Loading and Initialization Process

Complete Loading Process

  1. Acquisition Phase:
    • Fetch .wasm file via fetch or fs.readFile
    • Obtain ArrayBuffer or Uint8Array
  2. Compilation Phase:
    • WebAssembly.compile – Asynchronously compile module
    • WebAssembly.compileStreaming – Stream compilation (more efficient)
  3. Instantiation Phase:
    • new WebAssembly.Instance – Create instance
    • Optional import object (importObject) can be passed
  4. Initialization Phase:
    • Execute start function (if present)
    • Prepare exported interfaces

Streaming Compilation Example:

WebAssembly.compileStreaming(fetch('module.wasm'))
  .then(module => {
    return new WebAssembly.Instance(module, {
      env: {
        imported_func: (arg) => console.log(arg)
      }
    });
  })
  .then(instance => {
    instance.exports.exported_func();
  });

Initialization Details

  1. Import Handling:
    • Validate import type matching
    • Establish connection between module and host environment
  2. Memory Initialization:
    • Allocate initial memory
    • Load data segments into memory
  3. Function Table Initialization:
    • Set function table entries
    • Prepare for indirect calls

WebAssembly Security Sandbox Mechanism

Security Features

  1. Memory Safety:
    • Strict boundary checks
    • No pointer arithmetic
    • Linear memory isolation
  2. Type Safety:
    • Static type system
    • Runtime type checking
  3. Execution Isolation:
    • No direct system calls
    • Restricted host interaction

Sandbox Implementation Mechanisms

  1. Memory Isolation:
    • Each module has independent memory space
    • Memory sharing controlled via import/export
  2. System Call Restrictions:
    • Access system resources only through predefined imported functions
    • No direct file/network access
  3. Control Flow Integrity:
    • No arbitrary jumps
    • Structured control flow

Security Boundary Example:

// Host environment defines strict import interface
const importObject = {
  env: {
    // Expose only safe system functions
    log: (ptr, len) => {
      const memory = new Uint8Array(wasmMemory.buffer);
      const str = new TextDecoder().decode(memory.slice(ptr, ptr + len));
      console.log(str);
    }
  }
};

// WASM module can only output via log function
// Cannot directly access console or other system APIs

Security Extensions

  1. Reference Types Proposal:
    • Allows safe referencing of host objects
    • Maintains memory safety
  2. Threads Proposal:
    • Shared memory multithreading
    • Atomic operations ensure synchronization
  3. Exception Handling Proposal:
    • Structured exception handling
    • Preserves control flow integrity

Development Environment Setup

Compilation Toolchains (Emscripten, LLVM)

Emscripten Toolchain

Emscripten is the primary toolchain for compiling C/C++ to WebAssembly:

  1. Core Components:
    • LLVM backend – Compiles C/C++ to LLVM IR
    • Fastcomp – Converts LLVM IR to asm.js/WebAssembly
    • Embind – C++/JavaScript binding generator
  2. Typical Compilation Process: emcc hello.c -o hello.html # Generates: # - hello.html (HTML wrapper) # - hello.js (JavaScript glue code) # - hello.wasm (WebAssembly module)
  3. Common Options:
    • -O0 to -O3 – Optimization levels
    • -s WASM=1 – Enable WebAssembly output
    • -s SIDE_MODULE=1 – Generate standalone module
    • -s EXPORTED_FUNCTIONS – Specify exported functions

LLVM/Clang Toolchain

WebAssembly is a first-class target in LLVM:

  1. Compilation Process: clang --target=wasm32 -O3 -nostdlib -Wl,--no-entry -Wl,--export-all -o hello.wasm hello.c
  2. Toolchain Components:
    • clang – C/C++ frontend
    • lld – WebAssembly linker
    • wasm-ld – Dedicated WASM linker
  3. Advanced Feature Support:
    • Multi-file compilation
    • Static library linking
    • Debug information generation

Browser Support (Chrome, Firefox, Safari, Edge)

Mainstream Browser Support

BrowserWebAssembly MVPStreaming CompilationThreads ProposalReference TypesBulk Memory Operations
Chrome57+61+74+79+79+
Firefox52+58+79+79+79+
Safari11+12.1+15+15.4+15.4+
Edge16+17+79+79+79+

Browser Feature Detection

if ('WebAssembly' in window) {
  console.log('Browser supports WebAssembly');

  // Detect streaming compilation
  if (typeof WebAssembly.compileStreaming === 'function') {
    console.log('Supports streaming compilation');
  }

  // Detect thread support
  if ('SharedArrayBuffer' in window) {
    console.log('Supports shared memory (threading foundation)');
  }
} else {
  console.error('Browser does not support WebAssembly');
}

Development Tools (WebAssembly Studio, WABT)

WebAssembly Studio

An online integrated development environment:

  1. Main Features:
    • Online editing of C/C++/Rust code
    • Real-time compilation to WebAssembly
    • Integrated debugger
    • Visual memory inspection
  2. Use Cases:
    • Rapid prototyping
    • Educational demonstrations
    • No local environment setup required

WebAssembly Binary Toolkit (WABT)

A collection of command-line tools:

  1. Main Tools:
    • wat2wasm – Text format to binary
    • wasm2wat – Binary to text
    • wasm-interp – Interpreter
    • wasm-validate – Module validation
    • wasm-strip – Remove debug information
  2. Typical Usage: # Convert WAT to WASM wat2wasm add.wat -o add.wasm # Validate module wasm-validate add.wasm # Disassemble for inspection wasm2wat add.wasm -o add.txt

Debugging Tools (Chrome DevTools, Wasmtime)

Chrome DevTools Debugging

  1. Debugging Features:
    • Source map support (maps WASM back to original C/C++)
    • Breakpoint setting
    • Call stack inspection
    • Variable inspection
  2. Usage Steps:
    • Compile with -g option to generate debug information
    • View WASM in DevTools’ Sources panel
    • Set breakpoints and step through execution

Wasmtime Debugging Tool

A WebAssembly runtime debugging tool:

  1. Main Features:
    • Command-line debugging
    • WASM module inspection
    • Performance profiling
    • Memory inspection
  2. Typical Usage: # Run and debug module wasmtime --debug add.wasm # Inspect module exports wasmtime inspect add.wasm

Example Code and Demo Execution

Simple Calculator Example (C to WASM)

  1. C Source Code (calc.c):
#include <emscripten.h>

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

EMSCRIPTEN_KEEPALIVE
int mul(int a, int b) {
    return a * b;
}
  1. Compilation Command:
emcc calc.c -o calc.js -s EXPORTED_FUNCTIONS='["_add", "_mul"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
  1. HTML Usage Example:
<!DOCTYPE html>
<html>
<head>
    <title>WASM Calculator</title>
    <script src="calc.js"></script>
</head>
<body>
    <script>
        // Method 1: Direct call
        Module.onRuntimeInitialized = function() {
            console.log(Module._add(2, 3)); // 5
            console.log(Module._mul(2, 3)); // 6
        };

        // Method 2: Using ccall
        console.log(Module.ccall('add', 'number', ['number', 'number'], [2, 3]));

        // Method 3: Using cwrap to create JS function
        const add = Module.cwrap('add', 'number', ['number', 'number']);
        console.log(add(2, 3));
    </script>
</body>
</html>

Rust to WASM Example

  1. Rust Source Code (lib.rs):
#[no_mangle]
pub extern "C" fn greet(name: *const u8, len: usize) -> *mut u8 {
    let name_slice = unsafe { std::slice::from_raw_parts(name, len) };
    let name_str = std::str::from_utf8(name_slice).unwrap();
    let greeting = format!("Hello, {}!", name_str);

    let c_str = std::ffi::CString::new(greeting).unwrap();
    c_str.into_raw()
}

#[no_mangle]
pub extern "C" fn free_string(s: *mut u8) {
    if s.is_null() { return; }
    unsafe {
        std::ffi::CString::from_raw(s);
    }
}
  1. Compilation Command:
cargo build --target wasm32-unknown-unknown --release
wasm-bindgen target/wasm32-unknown-unknown/release/rust_wasm.wasm --out-dir ./pkg
  1. JavaScript Usage:
import init, { greet, free_string } from './pkg/rust_wasm.js';

async function run() {
    await init();

    const name = new TextEncoder().encode("World");
    const ptr = greet(name.byteOffset, name.length);

    const greetingPtr = new Uint32Array(wasmMemory.buffer, ptr, 1)[0];
    const greetingLen = new Uint32Array(wasmMemory.buffer, ptr + 4, 1)[0];
    const greeting = new TextDecoder().decode(
        new Uint8Array(wasmMemory.buffer, greetingPtr, greetingLen)
    );

    console.log(greeting); // "Hello, World!"
    free_string(ptr);
}

run();

These examples demonstrate the basic process of compiling from different languages to WebAssembly and running in a browser, covering common use cases and toolchain configurations.

Share your love