diff --git a/core/rexcode/ir/wasm/builder.odin b/core/rexcode/ir/wasm/builder.odin new file mode 100644 index 000000000..ff2b8ff26 --- /dev/null +++ b/core/rexcode/ir/wasm/builder.odin @@ -0,0 +1,155 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +import "base:runtime" + +// ============================================================================= +// SECTION: Builder (constructing ir.Operations for a WASM function body) +// ============================================================================= +// +// The IR leaf is `ir.Operation` -- an opcode (u16), a variable-arity `[]Operand` +// (caller-owned, unlike the old ISA `Instruction`'s inline [2]Operand), and an +// optional typed `Result` (always `.id == ID_NONE` here: WASM is a stack machine +// and names no results). So every builder that carries operands must allocate +// their backing store; the `Builder` below owns that store and accumulates ops +// into the current block, mirroring `spirv.Builder`. + +// Operation flags derived from the opcode's ENCODING_TABLE form, plus the +// terminator bit for the ops that end a stack-IR region. +@(require_results) +op_flags_for :: proc "contextless" (opcode: Opcode) -> Operation_Flags { + e := ENCODING_TABLE[opcode] + f: Operation_Flags + f.control = e.flags.control + f.memory = e.flags.memory + #partial switch opcode { + case .RETURN, .UNREACHABLE, + .BR, .BR_TABLE, + .RETURN_CALL, .RETURN_CALL_INDIRECT: + f.terminator = true + } + return f +} + +// The stack-IR result: WASM defines no SSA value, so every operation's result is +// empty. (Kept as a helper so the intent is explicit at every call site.) +@(require_results) +no_result :: #force_inline proc "contextless" () -> Result { + return Result{id = ID_NONE, type = TYPE_NONE} +} + +// ----------------------------------------------------------------------------- +// Stateless construction (caller owns `operands`) +// ----------------------------------------------------------------------------- + +// Build an Operation over an already-owned operand slice (no allocation). +@(require_results) +operation :: #force_inline proc "contextless" (opcode: Opcode, operands: []Operand = nil) -> Operation { + return Operation{ + opcode = u16(opcode), + operands = operands, + result = no_result(), + flags = op_flags_for(opcode), + } +} + +// ----------------------------------------------------------------------------- +// Builder +// ----------------------------------------------------------------------------- + +Builder :: struct { + alloc: runtime.Allocator, + ops: [dynamic]Operation, // current block +} + +@(require_results) +builder_make :: proc(allocator := context.allocator) -> Builder { + b: Builder + b.alloc = allocator + b.ops.allocator = allocator + return b +} + +// Detach the accumulated operations as a block body (and reset for the next). +@(require_results) +take_block :: proc(b: ^Builder, label: Id = ID_NONE) -> Block { + blk := Block{id = label, ops = b.ops[:]} + b.ops = nil + b.ops.allocator = b.alloc + return blk +} + +// Stable per-operation operand backing. +@(require_results) +opbuf :: proc(b: ^Builder, ops: ..Operand) -> []Operand { + if len(ops) == 0 { + return nil + } + buf := make([]Operand, len(ops), b.alloc) + copy(buf, ops) + return buf +} + +// Append an operation with the given operands, allocating their backing. +emit :: proc(b: ^Builder, opcode: Opcode, operands: ..Operand) { + append(&b.ops, operation(opcode, opbuf(b, ..operands))) +} + +// ----------------------------------------------------------------------------- +// Convenience emitters (a representative set; `emit` covers the rest) +// ----------------------------------------------------------------------------- + +emit_none :: proc(b: ^Builder, opcode: Opcode) { emit(b, opcode) } + +emit_i32 :: proc(b: ^Builder, v: i32) { emit(b, .I32_CONST, op_i32(v)) } +emit_i64 :: proc(b: ^Builder, v: i64) { emit(b, .I64_CONST, op_i64(v)) } +emit_f32 :: proc(b: ^Builder, v: f32) { emit(b, .F32_CONST, op_f32(v)) } +emit_f64 :: proc(b: ^Builder, v: f64) { emit(b, .F64_CONST, op_f64(v)) } + +emit_local_get :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_GET, op_local(n)) } +emit_local_set :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_SET, op_local(n)) } +emit_local_tee :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_TEE, op_local(n)) } +emit_global_get :: proc(b: ^Builder, n: u32) { emit(b, .GLOBAL_GET, op_global(n)) } +emit_global_set :: proc(b: ^Builder, n: u32) { emit(b, .GLOBAL_SET, op_global(n)) } + +emit_call :: proc(b: ^Builder, funcidx: u32) { emit(b, .CALL, op_func(funcidx)) } +emit_call_indirect :: proc(b: ^Builder, typeidx: u32, tableidx: u32 = 0) { + emit(b, .CALL_INDIRECT, op_typeidx(typeidx), op_table(tableidx)) +} + +emit_block :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .BLOCK, op_blocktype(bt)) } +emit_loop :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .LOOP, op_blocktype(bt)) } +emit_if :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .IF, op_blocktype(bt)) } +emit_else :: proc(b: ^Builder) { emit(b, .ELSE) } +emit_end :: proc(b: ^Builder) { emit(b, .END) } + +emit_br :: proc(b: ^Builder, depth: u32) { emit(b, .BR, op_labelidx(depth)) } +emit_br_if :: proc(b: ^Builder, depth: u32) { emit(b, .BR_IF, op_labelidx(depth)) } + +// br_table: operands are [default, case0, case1, ...], every entry a label depth. +emit_br_table :: proc(b: ^Builder, targets: []u32, default_depth: u32) { + buf := make([]Operand, len(targets)+1, b.alloc) + buf[0] = op_labelidx(default_depth) + for t, i in targets { + buf[i+1] = op_labelidx(t) + } + append(&b.ops, operation(.BR_TABLE, buf)) +} + +emit_return :: proc(b: ^Builder) { emit(b, .RETURN) } +emit_unreachable :: proc(b: ^Builder) { emit(b, .UNREACHABLE) } + +emit_load :: proc(b: ^Builder, opcode: Opcode, ma: Memarg) { emit(b, opcode, op_memarg(ma)) } +emit_store :: proc(b: ^Builder, opcode: Opcode, ma: Memarg) { emit(b, opcode, op_memarg(ma)) } + +// v128.const / i8x16.shuffle: the 16-byte immediate as two ATTRIBUTE halves. +emit_v128_const :: proc(b: ^Builder, value: [16]u8) { + lo, hi := op_v128(value) + emit(b, .V128_CONST, lo, hi) +} +emit_shuffle :: proc(b: ^Builder, lanes: [16]u8) { + lo, hi := op_v128(lanes) + emit(b, .I8X16_SHUFFLE, lo, hi) +} diff --git a/core/rexcode/ir/wasm/decoder.odin b/core/rexcode/ir/wasm/decoder.odin new file mode 100644 index 000000000..f780f16f3 --- /dev/null +++ b/core/rexcode/ir/wasm/decoder.odin @@ -0,0 +1,242 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +import "base:runtime" + +// ============================================================================= +// SECTION: Decoder (WASM instruction byte stream -> ir.Operations / Module) +// ============================================================================= +// +// Single forward pass, mirroring the encoder. Each step reads an opcode (a +// leading 0xFC/0xFD/0xFE switches to a prefix group whose sub-opcode is an +// unsigned LEB128; otherwise the single byte is the opcode), looks its form up +// in ENCODING_TABLE, and reads the immediates in declaration order, building the +// operation's variable-arity `[]Operand` as it goes. +// +// 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. + +// decode: parse a WASM `expr` byte stream into a single-function, single-block +// Module (dataflow = .STACK). The reusable stream decoder is `decode_ops`. +decode :: proc( + data: []u8, + m: ^Module, + errors: ^[dynamic]Error, + allocator := context.allocator, +) -> (byte_count: u32, ok: bool) { + ops: []Operation + ops, byte_count, ok = decode_ops(data, nil, errors, allocator) + + blocks := make([]Block, 1, allocator) + blocks[0] = Block{id = ID_NONE, ops = ops} + funcs := make([]Function, 1, allocator) + funcs[0] = Function{blocks = blocks, signature = TYPE_NONE} + + m.base.dataflow = .STACK + m.base.functions = funcs + m.version = WASM_VERSION + m.start = -1 + return +} + +// decode_ops: the reusable instruction-stream decoder. `relocs` (may be nil) are +// the input relocations to re-attach to symbolic index fields. +decode_ops :: proc( + data: []u8, + relocs: []Relocation, + errors: ^[dynamic]Error, + allocator := context.allocator, +) -> (ops: []Operation, byte_count: u32, ok: bool) { + errors_start := u32(len(errors)) + acc := make([dynamic]Operation, allocator) + n := u32(len(data)) + + for byte_count < n { + op, next, dok := decode_one(data, relocs, byte_count, allocator) + if !dok { + append(errors, Error{location = byte_count, code = .INVALID_OPCODE}) + append(&acc, operation(.INVALID)) + byte_count += 1 + continue + } + append(&acc, op) + byte_count = next + } + + ops = acc[:] + ok = u32(len(errors)) == errors_start + return +} + +@(require_results) +decode_one :: proc( + data: []u8, + relocs: []Relocation, + pc: u32, + allocator: runtime.Allocator, +) -> (op: Operation, next: u32, ok: bool) { + off := pc + if off >= u32(len(data)) { + next = pc + return + } + + b0 := data[off] + off += 1 + + m: Opcode = .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) + operands := make([dynamic]Operand, allocator) + defer if !ok { delete(operands) } + + for k, ki in form.imm { + switch k { + case .NONE: + // nothing + + case .BLOCKTYPE: + v := read_sleb(data, &off) or_return + append(&operands, Operand{kind = .ATTRIBUTE, imm = v, aux = u16(Attr.BLOCKTYPE)}) + + case .I32: + v := read_sleb(data, &off) or_return + append(&operands, op_int(v)) + + case .I64: + v := read_sleb(data, &off) or_return + append(&operands, op_int(v)) + + case .F32: + b := read_u32_block(data, &off) or_return + append(&operands, op_float(u64(b), 32)) + + case .F64: + b := read_u64_block(data, &off) or_return + append(&operands, op_float(b, 64)) + + case .IDX: + field := off + raw := read_uleb(data, &off) or_return + kind := idx_kind_for(m, ki) + o := op_wasm_index(kind, u32(raw)) + // An index is symbolic only when an input relocation actually lands + // on this field; otherwise it is a concrete, self-contained index + // (so a relocation-free stream round-trips byte-for-byte). + if lid, found := reloc_label_at(relocs, field); found { + o = op_wasm_index(kind, lid, symbolic = true) + } + append(&operands, o) + + case .MEMARG: + align := read_uleb(data, &off) or_return + offset := read_uleb(data, &off) or_return + // Stored as log2 on the wire; expand back to a byte alignment. + append(&operands, op_memarg(Memarg{align = u32(1 << align), offset = u32(offset)})) + + case .REFTYPE: + if off >= u32(len(data)) { + next = pc + return + } + t := data[off] + off += 1 + append(&operands, Operand{kind = .ATTRIBUTE, imm = i64(t), aux = u16(Attr.REFTYPE)}) + + case .BR_TABLE: + count := int(read_uleb(data, &off) or_return) + reserve(&operands, len(operands)+count+1) + operands_default_index := len(operands) + for _ in 0..= 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 + append(&operands, Operand{kind = .ATTRIBUTE, imm = i64(l), aux = u16(Attr.LANE)}) + + case .LANES16: + if off + 16 > u32(len(data)) { + next = pc + return + } + bytes: [16]u8 + copy(bytes[:], data[off:][:16]) + off += 16 + lo, hi := op_v128(bytes) + append(&operands, lo) + append(&operands, hi) + } + } + + op = operation(m, operands[:]) + next = off + ok = true + return +} + +// Which index space the IDX immediate in operand slot `which` addresses, by +// opcode. Mirrors how the builders tag each operand. +@(private="file", require_results) +idx_kind_for :: #force_inline proc "contextless" (m: Opcode, 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 +} diff --git a/core/rexcode/ir/wasm/decoding_tables.odin b/core/rexcode/ir/wasm/decoding_tables.odin new file mode 100644 index 000000000..f07d873ed --- /dev/null +++ b/core/rexcode/ir/wasm/decoding_tables.odin @@ -0,0 +1,560 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +// ============================================================================= +// WebAssembly DECODE DISPATCH TABLES +// ============================================================================= +// +// Reverse maps from wire opcode to Opcode. 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]Opcode{ + 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]Opcode{ + 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]Opcode{ + 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]Opcode{ + 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, +} diff --git a/core/rexcode/ir/wasm/encoder.odin b/core/rexcode/ir/wasm/encoder.odin new file mode 100644 index 000000000..0c5408cb2 --- /dev/null +++ b/core/rexcode/ir/wasm/encoder.odin @@ -0,0 +1,243 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +import "core:math/bits" + +// ============================================================================= +// SECTION: Encoder (Module -> WASM instruction byte stream) +// ============================================================================= +// +// Variable-length, byte-oriented, LEB128-heavy. Encoding is a single forward +// pass: each operation writes its 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 -- which is exactly why the ir verbs +// (docs/ir_design.md §4) drop the ISA `label_defs`/`resolve`/`base_address`. +// 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 always fits. +// +// The reusable core is `encode_ops` (an operation stream = a WASM `expr`). The +// Module verb `encode` drives it over each function's body blocks. Byte-level +// container framing (the type/function/code section wrappers) is a separate, +// symmetric concern -- the sibling container reader lives in the WASM `module` +// parsing path -- and is not part of this instruction-stream codec. + +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 op without a br_table: a 3-byte opcode plus the largest + // single immediate, v128.const's 16 raw bytes. br_table is unbounded in its + // target count; callers encoding those 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: serialize the module's function bodies into `code`, in order. Returns +// the total byte count written. (A Module built as one function / one block +// reproduces the old flat `encode([]Instruction)` behavior exactly.) +encode :: proc(m: Module, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (byte_count: u32, ok: bool) { + errors_start := u32(len(errors)) + op_index := u16(0) + for fn in m.functions { + for blk in fn.blocks { + for &op in blk.ops { + n := encode_operation(&op, byte_count, op_index, code, relocs, errors) or_return + byte_count += n + op_index += 1 + } + } + } + ok = u32(len(errors)) == errors_start + return +} + +// encode_ops: the reusable instruction-stream encoder (a WASM `expr`). +encode_ops :: proc(ops: []Operation, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (byte_count: u32, ok: bool) { + errors_start := u32(len(errors)) + for &op, i in ops { + n := encode_operation(&op, byte_count, u16(i), code, relocs, errors) or_return + byte_count += n + } + ok = u32(len(errors)) == errors_start + return +} + +encode_operation :: proc( + op: ^Operation, + pc: u32, + op_index: u16, + code: []u8, + relocs: ^[dynamic]Relocation, + errors: ^[dynamic]Error, +) -> (size: u32, ok: bool) { + opcode := Opcode(op.opcode) + if opcode == .INVALID { + append(errors, Error{location = u32(op_index), code = .INVALID_OPCODE}) + return + } + form := encoding_form(opcode) + + need := encoded_size(op, form) + if pc + need > u32(len(code)) { + append(errors, Error{location = u32(op_index), 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, op.operands[opi].imm) + opi += 1 + + case .F32: + write_u32_block(code, &off, u32(op.operands[opi].imm)) + opi += 1 + + case .F64: + write_u64_block(code, &off, u64(op.operands[opi].imm)) + opi += 1 + + case .IDX: + o := op.operands[opi] + if operand_symbolic(o) { + append(relocs, Relocation{ + offset = off, label_id = operand_index(o), addend = 0, + type = reloc_type_for(operand_index_kind(o)), size = 5, inst_idx = op_index, + }) + write_uleb_padded5(code, &off, u64(operand_index(o))) + } else { + write_uleb(code, &off, u64(operand_index(o))) + } + opi += 1 + + case .MEMARG: + ma := operand_memarg(op.operands[opi]) + // NOTE(bill): stored as log2 even though the spec text reads otherwise. + 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(op.operands[opi].imm) + off += 1 + opi += 1 + + case .BR_TABLE: + // operands = [default, case0, case1, ...] -- every entry a label depth. + cases := op.operands[opi + 1:] + write_uleb(code, &off, u64(len(cases))) + for c in cases { + write_uleb(code, &off, u64(operand_index(c))) + } + write_uleb(code, &off, u64(operand_index(op.operands[opi]))) // default + opi = len(op.operands) + + case .ZERO_BYTE: + code[off] = 0x00 + off += 1 + + case .LANE: + code[off] = u8(op.operands[opi].imm) + off += 1 + opi += 1 + + case .LANES16: + bytes := operand_v128(op.operands[opi], op.operands[opi + 1]) + for bb in bytes { + code[off] = bb + off += 1 + } + opi += 2 + } + } + + return off - pc, true +} + +@(private="file") +encoded_size :: proc(op: ^Operation, 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(op.operands[opi].imm) + opi += 1 + case .F32: + size += 4 + opi += 1 + case .F64: + size += 8 + opi += 1 + case .IDX: + o := op.operands[opi] + size += operand_symbolic(o) ? 5 : uleb_size(u64(operand_index(o))) + opi += 1 + case .MEMARG: + ma := operand_memarg(op.operands[opi]) + size += uleb_size(bits.log2(u64(ma.align))) + uleb_size(u64(ma.offset)) + opi += 1 + case .REFTYPE: + size += 1 + opi += 1 + case .BR_TABLE: + cases := op.operands[opi + 1:] + size += uleb_size(u64(len(cases))) + for c in cases { + size += uleb_size(u64(operand_index(c))) + } + size += uleb_size(u64(operand_index(op.operands[opi]))) + opi = len(op.operands) + case .ZERO_BYTE: + size += 1 + case .LANE: + size += 1 + opi += 1 + case .LANES16: + size += 16 + opi += 2 + } + } + 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 +} diff --git a/core/rexcode/ir/wasm/encoding.odin b/core/rexcode/ir/wasm/encoding.odin new file mode 100644 index 000000000..c9757aeda --- /dev/null +++ b/core/rexcode/ir/wasm/encoding.odin @@ -0,0 +1,95 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +// ============================================================================= +// SECTION: Encoding fundamentals (the table-driven codec vocabulary) +// ============================================================================= +// +// 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..0x113 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 opcode, so dispatch is a direct +// `ENCODING_TABLE[opcode]` lookup (O(1)) -- the docs/ir_design.md §5 "table- +// driven" strategy, literally the ISA `ENCODING_TABLE` shape. The immediate +// layout is described declaratively by `imm: [2]Imm_Kind`, walked in order by +// the encoder and decoder, each kind consuming zero or more `ir.Operand`s (see +// operands.odin for how a WASM immediate maps onto an `ir.Operand`). + +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) +} + +Encoding :: struct #packed { + mnemonic: Opcode, // 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 + imm: [2]Imm_Kind, // 2 -- immediate layout, walked in order + flags: Encoding_Flags, // 1 + inputs: i8, // 1 -- stack operands consumed; -1 when it varies + outputs: i8, // 1 -- stack results produced; -1 when it varies +} +#assert(size_of(Encoding) == 10) + +// `inputs`/`outputs` give an instruction's fixed stack arity: how many values +// it pops and pushes. They are -1 when the count is not a constant of the opcode +// -- i.e. it depends on a type/blocktype immediate or the surrounding control +// frame (call/call_indirect, block/loop/if/else/end, br*/return, unreachable). + +// ============================================================================= +// SECTION: Value types (the leaf types WASM names -- 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 used in a blocktype. +// ============================================================================= + +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 +} diff --git a/core/rexcode/ir/wasm/encoding_table.odin b/core/rexcode/ir/wasm/encoding_table.odin new file mode 100644 index 000000000..d03beed96 --- /dev/null +++ b/core/rexcode/ir/wasm/encoding_table.odin @@ -0,0 +1,505 @@ +// 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 Opcode 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 := [Opcode]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: Opcode) -> ^Encoding { + return &ENCODING_TABLE[m] +} diff --git a/core/rexcode/ir/wasm/leb.odin b/core/rexcode/ir/wasm/leb.odin new file mode 100644 index 000000000..c632ca491 --- /dev/null +++ b/core/rexcode/ir/wasm/leb.odin @@ -0,0 +1,162 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +// 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 +} diff --git a/core/rexcode/ir/wasm/module.odin b/core/rexcode/ir/wasm/module.odin new file mode 100644 index 000000000..acb34593e --- /dev/null +++ b/core/rexcode/ir/wasm/module.odin @@ -0,0 +1,105 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +import "core:rexcode/ir" + +// ============================================================================= +// SECTION: Module (the WASM module -- ir core + WASM's own container sections) +// ============================================================================= +// +// `wasm.Module` is the `ir.Module` STACK core (functions -> blocks -> operations, +// dataflow = .STACK) plus the WASM binary-container sections the shared core has +// no slot for. A concrete IR carries those "alongside the core" per +// docs/ir_design.md §3; here that is literal -- `using base: ir.Module` embeds +// the core so `m.functions`, `m.globals`, ... read straight through -- exactly +// the shape `spirv.Module` uses for its preamble / debug / annotation sections. +// +// Mapping the WASM container onto the ir core: +// +// * A WASM function body is one `expr` (a flat instruction stream). It lowers +// to a single `ir.Block` of `ir.Operation`s under one `ir.Function`; the +// structured control ops (block/loop/if/else/end) remain *operations* within +// that block rather than nested `ir.Block`s -- WASM's structure is carried +// by those ops and their label depths, matching how the decoder produced a +// flat stream. (Nesting into regions is a valid later refinement.) +// +// * WASM function signatures are `[]Value_Type -> []Value_Type`, not the ir +// type table's `Type_Kind`. Rather than lower every primitive into +// `ir.Module.types`, WASM keeps its value-typed signatures in the parallel +// `func_types` side table (analogous to how `spirv.Module` keeps +// `opaque_info` for what the shared core does not slot). `ir.Function. +// signature` indexes `func_types` by convention. + +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, +} + +External_Kind :: enum u8 { + FUNC = 0, + TABLE = 1, + MEMORY = 2, + GLOBAL = 3, +} + +// A WASM function signature: value-typed params and results. +Func_Type :: struct { + params: []Value_Type, + results: []Value_Type, +} + +Import :: struct { + kind: External_Kind, + module_name: string, + field_name: string, + index: u32, // typeidx for FUNC, 0 for other kinds +} + +Export :: struct { + kind: External_Kind, + name: string, + index: u32, +} + +// The WASM module -- the unit the verbs operate on. Embeds the ir core; adds the +// container metadata parsed from / emitted to the binary sections. +Module :: struct { + using base: ir.Module, // functions, globals, symbols, dataflow (= .STACK), target + + version: u32, // WASM_VERSION if 0 on encode + + // --- container sections (the WASM-specific data the ir core has no slot for) --- + func_types: []Func_Type, // the type section; ir.Function.signature indexes here + imports: []Import, + exports: []Export, + start: i64, // -1 if absent, else the start funcidx + + // --- side tables parallel to the ir core arrays --- + // A WASM function's declared locals (the code section's local groups), kept + // parallel to base.functions since ir.Function has no locals slot. + function_locals: [][]Value_Type, +} + +// A freshly-made WASM module declares STACK dataflow so the shared verbs and the +// printer pick the stack-machine path (no SSA results, no value refs). +@(require_results) +make_module :: proc "contextless" () -> Module { + m: Module + m.base.dataflow = .STACK + m.version = WASM_VERSION + m.start = -1 + return m +} diff --git a/core/rexcode/ir/wasm/opcodes.odin b/core/rexcode/ir/wasm/opcodes.odin new file mode 100644 index 000000000..8c8aec237 --- /dev/null +++ b/core/rexcode/ir/wasm/opcodes.odin @@ -0,0 +1,471 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +// ============================================================================= +// WebAssembly OPCODES +// ============================================================================= +// +// 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`. + +Opcode :: 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 Opcode; INVALID maps to "". +// ----------------------------------------------------------------------------- + +@(rodata) +MNEMONIC_NAMES := [Opcode]string{ + .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", +} diff --git a/core/rexcode/ir/wasm/operands.odin b/core/rexcode/ir/wasm/operands.odin new file mode 100644 index 000000000..c8cfc87c2 --- /dev/null +++ b/core/rexcode/ir/wasm/operands.odin @@ -0,0 +1,217 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +// ============================================================================= +// SECTION: Operands (WASM immediates expressed as ir.Operand) +// ============================================================================= +// +// The old ISA-shaped WASM package had its own `Operand` (a #raw_union of +// immediate / index / memarg / blocktype). Under the IR contract the operand +// model is *shared* -- `ir.Operand` is one discriminated value (LIT_INT / +// LIT_FLOAT / REF / TYPE / ATTRIBUTE) -- and a dialect's structured immediates +// "ride in `aux` + the IR's opcode table" (docs/ir_design.md §2). So WASM maps +// its immediates onto `ir.Operand` like this: +// +// i32.const / i64.const value -> LIT_INT (op_int) +// f32.const / f64.const -> LIT_FLOAT (op_float, aux = width) +// an index (local/func/...) -> REF (op_wasm_index; space is the +// best-fit ir.Ref_Space, and +// the exact WASM Index_Kind is +// kept in `aux`, losslessly) +// a branch label depth -> REF (.BLOCK) (op_labelidx) +// a blocktype (s33) -> ATTRIBUTE (op_blocktype) +// a memarg (align, offset) -> ATTRIBUTE (op_memarg; both packed in imm) +// a ref.null heap type -> ATTRIBUTE (op_reftype) +// a SIMD lane index -> ATTRIBUTE (op_lane) +// a v128 literal / shuffle mask -> two ATTRIBUTE (op_v128 -> lo, hi halves) +// +// Nothing needs an inline byte blob: the one 16-byte immediate (v128.const, +// i8x16.shuffle) is carried as two 64-bit ATTRIBUTE halves. `br_table`'s label +// vector is not a special field either -- with `ir.Operation.operands` now being +// variable-arity, it is just `[default, case0, case1, ...]`, every entry a BLOCK +// ref (see builder.odin / the encoder's BR_TABLE case). + +// Which WASM index space an index immediate addresses. Drives the on-wire +// encoding, relocation-type selection, and printer annotation. Kept in +// `ir.Operand.aux` for every REF operand so the exact space always round-trips, +// even where the shared `ir.Ref_Space` has no dedicated member (TABLE/DATA/ELEM). +Index_Kind :: enum u16 { + NONE, + LOCAL, + GLOBAL, + FUNC, + TYPE, + TABLE, + MEMORY, + LABEL, // br / br_if / br_table relative depth + DATA, + ELEM, +} + +// The `ir.Operand.aux` tag for the ATTRIBUTE-kind WASM immediates. (REF operands +// instead put an `Index_Kind` in `aux`; the opcode's ENCODING_TABLE form always +// says which of the two an operand slot is, so the two uses never collide.) +Attr :: enum u16 { + NONE, + BLOCKTYPE, + MEMARG, + REFTYPE, + LANE, + V128_LO, // low 8 bytes of a v128 literal / shuffle mask + V128_HI, // high 8 bytes +} + +// `ir.Operand.flags` bit: a REF whose index is a symbol id the linker fixes up +// (emitted as a fixed-width 5-byte LEB placeholder plus a Relocation). +FLAG_SYMBOLIC :: u32(1) + +// 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 +} + +// ----------------------------------------------------------------------------- +// Index_Kind <-> ir.Ref_Space +// ----------------------------------------------------------------------------- + +// Best-fit shared space for a WASM index space (used for printer annotation and +// generic ir tooling). The exact `Index_Kind` still lives in `aux`, so spaces +// the shared enum lacks (TABLE/DATA/ELEM) map to NONE here without loss. +@(require_results) +ref_space_for :: #force_inline proc "contextless" (k: Index_Kind) -> Ref_Space { + switch k { + case .LOCAL: return .VALUE // a local / value-stack slot (ir_design: VALUE) + case .GLOBAL: return .GLOBAL + case .FUNC: return .FUNCTION + case .TYPE: return .TYPE + case .MEMORY: return .MEMORY + case .LABEL: return .BLOCK + case .TABLE, .DATA, .ELEM: return .NONE + case .NONE: return .NONE + } + return .NONE +} + +// ----------------------------------------------------------------------------- +// Numeric constants +// ----------------------------------------------------------------------------- + +@(require_results) op_i32 :: #force_inline proc "contextless" (v: i32) -> Operand { return op_int(i64(v)) } +@(require_results) op_i64 :: #force_inline proc "contextless" (v: i64) -> Operand { return op_int(v) } +@(require_results) op_f32 :: #force_inline proc "contextless" (v: f32) -> Operand { return op_float(u64(transmute(u32)v), 32) } +@(require_results) op_f64 :: #force_inline proc "contextless" (v: f64) -> Operand { return op_float(transmute(u64)v, 64) } + +// ----------------------------------------------------------------------------- +// Index-space constructors (one per space; all unsigned LEB128 on the wire) +// ----------------------------------------------------------------------------- + +@(require_results) +op_wasm_index :: #force_inline proc "contextless" (kind: Index_Kind, value: u32, symbolic := false) -> Operand { + o := op_ref(ref_space_for(kind), Id(value)) + o.aux = u16(kind) + if symbolic { o.flags |= FLAG_SYMBOLIC } + return o +} + +@(require_results) op_local :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.LOCAL, n) } +@(require_results) op_global :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.GLOBAL, n) } +@(require_results) op_func :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.FUNC, n) } +@(require_results) op_typeidx:: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.TYPE, n) } +@(require_results) op_table :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.TABLE, n) } +@(require_results) op_memory :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.MEMORY, n) } +@(require_results) op_data :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_index(.DATA, n) } +@(require_results) op_elem :: #force_inline proc "contextless" (n: u32) -> Operand { return op_wasm_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_wasm_index(.LABEL, depth) } + +// Symbolic function reference: emitted as a relocatable funcidx placeholder. +@(require_results) op_label :: #force_inline proc "contextless" (label_id: u32) -> Operand { return op_wasm_index(.FUNC, label_id, symbolic = true) } + +// ----------------------------------------------------------------------------- +// Dialect ATTRIBUTE immediates +// ----------------------------------------------------------------------------- + +@(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" (ma: Memarg) -> Operand { + packed := i64(u64(ma.offset) | (u64(ma.align) << 32)) + return Operand{kind = .ATTRIBUTE, imm = packed, aux = u16(Attr.MEMARG)} +} + +@(require_results) +op_blocktype :: #force_inline proc "contextless" (bt: Block_Type) -> Operand { + return Operand{kind = .ATTRIBUTE, imm = i64(bt), aux = u16(Attr.BLOCKTYPE)} +} + +@(require_results) +op_block_typeidx :: #force_inline proc "contextless" (type_index: u32) -> Operand { + return Operand{kind = .ATTRIBUTE, imm = i64(type_index), aux = u16(Attr.BLOCKTYPE)} +} + +@(require_results) +op_reftype :: #force_inline proc "contextless" (t: Value_Type) -> Operand { + return Operand{kind = .ATTRIBUTE, imm = i64(t), aux = u16(Attr.REFTYPE)} +} + +@(require_results) +op_lane :: #force_inline proc "contextless" (n: u8) -> Operand { + return Operand{kind = .ATTRIBUTE, imm = i64(n), aux = u16(Attr.LANE)} +} + +// A 16-byte v128 literal (v128.const) or shuffle mask (i8x16.shuffle), as two +// little-endian 64-bit ATTRIBUTE halves. +@(require_results) +op_v128 :: #force_inline proc "contextless" (bytes: [16]u8) -> (lo, hi: Operand) { + l, h: u64 + for i in 0..<8 { l |= u64(bytes[i + 0]) << u64(i * 8) } + for i in 0..<8 { h |= u64(bytes[i + 8]) << u64(i * 8) } + lo = Operand{kind = .ATTRIBUTE, imm = i64(l), aux = u16(Attr.V128_LO)} + hi = Operand{kind = .ATTRIBUTE, imm = i64(h), aux = u16(Attr.V128_HI)} + return +} + +// ----------------------------------------------------------------------------- +// Accessors (reconstruct the WASM payload carried by an ir.Operand) +// ----------------------------------------------------------------------------- + +@(require_results) operand_index :: #force_inline proc "contextless" (o: Operand) -> u32 { return u32(o.imm) } +@(require_results) operand_index_kind :: #force_inline proc "contextless" (o: Operand) -> Index_Kind { return Index_Kind(o.aux) } +@(require_results) operand_symbolic :: #force_inline proc "contextless" (o: Operand) -> bool { return o.flags & FLAG_SYMBOLIC != 0 } +@(require_results) operand_attr :: #force_inline proc "contextless" (o: Operand) -> Attr { return Attr(o.aux) } + +@(require_results) +operand_memarg :: #force_inline proc "contextless" (o: Operand) -> Memarg { + u := u64(o.imm) + return Memarg{offset = u32(u), align = u32(u >> 32)} +} + +// v128 bytes from a lo/hi ATTRIBUTE pair. +@(require_results) +operand_v128 :: #force_inline proc "contextless" (lo, hi: Operand) -> (bytes: [16]u8) { + l, h := u64(lo.imm), u64(hi.imm) + for i in 0..<8 { bytes[i + 0] = u8(l >> u64(i * 8)) } + for i in 0..<8 { bytes[i + 8] = u8(h >> u64(i * 8)) } + return +} diff --git a/core/rexcode/ir/wasm/printer.odin b/core/rexcode/ir/wasm/printer.odin new file mode 100644 index 000000000..c2f2d30a1 --- /dev/null +++ b/core/rexcode/ir/wasm/printer.odin @@ -0,0 +1,291 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +package rexcode_wasm + +import "core:strings" +import "core:strconv" + +// ============================================================================= +// SECTION: Printer (Module -> WebAssembly text listing) +// ============================================================================= +// +// The third ir verb (docs/ir_design.md §4), same signature as the SPIR-V +// printer: `print(m, sb, options)`, disassembling the module into a caller-owned +// builder. It walks functions -> blocks -> operations and emits one instruction +// per line in the linear (unfolded) WAT form +// +// * +// +// e.g. +// +// 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 +// v128.const i8x16 0x01 0x02 ... +// +// The folded-stack form is not reconstructed (that needs structure the linear +// stream does not carry). Mnemonic spelling comes from the explicit +// MNEMONIC_NAMES table (WASM mixes '.' and '_' irregularly, e.g. `local.get` +// vs `i32.trunc_f32_s`). Immediates are recovered from the shared `ir.Operand` +// model via the operands.odin accessors -- structured payloads (memarg, +// blocktype, reftype, lanes, v128) ride in ATTRIBUTE operands tagged in `aux`. + +// print: disassemble `m` into the caller-owned builder `sb`. The canonical ir +// print verb; `sbprint` / `tprint` below are thin convenience sinks over it. +print :: proc(m: Module, sb: ^strings.Builder, options: ^Print_Options = nil) { + opts := DEFAULT_PRINT_OPTIONS + if options != nil { opts = options^ } + + for fn in m.functions { + if fn.name != "" { + strings.write_string(sb, "func ") + strings.write_string(sb, fn.name) + strings.write_string(sb, ":") + strings.write_string(sb, opts.separator) + } + for blk in fn.blocks { + for &op in blk.ops { + write_operation(sb, &op, &opts) + strings.write_string(sb, opts.separator) + } + } + } +} + +// sbprint: disassemble a bare operation stream (a WASM `expr`) -- the reusable +// core that `print` runs per block, and the mirror of the encoder's +// `encode_ops` / decoder's `decode_ops`. +sbprint :: proc(sb: ^strings.Builder, ops: []Operation, options: ^Print_Options = nil) { + opts := DEFAULT_PRINT_OPTIONS + if options != nil { opts = options^ } + for &op in ops { + write_operation(sb, &op, &opts) + strings.write_string(sb, opts.separator) + } +} + +// tprint: temp-allocated string of an operation stream (spot-checks / debugging). +@(require_results) +tprint :: proc(ops: []Operation, options: ^Print_Options = nil) -> string { + sb := strings.builder_make(context.temp_allocator) + sbprint(&sb, ops, options) + return strings.to_string(sb) +} + +// aprint: caller-allocated string of an operation stream. +@(require_results) +aprint :: proc(ops: []Operation, options: ^Print_Options = nil, allocator := context.allocator) -> string { + sb := strings.builder_make(allocator) + sbprint(&sb, ops, options) + return strings.to_string(sb) +} + +@(require_results) +mnemonic_to_string :: proc(op: Opcode, uppercase := false, allocator := context.temp_allocator) -> string { + sb := strings.builder_make(allocator) + write_mnemonic(&sb, op, uppercase) + return strings.to_string(sb) +} + +// ============================================================================= +// Per-operation writer +// ============================================================================= + +write_operation :: proc(sb: ^strings.Builder, op: ^Operation, opts: ^Print_Options) { + strings.write_string(sb, opts.indent) + + opcode := Opcode(op.opcode) + write_mnemonic(sb, opcode, opts.uppercase) + + // A couple of opcodes carry a bespoke operand layout the generic per-operand + // walk would mis-order; specialise those, and fall through for the rest. + #partial switch opcode { + case .BR_TABLE: + // operands = [default, case0, case1, ...]; WAT prints the cases first, + // then the default depth, matching the binary order. + for c in op.operands[1:] { + strings.write_byte(sb, ' ') + strings.write_u64(sb, u64(operand_index(c))) + } + if len(op.operands) > 0 { + strings.write_byte(sb, ' ') + strings.write_u64(sb, u64(operand_index(op.operands[0]))) + } + + case .V128_CONST: + strings.write_string(sb, " i8x16") + if len(op.operands) >= 2 { + bytes := operand_v128(op.operands[0], op.operands[1]) + for bb in bytes { + strings.write_byte(sb, ' ') + print_hex(sb, u64(bb), opts) + } + } + + case .I8X16_SHUFFLE: + if len(op.operands) >= 2 { + bytes := operand_v128(op.operands[0], op.operands[1]) + for bb in bytes { + strings.write_byte(sb, ' ') + strings.write_u64(sb, u64(bb)) + } + } + + case: + for &o in op.operands { + write_operand(sb, &o, opcode, opts) + } + } +} + +// ============================================================================= +// Internal writers +// ============================================================================= + +write_mnemonic :: proc(sb: ^strings.Builder, op: Opcode, uppercase: bool) { + name := MNEMONIC_NAMES[op] + if name == "" { + strings.write_string(sb, "") + return + } + if uppercase { + for i in 0..= 0 { + // a type index (positive s33) + strings.write_string(sb, " (type ") + strings.write_u64(sb, u64(u32(v))) + strings.write_byte(sb, ')') + return true + } + } + return false +} + +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, o: ^Operand) { + buf: [40]u8 + if o.aux == 32 { + f := transmute(f32)u32(o.imm) + s := strconv.write_float(buf[:], f64(f), 'g', -1, 32) + strings.write_string(sb, s) + } else { + f := transmute(f64)u64(o.imm) + s := strconv.write_float(buf[:], f, 'g', -1, 64) + strings.write_string(sb, s) + } +} + +write_label :: proc(sb: ^strings.Builder, label_id: u32) { + strings.write_byte(sb, '$') + strings.write_u64(sb, u64(label_id)) +} diff --git a/core/rexcode/ir/wasm/reloc.odin b/core/rexcode/ir/wasm/reloc.odin new file mode 100644 index 000000000..f3b5d3d46 --- /dev/null +++ b/core/rexcode/ir/wasm/reloc.odin @@ -0,0 +1,43 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// 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) diff --git a/core/rexcode/ir/wasm/wasm.odin b/core/rexcode/ir/wasm/wasm.odin new file mode 100644 index 000000000..a50b5f7ac --- /dev/null +++ b/core/rexcode/ir/wasm/wasm.odin @@ -0,0 +1,110 @@ +// rexcode · Brendan Punsky (dotbmp@github), original author +// Ginger Bill (gingerBill@github) + +// rexcode/ir/wasm -- the WebAssembly intermediate representation. +// +// WASM is a stack bytecode: a byte stream with LEB128 immediates, one encoding +// form per opcode, over an *implicit value stack* with a minimal type system. +// On the two axes that sort the IR family (docs/ir_design.md §2) it is +// **table-driven** on encoding -- a static `opcode -> operand-layout` table, +// exactly the ISA `ENCODING_TABLE` shape -- and **STACK** on dataflow (the one +// outlier among the modelled IRs; SPIR-V/LLVM are SSA). +// +// So this package is a sibling of the SPIR-V package under `ir/`: it re-exports +// the shared `ir` vocabulary (Module / Function / Block / Operation / Operand / +// Type / Id / ...), owns a `u16` `Opcode` (the WASM opcode set, with the 0xFC/ +// 0xFD/0xFE prefix groups folded in), a static `ENCODING_TABLE` that drives a +// byte/LEB codec, the three Module-based verbs (`encode` / `decode` / `print`), +// a `Relocation` for object-file index fixups, and the WASM value-type model. +// +// This is the migration anticipated by docs/ir_design.md §4: WASM was shaped as +// an ISA arch package (`core:rexcode/wasm`); its real `encode`/`decode` already +// dropped the ISA verbs' `label_defs`/`resolve`/`base_address` (WASM has no +// PC-relative pass -- branches carry structured label depths), so moving it onto +// the `ir` Module/Operation/Operand model is a change of *leaf shape*, not of +// codec. The table-driven codec below is carried over intact; what changed is +// the leaf (`ir.Operation` in place of the old fixed-operand `Instruction`), the +// operand model (`ir.Operand`, with WASM memarg/blocktype/lane riding in `aux`), +// and the unit of work (an `ir.Module` of functions->blocks->operations). +package rexcode_wasm + +import "core:rexcode/ir" + +// ============================================================================= +// SECTION: Re-exported shared vocabulary (the IR naming contract, ir_design §4) +// ============================================================================= +// +// A consumer sees one namespace -- `wasm.Operation`, `wasm.Operand`, ... -- the +// way an arch package re-exports `isa`. `Module` is NOT re-exported: WASM's +// module carries container sections (types / imports / exports / ...) the shared +// core has no slot for, so it is a superset struct embedding `ir.Module`; see +// module.odin (exactly as `spirv.Module` does). + +Function :: ir.Function +Block :: ir.Block +Global :: ir.Global +Operation :: ir.Operation +Operation_Flags :: ir.Operation_Flags +Operand :: ir.Operand +Operand_Kind :: ir.Operand_Kind +Result :: ir.Result +Type :: ir.Type +Type_Ref :: ir.Type_Ref +Type_Kind :: ir.Type_Kind +Id :: ir.Id +Ref :: ir.Ref +Ref_Space :: ir.Ref_Space +Symbol_Table :: ir.Symbol_Table +Dataflow :: ir.Dataflow +Error :: ir.Error +Error_Code :: ir.Error_Code +Token :: ir.Token +Token_Kind :: ir.Token_Kind +Print_Options :: ir.Print_Options +Print_Result :: ir.Print_Result + +ID_NONE :: ir.ID_NONE +TYPE_NONE :: ir.TYPE_NONE +DEFAULT_PRINT_OPTIONS :: ir.DEFAULT_PRINT_OPTIONS + +print_hex :: ir.print_hex +print_decimal :: ir.print_decimal + +// Shared operand / type / ref constructors (WASM adds the dialect helpers -- a +// memarg, a blocktype, a lane index -- in operands.odin). +op_int :: ir.op_int +op_float :: ir.op_float +op_type :: ir.op_type +op_ref :: ir.op_ref +op_value :: ir.op_value +op_block :: ir.op_block +operand_id :: ir.operand_id +operand_type :: ir.operand_type +ref :: ir.ref + +type_void :: ir.type_void +type_int :: ir.type_int +type_float :: ir.type_float +type_vector :: ir.type_vector +type_pointer :: ir.type_pointer + +symbol_table_init :: ir.symbol_table_init +symbol_table_destroy :: ir.symbol_table_destroy +symbol_define :: ir.symbol_define +symbol_reserve :: ir.symbol_reserve +symbol_lookup :: ir.symbol_lookup + +// ============================================================================= +// SECTION: Physical format (WebAssembly core spec §5 -- the byte stream) +// ============================================================================= + +// The four bytes that begin every module: "\0asm" as a little-endian u32. +WASM_MAGIC :: u32(0x6d73_6100) +WASM_VERSION :: u32(1) + +// Opcode-space prefix bytes. A prefixed opcode is `prefix byte` then an unsigned +// LEB128 sub-opcode (SIMD reaches 0x113, so the sub-opcode can be two 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