Opcode catalog
Every operation the wasm.<opcode>(...) escape hatch accepts: 199 of them, in catalog wastc-core-gc-0. Anything absent is a feature error, not a fallback to raw WAT.
The opcode list is extracted from KNOWN_OPCODES in wastc-core -- the compiler decides what actually parses. Stack signatures, descriptions, and byte encodings come from vendor/wasm-ops/data/, last checked against the specification in July 2026. Immediate syntax is the WAST source form, which is not what the vendor data records (it describes binary immediates). Regenerate with mise run docs:opcodes.
Reading the tables
Stack is the WebAssembly stack effect: operands consumed on the left, results produced on the right. It is the instruction's contract, and it is why the escape hatch can type-check your call rather than pasting text.
In WAST the operands are ordinary expressions and the immediates go in <...>, so [i32 i32] → [i32] with no immediates reads as wasm.i32.add(a, b). t is a type the immediate supplies; field is a struct field or array element type.
wasm.i32.add(left, right) // [i32 i32] → [i32]
wasm.struct.get<Circle, radius>(circle) // [(ref null $t)] → [field]
wasm.i32.store<Heap, offset=0, align=4>(a, v) // [i32 i32] → []
A Const mark means the operation is valid in a global initializer. Only eight are: f32.const, f64.const, global.get, i32.const, i64.const, ref.func, ref.i31, ref.null (the last restricted to imported immutable globals).
Constants
No operands. Immediates do not accept a leading -; write the unsigned bit pattern instead (wasm.i32.const<4294967295> for -1).
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
i32.const ᶜ | <n> | [] → [i32] | Push a 32-bit integer value to the stack | 0x41 |
i64.const ᶜ | <n> | [] → [i64] | Push a 64-bit integer value to the stack | 0x42 |
f32.const ᶜ | <x.y> | [] → [f32] | Push a 32-bit float value to the stack | 0x43 |
f64.const ᶜ | <x.y> | [] → [f64] | Push a 64-bit float value to the stack | 0x44 |
i32 arithmetic, bitwise, and comparison
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
i32.add | none | [i32 i32] → [i32] | sign-agnostic addition | 0x6A |
i32.sub | none | [i32 i32] → [i32] | sign-agnostic subtraction | 0x6B |
i32.mul | none | [i32 i32] → [i32] | sign-agnostic multiplication, modulo 2³² | 0x6C |
i32.div_s | none | [i32 i32] → [i32] | signed division (result is truncated toward zero) | 0x6D |
i32.div_u | none | [i32 i32] → [i32] | unsigned division (result is floored) | 0x6E |
i32.rem_s | none | [i32 i32] → [i32] | signed remainder (result has the sign of the dividend) | 0x6F |
i32.rem_u | none | [i32 i32] → [i32] | unsigned remainder | 0x70 |
i32.and | none | [i32 i32] → [i32] | sign-agnostic bitwise and. Return the bitwise conjunction of 𝑖1 and 𝑖2 | 0x71 |
i32.or | none | [i32 i32] → [i32] | sign-agnostic bitwise inclusive or. Return the bitwise disjunction of 𝑖1 and 𝑖2 | 0x72 |
i32.xor | none | [i32 i32] → [i32] | sign-agnostic bitwise exclusive or. Return the bitwise exclusive disjunction of 𝑖1 and 𝑖2 | 0x73 |
i32.shl | none | [i32 i32] → [i32] | sign-agnostic shift left. Return the result of shifting i1 left by k bits, modulo 2³² | 0x74 |
i32.shr_s | none | [i32 i32] → [i32] | sign-replicating (arithmetic) shift right. Return the result of shifting i1 right by k bits, extended with the most significant bit of the original value | 0x75 |
i32.shr_u | none | [i32 i32] → [i32] | zero-replicating (logical) shift right. Return the result of shifting i1 right by k bits, extended with 0 bits | 0x76 |
i32.rotl | none | [i32 i32] → [i32] | sign-agnostic rotate left. Return the result of rotating i1 left by k bits | 0x77 |
i32.rotr | none | [i32 i32] → [i32] | sign-agnostic rotate right. Return the result of rotating i1 right by k bits | 0x78 |
i32.eq | none | [i32 i32] → [i32] | == sign-agnostic compare equal | 0x46 |
i32.ne | none | [i32 i32] → [i32] | ≠ sign-agnostic compare unequal | 0x47 |
i32.lt_s | none | [i32 i32] → [i32] | < signed less than | 0x48 |
i32.lt_u | none | [i32 i32] → [i32] | < unsigned less than | 0x49 |
i32.gt_s | none | [i32 i32] → [i32] | > signed greater than | 0x4A |
i32.gt_u | none | [i32 i32] → [i32] | > unsigned greater than | 0x4B |
i32.le_s | none | [i32 i32] → [i32] | ≤ signed less than or equal | 0x4C |
i32.le_u | none | [i32 i32] → [i32] | ≤ unsigned less than or equal | 0x4D |
i32.ge_s | none | [i32 i32] → [i32] | ≥ signed greater than or equal | 0x4E |
i32.ge_u | none | [i32 i32] → [i32] | ≥ unsigned greater than or equal | 0x4F |
i32.clz | none | [i32] → [i32] | sign-agnostic count leading zero bits. Return the count of leading zero bits in i. All zero bits are considered leading if the value is zero | 0x67 |
i32.ctz | none | [i32] → [i32] | sign-agnostic count trailing zero bits. Return the count of trailing zero bits in i. All zero bits are considered trailing if the value is zero | 0x68 |
i32.popcnt | none | [i32] → [i32] | sign-agnostic count number of one bits. Return the count of non-zero bits in i | 0x69 |
i32.eqz | none | [i32] → [i32] | compare equal to zero. Return 1 if operand is zero, 0 otherwise | 0x45 |
i64 arithmetic, bitwise, and comparison
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
i64.add | none | [i64 i64] → [i64] | sign-agnostic addition | 0x7C |
i64.sub | none | [i64 i64] → [i64] | sign-agnostic subtraction | 0x7D |
i64.mul | none | [i64 i64] → [i64] | sign-agnostic multiplication, modulo 2⁶⁴ | 0x7E |
i64.div_s | none | [i64 i64] → [i64] | signed division (result is truncated toward zero) | 0x7F |
i64.div_u | none | [i64 i64] → [i64] | unsigned division (result is floored) | 0x80 |
i64.rem_s | none | [i64 i64] → [i64] | signed remainder (result has the sign of the dividend) | 0x81 |
i64.rem_u | none | [i64 i64] → [i64] | unsigned remainder | 0x82 |
i64.and | none | [i64 i64] → [i64] | sign-agnostic bitwise and. Return the bitwise conjunction of 𝑖1 and 𝑖2 | 0x83 |
i64.or | none | [i64 i64] → [i64] | sign-agnostic bitwise inclusive or. Return the bitwise disjunction of 𝑖1 and 𝑖2 | 0x84 |
i64.xor | none | [i64 i64] → [i64] | sign-agnostic bitwise exclusive or. Return the bitwise exclusive disjunction of 𝑖1 and 𝑖2 | 0x85 |
i64.shl | none | [i64 i64] → [i64] | sign-agnostic shift left. Return the result of shifting i1 left by k bits, modulo 2⁶⁴ | 0x86 |
i64.shr_s | none | [i64 i64] → [i64] | sign-replicating (arithmetic) shift right. Return the result of shifting i1 right by k bits, extended with the most significant bit of the original value | 0x87 |
i64.shr_u | none | [i64 i64] → [i64] | zero-replicating (logical) shift right. Return the result of shifting i1 right by k bits, extended with 0 bits | 0x88 |
i64.rotl | none | [i64 i64] → [i64] | sign-agnostic rotate left. Return the result of rotating i1 left by k bits | 0x89 |
i64.rotr | none | [i64 i64] → [i64] | sign-agnostic rotate right. Return the result of rotating i1 right by k bits | 0x8A |
i64.eq | none | [i64 i64] → [i32] | == sign-agnostic compare equal | 0x51 |
i64.ne | none | [i64 i64] → [i32] | ≠ sign-agnostic compare unequal | 0x52 |
i64.lt_s | none | [i64 i64] → [i32] | < signed less than | 0x53 |
i64.lt_u | none | [i64 i64] → [i32] | < unsigned less than | 0x54 |
i64.gt_s | none | [i64 i64] → [i32] | > signed greater than | 0x55 |
i64.gt_u | none | [i64 i64] → [i32] | > unsigned greater than | 0x56 |
i64.le_s | none | [i64 i64] → [i32] | ≤ signed less than or equal | 0x57 |
i64.le_u | none | [i64 i64] → [i32] | ≤ unsigned less than or equal | 0x58 |
i64.ge_s | none | [i64 i64] → [i32] | ≥ signed greater than or equal | 0x59 |
i64.ge_u | none | [i64 i64] → [i32] | ≥ unsigned greater than or equal | 0x5A |
i64.clz | none | [i64] → [i64] | sign-agnostic count leading zero bits. Return the count of leading zero bits in i. All zero bits are considered leading if the value is zero | 0x79 |
i64.ctz | none | [i64] → [i64] | sign-agnostic count trailing zero bits. Return the count of trailing zero bits in i. All zero bits are considered trailing if the value is zero | 0x7A |
i64.popcnt | none | [i64] → [i64] | sign-agnostic count number of one bits. Return the count of non-zero bits in i | 0x7B |
i64.eqz | none | [i64] → [i32] | compare equal to zero. Return 1 if operand is zero, 0 otherwise | 0x50 |
f32 arithmetic and comparison
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
f32.add | none | [f32 f32] → [f32] | addition | 0x92 |
f32.sub | none | [f32 f32] → [f32] | subtraction | 0x93 |
f32.mul | none | [f32 f32] → [f32] | multiplication | 0x94 |
f32.div | none | [f32 f32] → [f32] | division. partial function: division by 0 is undefined | 0x95 |
f32.min | none | [f32 f32] → [f32] | minimum (binary operator); if either operand is NaN, returns NaN | 0x96 |
f32.max | none | [f32 f32] → [f32] | maximum (binary operator); if either operand is NaN, returns NaN | 0x97 |
f32.copysign | none | [f32 f32] → [f32] | If z1 and z2 have the same sign, then return z1. Else return z1 with negated sign | 0x98 |
f32.eq | none | [f32 f32] → [i32] | == compare equal | 0x5B |
f32.ne | none | [f32 f32] → [i32] | ≠ compare unordered or unequal | 0x5C |
f32.lt | none | [f32 f32] → [i32] | < less than | 0x5D |
f32.gt | none | [f32 f32] → [i32] | > greater than | 0x5E |
f32.le | none | [f32 f32] → [i32] | ≤ less than or equal | 0x5F |
f32.ge | none | [f32 f32] → [i32] | ≥ greater than or equal | 0x60 |
f32.neg | none | [f32] → [f32] | negation | 0x8C |
f32.abs | none | [f32] → [f32] | absolute value | 0x8B |
f32.sqrt | none | [f32] → [f32] | square root | 0x91 |
f32.ceil | none | [f32] → [f32] | ceiling operator | 0x8D |
f32.floor | none | [f32] → [f32] | floor operator | 0x8E |
f32.trunc | none | [f32] → [f32] | round to nearest integer towards zero | 0x8F |
f32.nearest | none | [f32] → [f32] | round to nearest integer, ties to even | 0x90 |
f64 arithmetic and comparison
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
f64.add | none | [f64 f64] → [f64] | addition | 0xA0 |
f64.sub | none | [f64 f64] → [f64] | subtraction | 0xA1 |
f64.mul | none | [f64 f64] → [f64] | multiplication | 0xA2 |
f64.div | none | [f64 f64] → [f64] | division. partial function: division by 0 is undefined | 0xA3 |
f64.min | none | [f64 f64] → [f64] | minimum (binary operator); if either operand is NaN, returns NaN | 0xA4 |
f64.max | none | [f64 f64] → [f64] | maximum (binary operator); if either operand is NaN, returns NaN | 0xA5 |
f64.copysign | none | [f64 f64] → [f64] | If z1 and z2 have the same sign, then return z1. Else return z1 with negated sign | 0xA6 |
f64.eq | none | [f64 f64] → [i32] | == compare equal | 0x61 |
f64.ne | none | [f64 f64] → [i32] | ≠ compare unordered or unequal | 0x62 |
f64.lt | none | [f64 f64] → [i32] | < less than | 0x63 |
f64.gt | none | [f64 f64] → [i32] | > greater than | 0x64 |
f64.le | none | [f64 f64] → [i32] | ≤ less than or equal | 0x65 |
f64.ge | none | [f64 f64] → [i32] | ≥ greater than or equal | 0x66 |
f64.neg | none | [f64] → [f64] | negation | 0x9A |
f64.abs | none | [f64] → [f64] | absolute value | 0x99 |
f64.sqrt | none | [f64] → [f64] | square root | 0x9F |
f64.ceil | none | [f64] → [f64] | ceiling operator | 0x9B |
f64.floor | none | [f64] → [f64] | floor operator | 0x9C |
f64.trunc | none | [f64] → [f64] | round to nearest integer towards zero | 0x9D |
f64.nearest | none | [f64] → [f64] | round to nearest integer, ties to even | 0x9E |
Conversions
No saturating (_sat_) truncation: a float-to-int conversion traps when the value is out of range or NaN.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
i32.wrap_i64 | none | [i64] → [i32] | wrap a 64-bit integer to a 32-bit integer. Return i modulo 2³² | 0xA7 |
i64.extend_i32_s | none | [i32] → [i64] | extend a signed 32-bit integer to a 64-bit integer | 0xAC |
i64.extend_i32_u | none | [i32] → [i64] | extend an unsigned 32-bit integer to a 64-bit integer | 0xAD |
i32.extend8_s | none | [i32] → [i32] | extend a signed 8-bit integer to a 32-bit integer. Sign-extension operators extension | 0xC0 |
i32.extend16_s | none | [i32] → [i32] | extend a signed 16-bit integer to a 32-bit integer. Sign-extension operators extension | 0xC1 |
i64.extend8_s | none | [i64] → [i64] | extend a signed 8-bit integer to a 64-bit integer. Sign-extension operators extension | 0xC2 |
i64.extend16_s | none | [i64] → [i64] | extend a signed 16-bit integer to a 64-bit integer. Sign-extension operators extension | 0xC3 |
i64.extend32_s | none | [i64] → [i64] | extend a signed 32-bit integer to a 64-bit integer. Sign-extension operators extension | 0xC4 |
f32.convert_i32_s | none | [i32] → [f32] | convert a signed 32-bit integer to a 32-bit float | 0xB2 |
f32.convert_i32_u | none | [i32] → [f32] | convert an unsigned 32-bit integer to a 32-bit float | 0xB3 |
f32.convert_i64_s | none | [i64] → [f32] | convert a signed 64-bit integer to a 32-bit float | 0xB4 |
f32.convert_i64_u | none | [i64] → [f32] | convert an unsigned 64-bit integer to a 32-bit float | 0xB5 |
f64.convert_i32_s | none | [i32] → [f64] | convert a signed 32-bit integer to a 64-bit float | 0xB7 |
f64.convert_i32_u | none | [i32] → [f64] | convert an unsigned 32-bit integer to a 64-bit float | 0xB8 |
f64.convert_i64_s | none | [i64] → [f64] | convert a signed 64-bit integer to a 64-bit float | 0xB9 |
f64.convert_i64_u | none | [i64] → [f64] | convert an unsigned 64-bit integer to a 64-bit float | 0xBA |
i32.trunc_f32_s | none | [f32] → [i32] | truncate a 32-bit float to a signed 32-bit integer | 0xA8 |
i32.trunc_f32_u | none | [f32] → [i32] | truncate a 32-bit float to an unsigned 32-bit integer | 0xA9 |
i32.trunc_f64_s | none | [f64] → [i32] | truncate a 64-bit float to a signed 32-bit integer | 0xAA |
i32.trunc_f64_u | none | [f64] → [i32] | truncate a 64-bit float to an unsigned 32-bit integer | 0xAB |
i64.trunc_f32_s | none | [f32] → [i64] | truncate a 32-bit float to a signed 64-bit integer | 0xAE |
i64.trunc_f32_u | none | [f32] → [i64] | truncate a 32-bit float to an unsigned 64-bit integer | 0xAF |
i64.trunc_f64_s | none | [f64] → [i64] | truncate a 64-bit float to a signed 64-bit integer | 0xB0 |
i64.trunc_f64_u | none | [f64] → [i64] | truncate a 64-bit float to an unsigned 64-bit integer | 0xB1 |
f32.demote_f64 | none | [f64] → [f32] | demote a 64-bit float to a 32-bit float | 0xB6 |
f64.promote_f32 | none | [f32] → [f64] | promote a 32-bit float to a 64-bit float | 0xBB |
i32.reinterpret_f32 | none | [f32] → [i32] | reinterpret the bits of a 32-bit float as a 32-bit integer | 0xBC |
f32.reinterpret_i32 | none | [i32] → [f32] | reinterpret the bits of a 32-bit integer as a 32-bit float | 0xBE |
i64.reinterpret_f64 | none | [f64] → [i64] | reinterpret the bits of a 64-bit float as a 64-bit integer | 0xBD |
f64.reinterpret_i64 | none | [i64] → [f64] | reinterpret the bits of a 64-bit integer as a 64-bit float | 0xBF |
Linear memory
Every access names its memory -- there is no default memory. align is a byte count, must be a power of two, and must not exceed the natural alignment of the access. Addresses are always i32.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
i32.load | <Memory[, offset=N][, align=N]> | [i32] → [i32] | load 4 bytes as i32 | 0x28 |
i64.load | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 8 bytes as i64 | 0x29 |
f32.load | <Memory[, offset=N][, align=N]> | [i32] → [f32] | load 4 bytes as f32 | 0x2A |
f64.load | <Memory[, offset=N][, align=N]> | [i32] → [f64] | load 8 bytes as f64 | 0x2B |
i32.load8_s | <Memory[, offset=N][, align=N]> | [i32] → [i32] | load 1 byte and sign-extend i8 to i32 | 0x2C |
i32.load8_u | <Memory[, offset=N][, align=N]> | [i32] → [i32] | load 1 byte and zero-extend i8 to i32 | 0x2D |
i32.load16_s | <Memory[, offset=N][, align=N]> | [i32] → [i32] | load 2 bytes and sign-extend i16 to i32 | 0x2E |
i32.load16_u | <Memory[, offset=N][, align=N]> | [i32] → [i32] | load 2 bytes and zero-extend i16 to i32 | 0x2F |
i64.load8_s | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 1 byte and sign-extend i8 to i64 | 0x30 |
i64.load8_u | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 1 byte and zero-extend i8 to i64 | 0x31 |
i64.load16_s | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 2 bytes and sign-extend i16 to i64 | 0x32 |
i64.load16_u | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 2 bytes and zero-extend i16 to i64 | 0x33 |
i64.load32_s | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 4 bytes and sign-extend i32 to i64 | 0x34 |
i64.load32_u | <Memory[, offset=N][, align=N]> | [i32] → [i64] | load 4 bytes and zero-extend i32 to i64 | 0x35 |
i32.store | <Memory[, offset=N][, align=N]> | [i32 i32] → [] | store 4 bytes (no conversion) | 0x36 |
i64.store | <Memory[, offset=N][, align=N]> | [i32 i64] → [] | store 8 bytes (no conversion) | 0x37 |
f32.store | <Memory[, offset=N][, align=N]> | [i32 f32] → [] | store 4 bytes (no conversion) | 0x38 |
f64.store | <Memory[, offset=N][, align=N]> | [i32 f64] → [] | store 8 bytes (no conversion) | 0x39 |
i32.store8 | <Memory[, offset=N][, align=N]> | [i32 i32] → [] | wrap i32 to i8 and store 1 byte | 0x3A |
i32.store16 | <Memory[, offset=N][, align=N]> | [i32 i32] → [] | wrap i32 to i16 and store 2 bytes | 0x3B |
i64.store8 | <Memory[, offset=N][, align=N]> | [i32 i64] → [] | wrap i64 to i8 and store 1 byte | 0x3C |
i64.store16 | <Memory[, offset=N][, align=N]> | [i32 i64] → [] | wrap i64 to i16 and store 2 bytes | 0x3D |
i64.store32 | <Memory[, offset=N][, align=N]> | [i32 i64] → [] | wrap i64 to i32 and store 4 bytes | 0x3E |
memory.size | <Memory> | [] → [i32] | The memory.size instruction returns the current size of a memory. Operates in units of page size. Each page is 65,536 bytes (64KB) | 0x3F |
memory.grow | <Memory> | [i32] → [i32] | The memory.grow instruction grows memory by a given delta and returns the previous size, or −1 if enough memory cannot be allocated. Operates in units of page size. Each page is 65,536 bytes (64KB) | 0x40 |
Locals and globals
local.set/local.tee require a var binding; global.set requires a mut global.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
local.get | <local> | [] → [t] | This instruction gets the value of a variable | 0x20 |
local.set | <local> | [t] → [] | This instruction sets the value of a variable | 0x21 |
local.tee | <local> | [t] → [t] | The local.tee instruction is like local.set but also returns its argument | 0x22 |
global.get ᶜ | <Global> | [] → [t] | This instruction gets the value of a variable | 0x23 |
global.set | <Global> | [t] → [] | This instruction sets the value of a variable | 0x24 |
References and casts
ref.test/ref.cast only ever downcast: the immediate's heap type must be a subtype of the operand's. A bare heap keyword is not a type immediate -- write <&struct>, never <struct>.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
ref.null ᶜ | <?T> | [] → [(ref null t)] | evaluates to the null reference constant | 0xD0 |
ref.is_null | none | [(ref null t)] → [i32] | checks for null | 0xD1 |
ref.eq | none | [eqref eqref] → [i32] | [eqref eqref] -> [i32] | 0xD3 |
ref.func ᶜ | <func> | [] → [(ref f)] | creates a reference to a given function | 0xD2 |
ref.test | <&T> / <?T> | [(ref null t)] → [i32] | checks whether a reference has a given heap type | 0xFB.20 |
ref.cast | <&T> / <?T> | [(ref null t₁)] → [(ref t₂)] | tries to convert to a given heap type | 0xFB.22 |
i31
Note the asymmetry: ref.i31 produces a non-null &i31, but both accessors take a nullable ?i31.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
ref.i31 ᶜ | none | [i32] → [(ref i31)] | creates an i31ref from a 32 bit value, truncating high bit | 0xFB.28 |
i31.get_s | none | [i31ref] → [i32] | extracts the value, sign-extending | 0xFB.29 |
i31.get_u | none | [i31ref] → [i32] | extracts the value, zero-extending | 0xFB.30 |
GC structs
The immediate names the field; the emitted WAT uses its flattened parent-first index. A packed (i8/i16) field requires _s/_u, and a non-packed field rejects them.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
struct.new | <Struct> | [field*] → [(ref $t)] | allocates a structure with canonical RTT (runtime type) and initialises its fields with given values | 0xFB.0 |
struct.get | <Struct, field> | [(ref null $t)] → [field] | reads field i from a structure | 0xFB.2 |
struct.get_s | <Struct, field> | [(ref null $t)] → [i32] | -- | 0xFB.3 |
struct.get_u | <Struct, field> | [(ref null $t)] → [i32] | -- | 0xFB.4 |
struct.set | <Struct, field> | [(ref null $t) field] → [] | writes field i of a structure | 0xFB.5 |
struct.new_default | <Struct> | [] → [(ref $t)] | allocates a structure of type $t with canonical RTT (runtime type) and initialises its fields with default values | 0xFB.1 |
GC arrays
array.copy takes the destination type first and requires both arrays to agree on packedness.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
array.new_fixed | <Array> | [field^n] → [(ref $t)] | allocates an array with canonical RTT (runtime type) of fixed size and initialises it from operands | 0xFB.8 |
array.get | <Array> | [(ref null $t) i32] → [field] | reads an element from an array | 0xFB.11 |
array.get_s | <Array> | [(ref null $t) i32] → [i32] | -- | 0xFB.12 |
array.get_u | <Array> | [(ref null $t) i32] → [i32] | -- | 0xFB.13 |
array.set | <Array> | [(ref null $t) i32 field] → [] | writes an element to an array | 0xFB.14 |
array.len | none | [arrayref] → [i32] | inquires the length of an array | 0xFB.15 |
array.new | <Array> | [field i32] → [(ref $t)] | allocates an array with canonical RTT (runtime type) | 0xFB.6 |
array.new_default | <Array> | [i32] → [(ref $t)] | allocates an array with canonical RTT (runtime type) and initialises its fields with the default value | 0xFB.7 |
array.fill | <Array> | [(ref null $t) i32 field i32] → [] | -- | 0xFB.16 |
array.copy | <Dest, Src> | [(ref null $t₁) i32 (ref null $t₂) i32 i32] → [] | -- | 0xFB.17 |
Tables
table.grow takes the fill value first, then the delta.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
table.get | <Table> | [i32] → [t] | Access tables | 0x25 |
table.set | <Table> | [i32 t] → [] | Access tables | 0x26 |
table.size | <Table> | [] → [i32] | current number of elements in the table | -- |
table.grow | <Table> | [t i32] → [i32] | grow the table by n elements, returning the previous size or -1 | -- |
Host reference bridge
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
any.convert_extern | none | [externref] → [anyref] | converts an external value into the internal representation | 0xFB.26 |
extern.convert_any | none | [anyref] → [externref] | converts an internal value into the external representation | 0xFB.27 |
Calls
call_indirect takes the table index as its last argument, after the call arguments.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
call | <func> | [t∗_1] → [t∗_2] | The call instruction invokes another function, consuming the necessary arguments from the stack and returning the result values of the call | 0x10 |
call_indirect | <Table, FuncType> | [t? i32] → [t?] | The call_indirect instruction calls a function indirectly through an operand indexing into a table | 0x11 |
Stack and control
unreachable is polymorphic and marks the path diverging. select emits an explicit result annotation only when the result is a reference.
| Opcode | Immediates | Stack | Description | Byte |
|---|---|---|---|---|
drop | none | [t] → [] | The drop instruction simply throws away a single operand. Arguably the only stack manipulation instruction WebAssembly has. There is no dup, swap or rot, and no way to reach past the top of the stack: select consumes three operands and computes with them, and local.tee does the work dup would by going out to a local and back. See WebAssembly is not quite a stack machine, by purplesyringa | 0x1A |
select | none (rejects any) | [t t i32] → [t] | The select instruction selects one of its first two operands based on whether its third operand is zero or not | 0x1B |
nop | none | [] → [] | The nop instruction does nothing | 0x01 |
unreachable | none | [t∗_1] → [t∗_2] | The unreachable instruction causes an unconditional trap. A trap immediately aborts execution. Traps cannot be handled by WebAssembly code, but are reported to the outside environment, where they typically can be caught | 0x00 |
Not in the catalog
These are absent on purpose. Reaching for one is a feature error:
feature error at L:C: 'memory.copy' is not available in the selected
target's instruction catalog (wastc-core-gc-0)
| Excluded | Why |
|---|---|
br, br_if, br_table, block, loop, if, return, br_on_null, br_on_cast | The escape hatch's grammar is an expression producing a value; a branch is a control-flow edge and has no such shape. The language's own structured control flow covers these, and always will. |
return_call, return_call_ref, raw call_ref | Tail calls are a compiler output mode (--tail-calls) applied to your own return, never source syntax. |
memory.init, data.drop, memory.copy, memory.fill | Bulk memory is outside the target feature set. A passive data segment currently has no way to be used from source as a result. |
table.copy, table.fill, table.init, elem.drop | Same. |
array.new_data, array.new_elem | Depend on the segment operations above. |
i32.trunc_sat_*, i64.trunc_sat_* | The fc proposal is out of scope, so out-of-range float-to-int conversion traps rather than saturating. |
| SIMD, threads/atomics, exception handling, stringref, memory64 | Outside the target feature set entirely -- see limitations. |
Two behaviours that will surprise you
Numeric opcodes ignore their immediates. wasm.i32.add<Nonsense>(a, b) type-checks and silently discards the immediate. It is unchecked, not rejected. select is the only opcode that errors on a stray immediate.
A typo reads as a missing feature. The unknown-opcode arm is the same one that reports out-of-scope instructions, so wasm.i32.addd(...) claims the catalog doesn't have it rather than telling you it isn't a real opcode.
See also
- Raw WebAssembly operations -- how the escape hatch works and when to reach for it.
- Syntax cheat sheet -- the operators that mean you often don't have to.
- Specification section 12.