Files
Odin/core/rexcode/wasm/instructions.odin
2026-06-16 14:11:15 +01:00

152 lines
6.1 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// INSTRUCTION
// =============================================================================
//
// WASM instructions are variable length: a single opcode byte (or a prefix
// byte 0xFC/0xFD/0xFE plus an unsigned-LEB sub-opcode) followed by zero or
// more immediate fields. Two immediate slots cover every modelled form
// (e.g. call_indirect's typeidx + tableidx, table.copy's two tableidx).
//
// `br_table` is the one operator whose immediate is a *vector* of label
// depths; its default label lives in ops[0] and the case targets in the
// `targets` slice (caller-owned, like the rest of the input). `length` is
// filled by the encoder (and by the decoder) since it is not fixed.
Instruction_Flags :: bit_field u8 {
_: u8 | 8,
}
Instruction :: struct {
ops: [2]Operand `fmt:"v,operand_count"`,
targets: []u32, // br_table case labels (default in ops[0])
bytes: [16]u8, // v128.const value / i8x16.shuffle lane mask (LANES16)
mnemonic: Mnemonic,
operand_count: u8,
flags: Instruction_Flags,
length: u8, // filled by encoder/decoder (1..N)
_: [3]u8,
}
#assert(size_of(Instruction) == 48 + 2*size_of(int))
// =============================================================================
// Builders (shape spelled out, comma-separated -- contract surface)
// =============================================================================
@(require_results)
inst_none :: #force_inline proc "contextless" (m: Mnemonic) -> Instruction {
return Instruction{mnemonic = m, operand_count = 0}
}
// Single immediate constant (i32/i64/f32/f64.const, ref.null).
@(require_results)
inst_i :: #force_inline proc "contextless" (m: Mnemonic, o: Operand) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {o, {}}}
}
// Single index immediate (local/global/func/.../label).
@(require_results)
inst_idx :: #force_inline proc "contextless" (m: Mnemonic, o: Operand) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {o, {}}}
}
// Memory access: a single memarg.
@(require_results)
inst_memarg :: #force_inline proc "contextless" (m: Mnemonic, ma: Memarg) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {op_mem(ma), {}}}
}
// Block / loop / if with a signature.
@(require_results)
inst_block :: #force_inline proc "contextless" (m: Mnemonic, bt: Block_Type = .EMPTY) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {op_blocktype(bt), {}}}
}
// Branch with a relative label depth (br / br_if).
@(require_results)
inst_br :: #force_inline proc "contextless" (m: Mnemonic, depth: u32) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {op_labelidx(depth), {}}}
}
// br_table: a vector of case depths plus a default depth.
@(require_results)
inst_br_table :: #force_inline proc "contextless" (targets: []u32, default_depth: u32) -> Instruction {
return Instruction{
mnemonic = .BR_TABLE, operand_count = 1,
ops = {op_labelidx(default_depth), {}}, targets = targets,
}
}
// call_indirect typeidx, tableidx.
@(require_results)
inst_call_indirect :: #force_inline proc "contextless" (type_index: u32, table_index: u32 = 0) -> Instruction {
return Instruction{
mnemonic = .CALL_INDIRECT, operand_count = 2,
ops = {op_type(type_index), op_table(table_index)},
}
}
// Two-index operators (table.init elemidx tableidx; table.copy dst src).
@(require_results)
inst_idx_idx :: #force_inline proc "contextless" (m: Mnemonic, a, b: Operand) -> Instruction {
return Instruction{mnemonic = m, operand_count = 2, ops = {a, b}}
}
// -----------------------------------------------------------------------------
// SIMD (0xFD) builders
// -----------------------------------------------------------------------------
// v128.const: a 16-byte literal carried in `bytes` (no stack operand).
@(require_results)
inst_v128_const :: #force_inline proc "contextless" (value: [16]u8) -> Instruction {
return Instruction{mnemonic = .V128_CONST, operand_count = 0, bytes = value}
}
// i8x16.shuffle: a 16-lane index mask carried in `bytes`.
@(require_results)
inst_shuffle :: #force_inline proc "contextless" (lanes: [16]u8) -> Instruction {
return Instruction{mnemonic = .I8X16_SHUFFLE, operand_count = 0, bytes = lanes}
}
// extract_lane / replace_lane: a single lane index immediate.
@(require_results)
inst_lane :: #force_inline proc "contextless" (m: Mnemonic, lane: u8) -> Instruction {
return Instruction{mnemonic = m, operand_count = 1, ops = {op_lane(lane), {}}}
}
// v128 load/store *_lane: a memarg plus a lane index.
@(require_results)
inst_mem_lane :: #force_inline proc "contextless" (m: Mnemonic, ma: Memarg, lane: u8) -> Instruction {
return Instruction{mnemonic = m, operand_count = 2, ops = {op_mem(ma), op_lane(lane)}}
}
// =============================================================================
// Emitters (append to a [dynamic]Instruction)
// =============================================================================
emit_none :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic) {
append(buf, inst_none(m))
}
emit_i :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, o: Operand) {
append(buf, inst_i(m, o))
}
emit_idx :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, o: Operand) {
append(buf, inst_idx(m, o))
}
emit_memarg :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, ma: Memarg) {
append(buf, inst_memarg(m, ma))
}
emit_block :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, bt: Block_Type = .EMPTY) {
append(buf, inst_block(m, bt))
}
emit_br :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, depth: u32) {
append(buf, inst_br(m, depth))
}
emit_call_indirect :: #force_inline proc(buf: ^[dynamic]Instruction, type_index: u32, table_index: u32 = 0) {
append(buf, inst_call_indirect(type_index, table_index))
}