Files
Odin/core/rexcode/ir/wasm/encoding.odin

96 lines
4.0 KiB
Odin

// 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
}