WebAssembly Instruction Reference

Core WebAssembly instructions by type — numeric, memory, control and parametric.

Searchable WebAssembly MVP and 2.0 instruction reference with opcode, stack type signature and description, grouped into control, parametric, variable, memory and numeric categories. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What do the brackets in the type signature mean?

WebAssembly is a stack machine, so each instruction has a signature [args] to [results] describing the values it pops and pushes. For example i32.add has [i32 i32] to [i32]: it pops two 32-bit integers and pushes their sum. The symbol for the bottom type marks a stack-polymorphic or unreachable result.

A stack machine in bytecode

WebAssembly is a compact, typed stack machine. Each instruction pops a fixed set of operands off the value stack and pushes its results back, which makes the bytecode easy to validate and fast to compile. This reference groups the core MVP and WebAssembly 2.0 instructions into control, parametric, variable, memory and numeric categories, each with its opcode, stack type signature and a short description.

How it works

Every instruction is described by a signature [args] → [results]. Numeric instructions like i32.add consume their operands and push a typed result; the integer ops use two’s-complement wrapping while float ops follow IEEE-754. Control instructions (block, loop, if, br, br_table, call) manage labelled scopes and jumps — branches target blocks by depth, not by name, in the binary format.

Variable instructions move values between the stack and locals or globals, and memory instructions read and write the single linear memory using an address on the stack plus an alignment hint and offset immediate. Opcodes are single bytes for the MVP; later extensions such as bulk-memory use a 0xFC prefix and a sub-opcode. Filter the list above by name, opcode or category to find what you need.

The five instruction categories

Understanding which category an instruction belongs to clarifies what it can and cannot do:

Control instructions (block, loop, if, br, br_table, return, call, call_indirect, unreachable, nop) manage program flow. A key design choice: branches in WebAssembly jump forward out of a block or back to the top of a loop — there is no arbitrary goto. This structured control flow makes validation fast and prevents many classes of jump-target bugs.

Parametric instructions (drop, select) are type-agnostic and operate on any value type. drop discards the top of the stack; select pops a condition and picks between two typed values, like a ternary operator on the stack.

Variable instructions (local.get, local.set, local.tee, global.get, global.set) move values between the stack and named locals or module-level globals. Locals are function-scoped and always typed; there are no untyped registers.

Memory instructions (i32.load, i32.store, memory.size, memory.grow, and the bulk-memory variants memory.copy, memory.fill) access the flat linear memory. Every load and store takes a static alignment hint and a byte offset immediate plus a dynamic address from the stack. Out-of-bounds access traps; there is no undefined behaviour.

Numeric instructions form the largest group: integer arithmetic (add, sub, mul, div_s, div_u, rem, bitwise ops, shifts, rotates, clz, ctz, popcnt), float arithmetic (IEEE-754 add, sub, mul, div, sqrt, min, max, abs, neg, ceil, floor, trunc, nearest), comparisons, and conversions between types.

Practical usage tips

The reference is most useful when you are reading .wat text format or inspecting a disassembled binary. Some patterns come up repeatedly:

  • Trapping vs. saturating conversions. i32.trunc_f32_s traps when the float is NaN or out of the i32 range. The saturating variant i32.trunc_sat_f32_s (opcode 0xFC 0x00) clamps to the min or max i32 instead — safer when you cannot guarantee a clean float.
  • Integer division. Both i32.div_s (signed) and i32.div_u (unsigned) trap on divide-by-zero. The signed form also traps on the overflow case of dividing the minimum i32 by -1. Always guard or use a known-nonzero divisor.
  • local.tee pattern. local.tee $x is identical to local.set $x but leaves a copy on the stack, enabling (local.tee $x) (if ...) patterns without a second local.get.
  • memory.grow return value. It returns the previous size in 64 KiB pages, or -1 on failure. Code that ignores the return value and assumes success will silently write into unmapped memory if the host cannot satisfy the request.

Notes and gotchas

  • Signed division (i32.div_s) and the float-to-int truncations trap on divide by zero and on out-of-range conversions; use the saturating *_sat variants to clamp instead of trapping.
  • local.tee is local.set that leaves the value on the stack — handy for chaining.
  • Memory addresses are byte offsets into one flat array starting at zero; out-of-bounds access traps deterministically.
  • The text format (.wat) names blocks and locals for readability, but the binary format resolves those to numeric indices and branch depths.