Remove old wasm and wasm/module code

This commit is contained in:
gingerBill
2026-07-13 14:17:06 +01:00
parent 2478af9b1f
commit fcc6b65ed4
19 changed files with 0 additions and 5267 deletions

View File

@@ -1,259 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
import "base:runtime"
// =============================================================================
// WebAssembly DECODER
// =============================================================================
//
// Single forward pass, mirroring the encoder. Each step:
//
// 1. Read the opcode. A leading 0xFC switches to the misc group, whose
// sub-opcode is an unsigned LEB128 read next; otherwise the single byte
// is the opcode. The byte (or sub-opcode) indexes the DECODE_MAIN /
// DECODE_MISC tables built from ENCODING_TABLE at package init.
// 2. Look the resulting Mnemonic's form back up in ENCODING_TABLE and read
// its immediates in declaration order, reconstructing Operands.
//
// WASM control flow is structured (branches carry relative label depths, not
// byte offsets), so there is no PC-relative label inference. Object-file index
// relocations *are* re-attached: when an input relocation lands on a decoded
// index field, that operand is marked `symbolic` and carries the label id.
//
// `br_table`'s case-label vector is materialised into a freshly allocated
// `[]u32` (caller owns it, like the rest of the decoded output).
Instruction_Info :: struct {
offset: u32,
decode_entry: u16,
_: u16,
}
#assert(size_of(Instruction_Info) == 8)
decode :: proc(
data: []u8,
relocs: []Relocation,
instructions: ^[dynamic]Instruction,
inst_info: ^[dynamic]Instruction_Info,
errors: ^[dynamic]Error,
targets_allocator := context.allocator,
) -> (byte_count: u32, ok: bool) {
errors_start := u32(len(errors))
n := u32(len(data))
for byte_count < n {
inst, info, next, dok := decode_one(data, relocs, byte_count, targets_allocator)
if !dok {
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
inst = Instruction{mnemonic = .INVALID, length = 1}
info = Instruction_Info{offset = byte_count}
append(instructions, inst)
append(inst_info, info)
byte_count += 1
continue
}
inst.length = u8(min(next - byte_count, 255))
append(instructions, inst)
append(inst_info, info)
byte_count = next
}
ok = u32(len(errors)) == errors_start
return
}
@(require_results)
decode_one :: proc(
data: []u8,
relocs: []Relocation,
pc: u32,
targets_allocator: runtime.Allocator,
) -> (inst: Instruction, info: Instruction_Info, next: u32, ok: bool) {
off := pc
if off >= u32(len(data)) {
next = pc
return
}
b0 := data[off]
off += 1
m: Mnemonic = .INVALID
switch b0 {
case PREFIX_MISC:
sub := read_uleb(data, &off) or_return
if sub < u64(DECODE_MISC_COUNT) {
m = DECODE_MISC[sub]
}
case PREFIX_SIMD:
sub := read_uleb(data, &off) or_return
if sub < u64(DECODE_SIMD_COUNT) {
m = DECODE_SIMD[sub]
}
case PREFIX_ATOM:
sub := read_uleb(data, &off) or_return
if sub < u64(DECODE_ATOMIC_COUNT) {
m = DECODE_ATOMIC[sub]
}
case:
m = DECODE_MAIN[b0]
}
if m == .INVALID {
next = pc
return
}
form := encoding_form(m)
inst.mnemonic = m
inst.flags = {}
slot := 0
for k, ki in form.imm {
switch k {
case .NONE:
// nothing
case .BLOCKTYPE:
v := read_sleb(data, &off) or_return
inst.ops[slot] = Operand{kind = .BLOCK_TYPE, immediate = v}
slot += 1
case .I32:
v := read_sleb(data, &off) or_return
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = v, size = 4}
slot += 1
case .I64:
v := read_sleb(data, &off) or_return
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = v, size = 8}
slot += 1
case .F32:
bits := read_u32_block(data, &off) or_return
inst.ops[slot] = Operand{
kind = .IMMEDIATE,
immediate = i64(bits), size = 4, flags = {is_float = true},
}
slot += 1
case .F64:
bits := read_u64_block(data, &off) or_return
inst.ops[slot] = Operand{
kind = .IMMEDIATE,
immediate = i64(bits), size = 8, flags = {is_float = true},
}
slot += 1
case .IDX:
field := off
raw := read_uleb(data, &off) or_return
op := Operand{kind = .INDEX, index = u32(raw), idx_kind = idx_kind_for(m, ki)}
if lid, found := reloc_label_at(relocs, field); found {
op.index = lid
op.flags.symbolic = true
op.size = 5
} else if m == .CALL {
op.flags.symbolic = true
}
inst.ops[slot] = op
slot += 1
case .MEMARG:
// TODO(bill): Is this fully correct?
// See: https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
align := read_uleb(data, &off) or_return
offset := read_uleb(data, &off) or_return
// NOTE(bill) this appears to be stored as log2 even though the docs say otherwise
align = 1<<align
inst.ops[slot] = Operand{kind = .MEMARG, memarg = Memarg{align = u32(align), offset = u32(offset)}}
slot += 1
case .REFTYPE:
if off >= u32(len(data)) {
next = pc
return
}
t := data[off]
off += 1
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = i64(t), size = 1}
slot += 1
case .BR_TABLE:
count := read_uleb(data, &off) or_return
targets := make([]u32, int(count), targets_allocator)
for &target in targets {
t := read_uleb(data, &off) or_return
target = u32(t)
}
def := read_uleb(data, &off) or_return
inst.targets = targets
inst.ops[slot] = Operand{kind = .INDEX, index = u32(def), idx_kind = .LABEL}
slot += 1
case .ZERO_BYTE:
if off >= u32(len(data)) {
next = pc
return
}
off += 1 // reserved 0x00, consumes no operand
case .LANE:
if off >= u32(len(data)) {
next = pc
return
}
l := data[off]
off += 1
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = i64(l), size = 1}
slot += 1
case .LANES16:
if off + 16 > u32(len(data)) {
next = pc
return
}
copy(inst.bytes[:], data[off:][:16])
off += 16 // value lives in inst.bytes, no operand
}
}
inst.operand_count = u8(slot)
info.offset = pc
info.decode_entry = u16(m)
next = off
ok = true
return
}
// Which index space the IDX immediate in operand slot `which` addresses, by
// mnemonic. Mirrors how the builders in instructions.odin tag each operand.
@(private="file", require_results)
idx_kind_for :: #force_inline proc "contextless" (m: Mnemonic, which: int) -> Index_Kind {
#partial switch m {
case .BR, .BR_IF: return .LABEL
case .CALL, .REF_FUNC: return .FUNC
case .CALL_INDIRECT: return .TYPE if which == 0 else .TABLE
case .LOCAL_GET, .LOCAL_SET, .LOCAL_TEE: return .LOCAL
case .GLOBAL_GET, .GLOBAL_SET: return .GLOBAL
case .MEMORY_INIT, .DATA_DROP: return .DATA
case .TABLE_INIT: return .ELEM if which == 0 else .TABLE
case .ELEM_DROP: return .ELEM
case .TABLE_COPY: return .TABLE
case .TABLE_GROW, .TABLE_SIZE, .TABLE_FILL: return .TABLE
}
return .NONE
}
@(private="file", require_results)
reloc_label_at :: #force_inline proc "contextless" (relocs: []Relocation, offset: u32) -> (label_id: u32, found: bool) {
for r in relocs {
if r.offset == offset {
return r.label_id, true
}
}
return
}

View File

@@ -1,560 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly DECODE DISPATCH TABLES
// =============================================================================
//
// Reverse maps from wire opcode to Mnemonic. Dispatch is two-level:
//
// * core opcodes (prefix 0x00): DECODE_MAIN[opcode_byte]
// * 0xFC misc group: DECODE_MISC[sub_opcode]
//
// These mirror ENCODING_TABLE (the single source of truth) entry-for-entry;
// unlisted slots default to .INVALID. Four dispatch arrays cover the four
// opcode spaces: core (DECODE_MAIN), 0xFC misc (DECODE_MISC), 0xFD SIMD
// (DECODE_SIMD), and 0xFE threads/atomics (DECODE_ATOMIC).
DECODE_MAIN_COUNT :: 256 // (0..=0xD2)
DECODE_MISC_COUNT :: 32 // 0xFC sub-opcodes (0..=17)
DECODE_SIMD_COUNT :: 0x114 // 0xFD sub-opcodes (0..=0x113)
DECODE_ATOMIC_COUNT :: 0x4F // 0xFE sub-opcodes (0..=0x4E)
@(rodata)
DECODE_MAIN := [DECODE_MAIN_COUNT]Mnemonic{
0x00 = .UNREACHABLE,
0x01 = .NOP,
0x02 = .BLOCK,
0x03 = .LOOP,
0x04 = .IF,
0x05 = .ELSE,
0x0B = .END,
0x0C = .BR,
0x0D = .BR_IF,
0x0E = .BR_TABLE,
0x0F = .RETURN,
0x10 = .CALL,
0x11 = .CALL_INDIRECT,
0x1A = .DROP,
0x1B = .SELECT,
0x20 = .LOCAL_GET,
0x21 = .LOCAL_SET,
0x22 = .LOCAL_TEE,
0x23 = .GLOBAL_GET,
0x24 = .GLOBAL_SET,
0x28 = .I32_LOAD,
0x29 = .I64_LOAD,
0x2A = .F32_LOAD,
0x2B = .F64_LOAD,
0x2C = .I32_LOAD8_S,
0x2D = .I32_LOAD8_U,
0x2E = .I32_LOAD16_S,
0x2F = .I32_LOAD16_U,
0x30 = .I64_LOAD8_S,
0x31 = .I64_LOAD8_U,
0x32 = .I64_LOAD16_S,
0x33 = .I64_LOAD16_U,
0x34 = .I64_LOAD32_S,
0x35 = .I64_LOAD32_U,
0x36 = .I32_STORE,
0x37 = .I64_STORE,
0x38 = .F32_STORE,
0x39 = .F64_STORE,
0x3A = .I32_STORE8,
0x3B = .I32_STORE16,
0x3C = .I64_STORE8,
0x3D = .I64_STORE16,
0x3E = .I64_STORE32,
0x3F = .MEMORY_SIZE,
0x40 = .MEMORY_GROW,
0x41 = .I32_CONST,
0x42 = .I64_CONST,
0x43 = .F32_CONST,
0x44 = .F64_CONST,
0x45 = .I32_EQZ,
0x46 = .I32_EQ,
0x47 = .I32_NE,
0x48 = .I32_LT_S,
0x49 = .I32_LT_U,
0x4A = .I32_GT_S,
0x4B = .I32_GT_U,
0x4C = .I32_LE_S,
0x4D = .I32_LE_U,
0x4E = .I32_GE_S,
0x4F = .I32_GE_U,
0x50 = .I64_EQZ,
0x51 = .I64_EQ,
0x52 = .I64_NE,
0x53 = .I64_LT_S,
0x54 = .I64_LT_U,
0x55 = .I64_GT_S,
0x56 = .I64_GT_U,
0x57 = .I64_LE_S,
0x58 = .I64_LE_U,
0x59 = .I64_GE_S,
0x5A = .I64_GE_U,
0x5B = .F32_EQ,
0x5C = .F32_NE,
0x5D = .F32_LT,
0x5E = .F32_GT,
0x5F = .F32_LE,
0x60 = .F32_GE,
0x61 = .F64_EQ,
0x62 = .F64_NE,
0x63 = .F64_LT,
0x64 = .F64_GT,
0x65 = .F64_LE,
0x66 = .F64_GE,
0x67 = .I32_CLZ,
0x68 = .I32_CTZ,
0x69 = .I32_POPCNT,
0x6A = .I32_ADD,
0x6B = .I32_SUB,
0x6C = .I32_MUL,
0x6D = .I32_DIV_S,
0x6E = .I32_DIV_U,
0x6F = .I32_REM_S,
0x70 = .I32_REM_U,
0x71 = .I32_AND,
0x72 = .I32_OR,
0x73 = .I32_XOR,
0x74 = .I32_SHL,
0x75 = .I32_SHR_S,
0x76 = .I32_SHR_U,
0x77 = .I32_ROTL,
0x78 = .I32_ROTR,
0x79 = .I64_CLZ,
0x7A = .I64_CTZ,
0x7B = .I64_POPCNT,
0x7C = .I64_ADD,
0x7D = .I64_SUB,
0x7E = .I64_MUL,
0x7F = .I64_DIV_S,
0x80 = .I64_DIV_U,
0x81 = .I64_REM_S,
0x82 = .I64_REM_U,
0x83 = .I64_AND,
0x84 = .I64_OR,
0x85 = .I64_XOR,
0x86 = .I64_SHL,
0x87 = .I64_SHR_S,
0x88 = .I64_SHR_U,
0x89 = .I64_ROTL,
0x8A = .I64_ROTR,
0x8B = .F32_ABS,
0x8C = .F32_NEG,
0x8D = .F32_CEIL,
0x8E = .F32_FLOOR,
0x8F = .F32_TRUNC,
0x90 = .F32_NEAREST,
0x91 = .F32_SQRT,
0x92 = .F32_ADD,
0x93 = .F32_SUB,
0x94 = .F32_MUL,
0x95 = .F32_DIV,
0x96 = .F32_MIN,
0x97 = .F32_MAX,
0x98 = .F32_COPYSIGN,
0x99 = .F64_ABS,
0x9A = .F64_NEG,
0x9B = .F64_CEIL,
0x9C = .F64_FLOOR,
0x9D = .F64_TRUNC,
0x9E = .F64_NEAREST,
0x9F = .F64_SQRT,
0xA0 = .F64_ADD,
0xA1 = .F64_SUB,
0xA2 = .F64_MUL,
0xA3 = .F64_DIV,
0xA4 = .F64_MIN,
0xA5 = .F64_MAX,
0xA6 = .F64_COPYSIGN,
0xA7 = .I32_WRAP_I64,
0xA8 = .I32_TRUNC_F32_S,
0xA9 = .I32_TRUNC_F32_U,
0xAA = .I32_TRUNC_F64_S,
0xAB = .I32_TRUNC_F64_U,
0xAC = .I64_EXTEND_I32_S,
0xAD = .I64_EXTEND_I32_U,
0xAE = .I64_TRUNC_F32_S,
0xAF = .I64_TRUNC_F32_U,
0xB0 = .I64_TRUNC_F64_S,
0xB1 = .I64_TRUNC_F64_U,
0xB2 = .F32_CONVERT_I32_S,
0xB3 = .F32_CONVERT_I32_U,
0xB4 = .F32_CONVERT_I64_S,
0xB5 = .F32_CONVERT_I64_U,
0xB6 = .F32_DEMOTE_F64,
0xB7 = .F64_CONVERT_I32_S,
0xB8 = .F64_CONVERT_I32_U,
0xB9 = .F64_CONVERT_I64_S,
0xBA = .F64_CONVERT_I64_U,
0xBB = .F64_PROMOTE_F32,
0xBC = .I32_REINTERPRET_F32,
0xBD = .I64_REINTERPRET_F64,
0xBE = .F32_REINTERPRET_I32,
0xBF = .F64_REINTERPRET_I64,
0xC0 = .I32_EXTEND8_S,
0xC1 = .I32_EXTEND16_S,
0xC2 = .I64_EXTEND8_S,
0xC3 = .I64_EXTEND16_S,
0xC4 = .I64_EXTEND32_S,
0xD0 = .REF_NULL,
0xD1 = .REF_IS_NULL,
0xD2 = .REF_FUNC,
}
@(rodata)
DECODE_MISC := [DECODE_MISC_COUNT]Mnemonic{
0 = .I32_TRUNC_SAT_F32_S,
1 = .I32_TRUNC_SAT_F32_U,
2 = .I32_TRUNC_SAT_F64_S,
3 = .I32_TRUNC_SAT_F64_U,
4 = .I64_TRUNC_SAT_F32_S,
5 = .I64_TRUNC_SAT_F32_U,
6 = .I64_TRUNC_SAT_F64_S,
7 = .I64_TRUNC_SAT_F64_U,
8 = .MEMORY_INIT,
9 = .DATA_DROP,
10 = .MEMORY_COPY,
11 = .MEMORY_FILL,
12 = .TABLE_INIT,
13 = .ELEM_DROP,
14 = .TABLE_COPY,
15 = .TABLE_GROW,
16 = .TABLE_SIZE,
17 = .TABLE_FILL,
}
@(rodata)
DECODE_SIMD := [DECODE_SIMD_COUNT]Mnemonic{
0x00 = .V128_LOAD,
0x01 = .V128_LOAD8X8_S,
0x02 = .V128_LOAD8X8_U,
0x03 = .V128_LOAD16X4_S,
0x04 = .V128_LOAD16X4_U,
0x05 = .V128_LOAD32X2_S,
0x06 = .V128_LOAD32X2_U,
0x07 = .V128_LOAD8_SPLAT,
0x08 = .V128_LOAD16_SPLAT,
0x09 = .V128_LOAD32_SPLAT,
0x0A = .V128_LOAD64_SPLAT,
0x0B = .V128_STORE,
0x0C = .V128_CONST,
0x0D = .I8X16_SHUFFLE,
0x0E = .I8X16_SWIZZLE,
0x0F = .I8X16_SPLAT,
0x10 = .I16X8_SPLAT,
0x11 = .I32X4_SPLAT,
0x12 = .I64X2_SPLAT,
0x13 = .F32X4_SPLAT,
0x14 = .F64X2_SPLAT,
0x15 = .I8X16_EXTRACT_LANE_S,
0x16 = .I8X16_EXTRACT_LANE_U,
0x17 = .I8X16_REPLACE_LANE,
0x18 = .I16X8_EXTRACT_LANE_S,
0x19 = .I16X8_EXTRACT_LANE_U,
0x1A = .I16X8_REPLACE_LANE,
0x1B = .I32X4_EXTRACT_LANE,
0x1C = .I32X4_REPLACE_LANE,
0x1D = .I64X2_EXTRACT_LANE,
0x1E = .I64X2_REPLACE_LANE,
0x1F = .F32X4_EXTRACT_LANE,
0x20 = .F32X4_REPLACE_LANE,
0x21 = .F64X2_EXTRACT_LANE,
0x22 = .F64X2_REPLACE_LANE,
0x23 = .I8X16_EQ,
0x24 = .I8X16_NE,
0x25 = .I8X16_LT_S,
0x26 = .I8X16_LT_U,
0x27 = .I8X16_GT_S,
0x28 = .I8X16_GT_U,
0x29 = .I8X16_LE_S,
0x2A = .I8X16_LE_U,
0x2B = .I8X16_GE_S,
0x2C = .I8X16_GE_U,
0x2D = .I16X8_EQ,
0x2E = .I16X8_NE,
0x2F = .I16X8_LT_S,
0x30 = .I16X8_LT_U,
0x31 = .I16X8_GT_S,
0x32 = .I16X8_GT_U,
0x33 = .I16X8_LE_S,
0x34 = .I16X8_LE_U,
0x35 = .I16X8_GE_S,
0x36 = .I16X8_GE_U,
0x37 = .I32X4_EQ,
0x38 = .I32X4_NE,
0x39 = .I32X4_LT_S,
0x3A = .I32X4_LT_U,
0x3B = .I32X4_GT_S,
0x3C = .I32X4_GT_U,
0x3D = .I32X4_LE_S,
0x3E = .I32X4_LE_U,
0x3F = .I32X4_GE_S,
0x40 = .I32X4_GE_U,
0x41 = .F32X4_EQ,
0x42 = .F32X4_NE,
0x43 = .F32X4_LT,
0x44 = .F32X4_GT,
0x45 = .F32X4_LE,
0x46 = .F32X4_GE,
0x47 = .F64X2_EQ,
0x48 = .F64X2_NE,
0x49 = .F64X2_LT,
0x4A = .F64X2_GT,
0x4B = .F64X2_LE,
0x4C = .F64X2_GE,
0x4D = .V128_NOT,
0x4E = .V128_AND,
0x4F = .V128_ANDNOT,
0x50 = .V128_OR,
0x51 = .V128_XOR,
0x52 = .V128_BITSELECT,
0x53 = .V128_ANY_TRUE,
0x54 = .V128_LOAD8_LANE,
0x55 = .V128_LOAD16_LANE,
0x56 = .V128_LOAD32_LANE,
0x57 = .V128_LOAD64_LANE,
0x58 = .V128_STORE8_LANE,
0x59 = .V128_STORE16_LANE,
0x5A = .V128_STORE32_LANE,
0x5B = .V128_STORE64_LANE,
0x5C = .V128_LOAD32_ZERO,
0x5D = .V128_LOAD64_ZERO,
0x5E = .F32X4_DEMOTE_F64X2_ZERO,
0x5F = .F64X2_PROMOTE_LOW_F32X4,
0x60 = .I8X16_ABS,
0x61 = .I8X16_NEG,
0x62 = .I8X16_POPCNT,
0x63 = .I8X16_ALL_TRUE,
0x64 = .I8X16_BITMASK,
0x65 = .I8X16_NARROW_I16X8_S,
0x66 = .I8X16_NARROW_I16X8_U,
0x67 = .F32X4_CEIL,
0x68 = .F32X4_FLOOR,
0x69 = .F32X4_TRUNC,
0x6A = .F32X4_NEAREST,
0x6B = .I8X16_SHL,
0x6C = .I8X16_SHR_S,
0x6D = .I8X16_SHR_U,
0x6E = .I8X16_ADD,
0x6F = .I8X16_ADD_SAT_S,
0x70 = .I8X16_ADD_SAT_U,
0x71 = .I8X16_SUB,
0x72 = .I8X16_SUB_SAT_S,
0x73 = .I8X16_SUB_SAT_U,
0x74 = .F64X2_CEIL,
0x75 = .F64X2_FLOOR,
0x76 = .I8X16_MIN_S,
0x77 = .I8X16_MIN_U,
0x78 = .I8X16_MAX_S,
0x79 = .I8X16_MAX_U,
0x7A = .F64X2_TRUNC,
0x7B = .I8X16_AVGR_U,
0x7C = .I16X8_EXTADD_PAIRWISE_I8X16_S,
0x7D = .I16X8_EXTADD_PAIRWISE_I8X16_U,
0x7E = .I32X4_EXTADD_PAIRWISE_I16X8_S,
0x7F = .I32X4_EXTADD_PAIRWISE_I16X8_U,
0x80 = .I16X8_ABS,
0x81 = .I16X8_NEG,
0x82 = .I16X8_Q15MULR_SAT_S,
0x83 = .I16X8_ALL_TRUE,
0x84 = .I16X8_BITMASK,
0x85 = .I16X8_NARROW_I32X4_S,
0x86 = .I16X8_NARROW_I32X4_U,
0x87 = .I16X8_EXTEND_LOW_I8X16_S,
0x88 = .I16X8_EXTEND_HIGH_I8X16_S,
0x89 = .I16X8_EXTEND_LOW_I8X16_U,
0x8A = .I16X8_EXTEND_HIGH_I8X16_U,
0x8B = .I16X8_SHL,
0x8C = .I16X8_SHR_S,
0x8D = .I16X8_SHR_U,
0x8E = .I16X8_ADD,
0x8F = .I16X8_ADD_SAT_S,
0x90 = .I16X8_ADD_SAT_U,
0x91 = .I16X8_SUB,
0x92 = .I16X8_SUB_SAT_S,
0x93 = .I16X8_SUB_SAT_U,
0x94 = .F64X2_NEAREST,
0x95 = .I16X8_MUL,
0x96 = .I16X8_MIN_S,
0x97 = .I16X8_MIN_U,
0x98 = .I16X8_MAX_S,
0x99 = .I16X8_MAX_U,
0x9B = .I16X8_AVGR_U,
0x9C = .I16X8_EXTMUL_LOW_I8X16_S,
0x9D = .I16X8_EXTMUL_HIGH_I8X16_S,
0x9E = .I16X8_EXTMUL_LOW_I8X16_U,
0x9F = .I16X8_EXTMUL_HIGH_I8X16_U,
0xA0 = .I32X4_ABS,
0xA1 = .I32X4_NEG,
0xA3 = .I32X4_ALL_TRUE,
0xA4 = .I32X4_BITMASK,
0xA7 = .I32X4_EXTEND_LOW_I16X8_S,
0xA8 = .I32X4_EXTEND_HIGH_I16X8_S,
0xA9 = .I32X4_EXTEND_LOW_I16X8_U,
0xAA = .I32X4_EXTEND_HIGH_I16X8_U,
0xAB = .I32X4_SHL,
0xAC = .I32X4_SHR_S,
0xAD = .I32X4_SHR_U,
0xAE = .I32X4_ADD,
0xB1 = .I32X4_SUB,
0xB5 = .I32X4_MUL,
0xB6 = .I32X4_MIN_S,
0xB7 = .I32X4_MIN_U,
0xB8 = .I32X4_MAX_S,
0xB9 = .I32X4_MAX_U,
0xBA = .I32X4_DOT_I16X8_S,
0xBC = .I32X4_EXTMUL_LOW_I16X8_S,
0xBD = .I32X4_EXTMUL_HIGH_I16X8_S,
0xBE = .I32X4_EXTMUL_LOW_I16X8_U,
0xBF = .I32X4_EXTMUL_HIGH_I16X8_U,
0xC0 = .I64X2_ABS,
0xC1 = .I64X2_NEG,
0xC3 = .I64X2_ALL_TRUE,
0xC4 = .I64X2_BITMASK,
0xC7 = .I64X2_EXTEND_LOW_I32X4_S,
0xC8 = .I64X2_EXTEND_HIGH_I32X4_S,
0xC9 = .I64X2_EXTEND_LOW_I32X4_U,
0xCA = .I64X2_EXTEND_HIGH_I32X4_U,
0xCB = .I64X2_SHL,
0xCC = .I64X2_SHR_S,
0xCD = .I64X2_SHR_U,
0xCE = .I64X2_ADD,
0xD1 = .I64X2_SUB,
0xD5 = .I64X2_MUL,
0xD6 = .I64X2_EQ,
0xD7 = .I64X2_NE,
0xD8 = .I64X2_LT_S,
0xD9 = .I64X2_GT_S,
0xDA = .I64X2_LE_S,
0xDB = .I64X2_GE_S,
0xDC = .I64X2_EXTMUL_LOW_I32X4_S,
0xDD = .I64X2_EXTMUL_HIGH_I32X4_S,
0xDE = .I64X2_EXTMUL_LOW_I32X4_U,
0xDF = .I64X2_EXTMUL_HIGH_I32X4_U,
0xE0 = .F32X4_ABS,
0xE1 = .F32X4_NEG,
0xE3 = .F32X4_SQRT,
0xE4 = .F32X4_ADD,
0xE5 = .F32X4_SUB,
0xE6 = .F32X4_MUL,
0xE7 = .F32X4_DIV,
0xE8 = .F32X4_MIN,
0xE9 = .F32X4_MAX,
0xEA = .F32X4_PMIN,
0xEB = .F32X4_PMAX,
0xEC = .F64X2_ABS,
0xED = .F64X2_NEG,
0xEF = .F64X2_SQRT,
0xF0 = .F64X2_ADD,
0xF1 = .F64X2_SUB,
0xF2 = .F64X2_MUL,
0xF3 = .F64X2_DIV,
0xF4 = .F64X2_MIN,
0xF5 = .F64X2_MAX,
0xF6 = .F64X2_PMIN,
0xF7 = .F64X2_PMAX,
0xF8 = .I32X4_TRUNC_SAT_F32X4_S,
0xF9 = .I32X4_TRUNC_SAT_F32X4_U,
0xFA = .F32X4_CONVERT_I32X4_S,
0xFB = .F32X4_CONVERT_I32X4_U,
0xFC = .I32X4_TRUNC_SAT_F64X2_S_ZERO,
0xFD = .I32X4_TRUNC_SAT_F64X2_U_ZERO,
0xFE = .F64X2_CONVERT_LOW_I32X4_S,
0xFF = .F64X2_CONVERT_LOW_I32X4_U,
0x100 = .I8X16_RELAXED_SWIZZLE,
0x101 = .I32X4_RELAXED_TRUNC_F32X4_S,
0x102 = .I32X4_RELAXED_TRUNC_F32X4_U,
0x103 = .I32X4_RELAXED_TRUNC_F64X2_S_ZERO,
0x104 = .I32X4_RELAXED_TRUNC_F64X2_U_ZERO,
0x105 = .F32X4_RELAXED_MADD,
0x106 = .F32X4_RELAXED_NMADD,
0x107 = .F64X2_RELAXED_MADD,
0x108 = .F64X2_RELAXED_NMADD,
0x109 = .I8X16_RELAXED_LANESELECT,
0x10A = .I16X8_RELAXED_LANESELECT,
0x10B = .I32X4_RELAXED_LANESELECT,
0x10C = .I64X2_RELAXED_LANESELECT,
0x10D = .F32X4_RELAXED_MIN,
0x10E = .F32X4_RELAXED_MAX,
0x10F = .F64X2_RELAXED_MIN,
0x110 = .F64X2_RELAXED_MAX,
0x111 = .I16X8_RELAXED_Q15MULR_S,
0x112 = .I16X8_RELAXED_DOT_I8X16_I7X16_S,
0x113 = .I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S,
}
@(rodata)
DECODE_ATOMIC := [DECODE_ATOMIC_COUNT]Mnemonic{
0x00 = .MEMORY_ATOMIC_NOTIFY,
0x01 = .MEMORY_ATOMIC_WAIT32,
0x02 = .MEMORY_ATOMIC_WAIT64,
0x03 = .ATOMIC_FENCE,
0x10 = .I32_ATOMIC_LOAD,
0x11 = .I64_ATOMIC_LOAD,
0x12 = .I32_ATOMIC_LOAD8_U,
0x13 = .I32_ATOMIC_LOAD16_U,
0x14 = .I64_ATOMIC_LOAD8_U,
0x15 = .I64_ATOMIC_LOAD16_U,
0x16 = .I64_ATOMIC_LOAD32_U,
0x17 = .I32_ATOMIC_STORE,
0x18 = .I64_ATOMIC_STORE,
0x19 = .I32_ATOMIC_STORE8,
0x1A = .I32_ATOMIC_STORE16,
0x1B = .I64_ATOMIC_STORE8,
0x1C = .I64_ATOMIC_STORE16,
0x1D = .I64_ATOMIC_STORE32,
0x1E = .I32_ATOMIC_RMW_ADD,
0x1F = .I64_ATOMIC_RMW_ADD,
0x20 = .I32_ATOMIC_RMW8_ADD_U,
0x21 = .I32_ATOMIC_RMW16_ADD_U,
0x22 = .I64_ATOMIC_RMW8_ADD_U,
0x23 = .I64_ATOMIC_RMW16_ADD_U,
0x24 = .I64_ATOMIC_RMW32_ADD_U,
0x25 = .I32_ATOMIC_RMW_SUB,
0x26 = .I64_ATOMIC_RMW_SUB,
0x27 = .I32_ATOMIC_RMW8_SUB_U,
0x28 = .I32_ATOMIC_RMW16_SUB_U,
0x29 = .I64_ATOMIC_RMW8_SUB_U,
0x2A = .I64_ATOMIC_RMW16_SUB_U,
0x2B = .I64_ATOMIC_RMW32_SUB_U,
0x2C = .I32_ATOMIC_RMW_AND,
0x2D = .I64_ATOMIC_RMW_AND,
0x2E = .I32_ATOMIC_RMW8_AND_U,
0x2F = .I32_ATOMIC_RMW16_AND_U,
0x30 = .I64_ATOMIC_RMW8_AND_U,
0x31 = .I64_ATOMIC_RMW16_AND_U,
0x32 = .I64_ATOMIC_RMW32_AND_U,
0x33 = .I32_ATOMIC_RMW_OR,
0x34 = .I64_ATOMIC_RMW_OR,
0x35 = .I32_ATOMIC_RMW8_OR_U,
0x36 = .I32_ATOMIC_RMW16_OR_U,
0x37 = .I64_ATOMIC_RMW8_OR_U,
0x38 = .I64_ATOMIC_RMW16_OR_U,
0x39 = .I64_ATOMIC_RMW32_OR_U,
0x3A = .I32_ATOMIC_RMW_XOR,
0x3B = .I64_ATOMIC_RMW_XOR,
0x3C = .I32_ATOMIC_RMW8_XOR_U,
0x3D = .I32_ATOMIC_RMW16_XOR_U,
0x3E = .I64_ATOMIC_RMW8_XOR_U,
0x3F = .I64_ATOMIC_RMW16_XOR_U,
0x40 = .I64_ATOMIC_RMW32_XOR_U,
0x41 = .I32_ATOMIC_RMW_XCHG,
0x42 = .I64_ATOMIC_RMW_XCHG,
0x43 = .I32_ATOMIC_RMW8_XCHG_U,
0x44 = .I32_ATOMIC_RMW16_XCHG_U,
0x45 = .I64_ATOMIC_RMW8_XCHG_U,
0x46 = .I64_ATOMIC_RMW16_XCHG_U,
0x47 = .I64_ATOMIC_RMW32_XCHG_U,
0x48 = .I32_ATOMIC_RMW_CMPXCHG,
0x49 = .I64_ATOMIC_RMW_CMPXCHG,
0x4A = .I32_ATOMIC_RMW8_CMPXCHG_U,
0x4B = .I32_ATOMIC_RMW16_CMPXCHG_U,
0x4C = .I64_ATOMIC_RMW8_CMPXCHG_U,
0x4D = .I64_ATOMIC_RMW16_CMPXCHG_U,
0x4E = .I64_ATOMIC_RMW32_CMPXCHG_U,
}

View File

@@ -1,212 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
import "core:math/bits"
// =============================================================================
// WebAssembly ENCODER
// =============================================================================
//
// Variable-length, byte-oriented, LEB128-heavy. Because LEB fields are not a
// fixed width, encoding is sequential: a single forward pass writes each
// instruction's opcode (a byte, or a prefix byte plus an unsigned-LEB
// sub-opcode) followed by its immediates, advancing a byte cursor.
//
// WASM has no PC-relative branches (control flow uses structured label
// depths), so there is no second resolution pass and no rewrite of
// `label_defs`: those parameters are part of the universal signature but are
// inert here. Relocations *are* produced -- for symbolic index references
// (see op_label) -- and returned for a linker to patch; symbolic indices are
// laid down as fixed-width 5-byte LEB placeholders so the patched value fits.
MAX_OPCODE_SIZE :: 3 // prefix byte + two-byte unsigned-LEB sub-opcode (SIMD reaches 0x113)
@(require_results)
encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int {
// Worst case per instruction without a br_table: a 3-byte opcode plus the
// largest single immediate, which is v128.const's 16 raw bytes (a memarg+
// lane pair is smaller). br_table is unbounded in its target count;
// callers encoding tables should size from the target totals.
return n * 24
}
@(require_results)
encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int {
return n
}
encode :: proc(
instructions: []Instruction,
code: []u8,
relocs: ^[dynamic]Relocation,
errors: ^[dynamic]Error,
) -> (byte_count: u32, ok: bool) {
errors_start := u32(len(errors))
for &inst, i in instructions {
n := encode_one(&inst, byte_count, u16(i), code, relocs, errors) or_return
inst.length = u8(min(n, 255))
byte_count += n
}
ok = u32(len(errors)) == errors_start
return
}
encode_one :: proc(
inst: ^Instruction,
pc: u32,
inst_idx: u16,
code: []u8,
relocs: ^[dynamic]Relocation,
errors: ^[dynamic]Error,
) -> (size: u32, ok: bool) {
if inst.mnemonic == .INVALID {
append(errors, Error{inst_idx = u32(inst_idx), code = .INVALID_MNEMONIC})
return
}
form := encoding_form(inst.mnemonic)
need := encoded_size(inst, form)
if pc + need > u32(len(code)) {
append(errors, Error{inst_idx = u32(inst_idx), code = .BUFFER_OVERFLOW})
return
}
off := pc
// Opcode (and prefix sub-opcode).
if form.prefix == PREFIX_NONE {
code[off] = u8(form.opcode)
off += 1
} else {
code[off] = form.prefix
off += 1
write_uleb(code, &off, u64(form.opcode))
}
// Immediates, walked in declaration order with an operand cursor.
opi := 0
for k in form.imm {
switch k {
case .NONE:
// nothing
case .BLOCKTYPE, .I32, .I64:
write_sleb(code, &off, inst.ops[opi].immediate)
opi += 1
case .F32:
write_u32_block(code, &off, u32(inst.ops[opi].immediate))
opi += 1
case .F64:
write_u64_block(code, &off, u64(inst.ops[opi].immediate))
opi += 1
case .IDX:
op := &inst.ops[opi]
if op.flags.symbolic {
append(relocs, Relocation{
offset = off, label_id = op.index, addend = 0,
type = reloc_type_for(op.idx_kind), size = 5, inst_idx = inst_idx,
})
write_uleb_padded5(code, &off, u64(op.index))
} else {
write_uleb(code, &off, u64(op.index))
}
opi += 1
case .MEMARG:
ma := inst.ops[opi].memarg
// TODO(bill): is this correct because the spec says otherwise but the binary formats look like it's log2
align := bits.log2(u64(ma.align))
write_uleb(code, &off, align)
write_uleb(code, &off, u64(ma.offset))
opi += 1
case .REFTYPE:
code[off] = u8(inst.ops[opi].immediate)
off += 1
opi += 1
case .BR_TABLE:
write_uleb(code, &off, u64(len(inst.targets)))
for t in inst.targets {
write_uleb(code, &off, u64(t))
}
write_uleb(code, &off, u64(inst.ops[opi].index)) // default depth
opi += 1
case .ZERO_BYTE:
code[off] = 0x00
off += 1
case .LANE:
code[off] = u8(inst.ops[opi].immediate)
off += 1
opi += 1
case .LANES16:
for bb in inst.bytes {
code[off] = bb
off += 1
}
}
}
return off - pc, true
}
@(private="file")
encoded_size :: proc(inst: ^Instruction, form: ^Encoding) -> u32 {
size: u32 = 1
if form.prefix != PREFIX_NONE {
size += uleb_size(u64(form.opcode))
}
opi := 0
for k in form.imm {
switch k {
case .NONE:
case .BLOCKTYPE, .I32, .I64:
size += sleb_size(inst.ops[opi].immediate)
opi += 1
case .F32:
size += 4
opi += 1
case .F64:
size += 8
opi += 1
case .IDX:
op := &inst.ops[opi]
size += op.flags.symbolic ? 5 : uleb_size(u64(op.index))
opi += 1
case .MEMARG:
ma := inst.ops[opi].memarg
size += uleb_size(u64(ma.align)) + uleb_size(u64(ma.offset))
opi += 1
case .REFTYPE:
size += 1
opi += 1
case .BR_TABLE:
size += uleb_size(u64(len(inst.targets)))
for t in inst.targets {
size += uleb_size(u64(t))
}
size += uleb_size(u64(inst.ops[opi].index))
opi += 1
case .ZERO_BYTE:
size += 1
case .LANE:
size += 1
opi += 1
case .LANES16:
size += 16
}
}
return size
}
@(private="file")
reloc_type_for :: #force_inline proc "contextless" (k: Index_Kind) -> Relocation_Type {
#partial switch k {
case .FUNC: return .FUNCTION_INDEX_LEB
case .TYPE: return .TYPE_INDEX_LEB
case .GLOBAL: return .GLOBAL_INDEX_LEB
case .TABLE: return .TABLE_NUMBER_LEB
}
return .FUNCTION_INDEX_LEB
}

View File

@@ -1,505 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly ENCODING TABLE (single source of truth)
// =============================================================================
//
// One form per mnemonic, indexed directly by the Mnemonic enum. Each entry
// records the prefix byte, the (sub-)opcode, and the immediate layout. The
// decode dispatch in decoding_tables.odin is derived from this table at
// package init, so opcode bytes are written down exactly once.
//
// The `mnemonic` field of each Encoding is left at INVALID here: the table
// index already identifies the mnemonic and the encoder never reads it back.
@(private="file") CTRL :: Encoding_Flags{control = true}
@(private="file") MEM :: Encoding_Flags{memory = true}
@(rodata)
ENCODING_TABLE := [Mnemonic]Encoding{
.INVALID = {inputs = 0, outputs = 0},
// ------------------------------------------------------------------ control
.UNREACHABLE = {prefix = PREFIX_NONE, opcode = 0x00, flags = CTRL, inputs = 0, outputs = 0},
.NOP = {prefix = PREFIX_NONE, opcode = 0x01, inputs = 0, outputs = 0},
.BLOCK = {prefix = PREFIX_NONE, opcode = 0x02, imm = {.BLOCKTYPE, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.LOOP = {prefix = PREFIX_NONE, opcode = 0x03, imm = {.BLOCKTYPE, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.IF = {prefix = PREFIX_NONE, opcode = 0x04, imm = {.BLOCKTYPE, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.ELSE = {prefix = PREFIX_NONE, opcode = 0x05, flags = CTRL, inputs = -1, outputs = -1},
.END = {prefix = PREFIX_NONE, opcode = 0x0B, flags = CTRL, inputs = -1, outputs = -1},
.BR = {prefix = PREFIX_NONE, opcode = 0x0C, imm = {.IDX, .NONE}, flags = CTRL, inputs = 0, outputs = 0},
.BR_IF = {prefix = PREFIX_NONE, opcode = 0x0D, imm = {.IDX, .NONE}, flags = CTRL, inputs = 1, outputs = 0},
.BR_TABLE = {prefix = PREFIX_NONE, opcode = 0x0E, imm = {.BR_TABLE, .NONE}, flags = CTRL, inputs = 1, outputs = 0},
.RETURN = {prefix = PREFIX_NONE, opcode = 0x0F, flags = CTRL, inputs = -1, outputs = -1},
.CALL = {prefix = PREFIX_NONE, opcode = 0x10, imm = {.IDX, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.CALL_INDIRECT = {prefix = PREFIX_NONE, opcode = 0x11, imm = {.IDX, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.RETURN_CALL = {prefix = PREFIX_NONE, opcode = 0x12, imm = {.IDX, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
.RETURN_CALL_INDIRECT = {prefix = PREFIX_NONE, opcode = 0x13, imm = {.IDX, .NONE}, flags = CTRL, inputs = -1, outputs = -1},
// -------------------------------------------------------------- parametric
.DROP = {prefix = PREFIX_NONE, opcode = 0x1A, inputs = 1, outputs = 0},
.SELECT = {prefix = PREFIX_NONE, opcode = 0x1B, inputs = 3, outputs = 1},
// ---------------------------------------------------------------- variable
.LOCAL_GET = {prefix = PREFIX_NONE, opcode = 0x20, imm = {.IDX, .NONE}, inputs = 0, outputs = 1},
.LOCAL_SET = {prefix = PREFIX_NONE, opcode = 0x21, imm = {.IDX, .NONE}, inputs = 1, outputs = 0},
.LOCAL_TEE = {prefix = PREFIX_NONE, opcode = 0x22, imm = {.IDX, .NONE}, inputs = 1, outputs = 1},
.GLOBAL_GET = {prefix = PREFIX_NONE, opcode = 0x23, imm = {.IDX, .NONE}, inputs = 0, outputs = 1},
.GLOBAL_SET = {prefix = PREFIX_NONE, opcode = 0x24, imm = {.IDX, .NONE}, inputs = 1, outputs = 0},
// ------------------------------------------------------------------- memory
.I32_LOAD = {prefix = PREFIX_NONE, opcode = 0x28, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD = {prefix = PREFIX_NONE, opcode = 0x29, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.F32_LOAD = {prefix = PREFIX_NONE, opcode = 0x2A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.F64_LOAD = {prefix = PREFIX_NONE, opcode = 0x2B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_LOAD8_S = {prefix = PREFIX_NONE, opcode = 0x2C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_LOAD8_U = {prefix = PREFIX_NONE, opcode = 0x2D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_LOAD16_S = {prefix = PREFIX_NONE, opcode = 0x2E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_LOAD16_U = {prefix = PREFIX_NONE, opcode = 0x2F, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD8_S = {prefix = PREFIX_NONE, opcode = 0x30, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD8_U = {prefix = PREFIX_NONE, opcode = 0x31, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD16_S = {prefix = PREFIX_NONE, opcode = 0x32, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD16_U = {prefix = PREFIX_NONE, opcode = 0x33, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD32_S = {prefix = PREFIX_NONE, opcode = 0x34, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_LOAD32_U = {prefix = PREFIX_NONE, opcode = 0x35, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_STORE = {prefix = PREFIX_NONE, opcode = 0x36, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_STORE = {prefix = PREFIX_NONE, opcode = 0x37, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.F32_STORE = {prefix = PREFIX_NONE, opcode = 0x38, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.F64_STORE = {prefix = PREFIX_NONE, opcode = 0x39, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I32_STORE8 = {prefix = PREFIX_NONE, opcode = 0x3A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I32_STORE16 = {prefix = PREFIX_NONE, opcode = 0x3B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_STORE8 = {prefix = PREFIX_NONE, opcode = 0x3C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_STORE16 = {prefix = PREFIX_NONE, opcode = 0x3D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_STORE32 = {prefix = PREFIX_NONE, opcode = 0x3E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.MEMORY_SIZE = {prefix = PREFIX_NONE, opcode = 0x3F, imm = {.ZERO_BYTE, .NONE}, flags = MEM, inputs = 0, outputs = 1},
.MEMORY_GROW = {prefix = PREFIX_NONE, opcode = 0x40, imm = {.ZERO_BYTE, .NONE}, flags = MEM, inputs = 1, outputs = 1},
// ----------------------------------------------------------------- numeric
.I32_CONST = {prefix = PREFIX_NONE, opcode = 0x41, imm = {.I32, .NONE}, inputs = 0, outputs = 1},
.I64_CONST = {prefix = PREFIX_NONE, opcode = 0x42, imm = {.I64, .NONE}, inputs = 0, outputs = 1},
.F32_CONST = {prefix = PREFIX_NONE, opcode = 0x43, imm = {.F32, .NONE}, inputs = 0, outputs = 1},
.F64_CONST = {prefix = PREFIX_NONE, opcode = 0x44, imm = {.F64, .NONE}, inputs = 0, outputs = 1},
.I32_EQZ = {prefix = PREFIX_NONE, opcode = 0x45, inputs = 1, outputs = 1}, .I32_EQ = {prefix = PREFIX_NONE, opcode = 0x46, inputs = 2, outputs = 1}, .I32_NE = {prefix = PREFIX_NONE, opcode = 0x47, inputs = 2, outputs = 1},
.I32_LT_S = {prefix = PREFIX_NONE, opcode = 0x48, inputs = 2, outputs = 1}, .I32_LT_U = {prefix = PREFIX_NONE, opcode = 0x49, inputs = 2, outputs = 1},
.I32_GT_S = {prefix = PREFIX_NONE, opcode = 0x4A, inputs = 2, outputs = 1}, .I32_GT_U = {prefix = PREFIX_NONE, opcode = 0x4B, inputs = 2, outputs = 1},
.I32_LE_S = {prefix = PREFIX_NONE, opcode = 0x4C, inputs = 2, outputs = 1}, .I32_LE_U = {prefix = PREFIX_NONE, opcode = 0x4D, inputs = 2, outputs = 1},
.I32_GE_S = {prefix = PREFIX_NONE, opcode = 0x4E, inputs = 2, outputs = 1}, .I32_GE_U = {prefix = PREFIX_NONE, opcode = 0x4F, inputs = 2, outputs = 1},
.I64_EQZ = {prefix = PREFIX_NONE, opcode = 0x50, inputs = 1, outputs = 1}, .I64_EQ = {prefix = PREFIX_NONE, opcode = 0x51, inputs = 2, outputs = 1}, .I64_NE = {prefix = PREFIX_NONE, opcode = 0x52, inputs = 2, outputs = 1},
.I64_LT_S = {prefix = PREFIX_NONE, opcode = 0x53, inputs = 2, outputs = 1}, .I64_LT_U = {prefix = PREFIX_NONE, opcode = 0x54, inputs = 2, outputs = 1},
.I64_GT_S = {prefix = PREFIX_NONE, opcode = 0x55, inputs = 2, outputs = 1}, .I64_GT_U = {prefix = PREFIX_NONE, opcode = 0x56, inputs = 2, outputs = 1},
.I64_LE_S = {prefix = PREFIX_NONE, opcode = 0x57, inputs = 2, outputs = 1}, .I64_LE_U = {prefix = PREFIX_NONE, opcode = 0x58, inputs = 2, outputs = 1},
.I64_GE_S = {prefix = PREFIX_NONE, opcode = 0x59, inputs = 2, outputs = 1}, .I64_GE_U = {prefix = PREFIX_NONE, opcode = 0x5A, inputs = 2, outputs = 1},
.F32_EQ = {prefix = PREFIX_NONE, opcode = 0x5B, inputs = 2, outputs = 1}, .F32_NE = {prefix = PREFIX_NONE, opcode = 0x5C, inputs = 2, outputs = 1},
.F32_LT = {prefix = PREFIX_NONE, opcode = 0x5D, inputs = 2, outputs = 1}, .F32_GT = {prefix = PREFIX_NONE, opcode = 0x5E, inputs = 2, outputs = 1},
.F32_LE = {prefix = PREFIX_NONE, opcode = 0x5F, inputs = 2, outputs = 1}, .F32_GE = {prefix = PREFIX_NONE, opcode = 0x60, inputs = 2, outputs = 1},
.F64_EQ = {prefix = PREFIX_NONE, opcode = 0x61, inputs = 2, outputs = 1}, .F64_NE = {prefix = PREFIX_NONE, opcode = 0x62, inputs = 2, outputs = 1},
.F64_LT = {prefix = PREFIX_NONE, opcode = 0x63, inputs = 2, outputs = 1}, .F64_GT = {prefix = PREFIX_NONE, opcode = 0x64, inputs = 2, outputs = 1},
.F64_LE = {prefix = PREFIX_NONE, opcode = 0x65, inputs = 2, outputs = 1}, .F64_GE = {prefix = PREFIX_NONE, opcode = 0x66, inputs = 2, outputs = 1},
.I32_CLZ = {prefix = PREFIX_NONE, opcode = 0x67, inputs = 1, outputs = 1}, .I32_CTZ = {prefix = PREFIX_NONE, opcode = 0x68, inputs = 1, outputs = 1}, .I32_POPCNT = {prefix = PREFIX_NONE, opcode = 0x69, inputs = 1, outputs = 1},
.I32_ADD = {prefix = PREFIX_NONE, opcode = 0x6A, inputs = 2, outputs = 1}, .I32_SUB = {prefix = PREFIX_NONE, opcode = 0x6B, inputs = 2, outputs = 1}, .I32_MUL = {prefix = PREFIX_NONE, opcode = 0x6C, inputs = 2, outputs = 1},
.I32_DIV_S = {prefix = PREFIX_NONE, opcode = 0x6D, inputs = 2, outputs = 1}, .I32_DIV_U = {prefix = PREFIX_NONE, opcode = 0x6E, inputs = 2, outputs = 1},
.I32_REM_S = {prefix = PREFIX_NONE, opcode = 0x6F, inputs = 2, outputs = 1}, .I32_REM_U = {prefix = PREFIX_NONE, opcode = 0x70, inputs = 2, outputs = 1},
.I32_AND = {prefix = PREFIX_NONE, opcode = 0x71, inputs = 2, outputs = 1}, .I32_OR = {prefix = PREFIX_NONE, opcode = 0x72, inputs = 2, outputs = 1}, .I32_XOR = {prefix = PREFIX_NONE, opcode = 0x73, inputs = 2, outputs = 1},
.I32_SHL = {prefix = PREFIX_NONE, opcode = 0x74, inputs = 2, outputs = 1}, .I32_SHR_S = {prefix = PREFIX_NONE, opcode = 0x75, inputs = 2, outputs = 1}, .I32_SHR_U = {prefix = PREFIX_NONE, opcode = 0x76, inputs = 2, outputs = 1},
.I32_ROTL = {prefix = PREFIX_NONE, opcode = 0x77, inputs = 2, outputs = 1}, .I32_ROTR = {prefix = PREFIX_NONE, opcode = 0x78, inputs = 2, outputs = 1},
.I64_CLZ = {prefix = PREFIX_NONE, opcode = 0x79, inputs = 1, outputs = 1}, .I64_CTZ = {prefix = PREFIX_NONE, opcode = 0x7A, inputs = 1, outputs = 1}, .I64_POPCNT = {prefix = PREFIX_NONE, opcode = 0x7B, inputs = 1, outputs = 1},
.I64_ADD = {prefix = PREFIX_NONE, opcode = 0x7C, inputs = 2, outputs = 1}, .I64_SUB = {prefix = PREFIX_NONE, opcode = 0x7D, inputs = 2, outputs = 1}, .I64_MUL = {prefix = PREFIX_NONE, opcode = 0x7E, inputs = 2, outputs = 1},
.I64_DIV_S = {prefix = PREFIX_NONE, opcode = 0x7F, inputs = 2, outputs = 1}, .I64_DIV_U = {prefix = PREFIX_NONE, opcode = 0x80, inputs = 2, outputs = 1},
.I64_REM_S = {prefix = PREFIX_NONE, opcode = 0x81, inputs = 2, outputs = 1}, .I64_REM_U = {prefix = PREFIX_NONE, opcode = 0x82, inputs = 2, outputs = 1},
.I64_AND = {prefix = PREFIX_NONE, opcode = 0x83, inputs = 2, outputs = 1}, .I64_OR = {prefix = PREFIX_NONE, opcode = 0x84, inputs = 2, outputs = 1}, .I64_XOR = {prefix = PREFIX_NONE, opcode = 0x85, inputs = 2, outputs = 1},
.I64_SHL = {prefix = PREFIX_NONE, opcode = 0x86, inputs = 2, outputs = 1}, .I64_SHR_S = {prefix = PREFIX_NONE, opcode = 0x87, inputs = 2, outputs = 1}, .I64_SHR_U = {prefix = PREFIX_NONE, opcode = 0x88, inputs = 2, outputs = 1},
.I64_ROTL = {prefix = PREFIX_NONE, opcode = 0x89, inputs = 2, outputs = 1}, .I64_ROTR = {prefix = PREFIX_NONE, opcode = 0x8A, inputs = 2, outputs = 1},
.F32_ABS = {prefix = PREFIX_NONE, opcode = 0x8B, inputs = 1, outputs = 1}, .F32_NEG = {prefix = PREFIX_NONE, opcode = 0x8C, inputs = 1, outputs = 1}, .F32_CEIL = {prefix = PREFIX_NONE, opcode = 0x8D, inputs = 1, outputs = 1},
.F32_FLOOR = {prefix = PREFIX_NONE, opcode = 0x8E, inputs = 1, outputs = 1}, .F32_TRUNC = {prefix = PREFIX_NONE, opcode = 0x8F, inputs = 1, outputs = 1}, .F32_NEAREST = {prefix = PREFIX_NONE, opcode = 0x90, inputs = 1, outputs = 1},
.F32_SQRT = {prefix = PREFIX_NONE, opcode = 0x91, inputs = 1, outputs = 1}, .F32_ADD = {prefix = PREFIX_NONE, opcode = 0x92, inputs = 2, outputs = 1}, .F32_SUB = {prefix = PREFIX_NONE, opcode = 0x93, inputs = 2, outputs = 1},
.F32_MUL = {prefix = PREFIX_NONE, opcode = 0x94, inputs = 2, outputs = 1}, .F32_DIV = {prefix = PREFIX_NONE, opcode = 0x95, inputs = 2, outputs = 1}, .F32_MIN = {prefix = PREFIX_NONE, opcode = 0x96, inputs = 2, outputs = 1},
.F32_MAX = {prefix = PREFIX_NONE, opcode = 0x97, inputs = 2, outputs = 1}, .F32_COPYSIGN = {prefix = PREFIX_NONE, opcode = 0x98, inputs = 2, outputs = 1},
.F64_ABS = {prefix = PREFIX_NONE, opcode = 0x99, inputs = 1, outputs = 1}, .F64_NEG = {prefix = PREFIX_NONE, opcode = 0x9A, inputs = 1, outputs = 1}, .F64_CEIL = {prefix = PREFIX_NONE, opcode = 0x9B, inputs = 1, outputs = 1},
.F64_FLOOR = {prefix = PREFIX_NONE, opcode = 0x9C, inputs = 1, outputs = 1}, .F64_TRUNC = {prefix = PREFIX_NONE, opcode = 0x9D, inputs = 1, outputs = 1}, .F64_NEAREST = {prefix = PREFIX_NONE, opcode = 0x9E, inputs = 1, outputs = 1},
.F64_SQRT = {prefix = PREFIX_NONE, opcode = 0x9F, inputs = 1, outputs = 1}, .F64_ADD = {prefix = PREFIX_NONE, opcode = 0xA0, inputs = 2, outputs = 1}, .F64_SUB = {prefix = PREFIX_NONE, opcode = 0xA1, inputs = 2, outputs = 1},
.F64_MUL = {prefix = PREFIX_NONE, opcode = 0xA2, inputs = 2, outputs = 1}, .F64_DIV = {prefix = PREFIX_NONE, opcode = 0xA3, inputs = 2, outputs = 1}, .F64_MIN = {prefix = PREFIX_NONE, opcode = 0xA4, inputs = 2, outputs = 1},
.F64_MAX = {prefix = PREFIX_NONE, opcode = 0xA5, inputs = 2, outputs = 1}, .F64_COPYSIGN = {prefix = PREFIX_NONE, opcode = 0xA6, inputs = 2, outputs = 1},
.I32_WRAP_I64 = {prefix = PREFIX_NONE, opcode = 0xA7, inputs = 1, outputs = 1},
.I32_TRUNC_F32_S = {prefix = PREFIX_NONE, opcode = 0xA8, inputs = 1, outputs = 1}, .I32_TRUNC_F32_U = {prefix = PREFIX_NONE, opcode = 0xA9, inputs = 1, outputs = 1},
.I32_TRUNC_F64_S = {prefix = PREFIX_NONE, opcode = 0xAA, inputs = 1, outputs = 1}, .I32_TRUNC_F64_U = {prefix = PREFIX_NONE, opcode = 0xAB, inputs = 1, outputs = 1},
.I64_EXTEND_I32_S = {prefix = PREFIX_NONE, opcode = 0xAC, inputs = 1, outputs = 1}, .I64_EXTEND_I32_U = {prefix = PREFIX_NONE, opcode = 0xAD, inputs = 1, outputs = 1},
.I64_TRUNC_F32_S = {prefix = PREFIX_NONE, opcode = 0xAE, inputs = 1, outputs = 1}, .I64_TRUNC_F32_U = {prefix = PREFIX_NONE, opcode = 0xAF, inputs = 1, outputs = 1},
.I64_TRUNC_F64_S = {prefix = PREFIX_NONE, opcode = 0xB0, inputs = 1, outputs = 1}, .I64_TRUNC_F64_U = {prefix = PREFIX_NONE, opcode = 0xB1, inputs = 1, outputs = 1},
.F32_CONVERT_I32_S = {prefix = PREFIX_NONE, opcode = 0xB2, inputs = 1, outputs = 1}, .F32_CONVERT_I32_U = {prefix = PREFIX_NONE, opcode = 0xB3, inputs = 1, outputs = 1},
.F32_CONVERT_I64_S = {prefix = PREFIX_NONE, opcode = 0xB4, inputs = 1, outputs = 1}, .F32_CONVERT_I64_U = {prefix = PREFIX_NONE, opcode = 0xB5, inputs = 1, outputs = 1},
.F32_DEMOTE_F64 = {prefix = PREFIX_NONE, opcode = 0xB6, inputs = 1, outputs = 1},
.F64_CONVERT_I32_S = {prefix = PREFIX_NONE, opcode = 0xB7, inputs = 1, outputs = 1}, .F64_CONVERT_I32_U = {prefix = PREFIX_NONE, opcode = 0xB8, inputs = 1, outputs = 1},
.F64_CONVERT_I64_S = {prefix = PREFIX_NONE, opcode = 0xB9, inputs = 1, outputs = 1}, .F64_CONVERT_I64_U = {prefix = PREFIX_NONE, opcode = 0xBA, inputs = 1, outputs = 1},
.F64_PROMOTE_F32 = {prefix = PREFIX_NONE, opcode = 0xBB, inputs = 1, outputs = 1},
.I32_REINTERPRET_F32 = {prefix = PREFIX_NONE, opcode = 0xBC, inputs = 1, outputs = 1}, .I64_REINTERPRET_F64 = {prefix = PREFIX_NONE, opcode = 0xBD, inputs = 1, outputs = 1},
.F32_REINTERPRET_I32 = {prefix = PREFIX_NONE, opcode = 0xBE, inputs = 1, outputs = 1}, .F64_REINTERPRET_I64 = {prefix = PREFIX_NONE, opcode = 0xBF, inputs = 1, outputs = 1},
.I32_EXTEND8_S = {prefix = PREFIX_NONE, opcode = 0xC0, inputs = 1, outputs = 1}, .I32_EXTEND16_S = {prefix = PREFIX_NONE, opcode = 0xC1, inputs = 1, outputs = 1},
.I64_EXTEND8_S = {prefix = PREFIX_NONE, opcode = 0xC2, inputs = 1, outputs = 1}, .I64_EXTEND16_S = {prefix = PREFIX_NONE, opcode = 0xC3, inputs = 1, outputs = 1}, .I64_EXTEND32_S = {prefix = PREFIX_NONE, opcode = 0xC4, inputs = 1, outputs = 1},
.REF_NULL = {prefix = PREFIX_NONE, opcode = 0xD0, imm = {.REFTYPE, .NONE}, inputs = 0, outputs = 1},
.REF_IS_NULL = {prefix = PREFIX_NONE, opcode = 0xD1, inputs = 1, outputs = 1},
.REF_FUNC = {prefix = PREFIX_NONE, opcode = 0xD2, imm = {.IDX, .NONE}, inputs = 0, outputs = 1},
// ------------------------------------------------------- 0xFC misc prefix
.I32_TRUNC_SAT_F32_S = {prefix = PREFIX_MISC, opcode = 0, inputs = 1, outputs = 1}, .I32_TRUNC_SAT_F32_U = {prefix = PREFIX_MISC, opcode = 1, inputs = 1, outputs = 1},
.I32_TRUNC_SAT_F64_S = {prefix = PREFIX_MISC, opcode = 2, inputs = 1, outputs = 1}, .I32_TRUNC_SAT_F64_U = {prefix = PREFIX_MISC, opcode = 3, inputs = 1, outputs = 1},
.I64_TRUNC_SAT_F32_S = {prefix = PREFIX_MISC, opcode = 4, inputs = 1, outputs = 1}, .I64_TRUNC_SAT_F32_U = {prefix = PREFIX_MISC, opcode = 5, inputs = 1, outputs = 1},
.I64_TRUNC_SAT_F64_S = {prefix = PREFIX_MISC, opcode = 6, inputs = 1, outputs = 1}, .I64_TRUNC_SAT_F64_U = {prefix = PREFIX_MISC, opcode = 7, inputs = 1, outputs = 1},
.MEMORY_INIT = {prefix = PREFIX_MISC, opcode = 8, imm = {.IDX, .ZERO_BYTE}, flags = MEM, inputs = 3, outputs = 0},
.DATA_DROP = {prefix = PREFIX_MISC, opcode = 9, imm = {.IDX, .NONE}, inputs = 0, outputs = 0},
.MEMORY_COPY = {prefix = PREFIX_MISC, opcode = 10, imm = {.ZERO_BYTE, .ZERO_BYTE}, flags = MEM, inputs = 3, outputs = 0},
.MEMORY_FILL = {prefix = PREFIX_MISC, opcode = 11, imm = {.ZERO_BYTE, .NONE}, flags = MEM, inputs = 3, outputs = 0},
.TABLE_INIT = {prefix = PREFIX_MISC, opcode = 12, imm = {.IDX, .IDX}, inputs = 3, outputs = 0},
.ELEM_DROP = {prefix = PREFIX_MISC, opcode = 13, imm = {.IDX, .NONE}, inputs = 0, outputs = 0},
.TABLE_COPY = {prefix = PREFIX_MISC, opcode = 14, imm = {.IDX, .IDX}, inputs = 3, outputs = 0},
.TABLE_GROW = {prefix = PREFIX_MISC, opcode = 15, imm = {.IDX, .NONE}, inputs = 2, outputs = 1},
.TABLE_SIZE = {prefix = PREFIX_MISC, opcode = 16, imm = {.IDX, .NONE}, inputs = 0, outputs = 1},
.TABLE_FILL = {prefix = PREFIX_MISC, opcode = 17, imm = {.IDX, .NONE}, inputs = 3, outputs = 0},
// ----------------------------------------------- 0xFD SIMD (v128) prefix
.V128_LOAD = {prefix = PREFIX_SIMD, opcode = 0x00, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD8X8_S = {prefix = PREFIX_SIMD, opcode = 0x01, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD8X8_U = {prefix = PREFIX_SIMD, opcode = 0x02, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD16X4_S = {prefix = PREFIX_SIMD, opcode = 0x03, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD16X4_U = {prefix = PREFIX_SIMD, opcode = 0x04, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD32X2_S = {prefix = PREFIX_SIMD, opcode = 0x05, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD32X2_U = {prefix = PREFIX_SIMD, opcode = 0x06, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD8_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x07, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD16_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x08, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD32_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x09, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD64_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x0A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_STORE = {prefix = PREFIX_SIMD, opcode = 0x0B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.V128_CONST = {prefix = PREFIX_SIMD, opcode = 0x0C, imm = {.LANES16, .NONE}, inputs = 0, outputs = 1},
.I8X16_SHUFFLE = {prefix = PREFIX_SIMD, opcode = 0x0D, imm = {.LANES16, .NONE}, inputs = 2, outputs = 1},
.I8X16_SWIZZLE = {prefix = PREFIX_SIMD, opcode = 0x0E, inputs = 2, outputs = 1},
.I8X16_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x0F, inputs = 1, outputs = 1},
.I16X8_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x10, inputs = 1, outputs = 1},
.I32X4_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x11, inputs = 1, outputs = 1},
.I64X2_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x12, inputs = 1, outputs = 1},
.F32X4_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x13, inputs = 1, outputs = 1},
.F64X2_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x14, inputs = 1, outputs = 1},
.I8X16_EXTRACT_LANE_S = {prefix = PREFIX_SIMD, opcode = 0x15, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I8X16_EXTRACT_LANE_U = {prefix = PREFIX_SIMD, opcode = 0x16, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I8X16_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x17, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.I16X8_EXTRACT_LANE_S = {prefix = PREFIX_SIMD, opcode = 0x18, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I16X8_EXTRACT_LANE_U = {prefix = PREFIX_SIMD, opcode = 0x19, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I16X8_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1A, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.I32X4_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1B, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I32X4_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1C, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.I64X2_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1D, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.I64X2_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1E, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.F32X4_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1F, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.F32X4_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x20, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.F64X2_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x21, imm = {.LANE, .NONE}, inputs = 1, outputs = 1},
.F64X2_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x22, imm = {.LANE, .NONE}, inputs = 2, outputs = 1},
.I8X16_EQ = {prefix = PREFIX_SIMD, opcode = 0x23, inputs = 2, outputs = 1},
.I8X16_NE = {prefix = PREFIX_SIMD, opcode = 0x24, inputs = 2, outputs = 1},
.I8X16_LT_S = {prefix = PREFIX_SIMD, opcode = 0x25, inputs = 2, outputs = 1},
.I8X16_LT_U = {prefix = PREFIX_SIMD, opcode = 0x26, inputs = 2, outputs = 1},
.I8X16_GT_S = {prefix = PREFIX_SIMD, opcode = 0x27, inputs = 2, outputs = 1},
.I8X16_GT_U = {prefix = PREFIX_SIMD, opcode = 0x28, inputs = 2, outputs = 1},
.I8X16_LE_S = {prefix = PREFIX_SIMD, opcode = 0x29, inputs = 2, outputs = 1},
.I8X16_LE_U = {prefix = PREFIX_SIMD, opcode = 0x2A, inputs = 2, outputs = 1},
.I8X16_GE_S = {prefix = PREFIX_SIMD, opcode = 0x2B, inputs = 2, outputs = 1},
.I8X16_GE_U = {prefix = PREFIX_SIMD, opcode = 0x2C, inputs = 2, outputs = 1},
.I16X8_EQ = {prefix = PREFIX_SIMD, opcode = 0x2D, inputs = 2, outputs = 1},
.I16X8_NE = {prefix = PREFIX_SIMD, opcode = 0x2E, inputs = 2, outputs = 1},
.I16X8_LT_S = {prefix = PREFIX_SIMD, opcode = 0x2F, inputs = 2, outputs = 1},
.I16X8_LT_U = {prefix = PREFIX_SIMD, opcode = 0x30, inputs = 2, outputs = 1},
.I16X8_GT_S = {prefix = PREFIX_SIMD, opcode = 0x31, inputs = 2, outputs = 1},
.I16X8_GT_U = {prefix = PREFIX_SIMD, opcode = 0x32, inputs = 2, outputs = 1},
.I16X8_LE_S = {prefix = PREFIX_SIMD, opcode = 0x33, inputs = 2, outputs = 1},
.I16X8_LE_U = {prefix = PREFIX_SIMD, opcode = 0x34, inputs = 2, outputs = 1},
.I16X8_GE_S = {prefix = PREFIX_SIMD, opcode = 0x35, inputs = 2, outputs = 1},
.I16X8_GE_U = {prefix = PREFIX_SIMD, opcode = 0x36, inputs = 2, outputs = 1},
.I32X4_EQ = {prefix = PREFIX_SIMD, opcode = 0x37, inputs = 2, outputs = 1},
.I32X4_NE = {prefix = PREFIX_SIMD, opcode = 0x38, inputs = 2, outputs = 1},
.I32X4_LT_S = {prefix = PREFIX_SIMD, opcode = 0x39, inputs = 2, outputs = 1},
.I32X4_LT_U = {prefix = PREFIX_SIMD, opcode = 0x3A, inputs = 2, outputs = 1},
.I32X4_GT_S = {prefix = PREFIX_SIMD, opcode = 0x3B, inputs = 2, outputs = 1},
.I32X4_GT_U = {prefix = PREFIX_SIMD, opcode = 0x3C, inputs = 2, outputs = 1},
.I32X4_LE_S = {prefix = PREFIX_SIMD, opcode = 0x3D, inputs = 2, outputs = 1},
.I32X4_LE_U = {prefix = PREFIX_SIMD, opcode = 0x3E, inputs = 2, outputs = 1},
.I32X4_GE_S = {prefix = PREFIX_SIMD, opcode = 0x3F, inputs = 2, outputs = 1},
.I32X4_GE_U = {prefix = PREFIX_SIMD, opcode = 0x40, inputs = 2, outputs = 1},
.F32X4_EQ = {prefix = PREFIX_SIMD, opcode = 0x41, inputs = 2, outputs = 1},
.F32X4_NE = {prefix = PREFIX_SIMD, opcode = 0x42, inputs = 2, outputs = 1},
.F32X4_LT = {prefix = PREFIX_SIMD, opcode = 0x43, inputs = 2, outputs = 1},
.F32X4_GT = {prefix = PREFIX_SIMD, opcode = 0x44, inputs = 2, outputs = 1},
.F32X4_LE = {prefix = PREFIX_SIMD, opcode = 0x45, inputs = 2, outputs = 1},
.F32X4_GE = {prefix = PREFIX_SIMD, opcode = 0x46, inputs = 2, outputs = 1},
.F64X2_EQ = {prefix = PREFIX_SIMD, opcode = 0x47, inputs = 2, outputs = 1},
.F64X2_NE = {prefix = PREFIX_SIMD, opcode = 0x48, inputs = 2, outputs = 1},
.F64X2_LT = {prefix = PREFIX_SIMD, opcode = 0x49, inputs = 2, outputs = 1},
.F64X2_GT = {prefix = PREFIX_SIMD, opcode = 0x4A, inputs = 2, outputs = 1},
.F64X2_LE = {prefix = PREFIX_SIMD, opcode = 0x4B, inputs = 2, outputs = 1},
.F64X2_GE = {prefix = PREFIX_SIMD, opcode = 0x4C, inputs = 2, outputs = 1},
.V128_NOT = {prefix = PREFIX_SIMD, opcode = 0x4D, inputs = 1, outputs = 1},
.V128_AND = {prefix = PREFIX_SIMD, opcode = 0x4E, inputs = 2, outputs = 1},
.V128_ANDNOT = {prefix = PREFIX_SIMD, opcode = 0x4F, inputs = 2, outputs = 1},
.V128_OR = {prefix = PREFIX_SIMD, opcode = 0x50, inputs = 2, outputs = 1},
.V128_XOR = {prefix = PREFIX_SIMD, opcode = 0x51, inputs = 2, outputs = 1},
.V128_BITSELECT = {prefix = PREFIX_SIMD, opcode = 0x52, inputs = 3, outputs = 1},
.V128_ANY_TRUE = {prefix = PREFIX_SIMD, opcode = 0x53, inputs = 1, outputs = 1},
.V128_LOAD8_LANE = {prefix = PREFIX_SIMD, opcode = 0x54, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 1},
.V128_LOAD16_LANE = {prefix = PREFIX_SIMD, opcode = 0x55, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 1},
.V128_LOAD32_LANE = {prefix = PREFIX_SIMD, opcode = 0x56, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 1},
.V128_LOAD64_LANE = {prefix = PREFIX_SIMD, opcode = 0x57, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 1},
.V128_STORE8_LANE = {prefix = PREFIX_SIMD, opcode = 0x58, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 0},
.V128_STORE16_LANE = {prefix = PREFIX_SIMD, opcode = 0x59, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 0},
.V128_STORE32_LANE = {prefix = PREFIX_SIMD, opcode = 0x5A, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 0},
.V128_STORE64_LANE = {prefix = PREFIX_SIMD, opcode = 0x5B, imm = {.MEMARG, .LANE}, flags = MEM, inputs = 2, outputs = 0},
.V128_LOAD32_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.V128_LOAD64_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.F32X4_DEMOTE_F64X2_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5E, inputs = 1, outputs = 1},
.F64X2_PROMOTE_LOW_F32X4 = {prefix = PREFIX_SIMD, opcode = 0x5F, inputs = 1, outputs = 1},
.I8X16_ABS = {prefix = PREFIX_SIMD, opcode = 0x60, inputs = 1, outputs = 1},
.I8X16_NEG = {prefix = PREFIX_SIMD, opcode = 0x61, inputs = 1, outputs = 1},
.I8X16_POPCNT = {prefix = PREFIX_SIMD, opcode = 0x62, inputs = 1, outputs = 1},
.I8X16_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0x63, inputs = 1, outputs = 1},
.I8X16_BITMASK = {prefix = PREFIX_SIMD, opcode = 0x64, inputs = 1, outputs = 1},
.I8X16_NARROW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0x65, inputs = 2, outputs = 1},
.I8X16_NARROW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0x66, inputs = 2, outputs = 1},
.F32X4_CEIL = {prefix = PREFIX_SIMD, opcode = 0x67, inputs = 1, outputs = 1},
.F32X4_FLOOR = {prefix = PREFIX_SIMD, opcode = 0x68, inputs = 1, outputs = 1},
.F32X4_TRUNC = {prefix = PREFIX_SIMD, opcode = 0x69, inputs = 1, outputs = 1},
.F32X4_NEAREST = {prefix = PREFIX_SIMD, opcode = 0x6A, inputs = 1, outputs = 1},
.I8X16_SHL = {prefix = PREFIX_SIMD, opcode = 0x6B, inputs = 2, outputs = 1},
.I8X16_SHR_S = {prefix = PREFIX_SIMD, opcode = 0x6C, inputs = 2, outputs = 1},
.I8X16_SHR_U = {prefix = PREFIX_SIMD, opcode = 0x6D, inputs = 2, outputs = 1},
.I8X16_ADD = {prefix = PREFIX_SIMD, opcode = 0x6E, inputs = 2, outputs = 1},
.I8X16_ADD_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x6F, inputs = 2, outputs = 1},
.I8X16_ADD_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x70, inputs = 2, outputs = 1},
.I8X16_SUB = {prefix = PREFIX_SIMD, opcode = 0x71, inputs = 2, outputs = 1},
.I8X16_SUB_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x72, inputs = 2, outputs = 1},
.I8X16_SUB_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x73, inputs = 2, outputs = 1},
.F64X2_CEIL = {prefix = PREFIX_SIMD, opcode = 0x74, inputs = 1, outputs = 1},
.F64X2_FLOOR = {prefix = PREFIX_SIMD, opcode = 0x75, inputs = 1, outputs = 1},
.I8X16_MIN_S = {prefix = PREFIX_SIMD, opcode = 0x76, inputs = 2, outputs = 1},
.I8X16_MIN_U = {prefix = PREFIX_SIMD, opcode = 0x77, inputs = 2, outputs = 1},
.I8X16_MAX_S = {prefix = PREFIX_SIMD, opcode = 0x78, inputs = 2, outputs = 1},
.I8X16_MAX_U = {prefix = PREFIX_SIMD, opcode = 0x79, inputs = 2, outputs = 1},
.F64X2_TRUNC = {prefix = PREFIX_SIMD, opcode = 0x7A, inputs = 1, outputs = 1},
.I8X16_AVGR_U = {prefix = PREFIX_SIMD, opcode = 0x7B, inputs = 2, outputs = 1},
.I16X8_EXTADD_PAIRWISE_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x7C, inputs = 1, outputs = 1},
.I16X8_EXTADD_PAIRWISE_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x7D, inputs = 1, outputs = 1},
.I32X4_EXTADD_PAIRWISE_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0x7E, inputs = 1, outputs = 1},
.I32X4_EXTADD_PAIRWISE_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0x7F, inputs = 1, outputs = 1},
.I16X8_ABS = {prefix = PREFIX_SIMD, opcode = 0x80, inputs = 1, outputs = 1},
.I16X8_NEG = {prefix = PREFIX_SIMD, opcode = 0x81, inputs = 1, outputs = 1},
.I16X8_Q15MULR_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x82, inputs = 2, outputs = 1},
.I16X8_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0x83, inputs = 1, outputs = 1},
.I16X8_BITMASK = {prefix = PREFIX_SIMD, opcode = 0x84, inputs = 1, outputs = 1},
.I16X8_NARROW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0x85, inputs = 2, outputs = 1},
.I16X8_NARROW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0x86, inputs = 2, outputs = 1},
.I16X8_EXTEND_LOW_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x87, inputs = 1, outputs = 1},
.I16X8_EXTEND_HIGH_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x88, inputs = 1, outputs = 1},
.I16X8_EXTEND_LOW_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x89, inputs = 1, outputs = 1},
.I16X8_EXTEND_HIGH_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x8A, inputs = 1, outputs = 1},
.I16X8_SHL = {prefix = PREFIX_SIMD, opcode = 0x8B, inputs = 2, outputs = 1},
.I16X8_SHR_S = {prefix = PREFIX_SIMD, opcode = 0x8C, inputs = 2, outputs = 1},
.I16X8_SHR_U = {prefix = PREFIX_SIMD, opcode = 0x8D, inputs = 2, outputs = 1},
.I16X8_ADD = {prefix = PREFIX_SIMD, opcode = 0x8E, inputs = 2, outputs = 1},
.I16X8_ADD_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x8F, inputs = 2, outputs = 1},
.I16X8_ADD_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x90, inputs = 2, outputs = 1},
.I16X8_SUB = {prefix = PREFIX_SIMD, opcode = 0x91, inputs = 2, outputs = 1},
.I16X8_SUB_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x92, inputs = 2, outputs = 1},
.I16X8_SUB_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x93, inputs = 2, outputs = 1},
.F64X2_NEAREST = {prefix = PREFIX_SIMD, opcode = 0x94, inputs = 1, outputs = 1},
.I16X8_MUL = {prefix = PREFIX_SIMD, opcode = 0x95, inputs = 2, outputs = 1},
.I16X8_MIN_S = {prefix = PREFIX_SIMD, opcode = 0x96, inputs = 2, outputs = 1},
.I16X8_MIN_U = {prefix = PREFIX_SIMD, opcode = 0x97, inputs = 2, outputs = 1},
.I16X8_MAX_S = {prefix = PREFIX_SIMD, opcode = 0x98, inputs = 2, outputs = 1},
.I16X8_MAX_U = {prefix = PREFIX_SIMD, opcode = 0x99, inputs = 2, outputs = 1},
.I16X8_AVGR_U = {prefix = PREFIX_SIMD, opcode = 0x9B, inputs = 2, outputs = 1},
.I16X8_EXTMUL_LOW_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x9C, inputs = 2, outputs = 1},
.I16X8_EXTMUL_HIGH_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x9D, inputs = 2, outputs = 1},
.I16X8_EXTMUL_LOW_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x9E, inputs = 2, outputs = 1},
.I16X8_EXTMUL_HIGH_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x9F, inputs = 2, outputs = 1},
.I32X4_ABS = {prefix = PREFIX_SIMD, opcode = 0xA0, inputs = 1, outputs = 1},
.I32X4_NEG = {prefix = PREFIX_SIMD, opcode = 0xA1, inputs = 1, outputs = 1},
.I32X4_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0xA3, inputs = 1, outputs = 1},
.I32X4_BITMASK = {prefix = PREFIX_SIMD, opcode = 0xA4, inputs = 1, outputs = 1},
.I32X4_EXTEND_LOW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xA7, inputs = 1, outputs = 1},
.I32X4_EXTEND_HIGH_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xA8, inputs = 1, outputs = 1},
.I32X4_EXTEND_LOW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xA9, inputs = 1, outputs = 1},
.I32X4_EXTEND_HIGH_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xAA, inputs = 1, outputs = 1},
.I32X4_SHL = {prefix = PREFIX_SIMD, opcode = 0xAB, inputs = 2, outputs = 1},
.I32X4_SHR_S = {prefix = PREFIX_SIMD, opcode = 0xAC, inputs = 2, outputs = 1},
.I32X4_SHR_U = {prefix = PREFIX_SIMD, opcode = 0xAD, inputs = 2, outputs = 1},
.I32X4_ADD = {prefix = PREFIX_SIMD, opcode = 0xAE, inputs = 2, outputs = 1},
.I32X4_SUB = {prefix = PREFIX_SIMD, opcode = 0xB1, inputs = 2, outputs = 1},
.I32X4_MUL = {prefix = PREFIX_SIMD, opcode = 0xB5, inputs = 2, outputs = 1},
.I32X4_MIN_S = {prefix = PREFIX_SIMD, opcode = 0xB6, inputs = 2, outputs = 1},
.I32X4_MIN_U = {prefix = PREFIX_SIMD, opcode = 0xB7, inputs = 2, outputs = 1},
.I32X4_MAX_S = {prefix = PREFIX_SIMD, opcode = 0xB8, inputs = 2, outputs = 1},
.I32X4_MAX_U = {prefix = PREFIX_SIMD, opcode = 0xB9, inputs = 2, outputs = 1},
.I32X4_DOT_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBA, inputs = 2, outputs = 1},
.I32X4_EXTMUL_LOW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBC, inputs = 2, outputs = 1},
.I32X4_EXTMUL_HIGH_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBD, inputs = 2, outputs = 1},
.I32X4_EXTMUL_LOW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xBE, inputs = 2, outputs = 1},
.I32X4_EXTMUL_HIGH_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xBF, inputs = 2, outputs = 1},
.I64X2_ABS = {prefix = PREFIX_SIMD, opcode = 0xC0, inputs = 1, outputs = 1},
.I64X2_NEG = {prefix = PREFIX_SIMD, opcode = 0xC1, inputs = 1, outputs = 1},
.I64X2_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0xC3, inputs = 1, outputs = 1},
.I64X2_BITMASK = {prefix = PREFIX_SIMD, opcode = 0xC4, inputs = 1, outputs = 1},
.I64X2_EXTEND_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xC7, inputs = 1, outputs = 1},
.I64X2_EXTEND_HIGH_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xC8, inputs = 1, outputs = 1},
.I64X2_EXTEND_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xC9, inputs = 1, outputs = 1},
.I64X2_EXTEND_HIGH_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xCA, inputs = 1, outputs = 1},
.I64X2_SHL = {prefix = PREFIX_SIMD, opcode = 0xCB, inputs = 2, outputs = 1},
.I64X2_SHR_S = {prefix = PREFIX_SIMD, opcode = 0xCC, inputs = 2, outputs = 1},
.I64X2_SHR_U = {prefix = PREFIX_SIMD, opcode = 0xCD, inputs = 2, outputs = 1},
.I64X2_ADD = {prefix = PREFIX_SIMD, opcode = 0xCE, inputs = 2, outputs = 1},
.I64X2_SUB = {prefix = PREFIX_SIMD, opcode = 0xD1, inputs = 2, outputs = 1},
.I64X2_MUL = {prefix = PREFIX_SIMD, opcode = 0xD5, inputs = 2, outputs = 1},
.I64X2_EQ = {prefix = PREFIX_SIMD, opcode = 0xD6, inputs = 2, outputs = 1},
.I64X2_NE = {prefix = PREFIX_SIMD, opcode = 0xD7, inputs = 2, outputs = 1},
.I64X2_LT_S = {prefix = PREFIX_SIMD, opcode = 0xD8, inputs = 2, outputs = 1},
.I64X2_GT_S = {prefix = PREFIX_SIMD, opcode = 0xD9, inputs = 2, outputs = 1},
.I64X2_LE_S = {prefix = PREFIX_SIMD, opcode = 0xDA, inputs = 2, outputs = 1},
.I64X2_GE_S = {prefix = PREFIX_SIMD, opcode = 0xDB, inputs = 2, outputs = 1},
.I64X2_EXTMUL_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xDC, inputs = 2, outputs = 1},
.I64X2_EXTMUL_HIGH_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xDD, inputs = 2, outputs = 1},
.I64X2_EXTMUL_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xDE, inputs = 2, outputs = 1},
.I64X2_EXTMUL_HIGH_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xDF, inputs = 2, outputs = 1},
.F32X4_ABS = {prefix = PREFIX_SIMD, opcode = 0xE0, inputs = 1, outputs = 1},
.F32X4_NEG = {prefix = PREFIX_SIMD, opcode = 0xE1, inputs = 1, outputs = 1},
.F32X4_SQRT = {prefix = PREFIX_SIMD, opcode = 0xE3, inputs = 1, outputs = 1},
.F32X4_ADD = {prefix = PREFIX_SIMD, opcode = 0xE4, inputs = 2, outputs = 1},
.F32X4_SUB = {prefix = PREFIX_SIMD, opcode = 0xE5, inputs = 2, outputs = 1},
.F32X4_MUL = {prefix = PREFIX_SIMD, opcode = 0xE6, inputs = 2, outputs = 1},
.F32X4_DIV = {prefix = PREFIX_SIMD, opcode = 0xE7, inputs = 2, outputs = 1},
.F32X4_MIN = {prefix = PREFIX_SIMD, opcode = 0xE8, inputs = 2, outputs = 1},
.F32X4_MAX = {prefix = PREFIX_SIMD, opcode = 0xE9, inputs = 2, outputs = 1},
.F32X4_PMIN = {prefix = PREFIX_SIMD, opcode = 0xEA, inputs = 2, outputs = 1},
.F32X4_PMAX = {prefix = PREFIX_SIMD, opcode = 0xEB, inputs = 2, outputs = 1},
.F64X2_ABS = {prefix = PREFIX_SIMD, opcode = 0xEC, inputs = 1, outputs = 1},
.F64X2_NEG = {prefix = PREFIX_SIMD, opcode = 0xED, inputs = 1, outputs = 1},
.F64X2_SQRT = {prefix = PREFIX_SIMD, opcode = 0xEF, inputs = 1, outputs = 1},
.F64X2_ADD = {prefix = PREFIX_SIMD, opcode = 0xF0, inputs = 2, outputs = 1},
.F64X2_SUB = {prefix = PREFIX_SIMD, opcode = 0xF1, inputs = 2, outputs = 1},
.F64X2_MUL = {prefix = PREFIX_SIMD, opcode = 0xF2, inputs = 2, outputs = 1},
.F64X2_DIV = {prefix = PREFIX_SIMD, opcode = 0xF3, inputs = 2, outputs = 1},
.F64X2_MIN = {prefix = PREFIX_SIMD, opcode = 0xF4, inputs = 2, outputs = 1},
.F64X2_MAX = {prefix = PREFIX_SIMD, opcode = 0xF5, inputs = 2, outputs = 1},
.F64X2_PMIN = {prefix = PREFIX_SIMD, opcode = 0xF6, inputs = 2, outputs = 1},
.F64X2_PMAX = {prefix = PREFIX_SIMD, opcode = 0xF7, inputs = 2, outputs = 1},
.I32X4_TRUNC_SAT_F32X4_S = {prefix = PREFIX_SIMD, opcode = 0xF8, inputs = 1, outputs = 1},
.I32X4_TRUNC_SAT_F32X4_U = {prefix = PREFIX_SIMD, opcode = 0xF9, inputs = 1, outputs = 1},
.F32X4_CONVERT_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xFA, inputs = 1, outputs = 1},
.F32X4_CONVERT_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xFB, inputs = 1, outputs = 1},
.I32X4_TRUNC_SAT_F64X2_S_ZERO = {prefix = PREFIX_SIMD, opcode = 0xFC, inputs = 1, outputs = 1},
.I32X4_TRUNC_SAT_F64X2_U_ZERO = {prefix = PREFIX_SIMD, opcode = 0xFD, inputs = 1, outputs = 1},
.F64X2_CONVERT_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xFE, inputs = 1, outputs = 1},
.F64X2_CONVERT_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xFF, inputs = 1, outputs = 1},
.I8X16_RELAXED_SWIZZLE = {prefix = PREFIX_SIMD, opcode = 0x100, inputs = 2, outputs = 1},
.I32X4_RELAXED_TRUNC_F32X4_S = {prefix = PREFIX_SIMD, opcode = 0x101, inputs = 1, outputs = 1},
.I32X4_RELAXED_TRUNC_F32X4_U = {prefix = PREFIX_SIMD, opcode = 0x102, inputs = 1, outputs = 1},
.I32X4_RELAXED_TRUNC_F64X2_S_ZERO = {prefix = PREFIX_SIMD, opcode = 0x103, inputs = 1, outputs = 1},
.I32X4_RELAXED_TRUNC_F64X2_U_ZERO = {prefix = PREFIX_SIMD, opcode = 0x104, inputs = 1, outputs = 1},
.F32X4_RELAXED_MADD = {prefix = PREFIX_SIMD, opcode = 0x105, inputs = 3, outputs = 1},
.F32X4_RELAXED_NMADD = {prefix = PREFIX_SIMD, opcode = 0x106, inputs = 3, outputs = 1},
.F64X2_RELAXED_MADD = {prefix = PREFIX_SIMD, opcode = 0x107, inputs = 3, outputs = 1},
.F64X2_RELAXED_NMADD = {prefix = PREFIX_SIMD, opcode = 0x108, inputs = 3, outputs = 1},
.I8X16_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x109, inputs = 3, outputs = 1},
.I16X8_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10A, inputs = 3, outputs = 1},
.I32X4_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10B, inputs = 3, outputs = 1},
.I64X2_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10C, inputs = 3, outputs = 1},
.F32X4_RELAXED_MIN = {prefix = PREFIX_SIMD, opcode = 0x10D, inputs = 2, outputs = 1},
.F32X4_RELAXED_MAX = {prefix = PREFIX_SIMD, opcode = 0x10E, inputs = 2, outputs = 1},
.F64X2_RELAXED_MIN = {prefix = PREFIX_SIMD, opcode = 0x10F, inputs = 2, outputs = 1},
.F64X2_RELAXED_MAX = {prefix = PREFIX_SIMD, opcode = 0x110, inputs = 2, outputs = 1},
.I16X8_RELAXED_Q15MULR_S = {prefix = PREFIX_SIMD, opcode = 0x111, inputs = 2, outputs = 1},
.I16X8_RELAXED_DOT_I8X16_I7X16_S = {prefix = PREFIX_SIMD, opcode = 0x112, inputs = 2, outputs = 1},
.I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S = {prefix = PREFIX_SIMD, opcode = 0x113, inputs = 3, outputs = 1},
// ------------------------------------------ 0xFE threads / atomics prefix
.MEMORY_ATOMIC_NOTIFY = {prefix = PREFIX_ATOM, opcode = 0x00, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.MEMORY_ATOMIC_WAIT32 = {prefix = PREFIX_ATOM, opcode = 0x01, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.MEMORY_ATOMIC_WAIT64 = {prefix = PREFIX_ATOM, opcode = 0x02, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.ATOMIC_FENCE = {prefix = PREFIX_ATOM, opcode = 0x03, imm = {.ZERO_BYTE, .NONE}, inputs = 0, outputs = 0},
.I32_ATOMIC_LOAD = {prefix = PREFIX_ATOM, opcode = 0x10, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_ATOMIC_LOAD = {prefix = PREFIX_ATOM, opcode = 0x11, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_ATOMIC_LOAD8_U = {prefix = PREFIX_ATOM, opcode = 0x12, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_ATOMIC_LOAD16_U = {prefix = PREFIX_ATOM, opcode = 0x13, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_ATOMIC_LOAD8_U = {prefix = PREFIX_ATOM, opcode = 0x14, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_ATOMIC_LOAD16_U = {prefix = PREFIX_ATOM, opcode = 0x15, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I64_ATOMIC_LOAD32_U = {prefix = PREFIX_ATOM, opcode = 0x16, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 1, outputs = 1},
.I32_ATOMIC_STORE = {prefix = PREFIX_ATOM, opcode = 0x17, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_ATOMIC_STORE = {prefix = PREFIX_ATOM, opcode = 0x18, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I32_ATOMIC_STORE8 = {prefix = PREFIX_ATOM, opcode = 0x19, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I32_ATOMIC_STORE16 = {prefix = PREFIX_ATOM, opcode = 0x1A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_ATOMIC_STORE8 = {prefix = PREFIX_ATOM, opcode = 0x1B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_ATOMIC_STORE16 = {prefix = PREFIX_ATOM, opcode = 0x1C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I64_ATOMIC_STORE32 = {prefix = PREFIX_ATOM, opcode = 0x1D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 0},
.I32_ATOMIC_RMW_ADD = {prefix = PREFIX_ATOM, opcode = 0x1E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_ADD = {prefix = PREFIX_ATOM, opcode = 0x1F, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x20, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x21, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x22, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x23, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x24, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_SUB = {prefix = PREFIX_ATOM, opcode = 0x25, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_SUB = {prefix = PREFIX_ATOM, opcode = 0x26, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x27, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x28, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x29, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x2A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x2B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_AND = {prefix = PREFIX_ATOM, opcode = 0x2C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_AND = {prefix = PREFIX_ATOM, opcode = 0x2D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_AND_U = {prefix = PREFIX_ATOM, opcode = 0x2E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_AND_U = {prefix = PREFIX_ATOM, opcode = 0x2F, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_AND_U = {prefix = PREFIX_ATOM, opcode = 0x30, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_AND_U = {prefix = PREFIX_ATOM, opcode = 0x31, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_AND_U = {prefix = PREFIX_ATOM, opcode = 0x32, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_OR = {prefix = PREFIX_ATOM, opcode = 0x33, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_OR = {prefix = PREFIX_ATOM, opcode = 0x34, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_OR_U = {prefix = PREFIX_ATOM, opcode = 0x35, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_OR_U = {prefix = PREFIX_ATOM, opcode = 0x36, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_OR_U = {prefix = PREFIX_ATOM, opcode = 0x37, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_OR_U = {prefix = PREFIX_ATOM, opcode = 0x38, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_OR_U = {prefix = PREFIX_ATOM, opcode = 0x39, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_XOR = {prefix = PREFIX_ATOM, opcode = 0x3A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_XOR = {prefix = PREFIX_ATOM, opcode = 0x3B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3F, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x40, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_XCHG = {prefix = PREFIX_ATOM, opcode = 0x41, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW_XCHG = {prefix = PREFIX_ATOM, opcode = 0x42, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW8_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x43, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW16_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x44, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW8_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x45, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW16_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x46, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I64_ATOMIC_RMW32_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x47, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 2, outputs = 1},
.I32_ATOMIC_RMW_CMPXCHG = {prefix = PREFIX_ATOM, opcode = 0x48, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I64_ATOMIC_RMW_CMPXCHG = {prefix = PREFIX_ATOM, opcode = 0x49, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I32_ATOMIC_RMW8_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4A, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I32_ATOMIC_RMW16_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4B, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I64_ATOMIC_RMW8_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4C, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I64_ATOMIC_RMW16_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4D, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
.I64_ATOMIC_RMW32_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4E, imm = {.MEMARG, .NONE}, flags = MEM, inputs = 3, outputs = 1},
}
// Per-mnemonic encode form. Returns a pointer into the rodata table.
@(private, require_results)
encoding_form :: #force_inline proc "contextless" (m: Mnemonic) -> ^Encoding {
return &ENCODING_TABLE[m]
}

View File

@@ -1,239 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
import "core:rexcode/isa"
// =============================================================================
// WebAssembly ENCODING FUNDAMENTALS
// =============================================================================
//
// An instruction is: [prefix?] opcode immediate*
//
// * `prefix` is 0 for the single-byte core opcodes, or one of 0xFC (misc),
// 0xFD (SIMD), 0xFE (threads). When present, the *sub*-opcode that
// follows is an unsigned LEB128 (so SIMD's 0..275 fit).
// * Integer immediates use LEB128 (unsigned for indices/alignment, signed
// for i32.const/i64.const and the s33 blocktype).
// * Float constants are raw little-endian IEEE-754 (4 or 8 bytes).
//
// There is at most one encoding form per mnemonic, so dispatch is a direct
// `ENCODING_TABLE[mnemonic]` lookup (O(1)) rather than the operand-shape
// scan the variable-form arches (x86) need. The immediate layout is described
// declaratively by `imm: [2]Imm_Kind`, walked in order by the encoder and
// decoder.
Error :: isa.Error
Label_Definition :: isa.Label_Definition
LABEL_UNDEFINED :: isa.LABEL_UNDEFINED
// Relocation / Relocation_Type live in reloc.odin (per-arch by design).
// Opcode-space prefix bytes.
PREFIX_NONE :: u8(0x00)
PREFIX_MISC :: u8(0xFC) // saturating truncation, bulk memory/table
PREFIX_SIMD :: u8(0xFD) // vector (v128)
PREFIX_ATOM :: u8(0xFE) // threads / atomics
Encoding_Flags :: bit_field u8 {
control: bool | 1, // structured control flow (block/loop/if/else/end/br*)
memory: bool | 1, // touches linear memory
_: u8 | 6,
}
// How one immediate field is laid down after the opcode.
Imm_Kind :: enum u8 {
NONE,
BLOCKTYPE, // signed LEB128 s33 (negative valtype byte, or type index)
I32, // signed LEB128 (i32.const)
I64, // signed LEB128 (i64.const)
F32, // 4 little-endian bytes
F64, // 8 little-endian bytes
IDX, // unsigned LEB128 index (space comes from the operand)
MEMARG, // unsigned LEB128 align, then unsigned LEB128 offset
REFTYPE, // single value-type byte (ref.null)
BR_TABLE, // unsigned LEB128 count, that many label depths, default depth
ZERO_BYTE, // a single reserved 0x00 byte (memidx placeholders)
LANE, // single byte lane index (SIMD extract/replace/load/store lane)
LANES16, // sixteen raw bytes (v128.const value / i8x16.shuffle mask), from Instruction.bytes
}
Encoding :: struct #packed {
mnemonic: Mnemonic, // 2 -- redundant w/ table index, kept for parity
prefix: u8, // 1 -- PREFIX_NONE / PREFIX_MISC / PREFIX_SIMD / PREFIX_ATOM
opcode: u16, // 2 -- primary opcode, or sub-opcode within a prefix group (SIMD reaches 0x113)
imm: [2]Imm_Kind, // 2 -- immediate layout, walked in order
flags: Encoding_Flags, // 1
inputs: i8, // 1 -- stack operands consumed (the arity in); -1 when it varies
outputs: i8, // 1 -- stack results produced (the arity out); -1 when it varies
}
#assert(size_of(Encoding) == 10)
// `inputs`/`outputs` give the fixed stack arity of an instruction: how many
// values it pops and how many it pushes. They are -1 when the count is not a
// constant of the opcode -- i.e. it depends on a type/blocktype immediate or on
// the surrounding control frame. That covers `call`/`call_indirect` (the callee
// or referenced signature), the structured control ops `block`/`loop`/`if`
// (their blocktype) plus `else`/`end`, the branches `br`/`br_if`/`br_table` and
// `return` (the target/result types, and stack-polymorphism), and the
// stack-polymorphic `unreachable`. Everything else is a fixed (inputs, outputs).
// =============================================================================
// LEB128 + little-endian primitives (shared by encoder and decoder)
// =============================================================================
// Unsigned LEB128. Advances `*offset`. Caller guarantees buffer space.
write_uleb :: #force_inline proc "contextless" (code: []u8, offset: ^u32, value: u64) {
v := value
for {
b := u8(v & 0x7F)
v >>= 7
if v != 0 {
b |= 0x80
}
code[offset^] = b
offset^ += 1
if v == 0 {
break
}
}
}
// Signed LEB128. Advances `*offset`.
write_sleb :: #force_inline proc "contextless" (code: []u8, offset: ^u32, value: i64) {
v := value
for {
b := u8(v & 0x7F)
v >>= 7 // arithmetic shift on signed value sign-extends
done := (v == 0 && (b & 0x40) == 0) || (v == -1 && (b & 0x40) != 0)
if !done {
b |= 0x80
}
code[offset^] = b
offset^ += 1
if done {
break
}
}
}
// Fixed 5-byte unsigned LEB128 (relocatable placeholder for 32-bit indices).
write_uleb_padded5 :: #force_inline proc "contextless" (code: []u8, offset: ^u32, value: u64) {
v := value
for i := 0; i < 5 && offset^ < u32(len(code)); i += 1 {
b := u8(v & 0x7F)
v >>= 7
if i != 4 {
b |= 0x80
}
code[offset^] = b
offset^ += 1
}
}
@(require_results)
uleb_size :: #force_inline proc "contextless" (value: u64) -> u32 {
v := value
n: u32 = 1
for /**/; v >= 0x80; n += 1 {
v >>= 7
}
return n
}
@(require_results)
sleb_size :: #force_inline proc "contextless" (value: i64) -> u32 {
v := value
n: u32 = 0
for {
b := u8(v & 0x7F)
v >>= 7
n += 1
if (v == 0 && (b & 0x40) == 0) || (v == -1 && (b & 0x40) != 0) {
break
}
}
return n
}
// Read unsigned LEB128 starting at `*offset`; advances it. `ok` is false on
// truncation. Reads at most `max` bytes (10 covers u64).
@(require_results)
read_uleb :: #force_inline proc "contextless" (data: []u8, offset: ^u32) -> (value: u64, ok: bool) {
shift: uint = 0
for i := 0; i < 10 && offset^ < u32(len(data)); i += 1 {
b := data[offset^]
offset^ += 1
value |= u64(b & 0x7F) << shift
if b & 0x80 == 0 {
return value, true
}
shift += 7
}
return 0, false
}
// Read signed LEB128 starting at `*offset`; advances it.
@(require_results)
read_sleb :: #force_inline proc "contextless" (data: []u8, offset: ^u32) -> (value: i64, ok: bool) {
shift: uint = 0
b: u8 = 0
for i := 0; i < 10 && offset^ < u32(len(data)); i += 1 {
b = data[offset^]
offset^ += 1
value |= i64(b & 0x7F) << shift
shift += 7
if b & 0x80 == 0 {
break
}
}
if shift < 64 && (b & 0x40) != 0 {
value |= -(i64(1) << shift)
}
ok = true
return
}
write_u32_block :: #force_inline proc(code: []u8, offset: ^u32, v: u32) {
assert(offset^+ 4 <= u32(len(code)))
code[offset^+0] = u8(v)
code[offset^+1] = u8(v >> 8)
code[offset^+2] = u8(v >> 16)
code[offset^+3] = u8(v >> 24)
offset^ += 4
}
write_u64_block :: #force_inline proc(code: []u8, offset: ^u32, v: u64) {
assert(offset^+ 8 <= u32(len(code)))
for i in u32(0)..<8 {
code[offset^+i] = u8(v >> (8 * i))
}
offset^ += 8
}
@(require_results)
read_u32_block :: #force_inline proc "contextless" (data: []u8, offset: ^u32) -> (u32, bool) {
if offset^ + 4 > u32(len(data)) {
return 0, false
}
v := u32(data[offset^+0]) |
u32(data[offset^+1])<<8 |
u32(data[offset^+2])<<16 |
u32(data[offset^+3])<<24
offset^ += 4
return v, true
}
@(require_results)
read_u64_block :: #force_inline proc "contextless" (data: []u8, offset: ^u32) -> (u64, bool) {
if offset^ + 8 > u32(len(data)) {
return 0, false
}
v: u64 = 0
for i in u32(0)..<8 {
v |= u64(data[offset^+i]) << (8 * i)
}
offset^ += 8
return v, true
}

View File

@@ -1,151 +0,0 @@
// 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))
}

View File

@@ -1,471 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly MNEMONICS
// =============================================================================
//
// Coverage:
// - WebAssembly 1.0 (MVP) core: control flow, parametric, variable,
// memory, numeric (i32/i64/f32/f64) and conversion instructions.
// - The sign-extension operators (0xC0..0xC4).
// - Reference types ref.null / ref.is_null / ref.func (0xD0..0xD2).
// - The 0xFC misc prefix group: saturating float->int truncation plus the
// bulk memory / table operators (memory.init/copy/fill, table.*, ...).
// - The 0xFD SIMD (fixed-width + relaxed) vector group (v128.*, i8x16.*, ...).
// - The 0xFE threads / atomics group (atomic.fence, *.atomic.load/store/rmw*,
// memory.atomic.notify/wait*).
//
// Per the cross-arch contract: `enum u16`, `INVALID = 0`.
Mnemonic :: enum u16 {
INVALID = 0,
// ------------------------------------------------------------------ control
UNREACHABLE, NOP,
BLOCK, LOOP, IF, ELSE, END,
BR, BR_IF, BR_TABLE,
RETURN, CALL, CALL_INDIRECT,
RETURN_CALL, RETURN_CALL_INDIRECT,
// -------------------------------------------------------------- parametric
DROP, SELECT,
// ---------------------------------------------------------------- variable
LOCAL_GET, LOCAL_SET, LOCAL_TEE,
GLOBAL_GET, GLOBAL_SET,
// ------------------------------------------------------------------- memory
I32_LOAD, I64_LOAD, F32_LOAD, F64_LOAD,
I32_LOAD8_S, I32_LOAD8_U, I32_LOAD16_S, I32_LOAD16_U,
I64_LOAD8_S, I64_LOAD8_U, I64_LOAD16_S, I64_LOAD16_U, I64_LOAD32_S, I64_LOAD32_U,
I32_STORE, I64_STORE, F32_STORE, F64_STORE,
I32_STORE8, I32_STORE16,
I64_STORE8, I64_STORE16, I64_STORE32,
MEMORY_SIZE, MEMORY_GROW,
// ----------------------------------------------------------------- numeric
I32_CONST, I64_CONST, F32_CONST, F64_CONST,
// i32 comparison
I32_EQZ, I32_EQ, I32_NE, I32_LT_S, I32_LT_U, I32_GT_S, I32_GT_U,
I32_LE_S, I32_LE_U, I32_GE_S, I32_GE_U,
// i64 comparison
I64_EQZ, I64_EQ, I64_NE, I64_LT_S, I64_LT_U, I64_GT_S, I64_GT_U,
I64_LE_S, I64_LE_U, I64_GE_S, I64_GE_U,
// f32 comparison
F32_EQ, F32_NE, F32_LT, F32_GT, F32_LE, F32_GE,
// f64 comparison
F64_EQ, F64_NE, F64_LT, F64_GT, F64_LE, F64_GE,
// i32 arithmetic
I32_CLZ, I32_CTZ, I32_POPCNT,
I32_ADD, I32_SUB, I32_MUL, I32_DIV_S, I32_DIV_U, I32_REM_S, I32_REM_U,
I32_AND, I32_OR, I32_XOR, I32_SHL, I32_SHR_S, I32_SHR_U, I32_ROTL, I32_ROTR,
// i64 arithmetic
I64_CLZ, I64_CTZ, I64_POPCNT,
I64_ADD, I64_SUB, I64_MUL, I64_DIV_S, I64_DIV_U, I64_REM_S, I64_REM_U,
I64_AND, I64_OR, I64_XOR, I64_SHL, I64_SHR_S, I64_SHR_U, I64_ROTL, I64_ROTR,
// f32 arithmetic
F32_ABS, F32_NEG, F32_CEIL, F32_FLOOR, F32_TRUNC, F32_NEAREST, F32_SQRT,
F32_ADD, F32_SUB, F32_MUL, F32_DIV, F32_MIN, F32_MAX, F32_COPYSIGN,
// f64 arithmetic
F64_ABS, F64_NEG, F64_CEIL, F64_FLOOR, F64_TRUNC, F64_NEAREST, F64_SQRT,
F64_ADD, F64_SUB, F64_MUL, F64_DIV, F64_MIN, F64_MAX, F64_COPYSIGN,
// conversions
I32_WRAP_I64,
I32_TRUNC_F32_S, I32_TRUNC_F32_U, I32_TRUNC_F64_S, I32_TRUNC_F64_U,
I64_EXTEND_I32_S, I64_EXTEND_I32_U,
I64_TRUNC_F32_S, I64_TRUNC_F32_U, I64_TRUNC_F64_S, I64_TRUNC_F64_U,
F32_CONVERT_I32_S, F32_CONVERT_I32_U, F32_CONVERT_I64_S, F32_CONVERT_I64_U, F32_DEMOTE_F64,
F64_CONVERT_I32_S, F64_CONVERT_I32_U, F64_CONVERT_I64_S, F64_CONVERT_I64_U, F64_PROMOTE_F32,
I32_REINTERPRET_F32, I64_REINTERPRET_F64, F32_REINTERPRET_I32, F64_REINTERPRET_I64,
// sign-extension operators (0xC0..0xC4)
I32_EXTEND8_S, I32_EXTEND16_S,
I64_EXTEND8_S, I64_EXTEND16_S, I64_EXTEND32_S,
// reference types
REF_NULL, REF_IS_NULL, REF_FUNC,
// ------------------------------------------------------- 0xFC misc prefix
// saturating truncation
I32_TRUNC_SAT_F32_S, I32_TRUNC_SAT_F32_U, I32_TRUNC_SAT_F64_S, I32_TRUNC_SAT_F64_U,
I64_TRUNC_SAT_F32_S, I64_TRUNC_SAT_F32_U, I64_TRUNC_SAT_F64_S, I64_TRUNC_SAT_F64_U,
// bulk memory & table
MEMORY_INIT, DATA_DROP, MEMORY_COPY, MEMORY_FILL,
TABLE_INIT, ELEM_DROP, TABLE_COPY, TABLE_GROW, TABLE_SIZE, TABLE_FILL,
// ----------------------------------------------- 0xFD SIMD (v128) prefix
V128_LOAD, V128_LOAD8X8_S, V128_LOAD8X8_U,
V128_LOAD16X4_S, V128_LOAD16X4_U, V128_LOAD32X2_S,
V128_LOAD32X2_U, V128_LOAD8_SPLAT, V128_LOAD16_SPLAT,
V128_LOAD32_SPLAT, V128_LOAD64_SPLAT, V128_STORE,
V128_CONST, I8X16_SHUFFLE, I8X16_SWIZZLE,
I8X16_SPLAT, I16X8_SPLAT, I32X4_SPLAT,
I64X2_SPLAT, F32X4_SPLAT, F64X2_SPLAT,
I8X16_EXTRACT_LANE_S, I8X16_EXTRACT_LANE_U, I8X16_REPLACE_LANE,
I16X8_EXTRACT_LANE_S, I16X8_EXTRACT_LANE_U, I16X8_REPLACE_LANE,
I32X4_EXTRACT_LANE, I32X4_REPLACE_LANE, I64X2_EXTRACT_LANE,
I64X2_REPLACE_LANE, F32X4_EXTRACT_LANE, F32X4_REPLACE_LANE,
F64X2_EXTRACT_LANE, F64X2_REPLACE_LANE, I8X16_EQ,
I8X16_NE, I8X16_LT_S, I8X16_LT_U,
I8X16_GT_S, I8X16_GT_U, I8X16_LE_S,
I8X16_LE_U, I8X16_GE_S, I8X16_GE_U,
I16X8_EQ, I16X8_NE, I16X8_LT_S,
I16X8_LT_U, I16X8_GT_S, I16X8_GT_U,
I16X8_LE_S, I16X8_LE_U, I16X8_GE_S,
I16X8_GE_U, I32X4_EQ, I32X4_NE,
I32X4_LT_S, I32X4_LT_U, I32X4_GT_S,
I32X4_GT_U, I32X4_LE_S, I32X4_LE_U,
I32X4_GE_S, I32X4_GE_U, F32X4_EQ,
F32X4_NE, F32X4_LT, F32X4_GT,
F32X4_LE, F32X4_GE, F64X2_EQ,
F64X2_NE, F64X2_LT, F64X2_GT,
F64X2_LE, F64X2_GE, V128_NOT,
V128_AND, V128_ANDNOT, V128_OR,
V128_XOR, V128_BITSELECT, V128_ANY_TRUE,
V128_LOAD8_LANE, V128_LOAD16_LANE, V128_LOAD32_LANE,
V128_LOAD64_LANE, V128_STORE8_LANE, V128_STORE16_LANE,
V128_STORE32_LANE, V128_STORE64_LANE, V128_LOAD32_ZERO,
V128_LOAD64_ZERO, F32X4_DEMOTE_F64X2_ZERO, F64X2_PROMOTE_LOW_F32X4,
I8X16_ABS, I8X16_NEG, I8X16_POPCNT,
I8X16_ALL_TRUE, I8X16_BITMASK, I8X16_NARROW_I16X8_S,
I8X16_NARROW_I16X8_U, F32X4_CEIL, F32X4_FLOOR,
F32X4_TRUNC, F32X4_NEAREST, I8X16_SHL,
I8X16_SHR_S, I8X16_SHR_U, I8X16_ADD,
I8X16_ADD_SAT_S, I8X16_ADD_SAT_U, I8X16_SUB,
I8X16_SUB_SAT_S, I8X16_SUB_SAT_U, F64X2_CEIL,
F64X2_FLOOR, I8X16_MIN_S, I8X16_MIN_U,
I8X16_MAX_S, I8X16_MAX_U, F64X2_TRUNC,
I8X16_AVGR_U, I16X8_EXTADD_PAIRWISE_I8X16_S, I16X8_EXTADD_PAIRWISE_I8X16_U,
I32X4_EXTADD_PAIRWISE_I16X8_S, I32X4_EXTADD_PAIRWISE_I16X8_U, I16X8_ABS,
I16X8_NEG, I16X8_Q15MULR_SAT_S, I16X8_ALL_TRUE,
I16X8_BITMASK, I16X8_NARROW_I32X4_S, I16X8_NARROW_I32X4_U,
I16X8_EXTEND_LOW_I8X16_S, I16X8_EXTEND_HIGH_I8X16_S, I16X8_EXTEND_LOW_I8X16_U,
I16X8_EXTEND_HIGH_I8X16_U, I16X8_SHL, I16X8_SHR_S,
I16X8_SHR_U, I16X8_ADD, I16X8_ADD_SAT_S,
I16X8_ADD_SAT_U, I16X8_SUB, I16X8_SUB_SAT_S,
I16X8_SUB_SAT_U, F64X2_NEAREST, I16X8_MUL,
I16X8_MIN_S, I16X8_MIN_U, I16X8_MAX_S,
I16X8_MAX_U, I16X8_AVGR_U, I16X8_EXTMUL_LOW_I8X16_S,
I16X8_EXTMUL_HIGH_I8X16_S, I16X8_EXTMUL_LOW_I8X16_U, I16X8_EXTMUL_HIGH_I8X16_U,
I32X4_ABS, I32X4_NEG, I32X4_ALL_TRUE,
I32X4_BITMASK, I32X4_EXTEND_LOW_I16X8_S, I32X4_EXTEND_HIGH_I16X8_S,
I32X4_EXTEND_LOW_I16X8_U, I32X4_EXTEND_HIGH_I16X8_U, I32X4_SHL,
I32X4_SHR_S, I32X4_SHR_U, I32X4_ADD,
I32X4_SUB, I32X4_MUL, I32X4_MIN_S,
I32X4_MIN_U, I32X4_MAX_S, I32X4_MAX_U,
I32X4_DOT_I16X8_S, I32X4_EXTMUL_LOW_I16X8_S, I32X4_EXTMUL_HIGH_I16X8_S,
I32X4_EXTMUL_LOW_I16X8_U, I32X4_EXTMUL_HIGH_I16X8_U, I64X2_ABS,
I64X2_NEG, I64X2_ALL_TRUE, I64X2_BITMASK,
I64X2_EXTEND_LOW_I32X4_S, I64X2_EXTEND_HIGH_I32X4_S, I64X2_EXTEND_LOW_I32X4_U,
I64X2_EXTEND_HIGH_I32X4_U, I64X2_SHL, I64X2_SHR_S,
I64X2_SHR_U, I64X2_ADD, I64X2_SUB,
I64X2_MUL, I64X2_EQ, I64X2_NE,
I64X2_LT_S, I64X2_GT_S, I64X2_LE_S,
I64X2_GE_S, I64X2_EXTMUL_LOW_I32X4_S, I64X2_EXTMUL_HIGH_I32X4_S,
I64X2_EXTMUL_LOW_I32X4_U, I64X2_EXTMUL_HIGH_I32X4_U, F32X4_ABS,
F32X4_NEG, F32X4_SQRT, F32X4_ADD,
F32X4_SUB, F32X4_MUL, F32X4_DIV,
F32X4_MIN, F32X4_MAX, F32X4_PMIN,
F32X4_PMAX, F64X2_ABS, F64X2_NEG,
F64X2_SQRT, F64X2_ADD, F64X2_SUB,
F64X2_MUL, F64X2_DIV, F64X2_MIN,
F64X2_MAX, F64X2_PMIN, F64X2_PMAX,
I32X4_TRUNC_SAT_F32X4_S, I32X4_TRUNC_SAT_F32X4_U, F32X4_CONVERT_I32X4_S,
F32X4_CONVERT_I32X4_U, I32X4_TRUNC_SAT_F64X2_S_ZERO, I32X4_TRUNC_SAT_F64X2_U_ZERO,
F64X2_CONVERT_LOW_I32X4_S, F64X2_CONVERT_LOW_I32X4_U, I8X16_RELAXED_SWIZZLE,
I32X4_RELAXED_TRUNC_F32X4_S, I32X4_RELAXED_TRUNC_F32X4_U, I32X4_RELAXED_TRUNC_F64X2_S_ZERO,
I32X4_RELAXED_TRUNC_F64X2_U_ZERO, F32X4_RELAXED_MADD, F32X4_RELAXED_NMADD,
F64X2_RELAXED_MADD, F64X2_RELAXED_NMADD, I8X16_RELAXED_LANESELECT,
I16X8_RELAXED_LANESELECT, I32X4_RELAXED_LANESELECT, I64X2_RELAXED_LANESELECT,
F32X4_RELAXED_MIN, F32X4_RELAXED_MAX, F64X2_RELAXED_MIN,
F64X2_RELAXED_MAX, I16X8_RELAXED_Q15MULR_S, I16X8_RELAXED_DOT_I8X16_I7X16_S,
I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S,
// ------------------------------------------ 0xFE threads / atomics prefix
MEMORY_ATOMIC_NOTIFY, MEMORY_ATOMIC_WAIT32, MEMORY_ATOMIC_WAIT64,
ATOMIC_FENCE, I32_ATOMIC_LOAD, I64_ATOMIC_LOAD,
I32_ATOMIC_LOAD8_U, I32_ATOMIC_LOAD16_U, I64_ATOMIC_LOAD8_U,
I64_ATOMIC_LOAD16_U, I64_ATOMIC_LOAD32_U, I32_ATOMIC_STORE,
I64_ATOMIC_STORE, I32_ATOMIC_STORE8, I32_ATOMIC_STORE16,
I64_ATOMIC_STORE8, I64_ATOMIC_STORE16, I64_ATOMIC_STORE32,
I32_ATOMIC_RMW_ADD, I64_ATOMIC_RMW_ADD, I32_ATOMIC_RMW8_ADD_U,
I32_ATOMIC_RMW16_ADD_U, I64_ATOMIC_RMW8_ADD_U, I64_ATOMIC_RMW16_ADD_U,
I64_ATOMIC_RMW32_ADD_U, I32_ATOMIC_RMW_SUB, I64_ATOMIC_RMW_SUB,
I32_ATOMIC_RMW8_SUB_U, I32_ATOMIC_RMW16_SUB_U, I64_ATOMIC_RMW8_SUB_U,
I64_ATOMIC_RMW16_SUB_U, I64_ATOMIC_RMW32_SUB_U, I32_ATOMIC_RMW_AND,
I64_ATOMIC_RMW_AND, I32_ATOMIC_RMW8_AND_U, I32_ATOMIC_RMW16_AND_U,
I64_ATOMIC_RMW8_AND_U, I64_ATOMIC_RMW16_AND_U, I64_ATOMIC_RMW32_AND_U,
I32_ATOMIC_RMW_OR, I64_ATOMIC_RMW_OR, I32_ATOMIC_RMW8_OR_U,
I32_ATOMIC_RMW16_OR_U, I64_ATOMIC_RMW8_OR_U, I64_ATOMIC_RMW16_OR_U,
I64_ATOMIC_RMW32_OR_U, I32_ATOMIC_RMW_XOR, I64_ATOMIC_RMW_XOR,
I32_ATOMIC_RMW8_XOR_U, I32_ATOMIC_RMW16_XOR_U, I64_ATOMIC_RMW8_XOR_U,
I64_ATOMIC_RMW16_XOR_U, I64_ATOMIC_RMW32_XOR_U, I32_ATOMIC_RMW_XCHG,
I64_ATOMIC_RMW_XCHG, I32_ATOMIC_RMW8_XCHG_U, I32_ATOMIC_RMW16_XCHG_U,
I64_ATOMIC_RMW8_XCHG_U, I64_ATOMIC_RMW16_XCHG_U, I64_ATOMIC_RMW32_XCHG_U,
I32_ATOMIC_RMW_CMPXCHG, I64_ATOMIC_RMW_CMPXCHG, I32_ATOMIC_RMW8_CMPXCHG_U,
I32_ATOMIC_RMW16_CMPXCHG_U, I64_ATOMIC_RMW8_CMPXCHG_U, I64_ATOMIC_RMW16_CMPXCHG_U,
I64_ATOMIC_RMW32_CMPXCHG_U,
}
// -----------------------------------------------------------------------------
// Canonical WAT text names (per-arch formatting -- WASM mixes '.' and '_' in
// ways no single transform of the enum name captures, so the names are
// explicit). Indexed by Mnemonic; INVALID maps to "<invalid>".
// -----------------------------------------------------------------------------
@(rodata)
MNEMONIC_NAMES := [Mnemonic]string{
.INVALID = "<invalid>",
.UNREACHABLE = "unreachable", .NOP = "nop",
.BLOCK = "block", .LOOP = "loop", .IF = "if", .ELSE = "else", .END = "end",
.BR = "br", .BR_IF = "br_if", .BR_TABLE = "br_table",
.RETURN = "return", .CALL = "call", .CALL_INDIRECT = "call_indirect",
.RETURN_CALL = "return_call", .RETURN_CALL_INDIRECT = "return_call_indirect",
.DROP = "drop", .SELECT = "select",
.LOCAL_GET = "local.get", .LOCAL_SET = "local.set", .LOCAL_TEE = "local.tee",
.GLOBAL_GET = "global.get", .GLOBAL_SET = "global.set",
.I32_LOAD = "i32.load", .I64_LOAD = "i64.load", .F32_LOAD = "f32.load", .F64_LOAD = "f64.load",
.I32_LOAD8_S = "i32.load8_s", .I32_LOAD8_U = "i32.load8_u",
.I32_LOAD16_S = "i32.load16_s", .I32_LOAD16_U = "i32.load16_u",
.I64_LOAD8_S = "i64.load8_s", .I64_LOAD8_U = "i64.load8_u",
.I64_LOAD16_S = "i64.load16_s", .I64_LOAD16_U = "i64.load16_u",
.I64_LOAD32_S = "i64.load32_s", .I64_LOAD32_U = "i64.load32_u",
.I32_STORE = "i32.store", .I64_STORE = "i64.store", .F32_STORE = "f32.store", .F64_STORE = "f64.store",
.I32_STORE8 = "i32.store8", .I32_STORE16 = "i32.store16",
.I64_STORE8 = "i64.store8", .I64_STORE16 = "i64.store16", .I64_STORE32 = "i64.store32",
.MEMORY_SIZE = "memory.size", .MEMORY_GROW = "memory.grow",
.I32_CONST = "i32.const", .I64_CONST = "i64.const", .F32_CONST = "f32.const", .F64_CONST = "f64.const",
.I32_EQZ = "i32.eqz", .I32_EQ = "i32.eq", .I32_NE = "i32.ne",
.I32_LT_S = "i32.lt_s", .I32_LT_U = "i32.lt_u", .I32_GT_S = "i32.gt_s", .I32_GT_U = "i32.gt_u",
.I32_LE_S = "i32.le_s", .I32_LE_U = "i32.le_u", .I32_GE_S = "i32.ge_s", .I32_GE_U = "i32.ge_u",
.I64_EQZ = "i64.eqz", .I64_EQ = "i64.eq", .I64_NE = "i64.ne",
.I64_LT_S = "i64.lt_s", .I64_LT_U = "i64.lt_u", .I64_GT_S = "i64.gt_s", .I64_GT_U = "i64.gt_u",
.I64_LE_S = "i64.le_s", .I64_LE_U = "i64.le_u", .I64_GE_S = "i64.ge_s", .I64_GE_U = "i64.ge_u",
.F32_EQ = "f32.eq", .F32_NE = "f32.ne", .F32_LT = "f32.lt", .F32_GT = "f32.gt", .F32_LE = "f32.le", .F32_GE = "f32.ge",
.F64_EQ = "f64.eq", .F64_NE = "f64.ne", .F64_LT = "f64.lt", .F64_GT = "f64.gt", .F64_LE = "f64.le", .F64_GE = "f64.ge",
.I32_CLZ = "i32.clz", .I32_CTZ = "i32.ctz", .I32_POPCNT = "i32.popcnt",
.I32_ADD = "i32.add", .I32_SUB = "i32.sub", .I32_MUL = "i32.mul",
.I32_DIV_S = "i32.div_s", .I32_DIV_U = "i32.div_u", .I32_REM_S = "i32.rem_s", .I32_REM_U = "i32.rem_u",
.I32_AND = "i32.and", .I32_OR = "i32.or", .I32_XOR = "i32.xor",
.I32_SHL = "i32.shl", .I32_SHR_S = "i32.shr_s", .I32_SHR_U = "i32.shr_u", .I32_ROTL = "i32.rotl", .I32_ROTR = "i32.rotr",
.I64_CLZ = "i64.clz", .I64_CTZ = "i64.ctz", .I64_POPCNT = "i64.popcnt",
.I64_ADD = "i64.add", .I64_SUB = "i64.sub", .I64_MUL = "i64.mul",
.I64_DIV_S = "i64.div_s", .I64_DIV_U = "i64.div_u", .I64_REM_S = "i64.rem_s", .I64_REM_U = "i64.rem_u",
.I64_AND = "i64.and", .I64_OR = "i64.or", .I64_XOR = "i64.xor",
.I64_SHL = "i64.shl", .I64_SHR_S = "i64.shr_s", .I64_SHR_U = "i64.shr_u", .I64_ROTL = "i64.rotl", .I64_ROTR = "i64.rotr",
.F32_ABS = "f32.abs", .F32_NEG = "f32.neg", .F32_CEIL = "f32.ceil", .F32_FLOOR = "f32.floor",
.F32_TRUNC = "f32.trunc", .F32_NEAREST = "f32.nearest", .F32_SQRT = "f32.sqrt",
.F32_ADD = "f32.add", .F32_SUB = "f32.sub", .F32_MUL = "f32.mul", .F32_DIV = "f32.div",
.F32_MIN = "f32.min", .F32_MAX = "f32.max", .F32_COPYSIGN = "f32.copysign",
.F64_ABS = "f64.abs", .F64_NEG = "f64.neg", .F64_CEIL = "f64.ceil", .F64_FLOOR = "f64.floor",
.F64_TRUNC = "f64.trunc", .F64_NEAREST = "f64.nearest", .F64_SQRT = "f64.sqrt",
.F64_ADD = "f64.add", .F64_SUB = "f64.sub", .F64_MUL = "f64.mul", .F64_DIV = "f64.div",
.F64_MIN = "f64.min", .F64_MAX = "f64.max", .F64_COPYSIGN = "f64.copysign",
.I32_WRAP_I64 = "i32.wrap_i64",
.I32_TRUNC_F32_S = "i32.trunc_f32_s", .I32_TRUNC_F32_U = "i32.trunc_f32_u",
.I32_TRUNC_F64_S = "i32.trunc_f64_s", .I32_TRUNC_F64_U = "i32.trunc_f64_u",
.I64_EXTEND_I32_S = "i64.extend_i32_s", .I64_EXTEND_I32_U = "i64.extend_i32_u",
.I64_TRUNC_F32_S = "i64.trunc_f32_s", .I64_TRUNC_F32_U = "i64.trunc_f32_u",
.I64_TRUNC_F64_S = "i64.trunc_f64_s", .I64_TRUNC_F64_U = "i64.trunc_f64_u",
.F32_CONVERT_I32_S = "f32.convert_i32_s", .F32_CONVERT_I32_U = "f32.convert_i32_u",
.F32_CONVERT_I64_S = "f32.convert_i64_s", .F32_CONVERT_I64_U = "f32.convert_i64_u",
.F32_DEMOTE_F64 = "f32.demote_f64",
.F64_CONVERT_I32_S = "f64.convert_i32_s", .F64_CONVERT_I32_U = "f64.convert_i32_u",
.F64_CONVERT_I64_S = "f64.convert_i64_s", .F64_CONVERT_I64_U = "f64.convert_i64_u",
.F64_PROMOTE_F32 = "f64.promote_f32",
.I32_REINTERPRET_F32 = "i32.reinterpret_f32", .I64_REINTERPRET_F64 = "i64.reinterpret_f64",
.F32_REINTERPRET_I32 = "f32.reinterpret_i32", .F64_REINTERPRET_I64 = "f64.reinterpret_i64",
.I32_EXTEND8_S = "i32.extend8_s", .I32_EXTEND16_S = "i32.extend16_s",
.I64_EXTEND8_S = "i64.extend8_s", .I64_EXTEND16_S = "i64.extend16_s", .I64_EXTEND32_S = "i64.extend32_s",
.REF_NULL = "ref.null", .REF_IS_NULL = "ref.is_null", .REF_FUNC = "ref.func",
.I32_TRUNC_SAT_F32_S = "i32.trunc_sat_f32_s", .I32_TRUNC_SAT_F32_U = "i32.trunc_sat_f32_u",
.I32_TRUNC_SAT_F64_S = "i32.trunc_sat_f64_s", .I32_TRUNC_SAT_F64_U = "i32.trunc_sat_f64_u",
.I64_TRUNC_SAT_F32_S = "i64.trunc_sat_f32_s", .I64_TRUNC_SAT_F32_U = "i64.trunc_sat_f32_u",
.I64_TRUNC_SAT_F64_S = "i64.trunc_sat_f64_s", .I64_TRUNC_SAT_F64_U = "i64.trunc_sat_f64_u",
.MEMORY_INIT = "memory.init", .DATA_DROP = "data.drop", .MEMORY_COPY = "memory.copy", .MEMORY_FILL = "memory.fill",
.TABLE_INIT = "table.init", .ELEM_DROP = "elem.drop", .TABLE_COPY = "table.copy",
.TABLE_GROW = "table.grow", .TABLE_SIZE = "table.size", .TABLE_FILL = "table.fill",
// SIMD (0xFD)
.V128_LOAD = "v128.load", .V128_LOAD8X8_S = "v128.load8x8_s",
.V128_LOAD8X8_U = "v128.load8x8_u", .V128_LOAD16X4_S = "v128.load16x4_s",
.V128_LOAD16X4_U = "v128.load16x4_u", .V128_LOAD32X2_S = "v128.load32x2_s",
.V128_LOAD32X2_U = "v128.load32x2_u", .V128_LOAD8_SPLAT = "v128.load8_splat",
.V128_LOAD16_SPLAT = "v128.load16_splat", .V128_LOAD32_SPLAT = "v128.load32_splat",
.V128_LOAD64_SPLAT = "v128.load64_splat", .V128_STORE = "v128.store",
.V128_CONST = "v128.const", .I8X16_SHUFFLE = "i8x16.shuffle",
.I8X16_SWIZZLE = "i8x16.swizzle", .I8X16_SPLAT = "i8x16.splat",
.I16X8_SPLAT = "i16x8.splat", .I32X4_SPLAT = "i32x4.splat",
.I64X2_SPLAT = "i64x2.splat", .F32X4_SPLAT = "f32x4.splat",
.F64X2_SPLAT = "f64x2.splat", .I8X16_EXTRACT_LANE_S = "i8x16.extract_lane_s",
.I8X16_EXTRACT_LANE_U = "i8x16.extract_lane_u", .I8X16_REPLACE_LANE = "i8x16.replace_lane",
.I16X8_EXTRACT_LANE_S = "i16x8.extract_lane_s", .I16X8_EXTRACT_LANE_U = "i16x8.extract_lane_u",
.I16X8_REPLACE_LANE = "i16x8.replace_lane", .I32X4_EXTRACT_LANE = "i32x4.extract_lane",
.I32X4_REPLACE_LANE = "i32x4.replace_lane", .I64X2_EXTRACT_LANE = "i64x2.extract_lane",
.I64X2_REPLACE_LANE = "i64x2.replace_lane", .F32X4_EXTRACT_LANE = "f32x4.extract_lane",
.F32X4_REPLACE_LANE = "f32x4.replace_lane", .F64X2_EXTRACT_LANE = "f64x2.extract_lane",
.F64X2_REPLACE_LANE = "f64x2.replace_lane", .I8X16_EQ = "i8x16.eq",
.I8X16_NE = "i8x16.ne", .I8X16_LT_S = "i8x16.lt_s",
.I8X16_LT_U = "i8x16.lt_u", .I8X16_GT_S = "i8x16.gt_s",
.I8X16_GT_U = "i8x16.gt_u", .I8X16_LE_S = "i8x16.le_s",
.I8X16_LE_U = "i8x16.le_u", .I8X16_GE_S = "i8x16.ge_s",
.I8X16_GE_U = "i8x16.ge_u", .I16X8_EQ = "i16x8.eq",
.I16X8_NE = "i16x8.ne", .I16X8_LT_S = "i16x8.lt_s",
.I16X8_LT_U = "i16x8.lt_u", .I16X8_GT_S = "i16x8.gt_s",
.I16X8_GT_U = "i16x8.gt_u", .I16X8_LE_S = "i16x8.le_s",
.I16X8_LE_U = "i16x8.le_u", .I16X8_GE_S = "i16x8.ge_s",
.I16X8_GE_U = "i16x8.ge_u", .I32X4_EQ = "i32x4.eq",
.I32X4_NE = "i32x4.ne", .I32X4_LT_S = "i32x4.lt_s",
.I32X4_LT_U = "i32x4.lt_u", .I32X4_GT_S = "i32x4.gt_s",
.I32X4_GT_U = "i32x4.gt_u", .I32X4_LE_S = "i32x4.le_s",
.I32X4_LE_U = "i32x4.le_u", .I32X4_GE_S = "i32x4.ge_s",
.I32X4_GE_U = "i32x4.ge_u", .F32X4_EQ = "f32x4.eq",
.F32X4_NE = "f32x4.ne", .F32X4_LT = "f32x4.lt",
.F32X4_GT = "f32x4.gt", .F32X4_LE = "f32x4.le",
.F32X4_GE = "f32x4.ge", .F64X2_EQ = "f64x2.eq",
.F64X2_NE = "f64x2.ne", .F64X2_LT = "f64x2.lt",
.F64X2_GT = "f64x2.gt", .F64X2_LE = "f64x2.le",
.F64X2_GE = "f64x2.ge", .V128_NOT = "v128.not",
.V128_AND = "v128.and", .V128_ANDNOT = "v128.andnot",
.V128_OR = "v128.or", .V128_XOR = "v128.xor",
.V128_BITSELECT = "v128.bitselect", .V128_ANY_TRUE = "v128.any_true",
.V128_LOAD8_LANE = "v128.load8_lane", .V128_LOAD16_LANE = "v128.load16_lane",
.V128_LOAD32_LANE = "v128.load32_lane", .V128_LOAD64_LANE = "v128.load64_lane",
.V128_STORE8_LANE = "v128.store8_lane", .V128_STORE16_LANE = "v128.store16_lane",
.V128_STORE32_LANE = "v128.store32_lane", .V128_STORE64_LANE = "v128.store64_lane",
.V128_LOAD32_ZERO = "v128.load32_zero", .V128_LOAD64_ZERO = "v128.load64_zero",
.F32X4_DEMOTE_F64X2_ZERO = "f32x4.demote_f64x2_zero", .F64X2_PROMOTE_LOW_F32X4 = "f64x2.promote_low_f32x4",
.I8X16_ABS = "i8x16.abs", .I8X16_NEG = "i8x16.neg",
.I8X16_POPCNT = "i8x16.popcnt", .I8X16_ALL_TRUE = "i8x16.all_true",
.I8X16_BITMASK = "i8x16.bitmask", .I8X16_NARROW_I16X8_S = "i8x16.narrow_i16x8_s",
.I8X16_NARROW_I16X8_U = "i8x16.narrow_i16x8_u", .F32X4_CEIL = "f32x4.ceil",
.F32X4_FLOOR = "f32x4.floor", .F32X4_TRUNC = "f32x4.trunc",
.F32X4_NEAREST = "f32x4.nearest", .I8X16_SHL = "i8x16.shl",
.I8X16_SHR_S = "i8x16.shr_s", .I8X16_SHR_U = "i8x16.shr_u",
.I8X16_ADD = "i8x16.add", .I8X16_ADD_SAT_S = "i8x16.add_sat_s",
.I8X16_ADD_SAT_U = "i8x16.add_sat_u", .I8X16_SUB = "i8x16.sub",
.I8X16_SUB_SAT_S = "i8x16.sub_sat_s", .I8X16_SUB_SAT_U = "i8x16.sub_sat_u",
.F64X2_CEIL = "f64x2.ceil", .F64X2_FLOOR = "f64x2.floor",
.I8X16_MIN_S = "i8x16.min_s", .I8X16_MIN_U = "i8x16.min_u",
.I8X16_MAX_S = "i8x16.max_s", .I8X16_MAX_U = "i8x16.max_u",
.F64X2_TRUNC = "f64x2.trunc", .I8X16_AVGR_U = "i8x16.avgr_u",
.I16X8_EXTADD_PAIRWISE_I8X16_S = "i16x8.extadd_pairwise_i8x16_s", .I16X8_EXTADD_PAIRWISE_I8X16_U = "i16x8.extadd_pairwise_i8x16_u",
.I32X4_EXTADD_PAIRWISE_I16X8_S = "i32x4.extadd_pairwise_i16x8_s", .I32X4_EXTADD_PAIRWISE_I16X8_U = "i32x4.extadd_pairwise_i16x8_u",
.I16X8_ABS = "i16x8.abs", .I16X8_NEG = "i16x8.neg",
.I16X8_Q15MULR_SAT_S = "i16x8.q15mulr_sat_s", .I16X8_ALL_TRUE = "i16x8.all_true",
.I16X8_BITMASK = "i16x8.bitmask", .I16X8_NARROW_I32X4_S = "i16x8.narrow_i32x4_s",
.I16X8_NARROW_I32X4_U = "i16x8.narrow_i32x4_u", .I16X8_EXTEND_LOW_I8X16_S = "i16x8.extend_low_i8x16_s",
.I16X8_EXTEND_HIGH_I8X16_S = "i16x8.extend_high_i8x16_s", .I16X8_EXTEND_LOW_I8X16_U = "i16x8.extend_low_i8x16_u",
.I16X8_EXTEND_HIGH_I8X16_U = "i16x8.extend_high_i8x16_u", .I16X8_SHL = "i16x8.shl",
.I16X8_SHR_S = "i16x8.shr_s", .I16X8_SHR_U = "i16x8.shr_u",
.I16X8_ADD = "i16x8.add", .I16X8_ADD_SAT_S = "i16x8.add_sat_s",
.I16X8_ADD_SAT_U = "i16x8.add_sat_u", .I16X8_SUB = "i16x8.sub",
.I16X8_SUB_SAT_S = "i16x8.sub_sat_s", .I16X8_SUB_SAT_U = "i16x8.sub_sat_u",
.F64X2_NEAREST = "f64x2.nearest", .I16X8_MUL = "i16x8.mul",
.I16X8_MIN_S = "i16x8.min_s", .I16X8_MIN_U = "i16x8.min_u",
.I16X8_MAX_S = "i16x8.max_s", .I16X8_MAX_U = "i16x8.max_u",
.I16X8_AVGR_U = "i16x8.avgr_u", .I16X8_EXTMUL_LOW_I8X16_S = "i16x8.extmul_low_i8x16_s",
.I16X8_EXTMUL_HIGH_I8X16_S = "i16x8.extmul_high_i8x16_s", .I16X8_EXTMUL_LOW_I8X16_U = "i16x8.extmul_low_i8x16_u",
.I16X8_EXTMUL_HIGH_I8X16_U = "i16x8.extmul_high_i8x16_u", .I32X4_ABS = "i32x4.abs",
.I32X4_NEG = "i32x4.neg", .I32X4_ALL_TRUE = "i32x4.all_true",
.I32X4_BITMASK = "i32x4.bitmask", .I32X4_EXTEND_LOW_I16X8_S = "i32x4.extend_low_i16x8_s",
.I32X4_EXTEND_HIGH_I16X8_S = "i32x4.extend_high_i16x8_s", .I32X4_EXTEND_LOW_I16X8_U = "i32x4.extend_low_i16x8_u",
.I32X4_EXTEND_HIGH_I16X8_U = "i32x4.extend_high_i16x8_u", .I32X4_SHL = "i32x4.shl",
.I32X4_SHR_S = "i32x4.shr_s", .I32X4_SHR_U = "i32x4.shr_u",
.I32X4_ADD = "i32x4.add", .I32X4_SUB = "i32x4.sub",
.I32X4_MUL = "i32x4.mul", .I32X4_MIN_S = "i32x4.min_s",
.I32X4_MIN_U = "i32x4.min_u", .I32X4_MAX_S = "i32x4.max_s",
.I32X4_MAX_U = "i32x4.max_u", .I32X4_DOT_I16X8_S = "i32x4.dot_i16x8_s",
.I32X4_EXTMUL_LOW_I16X8_S = "i32x4.extmul_low_i16x8_s", .I32X4_EXTMUL_HIGH_I16X8_S = "i32x4.extmul_high_i16x8_s",
.I32X4_EXTMUL_LOW_I16X8_U = "i32x4.extmul_low_i16x8_u", .I32X4_EXTMUL_HIGH_I16X8_U = "i32x4.extmul_high_i16x8_u",
.I64X2_ABS = "i64x2.abs", .I64X2_NEG = "i64x2.neg",
.I64X2_ALL_TRUE = "i64x2.all_true", .I64X2_BITMASK = "i64x2.bitmask",
.I64X2_EXTEND_LOW_I32X4_S = "i64x2.extend_low_i32x4_s", .I64X2_EXTEND_HIGH_I32X4_S = "i64x2.extend_high_i32x4_s",
.I64X2_EXTEND_LOW_I32X4_U = "i64x2.extend_low_i32x4_u", .I64X2_EXTEND_HIGH_I32X4_U = "i64x2.extend_high_i32x4_u",
.I64X2_SHL = "i64x2.shl", .I64X2_SHR_S = "i64x2.shr_s",
.I64X2_SHR_U = "i64x2.shr_u", .I64X2_ADD = "i64x2.add",
.I64X2_SUB = "i64x2.sub", .I64X2_MUL = "i64x2.mul",
.I64X2_EQ = "i64x2.eq", .I64X2_NE = "i64x2.ne",
.I64X2_LT_S = "i64x2.lt_s", .I64X2_GT_S = "i64x2.gt_s",
.I64X2_LE_S = "i64x2.le_s", .I64X2_GE_S = "i64x2.ge_s",
.I64X2_EXTMUL_LOW_I32X4_S = "i64x2.extmul_low_i32x4_s", .I64X2_EXTMUL_HIGH_I32X4_S = "i64x2.extmul_high_i32x4_s",
.I64X2_EXTMUL_LOW_I32X4_U = "i64x2.extmul_low_i32x4_u", .I64X2_EXTMUL_HIGH_I32X4_U = "i64x2.extmul_high_i32x4_u",
.F32X4_ABS = "f32x4.abs", .F32X4_NEG = "f32x4.neg",
.F32X4_SQRT = "f32x4.sqrt", .F32X4_ADD = "f32x4.add",
.F32X4_SUB = "f32x4.sub", .F32X4_MUL = "f32x4.mul",
.F32X4_DIV = "f32x4.div", .F32X4_MIN = "f32x4.min",
.F32X4_MAX = "f32x4.max", .F32X4_PMIN = "f32x4.pmin",
.F32X4_PMAX = "f32x4.pmax", .F64X2_ABS = "f64x2.abs",
.F64X2_NEG = "f64x2.neg", .F64X2_SQRT = "f64x2.sqrt",
.F64X2_ADD = "f64x2.add", .F64X2_SUB = "f64x2.sub",
.F64X2_MUL = "f64x2.mul", .F64X2_DIV = "f64x2.div",
.F64X2_MIN = "f64x2.min", .F64X2_MAX = "f64x2.max",
.F64X2_PMIN = "f64x2.pmin", .F64X2_PMAX = "f64x2.pmax",
.I32X4_TRUNC_SAT_F32X4_S = "i32x4.trunc_sat_f32x4_s", .I32X4_TRUNC_SAT_F32X4_U = "i32x4.trunc_sat_f32x4_u",
.F32X4_CONVERT_I32X4_S = "f32x4.convert_i32x4_s", .F32X4_CONVERT_I32X4_U = "f32x4.convert_i32x4_u",
.I32X4_TRUNC_SAT_F64X2_S_ZERO = "i32x4.trunc_sat_f64x2_s_zero", .I32X4_TRUNC_SAT_F64X2_U_ZERO = "i32x4.trunc_sat_f64x2_u_zero",
.F64X2_CONVERT_LOW_I32X4_S = "f64x2.convert_low_i32x4_s", .F64X2_CONVERT_LOW_I32X4_U = "f64x2.convert_low_i32x4_u",
.I8X16_RELAXED_SWIZZLE = "i8x16.relaxed_swizzle", .I32X4_RELAXED_TRUNC_F32X4_S = "i32x4.relaxed_trunc_f32x4_s",
.I32X4_RELAXED_TRUNC_F32X4_U = "i32x4.relaxed_trunc_f32x4_u", .I32X4_RELAXED_TRUNC_F64X2_S_ZERO = "i32x4.relaxed_trunc_f64x2_s_zero",
.I32X4_RELAXED_TRUNC_F64X2_U_ZERO = "i32x4.relaxed_trunc_f64x2_u_zero", .F32X4_RELAXED_MADD = "f32x4.relaxed_madd",
.F32X4_RELAXED_NMADD = "f32x4.relaxed_nmadd", .F64X2_RELAXED_MADD = "f64x2.relaxed_madd",
.F64X2_RELAXED_NMADD = "f64x2.relaxed_nmadd", .I8X16_RELAXED_LANESELECT = "i8x16.relaxed_laneselect",
.I16X8_RELAXED_LANESELECT = "i16x8.relaxed_laneselect", .I32X4_RELAXED_LANESELECT = "i32x4.relaxed_laneselect",
.I64X2_RELAXED_LANESELECT = "i64x2.relaxed_laneselect", .F32X4_RELAXED_MIN = "f32x4.relaxed_min",
.F32X4_RELAXED_MAX = "f32x4.relaxed_max", .F64X2_RELAXED_MIN = "f64x2.relaxed_min",
.F64X2_RELAXED_MAX = "f64x2.relaxed_max", .I16X8_RELAXED_Q15MULR_S = "i16x8.relaxed_q15mulr_s",
.I16X8_RELAXED_DOT_I8X16_I7X16_S = "i16x8.relaxed_dot_i8x16_i7x16_s", .I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S = "i32x4.relaxed_dot_i8x16_i7x16_add_s",
// threads / atomics (0xFE)
.MEMORY_ATOMIC_NOTIFY = "memory.atomic.notify", .MEMORY_ATOMIC_WAIT32 = "memory.atomic.wait32",
.MEMORY_ATOMIC_WAIT64 = "memory.atomic.wait64", .ATOMIC_FENCE = "atomic.fence",
.I32_ATOMIC_LOAD = "i32.atomic.load", .I64_ATOMIC_LOAD = "i64.atomic.load",
.I32_ATOMIC_LOAD8_U = "i32.atomic.load8_u", .I32_ATOMIC_LOAD16_U = "i32.atomic.load16_u",
.I64_ATOMIC_LOAD8_U = "i64.atomic.load8_u", .I64_ATOMIC_LOAD16_U = "i64.atomic.load16_u",
.I64_ATOMIC_LOAD32_U = "i64.atomic.load32_u", .I32_ATOMIC_STORE = "i32.atomic.store",
.I64_ATOMIC_STORE = "i64.atomic.store", .I32_ATOMIC_STORE8 = "i32.atomic.store8",
.I32_ATOMIC_STORE16 = "i32.atomic.store16", .I64_ATOMIC_STORE8 = "i64.atomic.store8",
.I64_ATOMIC_STORE16 = "i64.atomic.store16", .I64_ATOMIC_STORE32 = "i64.atomic.store32",
.I32_ATOMIC_RMW_ADD = "i32.atomic.rmw.add", .I64_ATOMIC_RMW_ADD = "i64.atomic.rmw.add",
.I32_ATOMIC_RMW8_ADD_U = "i32.atomic.rmw8.add_u", .I32_ATOMIC_RMW16_ADD_U = "i32.atomic.rmw16.add_u",
.I64_ATOMIC_RMW8_ADD_U = "i64.atomic.rmw8.add_u", .I64_ATOMIC_RMW16_ADD_U = "i64.atomic.rmw16.add_u",
.I64_ATOMIC_RMW32_ADD_U = "i64.atomic.rmw32.add_u", .I32_ATOMIC_RMW_SUB = "i32.atomic.rmw.sub",
.I64_ATOMIC_RMW_SUB = "i64.atomic.rmw.sub", .I32_ATOMIC_RMW8_SUB_U = "i32.atomic.rmw8.sub_u",
.I32_ATOMIC_RMW16_SUB_U = "i32.atomic.rmw16.sub_u", .I64_ATOMIC_RMW8_SUB_U = "i64.atomic.rmw8.sub_u",
.I64_ATOMIC_RMW16_SUB_U = "i64.atomic.rmw16.sub_u", .I64_ATOMIC_RMW32_SUB_U = "i64.atomic.rmw32.sub_u",
.I32_ATOMIC_RMW_AND = "i32.atomic.rmw.and", .I64_ATOMIC_RMW_AND = "i64.atomic.rmw.and",
.I32_ATOMIC_RMW8_AND_U = "i32.atomic.rmw8.and_u", .I32_ATOMIC_RMW16_AND_U = "i32.atomic.rmw16.and_u",
.I64_ATOMIC_RMW8_AND_U = "i64.atomic.rmw8.and_u", .I64_ATOMIC_RMW16_AND_U = "i64.atomic.rmw16.and_u",
.I64_ATOMIC_RMW32_AND_U = "i64.atomic.rmw32.and_u", .I32_ATOMIC_RMW_OR = "i32.atomic.rmw.or",
.I64_ATOMIC_RMW_OR = "i64.atomic.rmw.or", .I32_ATOMIC_RMW8_OR_U = "i32.atomic.rmw8.or_u",
.I32_ATOMIC_RMW16_OR_U = "i32.atomic.rmw16.or_u", .I64_ATOMIC_RMW8_OR_U = "i64.atomic.rmw8.or_u",
.I64_ATOMIC_RMW16_OR_U = "i64.atomic.rmw16.or_u", .I64_ATOMIC_RMW32_OR_U = "i64.atomic.rmw32.or_u",
.I32_ATOMIC_RMW_XOR = "i32.atomic.rmw.xor", .I64_ATOMIC_RMW_XOR = "i64.atomic.rmw.xor",
.I32_ATOMIC_RMW8_XOR_U = "i32.atomic.rmw8.xor_u", .I32_ATOMIC_RMW16_XOR_U = "i32.atomic.rmw16.xor_u",
.I64_ATOMIC_RMW8_XOR_U = "i64.atomic.rmw8.xor_u", .I64_ATOMIC_RMW16_XOR_U = "i64.atomic.rmw16.xor_u",
.I64_ATOMIC_RMW32_XOR_U = "i64.atomic.rmw32.xor_u", .I32_ATOMIC_RMW_XCHG = "i32.atomic.rmw.xchg",
.I64_ATOMIC_RMW_XCHG = "i64.atomic.rmw.xchg", .I32_ATOMIC_RMW8_XCHG_U = "i32.atomic.rmw8.xchg_u",
.I32_ATOMIC_RMW16_XCHG_U = "i32.atomic.rmw16.xchg_u", .I64_ATOMIC_RMW8_XCHG_U = "i64.atomic.rmw8.xchg_u",
.I64_ATOMIC_RMW16_XCHG_U = "i64.atomic.rmw16.xchg_u", .I64_ATOMIC_RMW32_XCHG_U = "i64.atomic.rmw32.xchg_u",
.I32_ATOMIC_RMW_CMPXCHG = "i32.atomic.rmw.cmpxchg", .I64_ATOMIC_RMW_CMPXCHG = "i64.atomic.rmw.cmpxchg",
.I32_ATOMIC_RMW8_CMPXCHG_U = "i32.atomic.rmw8.cmpxchg_u", .I32_ATOMIC_RMW16_CMPXCHG_U = "i32.atomic.rmw16.cmpxchg_u",
.I64_ATOMIC_RMW8_CMPXCHG_U = "i64.atomic.rmw8.cmpxchg_u", .I64_ATOMIC_RMW16_CMPXCHG_U = "i64.atomic.rmw16.cmpxchg_u",
.I64_ATOMIC_RMW32_CMPXCHG_U = "i64.atomic.rmw32.cmpxchg_u",
}

View File

@@ -1,281 +0,0 @@
package rexcode_wasm_module
import "base:runtime"
import "core:fmt"
import "core:rexcode/wasm"
WASM_MAGIC :: u32(0x6d736100) // "\0asm" as a little-endian u32
WASM_VERSION :: u32(1)
Section_Id :: enum u8 { // Binary section ids (WebAssembly core spec §5.5.2).
CUSTOM = 0,
TYPE = 1,
IMPORT = 2,
FUNCTION = 3,
TABLE = 4,
MEMORY = 5,
GLOBAL = 6,
EXPORT = 7,
START = 8,
ELEMENT = 9,
CODE = 10,
DATA = 11,
DATA_COUNT = 12,
}
Section :: struct {
id: Section_Id,
offset: u32, // file offset of the section *contents*
size: u32, // contents length in bytes
count: u32, // element count
name: string, // custom-section name (borrowed)
}
External_Kind :: enum u8 {
FUNC = 0,
TABLE = 1,
MEMORY = 2,
GLOBAL = 3,
}
@(rodata)
external_kind_string := [External_Kind]string{
.FUNC = "func",
.TABLE = "table",
.MEMORY = "memory",
.GLOBAL = "global",
}
Func_Type :: struct {
params: []wasm.Value_Type,
results: []wasm.Value_Type,
}
Import :: struct {
kind: External_Kind,
module_name: string, // borrowed
field_name: string, // borrowed
index: u32, // typeidx for FUNC, 0 for other kinds
}
Export :: struct {
kind: External_Kind,
name: string, // borrowed
index: u32,
}
// A compressed run of declared locals (e.g. `3 x i32`)
Local_Group :: struct {
count: u32,
type: wasm.Value_Type,
}
// A function in the module's function index space.
// Imported functions occupy the low indices, followed by the module-defined functions.
Function :: struct {
func_index: u32,
type_index: u32,
type: Func_Type, // resolved signature ({} if the type id was out of range)
imported: bool,
exported: bool,
name: string, // export / name-section / import field (borrowed)
import_module: string, // borrowed, "" for defined functions
import_field: string, // borrowed, "" for defined functions
// defined functions only:
locals: []Local_Group,
body_offset: u32, // file offset of the instruction stream
body_size: u32, // instruction-stream length in bytes
}
Module :: struct {
version: u32,
sections: []Section,
customs: []Custom_Section,
types: []Func_Type,
imports: []Import,
functions: []Function, // whole function index space (imports + defined)
exports: []Export,
start: i64, // -1 if absent, else the start funcidx
reloc_groups: []Reloc_Group,
data: []u8, // borrowed reference to the whole file (body decode reads from it)
allocator: runtime.Allocator,
}
// -----------------------------------------------------------------------------
// Custom Section Layout
// -----------------------------------------------------------------------------
Custom_Section :: struct {
section: Section,
payload: []byte, // borrowed, m.data[section.offset:][:section.size]
variant: union {
Custom_Section_Name,
Custom_Section_Target_Features,
},
}
Custom_Section_Name_Function :: struct {
id: u32,
name: string, // borrowed
}
Custom_Section_Name_Local :: struct {
idx: u32,
name: string, // borrowed
}
Custom_Section_Name_Function_Locals :: struct {
func_idx: u32,
locals: []Custom_Section_Name_Local,
}
Custom_Section_Name :: struct {
module_name: string,
functions: []Custom_Section_Name_Function,
locals: []Custom_Section_Name_Function_Locals,
}
Custom_Section_Target_Feature_Prefix :: enum u8 {
Used = '+',
Disallowed = '-',
Required = '=',
}
Custom_Section_Target_Feature :: struct {
prefix: Custom_Section_Target_Feature_Prefix,
feature: string, // borrowed
}
Custom_Section_Target_Features :: struct {
features: []Custom_Section_Target_Feature,
}
@(require_results)
section_name :: #force_inline proc "contextless" (id: Section_Id) -> string {
switch id {
case .CUSTOM: return "custom"
case .TYPE: return "type"
case .IMPORT: return "import"
case .FUNCTION: return "function"
case .TABLE: return "table"
case .MEMORY: return "memory"
case .GLOBAL: return "global"
case .EXPORT: return "export"
case .START: return "start"
case .ELEMENT: return "element"
case .CODE: return "code"
case .DATA: return "data"
case .DATA_COUNT: return "data.count"
}
return "unknown"
}
@(require_results)
valtype_name :: #force_inline proc "contextless" (t: wasm.Value_Type) -> string {
switch t {
case .I32: return "i32"
case .I64: return "i64"
case .F32: return "f32"
case .F64: return "f64"
case .V128: return "v128"
case .FUNCREF: return "funcref"
case .EXTERNREF: return "externref"
}
return "?"
}
@(require_results)
external_kind_name :: #force_inline proc "contextless" (k: External_Kind) -> string {
switch k {
case .FUNC: return "func"
case .TABLE: return "table"
case .MEMORY: return "memory"
case .GLOBAL: return "global"
}
return "?"
}
@(require_results)
module_name :: proc "contextless" (m: Module) -> (string, bool) {
for c in m.customs {
#partial switch v in c.variant {
case Custom_Section_Name:
if v.module_name != "" {
return v.module_name, true
}
}
}
return "", false
}
@(require_results)
count_import_kind :: proc "contextless" (m: Module, k: External_Kind) -> u32 {
n: u32 = 0
for imp in m.imports {
if imp.kind == k {
n += 1
}
}
return n
}
@(require_results)
block_sig :: proc "contextless" (m: Module, imm: i64) -> (params: int, results: int) {
if imm < 0 {
if imm == i64(wasm.Block_Type.EMPTY) {
return 0, 0
}
return 0, 1 // single value-type result
}
if int(imm) < len(m.types) {
t := m.types[imm]
return len(t.params), len(t.results)
}
return 0, 0
}
// Stack arity (operands consumed, results produced) for non-control operators,
// plus the control operators that flow through the plain path. Block/loop/if
// are handled directly by `wat_fold_region`.
@(require_results)
instruction_arity :: proc(m: Module, inst: wasm.Instruction) -> (inputs: int, outputs: int) {
e := &wasm.ENCODING_TABLE[inst.mnemonic]
inputs, outputs = int(e.inputs), int(e.outputs)
if inputs < 0 || outputs < 0 {
#partial switch inst.mnemonic {
case .CALL, .RETURN_CALL:
if int(inst.ops[0].index) < len(m.functions) {
t := m.functions[inst.ops[0].index].type
return len(t.params), len(t.results)
}
return 0, 0
case .CALL_INDIRECT, .RETURN_CALL_INDIRECT:
if int(inst.ops[0].index) < len(m.types) {
t := m.types[inst.ops[0].index]
return len(t.params) + 1, len(t.results)
}
return 1, 0
case .RETURN:
if int(inst.ops[0].index) < len(m.types) {
t := m.types[inst.ops[0].index]
return len(t.results), 0
}
return 0, 0
}
fmt.panicf("Unknown optional arity handling %v", inst.mnemonic)
}
return
}

View File

@@ -1,522 +0,0 @@
package rexcode_wasm_module
import "base:runtime"
import "core:strings"
import "core:rexcode/wasm"
Parse_Error :: enum {
NONE = 0,
TRUNCATED,
BAD_MAGIC,
BAD_TYPE_FORM, // a functype did not start with 0x60
BAD_SECTION, // section contents extend past the section size
BAD_ULEB, // ULEB number didn't stop after 10 bytes
}
Reader_Error :: union #shared_nil {
Parse_Error,
runtime.Allocator_Error,
}
Reader :: struct {
data: []u8,
off: u32,
}
@(require_results)
reader :: proc(data: []u8, off: u32) -> Reader {
return Reader{data = data, off = off}
}
@(require_results)
rd_byte :: proc(r: ^Reader) -> (u8, Parse_Error) {
if r.off >= u32(len(r.data)) {
return 0, .TRUNCATED
}
b := r.data[r.off]
r.off += 1
return b, .NONE
}
@(require_results)
rd_u32le_block :: proc(r: ^Reader) -> (u32, Parse_Error) {
if r.off + 4 > u32(len(r.data)) {
return 0, .TRUNCATED
}
v := u32(r.data[r.off]) |
u32(r.data[r.off+1])<<8 |
u32(r.data[r.off+2])<<16 |
u32(r.data[r.off+3])<<24
r.off += 4
return v, .NONE
}
@(require_results)
rd_uleb :: proc(r: ^Reader) -> (u64, Parse_Error) {
shift: uint = 0
value: u64 = 0
for _ in 0..<10 {
if r.off >= u32(len(r.data)) {
return 0, .TRUNCATED
}
b := r.data[r.off]
r.off += 1
value |= u64(b & 0x7F) << shift
if b & 0x80 == 0 {
return value, .NONE
}
shift += 7
}
return 0, .BAD_ULEB
}
// Signed-LEB128 reader.
@(require_results)
rd_sleb :: proc(r: ^Reader) -> (i64, Parse_Error) {
shift: uint = 0
value: i64 = 0
b: u8 = 0
for _ in 0..<10 {
if r.off >= u32(len(r.data)) {
return 0, .TRUNCATED
}
b = r.data[r.off]
r.off += 1
value |= i64(b & 0x7F) << shift
shift += 7
if b & 0x80 == 0 {
break
}
}
if shift < 64 && (b & 0x40) != 0 {
value |= -(i64(1) << shift)
}
return value, .NONE
}
@(require_results)
rd_u32 :: proc(r: ^Reader) -> (u32, Parse_Error) {
v, err := rd_uleb(r)
return u32(v), err
}
@(require_results)
rd_name :: proc(r: ^Reader) -> (val: string, err: Parse_Error) {
n := rd_u32(r) or_return
if r.off + n > u32(len(r.data)) {
err = .TRUNCATED
return
}
val = string(r.data[r.off:][:n])
r.off += n
return
}
@(require_results)
rd_valtype_vec :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []wasm.Value_Type, err: Reader_Error) {
n := rd_u32(r) or_return
// now actually parse it
out = make([]wasm.Value_Type, int(n), allocator) or_return
for &v in out {
v = wasm.Value_Type(rd_byte(r) or_return)
}
return
}
@(require_results)
rd_limits :: proc(r: ^Reader) -> (min: u64, max: Maybe(u64), err: Parse_Error) {
flags := rd_byte(r) or_return
min = rd_uleb(r) or_return
if flags & 0x01 != 0 {
max = rd_uleb(r) or_return
}
return
}
@(require_results)
parse :: proc(data: []u8, allocator := context.allocator) -> (m: Module, err: Reader_Error) {
context.allocator = allocator
m.data = data
m.start = -1
m.allocator = allocator
r := reader(data, 0)
if (rd_u32le_block(&r) or_else 0) != WASM_MAGIC {
return m, .BAD_MAGIC
}
m.version = rd_u32le_block(&r) or_return
secs: [dynamic]Section
for r.off < u32(len(data)) {
id := Section_Id(rd_byte(&r) or_return)
size := rd_u32(&r) or_return
content := r.off
if content + size > u32(len(data)) {
return m, .BAD_SECTION
}
sec := Section{id = id, offset = content, size = size}
switch id {
case .CUSTOM:
sub := reader(data, content)
sec.name = rd_name(&sub) or_return
case .START:
// funcidx, no vector count
case .TYPE, .IMPORT, .FUNCTION, .TABLE, .MEMORY, .GLOBAL,
.EXPORT, .ELEMENT, .CODE, .DATA, .DATA_COUNT:
sub := reader(data, content)
sec.count = rd_u32(&sub) or_return
}
append(&secs, sec) or_return
r.off = content + size
}
m.sections = secs[:]
func_typeidx: []u32
codes: []Code_Body
for &sec in m.sections {
s := reader(data, sec.offset)
#partial switch sec.id {
case .TYPE: m.types = parse_types (&s, allocator) or_return
case .IMPORT: m.imports = parse_imports (&s, allocator) or_return
case .FUNCTION: func_typeidx = parse_function_section(&s, allocator) or_return
case .EXPORT: m.exports = parse_exports (&s, allocator) or_return
case .CODE: codes = parse_code (&s, allocator) or_return
case .START: m.start = i64(rd_u32(&s) or_return)
}
}
parse_custom_sections(&m, allocator) or_return
build_functions(&m, func_typeidx, codes, allocator) or_return
apply_name_section(&m)
m.reloc_groups = parse_relocations(m, allocator) or_return
return
}
@(require_results)
parse_types :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Func_Type, err: Reader_Error) {
n := rd_u32(r) or_return
out = make([]Func_Type, int(n), allocator) or_return
for &func in out {
form := rd_byte(r) or_return
if form != 0x60 {
return out, .BAD_TYPE_FORM
}
func.params = rd_valtype_vec(r, allocator) or_return
func.results = rd_valtype_vec(r, allocator) or_return
}
return
}
@(require_results)
parse_imports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Import, err: Reader_Error) {
n := rd_u32(r) or_return
out = make([]Import, int(n), allocator) or_return
for &imp in out {
imp.module_name = rd_name(r) or_return
imp.field_name = rd_name(r) or_return
imp.kind = External_Kind(rd_byte(r) or_return)
switch imp.kind {
case .FUNC:
imp.index = rd_u32(r) or_return
case .TABLE:
_ = rd_byte(r) or_return // reftype
_, _ = rd_limits(r) or_return
case .MEMORY:
_, _ = rd_limits(r) or_return
case .GLOBAL:
_ = rd_byte(r) or_return // valtype
_ = rd_byte(r) or_return // mutability
}
}
return
}
@(require_results)
parse_function_section :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []u32, err: Reader_Error) {
n := rd_u32(r) or_return
out = make([]u32, int(n), allocator) or_return
for &idx in out {
idx = rd_u32(r) or_return
}
return
}
@(require_results)
parse_exports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Export, err: Reader_Error) {
n := rd_u32(r) or_return
out = make([]Export, int(n), allocator) or_return
for &e in out {
e.name = rd_name(r) or_return
e.kind = External_Kind(rd_byte(r) or_return)
e.index = rd_u32(r) or_return
}
return
}
Code_Body :: struct {
locals: []Local_Group,
body_offset: u32,
body_size: u32,
}
@(require_results)
parse_code :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Code_Body, err: Reader_Error) {
n := rd_u32(r) or_return
out = make([]Code_Body, int(n), allocator) or_return
for &code_body in out {
total := rd_u32(r) or_return
body_start := r.off
body_end := body_start + total
nl := rd_u32(r) or_return
locals := make([]Local_Group, int(nl), allocator) or_return
for &local in locals {
cnt := rd_u32(r) or_return
t := wasm.Value_Type(rd_byte(r) or_return)
local = Local_Group{count = cnt, type = t}
}
code_body = Code_Body{
locals = locals,
body_offset = r.off,
body_size = body_end > r.off ? body_end - r.off : 0,
}
r.off = body_end // jump past the expression to the next entry
}
return
}
@(require_results)
parse_custom_sections :: proc(m: ^Module, allocator: runtime.Allocator) -> Reader_Error {
custom_count := 0
for &sec in m.sections {
if sec.id == .CUSTOM {
custom_count += 1
}
}
m.customs = make([]Custom_Section, custom_count, allocator) or_return
custom_index := 0
for &sec in m.sections {
if sec.id != .CUSTOM {
continue
}
custom := &m.customs[custom_index]
custom_index += 1
custom.section = sec
custom.payload = m.data[sec.offset:][:sec.size]
r := reader(custom.payload, 0)
sec_name := rd_name(&r) or_continue
assert(sec_name == sec.name)
if strings.has_prefix(sec.name, ".debug_") {
// DWARF debug stuff
} else if strings.has_prefix(sec.name, "reloc.") {
// other relocation stuff
}
custom_block: switch sec.name {
case "linking":
// not yet handled
case "producers":
// not yet handled
case "dynlink.0":
// not yet handled
case "external_debug_info", "sourceMappingURL":
// not yet handled, but debugging related
case "metadata.code.branch_hint":
// not yet handled
case "name":
cname: Custom_Section_Name
defer custom.variant = cname
for r.off < u32(len(r.data)) {
id := rd_byte(&r) or_return
size := rd_u32(&r) or_return
end_off := r.off+size
defer r.off = end_off
switch id {
case 0: // module
cname.module_name = rd_name(&r) or_return
case 1: // functions
count := rd_u32(&r) or_return
cname.functions = make([]Custom_Section_Name_Function, count, allocator) or_return
for &func in cname.functions {
func.id = rd_u32(&r) or_return
func.name = rd_name(&r) or_return
}
case 2: // locals
count := rd_u32(&r) or_return
cname.locals = make([]Custom_Section_Name_Function_Locals, count, allocator) or_return
for &local_func in cname.locals {
local_func.func_idx = rd_u32(&r) or_return
local_count := rd_u32(&r) or_return
local_func.locals = make([]Custom_Section_Name_Local, local_count, allocator) or_return
for &local in local_func.locals {
local.idx = rd_u32(&r) or_return
local.name = rd_name(&r) or_return
}
}
}
}
case "target_features":
target_features: Custom_Section_Target_Features
defer custom.variant = target_features
count := rd_u32(&r) or_return
target_features.features = make([]Custom_Section_Target_Feature, count, allocator) or_return
for &feature in target_features.features {
feature.prefix = Custom_Section_Target_Feature_Prefix(rd_byte(&r) or_return)
feature.feature = rd_name(&r) or_return
}
}
}
return nil
}
@(require_results)
build_functions :: proc(m: ^Module, func_typeidx: []u32, codes: []Code_Body, allocator: runtime.Allocator) -> runtime.Allocator_Error {
num_imports := 0
for imp in m.imports {
if imp.kind == .FUNC {
num_imports += 1
}
}
total := num_imports + len(func_typeidx)
if total == 0 {
return nil
}
funcs := make([]Function, total, allocator) or_return
idx := 0
for imp in m.imports {
(imp.kind == .FUNC) or_continue
f := Function{
func_index = u32(idx),
type_index = imp.index,
imported = true,
name = imp.field_name,
import_module = imp.module_name,
import_field = imp.field_name,
}
if int(imp.index) < len(m.types) {
f.type = m.types[imp.index]
}
funcs[idx] = f
idx += 1
}
for tidx, i in func_typeidx {
fi := num_imports + i
f := Function{
func_index = u32(fi),
type_index = tidx,
imported = false,
}
if int(tidx) < len(m.types) {
f.type = m.types[tidx]
}
if i < len(codes) {
c := &codes[i]
f.locals = c.locals
f.body_offset = c.body_offset
f.body_size = c.body_size
}
funcs[fi] = f
}
for e in m.exports {
if e.kind == .FUNC && int(e.index) < total && funcs[e.index].name == "" {
f := &funcs[e.index]
f.name = e.name
f.exported = true
}
}
m.functions = funcs
return nil
}
// Override function names with debug names from the "name" custom section's function-names subsection (id 1), if it exists.
apply_name_section :: proc(m: ^Module) {
for sec in m.sections {
if sec.id != .CUSTOM || sec.name != "name" {
continue
}
r := reader(m.data, sec.offset)
_ = rd_name(&r) or_break // re-read the section name to position at the subsections
end := sec.offset + sec.size
for r.off < end {
sub_id := rd_byte(&r) or_break
sub_size := rd_u32(&r) or_break
payload_end := r.off + sub_size
if sub_id == 1 {
count := rd_u32(&r) or_break
for _ in 0..<count {
fidx := rd_u32(&r) or_break
name := rd_name(&r) or_break
if int(fidx) < len(m.functions) {
m.functions[fidx].name = name
}
}
}
// skip any subsection we do not need to interpret
r.off = payload_end
}
return
}
}
module_destroy :: proc(m: ^Module) {
relocations_destroy(m.reloc_groups, m.allocator)
for t in m.types {
delete(t.params, m.allocator)
delete(t.results, m.allocator)
}
for f in m.functions {
if !f.imported {
delete(f.locals, m.allocator)
}
}
for c in m.customs {
switch v in c.variant {
case Custom_Section_Name:
for function_locals in v.locals {
delete(function_locals.locals, m.allocator)
}
delete(v.functions, m.allocator)
delete(v.locals, m.allocator)
case Custom_Section_Target_Features:
delete(v.features)
}
}
delete(m.customs, m.allocator)
delete(m.sections, m.allocator)
delete(m.types, m.allocator)
delete(m.imports, m.allocator)
delete(m.functions, m.allocator)
delete(m.exports, m.allocator)
m^ = {}
}

View File

@@ -1,267 +0,0 @@
package rexcode_wasm_module
import "core:strings"
import "core:os"
import "core:fmt"
import wasm "../"
print_module :: proc(m: Module, file: ^os.File) {
sb := strings.builder_make(context.allocator)
defer strings.builder_destroy(&sb)
sbprint_module(&sb, m)
s := strings.to_string(sb)
os.write_string(file, s)
}
sbprint_module :: proc(sb: ^strings.Builder, m: Module) {
write_func_type :: proc(sb: ^strings.Builder, t: Func_Type) {
strings.write_byte(sb, '(')
for p, i in t.params {
if i > 0 { strings.write_string(sb, ", ") }
strings.write_string(sb, valtype_name(p))
}
strings.write_string(sb, ") -> ")
strings.write_byte(sb, '(')
for rt, i in t.results {
if i > 0 { strings.write_string(sb, ", ") }
strings.write_string(sb, valtype_name(rt))
}
strings.write_byte(sb, ')')
}
strings.write_string(sb, "module\n")
strings.write_string(sb, " version: ")
strings.write_u64(sb, u64(m.version))
strings.write_byte(sb, '\n')
for c in m.customs {
#partial switch v in c.variant {
case Custom_Section_Name:
if v.module_name != "" {
strings.write_string(sb, " file: ")
strings.write_quoted_string(sb, v.module_name)
strings.write_byte(sb, '\n')
}
}
}
label_names: map[u32]string
defer delete(label_names)
for f in m.functions {
if f.name != "" {
label_names[f.func_index] = f.name
}
}
// sections
for sec in m.sections {
write_padded :: proc(sb: ^strings.Builder, s: string, width: int) {
strings.write_string(sb, s)
for _ in len(s)..<width {
strings.write_byte(sb, ' ')
}
}
strings.write_string(sb, ".")
write_padded(sb, section_name(sec.id), 12)
#partial switch sec.id {
case .CUSTOM:
strings.write_string(sb, " \"")
strings.write_string(sb, sec.name)
strings.write_byte(sb, '"')
case .START:
// do nothing
case:
strings.write_string(sb, " (")
strings.write_u64(sb, u64(sec.count))
strings.write_string(sb, " entries)")
}
strings.write_byte(sb, '\n')
section_data := m.data[sec.offset:][:sec.size]
section_printing: #partial switch sec.id {
case .CUSTOM:
for c in m.customs {
if c.section != sec {
continue
}
switch v in c.variant {
case Custom_Section_Name:
if v.module_name != "" {
fmt.sbprintf(sb, " module: %q\n", v.module_name)
}
// if len(v.functions) > 0 {
// fmt.sbprintf(sb, " functions:\n")
// for f in v.functions {
// fmt.sbprintf(sb, " [%d] %q\n", f.id, f.name)
// }
// }
// if len(v.locals) > 0 {
// fmt.sbprintf(sb, " locals:\n")
// for fl in v.locals {
// fmt.sbprintf(sb, " [%d] function\n", fl.func_idx)
// for local in fl.locals {
// fmt.sbprintf(sb, " [%d] %q\n", local.idx, local.name)
// }
// }
// }
case Custom_Section_Target_Features:
for f in v.features {
fmt.sbprintf(sb, " \"%c%s\"\n", u8(f.prefix), f.feature)
}
}
break
}
case .DATA:
r := reader(section_data, 0)
count := rd_u32(&r) or_break section_printing
assert(count == sec.count)
for i in 0..<sec.count {
fmt.sbprintf(sb, " [%d]\n", i)
kind := rd_u32(&r) or_break section_printing
switch kind {
case 2: // memidx + expr + bytes
memidx := rd_u32(&r) or_break section_printing
if memidx != 0 {
fmt.sbprintf(sb, " memidx:%d\n", memidx)
}
fallthrough
case 0: // expr + bytes
relocs := relocations_from_section_id(m.reloc_groups, sec.id)
for r.off < u32(len(r.data)) {
inst, info, next := wasm.decode_one(r.data[r.off:], relocs=relocs, pc=0, targets_allocator=context.temp_allocator) or_break section_printing
r.off += next
if inst.mnemonic == .END {
break
}
wasm.sbprint(sb, {inst}, {info}, nil, &label_names)
}
case 1: // bytes
break
}
size := rd_u32(&r) or_break section_printing
fmt.sbprintf(sb, " %q\n", r.data[r.off:][:size])
r.off += size
}
case .MEMORY:
r := reader(section_data, 0)
count := rd_u32(&r) or_break section_printing
assert(count == sec.count)
for i in 0..<sec.count {
fmt.sbprintf(sb, " [%d] ", i)
min, max := rd_limits(&r) or_break section_printing
if max == nil {
fmt.sbprintf(sb, "limits: %v..inf\n", min)
} else {
fmt.sbprintf(sb, "limits: %v..%v\n", min, max)
}
}
}
}
strings.write_string(sb, "\n")
if len(m.imports) > 0 {
strings.write_string(sb, ".import\n")
for imp in m.imports {
f := &m.functions[imp.index]
fmt.sbprintf(sb, " %s %q %q", external_kind_string[imp.kind], imp.module_name, imp.field_name)
write_func_type(sb, f.type)
strings.write_string(sb, "\n")
}
strings.write_string(sb, "\n")
}
if len(m.exports) > 0 {
strings.write_string(sb, ".export\n")
for e in m.exports {
f := &m.functions[e.index]
fmt.sbprintf(sb, " %s %q", external_kind_string[e.kind], e.name)
write_func_type(sb, f.type)
strings.write_string(sb, "\n")
}
strings.write_string(sb, "\n")
}
// if len(m.types) > 0 {
// strings.write_string(sb, "\n.")
// strings.write_string(sb, section_name(.TYPE))
// strings.write_string(sb, "\n")
// for t, i in m.types {
// strings.write_string(sb, " [")
// strings.write_u64(sb, u64(i))
// strings.write_string(sb, "] ")
// write_func_type(sb, t)
// strings.write_byte(sb, '\n')
// }
// }
func_relocs := relocations_from_section_id(m.reloc_groups, .FUNCTION)
for f in m.functions {
if f.exported {
strings.write_string(sb, "export ")
}
if f.name != "" {
strings.write_string(sb, external_kind_string[.FUNC])
strings.write_byte(sb, ' ')
strings.write_quoted_string(sb, f.name)
}
write_func_type(sb, f.type)
if f.imported {
strings.write_string(sb, "\n import ")
strings.write_quoted_string(sb, f.import_module)
strings.write_string(sb, " ")
strings.write_quoted_string(sb, f.import_field)
strings.write_string(sb, "\n")
continue
}
strings.write_byte(sb, '\n')
if len(f.locals) != 0 {
strings.write_string(sb, " locals:")
for g in f.locals {
strings.write_byte(sb, ' ')
if g.count > 1 {
strings.write_u64(sb, u64(g.count))
strings.write_string(sb, "x")
}
strings.write_string(sb, valtype_name(g.type))
}
strings.write_byte(sb, '\n')
}
if f.body_size != 0 {
sbprint_function(sb, m, f, func_relocs, &label_names)
}
}
}
// Disassemble and print one function body. Returns the empty string for
// imported functions (which have no body).
sbprint_function :: proc(sb: ^strings.Builder, m: Module, f: Function, relocs: []wasm.Relocation, label_names: ^map[u32]string) -> string {
if f.imported || f.body_size == 0 {
return ""
}
body := m.data[f.body_offset:][:f.body_size]
insts: [dynamic]wasm.Instruction
info: [dynamic]wasm.Instruction_Info
errs: [dynamic]wasm.Error
defer delete(insts)
defer delete(info)
defer delete(errs)
wasm.decode(body, relocs, &insts, &info, &errs)
wasm.sbprint(sb, insts[:], info[:], label_names=label_names)
return strings.to_string(sb^)
}

View File

@@ -1,708 +0,0 @@
package rexcode_wasm_module
import "base:runtime"
import "core:strings"
import "core:os"
import "core:io"
import "core:fmt"
import wasm "core:rexcode/wasm"
// =============================================================================
// WebAssembly TEXT (WAT) PRINTER
// =============================================================================
//
// `print.odin` emits a compact, custom *disassembly* of a parsed `Module`.
// This file emits the standard WebAssembly text format (`.wat`): the nested
// s-expression module that `wat2wasm` consumes and that `wasm2wat` produces.
//
// (module
// (type (;0;) (func (param i32 i32) (result i32)))
// (func $add (type 0) (param i32 i32) (result i32)
// (i32.add
// (local.get 0)
// (local.get 1)))
// (export "add" (func 0)))
//
// Folding
// -------
// A WASM code section is a *linear* stack-machine stream; WAT bodies are
// *folded* operator trees. Reconstructing the trees needs each instruction's
// stack arity (operands consumed / results produced), which the linear stream
// does not carry, so it is recovered here:
// * fixed for the numeric / memory / variable / parametric / reference /
// bulk-memory operators (`instruction_arity`),
// * computed from the module's own type table for `call` (callee signature),
// `call_indirect` (the referenced type), and `block`/`loop`/`if` results.
//
// The folder is the algorithm `wat2wasm`/`wasm2wat` use: keep a stack of
// partially built operand trees; an operator with N operands claims the top N
// trees as children; an operator that yields no value (a "statement", e.g. a
// store / drop / void call) flushes the stack so nothing is reordered. Where
// folding would be unsound (not enough operands contiguously on top, e.g.
// across a side-effecting statement) it degrades to flat folded exprs such as
// `(i32.add)` whose operands are the preceding sibling exprs -- still valid,
// still correct, just less nested. SIMD / atomic operators are conservatively
// treated as statements (rendered flat) rather than risk an incorrect tree.
//
// Index operands print numerically (`call 3`, `local.get 0`): the decoder
// marks `call` targets `symbolic` (which would print `$3`); that flag is
// cleared per-instruction so references always resolve. Names from the "name"
// custom section are attached to *definitions* as `$name` (or kept as an index
// comment when the text would not be a legal WAT identifier).
//
// Coverage: TYPE, IMPORT (all four kinds), FUNCTION/CODE, TABLE, MEMORY,
// GLOBAL, EXPORT, START, ELEMENT (active table-0 funcref form), DATA.
WAT_Options :: struct {
indent_unit: string, // one indentation step; default " "
}
DEFAULT_WAT_OPTIONS :: WAT_Options{
indent_unit = " ",
}
print_wat :: proc(m: Module, file: ^os.File, opts := DEFAULT_WAT_OPTIONS) {
sb := strings.builder_make(context.allocator)
defer strings.builder_destroy(&sb)
sbprint_wat(&sb, m, opts)
os.write_string(file, strings.to_string(sb))
}
fprint_wat :: proc(w: io.Writer, m: Module, opts := DEFAULT_WAT_OPTIONS) {
sb := strings.builder_make(context.temp_allocator)
sbprint_wat(&sb, m, opts)
io.write_string(w, strings.to_string(sb))
}
// Allocates the result with `allocator` (caller owns it).
aprint_wat :: proc(m: Module, opts := DEFAULT_WAT_OPTIONS, allocator := context.allocator) -> string {
sb := strings.builder_make(allocator)
sbprint_wat(&sb, m, opts)
return strings.to_string(sb)
}
// Result lives in the temp allocator.
tprint_wat :: proc(m: Module, opts := DEFAULT_WAT_OPTIONS) -> string {
sb := strings.builder_make(context.temp_allocator)
sbprint_wat(&sb, m, opts)
return strings.to_string(sb)
}
sbprint_wat :: proc(sb: ^strings.Builder, m: Module, opts := DEFAULT_WAT_OPTIONS) {
unit := opts.indent_unit
if unit == "" {
unit = " "
}
strings.write_string(sb, "(module")
defer strings.write_string(sb, ")\n")
if name, ok := module_name(m); ok {
if wat_ident_ok(name) {
strings.write_string(sb, " $")
strings.write_string(sb, name)
} else {
fmt.sbprintf(sb, " (;%q;)", name)
}
}
strings.write_byte(sb, '\n')
for t, i in m.types {
fmt.sbprintf(sb, "%s(type (;%d;) ", unit, i)
wat_write_functype(sb, t)
strings.write_string(sb, ")\n")
}
wat_write_imports(sb, m, unit)
func_relocs := relocations_from_section_id(m.reloc_groups, .FUNCTION)
for f in m.functions {
if f.imported {
continue
}
wat_write_function(sb, m, func_relocs, f, unit)
}
wat_write_tables (sb, m, unit)
wat_write_memories(sb, m, unit)
wat_write_globals (sb, m, unit)
for e in m.exports {
fmt.sbprintf(sb, "%s(export %q (%s %d))\n", unit, e.name, external_kind_name(e.kind), e.index)
}
if m.start >= 0 {
fmt.sbprintf(sb, "%s(start %d)\n", unit, m.start)
}
wat_write_elements(sb, m, unit)
wat_write_data (sb, m, unit)
}
wat_write_functype :: proc(sb: ^strings.Builder, t: Func_Type) {
strings.write_string(sb, "(func")
defer strings.write_byte(sb, ')')
if len(t.params) > 0 {
strings.write_string(sb, " (param")
defer strings.write_byte(sb, ')')
for p in t.params {
strings.write_byte(sb, ' ')
strings.write_string(sb, valtype_name(p))
}
}
if len(t.results) > 0 {
strings.write_string(sb, " (result")
defer strings.write_byte(sb, ')')
for rt in t.results {
strings.write_byte(sb, ' ')
strings.write_string(sb, valtype_name(rt))
}
}
}
wat_write_imports :: proc(sb: ^strings.Builder, m: Module, unit: string) -> Parse_Error {
sec: Section
{
found := false
for s in m.sections {
if s.id == .IMPORT {
sec = s
found = true
break
}
}
if !found {
return nil
}
}
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_return
fi: u32
ti: u32
mi: u32
gi: u32
for _ in 0..<count {
mod := rd_name(&r) or_return
fld := rd_name(&r) or_return
kb := rd_byte(&r) or_return
fmt.sbprintf(sb, "%s(import %q %q (", unit, mod, fld)
defer strings.write_string(sb, "))\n")
switch External_Kind(kb) {
case .FUNC:
tidx, _ := rd_u32(&r)
strings.write_string(sb, "func")
name := ""
if int(fi) < len(m.functions) {
name = m.functions[fi].name
}
wat_write_id_or_comment(sb, name, fi)
fmt.sbprintf(sb, " (type %d)", tidx)
fi += 1
case .TABLE:
rt, _ := rd_byte(&r)
min, max, _ := rd_limits(&r)
fmt.sbprintf(sb, "table (;%d;) %d", ti, min)
if mx, ok := max.?; ok {
fmt.sbprintf(sb, " %d", mx)
}
fmt.sbprintf(sb, " %s", valtype_name(wasm.Value_Type(rt)))
ti += 1
case .MEMORY:
min, max, _ := rd_limits(&r)
fmt.sbprintf(sb, "memory (;%d;) %d", mi, min)
if mx, ok := max.?; ok {
fmt.sbprintf(sb, " %d", mx)
}
mi += 1
case .GLOBAL:
vt, _ := rd_byte(&r)
mut, _ := rd_byte(&r)
fmt.sbprintf(sb, "global (;%d;) ", gi)
if mut == 1 {
fmt.sbprintf(sb, "(mut %s)", valtype_name(wasm.Value_Type(vt)))
} else {
strings.write_string(sb, valtype_name(wasm.Value_Type(vt)))
}
gi += 1
}
}
return nil
}
wat_write_function :: proc(sb: ^strings.Builder, m: Module, func_relocs: []wasm.Relocation, f: Function, unit: string) {
strings.write_string(sb, unit)
strings.write_string(sb, "(func")
defer strings.write_string(sb, ")\n")
wat_write_id_or_comment(sb, f.name, f.func_index)
fmt.sbprintf(sb, " (type %d)", f.type_index)
if len(f.type.params) > 0 {
strings.write_string(sb, " (param")
defer strings.write_byte(sb, ')')
for p in f.type.params {
strings.write_byte(sb, ' ')
strings.write_string(sb, valtype_name(p))
}
}
if len(f.type.results) > 0 {
strings.write_string(sb, " (result")
defer strings.write_byte(sb, ')')
for rt in f.type.results {
strings.write_byte(sb, ' ')
strings.write_string(sb, valtype_name(rt))
}
}
if len(f.locals) > 0 {
strings.write_string(sb, " (local")
defer strings.write_byte(sb, ')')
for g in f.locals {
for _ in 0..<g.count {
strings.write_byte(sb, ' ')
strings.write_string(sb, valtype_name(g.type))
}
}
}
strings.write_byte(sb, '\n')
if f.body_size != 0 {
body := m.data[f.body_offset:][:f.body_size]
wat_write_body(sb, m, func_relocs, body, unit, level0=2)
}
strings.write_string(sb, unit)
}
wat_write_body :: proc(sb: ^strings.Builder, m: Module, func_relocs: []wasm.Relocation, body: []u8, unit: string, level0: int) {
context.allocator = context.temp_allocator // scratch trees live in temp
insts: [dynamic]wasm.Instruction
info: [dynamic]wasm.Instruction_Info
errs: [dynamic]wasm.Error
wasm.decode(body, func_relocs, &insts, &info, &errs, context.temp_allocator)
i := 0
stmts, _ := wat_fold_region(insts[:], m, &i)
for &s in stmts {
indent_str(sb, unit, level0)
wat_render(sb, &s, level0, unit)
strings.write_byte(sb, '\n')
}
}
WAT_Node_Kind :: enum u8 {
PLAIN,
BLOCK,
LOOP,
IF,
}
WAT_Node :: struct {
kind: WAT_Node_Kind,
head: string, // "(head" payload: e.g. "i32.add", "block (result i32)"
children: [dynamic]WAT_Node, // folded operands (PLAIN) / condition (IF)
body: [dynamic]WAT_Node, // block / loop / then body
els: [dynamic]WAT_Node, // if else body
results: int,
}
// Fold a straight-line region (a function body or a block body) into a list of top-level statement nodes, stopping at the matching `end` / `else`.
// `i` is advanced past the terminator. Returns the terminator that was consumed.
wat_fold_region :: proc(insts: []wasm.Instruction, m: Module, i: ^int) -> (out: [dynamic]WAT_Node, term: wasm.Mnemonic) {
stack: [dynamic]WAT_Node
defer delete(stack)
for i^ < len(insts) {
inst := insts[i^]
#partial switch inst.mnemonic {
case .END, .ELSE:
i^ += 1
append(&out, ..stack[:])
return out, inst.mnemonic
case .BLOCK, .LOOP:
_, results := block_sig(m, inst.ops[0].immediate)
head := render_head(inst)
i^ += 1
body, _ := wat_fold_region(insts, m, i)
node := &WAT_Node{
kind = .BLOCK if inst.mnemonic == .BLOCK else .LOOP,
head = head,
body = body,
results = results,
}
push_expr(&stack, &out, node, 0, results)
case .IF:
_, results := block_sig(m, inst.ops[0].immediate)
head := render_head(inst)
i^ += 1
then_body, t := wat_fold_region(insts, m, i)
else_body: [dynamic]WAT_Node
if t == .ELSE {
else_body, _ = wat_fold_region(insts, m, i)
}
node := &WAT_Node{
kind = .IF,
head = head,
body = then_body,
els = else_body,
results = results,
}
push_expr(&stack, &out, node, 1, results) // 1 = the condition
case:
op, res := instruction_arity(m, inst)
node := &WAT_Node{
kind = .PLAIN,
head = render_head(inst),
results = res,
}
i^ += 1
push_expr(&stack, &out, node, op, res)
}
}
append(&out, ..stack[:])
return out, .END
}
push_expr :: proc(stack, out: ^[dynamic]WAT_Node, node: ^WAT_Node, op, res: int) {
if op <= len(stack) {
start := len(stack) - op
append(&node.children, ..stack[start:])
resize(stack, start)
append(stack, node^)
} else {
append(out, ..stack[:])
clear(stack)
append(stack, node^)
}
if res == 0 {
append(out, ..stack[:])
clear(stack)
}
}
render_head :: proc(inst: wasm.Instruction) -> string {
for j in 0..<inst.operand_count {
op := inst.ops[j]
if op.kind == .INDEX {
op.flags.symbolic = false
}
}
sb := strings.builder_make(context.temp_allocator)
o := wasm.DEFAULT_PRINT_OPTIONS
o.indent = ""
o.separator = ""
wasm.sbprint(&sb, {inst}, nil, &o, nil)
return strings.to_string(sb)
}
@(require_results)
wat_node_is_simple :: proc(n: ^WAT_Node) -> bool {
(n.kind == .PLAIN) or_return
for &c in n.children {
wat_node_is_simple(&c) or_return
}
return true
}
wat_render :: proc(sb: ^strings.Builder, n: ^WAT_Node, level: int, unit: string) {
strings.write_byte(sb, '(')
defer strings.write_byte(sb, ')')
strings.write_string(sb, n.head)
switch n.kind {
case .PLAIN:
if wat_node_is_simple(n) {
for &c in n.children {
strings.write_byte(sb, ' ')
wat_render(sb, &c, level, unit)
}
} else {
for &c in n.children {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 1)
wat_render(sb, &c, level + 1, unit)
}
}
case .BLOCK, .LOOP:
for &s in n.body {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 1)
wat_render(sb, &s, level + 1, unit)
}
case .IF:
for &c in n.children {
if wat_node_is_simple(&c) {
strings.write_byte(sb, ' ')
wat_render(sb, &c, level, unit)
} else {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 1)
wat_render(sb, &c, level + 1, unit)
}
}
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 1)
{
strings.write_string(sb, "(then")
defer strings.write_byte(sb, ')')
for &s in n.body {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 2)
wat_render(sb, &s, level + 2, unit)
}
}
if len(n.els) > 0 {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 1)
strings.write_string(sb, "(else")
defer strings.write_byte(sb, ')')
for &s in n.els {
strings.write_byte(sb, '\n')
indent_str(sb, unit, level + 2)
wat_render(sb, &s, level + 2, unit)
}
}
}
}
wat_write_tables :: proc(sb: ^strings.Builder, m: Module, unit: string) {
idx := count_import_kind(m, .TABLE)
for sec in m.sections {
if sec.id != .TABLE {
continue
}
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_break
for _ in 0..<count {
rt := rd_byte(&r) or_break
min, max := rd_limits(&r) or_break
fmt.sbprintf(sb, "%s(table (;%d;) %d", unit, idx, min)
defer fmt.sbprintf(sb, " %s)\n", valtype_name(wasm.Value_Type(rt)))
if mx, ok := max.?; ok {
fmt.sbprintf(sb, " %d", mx)
}
idx += 1
}
}
}
wat_write_memories :: proc(sb: ^strings.Builder, m: Module, unit: string) {
idx := count_import_kind(m, .MEMORY)
for sec in m.sections {
if sec.id != .MEMORY {
continue
}
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_break
for _ in 0..<count {
min, max := rd_limits(&r) or_break
fmt.sbprintf(sb, "%s(memory (;%d;) %d", unit, idx, min)
defer strings.write_string(sb, ")\n")
if mx, ok := max.?; ok {
fmt.sbprintf(sb, " %d", mx)
}
idx += 1
}
}
}
wat_write_globals :: proc(sb: ^strings.Builder, m: Module, unit: string) {
idx := count_import_kind(m, .GLOBAL)
for sec in m.sections {
if sec.id != .GLOBAL {
continue
}
relocs := relocations_from_section_id(m.reloc_groups, sec.id)
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_break
for _ in 0..<count {
vt := rd_byte(&r) or_break
mut := rd_byte(&r) or_break
fmt.sbprintf(sb, "%s(global (;%d;) ", unit, idx)
defer strings.write_string(sb, ")\n")
if mut == 1 {
fmt.sbprintf(sb, "(mut %s) ", valtype_name(wasm.Value_Type(vt)))
} else {
fmt.sbprintf(sb, "%s ", valtype_name(wasm.Value_Type(vt)))
}
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
idx += 1
}
}
}
wat_write_elements :: proc(sb: ^strings.Builder, m: Module, unit: string) -> Parse_Error {
elem_idx := 0
for sec in m.sections {
if sec.id != .ELEMENT {
continue
}
relocs := relocations_from_section_id(m.reloc_groups, sec.id)
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_break
for _ in 0..<count {
flags := rd_u32(&r) or_break
if flags != 0 {
fmt.sbprintf(sb, "%s(; elem segment %d: unsupported flags=%d ;)\n", unit, elem_idx, flags)
return nil
}
fmt.sbprintf(sb, "%s(elem (;%d;) ", unit, elem_idx)
defer strings.write_string(sb, ")\n")
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
strings.write_string(sb, " func")
n := rd_u32(&r) or_break
for _ in 0..<n {
fidx := rd_u32(&r) or_return
fmt.sbprintf(sb, " %d", fidx)
}
elem_idx += 1
}
}
return nil
}
wat_write_data :: proc(sb: ^strings.Builder, m: Module, unit: string) -> Parse_Error {
data_idx := 0
for sec in m.sections {
if sec.id != .DATA {
continue
}
r := reader(m.data, sec.offset)
count := rd_u32(&r) or_break
for _ in 0..<count {
kind := rd_u32(&r) or_return
fmt.sbprintf(sb, "%s(data (;%d;)", unit, data_idx)
defer strings.write_string(sb, ")\n")
memidx: u32 = 0
switch kind {
case 2:
// memidx + expr + bytes
memidx, _ = rd_u32(&r)
fallthrough
case 0:
// expr + bytes
if memidx != 0 {
fmt.sbprintf(sb, " (memory %d)", memidx)
}
strings.write_byte(sb, ' ')
relocs := relocations_from_section_id(m.reloc_groups, sec.id)
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
case 1:
// bytes
case:
fmt.sbprintf(sb, "%s(; data segment %d: unsupported kind=%d ;)\n", unit, data_idx, kind)
return nil
}
size := rd_u32(&r) or_return
strings.write_byte(sb, ' ')
wat_write_quoted_string(sb, m.data[r.off:][:size])
r.off += size
data_idx += 1
}
}
return nil
}
// Decode and fold a constant expression at `off^` (advancing past its `end`).
wat_write_const_expr :: proc(sb: ^strings.Builder, m: Module, relocs: []wasm.Relocation, data: []u8, off: ^u32, unit: string, allocator: runtime.Allocator) {
context.allocator = context.temp_allocator
exprs: [dynamic]wasm.Instruction
exprs.allocator = allocator
defer delete(exprs)
for off^ < u32(len(data)) {
inst, _, next := wasm.decode_one(data, relocs, off^, allocator) or_break
off^ = next
if inst.mnemonic == .END {
break
}
append(&exprs, inst)
}
i := 0
stmts, _ := wat_fold_region(exprs[:], m, &i)
for &s, k in stmts {
if k > 0 { strings.write_byte(sb, ' ') }
wat_render(sb, &s, 0, unit)
}
}
wat_write_id_or_comment :: proc(sb: ^strings.Builder, name: string, index: u32) {
if name != "" && wat_ident_ok(name) {
strings.write_string(sb, " $")
strings.write_string(sb, name)
} else {
fmt.sbprintf(sb, " (;%d;)", index)
}
}
@(private)
indent_str :: proc(sb: ^strings.Builder, unit: string, level: int) {
for _ in 0..<level {
strings.write_string(sb, unit)
}
}
@(require_results)
wat_ident_ok :: proc(s: string) -> bool {
if len(s) == 0 {
return false
}
#no_bounds_check for i in 0..<len(s) {
c := s[i]
switch c {
case '0'..='9', 'A'..='Z', 'a'..='z':
// okay
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '/',
':', '<', '=', '>', '?', '@', '\\', '^', '_', '`', '|',
'~':
// okay
case:
return false
}
}
return true
}
wat_write_quoted_string :: proc(sb: ^strings.Builder, bytes: []u8) {
@(rodata, static)
HEX := "0123456789abcdef"
strings.write_byte(sb, '"')
for b in bytes {
switch b {
case '"': strings.write_string(sb, "\\\"")
case '\\': strings.write_string(sb, "\\\\")
case 0x20..<0x7F: strings.write_byte(sb, b)
case:
strings.write_byte(sb, '\\')
strings.write_byte(sb, HEX[b >> 4])
strings.write_byte(sb, HEX[b & 0xF])
}
}
strings.write_byte(sb, '"')
}

View File

@@ -1,103 +0,0 @@
package rexcode_wasm_module
import "base:runtime"
import "core:strings"
import wasm "core:rexcode/wasm"
Reloc_Group :: struct {
target_section: Section_Id, // index of the section these apply to
relocs: []wasm.Relocation,
}
@(require_results)
parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (reloc_groups: []Reloc_Group, err: Reader_Error) {
groups: [dynamic]Reloc_Group
groups.allocator = m.allocator
for sec in m.sections {
if !(sec.id == .CUSTOM && strings.has_prefix(sec.name, "reloc.")) {
continue
}
r := reader(m.data[sec.offset:][:sec.size], 0)
_ = rd_name(&r) or_return // step past the custom-section name
target := Section_Id(rd_u32(&r) or_return)
count := rd_u32(&r) or_return
out := make([]wasm.Relocation, int(count), m.allocator)
w := 0
for _ in 0..<count {
code := rd_byte(&r) or_return
offset := rd_u32(&r) or_return // offset of the field within target_section
index := rd_u32(&r) or_return // symbol / target index
addend: i32 = 0
if reloc_has_addend(code) {
addend = i32(rd_sleb(&r) or_return)
}
t := reloc_type_from_wire(code) or_continue
out[w] = wasm.Relocation{
offset = offset,
label_id = index,
addend = addend,
type = t,
size = reloc_field_size(t),
}
w += 1
}
append(&groups, Reloc_Group{target_section = target, relocs = out[:w]}) or_return
}
reloc_groups = groups[:]
return
}
relocations_destroy :: proc(groups: []Reloc_Group, allocator: runtime.Allocator) {
context.allocator = allocator
for g in groups { delete(g.relocs) }
delete(groups)
}
@(require_results)
relocations_from_section_id :: proc(reloc_groups: []Reloc_Group, sec_id: Section_Id) -> []wasm.Relocation {
for rg in reloc_groups {
if rg.target_section == sec_id {
return rg.relocs
}
}
return nil
}
@(require_results)
reloc_type_from_wire :: proc(code: u8) -> (wasm.Relocation_Type, bool) {
switch code {
case 0: return .FUNCTION_INDEX_LEB, true // R_WASM_FUNCTION_INDEX_LEB
case 1: return .TABLE_INDEX_SLEB, true // R_WASM_TABLE_INDEX_SLEB
case 2: return .TABLE_INDEX_I32, true // R_WASM_TABLE_INDEX_I32
case 3: return .MEMORY_ADDR_LEB, true // R_WASM_MEMORY_ADDR_LEB
case 4: return .MEMORY_ADDR_SLEB, true // R_WASM_MEMORY_ADDR_SLEB
case 5: return .MEMORY_ADDR_I32, true // R_WASM_MEMORY_ADDR_I32
case 6: return .TYPE_INDEX_LEB, true // R_WASM_TYPE_INDEX_LEB
case 7: return .GLOBAL_INDEX_LEB, true // R_WASM_GLOBAL_INDEX_LEB
case 20: return .TABLE_NUMBER_LEB, true // R_WASM_TABLE_NUMBER_LEB
}
return .NONE, false
}
// MEMORY_ADDR_* (3,4,5) and the *_OFFSET_I32 (8,9) forms carry a trailing
// signed-LEB addend; the index-type relocations do not.
@(require_results)
reloc_has_addend :: proc(code: u8) -> bool {
switch code {
case 3, 4, 5, 8, 9: return true
}
return false
}
@(require_results)
reloc_field_size :: proc(t: wasm.Relocation_Type) -> u8 {
#partial switch t {
case .TABLE_INDEX_I32, .MEMORY_ADDR_I32:
return 4 // 4-byte LE field
}
return 5 // 5-byte padded (S)LEB field
}

View File

@@ -1,191 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly OPERANDS
// =============================================================================
//
// WASM operands are not registers or addressing modes; they are *immediates*
// that follow the opcode in the byte stream:
//
// i32.const 42 IMMEDIATE immediate = 42 (signed LEB128)
// f64.const 3.14 IMMEDIATE immediate = bits(3.14) (8 LE bytes)
// local.get 0 INDEX index = 0, idx_kind = LOCAL (unsigned LEB128)
// call $f INDEX index = funcidx, idx_kind = FUNC
// br 1 INDEX index = 1, idx_kind = LABEL (branch depth)
// i32.load align=2 off=8 MEMARG memarg = {align = 2, offset = 8}
// block (result i32) BLOCK_TYPE block_type = .I32
//
// Branching in WASM is *structured*: `br`/`br_if`/`br_table` take a relative
// label depth (an unsigned immediate), not a PC-relative byte offset. There
// are therefore no PC-relative relocations and the isa label-inference path is
// not used; the array-index `Label_Definition` machinery is re-exported for
// contract parity but WASM control flow does not consume it.
//
// Relocations *are* real, but for the object-file index spaces (function /
// global / table / type / data / elem indices that a linker fixes up). An
// INDEX operand flagged `symbolic` carries a label id and is emitted as a
// fixed-width 5-byte LEB placeholder plus a Relocation entry. `op_label`
// (required by the contract) produces exactly such a symbolic function index.
Operand_Kind :: enum u8 {
NONE,
IMMEDIATE, // i32/i64/f32/f64 constant (floats stored as raw bits)
INDEX, // LEB128 unsigned index into one of the index spaces
MEMARG, // load/store alignment + offset pair
BLOCK_TYPE, // block / loop / if signature
}
// Which index space an INDEX operand addresses. Drives matching, relocation
// type selection, and printer annotation.
Index_Kind :: enum u8 {
NONE,
LOCAL,
GLOBAL,
FUNC,
TYPE,
TABLE,
MEMORY,
LABEL, // br / br_if / br_table relative depth
DATA,
ELEM,
}
Operand_Flags :: bit_field u8 {
symbolic: bool | 1, // INDEX value is a label id needing a relocation
is_float: bool | 1, // IMMEDIATE holds float bits (vs a signed integer)
_: u8 | 6,
}
// Load/store immediate: alignment hint (log2 bytes) + static offset.
Memarg :: struct #packed {
offset: u32,
align: u32,
}
#assert(size_of(Memarg) == 8)
// Block signature. Negative sentinels are the s33 single-byte forms; a
// non-negative value is a type index encoded as a positive signed LEB128.
Block_Type :: enum i64 {
EMPTY = -64, // 0x40
I32 = -1, // 0x7F
I64 = -2, // 0x7E
F32 = -3, // 0x7D
F64 = -4, // 0x7C
V128 = -5, // 0x7B
FUNCREF = -16, // 0x70
EXTERNREF = -17, // 0x6F
}
Operand :: struct #packed {
using _: struct #raw_union {
reg: Register, // REGISTER (vestigial)
memarg: Memarg, // MEMARG
immediate: i64, // IMMEDIATE (int value or float bits) / BLOCK_TYPE (s33)
index: u32, // INDEX (value, or label id when symbolic)
},
kind: Operand_Kind,
idx_kind: Index_Kind,
size: u8, // value width in bytes where meaningful (4/8)
flags: Operand_Flags,
}
#assert(size_of(Operand) == 12)
// -----------------------------------------------------------------------------
// Generic constructors (contract surface)
// -----------------------------------------------------------------------------
@(require_results)
op_imm :: #force_inline proc "contextless" (v: i64, size: u8) -> Operand {
return Operand{immediate = v, kind = .IMMEDIATE, size = size}
}
@(require_results)
op_mem :: #force_inline proc "contextless" (m: Memarg, size: u8 = 0) -> Operand {
return Operand{memarg = m, kind = .MEMARG, size = size}
}
// Symbolic function reference: emitted as a relocatable funcidx placeholder.
@(require_results)
op_label :: #force_inline proc "contextless" (label_id: u32, size: u8 = 5) -> Operand {
return Operand{index = label_id, kind = .INDEX, idx_kind = .FUNC, size = size, flags = {symbolic = true}}
}
// -----------------------------------------------------------------------------
// Numeric constants
// -----------------------------------------------------------------------------
@(require_results)
op_i32 :: #force_inline proc "contextless" (v: i32) -> Operand {
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 4}
}
@(require_results)
op_i64 :: #force_inline proc "contextless" (v: i64) -> Operand {
return Operand{immediate = v, kind = .IMMEDIATE, size = 8}
}
@(require_results)
op_f32 :: #force_inline proc "contextless" (v: f32) -> Operand {
return Operand{immediate = i64(transmute(u32)v), kind = .IMMEDIATE, size = 4, flags = {is_float = true}}
}
@(require_results)
op_f64 :: #force_inline proc "contextless" (v: f64) -> Operand {
return Operand{immediate = transmute(i64)v, kind = .IMMEDIATE, size = 8, flags = {is_float = true}}
}
// -----------------------------------------------------------------------------
// Memory argument + block type
// -----------------------------------------------------------------------------
@(require_results)
memarg :: #force_inline proc "contextless" (align, offset: u32) -> Memarg {
return Memarg{align = align, offset = offset}
}
@(require_results)
op_memarg :: #force_inline proc "contextless" (align, offset: u32) -> Operand {
return Operand{memarg = Memarg{align = align, offset = offset}, kind = .MEMARG}
}
@(require_results)
op_blocktype :: #force_inline proc "contextless" (bt: Block_Type) -> Operand {
return Operand{immediate = i64(bt), kind = .BLOCK_TYPE}
}
@(require_results)
op_block_typeidx :: #force_inline proc "contextless" (type_index: u32) -> Operand {
return Operand{immediate = i64(type_index), kind = .BLOCK_TYPE}
}
// -----------------------------------------------------------------------------
// Index-space constructors (one per space; all unsigned LEB128 on the wire)
// -----------------------------------------------------------------------------
@(require_results)
op_index :: #force_inline proc "contextless" (kind: Index_Kind, value: u32) -> Operand {
return Operand{index = value, kind = .INDEX, idx_kind = kind}
}
@(require_results) op_local :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.LOCAL, n) }
@(require_results) op_global :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.GLOBAL, n) }
@(require_results) op_func :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.FUNC, n) }
@(require_results) op_type :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.TYPE, n) }
@(require_results) op_table :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.TABLE, n) }
@(require_results) op_memory :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.MEMORY, n) }
@(require_results) op_data :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.DATA, n) }
@(require_results) op_elem :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.ELEM, n) }
// Branch label depth (number of enclosing blocks to break out of).
@(require_results) op_labelidx :: #force_inline proc "contextless" (depth: u32) -> Operand { return op_index(.LABEL, depth) }
// ref.null heap type (encoded as a single value-type byte).
@(require_results)
op_reftype :: #force_inline proc "contextless" (t: Value_Type) -> Operand {
return Operand{immediate = i64(t), kind = .IMMEDIATE, size = 1}
}
// SIMD lane index (single byte) for extract_lane / replace_lane / load_lane /
// store_lane operators.
@(require_results)
op_lane :: #force_inline proc "contextless" (n: u8) -> Operand {
return Operand{immediate = i64(n), kind = .IMMEDIATE, size = 1}
}

View File

@@ -1,351 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
import "core:strings"
import "core:strconv"
import "core:os"
import "core:io"
import "core:rexcode/isa"
// =============================================================================
// WebAssembly PRINTER
// =============================================================================
//
// Emits WebAssembly text-format (WAT) instruction syntax: the folded-stack
// form is not reconstructed (that needs structure the linear stream does not
// carry); instead each instruction prints on its own line as
//
// <mnemonic> <immediate>*
//
// Examples:
//
// i32.const 42
// local.get 0
// i32.add
// call 3
// block (result i32)
// i32.load offset=8 align=2
// br_table 0 1 2 ; cases 0 1, default 2
// ref.null func
// f64.const 3.14
//
// Mnemonic spelling comes from the explicit MNEMONIC_NAMES table (WASM mixes
// '.' and '_' irregularly, e.g. `local.get` vs `i32.trunc_f32_s`). WASM has
// no register file, so register printing is vestigial.
Token :: isa.Token
Token_Kind :: isa.Token_Kind
Print_Options :: isa.Print_Options
Print_Result :: isa.Print_Result
DEFAULT_PRINT_OPTIONS :: isa.DEFAULT_PRINT_OPTIONS
@(require_results)
mnemonic_to_string :: proc(m: Mnemonic, lowercase: bool = true, allocator := context.temp_allocator) -> string {
sb := strings.builder_make(allocator)
write_mnemonic(&sb, m, !lowercase)
return strings.to_string(sb)
}
// =============================================================================
// Core sbprint
// =============================================================================
sbprint :: proc(
sb: ^strings.Builder,
instructions: []Instruction,
inst_info: []Instruction_Info,
options: ^Print_Options = nil,
label_names: ^map[u32]string = nil,
) {
opts := options
if opts == nil {
@(static) defaults := DEFAULT_PRINT_OPTIONS
opts = &defaults
}
for &inst, i in instructions {
offset := inst_info[i].offset if i < len(inst_info) else u32(0)
strings.write_string(sb, opts.indent)
if opts.show_offsets {
isa.print_hex(sb, u64(offset), opts)
strings.write_string(sb, ": ")
}
write_mnemonic(sb, inst.mnemonic, opts.uppercase)
// br_table prints its case vector followed by the default depth.
#partial switch inst.mnemonic {
case .BR_TABLE:
for t in inst.targets {
strings.write_byte(sb, ' ')
strings.write_u64(sb, u64(t))
}
strings.write_byte(sb, ' ')
strings.write_u64(sb, u64(inst.ops[0].index))
case .V128_CONST:
strings.write_string(sb, " i8x16")
for bb in inst.bytes {
strings.write_byte(sb, ' ')
isa.print_hex(sb, u64(bb), opts)
}
case .I8X16_SHUFFLE:
for bb in inst.bytes {
strings.write_byte(sb, ' ')
strings.write_u64(sb, u64(u32(bb)))
}
case .CALL_INDIRECT:
case:
for slot in 0..<inst.operand_count {
strings.write_byte(sb, ' ')
write_operand(sb, &inst.ops[slot], inst.mnemonic, label_names, opts)
}
}
strings.write_string(sb, opts.separator)
}
}
sbprintln :: proc(
sb: ^strings.Builder,
instructions: []Instruction,
inst_info: []Instruction_Info,
options: ^Print_Options = nil,
label_names: ^map[u32]string = nil,
) {
sbprint(sb, instructions, inst_info, options, label_names)
strings.write_byte(sb, '\n')
}
// =============================================================================
// Sink wrappers
// =============================================================================
print :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprint(&sb, instructions, inst_info, options, label_names)
os.write_string(os.stdout, strings.to_string(sb))
}
println :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprintln(&sb, instructions, inst_info, options, label_names)
os.write_string(os.stdout, strings.to_string(sb))
}
aprint :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
allocator := context.allocator,
) -> string {
sb := strings.builder_make(allocator)
sbprint(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
aprintln :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
allocator := context.allocator,
) -> string {
sb := strings.builder_make(allocator)
sbprintln(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
tprint :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) -> string {
sb := strings.builder_make(context.temp_allocator)
sbprint(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
tprintln :: proc(
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) -> string {
sb := strings.builder_make(context.temp_allocator)
sbprintln(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
bprint :: proc(
buf: []u8,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) -> string {
sb := strings.builder_from_bytes(buf)
sbprint(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
bprintln :: proc(
buf: []u8,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) -> string {
sb := strings.builder_from_bytes(buf)
sbprintln(&sb, instructions, inst_info, options, label_names)
return strings.to_string(sb)
}
fprint :: proc(
fd: ^os.File,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprint(&sb, instructions, inst_info, options, label_names)
os.write_string(fd, strings.to_string(sb))
}
fprintln :: proc(
fd: ^os.File,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprintln(&sb, instructions, inst_info, options, label_names)
os.write_string(fd, strings.to_string(sb))
}
wprint :: proc(
w: io.Writer,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprint(&sb, instructions, inst_info, options, label_names)
io.write_string(w, strings.to_string(sb))
}
wprintln :: proc(
w: io.Writer,
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
) {
sb := strings.builder_make(context.temp_allocator)
sbprintln(&sb, instructions, inst_info, options, label_names)
io.write_string(w, strings.to_string(sb))
}
// =============================================================================
// Internal writers
// =============================================================================
write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) {
name := MNEMONIC_NAMES[m]
if name == "" { strings.write_string(sb, "<?>"); return }
if uppercase {
for i in 0..<len(name) {
c := name[i] // to force ASCII
if 'a' <= c && c <= 'z' {
strings.write_byte(sb, c - 32)
} else {
strings.write_byte(sb, c)
}
}
} else {
strings.write_string(sb, name)
}
}
write_operand :: proc(
sb: ^strings.Builder,
op: ^Operand,
mnemonic: Mnemonic,
label_names: ^map[u32]string,
opts: ^Print_Options,
) {
switch op.kind {
case .NONE:
case .IMMEDIATE:
if mnemonic == .REF_NULL {
write_heap_type(sb, u8(op.immediate))
} else if op.flags.is_float {
write_float(sb, op)
} else {
strings.write_i64(sb, op.immediate)
}
case .INDEX:
if op.flags.symbolic {
write_label(sb, op.index, label_names, opts)
} else {
strings.write_u64(sb, u64(op.index))
}
case .MEMARG:
// WAT prints non-trivial memargs as `offset=N align=N`
// omitting either when it is the natural default is a refinement.
if op.memarg.offset != 0 {
strings.write_string(sb, "offset=")
strings.write_u64(sb, u64(op.memarg.offset))
}
if op.memarg.align != 0 {
strings.write_string(sb, " align=")
strings.write_u64(sb, u64(op.memarg.align))
}
case .BLOCK_TYPE:
write_block_type(sb, op.immediate)
}
}
write_block_type :: proc(sb: ^strings.Builder, v: i64) {
switch Block_Type(v) {
case .EMPTY: // no result annotation
case .I32: strings.write_string(sb, "(result i32)")
case .I64: strings.write_string(sb, "(result i64)")
case .F32: strings.write_string(sb, "(result f32)")
case .F64: strings.write_string(sb, "(result f64)")
case .V128: strings.write_string(sb, "(result v128)")
case .FUNCREF: strings.write_string(sb, "(result funcref)")
case .EXTERNREF: strings.write_string(sb, "(result externref)")
case:
// non-negative: a type index
strings.write_string(sb, "(type ")
strings.write_u64(sb, u64(u32(v)))
strings.write_byte(sb, ')')
}
}
write_heap_type :: proc(sb: ^strings.Builder, b: u8) {
#partial switch Value_Type(b) {
case .FUNCREF: strings.write_string(sb, "func")
case .EXTERNREF: strings.write_string(sb, "extern")
case:
strings.write_u64(sb, u64(u32(b)))
}
}
write_float :: proc(sb: ^strings.Builder, op: ^Operand) {
buf: [40]u8
if op.size == 4 {
f := transmute(f32)u32(op.immediate)
s := strconv.write_float(buf[:], f64(f), 'g', -1, 32)
strings.write_string(sb, s)
} else {
f := transmute(f64)u64(op.immediate)
s := strconv.write_float(buf[:], f, 'g', -1, 64)
strings.write_string(sb, s)
}
}
write_label :: proc(
sb: ^strings.Builder,
label_id: u32,
label_names: ^map[u32]string,
opts: ^Print_Options,
) {
if label_names != nil {
if name, ok := label_names^[label_id]; ok {
strings.write_string(sb, "$")
strings.write_quoted_string(sb, name)
return
}
}
strings.write_string(sb, "$")
strings.write_u64(sb, u64(label_id))
}

View File

@@ -1,76 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly "REGISTERS"
// =============================================================================
//
// WebAssembly is a stack machine: it has no general-purpose register file.
// Operands live on an implicit value stack and instructions reference locals,
// globals, and various index spaces by LEB128 immediate -- never by register.
//
// The cross-arch naming contract still asks every package for a `Register`
// type plus `reg_hw` / `reg_class` accessors, so we keep the same packed
// `distinct u16` scheme (class in the high byte, index in the low byte) used
// by the register-machine arches. It is *vestigial* here: the REGISTER
// operand kind is never produced by the encoder or decoder, and the value
// stack is modelled implicitly. The real per-arch content WASM cares about --
// value types and the index spaces -- lives below and in operands.odin.
Register :: distinct u16
REG_NONE :: 0x0000
NONE :: Register(0xFFFF)
@(require_results)
reg_hw :: #force_inline proc "contextless" (r: Register) -> u8 {
return u8(r) & 0xFF
}
@(require_results)
reg_class :: #force_inline proc "contextless" (r: Register) -> u16 {
return u16(r) & 0xFF00
}
@(require_results)
reg_size :: #force_inline proc "contextless" (_: Register) -> u8 {
return 0 // no fixed width: the value stack is implicit
}
// -----------------------------------------------------------------------------
// Value types (the bytes WASM actually uses where a register would otherwise
// appear: block result/param types, ref.null heap types, select t* types).
//
// The numeric byte is the WASM binary encoding; the same byte sign-extends to
// the negative s33 value used inside a blocktype. See operands.odin /
// Block_Type for how these participate in block / loop / if.
// -----------------------------------------------------------------------------
Value_Type :: enum u8 {
I32 = 0x7F,
I64 = 0x7E,
F32 = 0x7D,
F64 = 0x7C,
V128 = 0x7B,
FUNCREF = 0x70,
EXTERNREF = 0x6F,
}
@(require_results)
value_type_is_num :: #force_inline proc "contextless" (t: Value_Type) -> bool {
#partial switch t {
case .I32, .I64, .F32, .F64: return true
}
return false
}
@(require_results)
value_type_is_ref :: #force_inline proc "contextless" (t: Value_Type) -> bool {
#partial switch t {
case .FUNCREF, .EXTERNREF: return true
}
return false
}

View File

@@ -1,44 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
// Ginger Bill (gingerBill@github)
package rexcode_wasm
// =============================================================================
// WebAssembly RELOCATIONS
// =============================================================================
//
// Per the cross-arch design (§2.4) each arch owns its Relocation_Type. WASM's
// relocations are the object-file ("linking") relocations: symbolic index
// references the linker fixes up. They are emitted, never PC-relative -- WASM
// control flow uses structured label depths, not byte offsets, so the encoder
// does not resolve these in a pass 2; it records them and leaves the patching
// to the linker. The relocatable LEB encodings are written as fixed-width
// 5-byte placeholders so the patched value always fits.
//
// The subset modelled mirrors the names from the tool-conventions linking
// spec used by LLVM / wasm-ld.
Relocation_Type :: enum u8 {
NONE = 0,
FUNCTION_INDEX_LEB, // funcidx, 5-byte ULEB (call, ref.func)
TABLE_INDEX_SLEB, // 5-byte SLEB table element index
TABLE_INDEX_I32, // 4-byte LE table element index
MEMORY_ADDR_LEB, // linear-memory address, 5-byte ULEB
MEMORY_ADDR_SLEB, // linear-memory address, 5-byte SLEB
MEMORY_ADDR_I32, // linear-memory address, 4-byte LE
TYPE_INDEX_LEB, // typeidx, 5-byte ULEB (call_indirect)
GLOBAL_INDEX_LEB, // globalidx, 5-byte ULEB
TABLE_NUMBER_LEB, // tableidx, 5-byte ULEB
}
Relocation :: struct #packed {
offset: u32, // byte offset of the relocatable field
label_id: u32, // symbol / target label id
addend: i32,
type: Relocation_Type,
size: u8, // bytes occupied by the field (5 for LEB, 4 for I32)
inst_idx: u16,
}
#assert(size_of(Relocation) == 16)

View File

@@ -1,142 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm_tests
// End-to-end WASM pipeline: build a short instruction sequence, encode it,
// assert the exact byte stream against hand-computed LEB128 encodings, then
// decode the bytes back and confirm the mnemonics/operands round-trip, and
// finally print the decoded form and check the WAT text.
//
// Covers: nullary ops, signed-LEB constants, index immediates, a blocktype,
// a memarg, and the br_table vector form.
//
// Run with: odin run wasm/tests
import "core:fmt"
import "core:os"
import wasm "../"
@(private="file") rpasses := 0
@(private="file") rfailures := 0
@(private="file")
ok :: proc(name: string, cond: bool) {
if cond {
fmt.printfln(" [ok] %s", name)
rpasses += 1
} else {
fmt.printfln(" [FAIL] %s", name)
rfailures += 1
}
}
@(private="file")
eq_bytes :: proc(name: string, got, want: []u8) {
if string(got) == string(want) {
fmt.printfln(" [ok] %s (% x)", name, got)
rpasses += 1
} else {
fmt.printfln(" [FAIL] %-18s got=[% x] want=[% x]", name, got, want)
rfailures += 1
}
}
@(private="file")
eq_str :: proc(name, got, want: string) {
if got == want {
fmt.printfln(" [ok] %-18s %q", name, got)
rpasses += 1
} else {
fmt.printfln(" [FAIL] %-18s got=%q want=%q", name, got, want)
rfailures += 1
}
}
main :: proc() {
fmt.println("== wasm encode/decode/print pipeline ==")
insts := []wasm.Instruction{
wasm.inst_i(.I32_CONST, wasm.op_i32(42)), // 0x41 0x2A
wasm.inst_idx(.LOCAL_GET, wasm.op_local(0)), // 0x20 0x00
wasm.inst_none(.I32_ADD), // 0x6A
wasm.inst_idx(.CALL, wasm.op_func(3)), // 0x10 0x03
wasm.inst_block(.BLOCK, .I32), // 0x02 0x7F
wasm.inst_memarg(.I32_LOAD, wasm.memarg(2, 8)), // 0x28 0x02 0x08
wasm.inst_none(.END), // 0x0B
}
code := make([]u8, wasm.encode_max_code_size(len(insts)))
defer delete(code)
relocs: [dynamic]wasm.Relocation
errors: [dynamic]wasm.Error
defer delete(relocs)
defer delete(errors)
n, enc_ok := wasm.encode(insts, nil, code, &relocs, &errors)
ok("encode ok", enc_ok && len(errors) == 0)
want := []u8{
0x41, 0x2A,
0x20, 0x00,
0x6A,
0x10, 0x03,
0x02, 0x7F,
0x28, 0x02, 0x08,
0x0B,
}
eq_bytes("byte stream", code[:n], want)
// ---- br_table on its own (vector immediate) ----------------------------
bt := []wasm.Instruction{
wasm.inst_br_table([]u32{0, 1}, 2), // 0x0E 0x02 0x00 0x01 0x02
}
bt_code := make([]u8, 32)
defer delete(bt_code)
bt_relocs: [dynamic]wasm.Relocation
bt_errors: [dynamic]wasm.Error
defer delete(bt_relocs)
defer delete(bt_errors)
bn, _ := wasm.encode(bt, nil, bt_code, &bt_relocs, &bt_errors)
eq_bytes("br_table bytes", bt_code[:bn], []u8{0x0E, 0x02, 0x00, 0x01, 0x02})
// ---- decode round-trip --------------------------------------------------
dinsts: [dynamic]wasm.Instruction
dinfo: [dynamic]wasm.Instruction_Info
dlabels:[dynamic]wasm.Label_Definition
derrs: [dynamic]wasm.Error
defer delete(dinsts)
defer delete(dinfo)
defer delete(dlabels)
defer delete(derrs)
dn, dec_ok := wasm.decode(code[:n], nil, &dinsts, &dinfo, &dlabels, &derrs)
ok("decode ok", dec_ok && len(derrs) == 0)
ok("decode byte count", dn == n)
ok("decode count", len(dinsts) == len(insts))
if len(dinsts) == len(insts) {
ok("m[0] i32.const", dinsts[0].mnemonic == .I32_CONST && dinsts[0].ops[0].immediate == 42)
ok("m[1] local.get", dinsts[1].mnemonic == .LOCAL_GET && dinsts[1].ops[0].index == 0)
ok("m[2] i32.add", dinsts[2].mnemonic == .I32_ADD && dinsts[2].operand_count == 0)
ok("m[3] call", dinsts[3].mnemonic == .CALL && dinsts[3].ops[0].idx_kind == .FUNC)
ok("m[4] block", dinsts[4].mnemonic == .BLOCK && dinsts[4].ops[0].kind == .BLOCK_TYPE)
ok("m[5] i32.load", dinsts[5].mnemonic == .I32_LOAD && dinsts[5].ops[0].memarg.offset == 8)
ok("m[6] end", dinsts[6].mnemonic == .END)
}
// ---- print --------------------------------------------------------------
text := wasm.tprint(dinsts[:], dinfo[:], dlabels[:])
want_text :=
" i32.const 42\n" +
" local.get 0\n" +
" i32.add\n" +
" call 3\n" +
" block (result i32)\n" +
" i32.load offset=8 align=2\n" +
" end\n"
eq_str("disassembly", text, want_text)
fmt.printfln("\n%d passed, %d failed", rpasses, rfailures)
if rfailures > 0 { os.exit(1) }
}

View File

@@ -1,86 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
// Ginger Bill (gingerBill@github)
package rexcode_wasm_tests
// Spot-check ENCODING_TABLE entries against the canonical opcode bytes from
// the WebAssembly core specification (binary format, §5.4). One or two
// representatives from each opcode region, plus both 0xFC misc endpoints.
//
// Run with: odin run wasm/tests
import "core:fmt"
import "core:os"
import wasm "../"
@(private="file") passes := 0
@(private="file") failures := 0
@(private="file")
check :: proc(name: string, m: wasm.Mnemonic, want_prefix: u8, want_opcode: u16) {
e := wasm.ENCODING_TABLE[m]
if e.prefix != want_prefix || e.opcode != want_opcode {
fmt.printfln(" [FAIL] %-22s got prefix=%02x op=%02x want prefix=%02x op=%02x",
name, e.prefix, e.opcode, want_prefix, want_opcode)
failures += 1
return
}
fmt.printfln(" [ok] %-22s prefix=%02x op=%02x", name, e.prefix, e.opcode)
passes += 1
}
main :: proc() {
fmt.println("== wasm encoding-table spot checks ==")
// control
check("unreachable", .UNREACHABLE, 0x00, 0x00)
check("block", .BLOCK, 0x00, 0x02)
check("br_table", .BR_TABLE, 0x00, 0x0E)
check("call", .CALL, 0x00, 0x10)
check("call_indirect", .CALL_INDIRECT, 0x00, 0x11)
// parametric / variable
check("drop", .DROP, 0x00, 0x1A)
check("local.get", .LOCAL_GET, 0x00, 0x20)
check("global.set", .GLOBAL_SET, 0x00, 0x24)
// memory
check("i32.load", .I32_LOAD, 0x00, 0x28)
check("i64.store32", .I64_STORE32, 0x00, 0x3E)
check("memory.size", .MEMORY_SIZE, 0x00, 0x3F)
check("memory.grow", .MEMORY_GROW, 0x00, 0x40)
// numeric
check("i32.const", .I32_CONST, 0x00, 0x41)
check("f64.const", .F64_CONST, 0x00, 0x44)
check("i32.add", .I32_ADD, 0x00, 0x6A)
check("i64.mul", .I64_MUL, 0x00, 0x7E)
check("f32.add", .F32_ADD, 0x00, 0x92)
check("f64.sqrt", .F64_SQRT, 0x00, 0x9F)
// conversions / sign-extension / reftypes
check("i32.wrap_i64", .I32_WRAP_I64, 0x00, 0xA7)
check("i32.extend8_s", .I32_EXTEND8_S, 0x00, 0xC0)
check("ref.null", .REF_NULL, 0x00, 0xD0)
check("ref.func", .REF_FUNC, 0x00, 0xD2)
// 0xFC misc group endpoints
check("i32.trunc_sat_f32_s", .I32_TRUNC_SAT_F32_S, 0xFC, 0)
check("memory.init", .MEMORY_INIT, 0xFC, 8)
check("table.fill", .TABLE_FILL, 0xFC, 17)
// 0xFD SIMD group
check("v128.load", .V128_LOAD, 0xFD, 0x00)
check("v128.const", .V128_CONST, 0xFD, 0x0C)
check("i8x16.shuffle", .I8X16_SHUFFLE, 0xFD, 0x0D)
check("i32x4.add", .I32X4_ADD, 0xFD, 0xAE)
check("simd hi (relaxed)", .I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S, 0xFD, 0x113)
// 0xFE threads / atomics group
check("memory.atomic.notify", .MEMORY_ATOMIC_NOTIFY, 0xFE, 0x00)
check("atomic.fence", .ATOMIC_FENCE, 0xFE, 0x03)
check("i32.atomic.load", .I32_ATOMIC_LOAD, 0xFE, 0x10)
fmt.printfln("\n%d passed, %d failed", passes, failures)
if failures > 0 { os.exit(1) }
}

View File

@@ -1,99 +0,0 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package main
// =============================================================================
// WebAssembly verification manifest dumper
// =============================================================================
//
// Encodes one representative instruction per mnemonic (synthesising operands
// that fit the entry's immediate layout) and writes:
//
// /tmp/rexcode_wasm_input.hex -- comma-separated LE hex bytes, one row each
// /tmp/rexcode_wasm_meta.txt -- "<mnemonic>\t<prefix>\t<opcode>\t<size>"
//
// The canonical external oracle for cross-checking these bytes is wabt's
// `wasm-objdump` / `wasm2wat`, or LLVM's `llvm-mc -triple=wasm32`. Feed the
// hex rows through the disassembler and diff its mnemonics against the meta
// file.
//
// Run: cd wasm && odin run tools/dump_verify_input.odin -file
import "core:fmt"
import "core:os"
import "core:strings"
import w "../"
main :: proc() {
fmt.println("Dumping WASM verification manifest...")
hex_buf, meta_buf: strings.Builder
strings.builder_init(&hex_buf)
strings.builder_init(&meta_buf)
defer strings.builder_destroy(&hex_buf)
defer strings.builder_destroy(&meta_buf)
code: [32]u8
count := 0
for mn in w.Mnemonic {
if mn == .INVALID { continue }
form := w.ENCODING_TABLE[mn]
inst := synth(mn, form)
one := []w.Instruction{inst}
relocs: [dynamic]w.Relocation
errors: [dynamic]w.Error
defer delete(relocs)
defer delete(errors)
n := w.encode(one, nil, code[:], &relocs, &errors) or_continue
for i in 0..<n {
if i > 0 { strings.write_byte(&hex_buf, ',') }
fmt.sbprintf(&hex_buf, "0x%02x", code[i])
}
strings.write_byte(&hex_buf, '\n')
fmt.sbprintf(&meta_buf, "%v\t0x%02x\t0x%02x\t%d\n", mn, form.prefix, form.opcode, n)
count += 1
}
_ = os.write_entire_file("/tmp/rexcode_wasm_input.hex", hex_buf.buf[:])
_ = os.write_entire_file("/tmp/rexcode_wasm_meta.txt", meta_buf.buf[:])
fmt.printf("Wrote %d entries.\n", count)
}
// Build a minimal valid instruction for `mn` whose operands satisfy the
// immediate layout in `form`.
synth :: proc(mn: w.Mnemonic, form: w.Encoding) -> w.Instruction {
if mn == .BR_TABLE {
@(static) tbl := [1]u32{0}
return w.inst_br_table(tbl[:], 0)
}
inst := w.Instruction{mnemonic = mn}
slot := 0
for k in form.imm {
switch k {
case .NONE, .ZERO_BYTE:
// no operand
case .BLOCKTYPE: inst.ops[slot] = w.op_blocktype(.EMPTY); slot += 1
case .I32: inst.ops[slot] = w.op_i32(1); slot += 1
case .I64: inst.ops[slot] = w.op_i64(1); slot += 1
case .F32: inst.ops[slot] = w.op_f32(1); slot += 1
case .F64: inst.ops[slot] = w.op_f64(1); slot += 1
case .IDX: inst.ops[slot] = w.op_func(0); slot += 1
case .MEMARG: inst.ops[slot] = w.op_memarg(0, 0); slot += 1
case .REFTYPE: inst.ops[slot] = w.op_reftype(.FUNCREF); slot += 1
case .LANE: inst.ops[slot] = w.op_lane(0); slot += 1
case .LANES16: // 16-byte value lives in inst.bytes (left zero), no operand
case .BR_TABLE: // handled above
}
}
inst.operand_count = u8(slot)
return inst
}