Dynamically Generate WebAssembly Bytecode in Browser

This article explains how to dynamically generate WebAssembly (Wasm) bytecode at runtime directly within a web browser. It covers the technical feasibility of browser-side Wasm generation, the APIs required to instantiate this bytecode, practical use cases like JIT compilers, and the security constraints you must navigate to implement this technique successfully.

Yes, You Can Generate Wasm at Runtime

It is entirely possible to dynamically generate and execute WebAssembly bytecode at runtime inside the browser. WebAssembly modules are ultimately represented as binary data—specifically, an array of bytes conforming to the WebAssembly binary format specification.

Because JavaScript can manipulate binary data using typed arrays (like Uint8Array), you can programmatically construct Wasm bytecode instruction-by-instruction in JavaScript and then compile it into an executable module on the fly.

How Runtime Wasm Generation Works

To generate and run WebAssembly dynamically in the browser, you follow a three-step process:

1. Generate the Byte Array

You must first generate a Uint8Array that contains the valid binary representation of a WebAssembly module. This module must include the standard Wasm headers, sections (such as Type, Function, Export, and Code sections), and the actual bytecode instructions.

For example, a minimal Wasm module that exports a function returning the number 42 looks like this in raw bytes:

const binary = new Uint8Array([
  0x00, 0x61, 0x73, 0x6d, // Magic number: "\0asm"
  0x01, 0x00, 0x00, 0x00, // Version: 1
  // ... (additional section bytes defining types, functions, and code)
]);

Because writing raw binary by hand is difficult, developers often use helper libraries in the browser: * Binaryen (binaryen.js): A compiler infrastructure library that can be loaded in the browser to generate and optimize Wasm code via a friendly API. * WABT (wabt.js): The WebAssembly Binary Toolkit, which allows you to write human-readable WebAssembly Text Format (WAT) as a string and compile it to binary format directly in the browser.

2. Compile and Instantiate the Bytes

Once you have the bytecode in a Uint8Array, you use the browser’s built-in WebAssembly API to compile and instantiate the module:

async function loadDynamicWasm(bytes) {
  // Compile the raw bytes into a WebAssembly Module
  const module = await WebAssembly.compile(bytes);
  
  // Instantiate the module with optional imports
  const instance = await WebAssembly.instantiate(module, {});
  
  // Call an exported function from the dynamically generated Wasm
  console.log(instance.exports.myGeneratedFunction());
}

Alternatively, you can use WebAssembly.instantiate(bytes) to compile and instantiate in a single step.

Key Use Cases

Security Constraints: Content Security Policy (CSP)

While technically feasible, runtime Wasm compilation is subject to browser security policies. If the website uses a Content Security Policy (CSP), generating Wasm from raw bytes may be blocked by default.

To allow runtime compilation of dynamic Wasm bytes, the server hosting the page must deliver a CSP header that explicitly permits it.

Without these directives, calling WebAssembly.compile() or WebAssembly.instantiate() on a dynamically generated byte array will throw a SecurityError.