Chapter 18

Versions and Feature Detection

WebAssembly grows by proposals. Each one goes through a public process in the WebAssembly Community Group, is implemented in engines, and once it is finished, is folded into the next version of the specification. This chapter lists what each version contains, explains how a module can depend on newer features safely, and shows how to detect them at runtime.

#The versions18.1

WebAssembly 1.0, the MVP, standardised in 2019 but shipping in browsers since 2017: the four number types, functions, structured control flow, one linear memory, one table of functions, globals, imports and exports. Everything in Parts I to IV that is not marked otherwise.

WebAssembly 2.0, finished in 2022, collected the proposals that had shipped since:

Feature What it adds Chapter
sign extension i32.extend8_s and friends 4
non-trapping float-to-int the trunc_sat instructions 4
multiple values multiple results, and blocks with parameters 5, 6
bulk memory memory.copy, memory.fill, passive segments 9
reference types externref, multiple tables, table.get and friends 11
mutable globals importing and exporting mutable globals 11
fixed-width SIMD the v128 type and its instructions 19

WebAssembly 3.0, finished in 2025:

Feature What it adds Chapter
garbage collection struct and array types managed by the engine 22
typed function references non-null references, call_ref 22
exception handling tags, throw, try_table, exnref 20
tail calls return_call and friends 21
memory64 64-bit addresses 9
multiple memories more than one memory per module 9
extended constant expressions arithmetic in initialisers 11
relaxed SIMD faster SIMD with some non-determinism 19
JS string builtins fast JavaScript strings, in the JS API 16

Threads, despite shipping in every browser since around 2020, are still a separate proposal on the way to standardisation: the atomic instructions and shared memories of Chapter 23.

Proposals still in progress, as this book is written, include stack switching (for coroutines and effect handlers), JSPI (JavaScript promise integration, which lets synchronous WebAssembly code wait for a JavaScript promise), branch hinting, wide arithmetic (128-bit multiplication), custom page sizes, and memory control (unmapping and protecting pages).

#Support in browsers18.2

Every current version of Chrome, Edge, Firefox and Safari supports all of WebAssembly 2.0, and threads when the page is cross-origin isolated (Chapter 23). Support for the 3.0 features arrived at different times in different browsers: garbage collection and tail calls have been available everywhere since early 2025, while memory64 and some others reached Safari later than the others. The WebAssembly project keeps a current table at webassembly.org/features, and that, rather than any book, is the place to check.

For a game shipping today, the practical baseline is WebAssembly 2.0 plus whichever 3.0 features your language needs and your audience's browsers support. Everything in Parts I to V of this book is in that baseline.

#Why feature detection matters18.3

A module that uses an instruction an engine does not know fails to compile. There is no partial loading and no fallback: one unknown opcode anywhere, even in a function that is never called, and the whole module is rejected with a CompileError. So a program that wants to use a newer feature where it is available, without breaking where it is not, must ship two builds and choose between them before compiling either.

The way to choose is to ask the engine. Not by looking at the browser's version, which is unreliable, but by asking it to validate a tiny module that uses the feature. If it validates, the feature is there.

#Detecting features18.4

Here are four probes. Each is the smallest module that uses its feature:

(module
  (func (result v128) (v128.const i32x4 0 0 0 0)))
(module
  (func $f (return_call $f)))
(module
  (type $t (struct (field i32)))
  (func (result anyref) (struct.new $t (i32.const 0))))
(module
  (func (result exnref) (ref.null exn)))
import fs from 'node:fs';

for (const feature of ['simd', 'tail', 'gc', 'exnref']) {
  const bytes = fs.readFileSync(`${feature}.wasm`);
  console.log(feature.padEnd(8), WebAssembly.validate(bytes) ? 'supported' : 'not supported');
}
simd     supported
tail     supported
gc       supported
exnref   supported

Node 22 supports all four. In a page you would embed the probe modules as byte arrays in the JavaScript, since they are tiny:

node -e "console.log(JSON.stringify([...require('fs').readFileSync('simd.wasm')]))"
[0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,22,1,20,0,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11]

and choose a build:

const simd = new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,
  10,22,1,20,0,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11]);
const url = WebAssembly.validate(simd) ? 'game.simd.wasm' : 'game.wasm';
const { instance } = await WebAssembly.instantiateStreaming(fetch(url), imports);

The wasm-feature-detect package on npm is a ready-made collection of probes like these, one for every proposal.

Validation alone cannot detect everything. Threads need a shared memory, which a browser allows only on a cross-origin isolated page, so the probe for threads must check crossOriginIsolated too. JavaScript API features, such as the JS string builtins, are detected by trying the API.

#Telling tools which features you use18.5

The tools in this book accept every standard feature by default. Older tools, and some current ones, need to be told: wabt's tools take flags such as --enable-tail-call or --enable-all; Binaryen's take --enable-gc, --enable-simd or --all-features. When a tool rejects a module that an engine accepts, an unenabled feature is the usual reason.

#What you have seen18.6

WebAssembly grows through proposals, gathered into versions: 2.0 added multiple values, bulk memory, reference types and SIMD; 3.0 added garbage collection, exceptions, tail calls, memory64 and more. A module using an unsupported feature fails to compile entirely, so programs that want newer features ship several builds and pick one by validating tiny probe modules at runtime.

The following chapters take the most important newer features one at a time, starting with SIMD.