Skip to content

Binary encoding

Each WAVE instruction contains one or two 32-bit words.

Every WAVE instruction starts with a 32-bit word with the following bit layout:

BitsFieldWidthPurpose
31:24opcode8 bitsOperation class
23:16RD8 bitsDestination register; 0 through 255
15:8RS18 bitsSource register 1; 0 through 255
7:4modifier4 bitsOperation variant within opcode class; 0-15
3reserved1 bitReserved, must be zero
2pred_neg1 bitNegate predicate condition
1:0pred_reg2 bitsPredicate selector; zero without negation means unpredicated execution
31 24 23 16 15 8 7 4 3 2 1 0
┌────────┬────────┬────────┬──────┬───┬───┬────┐
│ opcode │ RD │ RS1 │ mod │rsv│neg│pred│
│ 8b │ 8b │ 8b │ 4b │1b │1b │ 2b │
└────────┴────────┴────────┴──────┴───┴───┴────┘

When pred_reg=0 and pred_neg=0, the instruction is unpredicated. A nonzero selector tests the named predicate register. pred_neg=1 negates that test. For example, @p1 sets pred_reg=1 and pred_neg=0; @!p2 sets pred_reg=2 and pred_neg=1.

Predicate register p0 is writable and can be used by comparisons and control-flow instructions. See Encoding and execution invariants.

Eight bits address 256 registers. Word0 holds rd and rs1; word1 holds additional source registers.

The 4-bit modifier selects one of up to 16 variants within an opcode. See Modifier field for the assignments.

Opcode 0x3F uses modifiers 0 through 7 for if, else, endif, loop, break, continue, endloop, and call. Modifiers 8 through 15 select return, halt, barrier, the fence variants, wait, and nop. Synchronization modifiers use SYNC_MODIFIER_OFFSET = 8.

When an instruction needs additional source registers, a memory scope, or an inline immediate, it uses a second 32-bit word. The opcode in word0 determines whether word1 is present.

BitsFieldWidthPurpose
31:24RS28 bitsSource register 2; 0 through 255
23:16RS38 bitsSource register 3; 0 through 255
15:8RS48 bitsSource register 4; 0 through 255
7:2reserved6 bitsReserved, must be zero
1:0scope2 bitsMemory scope; 00=wave, 01=workgroup, 10=device, 11=system
31 24 23 16 15 8 7 2 1 0
┌────────┬────────┬────────┬──────────┬────┐
│ RS2 │ RS3 │ RS4 │ reserved │scop│
│ 8b │ 8b │ 8b │ 6b │ 2b │
└────────┴────────┴────────┴──────────┴────┘

Alternatively, the entire word1 can serve as a 32-bit immediate value, such as for mov_imm. The opcode determines interpretation.

Word1 bits [1:0] select the scope of device atomics and fences:

ValueScopeMeaning
00WaveVisible within the executing wave
01WorkgroupVisible to all waves in the workgroup
10DeviceVisible to all waves on the device
11SystemVisible to the device and host CPU
  • Two-source operations such as iadd r0, r1, r2 use RS1 in word0 and RS2 in word1.
  • Fused multiply-add, fma r0, r1, r2, r3: needs RS1, RS2, and RS3.
  • Immediate loads, mov_imm r0, 0x3F800000: the 32-bit immediate occupies the entire word1.
  • Scoped atomics, atomic_add r0, r1, r2, workgroup: scope in word1 bits [1:0].
  • Atomic compare-and-swap: needs address, expected, desired, and result registers.

The .wbin container stores compiled WAVE programs. The parser validates section bounds and kernel ranges before exposing code.

OffsetSizeFieldDescription
04magicASCII "WAVE"; 0x57415645
42versionContainer format version
62flagsContainer flags
84code_offsetByte offset to code section
124code_sizeSize of code section in bytes
164symbol_offsetByte offset to symbol table
204symbol_sizeSize of symbol table in bytes
244metadata_offsetByte offset to metadata section
284metadata_sizeSize of metadata section in bytes

Each kernel entry in the metadata section describes the resource requirements that a backend needs for dispatch:

FieldPurpose
nameKernel identifier; null-terminated UTF-8
register_countNumber of registers used per thread
local_mem_sizeBytes of workgroup-local memory required
workgroup_size_xWorkgroup dimension X
workgroup_size_yWorkgroup dimension Y
workgroup_size_zWorkgroup dimension Z
code_offsetByte offset of this kernel's code within the code section
code_sizeSize of this kernel's code in bytes

The metadata section starts with a 32-bit kernel count, followed by 32-byte entries. Each entry stores the kernel name's offset into the symbol table and the fields listed above.