What WebAssembly actually is
WebAssembly, or wasm, is a binary format that every major browser can run. It became a W3C standard in 2019, so it is officially a grown-up web technology now, sitting right next to HTML, CSS and JavaScript.
Nobody writes it by hand, thankfully. You write C, C++ or Rust, a compiler turns that into a .wasm file, and the browser runs the result inside a sandboxed virtual machine.
Short version: code written for desktop programs can run in a web page, safely, at close to native speed.
Why browsers needed it
JavaScript is flexible, and you pay for that flexibility. The browser downloads text, parses it, guesses types while the code runs, then re-optimises when a guess turns out wrong. That is fine for menus and forms. It is not great for a game engine that has to hit a frame deadline sixty times a second.
WebAssembly takes most of the guessing out:
- Compact. A binary file is smaller than the same program as text and quicker to decode.
- Typed. Every value is a number of a known type, so nothing gets guessed at run time.
- Pre-optimised. The compiler already did the heavy lifting before the file reaches you.
- Predictable. No garbage collector pauses, so frame times stay steadier.
From source code to a running module
A wasm module goes through a short, strict routine before it does anything.
- Compile. A toolchain like Emscripten (built on LLVM) turns C or C++ into a
.wasmfile plus a little JavaScript glue. - Download. The browser fetches it like any other file, so it can be cached and served from a CDN.
- Validate. The browser checks that the module is well formed and type correct. If it isn't, it never runs.
- Compile again. The engine translates the module for your actual CPU. Browsers can start while the file is still downloading, and many use a quick first pass followed by an optimising pass for hot code.
- Instantiate. The module is linked to the functions it imports from the page and given its memory. After that you can call its exports.
In JavaScript, loading one looks like this:
const { instance } = await WebAssembly.instantiateStreaming(
fetch("engine.wasm"),
{ env: { log: (value) => console.log(value) } } // imports the module may call
);
instance.exports.start(); // exports the page may call
How it executes
Wasm defines a simple stack machine. Instructions push and pop values, so "push 2, push 3, add" leaves 5 on the stack. There are four basic number types, i32 and i64 integers and f32 and f64 floats, plus a 128 bit v128 for SIMD.
Control flow is structured: blocks, loops and branches to labelled blocks, and no jumping to a random address. That limit is what makes validation fast and stops a module from wandering out of its own code.
| Concept | What it is |
|---|---|
| Module | The compiled unit: functions, memory, tables, imports and exports. |
| Instance | A module that has been linked and given its own state. |
| Function | Typed code that takes numbers in and gives numbers back. |
| Linear memory | One resizable block of bytes the module reads and writes. |
| Table | A list of function references, used for indirect calls and virtual functions. |
| Import / export | The only doors between the module and the outside world. |
Memory, and why it can't escape
A module never sees your computer's memory. It gets linear memory: one flat array of bytes that grows in 64 KiB pages. C++ pointers become offsets into that array, and reading past the end traps instead of touching anything else.
The same rule covers everything else. A module can't open a file, make a socket or draw a pixel by itself. It can only call functions the page handed it. So the page decides what the module may do, and the module follows the same rules as the rest of the page.
Threads, SIMD and speed
Games want more than one core. WebAssembly borrows threads from the web platform instead of inventing its own:
- Web Workers run in parallel, and each can host a module instance.
- SharedArrayBuffer lets workers share memory, and Atomics give them safe locks and counters.
- Cross-origin isolation is required for shared memory, so the server has to send
Cross-Origin-Opener-PolicyandCross-Origin-Embedder-Policyheaders. PlayGTA5's server does. - SIMD instructions handle several numbers per instruction, which helps with animation, physics and image work.
With all that in place, well written WebAssembly usually lands within a small factor of the same program running natively. How small depends on the workload, the browser and your hardware, so be suspicious of anyone quoting one exact number.
Talking to the browser
A sealed module can't do anything useful alone, so everything goes through imports. Emscripten generates glue that maps familiar desktop ideas onto web APIs:
| A game needs | The web provides |
|---|---|
| Graphics | WebGL, or WebGPU for modern GPU access and compute |
| Files and saves | A virtual file system backed by browser storage (IndexedDB, OPFS) |
| Audio | Web Audio |
| Input | Keyboard, mouse, touch, Pointer Lock and the Gamepad API |
| Networking | fetch, WebSocket and WebRTC |
How GTA V fits in
Here is how this build is put together.
- The engine is a WebAssembly bundle. A compiled
.wasmengine, its glue and packed shader files add up to roughly 200 MB, downloaded once and cached. - Game data is streamed. Roughly 16 GB of archives are fetched in ranges, and an IO worker asks only for the blocks the game needs next.
- The browser keeps a block cache. Downloaded blocks live in the Origin Private File System (OPFS), which is why later launches start far faster.
- A separate worker renders with WebGPU. Draw calls from the engine turn into GPU work using shaders written in WGSL.
- The page stays responsive. Input, touch buttons, audio and settings run on the main thread while the heavy work happens in workers.
Why the first launch is slow: the browser has to download the engine, compile it and fill the block cache. Later launches skip most of that.
Where it falls over
- It needs WebGPU. Use a current Chromium browser.
chrome://gputells you whether WebGPU is available. - Hardware acceleration must be on. With software rendering the engine can't start.
- Memory is finite. A tab has a ceiling for WebAssembly memory, which is why the low-spec preset and a lower render scale matter on small devices.
- Keep the tab visible. Browsers throttle background tabs, which slows streaming and rendering.
- Use the device check. The launcher's Check this device tool tests the browser, GPU, memory and storage and tells you which one fails.
FAQ
What is WebAssembly?
WebAssembly (wasm) is a compact binary instruction format and a sandboxed virtual machine built into every major browser. It lets code written in languages like C++ and Rust run on the web at close to native speed.
Is WebAssembly faster than JavaScript?
For heavy numeric work such as physics, audio and rendering, usually yes, because the code is already compiled, typed and predictable. For ordinary page logic JavaScript is just as good, and the two are designed to be used together.
Is WebAssembly safe?
Yes. A module is validated before it runs, can only touch its own linear memory, and has no access to files, the network or the operating system except through functions the host page explicitly hands it.
Why does the game need WebGPU as well?
WebAssembly only runs code on the CPU. Drawing frames needs the graphics card, which the browser exposes through WebGPU.
Why do some browsers fail to run it?
The engine relies on WebAssembly threads, shared memory and WebGPU. Browsers or devices without WebGPU, or with hardware acceleration turned off, cannot start it.