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
- Just-In-Time (JIT) Compilers: Emulators, virtual machines, and custom scripting languages can translate guest code into Wasm bytecode at runtime for native-like execution speeds.
- Dynamic Query Optimization: Database engines running in the browser (like DuckDB or SQLite via Wasm) can compile SQL queries into optimized Wasm bytecode on the fly to accelerate execution.
- Mathematical Function Evaluation: Applications that require plotting or evaluating user-defined mathematical formulas can compile those formulas into Wasm at runtime to avoid the overhead of JavaScript evaluation.
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.
Modern Browsers: Require the
wasm-unsafe-evaldirective in thescript-srcpolicy:Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';Older Browsers: May require the broader and less secure
unsafe-evaldirective:Content-Security-Policy: script-src 'self' 'unsafe-eval';
Without these directives, calling WebAssembly.compile()
or WebAssembly.instantiate() on a dynamically generated
byte array will throw a SecurityError.