mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-15 01:59:15 +00:00
Merge origin/bill/rexcode: struct repack (#raw_union #packed), wasm arch
Merge gingerBill's latest into bill/rexcode. His changes: minimize the Instruction/Operand structs across ISAs with packed raw-unions (+ the compiler support for #raw_union #packed), the new core:rexcode/wasm arch and wasm/module, encode() now returns (byte_count, ok) instead of a Result struct, decode_one made public, and assorted formatting/inlining. Conflict: arm64/tests/pipeline_smoke.odin CSEL test -- kept the generated 4-arg inst_csel(dst,src,src2,cond) (mnemonic_builders.odin is generated, not from Bill's branch) and adopted Bill's (byte_count, success) encode signature. Required rebuilding ./odin from the merged source for the packed-union syntax. Re-validated after the repack: regenerated all artifacts (idempotent -- no spurious churn), all 10 arches gen/builders/check/test green, and byte-compared the new arm32 BF + mips PS/MMI/DSP/R6 forms to confirm no field truncation. arm64/arm32/mips still 100%.
This commit is contained in:
@@ -40,7 +40,7 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
mode: Mode = .A32,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data))
|
||||
if mode == .T32 { n_bytes = n_bytes & ~u32(1) }
|
||||
else { n_bytes = n_bytes & ~u32(3) }
|
||||
@@ -50,21 +50,20 @@ decode :: proc(
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
for byte_count < n_bytes {
|
||||
word: u32
|
||||
ilen: u32 = 4
|
||||
|
||||
if mode == .A32 {
|
||||
if pc + 4 > n_bytes { break }
|
||||
word = read_u32_le(data, pc)
|
||||
if byte_count + 4 > n_bytes { break }
|
||||
word = read_u32_le(data, byte_count)
|
||||
} else {
|
||||
// T32: 16 or 32 bit
|
||||
hword_hi := read_u16_le(data, pc)
|
||||
hword_hi := read_u16_le(data, byte_count)
|
||||
top5 := (hword_hi >> 11) & 0x1F
|
||||
if top5 == 0x1D || top5 == 0x1E || top5 == 0x1F {
|
||||
if pc + 4 > n_bytes { break }
|
||||
hword_lo := read_u16_le(data, pc + 2)
|
||||
if byte_count + 4 > n_bytes { break }
|
||||
hword_lo := read_u16_le(data, byte_count + 2)
|
||||
// Pack: bits = low_halfword | (high_halfword << 16)
|
||||
word = u32(hword_lo) | (u32(hword_hi) << 16)
|
||||
ilen = 4
|
||||
@@ -76,10 +75,10 @@ decode :: proc(
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
info.offset = pc
|
||||
info.offset = byte_count
|
||||
|
||||
if !find_and_decode(word, mode, ilen, &inst, &info) {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = u8(ilen), mode = mode}
|
||||
} else {
|
||||
inst.length = u8(ilen)
|
||||
@@ -103,11 +102,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += ilen
|
||||
byte_count += ilen
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -34,38 +34,37 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := len(instructions)
|
||||
if len(code) < n_inst * 4 {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
inst_pc := make([]u32, n_inst, context.temp_allocator)
|
||||
|
||||
// ---- PASS 1 ------------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst_pc[i] = pc
|
||||
inst_pc[i] = byte_count
|
||||
inst := &instructions[i]
|
||||
word, ilen, ok := encode_one_inline(inst, pc, u16(i), relocs, errors)
|
||||
if !ok { return Result{byte_count = pc, success = false} }
|
||||
word, ilen := encode_one_inline(inst, byte_count, u16(i), relocs, errors) or_return
|
||||
|
||||
if ilen == 2 {
|
||||
write_u16_le(code, pc, u16(word))
|
||||
write_u16_le(code, byte_count, u16(word))
|
||||
} else {
|
||||
// T32 32-bit: bits = low_hword | (high_hword << 16); each
|
||||
// halfword is written little-endian in its own slot.
|
||||
if inst.mode == .T32 {
|
||||
write_u16_le(code, pc, u16(word >> 16))
|
||||
write_u16_le(code, pc + 2, u16(word))
|
||||
write_u16_le(code, byte_count, u16(word >> 16))
|
||||
write_u16_le(code, byte_count + 2, u16(word))
|
||||
} else {
|
||||
write_u32_le(code, pc, word)
|
||||
write_u32_le(code, byte_count, word)
|
||||
}
|
||||
}
|
||||
pc += u32(ilen)
|
||||
byte_count += u32(ilen)
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: label_def instruction-idx -> byte-offset -----------------
|
||||
@@ -81,7 +80,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations ----------------------------------------
|
||||
@@ -97,7 +97,8 @@ encode :: proc(
|
||||
}
|
||||
if write_idx != n_relocs { resize(relocs, int(write_idx)) }
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -51,7 +51,6 @@ import "../isa"
|
||||
// All operand-driven fields live in the zeros of `mask`; the encoder ORs
|
||||
// them in. The matcher tests `(word & mask) == bits`.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -21,7 +21,7 @@ Instruction_Flags :: bit_field u8 {
|
||||
}
|
||||
|
||||
Instruction :: struct #packed {
|
||||
ops: [4]Operand `fmt:"v,operand_count"`, // 4 * 22 = 88
|
||||
ops: [4]Operand `fmt:"v,operand_count"`, // 4 * 18 = 68
|
||||
mnemonic: Mnemonic, // 2
|
||||
cond: u8, // 0..15 (AL=14)
|
||||
operand_count: u8, // 0..4
|
||||
@@ -35,9 +35,9 @@ Instruction :: struct #packed {
|
||||
// bits). User-constructed instructions leave it at 0; the encoder then
|
||||
// falls back to first-shape-match. Stored as u16 over the two padding bytes.
|
||||
form_id: u16,
|
||||
_: [7]u8,
|
||||
}
|
||||
#assert(size_of(Instruction) == 97)
|
||||
// 88 + 9 = 97 bytes (packed)
|
||||
#assert(size_of(Instruction) == 88)
|
||||
|
||||
// =============================================================================
|
||||
// Builders
|
||||
|
||||
@@ -94,7 +94,7 @@ mem_reg_shift :: #force_inline proc "contextless" (
|
||||
// ---- Operand structure -----------------------------------------------------
|
||||
|
||||
Operand :: struct #packed {
|
||||
using _: struct #raw_union {
|
||||
using _: struct #raw_union #packed {
|
||||
reg: Register,
|
||||
mem: Memory,
|
||||
immediate: i64,
|
||||
@@ -107,9 +107,7 @@ Operand :: struct #packed {
|
||||
lane: u8, // SIMD lane index for DPR_ELEM / QPR_ELEM
|
||||
cond: u8, // condition code 0..15 (default = AL = 14)
|
||||
}
|
||||
#assert(size_of(Operand) == 22)
|
||||
// 16-byte raw_union (Memory is largest) + 6 bytes of trailing fields = 22 bytes
|
||||
// (packed; no alignment padding).
|
||||
#assert(size_of(Operand) == 18)
|
||||
|
||||
// ---- Operand builders ------------------------------------------------------
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ check_bytes :: proc(name: string, inst: a.Instruction, want: []u8) {
|
||||
errors: [dynamic]a.Error
|
||||
defer { delete(label_defs); delete(code); delete(relocs); delete(errors) }
|
||||
|
||||
res := a.encode(insts, label_defs[:], code, &relocs, &errors)
|
||||
if !res.success {
|
||||
byte_count, success := a.encode(insts, label_defs[:], code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed (errors=%d)\n", name, len(errors))
|
||||
fail += 1
|
||||
return
|
||||
}
|
||||
if int(res.byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: got %d bytes, want %d\n", name, res.byte_count, len(want))
|
||||
if int(byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: got %d bytes, want %d\n", name, byte_count, len(want))
|
||||
fail += 1
|
||||
return
|
||||
}
|
||||
@@ -55,9 +55,9 @@ check_decode :: proc(name: string, bytes: []u8, want_mn: a.Mnemonic, mode: a.Mod
|
||||
labels: [dynamic]a.Label_Definition
|
||||
errors: [dynamic]a.Error
|
||||
defer { delete(insts); delete(info); delete(labels); delete(errors) }
|
||||
res := a.decode(bytes, relocs, &insts, &info, &labels, &errors, mode)
|
||||
if !res.success || len(insts) == 0 {
|
||||
fmt.printf(" [FAIL] decode %s: success=%v len=%d\n", name, res.success, len(insts))
|
||||
byte_count, success := a.decode(bytes, relocs, &insts, &info, &labels, &errors, mode)
|
||||
if !success || len(insts) == 0 {
|
||||
fmt.printf(" [FAIL] decode %s: success=%v len=%d\n", name, success, len(insts))
|
||||
fail += 1
|
||||
return
|
||||
}
|
||||
@@ -160,8 +160,8 @@ check_roundtrip :: proc(name: string, inst: a.Instruction) {
|
||||
errors: [dynamic]a.Error
|
||||
defer { delete(label_defs); delete(code); delete(relocs); delete(errors) }
|
||||
|
||||
res := a.encode(insts, label_defs[:], code, &relocs, &errors)
|
||||
if !res.success {
|
||||
byte_count, success := a.encode(insts, label_defs[:], code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] roundtrip %s: encode failed\n", name)
|
||||
fail += 1
|
||||
return
|
||||
@@ -174,8 +174,8 @@ check_roundtrip :: proc(name: string, inst: a.Instruction) {
|
||||
dec_err: [dynamic]a.Error
|
||||
defer { delete(decoded); delete(info); delete(labels); delete(dec_err) }
|
||||
|
||||
dec_res := a.decode(code[:res.byte_count], dec_relocs, &decoded, &info, &labels, &dec_err, inst.mode)
|
||||
if !dec_res.success || len(decoded) == 0 {
|
||||
dec_byte_count, dec_success := a.decode(code[:byte_count], dec_relocs, &decoded, &info, &labels, &dec_err, inst.mode)
|
||||
if !dec_success || len(decoded) == 0 {
|
||||
fmt.printf(" [FAIL] roundtrip %s: decode failed\n", name)
|
||||
fail += 1
|
||||
return
|
||||
|
||||
@@ -113,8 +113,8 @@ run_sweep_tests :: proc() {
|
||||
ren_errors: [dynamic]a.Error
|
||||
out: [4]u8
|
||||
defer { delete(ren_relocs); delete(ren_errors) }
|
||||
res := a.encode(insts[:], label_defs[:], out[:], &ren_relocs, &ren_errors, resolve=false)
|
||||
if !res.success {
|
||||
byte_count, success := a.encode(insts[:], label_defs[:], out[:], &ren_relocs, &ren_errors, resolve=false)
|
||||
if !success {
|
||||
stats.fail_encode += 1
|
||||
if failed_examples < max_fail_print && (only_print_kind == "" || only_print_kind == "re-enc") {
|
||||
fmt.printf(" [re-enc ] %v[%d] %08X re-encode failed\n", mn, idx, word)
|
||||
|
||||
@@ -157,7 +157,7 @@ decode_bitmask_imm :: proc "contextless" (n, immr, imms: u8, is_64: bool) -> (va
|
||||
elem_size: u32 = 0
|
||||
s_field: u8 = 0
|
||||
if n == 1 {
|
||||
if !is_64 { return 0, false } // N=1 only valid for 64-bit ops
|
||||
if !is_64 { return } // N=1 only valid for 64-bit ops
|
||||
elem_size = 64
|
||||
s_field = s
|
||||
} else {
|
||||
@@ -179,15 +179,15 @@ decode_bitmask_imm :: proc "contextless" (n, immr, imms: u8, is_64: bool) -> (va
|
||||
elem_size = 2
|
||||
s_field = s & 0b000001
|
||||
case:
|
||||
return 0, false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
width: u32 = is_64 ? 64 : 32
|
||||
if elem_size > width { return 0, false }
|
||||
if elem_size > width { return }
|
||||
|
||||
ones := u32(s_field) + 1
|
||||
if ones == 0 || ones >= elem_size { return 0, false }
|
||||
if ones == 0 || ones >= elem_size { return }
|
||||
|
||||
rotation := u32(immr) & (elem_size - 1)
|
||||
|
||||
@@ -199,10 +199,11 @@ decode_bitmask_imm :: proc "contextless" (n, immr, imms: u8, is_64: bool) -> (va
|
||||
rotated := rotate_right_u64(pattern, inv_rot, elem_size) & elem_mask
|
||||
|
||||
// Replicate to fill width.
|
||||
out: u64 = rotated
|
||||
value = rotated
|
||||
for size: u32 = elem_size; size < width; size *= 2 {
|
||||
out |= out << size
|
||||
value |= value << size
|
||||
}
|
||||
if width == 32 { out &= 0xFFFFFFFF }
|
||||
return out, true
|
||||
if width == 32 { value &= 0xFFFFFFFF }
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,25 +36,24 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
endianness: Endianness = .LITTLE,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data)) & ~u32(3)
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
word := read_u32(data, pc, endianness)
|
||||
for byte_count < n_bytes {
|
||||
word := read_u32(data, byte_count, endianness)
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx := decode_one_inline(word, pc, &inst, &info)
|
||||
entry_idx := decode_one_inline(word, byte_count, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 4}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
} else {
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
for slot in 0..<inst.operand_count {
|
||||
@@ -71,11 +70,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += 4
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -43,24 +43,22 @@ encode :: proc(
|
||||
endianness: Endianness = .LITTLE,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * 4 {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
// ---- PASS 1 -----------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst := &instructions[i]
|
||||
word, ok := encode_one_inline(inst, pc, u16(i), relocs, errors)
|
||||
if !ok { return Result{byte_count = pc, success = false} }
|
||||
write_u32(code, pc, word, endianness)
|
||||
pc += 4
|
||||
word := encode_one_inline(inst, byte_count, u16(i), relocs, errors) or_return
|
||||
write_u32(code, byte_count, word, endianness)
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: fixed-width => *4 -------------------------------------
|
||||
@@ -71,7 +69,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations -------------------------------------
|
||||
@@ -87,7 +86,8 @@ encode :: proc(
|
||||
}
|
||||
if write_idx != n_relocs { resize(relocs, int(write_idx)) }
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -37,7 +37,6 @@ import "../isa"
|
||||
// option 13-15 extend type (data-proc extended register, LDR/STR EXT)
|
||||
// imm3 10-12 extend amount
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -16,8 +16,9 @@ Instruction :: struct #packed {
|
||||
operand_count: u8, // 1
|
||||
flags: Instruction_Flags, // 1
|
||||
length: u8, // 1 -- always 4
|
||||
_: [3]u8,
|
||||
}
|
||||
#assert(size_of(Instruction) == 77)
|
||||
#assert(size_of(Instruction) == 64)
|
||||
|
||||
// =============================================================================
|
||||
// Builders -- the most common shapes; less-common forms can be built
|
||||
|
||||
@@ -89,7 +89,7 @@ Extended_Reg :: struct #packed {
|
||||
|
||||
// 16-byte tagged operand. The union holds whichever payload matches `kind`.
|
||||
Operand :: struct #packed {
|
||||
using _: struct #raw_union {
|
||||
using _: struct #raw_union #packed {
|
||||
reg: Register, // 2
|
||||
mem: Memory, // 12
|
||||
immediate: i64, // 8
|
||||
@@ -97,11 +97,11 @@ Operand :: struct #packed {
|
||||
shifted: Shifted_Reg, // 8
|
||||
extended: Extended_Reg, // 8
|
||||
cond: u8, // 1
|
||||
}, // 16 total because of alignment
|
||||
}, // 12 total because of alignment
|
||||
kind: Operand_Kind, // 1
|
||||
size: u8, // 1 -- carried width info; meaning varies
|
||||
}
|
||||
#assert(size_of(Operand) == 18)
|
||||
#assert(size_of(Operand) == 14)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Constructors -- generic
|
||||
|
||||
@@ -78,8 +78,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_i(.ADD_IMM, a.X0, a.X1, 100),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_IMM: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_IMM: encode", success)
|
||||
eq_word("ADD X0,X1,#100", load_le(code[:], 0), 0x91019020)
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ run_pipeline_tests :: proc() {
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
insts := []a.Instruction{ a.inst_mov_imm(.MOVZ, a.X0, 0x1234, 1) }
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MOVZ: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MOVZ: encode", success)
|
||||
eq_word("MOVZ X0,#0x1234,LSL#16", load_le(code[:], 0), 0xD2A24680)
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X0), a.op_reg(a.X1), a.op_shifted(a.X2, .LSL, 3), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_SR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_SR: encode", success)
|
||||
eq_word("ADD X0,X1,X2,LSL#3", load_le(code[:], 0), 0x8B020C20)
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X0), a.op_reg(a.SP), a.op_extended(a.W1, .UXTW, 2), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_ER: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADD_ER: encode", success)
|
||||
eq_word("ADD X0,SP,W1,UXTW#2", load_le(code[:], 0), 0x8B214BE0)
|
||||
}
|
||||
|
||||
@@ -144,8 +144,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_r_r_r (.UDIV, a.X0, a.X1, a.X2),
|
||||
a.inst_r_r_r_r(.MADD, a.X0, a.X1, a.X2, a.X3),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("UDIV/MADD: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("UDIV/MADD: encode", success)
|
||||
eq_word("UDIV X0,X1,X2", load_le(code[:], 0), 0x9AC20820)
|
||||
eq_word("MADD X0,X1,X2,X3", load_le(code[:], 4), 0x9B020C20)
|
||||
}
|
||||
@@ -160,8 +160,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_r_r(.CLZ, a.X0, a.X1),
|
||||
a.inst_r_r(.REV, a.X0, a.X1),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CLZ/REV: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CLZ/REV: encode", success)
|
||||
eq_word("CLZ X0,X1", load_le(code[:], 0), 0xDAC01020)
|
||||
eq_word("REV X0,X1", load_le(code[:], 4), 0xDAC00C20)
|
||||
}
|
||||
@@ -172,8 +172,8 @@ run_pipeline_tests :: proc() {
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
insts := []a.Instruction{ a.inst_csel(a.X0, a.X1, a.X2, .EQ) }
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CSEL: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CSEL: encode", success)
|
||||
eq_word("CSEL X0,X1,X2,EQ", load_le(code[:], 0), 0x9A820020)
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_ldst(.LDR, a.X0, a.mem_offset(a.X1, 16)),
|
||||
a.inst_ldst(.STR, a.W0, a.mem_offset(a.SP, 20)),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LDR/STR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LDR/STR: encode", success)
|
||||
eq_word("LDR X0,[X1,#16]", load_le(code[:], 0), 0xF9400820)
|
||||
eq_word("STR W0,[SP,#20]", load_le(code[:], 4), 0xB90017E0)
|
||||
}
|
||||
@@ -219,8 +219,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_none(.RET),
|
||||
a.inst_branch(.BL, 0),
|
||||
}
|
||||
r := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode", r.success)
|
||||
byte_count, success := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode", success)
|
||||
eq_word("B forward (+3 words)", load_le(code[:], 0), 0x14000003)
|
||||
eq_word("BL backward (-1 word)", load_le(code[:], 16), 0x97FFFFFF)
|
||||
}
|
||||
@@ -249,8 +249,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_tbz(a.X0, 5, 0),
|
||||
a.inst_none(.NOP), // target
|
||||
}
|
||||
r := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("CBZ/TBZ: encode", r.success)
|
||||
byte_count, success := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("CBZ/TBZ: encode", success)
|
||||
eq_word("CBZ X0,+2 words", load_le(code[:], 0), 0xB4000040)
|
||||
eq_word("TBZ X0,#5,+1 word", load_le(code[:], 4), 0x36280020)
|
||||
}
|
||||
@@ -286,8 +286,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_none(.NOP),
|
||||
a.inst_none(.NOP),
|
||||
}
|
||||
r := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("ADR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("ADR: encode", success)
|
||||
eq_word("ADR X0,+8", load_le(code[:], 0), 0x10000040)
|
||||
}
|
||||
|
||||
@@ -315,8 +315,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X0), a.op_imm(0xDA10, 2), {}, {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("system: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("system: encode", success)
|
||||
eq_word("NOP", load_le(code[:], 0), 0xD503201F)
|
||||
eq_word("SVC #1", load_le(code[:], 4), 0xD4000021)
|
||||
eq_word("MRS X0,NZCV", load_le(code[:], 8), 0xD53B4200)
|
||||
@@ -330,8 +330,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_r(.FADD, a.d_reg(0), a.d_reg(1), a.d_reg(2)),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("FADD: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("FADD: encode", success)
|
||||
eq_word("FADD D0,D1,D2", load_le(code[:], 0), 0x1E622820)
|
||||
}
|
||||
|
||||
@@ -349,16 +349,16 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_cbnz(a.X0, 0),
|
||||
a.inst_none(.RET),
|
||||
}
|
||||
r := a.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode", r.success)
|
||||
byte_count, success := a.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode", success)
|
||||
|
||||
d_insts: [dynamic]a.Instruction
|
||||
d_info: [dynamic]a.Instruction_Info
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("rt: decode", d.success)
|
||||
_, d_success := a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("rt: decode", d_success)
|
||||
ok("rt: 3 insts", len(d_insts) == 3)
|
||||
ok("rt: ADD", d_insts[0].mnemonic == .ADD_IMM)
|
||||
ok("rt: CBNZ", d_insts[1].mnemonic == .CBNZ)
|
||||
@@ -385,15 +385,15 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_b_cond(.EQ, 0),
|
||||
a.inst_none(.RET),
|
||||
}
|
||||
r := a.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("b.cond: encode", r.success)
|
||||
byte_count, success := a.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("b.cond: encode", success)
|
||||
|
||||
d_insts: [dynamic]a.Instruction
|
||||
d_info: [dynamic]a.Instruction_Info
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
|
||||
text := a.aprint(d_insts[:], d_info[:], d_labels[:],
|
||||
nil, nil, nil, context.temp_allocator)
|
||||
@@ -423,8 +423,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_i(.ORR_IMM, a.X0, a.X1, transmute(i64)mask),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("bitmask ORR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("bitmask ORR: encode", success)
|
||||
eq_word("ORR X0,X1,#0xFF00.. repeat", load_le(code[:], 0), 0xB2089C20)
|
||||
|
||||
// Round-trip: decode and verify operand round-trips back to the raw mask.
|
||||
@@ -433,7 +433,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("bitmask ORR: decode", len(d_insts) == 1 && d_insts[0].mnemonic == .ORR_IMM)
|
||||
if len(d_insts) == 1 {
|
||||
got := u64(d_insts[0].ops[2].immediate)
|
||||
@@ -457,8 +457,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_i(.AND_IMM, a.W0, a.W1, 0xF0),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("bitmask AND 32: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("bitmask AND 32: encode", success)
|
||||
eq_word("AND W0,W1,#0xF0", load_le(code[:], 0), 0x12040C20)
|
||||
|
||||
// Round-trip.
|
||||
@@ -467,7 +467,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("bitmask AND 32: decode", len(d_insts) == 1 && d_insts[0].mnemonic == .AND_IMM)
|
||||
if len(d_insts) == 1 {
|
||||
got := u64(d_insts[0].ops[2].immediate)
|
||||
@@ -487,8 +487,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_z_s(0), a.op_z_s(1), a.op_z_s(2), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE ADD Z: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE ADD Z: encode", success)
|
||||
eq_word("SVE ADD Z0.S,Z1.S,Z2.S", load_le(code[:], 0), 0x04A20020)
|
||||
|
||||
// Round-trip.
|
||||
@@ -497,7 +497,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("SVE ADD Z: decode", len(d_insts) == 1 && d_insts[0].mnemonic == .SVE_ADD_Z)
|
||||
}
|
||||
|
||||
@@ -514,8 +514,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_z_s(0), a.op_reg(p0), a.op_z_s(0), a.op_z_s(1)},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE ADD_PRED: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE ADD_PRED: encode", success)
|
||||
eq_word("SVE ADD Z0.S,P0/M,Z0.S,Z1.S", load_le(code[:], 0), 0x04810000)
|
||||
}
|
||||
|
||||
@@ -532,8 +532,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(p0), a.op_imm(0x1F, 1), {}, {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE PTRUE: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE PTRUE: encode", success)
|
||||
eq_word("SVE PTRUE P0.B,ALL", load_le(code[:], 0), 0x2518E3E0)
|
||||
}
|
||||
|
||||
@@ -547,8 +547,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_none(.SME_SMSTART),
|
||||
a.inst_none(.SME_SMSTOP),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME SMSTART/SMSTOP: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME SMSTART/SMSTOP: encode", success)
|
||||
eq_word("SME SMSTART", load_le(code[:], 0), 0xD503477F)
|
||||
eq_word("SME SMSTOP", load_le(code[:], 4), 0xD503467F)
|
||||
}
|
||||
@@ -572,8 +572,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_imm(0, 1), a.op_reg(p0), a.op_reg(p1), a.op_z_s(0)},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME FMOPA: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME FMOPA: encode", success)
|
||||
eq_word("SME FMOPA ZA0.S,P0/M,P1/M,Z0.S,Z0.S", load_le(code[:], 0), 0x80802000)
|
||||
}
|
||||
|
||||
@@ -595,8 +595,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_r(.AMX_STZ, a.X3),
|
||||
a.inst_none(.AMX_CLR),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("AMX: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("AMX: encode", success)
|
||||
eq_word("AMX SET", load_le(code[:], 0), 0x00201220)
|
||||
eq_word("AMX LDX X0", load_le(code[:], 4), 0x00201000)
|
||||
eq_word("AMX LDY X1", load_le(code[:], 8), 0x00201021)
|
||||
@@ -610,8 +610,8 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("AMX: decode", d.success)
|
||||
_, d_success := a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("AMX: decode", d_success)
|
||||
ok("AMX: 6 insts", len(d_insts) == 6)
|
||||
ok("AMX: SET", len(d_insts) >= 1 && d_insts[0].mnemonic == .AMX_SET)
|
||||
ok("AMX: LDX", len(d_insts) >= 2 && d_insts[1].mnemonic == .AMX_LDX)
|
||||
@@ -651,9 +651,9 @@ run_pipeline_tests :: proc() {
|
||||
isa.label_set_at(&labels, fwd, &insts) // .L1: at instruction 4
|
||||
append(&insts, a.inst_none(.RET)) // [4] byte 16: RET
|
||||
|
||||
r := a.encode(insts[:], labels[:], code[:], &relocs, &errors)
|
||||
ok("anon labels: encode", r.success)
|
||||
ok("anon labels: byte_count = 20", r.byte_count == 20)
|
||||
byte_count, success := a.encode(insts[:], labels[:], code[:], &relocs, &errors)
|
||||
ok("anon labels: encode", success)
|
||||
ok("anon labels: byte_count = 20", byte_count == 20)
|
||||
|
||||
// B.LT at byte 8 -> .L1 at byte 16: offset = +8 = +2 words.
|
||||
// bits = 0x54000000 | (2<<5) | LT(0xB) = 0x5400004B
|
||||
@@ -670,7 +670,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("anon labels: decode 5 insts", len(d_insts) == 5)
|
||||
have_back, have_fwd: bool
|
||||
for ld in d_labels {
|
||||
@@ -713,8 +713,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X3), a.op_imm(a.DCZID_EL0, 2), {}, {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("sysreg: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("sysreg: encode", success)
|
||||
eq_word("MRS X0,NZCV", load_le(code[:], 0), 0xD53B4200)
|
||||
eq_word("MRS X1,TPIDR_EL0", load_le(code[:], 4), 0xD53BD041)
|
||||
eq_word("MRS X2,CNTVCT_EL0", load_le(code[:], 8), 0xD53BE042)
|
||||
@@ -729,8 +729,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r(.DC_ZVA, a.X0),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("DC ZVA: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("DC ZVA: encode", success)
|
||||
eq_word("DC ZVA X0", load_le(code[:], 0), 0xD50B7420)
|
||||
|
||||
d_insts: [dynamic]a.Instruction
|
||||
@@ -738,7 +738,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("DC ZVA: decode", len(d_insts) == 1 && d_insts[0].mnemonic == .DC_ZVA)
|
||||
ok("DC ZVA: Rt = X0", len(d_insts) == 1 && d_insts[0].ops[0].reg == a.X0)
|
||||
}
|
||||
@@ -756,8 +756,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X0), a.op_shifted(a.X1, .LSL, 0), {}, {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CMP_SR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CMP_SR: encode", success)
|
||||
eq_word("CMP X0,X1", load_le(code[:], 0), 0xEB01001F)
|
||||
}
|
||||
|
||||
@@ -786,8 +786,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X0), a.op_reg(a.X1), a.op_reg(a.X2), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MOPS CPY: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MOPS CPY: encode", success)
|
||||
eq_word("CPYP X0,X1,X2", load_le(code[:], 0), 0x1D020420)
|
||||
eq_word("CPYM X0,X1,X2", load_le(code[:], 4), 0x1D420420)
|
||||
eq_word("CPYE X0,X1,X2", load_le(code[:], 8), 0x1D820420)
|
||||
@@ -808,8 +808,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_z_s(0), a.op_z_s(1), a.op_z_s(2), a.op_imm(2, 1)},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE FMLA indexed: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE FMLA indexed: encode", success)
|
||||
eq_word("SVE FMLA Z0.S, Z1.S, Z2.S[2]", load_le(code[:], 0), 0x64B20020)
|
||||
}
|
||||
|
||||
@@ -835,8 +835,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_z_s(0), a.op_reg(p0), a.op_mem(mem), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE LD1W gather: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SVE LD1W gather: encode", success)
|
||||
eq_word("SVE LD1W Z0.S,P0/Z,[X1,Z2.S,UXTW]", load_le(code[:], 0), 0x85024020)
|
||||
}
|
||||
|
||||
@@ -864,8 +864,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_imm(slice_packed, 2), a.op_reg(p5), a.op_mem(mem), {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME LD1B tile vs LLVM: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("SME LD1B tile vs LLVM: encode", success)
|
||||
eq_word("SME LD1B ZA0V.B[W14,5],P5/Z,[X10,X21]", load_le(code[:], 0), 0xE015D545)
|
||||
|
||||
d_insts: [dynamic]a.Instruction
|
||||
@@ -873,7 +873,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]a.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
a.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
a.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("SME LD1B tile: decode 1 inst", len(d_insts) == 1)
|
||||
ok("SME LD1B tile: mnemonic", len(d_insts) == 1 && d_insts[0].mnemonic == .SME_LD1B_TILE)
|
||||
ok("SME LD1B tile: slice roundtrip",
|
||||
@@ -891,8 +891,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_v_4s(0), a.op_v_4s(1), a.op_v_4s(2), a.op_imm(0, 1)},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("FCMLA: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("FCMLA: encode", success)
|
||||
eq_word("FCMLA V0.4S,V1.4S,V2.4S,#0", load_le(code[:], 0), 0x6E82C420)
|
||||
}
|
||||
|
||||
@@ -908,8 +908,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_none(.TCOMMIT),
|
||||
a.inst_r(.TTEST, a.X1),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("TME: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("TME: encode", success)
|
||||
eq_word("TSTART X0", load_le(code[:], 0), 0xD5233060)
|
||||
eq_word("TCOMMIT", load_le(code[:], 4), 0xD503307F)
|
||||
eq_word("TTEST X1", load_le(code[:], 8), 0xD5233161)
|
||||
@@ -925,8 +925,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_r_r(.UXTB, a.W0, a.W1),
|
||||
a.inst_r_r(.SXTW, a.X0, a.W1),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("extend aliases: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("extend aliases: encode", success)
|
||||
eq_word("UXTB W0,W1", load_le(code[:], 0), 0x53001C20)
|
||||
eq_word("SXTW X0,W1", load_le(code[:], 4), 0x93407C20)
|
||||
}
|
||||
@@ -942,8 +942,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_r_r_r(.ADC, a.X0, a.X1, a.X2),
|
||||
a.inst_r_r_r(.SBCS, a.X3, a.X4, a.X5),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADC/SBCS: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ADC/SBCS: encode", success)
|
||||
eq_word("ADC X0,X1,X2", load_le(code[:], 0), 0x9A020020)
|
||||
eq_word("SBCS X3,X4,X5", load_le(code[:], 4), 0xFA050083)
|
||||
}
|
||||
@@ -961,8 +961,8 @@ run_pipeline_tests :: proc() {
|
||||
ops = {a.op_reg(a.X7), a.op_imm(a.RNDR, 2), {}, {}},
|
||||
},
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MRS X7,RNDR: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("MRS X7,RNDR: encode", success)
|
||||
eq_word("MRS X7,RNDR", load_le(code[:], 0), 0xD53B2407)
|
||||
}
|
||||
|
||||
@@ -979,8 +979,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_ldst(.LDAPUR, a.X0, a.mem_offset(a.X1, 8)),
|
||||
a.inst_ldst(.STLUR, a.X2, a.mem_offset(a.SP, -8)),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("RCpc unscaled: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("RCpc unscaled: encode", success)
|
||||
eq_word("LDAPUR X0,[X1,#8]", load_le(code[:], 0), 0xD9408020)
|
||||
eq_word("STLUR X2,[SP,#-8]", load_le(code[:], 4), 0xD91F83E2)
|
||||
}
|
||||
@@ -994,8 +994,8 @@ run_pipeline_tests :: proc() {
|
||||
a.inst_none(.BTI_J),
|
||||
a.inst_none(.PSB_CSYNC),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("barriers/BTI: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("barriers/BTI: encode", success)
|
||||
eq_word("SB", load_le(code[:], 0), 0xD50330FF)
|
||||
eq_word("BTI j", load_le(code[:], 4), 0xD503245F)
|
||||
eq_word("PSB CSYNC", load_le(code[:], 8), 0xD503223F)
|
||||
@@ -1011,8 +1011,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_i(.LSL_IMM, a.W0, a.W1, 4),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LSL_IMM 32: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LSL_IMM 32: encode", success)
|
||||
eq_word("LSL W0,W1,#4", load_le(code[:], 0), 0x531C6C20)
|
||||
}
|
||||
|
||||
@@ -1026,8 +1026,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []a.Instruction{
|
||||
a.inst_r_r_i(.ROR_IMM, a.W0, a.W1, 4),
|
||||
}
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ROR_IMM 32: encode", r.success)
|
||||
byte_count, success := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("ROR_IMM 32: encode", success)
|
||||
eq_word("ROR W0,W1,#4", load_le(code[:], 0), 0x13811020)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,17 +51,8 @@ compile time — no table is built during a normal library build:
|
||||
odin run <arch>/tablegen # ENCODING_TABLE -> generated Odin + <arch>/tables.odin
|
||||
odin run <arch>/tablegen/generated # -> <arch>/tables/<arch>.*.bin
|
||||
```
|
||||
|
||||
Regenerate after editing `ENCODING_TABLE`. See `docs/table_migration.md`.
|
||||
|
||||
## Performance (x86)
|
||||
|
||||
With `-o:speed -microarch:native -no-bounds-check`:
|
||||
- Encoder: ~17 M instructions/sec (~56 MB/s)
|
||||
- Decoder: ~16 M instructions/sec (~54 MB/s)
|
||||
|
||||
Measured on AMD Ryzen 3950X.
|
||||
|
||||
## Usage
|
||||
|
||||
```odin
|
||||
|
||||
@@ -43,7 +43,3 @@ Error :: struct #packed {
|
||||
}
|
||||
#assert(size_of(Error) == 8)
|
||||
|
||||
Result :: struct {
|
||||
byte_count: u32, // Bytes written/read
|
||||
success: bool, // True if no errors
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
endianness: Endianness = .BIG,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data))
|
||||
if n_bytes & 3 != 0 {
|
||||
n_bytes &= ~u32(3) // ignore the dangling tail
|
||||
@@ -68,18 +68,17 @@ decode :: proc(
|
||||
defer delete(pending_branches)
|
||||
|
||||
// ---- PASS 1 -----------------------------------------------------------
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
word := read_u32(data, pc, endianness)
|
||||
for byte_count < n_bytes {
|
||||
word := read_u32(data, byte_count, endianness)
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx := decode_one_inline(word, pc, &inst, &info)
|
||||
entry_idx := decode_one_inline(word, byte_count, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 4}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
} else {
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
for slot in 0..<inst.operand_count {
|
||||
@@ -96,13 +95,14 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += 4
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
// ---- PASS 2: label inference -----------------------------------------
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -71,26 +71,22 @@ encode :: proc(
|
||||
endianness: Endianness = .BIG,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * 4 {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
// ---- PASS 1 ------------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst := &instructions[i]
|
||||
word, ok := encode_one_inline(inst, pc, u16(i), relocs, errors)
|
||||
if !ok {
|
||||
return Result{byte_count = pc, success = false}
|
||||
}
|
||||
write_u32(code, pc, word, endianness)
|
||||
pc += 4
|
||||
word := encode_one_inline(inst, byte_count, u16(i), relocs, errors) or_return
|
||||
write_u32(code, byte_count, word, endianness)
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: rewrite label_defs from inst-idx to byte-offset --------
|
||||
@@ -102,7 +98,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations ---------------------------------------
|
||||
@@ -124,7 +121,8 @@ encode :: proc(
|
||||
resize(relocs, int(write_idx))
|
||||
}
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -35,7 +35,6 @@ import "../isa"
|
||||
// (op=0x18, 0x19, 0x35, 0x37, 0x3F, ...) with their own layouts.
|
||||
|
||||
// Re-exports from isa.
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -80,8 +80,8 @@ run_decoder_tests :: proc() {
|
||||
mips.inst_r_i (.LUI, mips.T0, 0x1234),
|
||||
mips.inst_shift(.SLL, mips.T0, mips.T1, 5),
|
||||
}
|
||||
eres := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("rt: encode ok", eres.success, true)
|
||||
ebyte_count, esuccess := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("rt: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -91,11 +91,11 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
|
||||
dcheck_bool("rt: decode ok", dres.success, true)
|
||||
dcheck_int ("rt: byte_count", int(dres.byte_count), 24)
|
||||
dcheck_bool("rt: decode ok", dsuccess, true)
|
||||
dcheck_int ("rt: byte_count", int(dbyte_count), 24)
|
||||
dcheck_int ("rt: instruction n", len(dec_insts), 6)
|
||||
dcheck_int ("rt: info n", len(dec_info), 6)
|
||||
dcheck_int ("rt: errors n", len(errors), 0)
|
||||
@@ -161,8 +161,8 @@ run_decoder_tests :: proc() {
|
||||
mips.inst_branch2(.BNE, mips.T0, mips.ZERO, 0),
|
||||
mips.inst_none(.NOP),
|
||||
}
|
||||
eres := mips.encode(src, ld_in[:], code[:], &relocs, &errors)
|
||||
dcheck_bool("br: encode ok", eres.success, true)
|
||||
ebyte_count, esuccess := mips.encode(src, ld_in[:], code[:], &relocs, &errors)
|
||||
dcheck_bool("br: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -172,9 +172,9 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
dcheck_bool("br: decode ok", dres.success, true)
|
||||
dcheck_bool("br: decode ok", dsuccess, true)
|
||||
dcheck_int ("br: insts", len(dec_insts), 4)
|
||||
dcheck_mnem("br: BNE", dec_insts[2].mnemonic, .BNE)
|
||||
|
||||
@@ -206,9 +206,9 @@ run_decoder_tests :: proc() {
|
||||
mips.inst_none(.NOP),
|
||||
mips.inst_none(.NOP),
|
||||
}
|
||||
eres := mips.encode(src, ld_in[:], code[:], &relocs, &errors,
|
||||
ebyte_count, esuccess := mips.encode(src, ld_in[:], code[:], &relocs, &errors,
|
||||
base_address = 0)
|
||||
dcheck_bool("J: encode ok", eres.success, true)
|
||||
dcheck_bool("J: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -218,9 +218,9 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
dcheck_bool("J: decode ok", dres.success, true)
|
||||
dcheck_bool("J: decode ok", dsuccess, true)
|
||||
dcheck_mnem("J: mnemonic", dec_insts[0].mnemonic, .J)
|
||||
dcheck_int ("J: op kind", int(dec_insts[0].ops[0].kind),
|
||||
int(mips.Operand_Kind.RELATIVE))
|
||||
@@ -235,8 +235,8 @@ run_decoder_tests :: proc() {
|
||||
src := []mips.Instruction{
|
||||
mips.inst_r_r_r(.ADD_S, mips.F4, mips.F5, mips.F6),
|
||||
}
|
||||
eres := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("FPU: encode ok", eres.success, true)
|
||||
ebyte_count, esuccess := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("FPU: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -246,9 +246,9 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
dcheck_bool("FPU: decode ok", dres.success, true)
|
||||
dcheck_bool("FPU: decode ok", dsuccess, true)
|
||||
dcheck_mnem("FPU: ADD.S", dec_insts[0].mnemonic, .ADD_S)
|
||||
i0 := dec_insts[0]
|
||||
dcheck_reg ("FPU: op0=F4", i0.ops[0].reg, mips.F4)
|
||||
@@ -262,8 +262,8 @@ run_decoder_tests :: proc() {
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
|
||||
src := []mips.Instruction{mips.inst_none(.RTPS)}
|
||||
eres := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("GTE: encode ok", eres.success, true)
|
||||
ebyte_count, esuccess := mips.encode(src, nil, code[:], &relocs, &errors)
|
||||
dcheck_bool("GTE: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -273,9 +273,9 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
dcheck_bool("GTE: decode ok", dres.success, true)
|
||||
dcheck_bool("GTE: decode ok", dsuccess, true)
|
||||
dcheck_mnem("GTE: RTPS", dec_insts[0].mnemonic, .RTPS)
|
||||
dcheck_int ("GTE: opcnt 0", int(dec_insts[0].operand_count), 0)
|
||||
}
|
||||
@@ -288,9 +288,9 @@ run_decoder_tests :: proc() {
|
||||
src := []mips.Instruction{
|
||||
mips.inst_r_r_r(.ADD, mips.T0, mips.T1, mips.T2),
|
||||
}
|
||||
eres := mips.encode(src, nil, code[:], &relocs, &errors,
|
||||
ebyte_count, esuccess := mips.encode(src, nil, code[:], &relocs, &errors,
|
||||
endianness = .LITTLE)
|
||||
dcheck_bool("LE: encode ok", eres.success, true)
|
||||
dcheck_bool("LE: encode ok", esuccess, true)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -300,10 +300,10 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:ebyte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors,
|
||||
endianness = .LITTLE)
|
||||
dcheck_bool("LE: decode ok", dres.success, true)
|
||||
dcheck_bool("LE: decode ok", dsuccess, true)
|
||||
dcheck_mnem("LE: ADD", dec_insts[0].mnemonic, .ADD)
|
||||
}
|
||||
|
||||
@@ -326,9 +326,9 @@ run_decoder_tests :: proc() {
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:4], nil,
|
||||
dbyte_count, dsuccess := mips.decode(code[:4], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
dcheck_bool("garbage: success", dres.success, false)
|
||||
dcheck_bool("garbage: success", dsuccess, false)
|
||||
dcheck_int ("garbage: insts", len(dec_insts), 1)
|
||||
dcheck_mnem("garbage: INVALID", dec_insts[0].mnemonic, .INVALID)
|
||||
dcheck_int ("garbage: errors n", len(errors), 1)
|
||||
|
||||
@@ -88,10 +88,10 @@ run_encoder_tests :: proc() {
|
||||
mips.inst_r_i (.LUI, mips.T0, 0x1234), // 0x3C081234
|
||||
mips.inst_shift(.SLL, mips.T0, mips.T1, 5), // 0x00094140
|
||||
}
|
||||
res := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
|
||||
check_bool("core: success", res.success, true)
|
||||
check_int ("core: byte_count", int(res.byte_count), 24)
|
||||
check_bool("core: success", success, true)
|
||||
check_int ("core: byte_count", int(byte_count), 24)
|
||||
check_int ("core: errors len", len(errors), 0)
|
||||
check_int ("core: relocs len", len(relocs), 0)
|
||||
check_word("core: ADD t0,t1,t2", load_word_be(code[:], 0), 0x012A4020)
|
||||
@@ -111,10 +111,10 @@ run_encoder_tests :: proc() {
|
||||
insts := []mips.Instruction{
|
||||
mips.inst_r_r_r(.ADD, mips.T0, mips.T1, mips.T2),
|
||||
}
|
||||
res := mips.encode(insts, nil, code[:], &relocs, &errors,
|
||||
byte_count, success := mips.encode(insts, nil, code[:], &relocs, &errors,
|
||||
endianness = .LITTLE)
|
||||
|
||||
check_bool("LE: success", res.success, true)
|
||||
check_bool("LE: success", success, true)
|
||||
check_word("LE: ADD (le bytes)",
|
||||
load_word_le(code[:], 0), // reads as native u32 little-endian
|
||||
0x012A4020)
|
||||
@@ -144,12 +144,12 @@ run_encoder_tests :: proc() {
|
||||
mips.inst_branch2(.BNE, mips.T0, mips.ZERO, 0),
|
||||
mips.inst_none(.NOP),
|
||||
}
|
||||
res := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
|
||||
// BNE t0, zero, -3 = (op=5 << 26) | (rs=8 << 21) | (rt=0 << 16) | 0xFFFD
|
||||
// = 0x14000000 | 0x01000000 | 0xFFFD
|
||||
// = 0x1500FFFD
|
||||
check_bool("brB: success", res.success, true)
|
||||
check_bool("brB: success", success, true)
|
||||
check_int ("brB: relocs len",len(relocs), 0) // resolved
|
||||
check_int ("brB: errors len",len(errors), 0)
|
||||
check_word("brB: BNE -3w", load_word_be(code[:], 8), 0x1500FFFD)
|
||||
@@ -177,10 +177,10 @@ run_encoder_tests :: proc() {
|
||||
mips.inst_none(.NOP),
|
||||
mips.inst_r_r_r(.ADD, mips.T2, mips.T2, mips.T2),
|
||||
}
|
||||
res := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
|
||||
// BEQ t0,t1,+2 = 0x10000000 | (8<<21) | (9<<16) | 0x0002 = 0x11090002
|
||||
check_bool("brF: success", res.success, true)
|
||||
check_bool("brF: success", success, true)
|
||||
check_int ("brF: relocs len",len(relocs), 0)
|
||||
check_word("brF: BEQ +2w", load_word_be(code[:], 0), 0x11090002)
|
||||
check_int ("brF: label_def[0]", int(label_defs[0]), 12)
|
||||
@@ -209,10 +209,10 @@ run_encoder_tests :: proc() {
|
||||
mips.inst_none(.NOP),
|
||||
mips.inst_none(.NOP), // target
|
||||
}
|
||||
res := mips.encode(insts, label_defs[:], code[:], &relocs, &errors,
|
||||
byte_count, success := mips.encode(insts, label_defs[:], code[:], &relocs, &errors,
|
||||
base_address = 0x80000000)
|
||||
|
||||
check_bool("J: success", res.success, true)
|
||||
check_bool("J: success", success, true)
|
||||
check_int ("J: relocs len", len(relocs), 0)
|
||||
check_word("J: encoded", load_word_be(code[:], 0), 0x08000004)
|
||||
}
|
||||
@@ -230,9 +230,9 @@ run_encoder_tests :: proc() {
|
||||
insts := []mips.Instruction{
|
||||
mips.inst_branch2(.BEQ, mips.T0, mips.T1, 0),
|
||||
}
|
||||
res := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
|
||||
check_bool("unres: success", res.success, true)
|
||||
check_bool("unres: success", success, true)
|
||||
check_int ("unres: relocs left", len(relocs), 1) // kept for linker
|
||||
check_int ("unres: errors len", len(errors), 0)
|
||||
}
|
||||
@@ -256,9 +256,9 @@ run_encoder_tests :: proc() {
|
||||
insts := []mips.Instruction{
|
||||
mips.inst_branch2(.BEQ, mips.T0, mips.T1, 0),
|
||||
}
|
||||
res := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, label_defs[:], code[:], &relocs, &errors)
|
||||
|
||||
check_bool("OOR: success", res.success, false) // had errors
|
||||
check_bool("OOR: success", success, false) // had errors
|
||||
check_int ("OOR: relocs len", len(relocs), 0) // patched (truncated)
|
||||
check_int ("OOR: errors len", len(errors), 1)
|
||||
// Error code should be LABEL_OUT_OF_RANGE.
|
||||
@@ -277,9 +277,9 @@ run_encoder_tests :: proc() {
|
||||
mips.inst_none(.NOP),
|
||||
}
|
||||
small_code: [4]u8
|
||||
res := mips.encode(insts, nil, small_code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, nil, small_code[:], &relocs, &errors)
|
||||
|
||||
check_bool("OVF: success", res.success, false)
|
||||
check_bool("OVF: success", success, false)
|
||||
check_int ("OVF: errors len", len(errors), 1)
|
||||
check_bool("OVF: error code",
|
||||
len(errors) > 0 && errors[0].code == .BUFFER_OVERFLOW,
|
||||
@@ -300,9 +300,9 @@ run_encoder_tests :: proc() {
|
||||
insts := []mips.Instruction{
|
||||
mips.inst_r_r_r(.ADD_S, mips.F4, mips.F5, mips.F6),
|
||||
}
|
||||
res := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
|
||||
check_bool("FPU: success", res.success, true)
|
||||
check_bool("FPU: success", success, true)
|
||||
check_word("FPU: ADD.S", load_word_be(code[:], 0), 0x46062900)
|
||||
}
|
||||
|
||||
@@ -316,9 +316,9 @@ run_encoder_tests :: proc() {
|
||||
insts := []mips.Instruction{
|
||||
mips.inst_none(.RTPS),
|
||||
}
|
||||
res := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
byte_count, success := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
|
||||
check_bool("GTE: success", res.success, true)
|
||||
check_bool("GTE: success", success, true)
|
||||
check_word("GTE: RTPS", load_word_be(code[:], 0), 0x4A000001)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ encode_and_print :: proc(
|
||||
defer delete(relocs)
|
||||
defer delete(errors)
|
||||
|
||||
eres := mips.encode(insts, label_defs, code[:], &relocs, &errors)
|
||||
if !eres.success { return "<encode failed>" }
|
||||
byte_count, esuccess := mips.encode(insts, label_defs, code[:], &relocs, &errors)
|
||||
if !esuccess { return "<encode failed>" }
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -49,9 +49,9 @@ encode_and_print :: proc(
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
|
||||
dres := mips.decode(code[:eres.byte_count], nil,
|
||||
_, dsuccess := mips.decode(code[:byte_count], nil,
|
||||
&dec_insts, &dec_info, &dec_labels, &errors)
|
||||
if !dres.success { return "<decode failed>" }
|
||||
if !dsuccess { return "<decode failed>" }
|
||||
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
mips.sbprint(&sb, dec_insts[:], dec_info[:], dec_labels[:])
|
||||
@@ -162,7 +162,7 @@ run_printer_tests :: proc() {
|
||||
mips.inst_none(.NOP),
|
||||
mips.inst_branch2(.BEQ, mips.T0, mips.T1, 0),
|
||||
}
|
||||
eres := mips.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
byte_count, _ := mips.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -171,7 +171,7 @@ run_printer_tests :: proc() {
|
||||
defer delete(dec_info)
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
mips.decode(code[:eres.byte_count], nil, &dec_insts, &dec_info, &dec_labels, &errors)
|
||||
mips.decode(code[:byte_count], nil, &dec_insts, &dec_info, &dec_labels, &errors)
|
||||
|
||||
names: map[u32]string
|
||||
defer delete(names)
|
||||
@@ -197,7 +197,7 @@ run_printer_tests :: proc() {
|
||||
errors: [dynamic]mips.Error
|
||||
defer delete(relocs)
|
||||
defer delete(errors)
|
||||
eres := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
byte_count, _ := mips.encode(insts, nil, code[:], &relocs, &errors)
|
||||
|
||||
dec_insts: [dynamic]mips.Instruction
|
||||
dec_info: [dynamic]mips.Instruction_Info
|
||||
@@ -206,7 +206,7 @@ run_printer_tests :: proc() {
|
||||
defer delete(dec_info)
|
||||
defer delete(dec_labels)
|
||||
clear(&errors)
|
||||
mips.decode(code[:eres.byte_count], nil, &dec_insts, &dec_info, &dec_labels, &errors)
|
||||
mips.decode(code[:byte_count], nil, &dec_insts, &dec_info, &dec_labels, &errors)
|
||||
|
||||
out := mips.aprint(dec_insts[:], dec_info[:], dec_labels[:],
|
||||
nil, &opts, nil, context.temp_allocator)
|
||||
|
||||
@@ -44,23 +44,22 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
cpu: CPU = .NMOS,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data))
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
for byte_count < n_bytes {
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx, consumed := decode_one_inline(data, pc, n_bytes, cpu, &inst, &info)
|
||||
entry_idx, consumed := decode_one_inline(data, byte_count, n_bytes, cpu, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 1}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
consumed = 1
|
||||
} else {
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
@@ -78,11 +77,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += consumed
|
||||
byte_count += consumed
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -48,47 +48,43 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
|
||||
inst_offsets := make([]u32, n_inst, context.temp_allocator)
|
||||
|
||||
pc: u32 = 0
|
||||
|
||||
// ---- PASS 1 -----------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst_offsets[i] = pc
|
||||
inst_offsets[i] = byte_count
|
||||
|
||||
inst := &instructions[i]
|
||||
form, ok := find_form_inline(inst, u16(i), errors)
|
||||
if !ok {
|
||||
return Result{byte_count = pc, success = false}
|
||||
}
|
||||
form := find_form_inline(inst, u16(i), errors) or_return
|
||||
|
||||
if pc + u32(form.length) > u32(len(code)) {
|
||||
if byte_count + u32(form.length) > u32(len(code)) {
|
||||
append(errors, Error{inst_idx = i, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = pc, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
// Opcode byte
|
||||
code[pc] = form.opcode
|
||||
code[byte_count] = form.opcode
|
||||
|
||||
// Operand bytes
|
||||
if form.enc[0] != .NONE { pack_operand_inline(&inst.ops[0], form.enc[0], pc, u16(i), code, relocs) }
|
||||
if form.enc[1] != .NONE { pack_operand_inline(&inst.ops[1], form.enc[1], pc, u16(i), code, relocs) }
|
||||
if form.enc[2] != .NONE { pack_operand_inline(&inst.ops[2], form.enc[2], pc, u16(i), code, relocs) }
|
||||
if form.enc[0] != .NONE { pack_operand_inline(&inst.ops[0], form.enc[0], byte_count, u16(i), code, relocs) }
|
||||
if form.enc[1] != .NONE { pack_operand_inline(&inst.ops[1], form.enc[1], byte_count, u16(i), code, relocs) }
|
||||
if form.enc[2] != .NONE { pack_operand_inline(&inst.ops[2], form.enc[2], byte_count, u16(i), code, relocs) }
|
||||
|
||||
inst.length = form.length
|
||||
pc += u32(form.length)
|
||||
byte_count += u32(form.length)
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: inst-index -> byte-offset -----------------------------
|
||||
isa.rewrite_label_defs_to_offsets(label_defs, inst_offsets)
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations --------------------------------------
|
||||
@@ -108,7 +104,8 @@ encode :: proc(
|
||||
resize(relocs, int(write_idx))
|
||||
}
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -27,7 +27,6 @@ import "../isa"
|
||||
// 65C816 (SNES, Apple IIgs) is a separate 16/24-bit ISA and lives in a
|
||||
// sibling subpackage if/when added.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -69,8 +69,8 @@ encode_one :: proc(insts: []m.Instruction) -> ([]u8, bool) {
|
||||
@(static) errors: [dynamic]m.Error
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
r := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
return code[:r.byte_count], r.success
|
||||
byte_count, success := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
return code[:byte_count], success
|
||||
}
|
||||
|
||||
run_pipeline_tests :: proc() {
|
||||
@@ -139,10 +139,10 @@ run_pipeline_tests :: proc() {
|
||||
m.inst_rel(.BNE, 0),
|
||||
m.inst_none(.RTS),
|
||||
}
|
||||
r := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode ok", r.success)
|
||||
byte_count, success := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode ok", success)
|
||||
// BNE rel byte at code[4]; target = 0, next_pc = 5, rel = -5.
|
||||
eq_bytes("br: bytes", code[:r.byte_count],
|
||||
eq_bytes("br: bytes", code[:byte_count],
|
||||
{0xA5, 0x42, 0xCA, 0xD0, 0xFB, 0x60})
|
||||
// label_defs[0] should be byte offset 0.
|
||||
ok("br: label_def[0] = 0", int(ld[0]) == 0)
|
||||
@@ -173,10 +173,10 @@ run_pipeline_tests :: proc() {
|
||||
mnemonic = .JMP, operand_count = 1, length = 0,
|
||||
ops = {m.op_label(0, 2), {}, {}},
|
||||
}
|
||||
r := m.encode(insts, ld[:], code[:], &relocs, &errors,
|
||||
byte_count, success := m.encode(insts, ld[:], code[:], &relocs, &errors,
|
||||
base_address = 0x8000)
|
||||
ok("jmp lbl: encode ok", r.success)
|
||||
eq_bytes("jmp lbl: bytes", code[:r.byte_count],
|
||||
ok("jmp lbl: encode ok", success)
|
||||
eq_bytes("jmp lbl: bytes", code[:byte_count],
|
||||
{0xEA, 0x4C, 0x04, 0x80, 0x60})
|
||||
}
|
||||
|
||||
@@ -195,8 +195,8 @@ run_pipeline_tests :: proc() {
|
||||
m.inst_rel(.BNE, 0),
|
||||
m.inst_none(.RTS),
|
||||
}
|
||||
r := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", r.success)
|
||||
byte_count, success := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", success)
|
||||
|
||||
d_insts: [dynamic]m.Instruction
|
||||
d_info: [dynamic]m.Instruction_Info
|
||||
@@ -205,9 +205,9 @@ run_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := m.decode(code[:r.byte_count], nil,
|
||||
_, dsuccess := m.decode(code[:byte_count], nil,
|
||||
&d_insts, &d_info, &d_labels, &errors)
|
||||
ok("rt: decode ok", d.success)
|
||||
ok("rt: decode ok", dsuccess)
|
||||
ok("rt: 4 insts", len(d_insts) == 4)
|
||||
ok("rt: LDA", d_insts[0].mnemonic == .LDA)
|
||||
ok("rt: BNE", d_insts[2].mnemonic == .BNE)
|
||||
@@ -235,7 +235,7 @@ run_pipeline_tests :: proc() {
|
||||
m.inst_m(.STZ, m.mem_abs(0x1234)), // 65C02 STZ
|
||||
m.inst_rel(.BRA, 0), // 65C02 BRA
|
||||
}
|
||||
r := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
byte_count, success := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
|
||||
d_insts: [dynamic]m.Instruction
|
||||
d_info: [dynamic]m.Instruction_Info
|
||||
@@ -245,7 +245,7 @@ run_pipeline_tests :: proc() {
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
// Decode in 65C02 mode -- $B2 is LDA(zp), $9C is STZ, $80 is BRA.
|
||||
m.decode(code[:r.byte_count], nil,
|
||||
m.decode(code[:byte_count], nil,
|
||||
&d_insts, &d_info, &d_labels, &errors, cpu = .CMOS_65C02)
|
||||
|
||||
names: map[u32]string
|
||||
@@ -291,16 +291,16 @@ run_pipeline_tests :: proc() {
|
||||
src := []m.Instruction{
|
||||
m.inst_block(.TII, 0x4000, 0x2000, 0x100),
|
||||
}
|
||||
r := m.encode(src, nil, code[:], &relocs, &errors)
|
||||
ok("huc tii: encode ok", r.success)
|
||||
ok("huc tii: 7 bytes", int(r.byte_count) == 7)
|
||||
byte_count, success := m.encode(src, nil, code[:], &relocs, &errors)
|
||||
ok("huc tii: encode ok", success)
|
||||
ok("huc tii: 7 bytes", int(byte_count) == 7)
|
||||
|
||||
d_insts: [dynamic]m.Instruction
|
||||
d_info: [dynamic]m.Instruction_Info
|
||||
d_labels: [dynamic]m.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
m.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors,
|
||||
m.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors,
|
||||
cpu = .HUC6280)
|
||||
ok("huc tii: decode TII", len(d_insts) >= 1 && d_insts[0].mnemonic == .TII)
|
||||
ok("huc tii: 3 operands", d_insts[0].operand_count == 3)
|
||||
@@ -321,7 +321,7 @@ encode_or_fail :: proc(insts: []m.Instruction) -> []u8 {
|
||||
@(static) errors: [dynamic]m.Error
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
r := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
if !r.success { return nil }
|
||||
return code[:r.byte_count]
|
||||
byte_count, success := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
if !success { return nil }
|
||||
return code[:byte_count]
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
state: Assumed_State = NATIVE_16,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data))
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
@@ -44,16 +44,15 @@ decode :: proc(
|
||||
eff := state
|
||||
if eff.e { eff.m = true; eff.x = true }
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
for byte_count < n_bytes {
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx, consumed := decode_one_inline(data, pc, n_bytes, eff, &inst, &info)
|
||||
entry_idx, consumed := decode_one_inline(data, byte_count, n_bytes, eff, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 1}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
consumed = 1
|
||||
} else {
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
@@ -71,11 +70,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += consumed
|
||||
byte_count += consumed
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -40,38 +40,36 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
|
||||
inst_offsets := make([]u32, n_inst, context.temp_allocator)
|
||||
|
||||
pc: u32 = 0
|
||||
|
||||
for i in 0..<n_inst {
|
||||
inst_offsets[i] = pc
|
||||
inst_offsets[i] = byte_count
|
||||
inst := &instructions[i]
|
||||
form, ok := find_form_inline(inst, u16(i), errors)
|
||||
if !ok { return Result{byte_count = pc, success = false} }
|
||||
form := find_form_inline(inst, u16(i), errors) or_return
|
||||
|
||||
if pc + u32(form.length) > u32(len(code)) {
|
||||
if byte_count + u32(form.length) > u32(len(code)) {
|
||||
append(errors, Error{inst_idx = i, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = pc, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
code[pc] = form.opcode
|
||||
if form.enc[0] != .NONE { pack_operand_inline(&inst.ops[0], form.enc[0], pc, u16(i), code, relocs) }
|
||||
if form.enc[1] != .NONE { pack_operand_inline(&inst.ops[1], form.enc[1], pc, u16(i), code, relocs) }
|
||||
code[byte_count] = form.opcode
|
||||
if form.enc[0] != .NONE { pack_operand_inline(&inst.ops[0], form.enc[0], byte_count, u16(i), code, relocs) }
|
||||
if form.enc[1] != .NONE { pack_operand_inline(&inst.ops[1], form.enc[1], byte_count, u16(i), code, relocs) }
|
||||
|
||||
inst.length = form.length
|
||||
pc += u32(form.length)
|
||||
byte_count += u32(form.length)
|
||||
}
|
||||
|
||||
isa.rewrite_label_defs_to_offsets(label_defs, inst_offsets)
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
n_relocs := u32(len(relocs))
|
||||
@@ -90,7 +88,8 @@ encode :: proc(
|
||||
resize(relocs, int(write_idx))
|
||||
}
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -30,7 +30,6 @@ import "../isa"
|
||||
// passes `e=true` in Assumed_State so the decoder picks the 8-bit immediate
|
||||
// forms.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -68,9 +68,9 @@ enc :: proc(insts: []m.Instruction) -> []u8 {
|
||||
@(static) errors: [dynamic]m.Error
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
r := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
if !r.success { return nil }
|
||||
return code[:r.byte_count]
|
||||
byte_count, success := m.encode(insts, nil, code[:], &relocs, &errors)
|
||||
if !success { return nil }
|
||||
return code[:byte_count]
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
@@ -140,10 +140,10 @@ main :: proc() {
|
||||
m.inst_none(.NOP), // 1 byte
|
||||
m.inst_none(.RTS), // 1 byte (target)
|
||||
}
|
||||
r := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("BRL encode ok", r.success)
|
||||
byte_count, success := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("BRL encode ok", success)
|
||||
// BRL at pc=0, target at byte 4. next_pc = 3. rel = 4-3 = 1.
|
||||
eq_bytes("BRL forward", code[:r.byte_count], {0x82, 0x01, 0x00, 0xEA, 0x60})
|
||||
eq_bytes("BRL forward", code[:byte_count], {0x82, 0x01, 0x00, 0xEA, 0x60})
|
||||
}
|
||||
|
||||
// ---- 7. PER (push effective PC-rel, 16-bit signed) -------------------
|
||||
@@ -165,9 +165,9 @@ main :: proc() {
|
||||
m.inst_none(.NOP),
|
||||
m.inst_none(.RTS),
|
||||
}
|
||||
r := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("PER encode ok", r.success)
|
||||
eq_bytes("PER forward", code[:r.byte_count], {0x62, 0x01, 0x00, 0xEA, 0x60})
|
||||
byte_count, success := m.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("PER encode ok", success)
|
||||
eq_bytes("PER forward", code[:byte_count], {0x62, 0x01, 0x00, 0xEA, 0x60})
|
||||
}
|
||||
|
||||
// ---- 8. Round-trip: encode -> decode in 16-bit native, print ---------
|
||||
@@ -186,18 +186,18 @@ main :: proc() {
|
||||
m.inst_m(.STA, m.mem_long(0x7E1000)), // 4 bytes
|
||||
m.inst_rel(.BRA, 0), // 2 bytes back to loop
|
||||
}
|
||||
r := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", r.success)
|
||||
byte_count, success := m.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", success)
|
||||
|
||||
d_insts: [dynamic]m.Instruction
|
||||
d_info: [dynamic]m.Instruction_Info
|
||||
d_labels: [dynamic]m.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := m.decode(code[:r.byte_count], nil,
|
||||
_, dsuccess := m.decode(code[:byte_count], nil,
|
||||
&d_insts, &d_info, &d_labels, &errors,
|
||||
state = m.NATIVE_16)
|
||||
ok("rt: decode ok", d.success)
|
||||
ok("rt: decode ok", dsuccess)
|
||||
ok("rt: 3 insts", len(d_insts) == 3)
|
||||
ok("rt: LDA", d_insts[0].mnemonic == .LDA)
|
||||
ok("rt: STA", d_insts[1].mnemonic == .STA)
|
||||
@@ -261,14 +261,14 @@ main :: proc() {
|
||||
m.inst_m(.JML, m.mem_abs_ind_long(0xFFFC)),
|
||||
m.inst_m(.LDA, m.mem_sr_ind_y(0x10)),
|
||||
}
|
||||
r := m.encode(src, nil, code[:], &relocs, &errors)
|
||||
byte_count, success := m.encode(src, nil, code[:], &relocs, &errors)
|
||||
|
||||
d_insts: [dynamic]m.Instruction
|
||||
d_info: [dynamic]m.Instruction_Info
|
||||
d_labels: [dynamic]m.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
m.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors,
|
||||
m.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors,
|
||||
state = m.NATIVE_16)
|
||||
|
||||
text := m.aprint(d_insts[:], d_info[:], d_labels[:],
|
||||
|
||||
@@ -34,36 +34,35 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
mode: Mode = .PPC32,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data)) & ~u32(3)
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
if pc + 4 > n_bytes { break }
|
||||
word := read_u32_be(data, pc)
|
||||
for byte_count < n_bytes {
|
||||
if byte_count + 4 > n_bytes { break }
|
||||
word := read_u32_be(data, byte_count)
|
||||
|
||||
// Detect prefixed instruction: primary opcode = 1.
|
||||
is_prefixed := (word >> 26) == 0x01
|
||||
ilen: u32 = 4
|
||||
suffix: u32 = 0
|
||||
if is_prefixed {
|
||||
if pc + 8 > n_bytes { break }
|
||||
suffix = read_u32_be(data, pc + 4)
|
||||
if byte_count + 8 > n_bytes { break }
|
||||
suffix = read_u32_be(data, byte_count + 4)
|
||||
ilen = 8
|
||||
}
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
info.offset = pc
|
||||
info.offset = byte_count
|
||||
|
||||
match_word := is_prefixed ? suffix : word
|
||||
prefix_word := is_prefixed ? word : 0
|
||||
if !find_and_decode(match_word, prefix_word, is_prefixed, mode, &inst, &info) {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = u8(ilen), mode = mode}
|
||||
} else {
|
||||
inst.length = u8(ilen)
|
||||
@@ -74,7 +73,7 @@ decode :: proc(
|
||||
if op.kind == .RELATIVE && op.relative >= 0 {
|
||||
// The unpacker stores PC-relative byte offsets; convert
|
||||
// to absolute target = pc + relative.
|
||||
target := u32(i32(pc) + i32(op.relative))
|
||||
target := u32(i32(byte_count) + i32(op.relative))
|
||||
append(&pending_branches, isa.Branch_Target{
|
||||
inst_idx = inst_idx,
|
||||
op_idx = slot,
|
||||
@@ -86,11 +85,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += ilen
|
||||
byte_count += ilen
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -34,28 +34,24 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * MAX_INST_SIZE {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
inst_pc := make([]u32, n_inst, context.temp_allocator)
|
||||
|
||||
// ---- PASS 1 ------------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst_pc[i] = pc
|
||||
inst_pc[i] = byte_count
|
||||
inst := &instructions[i]
|
||||
ok := encode_one_inline(inst, pc, code, u16(i), relocs, errors)
|
||||
if !ok {
|
||||
return Result{byte_count = pc, success = false}
|
||||
}
|
||||
pc += u32(inst.length)
|
||||
encode_one_inline(inst, byte_count, code, u16(i), relocs, errors) or_return
|
||||
byte_count += u32(inst.length)
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: label instruction-idx -> byte offset --------------------
|
||||
@@ -71,7 +67,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations ---------------------------------------
|
||||
@@ -87,7 +84,8 @@ encode :: proc(
|
||||
}
|
||||
if write_idx != n_relocs { resize(relocs, int(write_idx)) }
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -67,7 +67,6 @@ import "../isa"
|
||||
// XX4-form vsx 4-op (xxsel) -- + XT + XA + XB + XC + XO + CX + AX + BX + TX
|
||||
// MLS / MMIRR / 8RR / 8LS prefixed (ISA 3.1) -- 8-byte (4 prefix + 4 suffix)
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -20,7 +20,7 @@ Instruction_Flags :: bit_field u8 {
|
||||
}
|
||||
|
||||
Instruction :: struct #packed {
|
||||
ops: [4]Operand `fmt:"v,operand_count"`, // 4 * 18 = 68
|
||||
ops: [4]Operand `fmt:"v,operand_count"`, // 4 * 14 = 56
|
||||
mnemonic: Mnemonic, // 2
|
||||
operand_count: u8, // 0..4
|
||||
flags: Instruction_Flags, // 1
|
||||
@@ -28,7 +28,7 @@ Instruction :: struct #packed {
|
||||
length: u8, // 4 or 8 (prefixed)
|
||||
form_id: u16, // 0 = no hint; otherwise 1 + form index
|
||||
}
|
||||
#assert(size_of(Instruction) == 80)
|
||||
#assert(size_of(Instruction) == 64)
|
||||
|
||||
// =============================================================================
|
||||
// Builders
|
||||
|
||||
@@ -46,7 +46,7 @@ mem_x :: #force_inline proc "contextless" (base, index: Register) -> Memory {
|
||||
}
|
||||
|
||||
Operand :: struct #packed {
|
||||
using _: struct #raw_union {
|
||||
using _: struct #raw_union #packed {
|
||||
reg: Register,
|
||||
mem: Memory,
|
||||
immediate: i64,
|
||||
@@ -55,7 +55,7 @@ Operand :: struct #packed {
|
||||
kind: Operand_Kind,
|
||||
size: u8, // operand size in bytes (4 = word, 8 = dword)
|
||||
}
|
||||
#assert(size_of(Operand) == 18)
|
||||
#assert(size_of(Operand) == 14)
|
||||
|
||||
@(require_results)
|
||||
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand {
|
||||
|
||||
@@ -16,15 +16,15 @@ check :: proc(name: string, instructions: []p.Instruction, label_defs: []isa.Lab
|
||||
errors: [dynamic]p.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
r := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed, %d errors\n", name, len(errors))
|
||||
for e in errors { fmt.printf(" code=%v inst_idx=%d\n", e.code, e.inst_idx) }
|
||||
fail += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: wrong byte count (got %d, want %d)\n", name, r.byte_count, len(want_bytes))
|
||||
if int(byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: wrong byte count (got %d, want %d)\n", name, byte_count, len(want_bytes))
|
||||
fail += 1
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ run_decode_sweep :: proc() {
|
||||
errors: [dynamic]p.Error
|
||||
defer delete(decoded); defer delete(info); defer delete(labels); defer delete(errors)
|
||||
|
||||
r := p.decode(buf[:ilen], nil, &decoded, &info, &labels, &errors, .PPC64)
|
||||
if !r.success || len(decoded) == 0 || (len(decoded) > 0 && decoded[0].mnemonic == .INVALID) {
|
||||
byte_count, success := p.decode(buf[:ilen], nil, &decoded, &info, &labels, &errors, .PPC64)
|
||||
if !success || len(decoded) == 0 || (len(decoded) > 0 && decoded[0].mnemonic == .INVALID) {
|
||||
if missing_mn_total < 20 {
|
||||
fmt.printf(" [UNDECODABLE] %v word=%08x prefixed=%v\n", mn, word, f.flags.prefixed)
|
||||
}
|
||||
|
||||
@@ -182,8 +182,8 @@ test_form :: proc(mn: p.Mnemonic, fi: int, f: ^p.Encoding, fails: ^[dynamic]stri
|
||||
|
||||
instructions := []p.Instruction{inst}
|
||||
label_defs: []isa.Label_Definition
|
||||
r := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
stats.encode_fail += 1
|
||||
if len(fails) < 100 { append(fails, fmt.aprintf("ENCODE_FAIL %v[%d] errors=%d", mn, fi, len(errors))) }
|
||||
return
|
||||
@@ -196,8 +196,8 @@ test_form :: proc(mn: p.Mnemonic, fi: int, f: ^p.Encoding, fails: ^[dynamic]stri
|
||||
dec_errors: [dynamic]p.Error
|
||||
defer delete(decoded); defer delete(dec_info); defer delete(dec_labels); defer delete(dec_errors)
|
||||
|
||||
dr := p.decode(code[:r.byte_count], nil, &decoded, &dec_info, &dec_labels, &dec_errors, f.mode)
|
||||
if !dr.success || len(decoded) == 0 || decoded[0].mnemonic == .INVALID {
|
||||
dbyte_count, dsuccess := p.decode(code[:byte_count], nil, &decoded, &dec_info, &dec_labels, &dec_errors, f.mode)
|
||||
if !dsuccess || len(decoded) == 0 || decoded[0].mnemonic == .INVALID {
|
||||
stats.decode_fail += 1
|
||||
if len(fails) < 100 {
|
||||
append(fails, fmt.aprintf("DECODE_FAIL %v[%d] bytes=%02x%02x%02x%02x",
|
||||
@@ -213,8 +213,8 @@ test_form :: proc(mn: p.Mnemonic, fi: int, f: ^p.Encoding, fails: ^[dynamic]stri
|
||||
re_errors: [dynamic]p.Error
|
||||
defer delete(re_relocs); defer delete(re_errors)
|
||||
|
||||
rr := p.encode(decoded[:], dec_labels[:], code2, &re_relocs, &re_errors)
|
||||
if !rr.success {
|
||||
rrbyte_count, rrsuccess := p.encode(decoded[:], dec_labels[:], code2, &re_relocs, &re_errors)
|
||||
if !rrsuccess {
|
||||
stats.reencode_fail += 1
|
||||
if len(fails) < 100 {
|
||||
append(fails, fmt.aprintf("REENCODE_FAIL %v[%d] decoded_mn=%v",
|
||||
@@ -223,14 +223,14 @@ test_form :: proc(mn: p.Mnemonic, fi: int, f: ^p.Encoding, fails: ^[dynamic]stri
|
||||
return
|
||||
}
|
||||
|
||||
if rr.byte_count != r.byte_count {
|
||||
if rrbyte_count != byte_count {
|
||||
stats.byte_mismatch += 1
|
||||
if len(fails) < 100 {
|
||||
append(fails, fmt.aprintf("LEN_MISMATCH %v[%d] orig=%d re=%d", mn, fi, r.byte_count, rr.byte_count))
|
||||
append(fails, fmt.aprintf("LEN_MISMATCH %v[%d] orig=%d re=%d", mn, fi, byte_count, rrbyte_count))
|
||||
}
|
||||
return
|
||||
}
|
||||
for i in 0..<r.byte_count {
|
||||
for i in 0..<byte_count {
|
||||
if code[i] != code2[i] {
|
||||
stats.byte_mismatch += 1
|
||||
if len(fails) < 100 {
|
||||
|
||||
@@ -18,17 +18,17 @@ check_roundtrip :: proc(name: string, inst: p.Instruction, want_bytes: []u8) {
|
||||
defer delete(errors)
|
||||
|
||||
instructions := []p.Instruction{inst}
|
||||
r := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := p.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed (%d errors)\n", name, len(errors))
|
||||
for e in errors { fmt.printf(" code=%v inst_idx=%d\n", e.code, e.inst_idx) }
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
|
||||
if int(r.byte_count) != len(want_bytes) {
|
||||
if int(byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: wrong byte count (got %d, want %d)\n",
|
||||
name, r.byte_count, len(want_bytes))
|
||||
name, byte_count, len(want_bytes))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
@@ -55,8 +55,8 @@ check_roundtrip :: proc(name: string, inst: p.Instruction, want_bytes: []u8) {
|
||||
defer delete(dec_label_defs)
|
||||
defer delete(dec_errors)
|
||||
|
||||
dr := p.decode(code[:r.byte_count], nil, &decoded, &decoded_info, &dec_label_defs, &dec_errors)
|
||||
if !dr.success {
|
||||
dbyte_count, dsuccess := p.decode(code[:byte_count], nil, &decoded, &decoded_info, &dec_label_defs, &dec_errors)
|
||||
if !dsuccess {
|
||||
fmt.printf(" [FAIL] %s: decode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
|
||||
@@ -25,37 +25,36 @@ decode :: proc(
|
||||
inst_info: ^[dynamic]Instruction_Info,
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data)) & ~u32(1)
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
if pc + 2 > n_bytes { break }
|
||||
hw := u32(read_u16_be(data, pc))
|
||||
for byte_count < n_bytes {
|
||||
if byte_count + 2 > n_bytes { break }
|
||||
hw := u32(read_u16_be(data, byte_count))
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
info.offset = pc
|
||||
info.offset = byte_count
|
||||
|
||||
matched := try_decode(hw, true, &inst, &info)
|
||||
ilen: u32 = 2
|
||||
|
||||
if !matched {
|
||||
if pc + 4 > n_bytes {
|
||||
append(errors, Error{inst_idx = pc, code = .BUFFER_TOO_SHORT})
|
||||
if byte_count + 4 > n_bytes {
|
||||
append(errors, Error{inst_idx = byte_count, code = .BUFFER_TOO_SHORT})
|
||||
break
|
||||
}
|
||||
word := (hw << 16) | u32(read_u16_be(data, pc + 2))
|
||||
word := (hw << 16) | u32(read_u16_be(data, byte_count + 2))
|
||||
matched = try_decode(word, false, &inst, &info)
|
||||
ilen = 4
|
||||
}
|
||||
|
||||
if !matched {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 2, mode = .PPC32_VLE}
|
||||
ilen = 2
|
||||
} else {
|
||||
@@ -70,7 +69,7 @@ decode :: proc(
|
||||
append(&pending_branches, isa.Branch_Target{
|
||||
inst_idx = inst_idx,
|
||||
op_idx = slot,
|
||||
target = u32(i32(pc) + i32(op.relative)),
|
||||
target = u32(i32(byte_count) + i32(op.relative)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -78,11 +77,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += ilen
|
||||
byte_count += ilen
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
|
||||
@@ -25,24 +25,23 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * MAX_INST_SIZE {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
inst_pc := make([]u32, n_inst, context.temp_allocator)
|
||||
|
||||
for i in 0..<n_inst {
|
||||
inst_pc[i] = pc
|
||||
inst_pc[i] = byte_count
|
||||
inst := &instructions[i]
|
||||
ok := encode_one_inline(inst, pc, code, u16(i), relocs, errors)
|
||||
if !ok { return Result{byte_count = pc, success = false} }
|
||||
pc += u32(inst.length)
|
||||
encode_one_inline(inst, byte_count, code, u16(i), relocs, errors) or_return
|
||||
byte_count += u32(inst.length)
|
||||
}
|
||||
|
||||
for &ld in label_defs {
|
||||
@@ -57,7 +56,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
n_relocs := u32(len(relocs))
|
||||
@@ -72,7 +72,8 @@ encode :: proc(
|
||||
}
|
||||
if write_idx != n_relocs { resize(relocs, int(write_idx)) }
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
|
||||
@@ -17,7 +17,6 @@ import "../isa"
|
||||
// VLE shares the same GPR/SPR register model as standard PowerPC; types are
|
||||
// duplicated here to keep ppc_vle as a standalone sibling package.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -26,7 +26,7 @@ Instruction :: struct #packed {
|
||||
length: u8, // 2 or 4
|
||||
form_id: u16,
|
||||
}
|
||||
#assert(size_of(Instruction) == 80)
|
||||
#assert(size_of(Instruction) == 64)
|
||||
|
||||
@(require_results)
|
||||
inst_none :: #force_inline proc "contextless" (m: Mnemonic) -> Instruction {
|
||||
|
||||
@@ -31,7 +31,7 @@ mem_x :: #force_inline proc "contextless" (base, index: Register) -> Memory {
|
||||
}
|
||||
|
||||
Operand :: struct #packed {
|
||||
using _: struct #raw_union {
|
||||
using _: struct #raw_union #packed {
|
||||
reg: Register,
|
||||
mem: Memory,
|
||||
immediate: i64,
|
||||
@@ -40,7 +40,7 @@ Operand :: struct #packed {
|
||||
kind: Operand_Kind,
|
||||
size: u8,
|
||||
}
|
||||
#assert(size_of(Operand) == 18)
|
||||
#assert(size_of(Operand) == 14)
|
||||
|
||||
@(require_results)
|
||||
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand {
|
||||
|
||||
@@ -9,99 +9,99 @@ import "../../isa"
|
||||
|
||||
@(private="file")
|
||||
check :: proc(name: string, instructions: []v.Instruction, label_defs: []isa.Label_Definition, want: []u8) {
|
||||
code := make([]u8, 64, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
code := make([]u8, 64, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
fmt.printf(" [FAIL] %s: encode failed\n", name)
|
||||
for e in errors { fmt.printf(" code=%v inst_idx=%d\n", e.code, e.inst_idx) }
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: byte_count %d (want %d)\n", name, r.byte_count, len(want))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
for i in 0..<len(want) {
|
||||
if code[i] != want[i] {
|
||||
fmt.printf(" [FAIL] %s: byte %d (got %02x, want %02x)\n", name, i, code[i], want[i])
|
||||
fmt.printf(" got ")
|
||||
for j in 0..<len(want) { fmt.printf("%02x ", code[j]) }
|
||||
fmt.printf("\n want ")
|
||||
for j in 0..<len(want) { fmt.printf("%02x ", want[j]) }
|
||||
fmt.println()
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.printf(" [ok] %-35s bytes=", name)
|
||||
for i in 0..<len(want) { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed\n", name)
|
||||
for e in errors { fmt.printf(" code=%v inst_idx=%d\n", e.code, e.inst_idx) }
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: byte_count %d (want %d)\n", name, byte_count, len(want))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
for i in 0..<len(want) {
|
||||
if code[i] != want[i] {
|
||||
fmt.printf(" [FAIL] %s: byte %d (got %02x, want %02x)\n", name, i, code[i], want[i])
|
||||
fmt.printf(" got ")
|
||||
for j in 0..<len(want) { fmt.printf("%02x ", code[j]) }
|
||||
fmt.printf("\n want ")
|
||||
for j in 0..<len(want) { fmt.printf("%02x ", want[j]) }
|
||||
fmt.println()
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.printf(" [ok] %-35s bytes=", name)
|
||||
for i in 0..<len(want) { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
|
||||
run_branch_test :: proc() {
|
||||
fmt.println("==== ppc_vle branches + labels ====")
|
||||
fmt.println("==== ppc_vle branches + labels ====")
|
||||
|
||||
// se_b L0; se_blr; L0: se_blr
|
||||
// -> first inst: se_b with displacement 4 (encoded as 4/2=2 in 8-bit field)
|
||||
// se_b form bits 0xE800, mask 0xFF00, B8 at bits 0..7, signed << 1
|
||||
// Target = pc + 4 = 4 bytes ahead, so B8 value = 2 (= 4/2)
|
||||
// bits: 0xE8 | 0x04 wait no. Looking at binutils BD8(58, 0, 0) = (58 << 10) = 0xE800
|
||||
// Actually se_b is BD8(58,0,0). Let me just verify encoder produces something reasonable.
|
||||
{
|
||||
label_defs := [?]isa.Label_Definition{isa.Label_Definition(2)} // points to inst 2
|
||||
instructions := [?]v.Instruction{
|
||||
v.inst_branch(.SE_B, 0),
|
||||
v.inst_none(.SE_BLR),
|
||||
v.inst_none(.SE_BLR),
|
||||
}
|
||||
// Just check encode succeeds and roundtrips
|
||||
code := make([]u8, 16, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
r := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !r.success {
|
||||
fmt.printf(" [FAIL] se_b+label: encode failed\n")
|
||||
for e in errors { fmt.printf(" code=%v\n", e.code) }
|
||||
fail_count += 1
|
||||
} else {
|
||||
fmt.printf(" [ok] se_b+label: %d bytes, bytes=", r.byte_count)
|
||||
for i in 0..<r.byte_count { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
}
|
||||
// se_b L0; se_blr; L0: se_blr
|
||||
// -> first inst: se_b with displacement 4 (encoded as 4/2=2 in 8-bit field)
|
||||
// se_b form bits 0xE800, mask 0xFF00, B8 at bits 0..7, signed << 1
|
||||
// Target = pc + 4 = 4 bytes ahead, so B8 value = 2 (= 4/2)
|
||||
// bits: 0xE8 | 0x04 wait no. Looking at binutils BD8(58, 0, 0) = (58 << 10) = 0xE800
|
||||
// Actually se_b is BD8(58,0,0). Let me just verify encoder produces something reasonable.
|
||||
{
|
||||
label_defs := [?]isa.Label_Definition{isa.Label_Definition(2)} // points to inst 2
|
||||
instructions := [?]v.Instruction{
|
||||
v.inst_branch(.SE_B, 0),
|
||||
v.inst_none(.SE_BLR),
|
||||
v.inst_none(.SE_BLR),
|
||||
}
|
||||
// Just check encode succeeds and roundtrips
|
||||
code := make([]u8, 16, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
byte_count, success := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] se_b+label: encode failed\n")
|
||||
for e in errors { fmt.printf(" code=%v\n", e.code) }
|
||||
fail_count += 1
|
||||
} else {
|
||||
fmt.printf(" [ok] se_b+label: %d bytes, bytes=", byte_count)
|
||||
for i in 0..<byte_count { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
}
|
||||
|
||||
// e_b L0; nop instruction (could be e_or); L0: e_blr equivalent
|
||||
{
|
||||
label_defs := [?]isa.Label_Definition{isa.Label_Definition(2)}
|
||||
instructions := [?]v.Instruction{
|
||||
v.inst_branch(.E_B, 0),
|
||||
v.inst_none(.SE_BLR),
|
||||
v.inst_none(.SE_BLR),
|
||||
}
|
||||
code := make([]u8, 16, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
r := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !r.success {
|
||||
fmt.printf(" [FAIL] e_b+label: encode failed\n")
|
||||
fail_count += 1
|
||||
} else {
|
||||
fmt.printf(" [ok] e_b+label: %d bytes, bytes=", r.byte_count)
|
||||
for i in 0..<r.byte_count { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
}
|
||||
// e_b L0; nop instruction (could be e_or); L0: e_blr equivalent
|
||||
{
|
||||
label_defs := [?]isa.Label_Definition{isa.Label_Definition(2)}
|
||||
instructions := [?]v.Instruction{
|
||||
v.inst_branch(.E_B, 0),
|
||||
v.inst_none(.SE_BLR),
|
||||
v.inst_none(.SE_BLR),
|
||||
}
|
||||
code := make([]u8, 16, context.temp_allocator)
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
byte_count, success := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] e_b+label: encode failed\n")
|
||||
fail_count += 1
|
||||
} else {
|
||||
fmt.printf(" [ok] e_b+label: %d bytes, bytes=", byte_count)
|
||||
for i in 0..<byte_count { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
}
|
||||
|
||||
fmt.printf("\n==> branch_test: %d passed, %d failed\n", ok_count, fail_count)
|
||||
if fail_count > 0 { os.exit(1) }
|
||||
fmt.printf("\n==> branch_test: %d passed, %d failed\n", ok_count, fail_count)
|
||||
if fail_count > 0 { os.exit(1) }
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ check :: proc(name: string, instructions: []v.Instruction, label_defs: []isa.Lab
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: bc=%d want=%d\n", name, r.byte_count, len(want))
|
||||
if int(byte_count) != len(want) {
|
||||
fmt.printf(" [FAIL] %s: bc=%d want=%d\n", name, byte_count, len(want))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
@@ -61,14 +61,14 @@ run_cond_branch :: proc() {
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
r := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := v.encode(instructions[:], label_defs[:], code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] e_bc encode failed (%d errors)\n", len(errors))
|
||||
for e in errors { fmt.printf(" code=%v\n", e.code) }
|
||||
fail_count += 1
|
||||
} else {
|
||||
fmt.printf(" [ok] e_bc 12, cr0[lt], L ")
|
||||
for i in 0..<r.byte_count { fmt.printf("%02x", code[i]) }
|
||||
for i in 0..<byte_count { fmt.printf("%02x", code[i]) }
|
||||
fmt.println()
|
||||
ok_count += 1
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ check :: proc(name: string, instructions: []v.Instruction, label_defs: []isa.Lab
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte_count=%d want=%d\n", name, r.byte_count, len(want_bytes))
|
||||
if int(byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte_count=%d want=%d\n", name, byte_count, len(want_bytes))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
@@ -45,8 +45,8 @@ check :: proc(name: string, instructions: []v.Instruction, label_defs: []isa.Lab
|
||||
dec_labs: [dynamic]v.Label_Definition
|
||||
dec_errs: [dynamic]v.Error
|
||||
defer delete(decoded); defer delete(info); defer delete(dec_labs); defer delete(dec_errs)
|
||||
dr := v.decode(code[:r.byte_count], relocs[:], &decoded, &info, &dec_labs, &dec_errs)
|
||||
if !dr.success {
|
||||
dbyte_count, dsuccess := v.decode(code[:byte_count], relocs[:], &decoded, &info, &dec_labs, &dec_errs)
|
||||
if !dsuccess {
|
||||
fmt.printf(" [FAIL] %s: decode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
@@ -65,7 +65,7 @@ check :: proc(name: string, instructions: []v.Instruction, label_defs: []isa.Lab
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.printf(" [ok] %-35s %d bytes, %d insts\n", name, r.byte_count, len(decoded))
|
||||
fmt.printf(" [ok] %-35s %d bytes, %d insts\n", name, byte_count, len(decoded))
|
||||
ok_count += 1
|
||||
}
|
||||
|
||||
|
||||
@@ -69,15 +69,15 @@ run_extension :: proc() {
|
||||
relocs: [dynamic]v.Relocation; defer delete(relocs)
|
||||
errors: [dynamic]v.Error; defer delete(errors)
|
||||
labels: []isa.Label_Definition
|
||||
r := v.encode(instructions[:], labels, code, &relocs, &errors)
|
||||
check("mixed 16/32-bit sequence encodes", r.success)
|
||||
byte_count, success := v.encode(instructions[:], labels, code, &relocs, &errors)
|
||||
check("mixed 16/32-bit sequence encodes", success)
|
||||
|
||||
decoded: [dynamic]v.Instruction; defer delete(decoded)
|
||||
info: [dynamic]v.Instruction_Info; defer delete(info)
|
||||
dlabs: [dynamic]v.Label_Definition; defer delete(dlabs)
|
||||
derrs: [dynamic]v.Error; defer delete(derrs)
|
||||
dr := v.decode(code[:r.byte_count], nil, &decoded, &info, &dlabs, &derrs)
|
||||
check("mixed sequence decodes", dr.success)
|
||||
dbyte_count, dsuccess := v.decode(code[:byte_count], nil, &decoded, &info, &dlabs, &derrs)
|
||||
check("mixed sequence decodes", dsuccess)
|
||||
check("decoded 3 instructions", len(decoded) == 3)
|
||||
check("first inst is SE_MR (16-bit)", decoded[0].length == 2)
|
||||
check("second inst is E_ADDI (32-bit)", decoded[1].length == 4)
|
||||
|
||||
@@ -10,85 +10,85 @@ import "../../isa"
|
||||
stats: struct { ok, mn_alias, byte_mismatch, encode_fail, decode_fail: int }
|
||||
|
||||
run_full_sweep :: proc() {
|
||||
fmt.println("==== ppc_vle full sweep ====")
|
||||
fmt.println("==== ppc_vle full sweep ====")
|
||||
|
||||
for mn in v.Mnemonic {
|
||||
_run := v.ENCODE_RUNS[u16(mn)]
|
||||
forms := v.ENCODE_FORMS[_run.start:][:_run.count]
|
||||
for &f, fi in forms {
|
||||
test_one(mn, fi, &f)
|
||||
}
|
||||
}
|
||||
for mn in v.Mnemonic {
|
||||
_run := v.ENCODE_RUNS[u16(mn)]
|
||||
forms := v.ENCODE_FORMS[_run.start:][:_run.count]
|
||||
for &f, fi in forms {
|
||||
test_one(mn, fi, &f)
|
||||
}
|
||||
}
|
||||
|
||||
total := stats.ok + stats.mn_alias + stats.byte_mismatch + stats.encode_fail + stats.decode_fail
|
||||
fmt.printf("\n[TOTAL] %d entries\n", total)
|
||||
fmt.printf(" OK: %d (%.1f%%)\n", stats.ok, 100.0 * f32(stats.ok) / f32(total))
|
||||
fmt.printf(" MN_ALIAS: %d (%.1f%%)\n", stats.mn_alias, 100.0 * f32(stats.mn_alias) / f32(total))
|
||||
fmt.printf(" BYTE_MISMATCH: %d (%.1f%%)\n", stats.byte_mismatch, 100.0 * f32(stats.byte_mismatch) / f32(total))
|
||||
fmt.printf(" ENCODE_FAIL: %d (%.1f%%)\n", stats.encode_fail, 100.0 * f32(stats.encode_fail) / f32(total))
|
||||
fmt.printf(" DECODE_FAIL: %d (%.1f%%)\n", stats.decode_fail, 100.0 * f32(stats.decode_fail) / f32(total))
|
||||
total := stats.ok + stats.mn_alias + stats.byte_mismatch + stats.encode_fail + stats.decode_fail
|
||||
fmt.printf("\n[TOTAL] %d entries\n", total)
|
||||
fmt.printf(" OK: %d (%.1f%%)\n", stats.ok, 100.0 * f32(stats.ok) / f32(total))
|
||||
fmt.printf(" MN_ALIAS: %d (%.1f%%)\n", stats.mn_alias, 100.0 * f32(stats.mn_alias) / f32(total))
|
||||
fmt.printf(" BYTE_MISMATCH: %d (%.1f%%)\n", stats.byte_mismatch, 100.0 * f32(stats.byte_mismatch) / f32(total))
|
||||
fmt.printf(" ENCODE_FAIL: %d (%.1f%%)\n", stats.encode_fail, 100.0 * f32(stats.encode_fail) / f32(total))
|
||||
fmt.printf(" DECODE_FAIL: %d (%.1f%%)\n", stats.decode_fail, 100.0 * f32(stats.decode_fail) / f32(total))
|
||||
}
|
||||
|
||||
test_one :: proc(mn: v.Mnemonic, fi: int, f: ^v.Encoding) {
|
||||
inst := v.Instruction{
|
||||
mnemonic = mn,
|
||||
mode = .PPC32_VLE,
|
||||
form_id = u16(fi + 1),
|
||||
length = f.flags.short ? 2 : 4,
|
||||
}
|
||||
code := make([]u8, 8, context.temp_allocator)
|
||||
label_defs: []isa.Label_Definition
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
inst := v.Instruction{
|
||||
mnemonic = mn,
|
||||
mode = .PPC32_VLE,
|
||||
form_id = u16(fi + 1),
|
||||
length = f.flags.short ? 2 : 4,
|
||||
}
|
||||
code := make([]u8, 8, context.temp_allocator)
|
||||
label_defs: []isa.Label_Definition
|
||||
relocs: [dynamic]v.Relocation
|
||||
errors: [dynamic]v.Error
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
instructions := []v.Instruction{inst}
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
stats.encode_fail += 1
|
||||
return
|
||||
}
|
||||
instructions := []v.Instruction{inst}
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
stats.encode_fail += 1
|
||||
return
|
||||
}
|
||||
|
||||
decoded: [dynamic]v.Instruction
|
||||
info: [dynamic]v.Instruction_Info
|
||||
dec_labels: [dynamic]v.Label_Definition
|
||||
dec_errors: [dynamic]v.Error
|
||||
defer delete(decoded); defer delete(info); defer delete(dec_labels); defer delete(dec_errors)
|
||||
decoded: [dynamic]v.Instruction
|
||||
info: [dynamic]v.Instruction_Info
|
||||
dec_labels: [dynamic]v.Label_Definition
|
||||
dec_errors: [dynamic]v.Error
|
||||
defer delete(decoded); defer delete(info); defer delete(dec_labels); defer delete(dec_errors)
|
||||
|
||||
dr := v.decode(code[:r.byte_count], nil, &decoded, &info, &dec_labels, &dec_errors)
|
||||
if !dr.success || len(decoded) == 0 || decoded[0].mnemonic == .INVALID {
|
||||
stats.decode_fail += 1
|
||||
return
|
||||
}
|
||||
dbyte_count, dsuccess := v.decode(code[:byte_count], nil, &decoded, &info, &dec_labels, &dec_errors)
|
||||
if !dsuccess || len(decoded) == 0 || decoded[0].mnemonic == .INVALID {
|
||||
stats.decode_fail += 1
|
||||
return
|
||||
}
|
||||
|
||||
// Re-encode and check bytes
|
||||
code2 := make([]u8, 8, context.temp_allocator)
|
||||
re_relocs: [dynamic]v.Relocation
|
||||
re_errors: [dynamic]v.Error
|
||||
defer delete(re_relocs); defer delete(re_errors)
|
||||
// Re-encode and check bytes
|
||||
code2 := make([]u8, 8, context.temp_allocator)
|
||||
re_relocs: [dynamic]v.Relocation
|
||||
re_errors: [dynamic]v.Error
|
||||
defer delete(re_relocs); defer delete(re_errors)
|
||||
|
||||
rr := v.encode(decoded[:], dec_labels[:], code2, &re_relocs, &re_errors)
|
||||
if !rr.success || rr.byte_count != r.byte_count {
|
||||
stats.byte_mismatch += 1
|
||||
return
|
||||
}
|
||||
for i in 0..<r.byte_count {
|
||||
if code[i] != code2[i] {
|
||||
stats.byte_mismatch += 1
|
||||
if stats.byte_mismatch <= 10 {
|
||||
fmt.printf(" [BYTE_MISMATCH] %v: orig=", mn)
|
||||
for j in 0..<r.byte_count { fmt.printf("%02x", code[j]) }
|
||||
fmt.printf(" re=")
|
||||
for j in 0..<r.byte_count { fmt.printf("%02x", code2[j]) }
|
||||
fmt.printf(" decoded=%v\n", decoded[0].mnemonic)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
rrbyte_count, rrsuccess := v.encode(decoded[:], dec_labels[:], code2, &re_relocs, &re_errors)
|
||||
if !rrsuccess || rrbyte_count != byte_count {
|
||||
stats.byte_mismatch += 1
|
||||
return
|
||||
}
|
||||
for i in 0..<byte_count {
|
||||
if code[i] != code2[i] {
|
||||
stats.byte_mismatch += 1
|
||||
if stats.byte_mismatch <= 10 {
|
||||
fmt.printf(" [BYTE_MISMATCH] %v: orig=", mn)
|
||||
for j in 0..<byte_count { fmt.printf("%02x", code[j]) }
|
||||
fmt.printf(" re=")
|
||||
for j in 0..<byte_count { fmt.printf("%02x", code2[j]) }
|
||||
fmt.printf(" decoded=%v\n", decoded[0].mnemonic)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if decoded[0].mnemonic == mn {
|
||||
stats.ok += 1
|
||||
} else {
|
||||
stats.mn_alias += 1
|
||||
}
|
||||
if decoded[0].mnemonic == mn {
|
||||
stats.ok += 1
|
||||
} else {
|
||||
stats.mn_alias += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ check :: proc(name: string, inst: v.Instruction, want_bytes: []u8) {
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
instructions := []v.Instruction{inst}
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte_count=%d (want %d)\n", name, r.byte_count, len(want_bytes))
|
||||
if int(byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte_count=%d (want %d)\n", name, byte_count, len(want_bytes))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,15 +15,15 @@ check_encode :: proc(name: string, inst: v.Instruction, want_bytes: []u8) {
|
||||
defer delete(relocs); defer delete(errors)
|
||||
|
||||
instructions := []v.Instruction{inst}
|
||||
r := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !r.success {
|
||||
byte_count, success := v.encode(instructions, label_defs, code, &relocs, &errors)
|
||||
if !success {
|
||||
fmt.printf(" [FAIL] %s: encode failed (%d errors)\n", name, len(errors))
|
||||
for e in errors { fmt.printf(" code=%v\n", e.code) }
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
if int(r.byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte count %d != %d\n", name, r.byte_count, len(want_bytes))
|
||||
if int(byte_count) != len(want_bytes) {
|
||||
fmt.printf(" [FAIL] %s: byte count %d != %d\n", name, byte_count, len(want_bytes))
|
||||
fail_count += 1
|
||||
return
|
||||
}
|
||||
@@ -46,8 +46,8 @@ check_encode :: proc(name: string, inst: v.Instruction, want_bytes: []u8) {
|
||||
dec_errors: [dynamic]v.Error
|
||||
defer delete(decoded); defer delete(info); defer delete(dec_labels); defer delete(dec_errors)
|
||||
|
||||
dr := v.decode(code[:r.byte_count], nil, &decoded, &info, &dec_labels, &dec_errors)
|
||||
if !dr.success {
|
||||
dbyte_count, dsuccess := v.decode(code[:byte_count], nil, &decoded, &info, &dec_labels, &dec_errors)
|
||||
if !dsuccess {
|
||||
fmt.printf(" [FAIL] %s: decode failed\n", name)
|
||||
fail_count += 1
|
||||
return
|
||||
|
||||
@@ -43,35 +43,34 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
xlen: XLEN = .RV64,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data)) & ~u32(1) // align to halfword (RVC is 2-byte)
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
for byte_count < n_bytes {
|
||||
// Read the first halfword; bits[1:0] != 11 means compressed (2 bytes).
|
||||
hword_lo := read_u16_le(data, pc)
|
||||
hword_lo := read_u16_le(data, byte_count)
|
||||
ilen: u32 = 4
|
||||
word: u32
|
||||
if (hword_lo & 0x3) != 0x3 {
|
||||
ilen = 2
|
||||
word = u32(hword_lo)
|
||||
} else {
|
||||
if pc + 4 > n_bytes { break }
|
||||
word = read_u32_le(data, pc)
|
||||
if byte_count + 4 > n_bytes { break }
|
||||
word = read_u32_le(data, byte_count)
|
||||
}
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx := decode_one_inline(word, pc, xlen, ilen == 2, &inst, &info)
|
||||
entry_idx := decode_one_inline(word, byte_count, xlen, ilen == 2, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = u8(ilen)}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
} else {
|
||||
inst.length = u8(ilen)
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
@@ -89,11 +88,12 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += ilen
|
||||
byte_count += ilen
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -41,16 +41,15 @@ encode :: proc(
|
||||
errors: ^[dynamic]Error,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * 4 {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
// Per-instruction byte offsets so label_defs (instruction-indexed)
|
||||
// can be rewritten to byte-offset after pass 1 in the presence of
|
||||
@@ -59,16 +58,15 @@ encode :: proc(
|
||||
|
||||
// ---- PASS 1 -----------------------------------------------------------
|
||||
for i in 0..<n_inst {
|
||||
inst_pc[i] = pc
|
||||
inst_pc[i] = byte_count
|
||||
inst := &instructions[i]
|
||||
word, ilen, ok := encode_one_inline(inst, pc, u16(i), relocs, errors)
|
||||
if !ok { return Result{byte_count = pc, success = false} }
|
||||
word, ilen := encode_one_inline(inst, byte_count, u16(i), relocs, errors) or_return
|
||||
if ilen == 2 {
|
||||
write_u16_le(code, pc, u16(word))
|
||||
write_u16_le(code, byte_count, u16(word))
|
||||
} else {
|
||||
write_u32_le(code, pc, word)
|
||||
write_u32_le(code, byte_count, word)
|
||||
}
|
||||
pc += u32(ilen)
|
||||
byte_count += u32(ilen)
|
||||
}
|
||||
|
||||
// ---- PASS 1.5: rewrite label_defs (instruction-index -> byte-offset) --
|
||||
@@ -84,7 +82,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// ---- PASS 2: resolve relocations --------------------------------------
|
||||
@@ -100,7 +99,8 @@ encode :: proc(
|
||||
}
|
||||
if write_idx != n_relocs { resize(relocs, int(write_idx)) }
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -31,7 +31,6 @@ import "../isa"
|
||||
// pattern, `mask` flags which positions are static. Operand-driven bits
|
||||
// land in the zero positions of `bits`.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -91,8 +91,8 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_u (.LUI, rv.T0, 0x12345),
|
||||
rv.inst_u (.AUIPC,rv.RA, 0x10),
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("R/I/U: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("R/I/U: encode ok", success)
|
||||
eq_word("R: ADD t0,a0,a1", load_le(code[:], 0), 0x00B502B3)
|
||||
eq_word("I: ADDI sp,sp,-16", load_le(code[:], 4), 0xFF010113)
|
||||
eq_word("U: LUI t0,0x12345", load_le(code[:], 8), 0x123452B7)
|
||||
@@ -114,8 +114,8 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_load (.LW, rv.T0, rv.mem(rv.SP, 100)),
|
||||
rv.inst_store(.SW, rv.A0, rv.mem(rv.SP, -8)),
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LW/SW: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("LW/SW: encode ok", success)
|
||||
eq_word("LW t0,100(sp)", load_le(code[:], 0), 0x06412283)
|
||||
eq_word("SW a0,-8(sp)", load_le(code[:], 4), 0xFEA12C23)
|
||||
}
|
||||
@@ -144,8 +144,8 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_branch(.BNE, rv.T0, rv.ZERO, 0),
|
||||
rv.inst_r_r_i(.ADDI, rv.ZERO, rv.ZERO, 0),
|
||||
}
|
||||
r := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("br: encode ok", success)
|
||||
ok("br: no leftover relocs", len(relocs) == 0)
|
||||
eq_word("BNE rel=-8", load_le(code[:], 8), 0xFE029CE3)
|
||||
}
|
||||
@@ -170,9 +170,9 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_r_r_i(.ADDI, rv.SP, rv.SP, 0),
|
||||
rv.inst_jalr(rv.GPR.ZERO, rv.GPR.RA, 0),
|
||||
}
|
||||
r := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("JAL: encode ok", r.success)
|
||||
eq_word("JAL ra,+8", load_le(code[:], 0), 0x008000EF)
|
||||
byte_count, success := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("JAL: encode ok", success)
|
||||
eq_word("JAL ra,+8", load_le(code[:], 0), 0x008000EF)
|
||||
}
|
||||
|
||||
// ---- 5. Round-trip: encode -> decode -> print -----------------------
|
||||
@@ -189,16 +189,16 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_load (.LW, rv.A0, rv.mem(rv.SP, 0)),
|
||||
rv.inst_branch(.BNE, rv.T0, rv.ZERO, 0),
|
||||
}
|
||||
r := rv.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", r.success)
|
||||
byte_count, success := rv.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
ok("rt: encode ok", success)
|
||||
|
||||
d_insts: [dynamic]rv.Instruction
|
||||
d_info: [dynamic]rv.Instruction_Info
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("rt: decode ok", d.success)
|
||||
dbyte_count, dsuccess := rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("rt: decode ok", dsuccess)
|
||||
ok("rt: 3 insts", len(d_insts) == 3)
|
||||
ok("rt: ADDI", d_insts[0].mnemonic == .ADDI)
|
||||
ok("rt: LW", d_insts[1].mnemonic == .LW)
|
||||
@@ -223,15 +223,15 @@ run_pipeline_tests :: proc() {
|
||||
rv.inst_r_r_r(.DIV, rv.T1, rv.A0, rv.A1),
|
||||
rv.inst_r_r_r(.REMU, rv.T2, rv.A0, rv.A1),
|
||||
}
|
||||
r := rv.encode(src, nil, code[:], &relocs, &errors)
|
||||
ok("M: encode ok", r.success)
|
||||
byte_count, success := rv.encode(src, nil, code[:], &relocs, &errors)
|
||||
ok("M: encode ok", success)
|
||||
|
||||
d_insts: [dynamic]rv.Instruction
|
||||
d_info: [dynamic]rv.Instruction_Info
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("M: MUL", d_insts[0].mnemonic == .MUL)
|
||||
ok("M: DIV", d_insts[1].mnemonic == .DIV)
|
||||
ok("M: REMU", d_insts[2].mnemonic == .REMU)
|
||||
@@ -258,8 +258,8 @@ run_pipeline_tests :: proc() {
|
||||
},
|
||||
},
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("A: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("A: encode ok", success)
|
||||
eq_word("A: AMOADD.W", load_le(code[:], 0), 0x00B522AF)
|
||||
}
|
||||
|
||||
@@ -283,8 +283,8 @@ run_pipeline_tests :: proc() {
|
||||
},
|
||||
},
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("F: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("F: encode ok", success)
|
||||
eq_word("F: FADD.S", load_le(code[:], 0), 0x00C58553)
|
||||
|
||||
d_insts: [dynamic]rv.Instruction
|
||||
@@ -292,7 +292,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
|
||||
text := rv.aprint(d_insts[:], d_info[:], d_labels[:],
|
||||
nil, nil, nil, context.temp_allocator)
|
||||
@@ -317,8 +317,8 @@ run_pipeline_tests :: proc() {
|
||||
rv.Register(rv.REG_FPR | 12), // fa2
|
||||
rv.Register(rv.REG_FPR | 13)), // fa3
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("D: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("D: encode ok", success)
|
||||
eq_word("D: FMADD.D", load_le(code[:], 0), 0x6AC58543)
|
||||
}
|
||||
|
||||
@@ -333,8 +333,8 @@ run_pipeline_tests :: proc() {
|
||||
insts := []rv.Instruction{
|
||||
rv.inst_csr(.CSRRW, rv.A0, 0xF14, rv.ZERO),
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CSR: encode ok", r.success)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CSR: encode ok", success)
|
||||
eq_word("CSR: csrrw", load_le(code[:], 0), 0xF1401573)
|
||||
}
|
||||
|
||||
@@ -382,9 +382,9 @@ run_pipeline_tests :: proc() {
|
||||
ops = {rv.op_reg(rv.A2), rv.op_reg(rv.A3), {}, {}},
|
||||
},
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("C: encode ok", r.success)
|
||||
ok("C: byte count", r.byte_count == 8)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("C: encode ok", success)
|
||||
ok("C: byte count", byte_count == 8)
|
||||
get_hw := proc(buf: []u8, off: u32) -> u16 {
|
||||
return u16(buf[off]) | (u16(buf[off+1]) << 8)
|
||||
}
|
||||
@@ -402,7 +402,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("C: decode 4 insts", len(d_insts) == 4)
|
||||
ok("C: NOP", len(d_insts) >= 1 && d_insts[0].mnemonic == .C_NOP)
|
||||
ok("C: LI", len(d_insts) >= 2 && d_insts[1].mnemonic == .C_LI)
|
||||
@@ -429,16 +429,16 @@ run_pipeline_tests :: proc() {
|
||||
ops = {rv.op_reg(rv.A2), rv.op_reg(rv.A0), {}, {}},
|
||||
},
|
||||
}
|
||||
r := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("C: mixed encode", r.success)
|
||||
ok("C: mixed bytes = 8", r.byte_count == 8)
|
||||
byte_count, success := rv.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("C: mixed encode", success)
|
||||
ok("C: mixed bytes = 8", byte_count == 8)
|
||||
|
||||
d_insts: [dynamic]rv.Instruction
|
||||
d_info: [dynamic]rv.Instruction_Info
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("C: mixed decode 3", len(d_insts) == 3)
|
||||
ok("C: [0]=C.LI len=2", len(d_insts) >= 1 && d_insts[0].mnemonic == .C_LI && d_insts[0].length == 2)
|
||||
ok("C: [1]=ADDI len=4", len(d_insts) >= 2 && d_insts[1].mnemonic == .ADDI && d_insts[1].length == 4)
|
||||
@@ -483,9 +483,9 @@ run_pipeline_tests :: proc() {
|
||||
},
|
||||
rv.inst_none(.C_NOP),
|
||||
}
|
||||
r := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("C.BEQZ: encode", r.success)
|
||||
ok("C.BEQZ: byte_count=8", r.byte_count == 8)
|
||||
byte_count, success := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("C.BEQZ: encode", success)
|
||||
ok("C.BEQZ: byte_count=8", byte_count == 8)
|
||||
|
||||
get_hw := proc(buf: []u8, off: u32) -> u16 {
|
||||
return u16(buf[off]) | (u16(buf[off+1]) << 8)
|
||||
@@ -502,7 +502,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("C.BEQZ: decode count", len(d_insts) == 4)
|
||||
ok("C.BEQZ: [0] mnemonic", len(d_insts) >= 1 && d_insts[0].mnemonic == .C_BEQZ)
|
||||
ok("C.BEQZ: target = 6", len(d_insts) >= 1 && d_insts[0].ops[1].kind == .RELATIVE && u32(d_insts[0].ops[0].relative + d_insts[0].ops[1].relative)*0+ u32(d_insts[0].ops[1].relative) == 6)
|
||||
@@ -552,9 +552,9 @@ run_pipeline_tests :: proc() {
|
||||
ops = {rv.op_label(0), {}, {}, {}},
|
||||
},
|
||||
}
|
||||
r := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("C.J: encode", r.success)
|
||||
ok("C.J: byte_count=10", r.byte_count == 10)
|
||||
byte_count, success := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("C.J: encode", success)
|
||||
ok("C.J: byte_count=10", byte_count == 10)
|
||||
|
||||
get_hw := proc(buf: []u8, off: u32) -> u16 {
|
||||
return u16(buf[off]) | (u16(buf[off+1]) << 8)
|
||||
@@ -571,7 +571,7 @@ run_pipeline_tests :: proc() {
|
||||
d_labels: [dynamic]rv.Label_Definition
|
||||
defer delete(d_insts); defer delete(d_info); defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rv.decode(code[:r.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rv.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("C.J: decode count", len(d_insts) == 4)
|
||||
ok("C.J: [3] mnemonic", len(d_insts) >= 4 && d_insts[3].mnemonic == .C_J)
|
||||
ok("C.J: target = 0", len(d_insts) >= 4 && d_insts[3].ops[0].kind == .RELATIVE && u32(d_insts[3].ops[0].relative) == 0)
|
||||
@@ -601,8 +601,8 @@ run_pipeline_tests :: proc() {
|
||||
// Target at byte 2 + 64*4 = 258 -- out of range for 9-bit signed (max 254)
|
||||
append(&long_insts, rv.inst_none(.C_NOP))
|
||||
|
||||
r := rv.encode(long_insts[:], ld[:], big_code[:], &relocs, &errors)
|
||||
ok("C.BEQZ out-of-range: error", !r.success && len(errors) > 0)
|
||||
byte_count, success := rv.encode(long_insts[:], ld[:], big_code[:], &relocs, &errors)
|
||||
ok("C.BEQZ out-of-range: error", !success && len(errors) > 0)
|
||||
if len(errors) > 0 {
|
||||
ok("C.BEQZ out-of-range: code", errors[0].code == .LABEL_OUT_OF_RANGE)
|
||||
}
|
||||
|
||||
@@ -29,25 +29,24 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
endianness: Endianness = .BIG,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_bytes := u32(len(data)) & ~u32(3) // drop dangling tail
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
defer delete(pending_branches)
|
||||
|
||||
pc: u32 = 0
|
||||
for pc < n_bytes {
|
||||
word := read_u32(data, pc, endianness)
|
||||
for byte_count < n_bytes {
|
||||
word := read_u32(data, byte_count, endianness)
|
||||
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
entry_idx := decode_one_inline(word, pc, &inst, &info)
|
||||
entry_idx := decode_one_inline(word, byte_count, &inst, &info)
|
||||
|
||||
if entry_idx < 0 {
|
||||
append(errors, Error{inst_idx = pc, code = .INVALID_OPCODE})
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 4}
|
||||
info = Instruction_Info{offset = pc}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
} else {
|
||||
inst_idx_for_branches := u32(len(instructions))
|
||||
for slot in 0..<inst.operand_count {
|
||||
@@ -64,12 +63,13 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pc += 4
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pc, label_defs, relocs)
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -38,25 +38,21 @@ encode :: proc(
|
||||
endianness: Endianness = .BIG,
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
n_inst := u32(len(instructions))
|
||||
if u32(len(code)) < n_inst * 4 {
|
||||
append(errors, Error{inst_idx = 0, code = .BUFFER_OVERFLOW})
|
||||
return Result{byte_count = 0, success = false}
|
||||
return
|
||||
}
|
||||
|
||||
errors_start := u32(len(errors))
|
||||
pending_start := u32(len(relocs))
|
||||
pc: u32 = 0
|
||||
|
||||
for i in 0..<n_inst {
|
||||
inst := &instructions[i]
|
||||
word, ok := encode_one_inline(inst, pc, u16(i), relocs, errors)
|
||||
if !ok {
|
||||
return Result{byte_count = pc, success = false}
|
||||
}
|
||||
write_u32(code, pc, word, endianness)
|
||||
pc += 4
|
||||
word := encode_one_inline(inst, byte_count, u16(i), relocs, errors) or_return
|
||||
write_u32(code, byte_count, word, endianness)
|
||||
byte_count += 4
|
||||
}
|
||||
|
||||
// PASS 1.5
|
||||
@@ -67,7 +63,8 @@ encode :: proc(
|
||||
}
|
||||
|
||||
if !resolve {
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// PASS 2
|
||||
@@ -87,7 +84,8 @@ encode :: proc(
|
||||
resize(relocs, int(write_idx))
|
||||
}
|
||||
|
||||
return Result{byte_count = pc, success = u32(len(errors)) == errors_start}
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -32,7 +32,6 @@ import "../isa"
|
||||
// is sign-extended and pre-scaled by element size, so the effective
|
||||
// range is `±64 × element_size` bytes.
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
Label_Definition :: isa.Label_Definition
|
||||
|
||||
@@ -76,8 +76,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
rsp.inst_r_m (.LW, rsp.T0, rsp.mem(rsp.SP, 16)),
|
||||
rsp.inst_none (.NOP),
|
||||
}
|
||||
e := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("scalar: encode success", e.success)
|
||||
byte_count, success := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("scalar: encode success", success)
|
||||
eq_word ("scalar: ADD word", load_be(code[:], 0), 0x012A4020)
|
||||
eq_word ("scalar: ADDIU word", load_be(code[:], 4), 0x25280064)
|
||||
eq_word ("scalar: LW word", load_be(code[:], 8), 0x8FA80010)
|
||||
@@ -90,8 +90,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("scalar: decode success", d.success)
|
||||
dbyte_count, dsuccess := rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("scalar: decode success", dsuccess)
|
||||
ok("scalar: 4 insts", len(d_insts) == 4)
|
||||
ok("scalar[0] ADD", d_insts[0].mnemonic == .ADD)
|
||||
ok("scalar[3] NOP", d_insts[3].mnemonic == .NOP)
|
||||
@@ -115,8 +115,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
insts := []rsp.Instruction{
|
||||
rsp.inst_v_v_v(.VMULF, rsp.VR0, rsp.VR1, rsp.VR2, 3),
|
||||
}
|
||||
e := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("vu: encode", e.success)
|
||||
byte_count, success := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("vu: encode", success)
|
||||
eq_word("vu: VMULF", load_be(code[:], 0), 0x4A620800)
|
||||
|
||||
d_insts: [dynamic]rsp.Instruction
|
||||
@@ -126,8 +126,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("vu: decode", d.success)
|
||||
dbyte_count, dsuccess := rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("vu: decode", dsuccess)
|
||||
ok("vu: VMULF mnem", d_insts[0].mnemonic == .VMULF)
|
||||
i0 := d_insts[0]
|
||||
ok("vu: vd=$v0", i0.ops[0].reg == rsp.VR0)
|
||||
@@ -153,8 +153,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
insts := []rsp.Instruction{
|
||||
rsp.inst_v_vmem(.LQV, rsp.VR4, rsp.vmem(rsp.T0, 0, 16)),
|
||||
}
|
||||
e := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("vls: encode", e.success)
|
||||
byte_count, success := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok ("vls: encode", success)
|
||||
eq_word("vls: LQV", load_be(code[:], 0), 0xC9042010)
|
||||
|
||||
d_insts: [dynamic]rsp.Instruction
|
||||
@@ -164,8 +164,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("vls: decode", d.success)
|
||||
dbyte_count, dsuccess := rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("vls: decode", dsuccess)
|
||||
ok("vls: LQV mnem", d_insts[0].mnemonic == .LQV)
|
||||
i0 := d_insts[0]
|
||||
ok("vls: vt=$v4", i0.ops[0].reg == rsp.VR4)
|
||||
@@ -195,8 +195,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
rsp.inst_branch2(.BNE, rsp.T0, rsp.ZERO, 0),
|
||||
rsp.inst_none(.NOP),
|
||||
}
|
||||
e := rsp.encode(insts, ld_in[:], code[:], &relocs, &errors)
|
||||
ok("br: encode", e.success)
|
||||
byte_count, success := rsp.encode(insts, ld_in[:], code[:], &relocs, &errors)
|
||||
ok("br: encode", success)
|
||||
eq_word("br: BNE word", load_be(code[:], 8), 0x1500FFFD)
|
||||
|
||||
d_insts: [dynamic]rsp.Instruction
|
||||
@@ -206,8 +206,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("br: decode", d.success)
|
||||
dbyte_count, dsuccess := rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("br: decode", dsuccess)
|
||||
ok("br: 1 label inferred", len(d_labels) == 1)
|
||||
ok("br: label at byte 0", int(d_labels[0]) == 0)
|
||||
|
||||
@@ -233,8 +233,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
ops = {rsp.op_reg(rsp.T0), rsp.op_reg(rsp.VCO), {}, {}},
|
||||
},
|
||||
}
|
||||
e := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("cop2c: encode", e.success)
|
||||
byte_count, success := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("cop2c: encode", success)
|
||||
eq_word("cop2c: CFC2",load_be(code[:], 0), 0x48480000)
|
||||
|
||||
d_insts: [dynamic]rsp.Instruction
|
||||
@@ -244,8 +244,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
d := rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("cop2c: decode", d.success)
|
||||
dbyte_count, dsuccess := rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
ok("cop2c: decode", dsuccess)
|
||||
ok("cop2c: CFC2 mnem",d_insts[0].mnemonic == .CFC2)
|
||||
|
||||
text := rsp.aprint(d_insts[:], d_info[:], d_labels[:],
|
||||
@@ -268,8 +268,8 @@ run_rsp_pipeline_tests :: proc() {
|
||||
ops = {rsp.op_reg(rsp.T0), rsp.op_reg(rsp.Register(rsp.REG_CP0 | 4)), {}, {}},
|
||||
},
|
||||
}
|
||||
e := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("cp0: encode", e.success)
|
||||
byte_count, success := rsp.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("cp0: encode", success)
|
||||
eq_word("cp0: MTC0", load_be(code[:], 0), 0x40882000)
|
||||
|
||||
d_insts: [dynamic]rsp.Instruction
|
||||
@@ -279,7 +279,7 @@ run_rsp_pipeline_tests :: proc() {
|
||||
defer delete(d_info)
|
||||
defer delete(d_labels)
|
||||
clear(&errors)
|
||||
rsp.decode(code[:e.byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
rsp.decode(code[:byte_count], nil, &d_insts, &d_info, &d_labels, &errors)
|
||||
|
||||
text := rsp.aprint(d_insts[:], d_info[:], d_labels[:],
|
||||
nil, nil, nil, context.temp_allocator)
|
||||
|
||||
258
core/rexcode/wasm/decoder.odin
Normal file
258
core/rexcode/wasm/decoder.odin
Normal file
@@ -0,0 +1,258 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly DECODER
|
||||
// =============================================================================
|
||||
//
|
||||
// Single forward pass, mirroring the encoder. Each step:
|
||||
//
|
||||
// 1. Read the opcode. A leading 0xFC switches to the misc group, whose
|
||||
// sub-opcode is an unsigned LEB128 read next; otherwise the single byte
|
||||
// is the opcode. The byte (or sub-opcode) indexes the DECODE_MAIN /
|
||||
// DECODE_MISC tables built from ENCODING_TABLE at package init.
|
||||
// 2. Look the resulting Mnemonic's form back up in ENCODING_TABLE and read
|
||||
// its immediates in declaration order, reconstructing Operands.
|
||||
//
|
||||
// WASM control flow is structured (branches carry relative label depths, not
|
||||
// byte offsets), so there is no PC-relative label inference. Object-file index
|
||||
// relocations *are* re-attached: when an input relocation lands on a decoded
|
||||
// index field, that operand is marked `symbolic` and carries the label id.
|
||||
//
|
||||
// `br_table`'s case-label vector is materialised into a freshly allocated
|
||||
// `[]u32` (caller owns it, like the rest of the decoded output).
|
||||
|
||||
Instruction_Info :: struct {
|
||||
offset: u32,
|
||||
decode_entry: u16,
|
||||
_: u16,
|
||||
}
|
||||
#assert(size_of(Instruction_Info) == 8)
|
||||
|
||||
decode :: proc(
|
||||
data: []u8,
|
||||
relocs: []Relocation,
|
||||
instructions: ^[dynamic]Instruction,
|
||||
inst_info: ^[dynamic]Instruction_Info,
|
||||
errors: ^[dynamic]Error,
|
||||
targets_allocator := context.allocator,
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
errors_start := u32(len(errors))
|
||||
n := u32(len(data))
|
||||
|
||||
for byte_count < n {
|
||||
inst, info, next, dok := decode_one(data, relocs, byte_count, targets_allocator)
|
||||
if !dok {
|
||||
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
||||
inst = Instruction{mnemonic = .INVALID, length = 1}
|
||||
info = Instruction_Info{offset = byte_count}
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
byte_count += 1
|
||||
continue
|
||||
}
|
||||
inst.length = u8(min(next - byte_count, 255))
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
byte_count = next
|
||||
}
|
||||
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
decode_one :: proc(
|
||||
data: []u8,
|
||||
relocs: []Relocation,
|
||||
pc: u32,
|
||||
targets_allocator: runtime.Allocator,
|
||||
) -> (inst: Instruction, info: Instruction_Info, next: u32, ok: bool) {
|
||||
off := pc
|
||||
if off >= u32(len(data)) {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
|
||||
b0 := data[off]
|
||||
off += 1
|
||||
|
||||
m: Mnemonic = .INVALID
|
||||
switch b0 {
|
||||
case PREFIX_MISC:
|
||||
sub := read_uleb(data, &off) or_return
|
||||
if sub < u64(DECODE_MISC_COUNT) {
|
||||
m = DECODE_MISC[sub]
|
||||
}
|
||||
case PREFIX_SIMD:
|
||||
sub := read_uleb(data, &off) or_return
|
||||
if sub < u64(DECODE_SIMD_COUNT) {
|
||||
m = DECODE_SIMD[sub]
|
||||
}
|
||||
case PREFIX_ATOM:
|
||||
sub := read_uleb(data, &off) or_return
|
||||
if sub < u64(DECODE_ATOMIC_COUNT) {
|
||||
m = DECODE_ATOMIC[sub]
|
||||
}
|
||||
case:
|
||||
m = DECODE_MAIN[b0]
|
||||
}
|
||||
if m == .INVALID {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
|
||||
form := encoding_form(m)
|
||||
inst.mnemonic = m
|
||||
inst.flags = {}
|
||||
|
||||
slot := 0
|
||||
for k, ki in form.imm {
|
||||
switch k {
|
||||
case .NONE:
|
||||
// nothing
|
||||
|
||||
case .BLOCKTYPE:
|
||||
v := read_sleb(data, &off) or_return
|
||||
inst.ops[slot] = Operand{kind = .BLOCK_TYPE, immediate = v}
|
||||
slot += 1
|
||||
|
||||
case .I32:
|
||||
v := read_sleb(data, &off) or_return
|
||||
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = v, size = 4}
|
||||
slot += 1
|
||||
|
||||
case .I64:
|
||||
v := read_sleb(data, &off) or_return
|
||||
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = v, size = 8}
|
||||
slot += 1
|
||||
|
||||
case .F32:
|
||||
bits := read_u32_block(data, &off) or_return
|
||||
inst.ops[slot] = Operand{
|
||||
kind = .IMMEDIATE,
|
||||
immediate = i64(bits), size = 4, flags = {is_float = true},
|
||||
}
|
||||
slot += 1
|
||||
|
||||
case .F64:
|
||||
bits := read_u64_block(data, &off) or_return
|
||||
inst.ops[slot] = Operand{
|
||||
kind = .IMMEDIATE,
|
||||
immediate = i64(bits), size = 8, flags = {is_float = true},
|
||||
}
|
||||
slot += 1
|
||||
|
||||
case .IDX:
|
||||
field := off
|
||||
raw := read_uleb(data, &off) or_return
|
||||
op := Operand{kind = .INDEX, index = u32(raw), idx_kind = idx_kind_for(m, ki)}
|
||||
if lid, found := reloc_label_at(relocs, field); found {
|
||||
op.index = lid
|
||||
op.flags.symbolic = true
|
||||
op.size = 5
|
||||
} else if m == .CALL {
|
||||
op.flags.symbolic = true
|
||||
}
|
||||
inst.ops[slot] = op
|
||||
slot += 1
|
||||
|
||||
case .MEMARG:
|
||||
// TODO(bill): Is this fully correct?
|
||||
// See: https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
|
||||
align := read_uleb(data, &off) or_return
|
||||
offset := read_uleb(data, &off) or_return
|
||||
// NOTE(bill) this appears to be stored as log2 even though the docs say otherwise
|
||||
align = 1<<align
|
||||
inst.ops[slot] = Operand{kind = .MEMARG, memarg = Memarg{align = u32(align), offset = u32(offset)}}
|
||||
slot += 1
|
||||
|
||||
case .REFTYPE:
|
||||
if off >= u32(len(data)) {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
t := data[off]
|
||||
off += 1
|
||||
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = i64(t), size = 1}
|
||||
slot += 1
|
||||
|
||||
case .BR_TABLE:
|
||||
count := read_uleb(data, &off) or_return
|
||||
targets := make([]u32, int(count), targets_allocator)
|
||||
for &target in targets {
|
||||
t := read_uleb(data, &off) or_return
|
||||
target = u32(t)
|
||||
}
|
||||
def := read_uleb(data, &off) or_return
|
||||
inst.targets = targets
|
||||
inst.ops[slot] = Operand{kind = .INDEX, index = u32(def), idx_kind = .LABEL}
|
||||
slot += 1
|
||||
|
||||
case .ZERO_BYTE:
|
||||
if off >= u32(len(data)) {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
off += 1 // reserved 0x00, consumes no operand
|
||||
|
||||
case .LANE:
|
||||
if off >= u32(len(data)) {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
l := data[off]
|
||||
off += 1
|
||||
inst.ops[slot] = Operand{kind = .IMMEDIATE, immediate = i64(l), size = 1}
|
||||
slot += 1
|
||||
|
||||
case .LANES16:
|
||||
if off + 16 > u32(len(data)) {
|
||||
next = pc
|
||||
return
|
||||
}
|
||||
copy(inst.bytes[:], data[off:][:16])
|
||||
off += 16 // value lives in inst.bytes, no operand
|
||||
}
|
||||
}
|
||||
|
||||
inst.operand_count = u8(slot)
|
||||
info.offset = pc
|
||||
info.decode_entry = u16(m)
|
||||
next = off
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// Which index space the IDX immediate in operand slot `which` addresses, by
|
||||
// mnemonic. Mirrors how the builders in instructions.odin tag each operand.
|
||||
@(private="file")
|
||||
idx_kind_for :: #force_inline proc "contextless" (m: Mnemonic, which: int) -> Index_Kind {
|
||||
#partial switch m {
|
||||
case .BR, .BR_IF: return .LABEL
|
||||
case .CALL, .REF_FUNC: return .FUNC
|
||||
case .CALL_INDIRECT: return .TYPE if which == 0 else .TABLE
|
||||
case .LOCAL_GET, .LOCAL_SET, .LOCAL_TEE: return .LOCAL
|
||||
case .GLOBAL_GET, .GLOBAL_SET: return .GLOBAL
|
||||
case .MEMORY_INIT, .DATA_DROP: return .DATA
|
||||
case .TABLE_INIT: return .ELEM if which == 0 else .TABLE
|
||||
case .ELEM_DROP: return .ELEM
|
||||
case .TABLE_COPY: return .TABLE
|
||||
case .TABLE_GROW, .TABLE_SIZE, .TABLE_FILL: return .TABLE
|
||||
}
|
||||
return .NONE
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
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
|
||||
}
|
||||
560
core/rexcode/wasm/decoding_tables.odin
Normal file
560
core/rexcode/wasm/decoding_tables.odin
Normal file
@@ -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 Mnemonic. Dispatch is two-level:
|
||||
//
|
||||
// * core opcodes (prefix 0x00): DECODE_MAIN[opcode_byte]
|
||||
// * 0xFC misc group: DECODE_MISC[sub_opcode]
|
||||
//
|
||||
// These mirror ENCODING_TABLE (the single source of truth) entry-for-entry;
|
||||
// unlisted slots default to .INVALID. Four dispatch arrays cover the four
|
||||
// opcode spaces: core (DECODE_MAIN), 0xFC misc (DECODE_MISC), 0xFD SIMD
|
||||
// (DECODE_SIMD), and 0xFE threads/atomics (DECODE_ATOMIC).
|
||||
|
||||
DECODE_MAIN_COUNT :: 256 // (0..=0xD2)
|
||||
DECODE_MISC_COUNT :: 32 // 0xFC sub-opcodes (0..=17)
|
||||
DECODE_SIMD_COUNT :: 0x114 // 0xFD sub-opcodes (0..=0x113)
|
||||
DECODE_ATOMIC_COUNT :: 0x4F // 0xFE sub-opcodes (0..=0x4E)
|
||||
|
||||
@(rodata)
|
||||
DECODE_MAIN := [DECODE_MAIN_COUNT]Mnemonic{
|
||||
0x00 = .UNREACHABLE,
|
||||
0x01 = .NOP,
|
||||
0x02 = .BLOCK,
|
||||
0x03 = .LOOP,
|
||||
0x04 = .IF,
|
||||
0x05 = .ELSE,
|
||||
0x0B = .END,
|
||||
0x0C = .BR,
|
||||
0x0D = .BR_IF,
|
||||
0x0E = .BR_TABLE,
|
||||
0x0F = .RETURN,
|
||||
0x10 = .CALL,
|
||||
0x11 = .CALL_INDIRECT,
|
||||
0x1A = .DROP,
|
||||
0x1B = .SELECT,
|
||||
0x20 = .LOCAL_GET,
|
||||
0x21 = .LOCAL_SET,
|
||||
0x22 = .LOCAL_TEE,
|
||||
0x23 = .GLOBAL_GET,
|
||||
0x24 = .GLOBAL_SET,
|
||||
0x28 = .I32_LOAD,
|
||||
0x29 = .I64_LOAD,
|
||||
0x2A = .F32_LOAD,
|
||||
0x2B = .F64_LOAD,
|
||||
0x2C = .I32_LOAD8_S,
|
||||
0x2D = .I32_LOAD8_U,
|
||||
0x2E = .I32_LOAD16_S,
|
||||
0x2F = .I32_LOAD16_U,
|
||||
0x30 = .I64_LOAD8_S,
|
||||
0x31 = .I64_LOAD8_U,
|
||||
0x32 = .I64_LOAD16_S,
|
||||
0x33 = .I64_LOAD16_U,
|
||||
0x34 = .I64_LOAD32_S,
|
||||
0x35 = .I64_LOAD32_U,
|
||||
0x36 = .I32_STORE,
|
||||
0x37 = .I64_STORE,
|
||||
0x38 = .F32_STORE,
|
||||
0x39 = .F64_STORE,
|
||||
0x3A = .I32_STORE8,
|
||||
0x3B = .I32_STORE16,
|
||||
0x3C = .I64_STORE8,
|
||||
0x3D = .I64_STORE16,
|
||||
0x3E = .I64_STORE32,
|
||||
0x3F = .MEMORY_SIZE,
|
||||
0x40 = .MEMORY_GROW,
|
||||
0x41 = .I32_CONST,
|
||||
0x42 = .I64_CONST,
|
||||
0x43 = .F32_CONST,
|
||||
0x44 = .F64_CONST,
|
||||
0x45 = .I32_EQZ,
|
||||
0x46 = .I32_EQ,
|
||||
0x47 = .I32_NE,
|
||||
0x48 = .I32_LT_S,
|
||||
0x49 = .I32_LT_U,
|
||||
0x4A = .I32_GT_S,
|
||||
0x4B = .I32_GT_U,
|
||||
0x4C = .I32_LE_S,
|
||||
0x4D = .I32_LE_U,
|
||||
0x4E = .I32_GE_S,
|
||||
0x4F = .I32_GE_U,
|
||||
0x50 = .I64_EQZ,
|
||||
0x51 = .I64_EQ,
|
||||
0x52 = .I64_NE,
|
||||
0x53 = .I64_LT_S,
|
||||
0x54 = .I64_LT_U,
|
||||
0x55 = .I64_GT_S,
|
||||
0x56 = .I64_GT_U,
|
||||
0x57 = .I64_LE_S,
|
||||
0x58 = .I64_LE_U,
|
||||
0x59 = .I64_GE_S,
|
||||
0x5A = .I64_GE_U,
|
||||
0x5B = .F32_EQ,
|
||||
0x5C = .F32_NE,
|
||||
0x5D = .F32_LT,
|
||||
0x5E = .F32_GT,
|
||||
0x5F = .F32_LE,
|
||||
0x60 = .F32_GE,
|
||||
0x61 = .F64_EQ,
|
||||
0x62 = .F64_NE,
|
||||
0x63 = .F64_LT,
|
||||
0x64 = .F64_GT,
|
||||
0x65 = .F64_LE,
|
||||
0x66 = .F64_GE,
|
||||
0x67 = .I32_CLZ,
|
||||
0x68 = .I32_CTZ,
|
||||
0x69 = .I32_POPCNT,
|
||||
0x6A = .I32_ADD,
|
||||
0x6B = .I32_SUB,
|
||||
0x6C = .I32_MUL,
|
||||
0x6D = .I32_DIV_S,
|
||||
0x6E = .I32_DIV_U,
|
||||
0x6F = .I32_REM_S,
|
||||
0x70 = .I32_REM_U,
|
||||
0x71 = .I32_AND,
|
||||
0x72 = .I32_OR,
|
||||
0x73 = .I32_XOR,
|
||||
0x74 = .I32_SHL,
|
||||
0x75 = .I32_SHR_S,
|
||||
0x76 = .I32_SHR_U,
|
||||
0x77 = .I32_ROTL,
|
||||
0x78 = .I32_ROTR,
|
||||
0x79 = .I64_CLZ,
|
||||
0x7A = .I64_CTZ,
|
||||
0x7B = .I64_POPCNT,
|
||||
0x7C = .I64_ADD,
|
||||
0x7D = .I64_SUB,
|
||||
0x7E = .I64_MUL,
|
||||
0x7F = .I64_DIV_S,
|
||||
0x80 = .I64_DIV_U,
|
||||
0x81 = .I64_REM_S,
|
||||
0x82 = .I64_REM_U,
|
||||
0x83 = .I64_AND,
|
||||
0x84 = .I64_OR,
|
||||
0x85 = .I64_XOR,
|
||||
0x86 = .I64_SHL,
|
||||
0x87 = .I64_SHR_S,
|
||||
0x88 = .I64_SHR_U,
|
||||
0x89 = .I64_ROTL,
|
||||
0x8A = .I64_ROTR,
|
||||
0x8B = .F32_ABS,
|
||||
0x8C = .F32_NEG,
|
||||
0x8D = .F32_CEIL,
|
||||
0x8E = .F32_FLOOR,
|
||||
0x8F = .F32_TRUNC,
|
||||
0x90 = .F32_NEAREST,
|
||||
0x91 = .F32_SQRT,
|
||||
0x92 = .F32_ADD,
|
||||
0x93 = .F32_SUB,
|
||||
0x94 = .F32_MUL,
|
||||
0x95 = .F32_DIV,
|
||||
0x96 = .F32_MIN,
|
||||
0x97 = .F32_MAX,
|
||||
0x98 = .F32_COPYSIGN,
|
||||
0x99 = .F64_ABS,
|
||||
0x9A = .F64_NEG,
|
||||
0x9B = .F64_CEIL,
|
||||
0x9C = .F64_FLOOR,
|
||||
0x9D = .F64_TRUNC,
|
||||
0x9E = .F64_NEAREST,
|
||||
0x9F = .F64_SQRT,
|
||||
0xA0 = .F64_ADD,
|
||||
0xA1 = .F64_SUB,
|
||||
0xA2 = .F64_MUL,
|
||||
0xA3 = .F64_DIV,
|
||||
0xA4 = .F64_MIN,
|
||||
0xA5 = .F64_MAX,
|
||||
0xA6 = .F64_COPYSIGN,
|
||||
0xA7 = .I32_WRAP_I64,
|
||||
0xA8 = .I32_TRUNC_F32_S,
|
||||
0xA9 = .I32_TRUNC_F32_U,
|
||||
0xAA = .I32_TRUNC_F64_S,
|
||||
0xAB = .I32_TRUNC_F64_U,
|
||||
0xAC = .I64_EXTEND_I32_S,
|
||||
0xAD = .I64_EXTEND_I32_U,
|
||||
0xAE = .I64_TRUNC_F32_S,
|
||||
0xAF = .I64_TRUNC_F32_U,
|
||||
0xB0 = .I64_TRUNC_F64_S,
|
||||
0xB1 = .I64_TRUNC_F64_U,
|
||||
0xB2 = .F32_CONVERT_I32_S,
|
||||
0xB3 = .F32_CONVERT_I32_U,
|
||||
0xB4 = .F32_CONVERT_I64_S,
|
||||
0xB5 = .F32_CONVERT_I64_U,
|
||||
0xB6 = .F32_DEMOTE_F64,
|
||||
0xB7 = .F64_CONVERT_I32_S,
|
||||
0xB8 = .F64_CONVERT_I32_U,
|
||||
0xB9 = .F64_CONVERT_I64_S,
|
||||
0xBA = .F64_CONVERT_I64_U,
|
||||
0xBB = .F64_PROMOTE_F32,
|
||||
0xBC = .I32_REINTERPRET_F32,
|
||||
0xBD = .I64_REINTERPRET_F64,
|
||||
0xBE = .F32_REINTERPRET_I32,
|
||||
0xBF = .F64_REINTERPRET_I64,
|
||||
0xC0 = .I32_EXTEND8_S,
|
||||
0xC1 = .I32_EXTEND16_S,
|
||||
0xC2 = .I64_EXTEND8_S,
|
||||
0xC3 = .I64_EXTEND16_S,
|
||||
0xC4 = .I64_EXTEND32_S,
|
||||
0xD0 = .REF_NULL,
|
||||
0xD1 = .REF_IS_NULL,
|
||||
0xD2 = .REF_FUNC,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
DECODE_MISC := [DECODE_MISC_COUNT]Mnemonic{
|
||||
0 = .I32_TRUNC_SAT_F32_S,
|
||||
1 = .I32_TRUNC_SAT_F32_U,
|
||||
2 = .I32_TRUNC_SAT_F64_S,
|
||||
3 = .I32_TRUNC_SAT_F64_U,
|
||||
4 = .I64_TRUNC_SAT_F32_S,
|
||||
5 = .I64_TRUNC_SAT_F32_U,
|
||||
6 = .I64_TRUNC_SAT_F64_S,
|
||||
7 = .I64_TRUNC_SAT_F64_U,
|
||||
8 = .MEMORY_INIT,
|
||||
9 = .DATA_DROP,
|
||||
10 = .MEMORY_COPY,
|
||||
11 = .MEMORY_FILL,
|
||||
12 = .TABLE_INIT,
|
||||
13 = .ELEM_DROP,
|
||||
14 = .TABLE_COPY,
|
||||
15 = .TABLE_GROW,
|
||||
16 = .TABLE_SIZE,
|
||||
17 = .TABLE_FILL,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
DECODE_SIMD := [DECODE_SIMD_COUNT]Mnemonic{
|
||||
0x00 = .V128_LOAD,
|
||||
0x01 = .V128_LOAD8X8_S,
|
||||
0x02 = .V128_LOAD8X8_U,
|
||||
0x03 = .V128_LOAD16X4_S,
|
||||
0x04 = .V128_LOAD16X4_U,
|
||||
0x05 = .V128_LOAD32X2_S,
|
||||
0x06 = .V128_LOAD32X2_U,
|
||||
0x07 = .V128_LOAD8_SPLAT,
|
||||
0x08 = .V128_LOAD16_SPLAT,
|
||||
0x09 = .V128_LOAD32_SPLAT,
|
||||
0x0A = .V128_LOAD64_SPLAT,
|
||||
0x0B = .V128_STORE,
|
||||
0x0C = .V128_CONST,
|
||||
0x0D = .I8X16_SHUFFLE,
|
||||
0x0E = .I8X16_SWIZZLE,
|
||||
0x0F = .I8X16_SPLAT,
|
||||
0x10 = .I16X8_SPLAT,
|
||||
0x11 = .I32X4_SPLAT,
|
||||
0x12 = .I64X2_SPLAT,
|
||||
0x13 = .F32X4_SPLAT,
|
||||
0x14 = .F64X2_SPLAT,
|
||||
0x15 = .I8X16_EXTRACT_LANE_S,
|
||||
0x16 = .I8X16_EXTRACT_LANE_U,
|
||||
0x17 = .I8X16_REPLACE_LANE,
|
||||
0x18 = .I16X8_EXTRACT_LANE_S,
|
||||
0x19 = .I16X8_EXTRACT_LANE_U,
|
||||
0x1A = .I16X8_REPLACE_LANE,
|
||||
0x1B = .I32X4_EXTRACT_LANE,
|
||||
0x1C = .I32X4_REPLACE_LANE,
|
||||
0x1D = .I64X2_EXTRACT_LANE,
|
||||
0x1E = .I64X2_REPLACE_LANE,
|
||||
0x1F = .F32X4_EXTRACT_LANE,
|
||||
0x20 = .F32X4_REPLACE_LANE,
|
||||
0x21 = .F64X2_EXTRACT_LANE,
|
||||
0x22 = .F64X2_REPLACE_LANE,
|
||||
0x23 = .I8X16_EQ,
|
||||
0x24 = .I8X16_NE,
|
||||
0x25 = .I8X16_LT_S,
|
||||
0x26 = .I8X16_LT_U,
|
||||
0x27 = .I8X16_GT_S,
|
||||
0x28 = .I8X16_GT_U,
|
||||
0x29 = .I8X16_LE_S,
|
||||
0x2A = .I8X16_LE_U,
|
||||
0x2B = .I8X16_GE_S,
|
||||
0x2C = .I8X16_GE_U,
|
||||
0x2D = .I16X8_EQ,
|
||||
0x2E = .I16X8_NE,
|
||||
0x2F = .I16X8_LT_S,
|
||||
0x30 = .I16X8_LT_U,
|
||||
0x31 = .I16X8_GT_S,
|
||||
0x32 = .I16X8_GT_U,
|
||||
0x33 = .I16X8_LE_S,
|
||||
0x34 = .I16X8_LE_U,
|
||||
0x35 = .I16X8_GE_S,
|
||||
0x36 = .I16X8_GE_U,
|
||||
0x37 = .I32X4_EQ,
|
||||
0x38 = .I32X4_NE,
|
||||
0x39 = .I32X4_LT_S,
|
||||
0x3A = .I32X4_LT_U,
|
||||
0x3B = .I32X4_GT_S,
|
||||
0x3C = .I32X4_GT_U,
|
||||
0x3D = .I32X4_LE_S,
|
||||
0x3E = .I32X4_LE_U,
|
||||
0x3F = .I32X4_GE_S,
|
||||
0x40 = .I32X4_GE_U,
|
||||
0x41 = .F32X4_EQ,
|
||||
0x42 = .F32X4_NE,
|
||||
0x43 = .F32X4_LT,
|
||||
0x44 = .F32X4_GT,
|
||||
0x45 = .F32X4_LE,
|
||||
0x46 = .F32X4_GE,
|
||||
0x47 = .F64X2_EQ,
|
||||
0x48 = .F64X2_NE,
|
||||
0x49 = .F64X2_LT,
|
||||
0x4A = .F64X2_GT,
|
||||
0x4B = .F64X2_LE,
|
||||
0x4C = .F64X2_GE,
|
||||
0x4D = .V128_NOT,
|
||||
0x4E = .V128_AND,
|
||||
0x4F = .V128_ANDNOT,
|
||||
0x50 = .V128_OR,
|
||||
0x51 = .V128_XOR,
|
||||
0x52 = .V128_BITSELECT,
|
||||
0x53 = .V128_ANY_TRUE,
|
||||
0x54 = .V128_LOAD8_LANE,
|
||||
0x55 = .V128_LOAD16_LANE,
|
||||
0x56 = .V128_LOAD32_LANE,
|
||||
0x57 = .V128_LOAD64_LANE,
|
||||
0x58 = .V128_STORE8_LANE,
|
||||
0x59 = .V128_STORE16_LANE,
|
||||
0x5A = .V128_STORE32_LANE,
|
||||
0x5B = .V128_STORE64_LANE,
|
||||
0x5C = .V128_LOAD32_ZERO,
|
||||
0x5D = .V128_LOAD64_ZERO,
|
||||
0x5E = .F32X4_DEMOTE_F64X2_ZERO,
|
||||
0x5F = .F64X2_PROMOTE_LOW_F32X4,
|
||||
0x60 = .I8X16_ABS,
|
||||
0x61 = .I8X16_NEG,
|
||||
0x62 = .I8X16_POPCNT,
|
||||
0x63 = .I8X16_ALL_TRUE,
|
||||
0x64 = .I8X16_BITMASK,
|
||||
0x65 = .I8X16_NARROW_I16X8_S,
|
||||
0x66 = .I8X16_NARROW_I16X8_U,
|
||||
0x67 = .F32X4_CEIL,
|
||||
0x68 = .F32X4_FLOOR,
|
||||
0x69 = .F32X4_TRUNC,
|
||||
0x6A = .F32X4_NEAREST,
|
||||
0x6B = .I8X16_SHL,
|
||||
0x6C = .I8X16_SHR_S,
|
||||
0x6D = .I8X16_SHR_U,
|
||||
0x6E = .I8X16_ADD,
|
||||
0x6F = .I8X16_ADD_SAT_S,
|
||||
0x70 = .I8X16_ADD_SAT_U,
|
||||
0x71 = .I8X16_SUB,
|
||||
0x72 = .I8X16_SUB_SAT_S,
|
||||
0x73 = .I8X16_SUB_SAT_U,
|
||||
0x74 = .F64X2_CEIL,
|
||||
0x75 = .F64X2_FLOOR,
|
||||
0x76 = .I8X16_MIN_S,
|
||||
0x77 = .I8X16_MIN_U,
|
||||
0x78 = .I8X16_MAX_S,
|
||||
0x79 = .I8X16_MAX_U,
|
||||
0x7A = .F64X2_TRUNC,
|
||||
0x7B = .I8X16_AVGR_U,
|
||||
0x7C = .I16X8_EXTADD_PAIRWISE_I8X16_S,
|
||||
0x7D = .I16X8_EXTADD_PAIRWISE_I8X16_U,
|
||||
0x7E = .I32X4_EXTADD_PAIRWISE_I16X8_S,
|
||||
0x7F = .I32X4_EXTADD_PAIRWISE_I16X8_U,
|
||||
0x80 = .I16X8_ABS,
|
||||
0x81 = .I16X8_NEG,
|
||||
0x82 = .I16X8_Q15MULR_SAT_S,
|
||||
0x83 = .I16X8_ALL_TRUE,
|
||||
0x84 = .I16X8_BITMASK,
|
||||
0x85 = .I16X8_NARROW_I32X4_S,
|
||||
0x86 = .I16X8_NARROW_I32X4_U,
|
||||
0x87 = .I16X8_EXTEND_LOW_I8X16_S,
|
||||
0x88 = .I16X8_EXTEND_HIGH_I8X16_S,
|
||||
0x89 = .I16X8_EXTEND_LOW_I8X16_U,
|
||||
0x8A = .I16X8_EXTEND_HIGH_I8X16_U,
|
||||
0x8B = .I16X8_SHL,
|
||||
0x8C = .I16X8_SHR_S,
|
||||
0x8D = .I16X8_SHR_U,
|
||||
0x8E = .I16X8_ADD,
|
||||
0x8F = .I16X8_ADD_SAT_S,
|
||||
0x90 = .I16X8_ADD_SAT_U,
|
||||
0x91 = .I16X8_SUB,
|
||||
0x92 = .I16X8_SUB_SAT_S,
|
||||
0x93 = .I16X8_SUB_SAT_U,
|
||||
0x94 = .F64X2_NEAREST,
|
||||
0x95 = .I16X8_MUL,
|
||||
0x96 = .I16X8_MIN_S,
|
||||
0x97 = .I16X8_MIN_U,
|
||||
0x98 = .I16X8_MAX_S,
|
||||
0x99 = .I16X8_MAX_U,
|
||||
0x9B = .I16X8_AVGR_U,
|
||||
0x9C = .I16X8_EXTMUL_LOW_I8X16_S,
|
||||
0x9D = .I16X8_EXTMUL_HIGH_I8X16_S,
|
||||
0x9E = .I16X8_EXTMUL_LOW_I8X16_U,
|
||||
0x9F = .I16X8_EXTMUL_HIGH_I8X16_U,
|
||||
0xA0 = .I32X4_ABS,
|
||||
0xA1 = .I32X4_NEG,
|
||||
0xA3 = .I32X4_ALL_TRUE,
|
||||
0xA4 = .I32X4_BITMASK,
|
||||
0xA7 = .I32X4_EXTEND_LOW_I16X8_S,
|
||||
0xA8 = .I32X4_EXTEND_HIGH_I16X8_S,
|
||||
0xA9 = .I32X4_EXTEND_LOW_I16X8_U,
|
||||
0xAA = .I32X4_EXTEND_HIGH_I16X8_U,
|
||||
0xAB = .I32X4_SHL,
|
||||
0xAC = .I32X4_SHR_S,
|
||||
0xAD = .I32X4_SHR_U,
|
||||
0xAE = .I32X4_ADD,
|
||||
0xB1 = .I32X4_SUB,
|
||||
0xB5 = .I32X4_MUL,
|
||||
0xB6 = .I32X4_MIN_S,
|
||||
0xB7 = .I32X4_MIN_U,
|
||||
0xB8 = .I32X4_MAX_S,
|
||||
0xB9 = .I32X4_MAX_U,
|
||||
0xBA = .I32X4_DOT_I16X8_S,
|
||||
0xBC = .I32X4_EXTMUL_LOW_I16X8_S,
|
||||
0xBD = .I32X4_EXTMUL_HIGH_I16X8_S,
|
||||
0xBE = .I32X4_EXTMUL_LOW_I16X8_U,
|
||||
0xBF = .I32X4_EXTMUL_HIGH_I16X8_U,
|
||||
0xC0 = .I64X2_ABS,
|
||||
0xC1 = .I64X2_NEG,
|
||||
0xC3 = .I64X2_ALL_TRUE,
|
||||
0xC4 = .I64X2_BITMASK,
|
||||
0xC7 = .I64X2_EXTEND_LOW_I32X4_S,
|
||||
0xC8 = .I64X2_EXTEND_HIGH_I32X4_S,
|
||||
0xC9 = .I64X2_EXTEND_LOW_I32X4_U,
|
||||
0xCA = .I64X2_EXTEND_HIGH_I32X4_U,
|
||||
0xCB = .I64X2_SHL,
|
||||
0xCC = .I64X2_SHR_S,
|
||||
0xCD = .I64X2_SHR_U,
|
||||
0xCE = .I64X2_ADD,
|
||||
0xD1 = .I64X2_SUB,
|
||||
0xD5 = .I64X2_MUL,
|
||||
0xD6 = .I64X2_EQ,
|
||||
0xD7 = .I64X2_NE,
|
||||
0xD8 = .I64X2_LT_S,
|
||||
0xD9 = .I64X2_GT_S,
|
||||
0xDA = .I64X2_LE_S,
|
||||
0xDB = .I64X2_GE_S,
|
||||
0xDC = .I64X2_EXTMUL_LOW_I32X4_S,
|
||||
0xDD = .I64X2_EXTMUL_HIGH_I32X4_S,
|
||||
0xDE = .I64X2_EXTMUL_LOW_I32X4_U,
|
||||
0xDF = .I64X2_EXTMUL_HIGH_I32X4_U,
|
||||
0xE0 = .F32X4_ABS,
|
||||
0xE1 = .F32X4_NEG,
|
||||
0xE3 = .F32X4_SQRT,
|
||||
0xE4 = .F32X4_ADD,
|
||||
0xE5 = .F32X4_SUB,
|
||||
0xE6 = .F32X4_MUL,
|
||||
0xE7 = .F32X4_DIV,
|
||||
0xE8 = .F32X4_MIN,
|
||||
0xE9 = .F32X4_MAX,
|
||||
0xEA = .F32X4_PMIN,
|
||||
0xEB = .F32X4_PMAX,
|
||||
0xEC = .F64X2_ABS,
|
||||
0xED = .F64X2_NEG,
|
||||
0xEF = .F64X2_SQRT,
|
||||
0xF0 = .F64X2_ADD,
|
||||
0xF1 = .F64X2_SUB,
|
||||
0xF2 = .F64X2_MUL,
|
||||
0xF3 = .F64X2_DIV,
|
||||
0xF4 = .F64X2_MIN,
|
||||
0xF5 = .F64X2_MAX,
|
||||
0xF6 = .F64X2_PMIN,
|
||||
0xF7 = .F64X2_PMAX,
|
||||
0xF8 = .I32X4_TRUNC_SAT_F32X4_S,
|
||||
0xF9 = .I32X4_TRUNC_SAT_F32X4_U,
|
||||
0xFA = .F32X4_CONVERT_I32X4_S,
|
||||
0xFB = .F32X4_CONVERT_I32X4_U,
|
||||
0xFC = .I32X4_TRUNC_SAT_F64X2_S_ZERO,
|
||||
0xFD = .I32X4_TRUNC_SAT_F64X2_U_ZERO,
|
||||
0xFE = .F64X2_CONVERT_LOW_I32X4_S,
|
||||
0xFF = .F64X2_CONVERT_LOW_I32X4_U,
|
||||
0x100 = .I8X16_RELAXED_SWIZZLE,
|
||||
0x101 = .I32X4_RELAXED_TRUNC_F32X4_S,
|
||||
0x102 = .I32X4_RELAXED_TRUNC_F32X4_U,
|
||||
0x103 = .I32X4_RELAXED_TRUNC_F64X2_S_ZERO,
|
||||
0x104 = .I32X4_RELAXED_TRUNC_F64X2_U_ZERO,
|
||||
0x105 = .F32X4_RELAXED_MADD,
|
||||
0x106 = .F32X4_RELAXED_NMADD,
|
||||
0x107 = .F64X2_RELAXED_MADD,
|
||||
0x108 = .F64X2_RELAXED_NMADD,
|
||||
0x109 = .I8X16_RELAXED_LANESELECT,
|
||||
0x10A = .I16X8_RELAXED_LANESELECT,
|
||||
0x10B = .I32X4_RELAXED_LANESELECT,
|
||||
0x10C = .I64X2_RELAXED_LANESELECT,
|
||||
0x10D = .F32X4_RELAXED_MIN,
|
||||
0x10E = .F32X4_RELAXED_MAX,
|
||||
0x10F = .F64X2_RELAXED_MIN,
|
||||
0x110 = .F64X2_RELAXED_MAX,
|
||||
0x111 = .I16X8_RELAXED_Q15MULR_S,
|
||||
0x112 = .I16X8_RELAXED_DOT_I8X16_I7X16_S,
|
||||
0x113 = .I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
DECODE_ATOMIC := [DECODE_ATOMIC_COUNT]Mnemonic{
|
||||
0x00 = .MEMORY_ATOMIC_NOTIFY,
|
||||
0x01 = .MEMORY_ATOMIC_WAIT32,
|
||||
0x02 = .MEMORY_ATOMIC_WAIT64,
|
||||
0x03 = .ATOMIC_FENCE,
|
||||
0x10 = .I32_ATOMIC_LOAD,
|
||||
0x11 = .I64_ATOMIC_LOAD,
|
||||
0x12 = .I32_ATOMIC_LOAD8_U,
|
||||
0x13 = .I32_ATOMIC_LOAD16_U,
|
||||
0x14 = .I64_ATOMIC_LOAD8_U,
|
||||
0x15 = .I64_ATOMIC_LOAD16_U,
|
||||
0x16 = .I64_ATOMIC_LOAD32_U,
|
||||
0x17 = .I32_ATOMIC_STORE,
|
||||
0x18 = .I64_ATOMIC_STORE,
|
||||
0x19 = .I32_ATOMIC_STORE8,
|
||||
0x1A = .I32_ATOMIC_STORE16,
|
||||
0x1B = .I64_ATOMIC_STORE8,
|
||||
0x1C = .I64_ATOMIC_STORE16,
|
||||
0x1D = .I64_ATOMIC_STORE32,
|
||||
0x1E = .I32_ATOMIC_RMW_ADD,
|
||||
0x1F = .I64_ATOMIC_RMW_ADD,
|
||||
0x20 = .I32_ATOMIC_RMW8_ADD_U,
|
||||
0x21 = .I32_ATOMIC_RMW16_ADD_U,
|
||||
0x22 = .I64_ATOMIC_RMW8_ADD_U,
|
||||
0x23 = .I64_ATOMIC_RMW16_ADD_U,
|
||||
0x24 = .I64_ATOMIC_RMW32_ADD_U,
|
||||
0x25 = .I32_ATOMIC_RMW_SUB,
|
||||
0x26 = .I64_ATOMIC_RMW_SUB,
|
||||
0x27 = .I32_ATOMIC_RMW8_SUB_U,
|
||||
0x28 = .I32_ATOMIC_RMW16_SUB_U,
|
||||
0x29 = .I64_ATOMIC_RMW8_SUB_U,
|
||||
0x2A = .I64_ATOMIC_RMW16_SUB_U,
|
||||
0x2B = .I64_ATOMIC_RMW32_SUB_U,
|
||||
0x2C = .I32_ATOMIC_RMW_AND,
|
||||
0x2D = .I64_ATOMIC_RMW_AND,
|
||||
0x2E = .I32_ATOMIC_RMW8_AND_U,
|
||||
0x2F = .I32_ATOMIC_RMW16_AND_U,
|
||||
0x30 = .I64_ATOMIC_RMW8_AND_U,
|
||||
0x31 = .I64_ATOMIC_RMW16_AND_U,
|
||||
0x32 = .I64_ATOMIC_RMW32_AND_U,
|
||||
0x33 = .I32_ATOMIC_RMW_OR,
|
||||
0x34 = .I64_ATOMIC_RMW_OR,
|
||||
0x35 = .I32_ATOMIC_RMW8_OR_U,
|
||||
0x36 = .I32_ATOMIC_RMW16_OR_U,
|
||||
0x37 = .I64_ATOMIC_RMW8_OR_U,
|
||||
0x38 = .I64_ATOMIC_RMW16_OR_U,
|
||||
0x39 = .I64_ATOMIC_RMW32_OR_U,
|
||||
0x3A = .I32_ATOMIC_RMW_XOR,
|
||||
0x3B = .I64_ATOMIC_RMW_XOR,
|
||||
0x3C = .I32_ATOMIC_RMW8_XOR_U,
|
||||
0x3D = .I32_ATOMIC_RMW16_XOR_U,
|
||||
0x3E = .I64_ATOMIC_RMW8_XOR_U,
|
||||
0x3F = .I64_ATOMIC_RMW16_XOR_U,
|
||||
0x40 = .I64_ATOMIC_RMW32_XOR_U,
|
||||
0x41 = .I32_ATOMIC_RMW_XCHG,
|
||||
0x42 = .I64_ATOMIC_RMW_XCHG,
|
||||
0x43 = .I32_ATOMIC_RMW8_XCHG_U,
|
||||
0x44 = .I32_ATOMIC_RMW16_XCHG_U,
|
||||
0x45 = .I64_ATOMIC_RMW8_XCHG_U,
|
||||
0x46 = .I64_ATOMIC_RMW16_XCHG_U,
|
||||
0x47 = .I64_ATOMIC_RMW32_XCHG_U,
|
||||
0x48 = .I32_ATOMIC_RMW_CMPXCHG,
|
||||
0x49 = .I64_ATOMIC_RMW_CMPXCHG,
|
||||
0x4A = .I32_ATOMIC_RMW8_CMPXCHG_U,
|
||||
0x4B = .I32_ATOMIC_RMW16_CMPXCHG_U,
|
||||
0x4C = .I64_ATOMIC_RMW8_CMPXCHG_U,
|
||||
0x4D = .I64_ATOMIC_RMW16_CMPXCHG_U,
|
||||
0x4E = .I64_ATOMIC_RMW32_CMPXCHG_U,
|
||||
}
|
||||
213
core/rexcode/wasm/encoder.odin
Normal file
213
core/rexcode/wasm/encoder.odin
Normal file
@@ -0,0 +1,213 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
import "core:math/bits"
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly ENCODER
|
||||
// =============================================================================
|
||||
//
|
||||
// Variable-length, byte-oriented, LEB128-heavy. Because LEB fields are not a
|
||||
// fixed width, encoding is sequential: a single forward pass writes each
|
||||
// instruction's opcode (a byte, or a prefix byte plus an unsigned-LEB
|
||||
// sub-opcode) followed by its immediates, advancing a byte cursor.
|
||||
//
|
||||
// WASM has no PC-relative branches (control flow uses structured label
|
||||
// depths), so there is no second resolution pass and no rewrite of
|
||||
// `label_defs`: those parameters are part of the universal signature but are
|
||||
// inert here. Relocations *are* produced -- for symbolic index references
|
||||
// (see op_label) -- and returned for a linker to patch; symbolic indices are
|
||||
// laid down as fixed-width 5-byte LEB placeholders so the patched value fits.
|
||||
|
||||
MAX_OPCODE_SIZE :: 3 // prefix byte + two-byte unsigned-LEB sub-opcode (SIMD reaches 0x113)
|
||||
|
||||
@(require_results)
|
||||
encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int {
|
||||
// Worst case per instruction without a br_table: a 3-byte opcode plus the
|
||||
// largest single immediate, which is v128.const's 16 raw bytes (a memarg+
|
||||
// lane pair is smaller). br_table is unbounded in its target count;
|
||||
// callers encoding tables should size from the target totals.
|
||||
return n * 24
|
||||
}
|
||||
@(require_results)
|
||||
encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int {
|
||||
return n
|
||||
}
|
||||
|
||||
encode :: proc(
|
||||
instructions: []Instruction,
|
||||
label_defs: []Label_Definition,
|
||||
code: []u8,
|
||||
relocs: ^[dynamic]Relocation,
|
||||
errors: ^[dynamic]Error,
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
for &inst, i in instructions {
|
||||
n := encode_one(&inst, byte_count, u16(i), code, relocs, errors) or_return
|
||||
inst.length = u8(min(n, 255))
|
||||
byte_count += n
|
||||
}
|
||||
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
encode_one :: proc(
|
||||
inst: ^Instruction,
|
||||
pc: u32,
|
||||
inst_idx: u16,
|
||||
code: []u8,
|
||||
relocs: ^[dynamic]Relocation,
|
||||
errors: ^[dynamic]Error,
|
||||
) -> (size: u32, ok: bool) {
|
||||
if inst.mnemonic == .INVALID {
|
||||
append(errors, Error{inst_idx = u32(inst_idx), code = .INVALID_MNEMONIC})
|
||||
return
|
||||
}
|
||||
form := encoding_form(inst.mnemonic)
|
||||
|
||||
need := encoded_size(inst, form)
|
||||
if pc + need > u32(len(code)) {
|
||||
append(errors, Error{inst_idx = u32(inst_idx), code = .BUFFER_OVERFLOW})
|
||||
return
|
||||
}
|
||||
|
||||
off := pc
|
||||
|
||||
// Opcode (and prefix sub-opcode).
|
||||
if form.prefix == PREFIX_NONE {
|
||||
code[off] = u8(form.opcode)
|
||||
off += 1
|
||||
} else {
|
||||
code[off] = form.prefix
|
||||
off += 1
|
||||
write_uleb(code, &off, u64(form.opcode))
|
||||
}
|
||||
|
||||
// Immediates, walked in declaration order with an operand cursor.
|
||||
opi := 0
|
||||
for k in form.imm {
|
||||
switch k {
|
||||
case .NONE:
|
||||
// nothing
|
||||
case .BLOCKTYPE, .I32, .I64:
|
||||
write_sleb(code, &off, inst.ops[opi].immediate)
|
||||
opi += 1
|
||||
case .F32:
|
||||
write_u32_block(code, &off, u32(inst.ops[opi].immediate))
|
||||
opi += 1
|
||||
case .F64:
|
||||
write_u64_block(code, &off, u64(inst.ops[opi].immediate))
|
||||
opi += 1
|
||||
case .IDX:
|
||||
op := &inst.ops[opi]
|
||||
if op.flags.symbolic {
|
||||
append(relocs, Relocation{
|
||||
offset = off, label_id = op.index, addend = 0,
|
||||
type = reloc_type_for(op.idx_kind), size = 5, inst_idx = inst_idx,
|
||||
})
|
||||
write_uleb_padded5(code, &off, u64(op.index))
|
||||
} else {
|
||||
write_uleb(code, &off, u64(op.index))
|
||||
}
|
||||
opi += 1
|
||||
case .MEMARG:
|
||||
ma := inst.ops[opi].memarg
|
||||
// TODO(bill): is this correct because the spec says otherwise but the binary formats look like it's log2
|
||||
align := bits.log2(u64(ma.align))
|
||||
|
||||
write_uleb(code, &off, align)
|
||||
write_uleb(code, &off, u64(ma.offset))
|
||||
opi += 1
|
||||
case .REFTYPE:
|
||||
code[off] = u8(inst.ops[opi].immediate)
|
||||
off += 1
|
||||
opi += 1
|
||||
case .BR_TABLE:
|
||||
write_uleb(code, &off, u64(len(inst.targets)))
|
||||
for t in inst.targets {
|
||||
write_uleb(code, &off, u64(t))
|
||||
}
|
||||
write_uleb(code, &off, u64(inst.ops[opi].index)) // default depth
|
||||
opi += 1
|
||||
case .ZERO_BYTE:
|
||||
code[off] = 0x00
|
||||
off += 1
|
||||
case .LANE:
|
||||
code[off] = u8(inst.ops[opi].immediate)
|
||||
off += 1
|
||||
opi += 1
|
||||
case .LANES16:
|
||||
for bb in inst.bytes {
|
||||
code[off] = bb
|
||||
off += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return off - pc, true
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
encoded_size :: proc(inst: ^Instruction, form: ^Encoding) -> u32 {
|
||||
size: u32 = 1
|
||||
if form.prefix != PREFIX_NONE {
|
||||
size += uleb_size(u64(form.opcode))
|
||||
}
|
||||
opi := 0
|
||||
for k in form.imm {
|
||||
switch k {
|
||||
case .NONE:
|
||||
case .BLOCKTYPE, .I32, .I64:
|
||||
size += sleb_size(inst.ops[opi].immediate)
|
||||
opi += 1
|
||||
case .F32:
|
||||
size += 4
|
||||
opi += 1
|
||||
case .F64:
|
||||
size += 8
|
||||
opi += 1
|
||||
case .IDX:
|
||||
op := &inst.ops[opi]
|
||||
size += op.flags.symbolic ? 5 : uleb_size(u64(op.index))
|
||||
opi += 1
|
||||
case .MEMARG:
|
||||
ma := inst.ops[opi].memarg
|
||||
size += uleb_size(u64(ma.align)) + uleb_size(u64(ma.offset))
|
||||
opi += 1
|
||||
case .REFTYPE:
|
||||
size += 1
|
||||
opi += 1
|
||||
case .BR_TABLE:
|
||||
size += uleb_size(u64(len(inst.targets)))
|
||||
for t in inst.targets {
|
||||
size += uleb_size(u64(t))
|
||||
}
|
||||
size += uleb_size(u64(inst.ops[opi].index))
|
||||
opi += 1
|
||||
case .ZERO_BYTE:
|
||||
size += 1
|
||||
case .LANE:
|
||||
size += 1
|
||||
opi += 1
|
||||
case .LANES16:
|
||||
size += 16
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
reloc_type_for :: #force_inline proc "contextless" (k: Index_Kind) -> Relocation_Type {
|
||||
#partial switch k {
|
||||
case .FUNC: return .FUNCTION_INDEX_LEB
|
||||
case .TYPE: return .TYPE_INDEX_LEB
|
||||
case .GLOBAL: return .GLOBAL_INDEX_LEB
|
||||
case .TABLE: return .TABLE_NUMBER_LEB
|
||||
}
|
||||
return .FUNCTION_INDEX_LEB
|
||||
}
|
||||
502
core/rexcode/wasm/encoding_table.odin
Normal file
502
core/rexcode/wasm/encoding_table.odin
Normal file
@@ -0,0 +1,502 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly ENCODING TABLE (single source of truth)
|
||||
// =============================================================================
|
||||
//
|
||||
// One form per mnemonic, indexed directly by the Mnemonic enum. Each entry
|
||||
// records the prefix byte, the (sub-)opcode, and the immediate layout. The
|
||||
// decode dispatch in decoding_tables.odin is derived from this table at
|
||||
// package init, so opcode bytes are written down exactly once.
|
||||
//
|
||||
// The `mnemonic` field of each Encoding is left at INVALID here: the table
|
||||
// index already identifies the mnemonic and the encoder never reads it back.
|
||||
|
||||
@(private="file") CTRL :: Encoding_Flags{control = true}
|
||||
@(private="file") MEM :: Encoding_Flags{memory = true}
|
||||
|
||||
@(rodata)
|
||||
ENCODING_TABLE := [Mnemonic]Encoding{
|
||||
.INVALID = {},
|
||||
|
||||
// ------------------------------------------------------------------ control
|
||||
.UNREACHABLE = {prefix = PREFIX_NONE, opcode = 0x00, flags = CTRL},
|
||||
.NOP = {prefix = PREFIX_NONE, opcode = 0x01},
|
||||
.BLOCK = {prefix = PREFIX_NONE, opcode = 0x02, imm = {.BLOCKTYPE, .NONE}, flags = CTRL},
|
||||
.LOOP = {prefix = PREFIX_NONE, opcode = 0x03, imm = {.BLOCKTYPE, .NONE}, flags = CTRL},
|
||||
.IF = {prefix = PREFIX_NONE, opcode = 0x04, imm = {.BLOCKTYPE, .NONE}, flags = CTRL},
|
||||
.ELSE = {prefix = PREFIX_NONE, opcode = 0x05, flags = CTRL},
|
||||
.END = {prefix = PREFIX_NONE, opcode = 0x0B, flags = CTRL},
|
||||
.BR = {prefix = PREFIX_NONE, opcode = 0x0C, imm = {.IDX, .NONE}, flags = CTRL},
|
||||
.BR_IF = {prefix = PREFIX_NONE, opcode = 0x0D, imm = {.IDX, .NONE}, flags = CTRL},
|
||||
.BR_TABLE = {prefix = PREFIX_NONE, opcode = 0x0E, imm = {.BR_TABLE, .NONE}, flags = CTRL},
|
||||
.RETURN = {prefix = PREFIX_NONE, opcode = 0x0F, flags = CTRL},
|
||||
.CALL = {prefix = PREFIX_NONE, opcode = 0x10, imm = {.IDX, .NONE}, flags = CTRL},
|
||||
.CALL_INDIRECT = {prefix = PREFIX_NONE, opcode = 0x11, imm = {.IDX, .IDX}, flags = CTRL},
|
||||
|
||||
// -------------------------------------------------------------- parametric
|
||||
.DROP = {prefix = PREFIX_NONE, opcode = 0x1A},
|
||||
.SELECT = {prefix = PREFIX_NONE, opcode = 0x1B},
|
||||
|
||||
// ---------------------------------------------------------------- variable
|
||||
.LOCAL_GET = {prefix = PREFIX_NONE, opcode = 0x20, imm = {.IDX, .NONE}},
|
||||
.LOCAL_SET = {prefix = PREFIX_NONE, opcode = 0x21, imm = {.IDX, .NONE}},
|
||||
.LOCAL_TEE = {prefix = PREFIX_NONE, opcode = 0x22, imm = {.IDX, .NONE}},
|
||||
.GLOBAL_GET = {prefix = PREFIX_NONE, opcode = 0x23, imm = {.IDX, .NONE}},
|
||||
.GLOBAL_SET = {prefix = PREFIX_NONE, opcode = 0x24, imm = {.IDX, .NONE}},
|
||||
|
||||
// ------------------------------------------------------------------- memory
|
||||
.I32_LOAD = {prefix = PREFIX_NONE, opcode = 0x28, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD = {prefix = PREFIX_NONE, opcode = 0x29, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.F32_LOAD = {prefix = PREFIX_NONE, opcode = 0x2A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.F64_LOAD = {prefix = PREFIX_NONE, opcode = 0x2B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_LOAD8_S = {prefix = PREFIX_NONE, opcode = 0x2C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_LOAD8_U = {prefix = PREFIX_NONE, opcode = 0x2D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_LOAD16_S = {prefix = PREFIX_NONE, opcode = 0x2E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_LOAD16_U = {prefix = PREFIX_NONE, opcode = 0x2F, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD8_S = {prefix = PREFIX_NONE, opcode = 0x30, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD8_U = {prefix = PREFIX_NONE, opcode = 0x31, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD16_S = {prefix = PREFIX_NONE, opcode = 0x32, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD16_U = {prefix = PREFIX_NONE, opcode = 0x33, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD32_S = {prefix = PREFIX_NONE, opcode = 0x34, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_LOAD32_U = {prefix = PREFIX_NONE, opcode = 0x35, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_STORE = {prefix = PREFIX_NONE, opcode = 0x36, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_STORE = {prefix = PREFIX_NONE, opcode = 0x37, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.F32_STORE = {prefix = PREFIX_NONE, opcode = 0x38, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.F64_STORE = {prefix = PREFIX_NONE, opcode = 0x39, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_STORE8 = {prefix = PREFIX_NONE, opcode = 0x3A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_STORE16 = {prefix = PREFIX_NONE, opcode = 0x3B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_STORE8 = {prefix = PREFIX_NONE, opcode = 0x3C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_STORE16 = {prefix = PREFIX_NONE, opcode = 0x3D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_STORE32 = {prefix = PREFIX_NONE, opcode = 0x3E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.MEMORY_SIZE = {prefix = PREFIX_NONE, opcode = 0x3F, imm = {.ZERO_BYTE, .NONE}, flags = MEM},
|
||||
.MEMORY_GROW = {prefix = PREFIX_NONE, opcode = 0x40, imm = {.ZERO_BYTE, .NONE}, flags = MEM},
|
||||
|
||||
// ----------------------------------------------------------------- numeric
|
||||
.I32_CONST = {prefix = PREFIX_NONE, opcode = 0x41, imm = {.I32, .NONE}},
|
||||
.I64_CONST = {prefix = PREFIX_NONE, opcode = 0x42, imm = {.I64, .NONE}},
|
||||
.F32_CONST = {prefix = PREFIX_NONE, opcode = 0x43, imm = {.F32, .NONE}},
|
||||
.F64_CONST = {prefix = PREFIX_NONE, opcode = 0x44, imm = {.F64, .NONE}},
|
||||
|
||||
.I32_EQZ = {prefix = PREFIX_NONE, opcode = 0x45}, .I32_EQ = {prefix = PREFIX_NONE, opcode = 0x46}, .I32_NE = {prefix = PREFIX_NONE, opcode = 0x47},
|
||||
.I32_LT_S = {prefix = PREFIX_NONE, opcode = 0x48}, .I32_LT_U = {prefix = PREFIX_NONE, opcode = 0x49},
|
||||
.I32_GT_S = {prefix = PREFIX_NONE, opcode = 0x4A}, .I32_GT_U = {prefix = PREFIX_NONE, opcode = 0x4B},
|
||||
.I32_LE_S = {prefix = PREFIX_NONE, opcode = 0x4C}, .I32_LE_U = {prefix = PREFIX_NONE, opcode = 0x4D},
|
||||
.I32_GE_S = {prefix = PREFIX_NONE, opcode = 0x4E}, .I32_GE_U = {prefix = PREFIX_NONE, opcode = 0x4F},
|
||||
|
||||
.I64_EQZ = {prefix = PREFIX_NONE, opcode = 0x50}, .I64_EQ = {prefix = PREFIX_NONE, opcode = 0x51}, .I64_NE = {prefix = PREFIX_NONE, opcode = 0x52},
|
||||
.I64_LT_S = {prefix = PREFIX_NONE, opcode = 0x53}, .I64_LT_U = {prefix = PREFIX_NONE, opcode = 0x54},
|
||||
.I64_GT_S = {prefix = PREFIX_NONE, opcode = 0x55}, .I64_GT_U = {prefix = PREFIX_NONE, opcode = 0x56},
|
||||
.I64_LE_S = {prefix = PREFIX_NONE, opcode = 0x57}, .I64_LE_U = {prefix = PREFIX_NONE, opcode = 0x58},
|
||||
.I64_GE_S = {prefix = PREFIX_NONE, opcode = 0x59}, .I64_GE_U = {prefix = PREFIX_NONE, opcode = 0x5A},
|
||||
|
||||
.F32_EQ = {prefix = PREFIX_NONE, opcode = 0x5B}, .F32_NE = {prefix = PREFIX_NONE, opcode = 0x5C},
|
||||
.F32_LT = {prefix = PREFIX_NONE, opcode = 0x5D}, .F32_GT = {prefix = PREFIX_NONE, opcode = 0x5E},
|
||||
.F32_LE = {prefix = PREFIX_NONE, opcode = 0x5F}, .F32_GE = {prefix = PREFIX_NONE, opcode = 0x60},
|
||||
|
||||
.F64_EQ = {prefix = PREFIX_NONE, opcode = 0x61}, .F64_NE = {prefix = PREFIX_NONE, opcode = 0x62},
|
||||
.F64_LT = {prefix = PREFIX_NONE, opcode = 0x63}, .F64_GT = {prefix = PREFIX_NONE, opcode = 0x64},
|
||||
.F64_LE = {prefix = PREFIX_NONE, opcode = 0x65}, .F64_GE = {prefix = PREFIX_NONE, opcode = 0x66},
|
||||
|
||||
.I32_CLZ = {prefix = PREFIX_NONE, opcode = 0x67}, .I32_CTZ = {prefix = PREFIX_NONE, opcode = 0x68}, .I32_POPCNT = {prefix = PREFIX_NONE, opcode = 0x69},
|
||||
.I32_ADD = {prefix = PREFIX_NONE, opcode = 0x6A}, .I32_SUB = {prefix = PREFIX_NONE, opcode = 0x6B}, .I32_MUL = {prefix = PREFIX_NONE, opcode = 0x6C},
|
||||
.I32_DIV_S = {prefix = PREFIX_NONE, opcode = 0x6D}, .I32_DIV_U = {prefix = PREFIX_NONE, opcode = 0x6E},
|
||||
.I32_REM_S = {prefix = PREFIX_NONE, opcode = 0x6F}, .I32_REM_U = {prefix = PREFIX_NONE, opcode = 0x70},
|
||||
.I32_AND = {prefix = PREFIX_NONE, opcode = 0x71}, .I32_OR = {prefix = PREFIX_NONE, opcode = 0x72}, .I32_XOR = {prefix = PREFIX_NONE, opcode = 0x73},
|
||||
.I32_SHL = {prefix = PREFIX_NONE, opcode = 0x74}, .I32_SHR_S = {prefix = PREFIX_NONE, opcode = 0x75}, .I32_SHR_U = {prefix = PREFIX_NONE, opcode = 0x76},
|
||||
.I32_ROTL = {prefix = PREFIX_NONE, opcode = 0x77}, .I32_ROTR = {prefix = PREFIX_NONE, opcode = 0x78},
|
||||
|
||||
.I64_CLZ = {prefix = PREFIX_NONE, opcode = 0x79}, .I64_CTZ = {prefix = PREFIX_NONE, opcode = 0x7A}, .I64_POPCNT = {prefix = PREFIX_NONE, opcode = 0x7B},
|
||||
.I64_ADD = {prefix = PREFIX_NONE, opcode = 0x7C}, .I64_SUB = {prefix = PREFIX_NONE, opcode = 0x7D}, .I64_MUL = {prefix = PREFIX_NONE, opcode = 0x7E},
|
||||
.I64_DIV_S = {prefix = PREFIX_NONE, opcode = 0x7F}, .I64_DIV_U = {prefix = PREFIX_NONE, opcode = 0x80},
|
||||
.I64_REM_S = {prefix = PREFIX_NONE, opcode = 0x81}, .I64_REM_U = {prefix = PREFIX_NONE, opcode = 0x82},
|
||||
.I64_AND = {prefix = PREFIX_NONE, opcode = 0x83}, .I64_OR = {prefix = PREFIX_NONE, opcode = 0x84}, .I64_XOR = {prefix = PREFIX_NONE, opcode = 0x85},
|
||||
.I64_SHL = {prefix = PREFIX_NONE, opcode = 0x86}, .I64_SHR_S = {prefix = PREFIX_NONE, opcode = 0x87}, .I64_SHR_U = {prefix = PREFIX_NONE, opcode = 0x88},
|
||||
.I64_ROTL = {prefix = PREFIX_NONE, opcode = 0x89}, .I64_ROTR = {prefix = PREFIX_NONE, opcode = 0x8A},
|
||||
|
||||
.F32_ABS = {prefix = PREFIX_NONE, opcode = 0x8B}, .F32_NEG = {prefix = PREFIX_NONE, opcode = 0x8C}, .F32_CEIL = {prefix = PREFIX_NONE, opcode = 0x8D},
|
||||
.F32_FLOOR = {prefix = PREFIX_NONE, opcode = 0x8E}, .F32_TRUNC = {prefix = PREFIX_NONE, opcode = 0x8F}, .F32_NEAREST = {prefix = PREFIX_NONE, opcode = 0x90},
|
||||
.F32_SQRT = {prefix = PREFIX_NONE, opcode = 0x91}, .F32_ADD = {prefix = PREFIX_NONE, opcode = 0x92}, .F32_SUB = {prefix = PREFIX_NONE, opcode = 0x93},
|
||||
.F32_MUL = {prefix = PREFIX_NONE, opcode = 0x94}, .F32_DIV = {prefix = PREFIX_NONE, opcode = 0x95}, .F32_MIN = {prefix = PREFIX_NONE, opcode = 0x96},
|
||||
.F32_MAX = {prefix = PREFIX_NONE, opcode = 0x97}, .F32_COPYSIGN = {prefix = PREFIX_NONE, opcode = 0x98},
|
||||
|
||||
.F64_ABS = {prefix = PREFIX_NONE, opcode = 0x99}, .F64_NEG = {prefix = PREFIX_NONE, opcode = 0x9A}, .F64_CEIL = {prefix = PREFIX_NONE, opcode = 0x9B},
|
||||
.F64_FLOOR = {prefix = PREFIX_NONE, opcode = 0x9C}, .F64_TRUNC = {prefix = PREFIX_NONE, opcode = 0x9D}, .F64_NEAREST = {prefix = PREFIX_NONE, opcode = 0x9E},
|
||||
.F64_SQRT = {prefix = PREFIX_NONE, opcode = 0x9F}, .F64_ADD = {prefix = PREFIX_NONE, opcode = 0xA0}, .F64_SUB = {prefix = PREFIX_NONE, opcode = 0xA1},
|
||||
.F64_MUL = {prefix = PREFIX_NONE, opcode = 0xA2}, .F64_DIV = {prefix = PREFIX_NONE, opcode = 0xA3}, .F64_MIN = {prefix = PREFIX_NONE, opcode = 0xA4},
|
||||
.F64_MAX = {prefix = PREFIX_NONE, opcode = 0xA5}, .F64_COPYSIGN = {prefix = PREFIX_NONE, opcode = 0xA6},
|
||||
|
||||
.I32_WRAP_I64 = {prefix = PREFIX_NONE, opcode = 0xA7},
|
||||
.I32_TRUNC_F32_S = {prefix = PREFIX_NONE, opcode = 0xA8}, .I32_TRUNC_F32_U = {prefix = PREFIX_NONE, opcode = 0xA9},
|
||||
.I32_TRUNC_F64_S = {prefix = PREFIX_NONE, opcode = 0xAA}, .I32_TRUNC_F64_U = {prefix = PREFIX_NONE, opcode = 0xAB},
|
||||
.I64_EXTEND_I32_S = {prefix = PREFIX_NONE, opcode = 0xAC}, .I64_EXTEND_I32_U = {prefix = PREFIX_NONE, opcode = 0xAD},
|
||||
.I64_TRUNC_F32_S = {prefix = PREFIX_NONE, opcode = 0xAE}, .I64_TRUNC_F32_U = {prefix = PREFIX_NONE, opcode = 0xAF},
|
||||
.I64_TRUNC_F64_S = {prefix = PREFIX_NONE, opcode = 0xB0}, .I64_TRUNC_F64_U = {prefix = PREFIX_NONE, opcode = 0xB1},
|
||||
.F32_CONVERT_I32_S = {prefix = PREFIX_NONE, opcode = 0xB2}, .F32_CONVERT_I32_U = {prefix = PREFIX_NONE, opcode = 0xB3},
|
||||
.F32_CONVERT_I64_S = {prefix = PREFIX_NONE, opcode = 0xB4}, .F32_CONVERT_I64_U = {prefix = PREFIX_NONE, opcode = 0xB5},
|
||||
.F32_DEMOTE_F64 = {prefix = PREFIX_NONE, opcode = 0xB6},
|
||||
.F64_CONVERT_I32_S = {prefix = PREFIX_NONE, opcode = 0xB7}, .F64_CONVERT_I32_U = {prefix = PREFIX_NONE, opcode = 0xB8},
|
||||
.F64_CONVERT_I64_S = {prefix = PREFIX_NONE, opcode = 0xB9}, .F64_CONVERT_I64_U = {prefix = PREFIX_NONE, opcode = 0xBA},
|
||||
.F64_PROMOTE_F32 = {prefix = PREFIX_NONE, opcode = 0xBB},
|
||||
.I32_REINTERPRET_F32 = {prefix = PREFIX_NONE, opcode = 0xBC}, .I64_REINTERPRET_F64 = {prefix = PREFIX_NONE, opcode = 0xBD},
|
||||
.F32_REINTERPRET_I32 = {prefix = PREFIX_NONE, opcode = 0xBE}, .F64_REINTERPRET_I64 = {prefix = PREFIX_NONE, opcode = 0xBF},
|
||||
|
||||
.I32_EXTEND8_S = {prefix = PREFIX_NONE, opcode = 0xC0}, .I32_EXTEND16_S = {prefix = PREFIX_NONE, opcode = 0xC1},
|
||||
.I64_EXTEND8_S = {prefix = PREFIX_NONE, opcode = 0xC2}, .I64_EXTEND16_S = {prefix = PREFIX_NONE, opcode = 0xC3}, .I64_EXTEND32_S = {prefix = PREFIX_NONE, opcode = 0xC4},
|
||||
|
||||
.REF_NULL = {prefix = PREFIX_NONE, opcode = 0xD0, imm = {.REFTYPE, .NONE}},
|
||||
.REF_IS_NULL = {prefix = PREFIX_NONE, opcode = 0xD1},
|
||||
.REF_FUNC = {prefix = PREFIX_NONE, opcode = 0xD2, imm = {.IDX, .NONE}},
|
||||
|
||||
// ------------------------------------------------------- 0xFC misc prefix
|
||||
.I32_TRUNC_SAT_F32_S = {prefix = PREFIX_MISC, opcode = 0}, .I32_TRUNC_SAT_F32_U = {prefix = PREFIX_MISC, opcode = 1},
|
||||
.I32_TRUNC_SAT_F64_S = {prefix = PREFIX_MISC, opcode = 2}, .I32_TRUNC_SAT_F64_U = {prefix = PREFIX_MISC, opcode = 3},
|
||||
.I64_TRUNC_SAT_F32_S = {prefix = PREFIX_MISC, opcode = 4}, .I64_TRUNC_SAT_F32_U = {prefix = PREFIX_MISC, opcode = 5},
|
||||
.I64_TRUNC_SAT_F64_S = {prefix = PREFIX_MISC, opcode = 6}, .I64_TRUNC_SAT_F64_U = {prefix = PREFIX_MISC, opcode = 7},
|
||||
.MEMORY_INIT = {prefix = PREFIX_MISC, opcode = 8, imm = {.IDX, .ZERO_BYTE}, flags = MEM},
|
||||
.DATA_DROP = {prefix = PREFIX_MISC, opcode = 9, imm = {.IDX, .NONE}},
|
||||
.MEMORY_COPY = {prefix = PREFIX_MISC, opcode = 10, imm = {.ZERO_BYTE, .ZERO_BYTE}, flags = MEM},
|
||||
.MEMORY_FILL = {prefix = PREFIX_MISC, opcode = 11, imm = {.ZERO_BYTE, .NONE}, flags = MEM},
|
||||
.TABLE_INIT = {prefix = PREFIX_MISC, opcode = 12, imm = {.IDX, .IDX}},
|
||||
.ELEM_DROP = {prefix = PREFIX_MISC, opcode = 13, imm = {.IDX, .NONE}},
|
||||
.TABLE_COPY = {prefix = PREFIX_MISC, opcode = 14, imm = {.IDX, .IDX}},
|
||||
.TABLE_GROW = {prefix = PREFIX_MISC, opcode = 15, imm = {.IDX, .NONE}},
|
||||
.TABLE_SIZE = {prefix = PREFIX_MISC, opcode = 16, imm = {.IDX, .NONE}},
|
||||
.TABLE_FILL = {prefix = PREFIX_MISC, opcode = 17, imm = {.IDX, .NONE}},
|
||||
|
||||
// ----------------------------------------------- 0xFD SIMD (v128) prefix
|
||||
.V128_LOAD = {prefix = PREFIX_SIMD, opcode = 0x00, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD8X8_S = {prefix = PREFIX_SIMD, opcode = 0x01, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD8X8_U = {prefix = PREFIX_SIMD, opcode = 0x02, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD16X4_S = {prefix = PREFIX_SIMD, opcode = 0x03, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD16X4_U = {prefix = PREFIX_SIMD, opcode = 0x04, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD32X2_S = {prefix = PREFIX_SIMD, opcode = 0x05, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD32X2_U = {prefix = PREFIX_SIMD, opcode = 0x06, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD8_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x07, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD16_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x08, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD32_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x09, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD64_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x0A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_STORE = {prefix = PREFIX_SIMD, opcode = 0x0B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_CONST = {prefix = PREFIX_SIMD, opcode = 0x0C, imm = {.LANES16, .NONE}},
|
||||
.I8X16_SHUFFLE = {prefix = PREFIX_SIMD, opcode = 0x0D, imm = {.LANES16, .NONE}},
|
||||
.I8X16_SWIZZLE = {prefix = PREFIX_SIMD, opcode = 0x0E},
|
||||
.I8X16_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x0F},
|
||||
.I16X8_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x10},
|
||||
.I32X4_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x11},
|
||||
.I64X2_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x12},
|
||||
.F32X4_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x13},
|
||||
.F64X2_SPLAT = {prefix = PREFIX_SIMD, opcode = 0x14},
|
||||
.I8X16_EXTRACT_LANE_S = {prefix = PREFIX_SIMD, opcode = 0x15, imm = {.LANE, .NONE}},
|
||||
.I8X16_EXTRACT_LANE_U = {prefix = PREFIX_SIMD, opcode = 0x16, imm = {.LANE, .NONE}},
|
||||
.I8X16_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x17, imm = {.LANE, .NONE}},
|
||||
.I16X8_EXTRACT_LANE_S = {prefix = PREFIX_SIMD, opcode = 0x18, imm = {.LANE, .NONE}},
|
||||
.I16X8_EXTRACT_LANE_U = {prefix = PREFIX_SIMD, opcode = 0x19, imm = {.LANE, .NONE}},
|
||||
.I16X8_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1A, imm = {.LANE, .NONE}},
|
||||
.I32X4_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1B, imm = {.LANE, .NONE}},
|
||||
.I32X4_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1C, imm = {.LANE, .NONE}},
|
||||
.I64X2_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1D, imm = {.LANE, .NONE}},
|
||||
.I64X2_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x1E, imm = {.LANE, .NONE}},
|
||||
.F32X4_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x1F, imm = {.LANE, .NONE}},
|
||||
.F32X4_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x20, imm = {.LANE, .NONE}},
|
||||
.F64X2_EXTRACT_LANE = {prefix = PREFIX_SIMD, opcode = 0x21, imm = {.LANE, .NONE}},
|
||||
.F64X2_REPLACE_LANE = {prefix = PREFIX_SIMD, opcode = 0x22, imm = {.LANE, .NONE}},
|
||||
.I8X16_EQ = {prefix = PREFIX_SIMD, opcode = 0x23},
|
||||
.I8X16_NE = {prefix = PREFIX_SIMD, opcode = 0x24},
|
||||
.I8X16_LT_S = {prefix = PREFIX_SIMD, opcode = 0x25},
|
||||
.I8X16_LT_U = {prefix = PREFIX_SIMD, opcode = 0x26},
|
||||
.I8X16_GT_S = {prefix = PREFIX_SIMD, opcode = 0x27},
|
||||
.I8X16_GT_U = {prefix = PREFIX_SIMD, opcode = 0x28},
|
||||
.I8X16_LE_S = {prefix = PREFIX_SIMD, opcode = 0x29},
|
||||
.I8X16_LE_U = {prefix = PREFIX_SIMD, opcode = 0x2A},
|
||||
.I8X16_GE_S = {prefix = PREFIX_SIMD, opcode = 0x2B},
|
||||
.I8X16_GE_U = {prefix = PREFIX_SIMD, opcode = 0x2C},
|
||||
.I16X8_EQ = {prefix = PREFIX_SIMD, opcode = 0x2D},
|
||||
.I16X8_NE = {prefix = PREFIX_SIMD, opcode = 0x2E},
|
||||
.I16X8_LT_S = {prefix = PREFIX_SIMD, opcode = 0x2F},
|
||||
.I16X8_LT_U = {prefix = PREFIX_SIMD, opcode = 0x30},
|
||||
.I16X8_GT_S = {prefix = PREFIX_SIMD, opcode = 0x31},
|
||||
.I16X8_GT_U = {prefix = PREFIX_SIMD, opcode = 0x32},
|
||||
.I16X8_LE_S = {prefix = PREFIX_SIMD, opcode = 0x33},
|
||||
.I16X8_LE_U = {prefix = PREFIX_SIMD, opcode = 0x34},
|
||||
.I16X8_GE_S = {prefix = PREFIX_SIMD, opcode = 0x35},
|
||||
.I16X8_GE_U = {prefix = PREFIX_SIMD, opcode = 0x36},
|
||||
.I32X4_EQ = {prefix = PREFIX_SIMD, opcode = 0x37},
|
||||
.I32X4_NE = {prefix = PREFIX_SIMD, opcode = 0x38},
|
||||
.I32X4_LT_S = {prefix = PREFIX_SIMD, opcode = 0x39},
|
||||
.I32X4_LT_U = {prefix = PREFIX_SIMD, opcode = 0x3A},
|
||||
.I32X4_GT_S = {prefix = PREFIX_SIMD, opcode = 0x3B},
|
||||
.I32X4_GT_U = {prefix = PREFIX_SIMD, opcode = 0x3C},
|
||||
.I32X4_LE_S = {prefix = PREFIX_SIMD, opcode = 0x3D},
|
||||
.I32X4_LE_U = {prefix = PREFIX_SIMD, opcode = 0x3E},
|
||||
.I32X4_GE_S = {prefix = PREFIX_SIMD, opcode = 0x3F},
|
||||
.I32X4_GE_U = {prefix = PREFIX_SIMD, opcode = 0x40},
|
||||
.F32X4_EQ = {prefix = PREFIX_SIMD, opcode = 0x41},
|
||||
.F32X4_NE = {prefix = PREFIX_SIMD, opcode = 0x42},
|
||||
.F32X4_LT = {prefix = PREFIX_SIMD, opcode = 0x43},
|
||||
.F32X4_GT = {prefix = PREFIX_SIMD, opcode = 0x44},
|
||||
.F32X4_LE = {prefix = PREFIX_SIMD, opcode = 0x45},
|
||||
.F32X4_GE = {prefix = PREFIX_SIMD, opcode = 0x46},
|
||||
.F64X2_EQ = {prefix = PREFIX_SIMD, opcode = 0x47},
|
||||
.F64X2_NE = {prefix = PREFIX_SIMD, opcode = 0x48},
|
||||
.F64X2_LT = {prefix = PREFIX_SIMD, opcode = 0x49},
|
||||
.F64X2_GT = {prefix = PREFIX_SIMD, opcode = 0x4A},
|
||||
.F64X2_LE = {prefix = PREFIX_SIMD, opcode = 0x4B},
|
||||
.F64X2_GE = {prefix = PREFIX_SIMD, opcode = 0x4C},
|
||||
.V128_NOT = {prefix = PREFIX_SIMD, opcode = 0x4D},
|
||||
.V128_AND = {prefix = PREFIX_SIMD, opcode = 0x4E},
|
||||
.V128_ANDNOT = {prefix = PREFIX_SIMD, opcode = 0x4F},
|
||||
.V128_OR = {prefix = PREFIX_SIMD, opcode = 0x50},
|
||||
.V128_XOR = {prefix = PREFIX_SIMD, opcode = 0x51},
|
||||
.V128_BITSELECT = {prefix = PREFIX_SIMD, opcode = 0x52},
|
||||
.V128_ANY_TRUE = {prefix = PREFIX_SIMD, opcode = 0x53},
|
||||
.V128_LOAD8_LANE = {prefix = PREFIX_SIMD, opcode = 0x54, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_LOAD16_LANE = {prefix = PREFIX_SIMD, opcode = 0x55, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_LOAD32_LANE = {prefix = PREFIX_SIMD, opcode = 0x56, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_LOAD64_LANE = {prefix = PREFIX_SIMD, opcode = 0x57, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_STORE8_LANE = {prefix = PREFIX_SIMD, opcode = 0x58, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_STORE16_LANE = {prefix = PREFIX_SIMD, opcode = 0x59, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_STORE32_LANE = {prefix = PREFIX_SIMD, opcode = 0x5A, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_STORE64_LANE = {prefix = PREFIX_SIMD, opcode = 0x5B, imm = {.MEMARG, .LANE}, flags = MEM},
|
||||
.V128_LOAD32_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.V128_LOAD64_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.F32X4_DEMOTE_F64X2_ZERO = {prefix = PREFIX_SIMD, opcode = 0x5E},
|
||||
.F64X2_PROMOTE_LOW_F32X4 = {prefix = PREFIX_SIMD, opcode = 0x5F},
|
||||
.I8X16_ABS = {prefix = PREFIX_SIMD, opcode = 0x60},
|
||||
.I8X16_NEG = {prefix = PREFIX_SIMD, opcode = 0x61},
|
||||
.I8X16_POPCNT = {prefix = PREFIX_SIMD, opcode = 0x62},
|
||||
.I8X16_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0x63},
|
||||
.I8X16_BITMASK = {prefix = PREFIX_SIMD, opcode = 0x64},
|
||||
.I8X16_NARROW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0x65},
|
||||
.I8X16_NARROW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0x66},
|
||||
.F32X4_CEIL = {prefix = PREFIX_SIMD, opcode = 0x67},
|
||||
.F32X4_FLOOR = {prefix = PREFIX_SIMD, opcode = 0x68},
|
||||
.F32X4_TRUNC = {prefix = PREFIX_SIMD, opcode = 0x69},
|
||||
.F32X4_NEAREST = {prefix = PREFIX_SIMD, opcode = 0x6A},
|
||||
.I8X16_SHL = {prefix = PREFIX_SIMD, opcode = 0x6B},
|
||||
.I8X16_SHR_S = {prefix = PREFIX_SIMD, opcode = 0x6C},
|
||||
.I8X16_SHR_U = {prefix = PREFIX_SIMD, opcode = 0x6D},
|
||||
.I8X16_ADD = {prefix = PREFIX_SIMD, opcode = 0x6E},
|
||||
.I8X16_ADD_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x6F},
|
||||
.I8X16_ADD_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x70},
|
||||
.I8X16_SUB = {prefix = PREFIX_SIMD, opcode = 0x71},
|
||||
.I8X16_SUB_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x72},
|
||||
.I8X16_SUB_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x73},
|
||||
.F64X2_CEIL = {prefix = PREFIX_SIMD, opcode = 0x74},
|
||||
.F64X2_FLOOR = {prefix = PREFIX_SIMD, opcode = 0x75},
|
||||
.I8X16_MIN_S = {prefix = PREFIX_SIMD, opcode = 0x76},
|
||||
.I8X16_MIN_U = {prefix = PREFIX_SIMD, opcode = 0x77},
|
||||
.I8X16_MAX_S = {prefix = PREFIX_SIMD, opcode = 0x78},
|
||||
.I8X16_MAX_U = {prefix = PREFIX_SIMD, opcode = 0x79},
|
||||
.F64X2_TRUNC = {prefix = PREFIX_SIMD, opcode = 0x7A},
|
||||
.I8X16_AVGR_U = {prefix = PREFIX_SIMD, opcode = 0x7B},
|
||||
.I16X8_EXTADD_PAIRWISE_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x7C},
|
||||
.I16X8_EXTADD_PAIRWISE_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x7D},
|
||||
.I32X4_EXTADD_PAIRWISE_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0x7E},
|
||||
.I32X4_EXTADD_PAIRWISE_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0x7F},
|
||||
.I16X8_ABS = {prefix = PREFIX_SIMD, opcode = 0x80},
|
||||
.I16X8_NEG = {prefix = PREFIX_SIMD, opcode = 0x81},
|
||||
.I16X8_Q15MULR_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x82},
|
||||
.I16X8_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0x83},
|
||||
.I16X8_BITMASK = {prefix = PREFIX_SIMD, opcode = 0x84},
|
||||
.I16X8_NARROW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0x85},
|
||||
.I16X8_NARROW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0x86},
|
||||
.I16X8_EXTEND_LOW_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x87},
|
||||
.I16X8_EXTEND_HIGH_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x88},
|
||||
.I16X8_EXTEND_LOW_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x89},
|
||||
.I16X8_EXTEND_HIGH_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x8A},
|
||||
.I16X8_SHL = {prefix = PREFIX_SIMD, opcode = 0x8B},
|
||||
.I16X8_SHR_S = {prefix = PREFIX_SIMD, opcode = 0x8C},
|
||||
.I16X8_SHR_U = {prefix = PREFIX_SIMD, opcode = 0x8D},
|
||||
.I16X8_ADD = {prefix = PREFIX_SIMD, opcode = 0x8E},
|
||||
.I16X8_ADD_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x8F},
|
||||
.I16X8_ADD_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x90},
|
||||
.I16X8_SUB = {prefix = PREFIX_SIMD, opcode = 0x91},
|
||||
.I16X8_SUB_SAT_S = {prefix = PREFIX_SIMD, opcode = 0x92},
|
||||
.I16X8_SUB_SAT_U = {prefix = PREFIX_SIMD, opcode = 0x93},
|
||||
.F64X2_NEAREST = {prefix = PREFIX_SIMD, opcode = 0x94},
|
||||
.I16X8_MUL = {prefix = PREFIX_SIMD, opcode = 0x95},
|
||||
.I16X8_MIN_S = {prefix = PREFIX_SIMD, opcode = 0x96},
|
||||
.I16X8_MIN_U = {prefix = PREFIX_SIMD, opcode = 0x97},
|
||||
.I16X8_MAX_S = {prefix = PREFIX_SIMD, opcode = 0x98},
|
||||
.I16X8_MAX_U = {prefix = PREFIX_SIMD, opcode = 0x99},
|
||||
.I16X8_AVGR_U = {prefix = PREFIX_SIMD, opcode = 0x9B},
|
||||
.I16X8_EXTMUL_LOW_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x9C},
|
||||
.I16X8_EXTMUL_HIGH_I8X16_S = {prefix = PREFIX_SIMD, opcode = 0x9D},
|
||||
.I16X8_EXTMUL_LOW_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x9E},
|
||||
.I16X8_EXTMUL_HIGH_I8X16_U = {prefix = PREFIX_SIMD, opcode = 0x9F},
|
||||
.I32X4_ABS = {prefix = PREFIX_SIMD, opcode = 0xA0},
|
||||
.I32X4_NEG = {prefix = PREFIX_SIMD, opcode = 0xA1},
|
||||
.I32X4_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0xA3},
|
||||
.I32X4_BITMASK = {prefix = PREFIX_SIMD, opcode = 0xA4},
|
||||
.I32X4_EXTEND_LOW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xA7},
|
||||
.I32X4_EXTEND_HIGH_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xA8},
|
||||
.I32X4_EXTEND_LOW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xA9},
|
||||
.I32X4_EXTEND_HIGH_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xAA},
|
||||
.I32X4_SHL = {prefix = PREFIX_SIMD, opcode = 0xAB},
|
||||
.I32X4_SHR_S = {prefix = PREFIX_SIMD, opcode = 0xAC},
|
||||
.I32X4_SHR_U = {prefix = PREFIX_SIMD, opcode = 0xAD},
|
||||
.I32X4_ADD = {prefix = PREFIX_SIMD, opcode = 0xAE},
|
||||
.I32X4_SUB = {prefix = PREFIX_SIMD, opcode = 0xB1},
|
||||
.I32X4_MUL = {prefix = PREFIX_SIMD, opcode = 0xB5},
|
||||
.I32X4_MIN_S = {prefix = PREFIX_SIMD, opcode = 0xB6},
|
||||
.I32X4_MIN_U = {prefix = PREFIX_SIMD, opcode = 0xB7},
|
||||
.I32X4_MAX_S = {prefix = PREFIX_SIMD, opcode = 0xB8},
|
||||
.I32X4_MAX_U = {prefix = PREFIX_SIMD, opcode = 0xB9},
|
||||
.I32X4_DOT_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBA},
|
||||
.I32X4_EXTMUL_LOW_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBC},
|
||||
.I32X4_EXTMUL_HIGH_I16X8_S = {prefix = PREFIX_SIMD, opcode = 0xBD},
|
||||
.I32X4_EXTMUL_LOW_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xBE},
|
||||
.I32X4_EXTMUL_HIGH_I16X8_U = {prefix = PREFIX_SIMD, opcode = 0xBF},
|
||||
.I64X2_ABS = {prefix = PREFIX_SIMD, opcode = 0xC0},
|
||||
.I64X2_NEG = {prefix = PREFIX_SIMD, opcode = 0xC1},
|
||||
.I64X2_ALL_TRUE = {prefix = PREFIX_SIMD, opcode = 0xC3},
|
||||
.I64X2_BITMASK = {prefix = PREFIX_SIMD, opcode = 0xC4},
|
||||
.I64X2_EXTEND_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xC7},
|
||||
.I64X2_EXTEND_HIGH_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xC8},
|
||||
.I64X2_EXTEND_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xC9},
|
||||
.I64X2_EXTEND_HIGH_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xCA},
|
||||
.I64X2_SHL = {prefix = PREFIX_SIMD, opcode = 0xCB},
|
||||
.I64X2_SHR_S = {prefix = PREFIX_SIMD, opcode = 0xCC},
|
||||
.I64X2_SHR_U = {prefix = PREFIX_SIMD, opcode = 0xCD},
|
||||
.I64X2_ADD = {prefix = PREFIX_SIMD, opcode = 0xCE},
|
||||
.I64X2_SUB = {prefix = PREFIX_SIMD, opcode = 0xD1},
|
||||
.I64X2_MUL = {prefix = PREFIX_SIMD, opcode = 0xD5},
|
||||
.I64X2_EQ = {prefix = PREFIX_SIMD, opcode = 0xD6},
|
||||
.I64X2_NE = {prefix = PREFIX_SIMD, opcode = 0xD7},
|
||||
.I64X2_LT_S = {prefix = PREFIX_SIMD, opcode = 0xD8},
|
||||
.I64X2_GT_S = {prefix = PREFIX_SIMD, opcode = 0xD9},
|
||||
.I64X2_LE_S = {prefix = PREFIX_SIMD, opcode = 0xDA},
|
||||
.I64X2_GE_S = {prefix = PREFIX_SIMD, opcode = 0xDB},
|
||||
.I64X2_EXTMUL_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xDC},
|
||||
.I64X2_EXTMUL_HIGH_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xDD},
|
||||
.I64X2_EXTMUL_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xDE},
|
||||
.I64X2_EXTMUL_HIGH_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xDF},
|
||||
.F32X4_ABS = {prefix = PREFIX_SIMD, opcode = 0xE0},
|
||||
.F32X4_NEG = {prefix = PREFIX_SIMD, opcode = 0xE1},
|
||||
.F32X4_SQRT = {prefix = PREFIX_SIMD, opcode = 0xE3},
|
||||
.F32X4_ADD = {prefix = PREFIX_SIMD, opcode = 0xE4},
|
||||
.F32X4_SUB = {prefix = PREFIX_SIMD, opcode = 0xE5},
|
||||
.F32X4_MUL = {prefix = PREFIX_SIMD, opcode = 0xE6},
|
||||
.F32X4_DIV = {prefix = PREFIX_SIMD, opcode = 0xE7},
|
||||
.F32X4_MIN = {prefix = PREFIX_SIMD, opcode = 0xE8},
|
||||
.F32X4_MAX = {prefix = PREFIX_SIMD, opcode = 0xE9},
|
||||
.F32X4_PMIN = {prefix = PREFIX_SIMD, opcode = 0xEA},
|
||||
.F32X4_PMAX = {prefix = PREFIX_SIMD, opcode = 0xEB},
|
||||
.F64X2_ABS = {prefix = PREFIX_SIMD, opcode = 0xEC},
|
||||
.F64X2_NEG = {prefix = PREFIX_SIMD, opcode = 0xED},
|
||||
.F64X2_SQRT = {prefix = PREFIX_SIMD, opcode = 0xEF},
|
||||
.F64X2_ADD = {prefix = PREFIX_SIMD, opcode = 0xF0},
|
||||
.F64X2_SUB = {prefix = PREFIX_SIMD, opcode = 0xF1},
|
||||
.F64X2_MUL = {prefix = PREFIX_SIMD, opcode = 0xF2},
|
||||
.F64X2_DIV = {prefix = PREFIX_SIMD, opcode = 0xF3},
|
||||
.F64X2_MIN = {prefix = PREFIX_SIMD, opcode = 0xF4},
|
||||
.F64X2_MAX = {prefix = PREFIX_SIMD, opcode = 0xF5},
|
||||
.F64X2_PMIN = {prefix = PREFIX_SIMD, opcode = 0xF6},
|
||||
.F64X2_PMAX = {prefix = PREFIX_SIMD, opcode = 0xF7},
|
||||
.I32X4_TRUNC_SAT_F32X4_S = {prefix = PREFIX_SIMD, opcode = 0xF8},
|
||||
.I32X4_TRUNC_SAT_F32X4_U = {prefix = PREFIX_SIMD, opcode = 0xF9},
|
||||
.F32X4_CONVERT_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xFA},
|
||||
.F32X4_CONVERT_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xFB},
|
||||
.I32X4_TRUNC_SAT_F64X2_S_ZERO = {prefix = PREFIX_SIMD, opcode = 0xFC},
|
||||
.I32X4_TRUNC_SAT_F64X2_U_ZERO = {prefix = PREFIX_SIMD, opcode = 0xFD},
|
||||
.F64X2_CONVERT_LOW_I32X4_S = {prefix = PREFIX_SIMD, opcode = 0xFE},
|
||||
.F64X2_CONVERT_LOW_I32X4_U = {prefix = PREFIX_SIMD, opcode = 0xFF},
|
||||
.I8X16_RELAXED_SWIZZLE = {prefix = PREFIX_SIMD, opcode = 0x100},
|
||||
.I32X4_RELAXED_TRUNC_F32X4_S = {prefix = PREFIX_SIMD, opcode = 0x101},
|
||||
.I32X4_RELAXED_TRUNC_F32X4_U = {prefix = PREFIX_SIMD, opcode = 0x102},
|
||||
.I32X4_RELAXED_TRUNC_F64X2_S_ZERO = {prefix = PREFIX_SIMD, opcode = 0x103},
|
||||
.I32X4_RELAXED_TRUNC_F64X2_U_ZERO = {prefix = PREFIX_SIMD, opcode = 0x104},
|
||||
.F32X4_RELAXED_MADD = {prefix = PREFIX_SIMD, opcode = 0x105},
|
||||
.F32X4_RELAXED_NMADD = {prefix = PREFIX_SIMD, opcode = 0x106},
|
||||
.F64X2_RELAXED_MADD = {prefix = PREFIX_SIMD, opcode = 0x107},
|
||||
.F64X2_RELAXED_NMADD = {prefix = PREFIX_SIMD, opcode = 0x108},
|
||||
.I8X16_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x109},
|
||||
.I16X8_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10A},
|
||||
.I32X4_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10B},
|
||||
.I64X2_RELAXED_LANESELECT = {prefix = PREFIX_SIMD, opcode = 0x10C},
|
||||
.F32X4_RELAXED_MIN = {prefix = PREFIX_SIMD, opcode = 0x10D},
|
||||
.F32X4_RELAXED_MAX = {prefix = PREFIX_SIMD, opcode = 0x10E},
|
||||
.F64X2_RELAXED_MIN = {prefix = PREFIX_SIMD, opcode = 0x10F},
|
||||
.F64X2_RELAXED_MAX = {prefix = PREFIX_SIMD, opcode = 0x110},
|
||||
.I16X8_RELAXED_Q15MULR_S = {prefix = PREFIX_SIMD, opcode = 0x111},
|
||||
.I16X8_RELAXED_DOT_I8X16_I7X16_S = {prefix = PREFIX_SIMD, opcode = 0x112},
|
||||
.I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S = {prefix = PREFIX_SIMD, opcode = 0x113},
|
||||
|
||||
// ------------------------------------------ 0xFE threads / atomics prefix
|
||||
.MEMORY_ATOMIC_NOTIFY = {prefix = PREFIX_ATOM, opcode = 0x00, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.MEMORY_ATOMIC_WAIT32 = {prefix = PREFIX_ATOM, opcode = 0x01, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.MEMORY_ATOMIC_WAIT64 = {prefix = PREFIX_ATOM, opcode = 0x02, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.ATOMIC_FENCE = {prefix = PREFIX_ATOM, opcode = 0x03, imm = {.ZERO_BYTE, .NONE}},
|
||||
.I32_ATOMIC_LOAD = {prefix = PREFIX_ATOM, opcode = 0x10, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_LOAD = {prefix = PREFIX_ATOM, opcode = 0x11, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_LOAD8_U = {prefix = PREFIX_ATOM, opcode = 0x12, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_LOAD16_U = {prefix = PREFIX_ATOM, opcode = 0x13, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_LOAD8_U = {prefix = PREFIX_ATOM, opcode = 0x14, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_LOAD16_U = {prefix = PREFIX_ATOM, opcode = 0x15, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_LOAD32_U = {prefix = PREFIX_ATOM, opcode = 0x16, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_STORE = {prefix = PREFIX_ATOM, opcode = 0x17, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_STORE = {prefix = PREFIX_ATOM, opcode = 0x18, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_STORE8 = {prefix = PREFIX_ATOM, opcode = 0x19, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_STORE16 = {prefix = PREFIX_ATOM, opcode = 0x1A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_STORE8 = {prefix = PREFIX_ATOM, opcode = 0x1B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_STORE16 = {prefix = PREFIX_ATOM, opcode = 0x1C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_STORE32 = {prefix = PREFIX_ATOM, opcode = 0x1D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_ADD = {prefix = PREFIX_ATOM, opcode = 0x1E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_ADD = {prefix = PREFIX_ATOM, opcode = 0x1F, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x20, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x21, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x22, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x23, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_ADD_U = {prefix = PREFIX_ATOM, opcode = 0x24, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_SUB = {prefix = PREFIX_ATOM, opcode = 0x25, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_SUB = {prefix = PREFIX_ATOM, opcode = 0x26, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x27, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x28, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x29, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x2A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_SUB_U = {prefix = PREFIX_ATOM, opcode = 0x2B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_AND = {prefix = PREFIX_ATOM, opcode = 0x2C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_AND = {prefix = PREFIX_ATOM, opcode = 0x2D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_AND_U = {prefix = PREFIX_ATOM, opcode = 0x2E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_AND_U = {prefix = PREFIX_ATOM, opcode = 0x2F, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_AND_U = {prefix = PREFIX_ATOM, opcode = 0x30, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_AND_U = {prefix = PREFIX_ATOM, opcode = 0x31, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_AND_U = {prefix = PREFIX_ATOM, opcode = 0x32, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_OR = {prefix = PREFIX_ATOM, opcode = 0x33, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_OR = {prefix = PREFIX_ATOM, opcode = 0x34, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_OR_U = {prefix = PREFIX_ATOM, opcode = 0x35, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_OR_U = {prefix = PREFIX_ATOM, opcode = 0x36, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_OR_U = {prefix = PREFIX_ATOM, opcode = 0x37, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_OR_U = {prefix = PREFIX_ATOM, opcode = 0x38, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_OR_U = {prefix = PREFIX_ATOM, opcode = 0x39, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_XOR = {prefix = PREFIX_ATOM, opcode = 0x3A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_XOR = {prefix = PREFIX_ATOM, opcode = 0x3B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x3F, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_XOR_U = {prefix = PREFIX_ATOM, opcode = 0x40, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_XCHG = {prefix = PREFIX_ATOM, opcode = 0x41, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_XCHG = {prefix = PREFIX_ATOM, opcode = 0x42, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x43, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x44, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x45, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x46, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_XCHG_U = {prefix = PREFIX_ATOM, opcode = 0x47, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW_CMPXCHG = {prefix = PREFIX_ATOM, opcode = 0x48, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW_CMPXCHG = {prefix = PREFIX_ATOM, opcode = 0x49, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW8_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4A, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I32_ATOMIC_RMW16_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4B, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW8_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4C, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW16_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4D, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
.I64_ATOMIC_RMW32_CMPXCHG_U = {prefix = PREFIX_ATOM, opcode = 0x4E, imm = {.MEMARG, .NONE}, flags = MEM},
|
||||
}
|
||||
|
||||
// Per-mnemonic encode form. Returns a pointer into the rodata table.
|
||||
@(private, require_results)
|
||||
encoding_form :: #force_inline proc "contextless" (m: Mnemonic) -> ^Encoding {
|
||||
return &ENCODING_TABLE[m]
|
||||
}
|
||||
226
core/rexcode/wasm/encoding_types.odin
Normal file
226
core/rexcode/wasm/encoding_types.odin
Normal file
@@ -0,0 +1,226 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
import "core:rexcode/isa"
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly ENCODING FUNDAMENTALS
|
||||
// =============================================================================
|
||||
//
|
||||
// An instruction is: [prefix?] opcode immediate*
|
||||
//
|
||||
// * `prefix` is 0 for the single-byte core opcodes, or one of 0xFC (misc),
|
||||
// 0xFD (SIMD), 0xFE (threads). When present, the *sub*-opcode that
|
||||
// follows is an unsigned LEB128 (so SIMD's 0..275 fit).
|
||||
// * Integer immediates use LEB128 (unsigned for indices/alignment, signed
|
||||
// for i32.const/i64.const and the s33 blocktype).
|
||||
// * Float constants are raw little-endian IEEE-754 (4 or 8 bytes).
|
||||
//
|
||||
// There is at most one encoding form per mnemonic, so dispatch is a direct
|
||||
// `ENCODING_TABLE[mnemonic]` lookup (O(1)) rather than the operand-shape
|
||||
// scan the variable-form arches (x86) need. The immediate layout is described
|
||||
// declaratively by `imm: [2]Imm_Kind`, walked in order by the encoder and
|
||||
// decoder.
|
||||
|
||||
Error :: isa.Error
|
||||
Label_Definition :: isa.Label_Definition
|
||||
LABEL_UNDEFINED :: isa.LABEL_UNDEFINED
|
||||
|
||||
// Relocation / Relocation_Type live in reloc.odin (per-arch by design).
|
||||
|
||||
// Opcode-space prefix bytes.
|
||||
PREFIX_NONE :: u8(0x00)
|
||||
PREFIX_MISC :: u8(0xFC) // saturating truncation, bulk memory/table
|
||||
PREFIX_SIMD :: u8(0xFD) // vector (v128)
|
||||
PREFIX_ATOM :: u8(0xFE) // threads / atomics
|
||||
|
||||
Encoding_Flags :: bit_field u8 {
|
||||
control: bool | 1, // structured control flow (block/loop/if/else/end/br*)
|
||||
memory: bool | 1, // touches linear memory
|
||||
_: u8 | 6,
|
||||
}
|
||||
|
||||
// How one immediate field is laid down after the opcode.
|
||||
Imm_Kind :: enum u8 {
|
||||
NONE,
|
||||
BLOCKTYPE, // signed LEB128 s33 (negative valtype byte, or type index)
|
||||
I32, // signed LEB128 (i32.const)
|
||||
I64, // signed LEB128 (i64.const)
|
||||
F32, // 4 little-endian bytes
|
||||
F64, // 8 little-endian bytes
|
||||
IDX, // unsigned LEB128 index (space comes from the operand)
|
||||
MEMARG, // unsigned LEB128 align, then unsigned LEB128 offset
|
||||
REFTYPE, // single value-type byte (ref.null)
|
||||
BR_TABLE, // unsigned LEB128 count, that many label depths, default depth
|
||||
ZERO_BYTE, // a single reserved 0x00 byte (memidx placeholders)
|
||||
LANE, // single byte lane index (SIMD extract/replace/load/store lane)
|
||||
LANES16, // sixteen raw bytes (v128.const value / i8x16.shuffle mask), from Instruction.bytes
|
||||
}
|
||||
|
||||
Encoding :: struct #packed {
|
||||
mnemonic: Mnemonic, // 2 -- redundant w/ table index, kept for parity
|
||||
prefix: u8, // 1 -- PREFIX_NONE / PREFIX_MISC / PREFIX_SIMD / PREFIX_ATOM
|
||||
opcode: u16, // 2 -- primary opcode, or sub-opcode within a prefix group (SIMD reaches 0x113)
|
||||
imm: [2]Imm_Kind, // 2 -- immediate layout, walked in order
|
||||
flags: Encoding_Flags, // 1
|
||||
}
|
||||
#assert(size_of(Encoding) == 8)
|
||||
|
||||
// =============================================================================
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
uleb_size :: #force_inline proc "contextless" (value: u64) -> u32 {
|
||||
v := value
|
||||
n: u32 = 1
|
||||
for /**/; v >= 0x80; n += 1 {
|
||||
v >>= 7
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
151
core/rexcode/wasm/instructions.odin
Normal file
151
core/rexcode/wasm/instructions.odin
Normal file
@@ -0,0 +1,151 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// INSTRUCTION
|
||||
// =============================================================================
|
||||
//
|
||||
// WASM instructions are variable length: a single opcode byte (or a prefix
|
||||
// byte 0xFC/0xFD/0xFE plus an unsigned-LEB sub-opcode) followed by zero or
|
||||
// more immediate fields. Two immediate slots cover every modelled form
|
||||
// (e.g. call_indirect's typeidx + tableidx, table.copy's two tableidx).
|
||||
//
|
||||
// `br_table` is the one operator whose immediate is a *vector* of label
|
||||
// depths; its default label lives in ops[0] and the case targets in the
|
||||
// `targets` slice (caller-owned, like the rest of the input). `length` is
|
||||
// filled by the encoder (and by the decoder) since it is not fixed.
|
||||
|
||||
Instruction_Flags :: bit_field u8 {
|
||||
_: u8 | 8,
|
||||
}
|
||||
|
||||
Instruction :: struct {
|
||||
ops: [2]Operand `fmt:"v,operand_count"`,
|
||||
targets: []u32, // br_table case labels (default in ops[0])
|
||||
bytes: [16]u8, // v128.const value / i8x16.shuffle lane mask (LANES16)
|
||||
mnemonic: Mnemonic,
|
||||
operand_count: u8,
|
||||
flags: Instruction_Flags,
|
||||
length: u8, // filled by encoder/decoder (1..N)
|
||||
_: [3]u8,
|
||||
}
|
||||
#assert(size_of(Instruction) == 48 + 2*size_of(int))
|
||||
|
||||
// =============================================================================
|
||||
// Builders (shape spelled out, comma-separated -- contract surface)
|
||||
// =============================================================================
|
||||
|
||||
@(require_results)
|
||||
inst_none :: #force_inline proc "contextless" (m: Mnemonic) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 0}
|
||||
}
|
||||
|
||||
// Single immediate constant (i32/i64/f32/f64.const, ref.null).
|
||||
@(require_results)
|
||||
inst_i :: #force_inline proc "contextless" (m: Mnemonic, o: Operand) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {o, {}}}
|
||||
}
|
||||
|
||||
// Single index immediate (local/global/func/.../label).
|
||||
@(require_results)
|
||||
inst_idx :: #force_inline proc "contextless" (m: Mnemonic, o: Operand) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {o, {}}}
|
||||
}
|
||||
|
||||
// Memory access: a single memarg.
|
||||
@(require_results)
|
||||
inst_memarg :: #force_inline proc "contextless" (m: Mnemonic, ma: Memarg) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {op_mem(ma), {}}}
|
||||
}
|
||||
|
||||
// Block / loop / if with a signature.
|
||||
@(require_results)
|
||||
inst_block :: #force_inline proc "contextless" (m: Mnemonic, bt: Block_Type = .EMPTY) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {op_blocktype(bt), {}}}
|
||||
}
|
||||
|
||||
// Branch with a relative label depth (br / br_if).
|
||||
@(require_results)
|
||||
inst_br :: #force_inline proc "contextless" (m: Mnemonic, depth: u32) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {op_labelidx(depth), {}}}
|
||||
}
|
||||
|
||||
// br_table: a vector of case depths plus a default depth.
|
||||
@(require_results)
|
||||
inst_br_table :: #force_inline proc "contextless" (targets: []u32, default_depth: u32) -> Instruction {
|
||||
return Instruction{
|
||||
mnemonic = .BR_TABLE, operand_count = 1,
|
||||
ops = {op_labelidx(default_depth), {}}, targets = targets,
|
||||
}
|
||||
}
|
||||
|
||||
// call_indirect typeidx, tableidx.
|
||||
@(require_results)
|
||||
inst_call_indirect :: #force_inline proc "contextless" (type_index: u32, table_index: u32 = 0) -> Instruction {
|
||||
return Instruction{
|
||||
mnemonic = .CALL_INDIRECT, operand_count = 2,
|
||||
ops = {op_type(type_index), op_table(table_index)},
|
||||
}
|
||||
}
|
||||
|
||||
// Two-index operators (table.init elemidx tableidx; table.copy dst src).
|
||||
@(require_results)
|
||||
inst_idx_idx :: #force_inline proc "contextless" (m: Mnemonic, a, b: Operand) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 2, ops = {a, b}}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SIMD (0xFD) builders
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// v128.const: a 16-byte literal carried in `bytes` (no stack operand).
|
||||
@(require_results)
|
||||
inst_v128_const :: #force_inline proc "contextless" (value: [16]u8) -> Instruction {
|
||||
return Instruction{mnemonic = .V128_CONST, operand_count = 0, bytes = value}
|
||||
}
|
||||
|
||||
// i8x16.shuffle: a 16-lane index mask carried in `bytes`.
|
||||
@(require_results)
|
||||
inst_shuffle :: #force_inline proc "contextless" (lanes: [16]u8) -> Instruction {
|
||||
return Instruction{mnemonic = .I8X16_SHUFFLE, operand_count = 0, bytes = lanes}
|
||||
}
|
||||
|
||||
// extract_lane / replace_lane: a single lane index immediate.
|
||||
@(require_results)
|
||||
inst_lane :: #force_inline proc "contextless" (m: Mnemonic, lane: u8) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 1, ops = {op_lane(lane), {}}}
|
||||
}
|
||||
|
||||
// v128 load/store *_lane: a memarg plus a lane index.
|
||||
@(require_results)
|
||||
inst_mem_lane :: #force_inline proc "contextless" (m: Mnemonic, ma: Memarg, lane: u8) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 2, ops = {op_mem(ma), op_lane(lane)}}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Emitters (append to a [dynamic]Instruction)
|
||||
// =============================================================================
|
||||
|
||||
emit_none :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic) {
|
||||
append(buf, inst_none(m))
|
||||
}
|
||||
emit_i :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, o: Operand) {
|
||||
append(buf, inst_i(m, o))
|
||||
}
|
||||
emit_idx :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, o: Operand) {
|
||||
append(buf, inst_idx(m, o))
|
||||
}
|
||||
emit_memarg :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, ma: Memarg) {
|
||||
append(buf, inst_memarg(m, ma))
|
||||
}
|
||||
emit_block :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, bt: Block_Type = .EMPTY) {
|
||||
append(buf, inst_block(m, bt))
|
||||
}
|
||||
emit_br :: #force_inline proc(buf: ^[dynamic]Instruction, m: Mnemonic, depth: u32) {
|
||||
append(buf, inst_br(m, depth))
|
||||
}
|
||||
emit_call_indirect :: #force_inline proc(buf: ^[dynamic]Instruction, type_index: u32, table_index: u32 = 0) {
|
||||
append(buf, inst_call_indirect(type_index, table_index))
|
||||
}
|
||||
469
core/rexcode/wasm/mnemonics.odin
Normal file
469
core/rexcode/wasm/mnemonics.odin
Normal file
@@ -0,0 +1,469 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly MNEMONICS
|
||||
// =============================================================================
|
||||
//
|
||||
// Coverage:
|
||||
// - WebAssembly 1.0 (MVP) core: control flow, parametric, variable,
|
||||
// memory, numeric (i32/i64/f32/f64) and conversion instructions.
|
||||
// - The sign-extension operators (0xC0..0xC4).
|
||||
// - Reference types ref.null / ref.is_null / ref.func (0xD0..0xD2).
|
||||
// - The 0xFC misc prefix group: saturating float->int truncation plus the
|
||||
// bulk memory / table operators (memory.init/copy/fill, table.*, ...).
|
||||
// - The 0xFD SIMD (fixed-width + relaxed) vector group (v128.*, i8x16.*, ...).
|
||||
// - The 0xFE threads / atomics group (atomic.fence, *.atomic.load/store/rmw*,
|
||||
// memory.atomic.notify/wait*).
|
||||
//
|
||||
// Per the cross-arch contract: `enum u16`, `INVALID = 0`.
|
||||
|
||||
Mnemonic :: enum u16 {
|
||||
INVALID = 0,
|
||||
|
||||
// ------------------------------------------------------------------ control
|
||||
UNREACHABLE, NOP,
|
||||
BLOCK, LOOP, IF, ELSE, END,
|
||||
BR, BR_IF, BR_TABLE,
|
||||
RETURN, CALL, CALL_INDIRECT,
|
||||
|
||||
// -------------------------------------------------------------- parametric
|
||||
DROP, SELECT,
|
||||
|
||||
// ---------------------------------------------------------------- variable
|
||||
LOCAL_GET, LOCAL_SET, LOCAL_TEE,
|
||||
GLOBAL_GET, GLOBAL_SET,
|
||||
|
||||
// ------------------------------------------------------------------- memory
|
||||
I32_LOAD, I64_LOAD, F32_LOAD, F64_LOAD,
|
||||
I32_LOAD8_S, I32_LOAD8_U, I32_LOAD16_S, I32_LOAD16_U,
|
||||
I64_LOAD8_S, I64_LOAD8_U, I64_LOAD16_S, I64_LOAD16_U, I64_LOAD32_S, I64_LOAD32_U,
|
||||
I32_STORE, I64_STORE, F32_STORE, F64_STORE,
|
||||
I32_STORE8, I32_STORE16,
|
||||
I64_STORE8, I64_STORE16, I64_STORE32,
|
||||
MEMORY_SIZE, MEMORY_GROW,
|
||||
|
||||
// ----------------------------------------------------------------- numeric
|
||||
I32_CONST, I64_CONST, F32_CONST, F64_CONST,
|
||||
|
||||
// i32 comparison
|
||||
I32_EQZ, I32_EQ, I32_NE, I32_LT_S, I32_LT_U, I32_GT_S, I32_GT_U,
|
||||
I32_LE_S, I32_LE_U, I32_GE_S, I32_GE_U,
|
||||
// i64 comparison
|
||||
I64_EQZ, I64_EQ, I64_NE, I64_LT_S, I64_LT_U, I64_GT_S, I64_GT_U,
|
||||
I64_LE_S, I64_LE_U, I64_GE_S, I64_GE_U,
|
||||
// f32 comparison
|
||||
F32_EQ, F32_NE, F32_LT, F32_GT, F32_LE, F32_GE,
|
||||
// f64 comparison
|
||||
F64_EQ, F64_NE, F64_LT, F64_GT, F64_LE, F64_GE,
|
||||
|
||||
// i32 arithmetic
|
||||
I32_CLZ, I32_CTZ, I32_POPCNT,
|
||||
I32_ADD, I32_SUB, I32_MUL, I32_DIV_S, I32_DIV_U, I32_REM_S, I32_REM_U,
|
||||
I32_AND, I32_OR, I32_XOR, I32_SHL, I32_SHR_S, I32_SHR_U, I32_ROTL, I32_ROTR,
|
||||
// i64 arithmetic
|
||||
I64_CLZ, I64_CTZ, I64_POPCNT,
|
||||
I64_ADD, I64_SUB, I64_MUL, I64_DIV_S, I64_DIV_U, I64_REM_S, I64_REM_U,
|
||||
I64_AND, I64_OR, I64_XOR, I64_SHL, I64_SHR_S, I64_SHR_U, I64_ROTL, I64_ROTR,
|
||||
// f32 arithmetic
|
||||
F32_ABS, F32_NEG, F32_CEIL, F32_FLOOR, F32_TRUNC, F32_NEAREST, F32_SQRT,
|
||||
F32_ADD, F32_SUB, F32_MUL, F32_DIV, F32_MIN, F32_MAX, F32_COPYSIGN,
|
||||
// f64 arithmetic
|
||||
F64_ABS, F64_NEG, F64_CEIL, F64_FLOOR, F64_TRUNC, F64_NEAREST, F64_SQRT,
|
||||
F64_ADD, F64_SUB, F64_MUL, F64_DIV, F64_MIN, F64_MAX, F64_COPYSIGN,
|
||||
|
||||
// conversions
|
||||
I32_WRAP_I64,
|
||||
I32_TRUNC_F32_S, I32_TRUNC_F32_U, I32_TRUNC_F64_S, I32_TRUNC_F64_U,
|
||||
I64_EXTEND_I32_S, I64_EXTEND_I32_U,
|
||||
I64_TRUNC_F32_S, I64_TRUNC_F32_U, I64_TRUNC_F64_S, I64_TRUNC_F64_U,
|
||||
F32_CONVERT_I32_S, F32_CONVERT_I32_U, F32_CONVERT_I64_S, F32_CONVERT_I64_U, F32_DEMOTE_F64,
|
||||
F64_CONVERT_I32_S, F64_CONVERT_I32_U, F64_CONVERT_I64_S, F64_CONVERT_I64_U, F64_PROMOTE_F32,
|
||||
I32_REINTERPRET_F32, I64_REINTERPRET_F64, F32_REINTERPRET_I32, F64_REINTERPRET_I64,
|
||||
|
||||
// sign-extension operators (0xC0..0xC4)
|
||||
I32_EXTEND8_S, I32_EXTEND16_S,
|
||||
I64_EXTEND8_S, I64_EXTEND16_S, I64_EXTEND32_S,
|
||||
|
||||
// reference types
|
||||
REF_NULL, REF_IS_NULL, REF_FUNC,
|
||||
|
||||
// ------------------------------------------------------- 0xFC misc prefix
|
||||
// saturating truncation
|
||||
I32_TRUNC_SAT_F32_S, I32_TRUNC_SAT_F32_U, I32_TRUNC_SAT_F64_S, I32_TRUNC_SAT_F64_U,
|
||||
I64_TRUNC_SAT_F32_S, I64_TRUNC_SAT_F32_U, I64_TRUNC_SAT_F64_S, I64_TRUNC_SAT_F64_U,
|
||||
// bulk memory & table
|
||||
MEMORY_INIT, DATA_DROP, MEMORY_COPY, MEMORY_FILL,
|
||||
TABLE_INIT, ELEM_DROP, TABLE_COPY, TABLE_GROW, TABLE_SIZE, TABLE_FILL,
|
||||
|
||||
// ----------------------------------------------- 0xFD SIMD (v128) prefix
|
||||
V128_LOAD, V128_LOAD8X8_S, V128_LOAD8X8_U,
|
||||
V128_LOAD16X4_S, V128_LOAD16X4_U, V128_LOAD32X2_S,
|
||||
V128_LOAD32X2_U, V128_LOAD8_SPLAT, V128_LOAD16_SPLAT,
|
||||
V128_LOAD32_SPLAT, V128_LOAD64_SPLAT, V128_STORE,
|
||||
V128_CONST, I8X16_SHUFFLE, I8X16_SWIZZLE,
|
||||
I8X16_SPLAT, I16X8_SPLAT, I32X4_SPLAT,
|
||||
I64X2_SPLAT, F32X4_SPLAT, F64X2_SPLAT,
|
||||
I8X16_EXTRACT_LANE_S, I8X16_EXTRACT_LANE_U, I8X16_REPLACE_LANE,
|
||||
I16X8_EXTRACT_LANE_S, I16X8_EXTRACT_LANE_U, I16X8_REPLACE_LANE,
|
||||
I32X4_EXTRACT_LANE, I32X4_REPLACE_LANE, I64X2_EXTRACT_LANE,
|
||||
I64X2_REPLACE_LANE, F32X4_EXTRACT_LANE, F32X4_REPLACE_LANE,
|
||||
F64X2_EXTRACT_LANE, F64X2_REPLACE_LANE, I8X16_EQ,
|
||||
I8X16_NE, I8X16_LT_S, I8X16_LT_U,
|
||||
I8X16_GT_S, I8X16_GT_U, I8X16_LE_S,
|
||||
I8X16_LE_U, I8X16_GE_S, I8X16_GE_U,
|
||||
I16X8_EQ, I16X8_NE, I16X8_LT_S,
|
||||
I16X8_LT_U, I16X8_GT_S, I16X8_GT_U,
|
||||
I16X8_LE_S, I16X8_LE_U, I16X8_GE_S,
|
||||
I16X8_GE_U, I32X4_EQ, I32X4_NE,
|
||||
I32X4_LT_S, I32X4_LT_U, I32X4_GT_S,
|
||||
I32X4_GT_U, I32X4_LE_S, I32X4_LE_U,
|
||||
I32X4_GE_S, I32X4_GE_U, F32X4_EQ,
|
||||
F32X4_NE, F32X4_LT, F32X4_GT,
|
||||
F32X4_LE, F32X4_GE, F64X2_EQ,
|
||||
F64X2_NE, F64X2_LT, F64X2_GT,
|
||||
F64X2_LE, F64X2_GE, V128_NOT,
|
||||
V128_AND, V128_ANDNOT, V128_OR,
|
||||
V128_XOR, V128_BITSELECT, V128_ANY_TRUE,
|
||||
V128_LOAD8_LANE, V128_LOAD16_LANE, V128_LOAD32_LANE,
|
||||
V128_LOAD64_LANE, V128_STORE8_LANE, V128_STORE16_LANE,
|
||||
V128_STORE32_LANE, V128_STORE64_LANE, V128_LOAD32_ZERO,
|
||||
V128_LOAD64_ZERO, F32X4_DEMOTE_F64X2_ZERO, F64X2_PROMOTE_LOW_F32X4,
|
||||
I8X16_ABS, I8X16_NEG, I8X16_POPCNT,
|
||||
I8X16_ALL_TRUE, I8X16_BITMASK, I8X16_NARROW_I16X8_S,
|
||||
I8X16_NARROW_I16X8_U, F32X4_CEIL, F32X4_FLOOR,
|
||||
F32X4_TRUNC, F32X4_NEAREST, I8X16_SHL,
|
||||
I8X16_SHR_S, I8X16_SHR_U, I8X16_ADD,
|
||||
I8X16_ADD_SAT_S, I8X16_ADD_SAT_U, I8X16_SUB,
|
||||
I8X16_SUB_SAT_S, I8X16_SUB_SAT_U, F64X2_CEIL,
|
||||
F64X2_FLOOR, I8X16_MIN_S, I8X16_MIN_U,
|
||||
I8X16_MAX_S, I8X16_MAX_U, F64X2_TRUNC,
|
||||
I8X16_AVGR_U, I16X8_EXTADD_PAIRWISE_I8X16_S, I16X8_EXTADD_PAIRWISE_I8X16_U,
|
||||
I32X4_EXTADD_PAIRWISE_I16X8_S, I32X4_EXTADD_PAIRWISE_I16X8_U, I16X8_ABS,
|
||||
I16X8_NEG, I16X8_Q15MULR_SAT_S, I16X8_ALL_TRUE,
|
||||
I16X8_BITMASK, I16X8_NARROW_I32X4_S, I16X8_NARROW_I32X4_U,
|
||||
I16X8_EXTEND_LOW_I8X16_S, I16X8_EXTEND_HIGH_I8X16_S, I16X8_EXTEND_LOW_I8X16_U,
|
||||
I16X8_EXTEND_HIGH_I8X16_U, I16X8_SHL, I16X8_SHR_S,
|
||||
I16X8_SHR_U, I16X8_ADD, I16X8_ADD_SAT_S,
|
||||
I16X8_ADD_SAT_U, I16X8_SUB, I16X8_SUB_SAT_S,
|
||||
I16X8_SUB_SAT_U, F64X2_NEAREST, I16X8_MUL,
|
||||
I16X8_MIN_S, I16X8_MIN_U, I16X8_MAX_S,
|
||||
I16X8_MAX_U, I16X8_AVGR_U, I16X8_EXTMUL_LOW_I8X16_S,
|
||||
I16X8_EXTMUL_HIGH_I8X16_S, I16X8_EXTMUL_LOW_I8X16_U, I16X8_EXTMUL_HIGH_I8X16_U,
|
||||
I32X4_ABS, I32X4_NEG, I32X4_ALL_TRUE,
|
||||
I32X4_BITMASK, I32X4_EXTEND_LOW_I16X8_S, I32X4_EXTEND_HIGH_I16X8_S,
|
||||
I32X4_EXTEND_LOW_I16X8_U, I32X4_EXTEND_HIGH_I16X8_U, I32X4_SHL,
|
||||
I32X4_SHR_S, I32X4_SHR_U, I32X4_ADD,
|
||||
I32X4_SUB, I32X4_MUL, I32X4_MIN_S,
|
||||
I32X4_MIN_U, I32X4_MAX_S, I32X4_MAX_U,
|
||||
I32X4_DOT_I16X8_S, I32X4_EXTMUL_LOW_I16X8_S, I32X4_EXTMUL_HIGH_I16X8_S,
|
||||
I32X4_EXTMUL_LOW_I16X8_U, I32X4_EXTMUL_HIGH_I16X8_U, I64X2_ABS,
|
||||
I64X2_NEG, I64X2_ALL_TRUE, I64X2_BITMASK,
|
||||
I64X2_EXTEND_LOW_I32X4_S, I64X2_EXTEND_HIGH_I32X4_S, I64X2_EXTEND_LOW_I32X4_U,
|
||||
I64X2_EXTEND_HIGH_I32X4_U, I64X2_SHL, I64X2_SHR_S,
|
||||
I64X2_SHR_U, I64X2_ADD, I64X2_SUB,
|
||||
I64X2_MUL, I64X2_EQ, I64X2_NE,
|
||||
I64X2_LT_S, I64X2_GT_S, I64X2_LE_S,
|
||||
I64X2_GE_S, I64X2_EXTMUL_LOW_I32X4_S, I64X2_EXTMUL_HIGH_I32X4_S,
|
||||
I64X2_EXTMUL_LOW_I32X4_U, I64X2_EXTMUL_HIGH_I32X4_U, F32X4_ABS,
|
||||
F32X4_NEG, F32X4_SQRT, F32X4_ADD,
|
||||
F32X4_SUB, F32X4_MUL, F32X4_DIV,
|
||||
F32X4_MIN, F32X4_MAX, F32X4_PMIN,
|
||||
F32X4_PMAX, F64X2_ABS, F64X2_NEG,
|
||||
F64X2_SQRT, F64X2_ADD, F64X2_SUB,
|
||||
F64X2_MUL, F64X2_DIV, F64X2_MIN,
|
||||
F64X2_MAX, F64X2_PMIN, F64X2_PMAX,
|
||||
I32X4_TRUNC_SAT_F32X4_S, I32X4_TRUNC_SAT_F32X4_U, F32X4_CONVERT_I32X4_S,
|
||||
F32X4_CONVERT_I32X4_U, I32X4_TRUNC_SAT_F64X2_S_ZERO, I32X4_TRUNC_SAT_F64X2_U_ZERO,
|
||||
F64X2_CONVERT_LOW_I32X4_S, F64X2_CONVERT_LOW_I32X4_U, I8X16_RELAXED_SWIZZLE,
|
||||
I32X4_RELAXED_TRUNC_F32X4_S, I32X4_RELAXED_TRUNC_F32X4_U, I32X4_RELAXED_TRUNC_F64X2_S_ZERO,
|
||||
I32X4_RELAXED_TRUNC_F64X2_U_ZERO, F32X4_RELAXED_MADD, F32X4_RELAXED_NMADD,
|
||||
F64X2_RELAXED_MADD, F64X2_RELAXED_NMADD, I8X16_RELAXED_LANESELECT,
|
||||
I16X8_RELAXED_LANESELECT, I32X4_RELAXED_LANESELECT, I64X2_RELAXED_LANESELECT,
|
||||
F32X4_RELAXED_MIN, F32X4_RELAXED_MAX, F64X2_RELAXED_MIN,
|
||||
F64X2_RELAXED_MAX, I16X8_RELAXED_Q15MULR_S, I16X8_RELAXED_DOT_I8X16_I7X16_S,
|
||||
I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S,
|
||||
|
||||
// ------------------------------------------ 0xFE threads / atomics prefix
|
||||
MEMORY_ATOMIC_NOTIFY, MEMORY_ATOMIC_WAIT32, MEMORY_ATOMIC_WAIT64,
|
||||
ATOMIC_FENCE, I32_ATOMIC_LOAD, I64_ATOMIC_LOAD,
|
||||
I32_ATOMIC_LOAD8_U, I32_ATOMIC_LOAD16_U, I64_ATOMIC_LOAD8_U,
|
||||
I64_ATOMIC_LOAD16_U, I64_ATOMIC_LOAD32_U, I32_ATOMIC_STORE,
|
||||
I64_ATOMIC_STORE, I32_ATOMIC_STORE8, I32_ATOMIC_STORE16,
|
||||
I64_ATOMIC_STORE8, I64_ATOMIC_STORE16, I64_ATOMIC_STORE32,
|
||||
I32_ATOMIC_RMW_ADD, I64_ATOMIC_RMW_ADD, I32_ATOMIC_RMW8_ADD_U,
|
||||
I32_ATOMIC_RMW16_ADD_U, I64_ATOMIC_RMW8_ADD_U, I64_ATOMIC_RMW16_ADD_U,
|
||||
I64_ATOMIC_RMW32_ADD_U, I32_ATOMIC_RMW_SUB, I64_ATOMIC_RMW_SUB,
|
||||
I32_ATOMIC_RMW8_SUB_U, I32_ATOMIC_RMW16_SUB_U, I64_ATOMIC_RMW8_SUB_U,
|
||||
I64_ATOMIC_RMW16_SUB_U, I64_ATOMIC_RMW32_SUB_U, I32_ATOMIC_RMW_AND,
|
||||
I64_ATOMIC_RMW_AND, I32_ATOMIC_RMW8_AND_U, I32_ATOMIC_RMW16_AND_U,
|
||||
I64_ATOMIC_RMW8_AND_U, I64_ATOMIC_RMW16_AND_U, I64_ATOMIC_RMW32_AND_U,
|
||||
I32_ATOMIC_RMW_OR, I64_ATOMIC_RMW_OR, I32_ATOMIC_RMW8_OR_U,
|
||||
I32_ATOMIC_RMW16_OR_U, I64_ATOMIC_RMW8_OR_U, I64_ATOMIC_RMW16_OR_U,
|
||||
I64_ATOMIC_RMW32_OR_U, I32_ATOMIC_RMW_XOR, I64_ATOMIC_RMW_XOR,
|
||||
I32_ATOMIC_RMW8_XOR_U, I32_ATOMIC_RMW16_XOR_U, I64_ATOMIC_RMW8_XOR_U,
|
||||
I64_ATOMIC_RMW16_XOR_U, I64_ATOMIC_RMW32_XOR_U, I32_ATOMIC_RMW_XCHG,
|
||||
I64_ATOMIC_RMW_XCHG, I32_ATOMIC_RMW8_XCHG_U, I32_ATOMIC_RMW16_XCHG_U,
|
||||
I64_ATOMIC_RMW8_XCHG_U, I64_ATOMIC_RMW16_XCHG_U, I64_ATOMIC_RMW32_XCHG_U,
|
||||
I32_ATOMIC_RMW_CMPXCHG, I64_ATOMIC_RMW_CMPXCHG, I32_ATOMIC_RMW8_CMPXCHG_U,
|
||||
I32_ATOMIC_RMW16_CMPXCHG_U, I64_ATOMIC_RMW8_CMPXCHG_U, I64_ATOMIC_RMW16_CMPXCHG_U,
|
||||
I64_ATOMIC_RMW32_CMPXCHG_U,
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Canonical WAT text names (per-arch formatting -- WASM mixes '.' and '_' in
|
||||
// ways no single transform of the enum name captures, so the names are
|
||||
// explicit). Indexed by Mnemonic; INVALID maps to "<invalid>".
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@(rodata)
|
||||
MNEMONIC_NAMES := [Mnemonic]string{
|
||||
.INVALID = "<invalid>",
|
||||
|
||||
.UNREACHABLE = "unreachable", .NOP = "nop",
|
||||
.BLOCK = "block", .LOOP = "loop", .IF = "if", .ELSE = "else", .END = "end",
|
||||
.BR = "br", .BR_IF = "br_if", .BR_TABLE = "br_table",
|
||||
.RETURN = "return", .CALL = "call", .CALL_INDIRECT = "call_indirect",
|
||||
|
||||
.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",
|
||||
}
|
||||
205
core/rexcode/wasm/module/module.odin
Normal file
205
core/rexcode/wasm/module/module.odin
Normal file
@@ -0,0 +1,205 @@
|
||||
package rexcode_wasm_module
|
||||
|
||||
import "base:runtime"
|
||||
import "core:rexcode/wasm"
|
||||
|
||||
WASM_MAGIC :: u32(0x6d736100) // "\0asm" as a little-endian u32
|
||||
WASM_VERSION :: u32(1)
|
||||
|
||||
Section_Id :: enum u8 { // Binary section ids (WebAssembly core spec §5.5.2).
|
||||
CUSTOM = 0,
|
||||
TYPE = 1,
|
||||
IMPORT = 2,
|
||||
FUNCTION = 3,
|
||||
TABLE = 4,
|
||||
MEMORY = 5,
|
||||
GLOBAL = 6,
|
||||
EXPORT = 7,
|
||||
START = 8,
|
||||
ELEMENT = 9,
|
||||
CODE = 10,
|
||||
DATA = 11,
|
||||
DATA_COUNT = 12,
|
||||
}
|
||||
|
||||
Section :: struct {
|
||||
id: Section_Id,
|
||||
offset: u32, // file offset of the section *contents*
|
||||
size: u32, // contents length in bytes
|
||||
count: u32, // element count
|
||||
name: string, // custom-section name (borrowed)
|
||||
}
|
||||
|
||||
External_Kind :: enum u8 {
|
||||
FUNC = 0,
|
||||
TABLE = 1,
|
||||
MEMORY = 2,
|
||||
GLOBAL = 3,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
external_kind_string := [External_Kind]string{
|
||||
.FUNC = "func",
|
||||
.TABLE = "table",
|
||||
.MEMORY = "memory",
|
||||
.GLOBAL = "global",
|
||||
}
|
||||
|
||||
Func_Type :: struct {
|
||||
params: []wasm.Value_Type,
|
||||
results: []wasm.Value_Type,
|
||||
}
|
||||
|
||||
Import :: struct {
|
||||
kind: External_Kind,
|
||||
module_name: string, // borrowed
|
||||
field_name: string, // borrowed
|
||||
index: u32, // typeidx for FUNC, 0 for other kinds
|
||||
}
|
||||
|
||||
Export :: struct {
|
||||
kind: External_Kind,
|
||||
name: string, // borrowed
|
||||
index: u32,
|
||||
}
|
||||
|
||||
// A compressed run of declared locals (e.g. `3 x i32`)
|
||||
Local_Group :: struct {
|
||||
count: u32,
|
||||
type: wasm.Value_Type,
|
||||
}
|
||||
|
||||
// A function in the module's function index space.
|
||||
// Imported functions occupy the low indices, followed by the module-defined functions.
|
||||
Function :: struct {
|
||||
func_index: u32,
|
||||
type_index: u32,
|
||||
type: Func_Type, // resolved signature ({} if the type id was out of range)
|
||||
imported: bool,
|
||||
exported: bool,
|
||||
name: string, // export / name-section / import field (borrowed)
|
||||
import_module: string, // borrowed, "" for defined functions
|
||||
import_field: string, // borrowed, "" for defined functions
|
||||
|
||||
// defined functions only:
|
||||
locals: []Local_Group,
|
||||
body_offset: u32, // file offset of the instruction stream
|
||||
body_size: u32, // instruction-stream length in bytes
|
||||
}
|
||||
|
||||
Module :: struct {
|
||||
version: u32,
|
||||
sections: []Section,
|
||||
customs: []Custom_Section,
|
||||
types: []Func_Type,
|
||||
imports: []Import,
|
||||
functions: []Function, // whole function index space (imports + defined)
|
||||
exports: []Export,
|
||||
start: i64, // -1 if absent, else the start funcidx
|
||||
|
||||
data: []u8, // borrowed reference to the whole file (body decode reads from it)
|
||||
|
||||
allocator: runtime.Allocator,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Custom Section Layout
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Custom_Section :: struct {
|
||||
section: Section,
|
||||
variant: union {
|
||||
Custom_Section_Name,
|
||||
Custom_Section_Target_Features,
|
||||
},
|
||||
}
|
||||
|
||||
Custom_Section_Name_Function :: struct {
|
||||
id: u32,
|
||||
name: string, // borrowed
|
||||
}
|
||||
|
||||
Custom_Section_Name_Local :: struct {
|
||||
idx: u32,
|
||||
name: string, // borrowed
|
||||
}
|
||||
|
||||
Custom_Section_Name_Function_Locals :: struct {
|
||||
func_idx: u32,
|
||||
locals: []Custom_Section_Name_Local,
|
||||
}
|
||||
|
||||
Custom_Section_Name :: struct {
|
||||
module_name: string,
|
||||
functions: []Custom_Section_Name_Function,
|
||||
locals: []Custom_Section_Name_Function_Locals,
|
||||
}
|
||||
|
||||
Custom_Section_Target_Feature_Prefix :: enum u8 {
|
||||
Used = '+',
|
||||
Disallowed = '-',
|
||||
Required = '=',
|
||||
}
|
||||
|
||||
Custom_Section_Target_Feature :: struct {
|
||||
prefix: Custom_Section_Target_Feature_Prefix,
|
||||
feature: string, // borrowed
|
||||
}
|
||||
|
||||
|
||||
Custom_Section_Target_Features :: struct {
|
||||
features: []Custom_Section_Target_Feature,
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Small display helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@(require_results)
|
||||
section_name :: proc(id: Section_Id) -> string {
|
||||
switch id {
|
||||
case .CUSTOM: return "custom"
|
||||
case .TYPE: return "type"
|
||||
case .IMPORT: return "import"
|
||||
case .FUNCTION: return "function"
|
||||
case .TABLE: return "table"
|
||||
case .MEMORY: return "memory"
|
||||
case .GLOBAL: return "global"
|
||||
case .EXPORT: return "export"
|
||||
case .START: return "start"
|
||||
case .ELEMENT: return "element"
|
||||
case .CODE: return "code"
|
||||
case .DATA: return "data"
|
||||
case .DATA_COUNT: return "data.count"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
valtype_name :: proc(t: wasm.Value_Type) -> string {
|
||||
switch t {
|
||||
case .I32: return "i32"
|
||||
case .I64: return "i64"
|
||||
case .F32: return "f32"
|
||||
case .F64: return "f64"
|
||||
case .V128: return "v128"
|
||||
case .FUNCREF: return "funcref"
|
||||
case .EXTERNREF: return "externref"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
external_kind_name :: proc(k: External_Kind) -> string {
|
||||
switch k {
|
||||
case .FUNC: return "func"
|
||||
case .TABLE: return "table"
|
||||
case .MEMORY: return "memory"
|
||||
case .GLOBAL: return "global"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
498
core/rexcode/wasm/module/parse.odin
Normal file
498
core/rexcode/wasm/module/parse.odin
Normal file
@@ -0,0 +1,498 @@
|
||||
package rexcode_wasm_module
|
||||
|
||||
import "base:runtime"
|
||||
import "core:rexcode/wasm"
|
||||
|
||||
Parse_Error :: enum {
|
||||
NONE = 0,
|
||||
TRUNCATED,
|
||||
BAD_MAGIC,
|
||||
BAD_TYPE_FORM, // a functype did not start with 0x60
|
||||
BAD_SECTION, // section contents extend past the section size
|
||||
BAD_ULEB, // ULEB number didn't stop after 10 bytes
|
||||
}
|
||||
|
||||
Reader_Error :: union #shared_nil {
|
||||
Parse_Error,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
Reader :: struct {
|
||||
data: []u8,
|
||||
off: u32,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reader :: proc(data: []u8, off: u32) -> Reader {
|
||||
return Reader{data = data, off = off}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_byte :: proc(r: ^Reader) -> (u8, Parse_Error) {
|
||||
if r.off >= u32(len(r.data)) {
|
||||
return 0, .TRUNCATED
|
||||
}
|
||||
b := r.data[r.off]
|
||||
r.off += 1
|
||||
return b, .NONE
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_u32le_block :: proc(r: ^Reader) -> (u32, Parse_Error) {
|
||||
if r.off + 4 > u32(len(r.data)) {
|
||||
return 0, .TRUNCATED
|
||||
}
|
||||
v := u32(r.data[r.off]) |
|
||||
u32(r.data[r.off+1])<<8 |
|
||||
u32(r.data[r.off+2])<<16 |
|
||||
u32(r.data[r.off+3])<<24
|
||||
r.off += 4
|
||||
return v, .NONE
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_uleb :: proc(r: ^Reader) -> (u64, Parse_Error) {
|
||||
shift: uint = 0
|
||||
value: u64 = 0
|
||||
for _ in 0..<10 {
|
||||
if r.off >= u32(len(r.data)) {
|
||||
return 0, .TRUNCATED
|
||||
}
|
||||
b := r.data[r.off]
|
||||
r.off += 1
|
||||
value |= u64(b & 0x7F) << shift
|
||||
if b & 0x80 == 0 {
|
||||
return value, .NONE
|
||||
}
|
||||
shift += 7
|
||||
}
|
||||
return 0, .BAD_ULEB
|
||||
}
|
||||
|
||||
// Signed-LEB128 reader.
|
||||
@(require_results)
|
||||
rd_sleb :: proc(r: ^Reader) -> (i64, Parse_Error) {
|
||||
shift: uint = 0
|
||||
value: i64 = 0
|
||||
b: u8 = 0
|
||||
for _ in 0..<10 {
|
||||
if r.off >= u32(len(r.data)) {
|
||||
return 0, .TRUNCATED
|
||||
}
|
||||
b = r.data[r.off]
|
||||
r.off += 1
|
||||
value |= i64(b & 0x7F) << shift
|
||||
shift += 7
|
||||
if b & 0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if shift < 64 && (b & 0x40) != 0 {
|
||||
value |= -(i64(1) << shift)
|
||||
}
|
||||
return value, .NONE
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
rd_u32 :: proc(r: ^Reader) -> (u32, Parse_Error) {
|
||||
v, err := rd_uleb(r)
|
||||
return u32(v), err
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_name :: proc(r: ^Reader) -> (val: string, err: Parse_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
if r.off + n > u32(len(r.data)) {
|
||||
err = .TRUNCATED
|
||||
return
|
||||
}
|
||||
val = string(r.data[r.off:][:n])
|
||||
r.off += n
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_valtype_vec :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []wasm.Value_Type, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
|
||||
// now actually parse it
|
||||
out = make([]wasm.Value_Type, int(n), allocator) or_return
|
||||
for &v in out {
|
||||
v = wasm.Value_Type(rd_byte(r) or_return)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rd_limits :: proc(r: ^Reader) -> (min: u64, max: Maybe(u64), err: Parse_Error) {
|
||||
flags := rd_byte(r) or_return
|
||||
min = rd_uleb(r) or_return
|
||||
if flags & 0x01 != 0 {
|
||||
max = rd_uleb(r) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
parse :: proc(data: []u8, allocator := context.allocator) -> (m: Module, err: Reader_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
m.data = data
|
||||
m.start = -1
|
||||
m.allocator = allocator
|
||||
|
||||
r := reader(data, 0)
|
||||
if (rd_u32le_block(&r) or_else 0) != WASM_MAGIC {
|
||||
return m, .BAD_MAGIC
|
||||
}
|
||||
m.version = rd_u32le_block(&r) or_return
|
||||
|
||||
secs: [dynamic]Section
|
||||
for r.off < u32(len(data)) {
|
||||
id := Section_Id(rd_byte(&r) or_return)
|
||||
size := rd_u32(&r) or_return
|
||||
content := r.off
|
||||
if content + size > u32(len(data)) {
|
||||
return m, .BAD_SECTION
|
||||
}
|
||||
|
||||
sec := Section{id = id, offset = content, size = size}
|
||||
switch id {
|
||||
case .CUSTOM:
|
||||
sub := reader(data, content)
|
||||
sec.name = rd_name(&sub) or_return
|
||||
case .START:
|
||||
// funcidx, no vector count
|
||||
case .TYPE, .IMPORT, .FUNCTION, .TABLE, .MEMORY, .GLOBAL,
|
||||
.EXPORT, .ELEMENT, .CODE, .DATA, .DATA_COUNT:
|
||||
sub := reader(data, content)
|
||||
sec.count = rd_u32(&sub) or_return
|
||||
}
|
||||
append(&secs, sec) or_return
|
||||
r.off = content + size
|
||||
}
|
||||
m.sections = secs[:]
|
||||
|
||||
|
||||
func_typeidx: []u32
|
||||
codes: []Code_Body
|
||||
|
||||
for &sec in m.sections {
|
||||
s := reader(data, sec.offset)
|
||||
#partial switch sec.id {
|
||||
case .TYPE: m.types = parse_types (&s, allocator) or_return
|
||||
case .IMPORT: m.imports = parse_imports (&s, allocator) or_return
|
||||
case .FUNCTION: func_typeidx = parse_function_section(&s, allocator) or_return
|
||||
case .EXPORT: m.exports = parse_exports (&s, allocator) or_return
|
||||
case .CODE: codes = parse_code (&s, allocator) or_return
|
||||
case .START: m.start = i64(rd_u32(&s) or_return)
|
||||
}
|
||||
}
|
||||
|
||||
parse_custom_sections(&m, allocator) or_return
|
||||
|
||||
|
||||
build_functions(&m, func_typeidx, codes, allocator) or_return
|
||||
apply_name_section(&m)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_types :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Func_Type, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]Func_Type, int(n), allocator) or_return
|
||||
for &func in out {
|
||||
form := rd_byte(r) or_return
|
||||
if form != 0x60 {
|
||||
return out, .BAD_TYPE_FORM
|
||||
}
|
||||
func.params = rd_valtype_vec(r, allocator) or_return
|
||||
func.results = rd_valtype_vec(r, allocator) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_imports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Import, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]Import, int(n), allocator) or_return
|
||||
for &imp in out {
|
||||
imp.module_name = rd_name(r) or_return
|
||||
imp.field_name = rd_name(r) or_return
|
||||
imp.kind = External_Kind(rd_byte(r) or_return)
|
||||
switch imp.kind {
|
||||
case .FUNC:
|
||||
imp.index = rd_u32(r) or_return
|
||||
case .TABLE:
|
||||
_ = rd_byte(r) or_return // reftype
|
||||
_, _ = rd_limits(r) or_return
|
||||
case .MEMORY:
|
||||
_, _ = rd_limits(r) or_return
|
||||
case .GLOBAL:
|
||||
_ = rd_byte(r) or_return // valtype
|
||||
_ = rd_byte(r) or_return // mutability
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_function_section :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []u32, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]u32, int(n), allocator) or_return
|
||||
for &idx in out {
|
||||
idx = rd_u32(r) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_exports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Export, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]Export, int(n), allocator) or_return
|
||||
for &e in out {
|
||||
e.name = rd_name(r) or_return
|
||||
e.kind = External_Kind(rd_byte(r) or_return)
|
||||
e.index = rd_u32(r) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Code_Body :: struct {
|
||||
locals: []Local_Group,
|
||||
body_offset: u32,
|
||||
body_size: u32,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_code :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Code_Body, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]Code_Body, int(n), allocator) or_return
|
||||
for &code_body in out {
|
||||
total := rd_u32(r) or_return
|
||||
body_start := r.off
|
||||
body_end := body_start + total
|
||||
|
||||
nl := rd_u32(r) or_return
|
||||
locals := make([]Local_Group, int(nl), allocator) or_return
|
||||
for &local in locals {
|
||||
cnt := rd_u32(r) or_return
|
||||
t := wasm.Value_Type(rd_byte(r) or_return)
|
||||
local = Local_Group{count = cnt, type = t}
|
||||
}
|
||||
code_body = Code_Body{
|
||||
locals = locals,
|
||||
body_offset = r.off,
|
||||
body_size = body_end > r.off ? body_end - r.off : 0,
|
||||
}
|
||||
r.off = body_end // jump past the expression to the next entry
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_custom_sections :: proc(m: ^Module, allocator: runtime.Allocator) -> Reader_Error {
|
||||
custom_count := 0
|
||||
for &sec in m.sections {
|
||||
if sec.id == .CUSTOM {
|
||||
custom_count += 1
|
||||
}
|
||||
}
|
||||
m.customs = make([]Custom_Section, custom_count, allocator) or_return
|
||||
custom_index := 0
|
||||
for &sec in m.sections {
|
||||
if sec.id != .CUSTOM {
|
||||
continue
|
||||
}
|
||||
|
||||
custom := &m.customs[custom_index]
|
||||
custom_index += 1
|
||||
|
||||
custom.section = sec
|
||||
|
||||
section_data := m.data[sec.offset:][:sec.size]
|
||||
r := reader(section_data, 0)
|
||||
sec_name := rd_name(&r) or_continue
|
||||
assert(sec_name == sec.name)
|
||||
|
||||
custom_block: switch sec.name {
|
||||
case "name":
|
||||
cname: Custom_Section_Name
|
||||
defer custom.variant = cname
|
||||
|
||||
for r.off < u32(len(r.data)) {
|
||||
id := rd_byte(&r) or_return
|
||||
size := rd_u32(&r) or_return
|
||||
end_off := r.off+size
|
||||
defer r.off = end_off
|
||||
|
||||
switch id {
|
||||
case 0: // module
|
||||
cname.module_name = rd_name(&r) or_return
|
||||
case 1: // functions
|
||||
count := rd_u32(&r) or_return
|
||||
cname.functions = make([]Custom_Section_Name_Function, count, allocator) or_return
|
||||
for &func in cname.functions {
|
||||
func.id = rd_u32(&r) or_return
|
||||
func.name = rd_name(&r) or_return
|
||||
}
|
||||
case 2: // locals
|
||||
count := rd_u32(&r) or_return
|
||||
|
||||
cname.locals = make([]Custom_Section_Name_Function_Locals, count, allocator) or_return
|
||||
|
||||
for &local_func in cname.locals {
|
||||
local_func.func_idx = rd_u32(&r) or_return
|
||||
local_count := rd_u32(&r) or_return
|
||||
local_func.locals = make([]Custom_Section_Name_Local, local_count, allocator) or_return
|
||||
for &local in local_func.locals {
|
||||
local.idx = rd_u32(&r) or_return
|
||||
local.name = rd_name(&r) or_return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "target_features":
|
||||
target_features: Custom_Section_Target_Features
|
||||
defer custom.variant = target_features
|
||||
|
||||
count := rd_u32(&r) or_return
|
||||
target_features.features = make([]Custom_Section_Target_Feature, count, allocator) or_return
|
||||
|
||||
for &feature in target_features.features {
|
||||
feature.prefix = Custom_Section_Target_Feature_Prefix(rd_byte(&r) or_return)
|
||||
feature.feature = rd_name(&r) or_return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
build_functions :: proc(m: ^Module, func_typeidx: []u32, codes: []Code_Body, allocator: runtime.Allocator) -> runtime.Allocator_Error {
|
||||
num_imports := 0
|
||||
for imp in m.imports {
|
||||
if imp.kind == .FUNC {
|
||||
num_imports += 1
|
||||
}
|
||||
}
|
||||
total := num_imports + len(func_typeidx)
|
||||
if total == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
funcs := make([]Function, total, allocator) or_return
|
||||
|
||||
idx := 0
|
||||
for imp in m.imports {
|
||||
(imp.kind == .FUNC) or_continue
|
||||
f := Function{
|
||||
func_index = u32(idx),
|
||||
type_index = imp.index,
|
||||
imported = true,
|
||||
name = imp.field_name,
|
||||
import_module = imp.module_name,
|
||||
import_field = imp.field_name,
|
||||
}
|
||||
if int(imp.index) < len(m.types) {
|
||||
f.type = m.types[imp.index]
|
||||
}
|
||||
funcs[idx] = f
|
||||
idx += 1
|
||||
}
|
||||
|
||||
for tidx, i in func_typeidx {
|
||||
fi := num_imports + i
|
||||
f := Function{
|
||||
func_index = u32(fi),
|
||||
type_index = tidx,
|
||||
imported = false,
|
||||
}
|
||||
if int(tidx) < len(m.types) {
|
||||
f.type = m.types[tidx]
|
||||
}
|
||||
if i < len(codes) {
|
||||
c := &codes[i]
|
||||
f.locals = c.locals
|
||||
f.body_offset = c.body_offset
|
||||
f.body_size = c.body_size
|
||||
}
|
||||
funcs[fi] = f
|
||||
}
|
||||
|
||||
for e in m.exports {
|
||||
if e.kind == .FUNC && int(e.index) < total && funcs[e.index].name == "" {
|
||||
funcs[e.index].name = e.name
|
||||
}
|
||||
}
|
||||
|
||||
m.functions = funcs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Override function names with debug names from the "name" custom section's function-names subsection (id 1), if it exists.
|
||||
apply_name_section :: proc(m: ^Module) {
|
||||
for sec in m.sections {
|
||||
if sec.id != .CUSTOM || sec.name != "name" {
|
||||
continue
|
||||
}
|
||||
|
||||
r := reader(m.data, sec.offset)
|
||||
_ = rd_name(&r) or_break // re-read the section name to position at the subsections
|
||||
end := sec.offset + sec.size
|
||||
|
||||
for r.off < end {
|
||||
sub_id := rd_byte(&r) or_break
|
||||
sub_size := rd_u32(&r) or_break
|
||||
payload_end := r.off + sub_size
|
||||
if sub_id == 1 {
|
||||
count := rd_u32(&r) or_break
|
||||
for _ in 0..<count {
|
||||
fidx := rd_u32(&r) or_break
|
||||
name := rd_name(&r) or_break
|
||||
if int(fidx) < len(m.functions) {
|
||||
m.functions[fidx].name = name
|
||||
}
|
||||
}
|
||||
}
|
||||
// skip any subsection we do not need to interpret
|
||||
r.off = payload_end
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module_destroy :: proc(m: ^Module) {
|
||||
for t in m.types {
|
||||
delete(t.params, m.allocator)
|
||||
delete(t.results, m.allocator)
|
||||
}
|
||||
for f in m.functions {
|
||||
if !f.imported {
|
||||
delete(f.locals, m.allocator)
|
||||
}
|
||||
}
|
||||
for c in m.customs {
|
||||
switch v in c.variant {
|
||||
case Custom_Section_Name:
|
||||
for function_locals in v.locals {
|
||||
delete(function_locals.locals, m.allocator)
|
||||
}
|
||||
delete(v.functions, m.allocator)
|
||||
delete(v.locals, m.allocator)
|
||||
case Custom_Section_Target_Features:
|
||||
delete(v.features)
|
||||
}
|
||||
}
|
||||
delete(m.customs, m.allocator)
|
||||
delete(m.sections, m.allocator)
|
||||
delete(m.types, m.allocator)
|
||||
delete(m.imports, m.allocator)
|
||||
delete(m.functions, m.allocator)
|
||||
delete(m.exports, m.allocator)
|
||||
m^ = {}
|
||||
}
|
||||
266
core/rexcode/wasm/module/print.odin
Normal file
266
core/rexcode/wasm/module/print.odin
Normal file
@@ -0,0 +1,266 @@
|
||||
package rexcode_wasm_module
|
||||
|
||||
import "core:strings"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
import wasm "../"
|
||||
|
||||
print_module :: proc(m: Module, file: ^os.File) {
|
||||
sb := strings.builder_make(context.allocator)
|
||||
defer strings.builder_destroy(&sb)
|
||||
sbprint_module(&sb, m)
|
||||
s := strings.to_string(sb)
|
||||
os.write_string(file, s)
|
||||
}
|
||||
|
||||
sbprint_module :: proc(sb: ^strings.Builder, m: Module) {
|
||||
write_func_type :: proc(sb: ^strings.Builder, t: Func_Type) {
|
||||
strings.write_byte(sb, '(')
|
||||
for p, i in t.params {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
strings.write_string(sb, valtype_name(p))
|
||||
}
|
||||
strings.write_string(sb, ") -> ")
|
||||
strings.write_byte(sb, '(')
|
||||
for rt, i in t.results {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
strings.write_string(sb, valtype_name(rt))
|
||||
}
|
||||
strings.write_byte(sb, ')')
|
||||
}
|
||||
|
||||
|
||||
strings.write_string(sb, "WebAssembly Module, Version: ")
|
||||
strings.write_u64(sb, u64(m.version))
|
||||
strings.write_byte(sb, '\n')
|
||||
|
||||
label_names: map[u32]string
|
||||
defer delete(label_names)
|
||||
for f in m.functions {
|
||||
if f.name != "" {
|
||||
label_names[f.func_index] = f.name
|
||||
}
|
||||
}
|
||||
|
||||
relocs_group, _ := parse_relocations(m, context.temp_allocator)
|
||||
|
||||
// sections
|
||||
for sec in m.sections {
|
||||
write_padded :: proc(sb: ^strings.Builder, s: string, width: int) {
|
||||
strings.write_string(sb, s)
|
||||
for _ in len(s)..<width {
|
||||
strings.write_byte(sb, ' ')
|
||||
}
|
||||
}
|
||||
|
||||
strings.write_string(sb, ".")
|
||||
write_padded(sb, section_name(sec.id), 12)
|
||||
#partial switch sec.id {
|
||||
case .CUSTOM:
|
||||
strings.write_string(sb, " \"")
|
||||
strings.write_string(sb, sec.name)
|
||||
strings.write_byte(sb, '"')
|
||||
case .START:
|
||||
// do nothing
|
||||
case:
|
||||
strings.write_string(sb, " (")
|
||||
strings.write_u64(sb, u64(sec.count))
|
||||
strings.write_string(sb, " entries)")
|
||||
}
|
||||
strings.write_byte(sb, '\n')
|
||||
|
||||
section_data := m.data[sec.offset:][:sec.size]
|
||||
|
||||
section_printing: #partial switch sec.id {
|
||||
case .CUSTOM:
|
||||
for c in m.customs {
|
||||
if c.section != sec {
|
||||
continue
|
||||
}
|
||||
switch v in c.variant {
|
||||
case Custom_Section_Name:
|
||||
if v.module_name != "" {
|
||||
fmt.sbprintf(sb, " module: %q\n", v.module_name)
|
||||
}
|
||||
if len(v.functions) > 0 {
|
||||
fmt.sbprintf(sb, " functions:\n")
|
||||
for f in v.functions {
|
||||
fmt.sbprintf(sb, " [%d] %q\n", f.id, f.name)
|
||||
}
|
||||
}
|
||||
if len(v.locals) > 0 {
|
||||
fmt.sbprintf(sb, " locals:\n")
|
||||
for fl in v.locals {
|
||||
fmt.sbprintf(sb, " [%d] function\n", fl.func_idx)
|
||||
for local in fl.locals {
|
||||
fmt.sbprintf(sb, " [%d] %q\n", local.idx, local.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
case Custom_Section_Target_Features:
|
||||
for f in v.features {
|
||||
fmt.sbprintf(sb, " \"%c%s\"\n", u8(f.prefix), f.feature)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
strings.write_byte(sb, '\n')
|
||||
|
||||
case .DATA:
|
||||
r := reader(section_data, 0)
|
||||
count := rd_u32(&r) or_break section_printing
|
||||
assert(count == sec.count)
|
||||
for i in 0..<sec.count {
|
||||
fmt.sbprintf(sb, " [%d]\n", i)
|
||||
kind := rd_u32(&r) or_break section_printing
|
||||
switch kind {
|
||||
case 2: // memidx + expr + []byte
|
||||
memidx := rd_u32(&r) or_break section_printing
|
||||
fmt.sbprintf(sb, " memidx:%d\n", memidx)
|
||||
fallthrough
|
||||
case 0: // expr + []byte
|
||||
relocs: []wasm.Relocation
|
||||
for rg in relocs_group {
|
||||
if rg.target_section == sec.id {
|
||||
relocs = rg.relocs
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for r.off < u32(len(r.data)) {
|
||||
inst, info, next := wasm.decode_one(r.data[r.off:], relocs=relocs, pc=0, targets_allocator=context.temp_allocator) or_break section_printing
|
||||
r.off += next
|
||||
if inst.mnemonic == .END {
|
||||
break
|
||||
}
|
||||
wasm.sbprint(sb, {inst}, {info}, nil, &label_names)
|
||||
}
|
||||
size := rd_u32(&r) or_break section_printing
|
||||
fmt.sbprintf(sb, " %q\n", r.data[r.off:][:size])
|
||||
case 1: // []byte
|
||||
fmt.sbprintf(sb, " %q\n", r.data[r.off:])
|
||||
}
|
||||
}
|
||||
case .MEMORY:
|
||||
r := reader(section_data, 0)
|
||||
count := rd_u32(&r) or_break section_printing
|
||||
assert(count == sec.count)
|
||||
for i in 0..<sec.count {
|
||||
fmt.sbprintf(sb, " [%d]\n", i)
|
||||
min, max := rd_limits(&r) or_break section_printing
|
||||
if max == nil {
|
||||
fmt.sbprintf(sb, " limits: %v..inf\n", min)
|
||||
} else {
|
||||
fmt.sbprintf(sb, " limits: %v..%v\n", min, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.imports) > 0 {
|
||||
strings.write_string(sb, "\n.import\n")
|
||||
for imp, i in m.imports {
|
||||
fmt.sbprintf(sb, " [%d] %s %q %q idx:%d\n", i, external_kind_string[imp.kind], imp.module_name, imp.field_name, imp.index)
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.exports) > 0 {
|
||||
strings.write_string(sb, "\n.export\n")
|
||||
for e, i in m.exports {
|
||||
fmt.sbprintf(sb, " [%d] %s %q idx:%d\n", i, external_kind_string[e.kind], e.name, e.index)
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.types) > 0 {
|
||||
strings.write_string(sb, "\n.")
|
||||
strings.write_string(sb, section_name(.TYPE))
|
||||
strings.write_string(sb, "\n")
|
||||
for t, i in m.types {
|
||||
strings.write_string(sb, " [")
|
||||
strings.write_u64(sb, u64(i))
|
||||
strings.write_string(sb, "] ")
|
||||
write_func_type(sb, t)
|
||||
strings.write_byte(sb, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func_relocs: []wasm.Relocation
|
||||
for rg in relocs_group {
|
||||
if rg.target_section == .FUNCTION {
|
||||
func_relocs = rg.relocs
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
strings.write_string(sb, "\nfunctions:\n")
|
||||
for f in m.functions {
|
||||
strings.write_string(sb, " [")
|
||||
strings.write_u64(sb, u64(f.func_index))
|
||||
strings.write_string(sb, "] ")
|
||||
if f.name != "" {
|
||||
strings.write_byte(sb, '$')
|
||||
strings.write_quoted_string(sb, f.name)
|
||||
strings.write_byte(sb, ' ')
|
||||
}
|
||||
write_func_type(sb, f.type)
|
||||
|
||||
if f.imported {
|
||||
strings.write_string(sb, "\n import ")
|
||||
strings.write_quoted_string(sb, f.import_module)
|
||||
strings.write_string(sb, " ")
|
||||
strings.write_quoted_string(sb, f.import_field)
|
||||
strings.write_string(sb, "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
strings.write_byte(sb, '\n')
|
||||
|
||||
if len(f.locals) != 0 {
|
||||
strings.write_string(sb, " locals:")
|
||||
for g in f.locals {
|
||||
strings.write_byte(sb, ' ')
|
||||
if g.count > 1 {
|
||||
strings.write_u64(sb, u64(g.count))
|
||||
strings.write_string(sb, "x")
|
||||
}
|
||||
strings.write_string(sb, valtype_name(g.type))
|
||||
}
|
||||
strings.write_byte(sb, '\n')
|
||||
}
|
||||
|
||||
if f.body_size != 0 {
|
||||
tmp_sb: strings.Builder
|
||||
defer strings.builder_destroy(&tmp_sb)
|
||||
text := sbprint_function(&tmp_sb, m, f, func_relocs, &label_names)
|
||||
for line in strings.split_lines_iterator(&text) {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
strings.write_string(sb, " ")
|
||||
strings.write_string(sb, line)
|
||||
strings.write_byte(sb, '\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disassemble and print one function body. Returns the empty string for
|
||||
// imported functions (which have no body).
|
||||
sbprint_function :: proc(sb: ^strings.Builder, m: Module, f: Function, relocs: []wasm.Relocation, label_names: ^map[u32]string) -> string {
|
||||
if f.imported || f.body_size == 0 {
|
||||
return ""
|
||||
}
|
||||
body := m.data[f.body_offset:][:f.body_size]
|
||||
|
||||
insts: [dynamic]wasm.Instruction
|
||||
info: [dynamic]wasm.Instruction_Info
|
||||
errs: [dynamic]wasm.Error
|
||||
defer delete(insts)
|
||||
defer delete(info)
|
||||
defer delete(errs)
|
||||
|
||||
wasm.decode(body, relocs, &insts, &info, &errs)
|
||||
wasm.sbprint(sb, insts[:], info[:], label_names=label_names)
|
||||
return strings.to_string(sb^)
|
||||
}
|
||||
93
core/rexcode/wasm/module/relocs.odin
Normal file
93
core/rexcode/wasm/module/relocs.odin
Normal file
@@ -0,0 +1,93 @@
|
||||
package rexcode_wasm_module
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import wasm "core:rexcode/wasm"
|
||||
|
||||
Reloc_Group :: struct {
|
||||
target_section: Section_Id, // index of the section these apply to
|
||||
relocs: []wasm.Relocation,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (reloc_groups: []Reloc_Group, err: Reader_Error) {
|
||||
groups: [dynamic]Reloc_Group
|
||||
groups.allocator = m.allocator
|
||||
for sec in m.sections {
|
||||
if !(sec.id == .CUSTOM && strings.has_prefix(sec.name, "reloc.")) {
|
||||
continue
|
||||
}
|
||||
|
||||
r := reader(m.data[sec.offset:][:sec.size], 0)
|
||||
_ = rd_name(&r) or_return // step past the custom-section name
|
||||
target := Section_Id(rd_u32(&r) or_return)
|
||||
count := rd_u32(&r) or_return
|
||||
|
||||
out := make([]wasm.Relocation, int(count), m.allocator)
|
||||
w := 0
|
||||
for _ in 0..<count {
|
||||
code := rd_byte(&r) or_return
|
||||
offset := rd_u32(&r) or_return // offset of the field within target_section
|
||||
index := rd_u32(&r) or_return // symbol / target index
|
||||
addend: i32 = 0
|
||||
if reloc_has_addend(code) {
|
||||
addend = i32(rd_sleb(&r) or_return)
|
||||
}
|
||||
|
||||
t := reloc_type_from_wire(code) or_continue
|
||||
|
||||
out[w] = wasm.Relocation{
|
||||
offset = offset,
|
||||
label_id = index,
|
||||
addend = addend,
|
||||
type = t,
|
||||
size = reloc_field_size(t),
|
||||
}
|
||||
w += 1
|
||||
}
|
||||
append(&groups, Reloc_Group{target_section = target, relocs = out[:w]}) or_return
|
||||
}
|
||||
reloc_groups = groups[:]
|
||||
return
|
||||
}
|
||||
|
||||
relocations_destroy :: proc(groups: []Reloc_Group, allocator: runtime.Allocator) {
|
||||
context.allocator = allocator
|
||||
for g in groups { delete(g.relocs) }
|
||||
delete(groups)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reloc_type_from_wire :: proc(code: u8) -> (wasm.Relocation_Type, bool) {
|
||||
switch code {
|
||||
case 0: return .FUNCTION_INDEX_LEB, true // R_WASM_FUNCTION_INDEX_LEB
|
||||
case 1: return .TABLE_INDEX_SLEB, true // R_WASM_TABLE_INDEX_SLEB
|
||||
case 2: return .TABLE_INDEX_I32, true // R_WASM_TABLE_INDEX_I32
|
||||
case 3: return .MEMORY_ADDR_LEB, true // R_WASM_MEMORY_ADDR_LEB
|
||||
case 4: return .MEMORY_ADDR_SLEB, true // R_WASM_MEMORY_ADDR_SLEB
|
||||
case 5: return .MEMORY_ADDR_I32, true // R_WASM_MEMORY_ADDR_I32
|
||||
case 6: return .TYPE_INDEX_LEB, true // R_WASM_TYPE_INDEX_LEB
|
||||
case 7: return .GLOBAL_INDEX_LEB, true // R_WASM_GLOBAL_INDEX_LEB
|
||||
case 20: return .TABLE_NUMBER_LEB, true // R_WASM_TABLE_NUMBER_LEB
|
||||
}
|
||||
return .NONE, false
|
||||
}
|
||||
|
||||
// MEMORY_ADDR_* (3,4,5) and the *_OFFSET_I32 (8,9) forms carry a trailing
|
||||
// signed-LEB addend; the index-type relocations do not.
|
||||
@(require_results)
|
||||
reloc_has_addend :: proc(code: u8) -> bool {
|
||||
switch code {
|
||||
case 3, 4, 5, 8, 9: return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reloc_field_size :: proc(t: wasm.Relocation_Type) -> u8 {
|
||||
#partial switch t {
|
||||
case .TABLE_INDEX_I32, .MEMORY_ADDR_I32:
|
||||
return 4 // 4-byte LE field
|
||||
}
|
||||
return 5 // 5-byte padded (S)LEB field
|
||||
}
|
||||
191
core/rexcode/wasm/operands.odin
Normal file
191
core/rexcode/wasm/operands.odin
Normal file
@@ -0,0 +1,191 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly OPERANDS
|
||||
// =============================================================================
|
||||
//
|
||||
// WASM operands are not registers or addressing modes; they are *immediates*
|
||||
// that follow the opcode in the byte stream:
|
||||
//
|
||||
// i32.const 42 IMMEDIATE immediate = 42 (signed LEB128)
|
||||
// f64.const 3.14 IMMEDIATE immediate = bits(3.14) (8 LE bytes)
|
||||
// local.get 0 INDEX index = 0, idx_kind = LOCAL (unsigned LEB128)
|
||||
// call $f INDEX index = funcidx, idx_kind = FUNC
|
||||
// br 1 INDEX index = 1, idx_kind = LABEL (branch depth)
|
||||
// i32.load align=2 off=8 MEMARG memarg = {align = 2, offset = 8}
|
||||
// block (result i32) BLOCK_TYPE block_type = .I32
|
||||
//
|
||||
// Branching in WASM is *structured*: `br`/`br_if`/`br_table` take a relative
|
||||
// label depth (an unsigned immediate), not a PC-relative byte offset. There
|
||||
// are therefore no PC-relative relocations and the isa label-inference path is
|
||||
// not used; the array-index `Label_Definition` machinery is re-exported for
|
||||
// contract parity but WASM control flow does not consume it.
|
||||
//
|
||||
// Relocations *are* real, but for the object-file index spaces (function /
|
||||
// global / table / type / data / elem indices that a linker fixes up). An
|
||||
// INDEX operand flagged `symbolic` carries a label id and is emitted as a
|
||||
// fixed-width 5-byte LEB placeholder plus a Relocation entry. `op_label`
|
||||
// (required by the contract) produces exactly such a symbolic function index.
|
||||
|
||||
Operand_Kind :: enum u8 {
|
||||
NONE,
|
||||
IMMEDIATE, // i32/i64/f32/f64 constant (floats stored as raw bits)
|
||||
INDEX, // LEB128 unsigned index into one of the index spaces
|
||||
MEMARG, // load/store alignment + offset pair
|
||||
BLOCK_TYPE, // block / loop / if signature
|
||||
}
|
||||
|
||||
// Which index space an INDEX operand addresses. Drives matching, relocation
|
||||
// type selection, and printer annotation.
|
||||
Index_Kind :: enum u8 {
|
||||
NONE,
|
||||
LOCAL,
|
||||
GLOBAL,
|
||||
FUNC,
|
||||
TYPE,
|
||||
TABLE,
|
||||
MEMORY,
|
||||
LABEL, // br / br_if / br_table relative depth
|
||||
DATA,
|
||||
ELEM,
|
||||
}
|
||||
|
||||
Operand_Flags :: bit_field u8 {
|
||||
symbolic: bool | 1, // INDEX value is a label id needing a relocation
|
||||
is_float: bool | 1, // IMMEDIATE holds float bits (vs a signed integer)
|
||||
_: u8 | 6,
|
||||
}
|
||||
|
||||
// Load/store immediate: alignment hint (log2 bytes) + static offset.
|
||||
Memarg :: struct #packed {
|
||||
offset: u32,
|
||||
align: u32,
|
||||
}
|
||||
#assert(size_of(Memarg) == 8)
|
||||
|
||||
// Block signature. Negative sentinels are the s33 single-byte forms; a
|
||||
// non-negative value is a type index encoded as a positive signed LEB128.
|
||||
Block_Type :: enum i64 {
|
||||
EMPTY = -64, // 0x40
|
||||
I32 = -1, // 0x7F
|
||||
I64 = -2, // 0x7E
|
||||
F32 = -3, // 0x7D
|
||||
F64 = -4, // 0x7C
|
||||
V128 = -5, // 0x7B
|
||||
FUNCREF = -16, // 0x70
|
||||
EXTERNREF = -17, // 0x6F
|
||||
}
|
||||
|
||||
Operand :: struct #packed {
|
||||
using _: struct #raw_union {
|
||||
reg: Register, // REGISTER (vestigial)
|
||||
memarg: Memarg, // MEMARG
|
||||
immediate: i64, // IMMEDIATE (int value or float bits) / BLOCK_TYPE (s33)
|
||||
index: u32, // INDEX (value, or label id when symbolic)
|
||||
},
|
||||
kind: Operand_Kind,
|
||||
idx_kind: Index_Kind,
|
||||
size: u8, // value width in bytes where meaningful (4/8)
|
||||
flags: Operand_Flags,
|
||||
}
|
||||
#assert(size_of(Operand) == 12)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Generic constructors (contract surface)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@(require_results)
|
||||
op_imm :: #force_inline proc "contextless" (v: i64, size: u8) -> Operand {
|
||||
return Operand{immediate = v, kind = .IMMEDIATE, size = size}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
op_mem :: #force_inline proc "contextless" (m: Memarg, size: u8 = 0) -> Operand {
|
||||
return Operand{memarg = m, kind = .MEMARG, size = size}
|
||||
}
|
||||
|
||||
// Symbolic function reference: emitted as a relocatable funcidx placeholder.
|
||||
@(require_results)
|
||||
op_label :: #force_inline proc "contextless" (label_id: u32, size: u8 = 5) -> Operand {
|
||||
return Operand{index = label_id, kind = .INDEX, idx_kind = .FUNC, size = size, flags = {symbolic = true}}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Numeric constants
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@(require_results)
|
||||
op_i32 :: #force_inline proc "contextless" (v: i32) -> Operand {
|
||||
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 4}
|
||||
}
|
||||
@(require_results)
|
||||
op_i64 :: #force_inline proc "contextless" (v: i64) -> Operand {
|
||||
return Operand{immediate = v, kind = .IMMEDIATE, size = 8}
|
||||
}
|
||||
@(require_results)
|
||||
op_f32 :: #force_inline proc "contextless" (v: f32) -> Operand {
|
||||
return Operand{immediate = i64(transmute(u32)v), kind = .IMMEDIATE, size = 4, flags = {is_float = true}}
|
||||
}
|
||||
@(require_results)
|
||||
op_f64 :: #force_inline proc "contextless" (v: f64) -> Operand {
|
||||
return Operand{immediate = transmute(i64)v, kind = .IMMEDIATE, size = 8, flags = {is_float = true}}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Memory argument + block type
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@(require_results)
|
||||
memarg :: #force_inline proc "contextless" (align, offset: u32) -> Memarg {
|
||||
return Memarg{align = align, offset = offset}
|
||||
}
|
||||
@(require_results)
|
||||
op_memarg :: #force_inline proc "contextless" (align, offset: u32) -> Operand {
|
||||
return Operand{memarg = Memarg{align = align, offset = offset}, kind = .MEMARG}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
op_blocktype :: #force_inline proc "contextless" (bt: Block_Type) -> Operand {
|
||||
return Operand{immediate = i64(bt), kind = .BLOCK_TYPE}
|
||||
}
|
||||
@(require_results)
|
||||
op_block_typeidx :: #force_inline proc "contextless" (type_index: u32) -> Operand {
|
||||
return Operand{immediate = i64(type_index), kind = .BLOCK_TYPE}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Index-space constructors (one per space; all unsigned LEB128 on the wire)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@(require_results)
|
||||
op_index :: #force_inline proc "contextless" (kind: Index_Kind, value: u32) -> Operand {
|
||||
return Operand{index = value, kind = .INDEX, idx_kind = kind}
|
||||
}
|
||||
|
||||
@(require_results) op_local :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.LOCAL, n) }
|
||||
@(require_results) op_global :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.GLOBAL, n) }
|
||||
@(require_results) op_func :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.FUNC, n) }
|
||||
@(require_results) op_type :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.TYPE, n) }
|
||||
@(require_results) op_table :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.TABLE, n) }
|
||||
@(require_results) op_memory :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.MEMORY, n) }
|
||||
@(require_results) op_data :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.DATA, n) }
|
||||
@(require_results) op_elem :: #force_inline proc "contextless" (n: u32) -> Operand { return op_index(.ELEM, n) }
|
||||
|
||||
// Branch label depth (number of enclosing blocks to break out of).
|
||||
@(require_results) op_labelidx :: #force_inline proc "contextless" (depth: u32) -> Operand { return op_index(.LABEL, depth) }
|
||||
|
||||
// ref.null heap type (encoded as a single value-type byte).
|
||||
@(require_results)
|
||||
op_reftype :: #force_inline proc "contextless" (t: Value_Type) -> Operand {
|
||||
return Operand{immediate = i64(t), kind = .IMMEDIATE, size = 1}
|
||||
}
|
||||
|
||||
// SIMD lane index (single byte) for extract_lane / replace_lane / load_lane /
|
||||
// store_lane operators.
|
||||
@(require_results)
|
||||
op_lane :: #force_inline proc "contextless" (n: u8) -> Operand {
|
||||
return Operand{immediate = i64(n), kind = .IMMEDIATE, size = 1}
|
||||
}
|
||||
343
core/rexcode/wasm/printer.odin
Normal file
343
core/rexcode/wasm/printer.odin
Normal file
@@ -0,0 +1,343 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:os"
|
||||
import "core:io"
|
||||
import "core:rexcode/isa"
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly PRINTER
|
||||
// =============================================================================
|
||||
//
|
||||
// Emits WebAssembly text-format (WAT) instruction syntax: the folded-stack
|
||||
// form is not reconstructed (that needs structure the linear stream does not
|
||||
// carry); instead each instruction prints on its own line as
|
||||
//
|
||||
// <mnemonic> <immediate>*
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// i32.const 42
|
||||
// local.get 0
|
||||
// i32.add
|
||||
// call 3
|
||||
// block (result i32)
|
||||
// i32.load offset=8 align=2
|
||||
// br_table 0 1 2 ; cases 0 1, default 2
|
||||
// ref.null func
|
||||
// f64.const 3.14
|
||||
//
|
||||
// Mnemonic spelling comes from the explicit MNEMONIC_NAMES table (WASM mixes
|
||||
// '.' and '_' irregularly, e.g. `local.get` vs `i32.trunc_f32_s`). WASM has
|
||||
// no register file, so register printing is vestigial.
|
||||
|
||||
Token :: isa.Token
|
||||
Token_Kind :: isa.Token_Kind
|
||||
Print_Options :: isa.Print_Options
|
||||
Print_Result :: isa.Print_Result
|
||||
DEFAULT_PRINT_OPTIONS :: isa.DEFAULT_PRINT_OPTIONS
|
||||
|
||||
mnemonic_to_string :: proc(m: Mnemonic, lowercase: bool = true, allocator := context.temp_allocator) -> string {
|
||||
sb := strings.builder_make(allocator)
|
||||
write_mnemonic(&sb, m, !lowercase)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Core sbprint
|
||||
// =============================================================================
|
||||
|
||||
sbprint :: proc(
|
||||
sb: ^strings.Builder,
|
||||
instructions: []Instruction,
|
||||
inst_info: []Instruction_Info,
|
||||
options: ^Print_Options = nil,
|
||||
label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
opts := options
|
||||
if opts == nil {
|
||||
@(static) defaults := DEFAULT_PRINT_OPTIONS
|
||||
opts = &defaults
|
||||
}
|
||||
|
||||
for &inst, i in instructions {
|
||||
offset := inst_info[i].offset if i < len(inst_info) else u32(0)
|
||||
|
||||
strings.write_string(sb, opts.indent)
|
||||
if opts.show_offsets {
|
||||
isa.print_hex(sb, u64(offset), opts)
|
||||
strings.write_string(sb, ": ")
|
||||
}
|
||||
|
||||
write_mnemonic(sb, inst.mnemonic, opts.uppercase)
|
||||
|
||||
// br_table prints its case vector followed by the default depth.
|
||||
#partial switch inst.mnemonic {
|
||||
case .BR_TABLE:
|
||||
for t in inst.targets {
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_u64(sb, u64(t))
|
||||
}
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_u64(sb, u64(inst.ops[0].index))
|
||||
case .V128_CONST:
|
||||
strings.write_string(sb, " i8x16")
|
||||
for bb in inst.bytes {
|
||||
strings.write_byte(sb, ' ')
|
||||
isa.print_hex(sb, u64(bb), opts)
|
||||
}
|
||||
case .I8X16_SHUFFLE:
|
||||
for bb in inst.bytes {
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_u64(sb, u64(u32(bb)))
|
||||
}
|
||||
case:
|
||||
for slot in 0..<inst.operand_count {
|
||||
strings.write_byte(sb, ' ')
|
||||
write_operand(sb, &inst.ops[slot], inst.mnemonic, label_names, opts)
|
||||
}
|
||||
}
|
||||
|
||||
strings.write_string(sb, opts.separator)
|
||||
}
|
||||
}
|
||||
|
||||
sbprintln :: proc(
|
||||
sb: ^strings.Builder,
|
||||
instructions: []Instruction,
|
||||
inst_info: []Instruction_Info,
|
||||
options: ^Print_Options = nil,
|
||||
label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sbprint(sb, instructions, inst_info, options, label_names)
|
||||
strings.write_byte(sb, '\n')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Sink wrappers
|
||||
// =============================================================================
|
||||
|
||||
print :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
os.write_string(os.stdout, strings.to_string(sb))
|
||||
}
|
||||
|
||||
println :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
os.write_string(os.stdout, strings.to_string(sb))
|
||||
}
|
||||
|
||||
aprint :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
allocator := context.allocator,
|
||||
) -> string {
|
||||
sb := strings.builder_make(allocator)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
aprintln :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
allocator := context.allocator,
|
||||
) -> string {
|
||||
sb := strings.builder_make(allocator)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
tprint :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) -> string {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
tprintln :: proc(
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) -> string {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
bprint :: proc(
|
||||
buf: []u8,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) -> string {
|
||||
sb := strings.builder_from_bytes(buf)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
bprintln :: proc(
|
||||
buf: []u8,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) -> string {
|
||||
sb := strings.builder_from_bytes(buf)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
fprint :: proc(
|
||||
fd: ^os.File,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
os.write_string(fd, strings.to_string(sb))
|
||||
}
|
||||
|
||||
fprintln :: proc(
|
||||
fd: ^os.File,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
os.write_string(fd, strings.to_string(sb))
|
||||
}
|
||||
|
||||
wprint :: proc(
|
||||
w: io.Writer,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprint(&sb, instructions, inst_info, options, label_names)
|
||||
io.write_string(w, strings.to_string(sb))
|
||||
}
|
||||
|
||||
wprintln :: proc(
|
||||
w: io.Writer,
|
||||
instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil,
|
||||
) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprintln(&sb, instructions, inst_info, options, label_names)
|
||||
io.write_string(w, strings.to_string(sb))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Internal writers
|
||||
// =============================================================================
|
||||
|
||||
write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) {
|
||||
name := MNEMONIC_NAMES[m]
|
||||
if name == "" { strings.write_string(sb, "<?>"); return }
|
||||
if uppercase {
|
||||
for i in 0..<len(name) {
|
||||
c := name[i] // to force ASCII
|
||||
if 'a' <= c && c <= 'z' {
|
||||
strings.write_byte(sb, c - 32)
|
||||
} else {
|
||||
strings.write_byte(sb, c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strings.write_string(sb, name)
|
||||
}
|
||||
}
|
||||
|
||||
write_operand :: proc(
|
||||
sb: ^strings.Builder,
|
||||
op: ^Operand,
|
||||
mnemonic: Mnemonic,
|
||||
label_names: ^map[u32]string,
|
||||
opts: ^Print_Options,
|
||||
) {
|
||||
switch op.kind {
|
||||
case .NONE:
|
||||
|
||||
case .IMMEDIATE:
|
||||
if mnemonic == .REF_NULL {
|
||||
write_heap_type(sb, u8(op.immediate))
|
||||
} else if op.flags.is_float {
|
||||
write_float(sb, op)
|
||||
} else {
|
||||
strings.write_i64(sb, op.immediate)
|
||||
}
|
||||
|
||||
case .INDEX:
|
||||
if op.flags.symbolic {
|
||||
write_label(sb, op.index, label_names, opts)
|
||||
} else {
|
||||
strings.write_u64(sb, u64(op.index))
|
||||
}
|
||||
|
||||
case .MEMARG:
|
||||
// WAT prints non-trivial memargs as `align=N offset=N`
|
||||
// omitting either when it is the natural default is a refinement.
|
||||
strings.write_string(sb, "align=")
|
||||
strings.write_u64(sb, u64(op.memarg.align))
|
||||
strings.write_string(sb, " offset=")
|
||||
strings.write_u64(sb, u64(op.memarg.offset))
|
||||
|
||||
case .BLOCK_TYPE:
|
||||
write_block_type(sb, op.immediate)
|
||||
}
|
||||
}
|
||||
|
||||
write_block_type :: proc(sb: ^strings.Builder, v: i64) {
|
||||
switch Block_Type(v) {
|
||||
case .EMPTY: // no result annotation
|
||||
case .I32: strings.write_string(sb, "(result i32)")
|
||||
case .I64: strings.write_string(sb, "(result i64)")
|
||||
case .F32: strings.write_string(sb, "(result f32)")
|
||||
case .F64: strings.write_string(sb, "(result f64)")
|
||||
case .V128: strings.write_string(sb, "(result v128)")
|
||||
case .FUNCREF: strings.write_string(sb, "(result funcref)")
|
||||
case .EXTERNREF: strings.write_string(sb, "(result externref)")
|
||||
case:
|
||||
// non-negative: a type index
|
||||
strings.write_string(sb, "(type ")
|
||||
strings.write_u64(sb, u64(u32(v)))
|
||||
strings.write_byte(sb, ')')
|
||||
}
|
||||
}
|
||||
|
||||
write_heap_type :: proc(sb: ^strings.Builder, b: u8) {
|
||||
#partial switch Value_Type(b) {
|
||||
case .FUNCREF: strings.write_string(sb, "func")
|
||||
case .EXTERNREF: strings.write_string(sb, "extern")
|
||||
case:
|
||||
strings.write_u64(sb, u64(u32(b)))
|
||||
}
|
||||
}
|
||||
|
||||
write_float :: proc(sb: ^strings.Builder, op: ^Operand) {
|
||||
buf: [40]u8
|
||||
if op.size == 4 {
|
||||
f := transmute(f32)u32(op.immediate)
|
||||
s := strconv.write_float(buf[:], f64(f), 'g', -1, 32)
|
||||
strings.write_string(sb, s)
|
||||
} else {
|
||||
f := transmute(f64)u64(op.immediate)
|
||||
s := strconv.write_float(buf[:], f, 'g', -1, 64)
|
||||
strings.write_string(sb, s)
|
||||
}
|
||||
}
|
||||
|
||||
write_label :: proc(
|
||||
sb: ^strings.Builder,
|
||||
label_id: u32,
|
||||
label_names: ^map[u32]string,
|
||||
opts: ^Print_Options,
|
||||
) {
|
||||
if label_names != nil {
|
||||
if name, ok := label_names^[label_id]; ok {
|
||||
strings.write_string(sb, "$")
|
||||
strings.write_quoted_string(sb, name)
|
||||
return
|
||||
}
|
||||
}
|
||||
strings.write_string(sb, "$")
|
||||
strings.write_u64(sb, u64(label_id))
|
||||
}
|
||||
76
core/rexcode/wasm/registers.odin
Normal file
76
core/rexcode/wasm/registers.odin
Normal file
@@ -0,0 +1,76 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly "REGISTERS"
|
||||
// =============================================================================
|
||||
//
|
||||
// WebAssembly is a stack machine: it has no general-purpose register file.
|
||||
// Operands live on an implicit value stack and instructions reference locals,
|
||||
// globals, and various index spaces by LEB128 immediate -- never by register.
|
||||
//
|
||||
// The cross-arch naming contract still asks every package for a `Register`
|
||||
// type plus `reg_hw` / `reg_class` accessors, so we keep the same packed
|
||||
// `distinct u16` scheme (class in the high byte, index in the low byte) used
|
||||
// by the register-machine arches. It is *vestigial* here: the REGISTER
|
||||
// operand kind is never produced by the encoder or decoder, and the value
|
||||
// stack is modelled implicitly. The real per-arch content WASM cares about --
|
||||
// value types and the index spaces -- lives below and in operands.odin.
|
||||
|
||||
Register :: distinct u16
|
||||
|
||||
REG_NONE :: 0x0000
|
||||
|
||||
NONE :: Register(0xFFFF)
|
||||
|
||||
@(require_results)
|
||||
reg_hw :: #force_inline proc "contextless" (r: Register) -> u8 {
|
||||
return u8(r) & 0xFF
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reg_class :: #force_inline proc "contextless" (r: Register) -> u16 {
|
||||
return u16(r) & 0xFF00
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reg_size :: #force_inline proc "contextless" (_: Register) -> u8 {
|
||||
return 0 // no fixed width: the value stack is implicit
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Value types (the bytes WASM actually uses where a register would otherwise
|
||||
// appear: block result/param types, ref.null heap types, select t* types).
|
||||
//
|
||||
// The numeric byte is the WASM binary encoding; the same byte sign-extends to
|
||||
// the negative s33 value used inside a blocktype. See operands.odin /
|
||||
// Block_Type for how these participate in block / loop / if.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Value_Type :: enum u8 {
|
||||
I32 = 0x7F,
|
||||
I64 = 0x7E,
|
||||
F32 = 0x7D,
|
||||
F64 = 0x7C,
|
||||
V128 = 0x7B,
|
||||
FUNCREF = 0x70,
|
||||
EXTERNREF = 0x6F,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
value_type_is_num :: #force_inline proc "contextless" (t: Value_Type) -> bool {
|
||||
#partial switch t {
|
||||
case .I32, .I64, .F32, .F64: return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
value_type_is_ref :: #force_inline proc "contextless" (t: Value_Type) -> bool {
|
||||
#partial switch t {
|
||||
case .FUNCREF, .EXTERNREF: return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
44
core/rexcode/wasm/reloc.odin
Normal file
44
core/rexcode/wasm/reloc.odin
Normal file
@@ -0,0 +1,44 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly RELOCATIONS
|
||||
// =============================================================================
|
||||
//
|
||||
// Per the cross-arch design (§2.4) each arch owns its Relocation_Type. WASM's
|
||||
// relocations are the object-file ("linking") relocations: symbolic index
|
||||
// references the linker fixes up. They are emitted, never PC-relative -- WASM
|
||||
// control flow uses structured label depths, not byte offsets, so the encoder
|
||||
// does not resolve these in a pass 2; it records them and leaves the patching
|
||||
// to the linker. The relocatable LEB encodings are written as fixed-width
|
||||
// 5-byte placeholders so the patched value always fits.
|
||||
//
|
||||
// The subset modelled mirrors the names from the tool-conventions linking
|
||||
// spec used by LLVM / wasm-ld.
|
||||
|
||||
Relocation_Type :: enum u8 {
|
||||
NONE = 0,
|
||||
|
||||
FUNCTION_INDEX_LEB, // funcidx, 5-byte ULEB (call, ref.func)
|
||||
TABLE_INDEX_SLEB, // 5-byte SLEB table element index
|
||||
TABLE_INDEX_I32, // 4-byte LE table element index
|
||||
MEMORY_ADDR_LEB, // linear-memory address, 5-byte ULEB
|
||||
MEMORY_ADDR_SLEB, // linear-memory address, 5-byte SLEB
|
||||
MEMORY_ADDR_I32, // linear-memory address, 4-byte LE
|
||||
TYPE_INDEX_LEB, // typeidx, 5-byte ULEB (call_indirect)
|
||||
GLOBAL_INDEX_LEB, // globalidx, 5-byte ULEB
|
||||
TABLE_NUMBER_LEB, // tableidx, 5-byte ULEB
|
||||
}
|
||||
|
||||
Relocation :: struct #packed {
|
||||
offset: u32, // byte offset of the relocatable field
|
||||
label_id: u32, // symbol / target label id
|
||||
addend: i32,
|
||||
type: Relocation_Type,
|
||||
size: u8, // bytes occupied by the field (5 for LEB, 4 for I32)
|
||||
inst_idx: u16,
|
||||
}
|
||||
#assert(size_of(Relocation) == 16)
|
||||
142
core/rexcode/wasm/tests/pipeline_smoke.odin
Normal file
142
core/rexcode/wasm/tests/pipeline_smoke.odin
Normal file
@@ -0,0 +1,142 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm_tests
|
||||
|
||||
// End-to-end WASM pipeline: build a short instruction sequence, encode it,
|
||||
// assert the exact byte stream against hand-computed LEB128 encodings, then
|
||||
// decode the bytes back and confirm the mnemonics/operands round-trip, and
|
||||
// finally print the decoded form and check the WAT text.
|
||||
//
|
||||
// Covers: nullary ops, signed-LEB constants, index immediates, a blocktype,
|
||||
// a memarg, and the br_table vector form.
|
||||
//
|
||||
// Run with: odin run wasm/tests
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import wasm "../"
|
||||
|
||||
@(private="file") rpasses := 0
|
||||
@(private="file") rfailures := 0
|
||||
|
||||
@(private="file")
|
||||
ok :: proc(name: string, cond: bool) {
|
||||
if cond {
|
||||
fmt.printfln(" [ok] %s", name)
|
||||
rpasses += 1
|
||||
} else {
|
||||
fmt.printfln(" [FAIL] %s", name)
|
||||
rfailures += 1
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
eq_bytes :: proc(name: string, got, want: []u8) {
|
||||
if string(got) == string(want) {
|
||||
fmt.printfln(" [ok] %s (% x)", name, got)
|
||||
rpasses += 1
|
||||
} else {
|
||||
fmt.printfln(" [FAIL] %-18s got=[% x] want=[% x]", name, got, want)
|
||||
rfailures += 1
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
eq_str :: proc(name, got, want: string) {
|
||||
if got == want {
|
||||
fmt.printfln(" [ok] %-18s %q", name, got)
|
||||
rpasses += 1
|
||||
} else {
|
||||
fmt.printfln(" [FAIL] %-18s got=%q want=%q", name, got, want)
|
||||
rfailures += 1
|
||||
}
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
fmt.println("== wasm encode/decode/print pipeline ==")
|
||||
|
||||
insts := []wasm.Instruction{
|
||||
wasm.inst_i(.I32_CONST, wasm.op_i32(42)), // 0x41 0x2A
|
||||
wasm.inst_idx(.LOCAL_GET, wasm.op_local(0)), // 0x20 0x00
|
||||
wasm.inst_none(.I32_ADD), // 0x6A
|
||||
wasm.inst_idx(.CALL, wasm.op_func(3)), // 0x10 0x03
|
||||
wasm.inst_block(.BLOCK, .I32), // 0x02 0x7F
|
||||
wasm.inst_memarg(.I32_LOAD, wasm.memarg(2, 8)), // 0x28 0x02 0x08
|
||||
wasm.inst_none(.END), // 0x0B
|
||||
}
|
||||
|
||||
code := make([]u8, wasm.encode_max_code_size(len(insts)))
|
||||
defer delete(code)
|
||||
relocs: [dynamic]wasm.Relocation
|
||||
errors: [dynamic]wasm.Error
|
||||
defer delete(relocs)
|
||||
defer delete(errors)
|
||||
|
||||
n, enc_ok := wasm.encode(insts, nil, code, &relocs, &errors)
|
||||
ok("encode ok", enc_ok && len(errors) == 0)
|
||||
|
||||
want := []u8{
|
||||
0x41, 0x2A,
|
||||
0x20, 0x00,
|
||||
0x6A,
|
||||
0x10, 0x03,
|
||||
0x02, 0x7F,
|
||||
0x28, 0x02, 0x08,
|
||||
0x0B,
|
||||
}
|
||||
eq_bytes("byte stream", code[:n], want)
|
||||
|
||||
// ---- br_table on its own (vector immediate) ----------------------------
|
||||
bt := []wasm.Instruction{
|
||||
wasm.inst_br_table([]u32{0, 1}, 2), // 0x0E 0x02 0x00 0x01 0x02
|
||||
}
|
||||
bt_code := make([]u8, 32)
|
||||
defer delete(bt_code)
|
||||
bt_relocs: [dynamic]wasm.Relocation
|
||||
bt_errors: [dynamic]wasm.Error
|
||||
defer delete(bt_relocs)
|
||||
defer delete(bt_errors)
|
||||
bn, _ := wasm.encode(bt, nil, bt_code, &bt_relocs, &bt_errors)
|
||||
eq_bytes("br_table bytes", bt_code[:bn], []u8{0x0E, 0x02, 0x00, 0x01, 0x02})
|
||||
|
||||
// ---- decode round-trip --------------------------------------------------
|
||||
dinsts: [dynamic]wasm.Instruction
|
||||
dinfo: [dynamic]wasm.Instruction_Info
|
||||
dlabels:[dynamic]wasm.Label_Definition
|
||||
derrs: [dynamic]wasm.Error
|
||||
defer delete(dinsts)
|
||||
defer delete(dinfo)
|
||||
defer delete(dlabels)
|
||||
defer delete(derrs)
|
||||
|
||||
dn, dec_ok := wasm.decode(code[:n], nil, &dinsts, &dinfo, &dlabels, &derrs)
|
||||
ok("decode ok", dec_ok && len(derrs) == 0)
|
||||
ok("decode byte count", dn == n)
|
||||
ok("decode count", len(dinsts) == len(insts))
|
||||
|
||||
if len(dinsts) == len(insts) {
|
||||
ok("m[0] i32.const", dinsts[0].mnemonic == .I32_CONST && dinsts[0].ops[0].immediate == 42)
|
||||
ok("m[1] local.get", dinsts[1].mnemonic == .LOCAL_GET && dinsts[1].ops[0].index == 0)
|
||||
ok("m[2] i32.add", dinsts[2].mnemonic == .I32_ADD && dinsts[2].operand_count == 0)
|
||||
ok("m[3] call", dinsts[3].mnemonic == .CALL && dinsts[3].ops[0].idx_kind == .FUNC)
|
||||
ok("m[4] block", dinsts[4].mnemonic == .BLOCK && dinsts[4].ops[0].kind == .BLOCK_TYPE)
|
||||
ok("m[5] i32.load", dinsts[5].mnemonic == .I32_LOAD && dinsts[5].ops[0].memarg.offset == 8)
|
||||
ok("m[6] end", dinsts[6].mnemonic == .END)
|
||||
}
|
||||
|
||||
// ---- print --------------------------------------------------------------
|
||||
text := wasm.tprint(dinsts[:], dinfo[:], dlabels[:])
|
||||
want_text :=
|
||||
" i32.const 42\n" +
|
||||
" local.get 0\n" +
|
||||
" i32.add\n" +
|
||||
" call 3\n" +
|
||||
" block (result i32)\n" +
|
||||
" i32.load offset=8 align=2\n" +
|
||||
" end\n"
|
||||
eq_str("disassembly", text, want_text)
|
||||
|
||||
fmt.printfln("\n%d passed, %d failed", rpasses, rfailures)
|
||||
if rfailures > 0 { os.exit(1) }
|
||||
}
|
||||
86
core/rexcode/wasm/tests/smoke.odin
Normal file
86
core/rexcode/wasm/tests/smoke.odin
Normal file
@@ -0,0 +1,86 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm_tests
|
||||
|
||||
// Spot-check ENCODING_TABLE entries against the canonical opcode bytes from
|
||||
// the WebAssembly core specification (binary format, §5.4). One or two
|
||||
// representatives from each opcode region, plus both 0xFC misc endpoints.
|
||||
//
|
||||
// Run with: odin run wasm/tests
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import wasm "../"
|
||||
|
||||
@(private="file") passes := 0
|
||||
@(private="file") failures := 0
|
||||
|
||||
@(private="file")
|
||||
check :: proc(name: string, m: wasm.Mnemonic, want_prefix: u8, want_opcode: u16) {
|
||||
e := wasm.ENCODING_TABLE[m]
|
||||
if e.prefix != want_prefix || e.opcode != want_opcode {
|
||||
fmt.printfln(" [FAIL] %-22s got prefix=%02x op=%02x want prefix=%02x op=%02x",
|
||||
name, e.prefix, e.opcode, want_prefix, want_opcode)
|
||||
failures += 1
|
||||
return
|
||||
}
|
||||
fmt.printfln(" [ok] %-22s prefix=%02x op=%02x", name, e.prefix, e.opcode)
|
||||
passes += 1
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
fmt.println("== wasm encoding-table spot checks ==")
|
||||
|
||||
// control
|
||||
check("unreachable", .UNREACHABLE, 0x00, 0x00)
|
||||
check("block", .BLOCK, 0x00, 0x02)
|
||||
check("br_table", .BR_TABLE, 0x00, 0x0E)
|
||||
check("call", .CALL, 0x00, 0x10)
|
||||
check("call_indirect", .CALL_INDIRECT, 0x00, 0x11)
|
||||
|
||||
// parametric / variable
|
||||
check("drop", .DROP, 0x00, 0x1A)
|
||||
check("local.get", .LOCAL_GET, 0x00, 0x20)
|
||||
check("global.set", .GLOBAL_SET, 0x00, 0x24)
|
||||
|
||||
// memory
|
||||
check("i32.load", .I32_LOAD, 0x00, 0x28)
|
||||
check("i64.store32", .I64_STORE32, 0x00, 0x3E)
|
||||
check("memory.size", .MEMORY_SIZE, 0x00, 0x3F)
|
||||
check("memory.grow", .MEMORY_GROW, 0x00, 0x40)
|
||||
|
||||
// numeric
|
||||
check("i32.const", .I32_CONST, 0x00, 0x41)
|
||||
check("f64.const", .F64_CONST, 0x00, 0x44)
|
||||
check("i32.add", .I32_ADD, 0x00, 0x6A)
|
||||
check("i64.mul", .I64_MUL, 0x00, 0x7E)
|
||||
check("f32.add", .F32_ADD, 0x00, 0x92)
|
||||
check("f64.sqrt", .F64_SQRT, 0x00, 0x9F)
|
||||
|
||||
// conversions / sign-extension / reftypes
|
||||
check("i32.wrap_i64", .I32_WRAP_I64, 0x00, 0xA7)
|
||||
check("i32.extend8_s", .I32_EXTEND8_S, 0x00, 0xC0)
|
||||
check("ref.null", .REF_NULL, 0x00, 0xD0)
|
||||
check("ref.func", .REF_FUNC, 0x00, 0xD2)
|
||||
|
||||
// 0xFC misc group endpoints
|
||||
check("i32.trunc_sat_f32_s", .I32_TRUNC_SAT_F32_S, 0xFC, 0)
|
||||
check("memory.init", .MEMORY_INIT, 0xFC, 8)
|
||||
check("table.fill", .TABLE_FILL, 0xFC, 17)
|
||||
|
||||
// 0xFD SIMD group
|
||||
check("v128.load", .V128_LOAD, 0xFD, 0x00)
|
||||
check("v128.const", .V128_CONST, 0xFD, 0x0C)
|
||||
check("i8x16.shuffle", .I8X16_SHUFFLE, 0xFD, 0x0D)
|
||||
check("i32x4.add", .I32X4_ADD, 0xFD, 0xAE)
|
||||
check("simd hi (relaxed)", .I32X4_RELAXED_DOT_I8X16_I7X16_ADD_S, 0xFD, 0x113)
|
||||
|
||||
// 0xFE threads / atomics group
|
||||
check("memory.atomic.notify", .MEMORY_ATOMIC_NOTIFY, 0xFE, 0x00)
|
||||
check("atomic.fence", .ATOMIC_FENCE, 0xFE, 0x03)
|
||||
check("i32.atomic.load", .I32_ATOMIC_LOAD, 0xFE, 0x10)
|
||||
|
||||
fmt.printfln("\n%d passed, %d failed", passes, failures)
|
||||
if failures > 0 { os.exit(1) }
|
||||
}
|
||||
99
core/rexcode/wasm/tools/dump_verify_input.odin
Normal file
99
core/rexcode/wasm/tools/dump_verify_input.odin
Normal file
@@ -0,0 +1,99 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package main
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly verification manifest dumper
|
||||
// =============================================================================
|
||||
//
|
||||
// Encodes one representative instruction per mnemonic (synthesising operands
|
||||
// that fit the entry's immediate layout) and writes:
|
||||
//
|
||||
// /tmp/rexcode_wasm_input.hex -- comma-separated LE hex bytes, one row each
|
||||
// /tmp/rexcode_wasm_meta.txt -- "<mnemonic>\t<prefix>\t<opcode>\t<size>"
|
||||
//
|
||||
// The canonical external oracle for cross-checking these bytes is wabt's
|
||||
// `wasm-objdump` / `wasm2wat`, or LLVM's `llvm-mc -triple=wasm32`. Feed the
|
||||
// hex rows through the disassembler and diff its mnemonics against the meta
|
||||
// file.
|
||||
//
|
||||
// Run: cd wasm && odin run tools/dump_verify_input.odin -file
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
import w "../"
|
||||
|
||||
main :: proc() {
|
||||
fmt.println("Dumping WASM verification manifest...")
|
||||
|
||||
hex_buf, meta_buf: strings.Builder
|
||||
strings.builder_init(&hex_buf)
|
||||
strings.builder_init(&meta_buf)
|
||||
defer strings.builder_destroy(&hex_buf)
|
||||
defer strings.builder_destroy(&meta_buf)
|
||||
|
||||
code: [32]u8
|
||||
count := 0
|
||||
|
||||
for mn in w.Mnemonic {
|
||||
if mn == .INVALID { continue }
|
||||
form := w.ENCODING_TABLE[mn]
|
||||
|
||||
inst := synth(mn, form)
|
||||
one := []w.Instruction{inst}
|
||||
|
||||
relocs: [dynamic]w.Relocation
|
||||
errors: [dynamic]w.Error
|
||||
defer delete(relocs)
|
||||
defer delete(errors)
|
||||
|
||||
n := w.encode(one, nil, code[:], &relocs, &errors) or_continue
|
||||
|
||||
for i in 0..<n {
|
||||
if i > 0 { strings.write_byte(&hex_buf, ',') }
|
||||
fmt.sbprintf(&hex_buf, "0x%02x", code[i])
|
||||
}
|
||||
strings.write_byte(&hex_buf, '\n')
|
||||
|
||||
fmt.sbprintf(&meta_buf, "%v\t0x%02x\t0x%02x\t%d\n", mn, form.prefix, form.opcode, n)
|
||||
count += 1
|
||||
}
|
||||
|
||||
_ = os.write_entire_file("/tmp/rexcode_wasm_input.hex", hex_buf.buf[:])
|
||||
_ = os.write_entire_file("/tmp/rexcode_wasm_meta.txt", meta_buf.buf[:])
|
||||
|
||||
fmt.printf("Wrote %d entries.\n", count)
|
||||
}
|
||||
|
||||
// Build a minimal valid instruction for `mn` whose operands satisfy the
|
||||
// immediate layout in `form`.
|
||||
synth :: proc(mn: w.Mnemonic, form: w.Encoding) -> w.Instruction {
|
||||
if mn == .BR_TABLE {
|
||||
@(static) tbl := [1]u32{0}
|
||||
return w.inst_br_table(tbl[:], 0)
|
||||
}
|
||||
|
||||
inst := w.Instruction{mnemonic = mn}
|
||||
slot := 0
|
||||
for k in form.imm {
|
||||
switch k {
|
||||
case .NONE, .ZERO_BYTE:
|
||||
// no operand
|
||||
case .BLOCKTYPE: inst.ops[slot] = w.op_blocktype(.EMPTY); slot += 1
|
||||
case .I32: inst.ops[slot] = w.op_i32(1); slot += 1
|
||||
case .I64: inst.ops[slot] = w.op_i64(1); slot += 1
|
||||
case .F32: inst.ops[slot] = w.op_f32(1); slot += 1
|
||||
case .F64: inst.ops[slot] = w.op_f64(1); slot += 1
|
||||
case .IDX: inst.ops[slot] = w.op_func(0); slot += 1
|
||||
case .MEMARG: inst.ops[slot] = w.op_memarg(0, 0); slot += 1
|
||||
case .REFTYPE: inst.ops[slot] = w.op_reftype(.FUNCREF); slot += 1
|
||||
case .LANE: inst.ops[slot] = w.op_lane(0); slot += 1
|
||||
case .LANES16: // 16-byte value lives in inst.bytes (left zero), no operand
|
||||
case .BR_TABLE: // handled above
|
||||
}
|
||||
}
|
||||
inst.operand_count = u8(slot)
|
||||
return inst
|
||||
}
|
||||
@@ -269,7 +269,7 @@ decode_opcode :: proc(state: ^Decoder_State) -> (entry: ^Decode_Entry, vex_entry
|
||||
}
|
||||
|
||||
// Handle VEX/EVEX encoded instructions
|
||||
if state.vex_type != .NONE {
|
||||
if state.vex_type != nil {
|
||||
return decode_opcode_vex(state)
|
||||
}
|
||||
|
||||
@@ -573,22 +573,14 @@ decode_opcode_vex :: #force_inline proc(state: ^Decoder_State) -> (entry: ^Decod
|
||||
decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: Instruction, err: Error_Code) {
|
||||
inst.mnemonic = entry.mnemonic
|
||||
|
||||
// Check if we need ModR/M
|
||||
needs_modrm := false
|
||||
for _, i in entry.enc {
|
||||
enc := entry.enc[i]
|
||||
if enc == .MR || enc == .REG || enc == .VVVV {
|
||||
needs_modrm = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
modrm: u8 = 0
|
||||
modrm_info: ModRM_Info
|
||||
sib: u8 = 0
|
||||
sib_info: SIB_Info
|
||||
has_sib := false
|
||||
|
||||
needs_modrm := entry.flags.needs_modrm
|
||||
|
||||
if needs_modrm {
|
||||
if state.position >= len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
@@ -617,25 +609,18 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I
|
||||
}
|
||||
|
||||
// Decode each operand
|
||||
for _, i in entry.ops {
|
||||
op_type := entry.ops[i]
|
||||
op_count := entry.flags.op_count
|
||||
for i in 0..<op_count {
|
||||
op_enc := entry.enc[i]
|
||||
|
||||
if op_type == .NONE {
|
||||
break
|
||||
}
|
||||
|
||||
// i386: default_64 entries have R64/RM64 operand types but
|
||||
// really mean R32/RM32 in 32-bit mode (same encoded bytes).
|
||||
effective := mode_rewrite_op_type(op_type, state.mode, entry.flags.default_64)
|
||||
inst.ops[i], err = decode_single_operand(state, effective, op_enc, modrm_info, sib_info, has_sib)
|
||||
if err != nil {
|
||||
return {}, err
|
||||
}
|
||||
inst.operand_count += 1
|
||||
effective := mode_rewrite_op_type(entry.ops[i], state.mode, entry.flags.default_64)
|
||||
inst.ops[i] = decode_single_operand(state, effective, op_enc, modrm_info, sib_info, has_sib) or_return
|
||||
}
|
||||
inst.operand_count += op_count
|
||||
|
||||
return inst, .NONE
|
||||
return
|
||||
}
|
||||
|
||||
decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) -> (inst: Instruction, err: Error_Code) {
|
||||
@@ -664,30 +649,25 @@ decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) ->
|
||||
}
|
||||
|
||||
// Decode each operand
|
||||
for _, i in entry.ops {
|
||||
op_type := entry.ops[i]
|
||||
op_enc := entry.enc[i]
|
||||
|
||||
for op_type, i in entry.ops {
|
||||
if op_type == .NONE {
|
||||
break
|
||||
}
|
||||
op_enc := entry.enc[i]
|
||||
|
||||
inst.ops[i], err = decode_single_operand_vex(state, op_type, op_enc, modrm_info, sib_info, has_sib)
|
||||
if err != nil {
|
||||
return {}, err
|
||||
}
|
||||
inst.ops[i] = decode_single_operand_vex(state, op_type, op_enc, modrm_info, sib_info, has_sib) or_return
|
||||
inst.operand_count += 1
|
||||
}
|
||||
|
||||
return inst, .NONE
|
||||
return
|
||||
}
|
||||
|
||||
decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_enc: Operand_Encoding,
|
||||
modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool) -> (op: Operand, err: Error_Code) {
|
||||
modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool) -> (op: Operand, err: Error_Code) {
|
||||
|
||||
switch op_enc {
|
||||
case .NONE:
|
||||
return {}, .NONE
|
||||
return
|
||||
|
||||
case .REG:
|
||||
// Register encoded in ModR/M.reg
|
||||
@@ -696,7 +676,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
register_number += 8
|
||||
}
|
||||
reg := decode_register(register_number, op_type, state.rex)
|
||||
return op_reg(reg), .NONE
|
||||
op = op_reg(reg)
|
||||
return
|
||||
|
||||
case .MR:
|
||||
// Register or memory in ModR/M.rm
|
||||
@@ -707,7 +688,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
register_number += 8
|
||||
}
|
||||
reg := decode_register(register_number, op_type, state.rex)
|
||||
return op_reg(reg), .NONE
|
||||
op = op_reg(reg)
|
||||
return
|
||||
} else {
|
||||
// Memory
|
||||
return decode_memory_operand(state, modrm_info, sib_info, has_sib, op_type)
|
||||
@@ -716,43 +698,44 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
case .IB:
|
||||
// 8-bit immediate or rel8
|
||||
if state.position >= len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
err = .BUFFER_TOO_SHORT
|
||||
return
|
||||
}
|
||||
immediate_value := i64(i8(state.data[state.position]))
|
||||
state.position += 1
|
||||
if op_type == .REL8 {
|
||||
return Operand{kind = .RELATIVE, relative = immediate_value, size = 1}, .NONE
|
||||
}
|
||||
return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 1}, .NONE
|
||||
op = Operand{kind = (op_type == .REL8 ? .RELATIVE : .IMMEDIATE), relative = immediate_value, size = 1}
|
||||
return
|
||||
|
||||
case .IW:
|
||||
// 16-bit immediate
|
||||
if state.position + 2 > len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
err = .BUFFER_TOO_SHORT
|
||||
return
|
||||
}
|
||||
immediate_value := i64(i16(u16(state.data[state.position]) | u16(state.data[state.position+1]) << 8))
|
||||
state.position += 2
|
||||
return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 2}, .NONE
|
||||
op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 2}
|
||||
return
|
||||
|
||||
case .ID:
|
||||
// 32-bit immediate or rel32
|
||||
if state.position + 4 > len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
err = .BUFFER_TOO_SHORT
|
||||
return
|
||||
}
|
||||
immediate_value := i64(i32(u32(state.data[state.position]) |
|
||||
u32(state.data[state.position+1]) << 8 |
|
||||
u32(state.data[state.position+2]) << 16 |
|
||||
u32(state.data[state.position+3]) << 24))
|
||||
state.position += 4
|
||||
if op_type == .REL32 {
|
||||
return Operand{kind = .RELATIVE, relative = immediate_value, size = 4}, .NONE
|
||||
}
|
||||
return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 4}, .NONE
|
||||
op = Operand{kind = (op_type == .REL32 ? .RELATIVE : .IMMEDIATE), relative = immediate_value, size = 4}
|
||||
return
|
||||
|
||||
case .IQ:
|
||||
// 64-bit immediate
|
||||
if state.position + 8 > len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
err = .BUFFER_TOO_SHORT
|
||||
return
|
||||
}
|
||||
immediate_value := i64(u64(state.data[state.position]) |
|
||||
u64(state.data[state.position+1]) << 8 |
|
||||
@@ -763,7 +746,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
u64(state.data[state.position+6]) << 48 |
|
||||
u64(state.data[state.position+7]) << 56)
|
||||
state.position += 8
|
||||
return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 8}, .NONE
|
||||
op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 8}
|
||||
return
|
||||
|
||||
case .IMPL:
|
||||
// Implicit register - decode from operand type
|
||||
@@ -776,7 +760,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
register_number += 8
|
||||
}
|
||||
reg := decode_register(register_number, op_type, state.rex)
|
||||
return op_reg(reg), .NONE
|
||||
op = op_reg(reg)
|
||||
return
|
||||
|
||||
case .VVVV:
|
||||
// VEX.vvvv register
|
||||
@@ -785,25 +770,28 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e
|
||||
register_number += 16
|
||||
}
|
||||
reg := decode_register(register_number, op_type, state.rex)
|
||||
return op_reg(reg), .NONE
|
||||
op = op_reg(reg)
|
||||
return
|
||||
|
||||
case .IS4:
|
||||
// Immediate byte with register in high 4 bits
|
||||
if state.position >= len(state.data) {
|
||||
return {}, .BUFFER_TOO_SHORT
|
||||
err = .BUFFER_TOO_SHORT
|
||||
return
|
||||
}
|
||||
immediate_byte := state.data[state.position]
|
||||
state.position += 1
|
||||
register_number := (immediate_byte >> 4) & 0x0F
|
||||
reg := decode_register(register_number, op_type, state.rex)
|
||||
return op_reg(reg), .NONE
|
||||
op = op_reg(reg)
|
||||
return
|
||||
|
||||
case .AAA:
|
||||
// EVEX opmask - already decoded in state
|
||||
return {}, .NONE
|
||||
return
|
||||
}
|
||||
|
||||
return {}, .NONE
|
||||
return
|
||||
}
|
||||
|
||||
decode_single_operand_vex :: proc(state: ^Decoder_State, op_type: Operand_Type, op_enc: Operand_Encoding,
|
||||
@@ -940,7 +928,7 @@ decode_memory_operand :: proc(state: ^Decoder_State, modrm_info: ModRM_Info,
|
||||
// 8.8 Register Decoding Helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
decode_register :: proc(num: u8, op_type: Operand_Type, rex: u8) -> Register {
|
||||
decode_register :: #force_inline proc "contextless" (num: u8, op_type: Operand_Type, rex: u8) -> Register {
|
||||
#partial switch op_type {
|
||||
case .R64, .RM64:
|
||||
return gpr64_from_num(num)
|
||||
@@ -1019,21 +1007,20 @@ decode :: proc(
|
||||
label_defs: ^[dynamic]Label_Definition,
|
||||
errors: ^[dynamic]Error,
|
||||
mode: Mode = ._64,
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
if mode == ._16 {
|
||||
// Real-mode decoding is not implemented; the ModRM addressing
|
||||
// model differs from protected/long mode and needs a separate
|
||||
// decode path. See Mode enum comment in encoding_types.odin.
|
||||
fmt.panicf("x64.decode: Mode._16 (real mode) is not yet supported")
|
||||
}
|
||||
ok = true
|
||||
|
||||
if len(data) == 0 {
|
||||
return Result{success = true}
|
||||
return
|
||||
}
|
||||
|
||||
data_length := len(data)
|
||||
pos: u32 = 0
|
||||
has_errors := false
|
||||
data_length := u32(len(data))
|
||||
|
||||
// Track branch targets for label inference (resolved in pass 2 by isa).
|
||||
pending_branches: [dynamic]isa.Branch_Target
|
||||
@@ -1043,26 +1030,26 @@ decode :: proc(
|
||||
// PASS 1: Decode all instructions, collect branch targets
|
||||
// =========================================================================
|
||||
|
||||
for pos < u32(data_length) {
|
||||
for byte_count < data_length {
|
||||
inst: Instruction
|
||||
info: Instruction_Info
|
||||
|
||||
// Record offset
|
||||
info.offset = pos
|
||||
info.offset = byte_count
|
||||
|
||||
// Initialize decoder state
|
||||
state := Decoder_State{
|
||||
data = data[pos:],
|
||||
data = data[byte_count:],
|
||||
position = 0,
|
||||
mode = mode,
|
||||
segment = NONE,
|
||||
mode = mode,
|
||||
segment = NONE,
|
||||
}
|
||||
|
||||
// Phase 1: Parse prefixes
|
||||
err := decode_prefixes(&state)
|
||||
if err != nil {
|
||||
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
|
||||
has_errors = true
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1080,7 +1067,7 @@ decode :: proc(
|
||||
is_dec := (b & 0x08) != 0
|
||||
reg: Register = state.prefix_66 ? gpr16_from_num(reg_num) : gpr32_from_num(reg_num)
|
||||
|
||||
inst.mnemonic = is_dec ? Mnemonic.DEC : Mnemonic.INC
|
||||
inst.mnemonic = is_dec ? .DEC : .INC
|
||||
inst.operand_count = 1
|
||||
inst.ops[0] = op_reg(reg)
|
||||
inst.length = u8(state.position)
|
||||
@@ -1095,7 +1082,7 @@ decode :: proc(
|
||||
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
pos += u32(state.position)
|
||||
byte_count += u32(state.position)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1106,7 +1093,7 @@ decode :: proc(
|
||||
entry, vex_entry, err = decode_opcode(&state)
|
||||
if err != nil {
|
||||
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
|
||||
has_errors = true
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1117,12 +1104,12 @@ decode :: proc(
|
||||
inst, err = decode_operands(&state, entry)
|
||||
} else {
|
||||
append(errors, Error{inst_idx = u32(len(instructions)), code = .INVALID_OPCODE})
|
||||
has_errors = true
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
|
||||
has_errors = true
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1140,7 +1127,7 @@ decode :: proc(
|
||||
info.rep = inst.flags.rep
|
||||
info.segment = state.segment
|
||||
info.vex_type = state.vex_type
|
||||
if state.vex_type != .NONE && vex_entry != nil {
|
||||
if state.vex_type != nil && vex_entry != nil {
|
||||
// Use encoding requirements to distinguish LIG/WIG from L0/W0
|
||||
// If encoding says LIG, the actual L value doesn't matter for re-encoding
|
||||
// If encoding says L0/L1/L2, we should preserve the actual value
|
||||
@@ -1157,7 +1144,7 @@ decode :: proc(
|
||||
info.evex_b = state.evex_b
|
||||
info.evex_z = state.evex_z
|
||||
info.opmask = state.evex_aaa
|
||||
} else if state.vex_type != .NONE {
|
||||
} else if state.vex_type != nil {
|
||||
// Fallback when vex_entry is nil (shouldn't happen normally)
|
||||
info.vex_l = state.vex_l == 0 ? .L0 : (state.vex_l == 1 ? .L1 : .L2)
|
||||
info.vex_w = state.vex_w ? .W1 : .W0
|
||||
@@ -1167,7 +1154,7 @@ decode :: proc(
|
||||
}
|
||||
|
||||
// Check for relative operands and record pending branch targets
|
||||
inst_end := pos + u32(state.position)
|
||||
inst_end := byte_count + u32(state.position)
|
||||
for op_idx in 0..<inst.operand_count {
|
||||
op := &inst.ops[op_idx]
|
||||
if op.kind == .RELATIVE {
|
||||
@@ -1186,17 +1173,14 @@ decode :: proc(
|
||||
append(instructions, inst)
|
||||
append(inst_info, info)
|
||||
|
||||
pos += u32(state.position)
|
||||
byte_count += u32(state.position)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PASS 2: Infer labels from branch targets within the decoded region
|
||||
// =========================================================================
|
||||
|
||||
isa.infer_labels_from_branches(pending_branches[:], pos, label_defs, relocs)
|
||||
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
||||
|
||||
return Result{
|
||||
byte_count = pos,
|
||||
success = !has_errors,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ encode :: proc(
|
||||
resolve: bool = true,
|
||||
base_address: u64 = 0,
|
||||
mode: Mode = ._64, // i386 vs x86-64 mode
|
||||
) -> Result {
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
if mode == ._16 {
|
||||
// Real-mode encoding is not implemented; the ModRM addressing
|
||||
// model differs from protected/long mode and needs a separate
|
||||
@@ -73,8 +73,7 @@ encode :: proc(
|
||||
fmt.panicf("x64.encode: Mode._16 (real mode) is not yet supported")
|
||||
}
|
||||
|
||||
code_pos: u32 = 0
|
||||
has_errors := false
|
||||
ok = true
|
||||
|
||||
// Temp storage for pending relocations (before resolution)
|
||||
pending_relocations: [dynamic]Relocation
|
||||
@@ -91,19 +90,19 @@ encode :: proc(
|
||||
|
||||
for &inst, instruction_index in instructions {
|
||||
// Record this instruction's byte offset
|
||||
inst_offsets[instruction_index] = code_pos
|
||||
inst_offsets[instruction_index] = byte_count
|
||||
|
||||
// Validate operand_count bounds
|
||||
if inst.operand_count > 4 {
|
||||
append(errors, Error{u32(instruction_index), .INVALID_OPERAND_COUNT, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
|
||||
// Check buffer space
|
||||
if code_pos + MAX_INST_SIZE > u32(len(code)) {
|
||||
if byte_count + MAX_INST_SIZE > u32(len(code)) {
|
||||
append(errors, Error{u32(instruction_index), .BUFFER_OVERFLOW, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -136,7 +135,7 @@ encode :: proc(
|
||||
}
|
||||
if invalid {
|
||||
append(errors, Error{u32(instruction_index), .OPERAND_MISMATCH, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -145,7 +144,7 @@ encode :: proc(
|
||||
encodings := encoding_forms(inst.mnemonic)
|
||||
if len(encodings) == 0 {
|
||||
append(errors, Error{u32(instruction_index), .INVALID_MNEMONIC, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ encode :: proc(
|
||||
|
||||
if matched_enc == nil {
|
||||
append(errors, Error{u32(instruction_index), .NO_MATCHING_ENCODING, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -169,7 +168,7 @@ encode :: proc(
|
||||
// =====================================================================
|
||||
|
||||
enc := matched_enc
|
||||
out := code[code_pos:]
|
||||
out := code[byte_count:]
|
||||
pos: u32 = 0
|
||||
|
||||
// --- Legacy Prefixes ---
|
||||
@@ -398,7 +397,7 @@ encode :: proc(
|
||||
// the instruction is not legal i386.
|
||||
if mode == ._32 && rex != 0 {
|
||||
append(errors, Error{u32(instruction_index), .OPERAND_MISMATCH, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -598,7 +597,7 @@ encode :: proc(
|
||||
case .RELATIVE:
|
||||
// Relative reference - record relocation
|
||||
label_id := u32(user_op.relative)
|
||||
append(&pending_relocations, Relocation{code_pos + pos, label_id, 0, .REL8, 1, u16(instruction_index)})
|
||||
append(&pending_relocations, Relocation{byte_count + pos, label_id, 0, .REL8, 1, u16(instruction_index)})
|
||||
out[pos] = 0
|
||||
pos += 1
|
||||
}
|
||||
@@ -623,7 +622,7 @@ encode :: proc(
|
||||
pos += 4
|
||||
case .RELATIVE:
|
||||
label_id := u32(user_op.relative)
|
||||
append(&pending_relocations, Relocation{code_pos + pos, label_id, 0, .REL32, 4, u16(instruction_index)})
|
||||
append(&pending_relocations, Relocation{byte_count + pos, label_id, 0, .REL32, 4, u16(instruction_index)})
|
||||
out[pos] = 0; out[pos+1] = 0; out[pos+2] = 0; out[pos+3] = 0
|
||||
pos += 4
|
||||
}
|
||||
@@ -639,7 +638,7 @@ encode :: proc(
|
||||
}
|
||||
}
|
||||
|
||||
code_pos += pos
|
||||
byte_count += pos
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -677,7 +676,7 @@ encode :: proc(
|
||||
next_pc := patch_offset + 1
|
||||
if !patch_pcrel_i8(code, patch_offset, target_offset, next_pc, relocation.addend) {
|
||||
append(errors, Error{u32(relocation.inst_idx), .LABEL_OUT_OF_RANGE, {}})
|
||||
has_errors = true
|
||||
ok = false
|
||||
}
|
||||
|
||||
case .REL32:
|
||||
@@ -692,10 +691,7 @@ encode :: proc(
|
||||
}
|
||||
}
|
||||
|
||||
return Result{
|
||||
byte_count = code_pos,
|
||||
success = !has_errors,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -705,21 +701,25 @@ encode :: proc(
|
||||
// Check if instruction matches encoding (inlined for hot path).
|
||||
// `mode` lets default_64 entries match 32-bit operands in i386 and
|
||||
// filters out mode-restricted (mode_32_only) encodings when not in i386.
|
||||
encoding_matches_inline :: #force_inline proc "contextless" (inst: ^Instruction, enc: ^Encoding, mode: Mode) -> bool {
|
||||
encoding_matches_inline :: proc "contextless" (inst: ^Instruction, enc: ^Encoding, mode: Mode) -> bool {
|
||||
// Mode gate: skip i386-only encodings (short-form INC/DEC at 0x40-0x4F)
|
||||
// when not in Mode._32.
|
||||
if enc.flags.mode_32_only && mode != ._32 { return false }
|
||||
|
||||
// Count non-implicit encoding operands
|
||||
encoding_operand_count: u8 = 0
|
||||
for op_type in enc.ops {
|
||||
if op_type == .NONE { break }
|
||||
if !is_implicit_op_inline(op_type) { encoding_operand_count += 1 }
|
||||
explicit_count := enc.flags.explicit_count
|
||||
|
||||
if !enc.flags.has_implicit {
|
||||
if inst.operand_count != explicit_count { return false }
|
||||
for i in 0 ..< explicit_count {
|
||||
eff := mode_rewrite_op_type(enc.ops[i], mode, enc.flags.default_64)
|
||||
operand_matches_inline(&inst.ops[i], eff) or_return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Special case: if user provides exactly one more operand than non-implicit count,
|
||||
// check if the extra operand matches an implicit operand (e.g., CL for shifts)
|
||||
if inst.operand_count == encoding_operand_count + 1 {
|
||||
if inst.operand_count == explicit_count + 1 {
|
||||
// Check if the last user operand matches an implicit operand in the encoding
|
||||
last_user_op := &inst.ops[inst.operand_count - 1]
|
||||
found_matching_implicit := false
|
||||
@@ -740,14 +740,14 @@ encoding_matches_inline :: #force_inline proc "contextless" (inst: ^Instruction,
|
||||
|
||||
if user_idx >= inst.operand_count - 1 { return false }
|
||||
effective_op_type := mode_rewrite_op_type(op_type, mode, enc.flags.default_64)
|
||||
if !operand_matches_inline(&inst.ops[user_idx], effective_op_type) { return false }
|
||||
operand_matches_inline(&inst.ops[user_idx], effective_op_type) or_return
|
||||
user_idx += 1
|
||||
}
|
||||
return user_idx == inst.operand_count - 1
|
||||
}
|
||||
|
||||
// STandard case: operand count must match non-implicit count
|
||||
if inst.operand_count != encoding_operand_count { return false }
|
||||
if inst.operand_count != explicit_count { return false }
|
||||
|
||||
// Match each user operand against non-implicit encoding operands
|
||||
user_idx: u8 = 0
|
||||
@@ -757,7 +757,7 @@ encoding_matches_inline :: #force_inline proc "contextless" (inst: ^Instruction,
|
||||
|
||||
if user_idx >= inst.operand_count { return false }
|
||||
effective_op_type := mode_rewrite_op_type(op_type, mode, enc.flags.default_64)
|
||||
if !operand_matches_inline(&inst.ops[user_idx], effective_op_type) { return false }
|
||||
operand_matches_inline(&inst.ops[user_idx], effective_op_type) or_return
|
||||
user_idx += 1
|
||||
}
|
||||
|
||||
@@ -861,16 +861,16 @@ imm_matches_inline :: #force_inline proc "contextless" (op: ^Operand, op_type: O
|
||||
#partial switch op_type {
|
||||
case .IMM8:
|
||||
// Full 8-bit range: signed [-128, 127] OR unsigned [0, 255]
|
||||
return op.immediate >= -128 && op.immediate <= 255
|
||||
return -128 <= op.immediate && op.immediate <= 255
|
||||
case .IMM8SX:
|
||||
// Sign-extended 8-bit: must be in signed 8-bit range
|
||||
return op.immediate >= -128 && op.immediate <= 127
|
||||
return -128 <= op.immediate && op.immediate <= 127
|
||||
case .IMM16:
|
||||
// Full 16-bit range: signed [-32768, 32767] OR unsigned [0, 65535]
|
||||
return op.immediate >= -32768 && op.immediate <= 65535
|
||||
return -32768 <= op.immediate && op.immediate <= 65535
|
||||
case .IMM32:
|
||||
// Full 32-bit range: signed [-2147483648, 2147483647] OR unsigned [0, 4294967295]
|
||||
return op.immediate >= -2147483648 && op.immediate <= 4294967295
|
||||
return -2147483648 <= op.immediate && op.immediate <= 4294967295
|
||||
case .IMM64:
|
||||
return true // Any i64 value fits
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import "../isa"
|
||||
// SECTION: 6.0 Re-exports from isa (status, relocation)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Result :: isa.Result
|
||||
Error :: isa.Error
|
||||
Error_Code :: isa.Error_Code
|
||||
// Relocation and Relocation_Type live in reloc.odin (per-arch by design).
|
||||
@@ -247,18 +246,24 @@ VEX_L :: enum u8 {
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Encoding_Flags :: bit_field u32 {
|
||||
esc: Escape | 2, // escape sequence
|
||||
prefix: u8 | 2, // mandatory prefix: 0=none, 1=66, 2=F3, 3=F2
|
||||
vex_type: VEX_Type | 2, // VEX/EVEX/XOP
|
||||
vex_w: VEX_W | 2, // VEX.W requirement
|
||||
vex_l: VEX_L | 2, // VEX.L requirement
|
||||
default_64: bool | 1, // default to 64-bit operand size (PUSH, POP, etc.)
|
||||
force_rex_w: bool | 1, // always emit REX.W
|
||||
no_rex: bool | 1, // REX prefix not allowed (high byte regs)
|
||||
lock_ok: bool | 1, // LOCK prefix valid
|
||||
rep_ok: bool | 1, // REP prefix valid
|
||||
modrm_reg_ext: bool | 1, // ModR/M reg field is opcode extension (use ext field)
|
||||
mode_32_only: bool | 1, // only valid in Mode._32 (e.g. short-form INC/DEC at 0x40-0x4F)
|
||||
esc: Escape | 2, // escape sequence
|
||||
prefix: u8 | 2, // mandatory prefix: 0=none, 1=66, 2=F3, 3=F2
|
||||
vex_type: VEX_Type | 2, // VEX/EVEX/XOP
|
||||
vex_w: VEX_W | 2, // VEX.W requirement
|
||||
vex_l: VEX_L | 2, // VEX.L requirement
|
||||
default_64: bool | 1, // default to 64-bit operand size (PUSH, POP, etc.)
|
||||
force_rex_w: bool | 1, // always emit REX.W
|
||||
no_rex: bool | 1, // REX prefix not allowed (high byte regs)
|
||||
lock_ok: bool | 1, // LOCK prefix valid
|
||||
rep_ok: bool | 1, // REP prefix valid
|
||||
modrm_reg_ext: bool | 1, // ModR/M reg field is opcode extension (use ext field)
|
||||
mode_32_only: bool | 1, // only valid in Mode._32 (e.g. short-form INC/DEC at 0x40-0x4F)
|
||||
|
||||
explicit_count: u8 | 3, // 0..<4 non-implicit operands
|
||||
has_implicit: bool | 1, // any implicit operand
|
||||
|
||||
op_count: u8 | 3, // total operands including implicit (0..<4)
|
||||
needs_modrm: bool | 1, // any enc is .MR/.REG/.VVVV
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -324,13 +324,13 @@ reg_needs_evex :: #force_inline proc "contextless" (r: Register) -> bool {
|
||||
@(require_results)
|
||||
reg_is_gpr :: #force_inline proc "contextless" (r: Register) -> bool {
|
||||
c := reg_class(r)
|
||||
return c >= REG_GPR64 && c <= REG_GPR8H
|
||||
return REG_GPR64 <= c && c <= REG_GPR8H
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reg_is_vector :: #force_inline proc "contextless" (r: Register) -> bool {
|
||||
c := reg_class(r)
|
||||
return c >= REG_XMM && c <= REG_ZMM
|
||||
return REG_XMM <= c && c <= REG_ZMM
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
@@ -340,7 +340,7 @@ reg_is_high_byte :: #force_inline proc "contextless" (r: Register) -> bool {
|
||||
|
||||
// Size in bits for register
|
||||
@(require_results)
|
||||
reg_size :: proc "contextless" (r: Register) -> u16 {
|
||||
reg_size :: #force_inline proc "contextless" (r: Register) -> u16 {
|
||||
switch reg_class(r) {
|
||||
case REG_GPR64: return 64
|
||||
case REG_GPR32: return 32
|
||||
@@ -382,19 +382,18 @@ gpr16_from_num :: #force_inline proc "contextless" (num: u8) -> Register {
|
||||
return num < 16 ? Register(REG_GPR16 | u16(num)) : NONE
|
||||
}
|
||||
|
||||
gpr8_from_num :: proc(num: u8, has_rex: bool) -> Register {
|
||||
@(require_results)
|
||||
gpr8_from_num :: #force_inline proc "contextless" (num: u8, has_rex: bool) -> Register {
|
||||
// Without REX prefix, nums 4-7 encode AH/CH/DH/BH (high byte legacy regs)
|
||||
// With REX prefix, nums 4-7 encode SPL/BPL/SIL/DIL (low byte regs)
|
||||
if has_rex {
|
||||
return num < 16 ? Register(REG_GPR8 | u16(num)) : NONE
|
||||
} else {
|
||||
if num < 4 {
|
||||
return Register(REG_GPR8 | u16(num)) // AL, CL, DL, BL
|
||||
} else if num < 8 {
|
||||
return Register(REG_GPR8H | u16(num)) // AH, CH, DH, BH (hw num 4-7)
|
||||
}
|
||||
return NONE
|
||||
} else if num < 4 {
|
||||
return Register(REG_GPR8 | u16(num)) // AL, CL, DL, BL
|
||||
} else if num < 8 {
|
||||
return Register(REG_GPR8H | u16(num)) // AH, CH, DH, BH (hw num 4-7)
|
||||
}
|
||||
return NONE
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
|
||||
@@ -135,7 +135,7 @@ write_encoding :: proc(sb: ^strings.Builder, e: lib.Encoding, max_name: int) {
|
||||
for en, i in e.enc { print_enum_buffered(sb, en, 4, i+1 < len(e.enc)) }
|
||||
strings.write_string(sb, "}, ")
|
||||
fmt.sbprintf(sb, "0x%02X, %d, ", e.opcode, e.ext)
|
||||
write_flags(sb, e.flags)
|
||||
write_flags(sb, e, e.flags)
|
||||
strings.write_string(sb, "},\n")
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ gen_entries :: proc(sb: ^strings.Builder, name, typ: string, entries: []Collecte
|
||||
strings.write_string(sb, "}, {")
|
||||
for en, i in e.enc { print_enum_buffered(sb, en, 4, i+1 < len(e.enc)) }
|
||||
strings.write_string(sb, "}, ")
|
||||
write_flags(sb, e.flags)
|
||||
write_flags(sb, e, e.flags)
|
||||
strings.write_string(sb, "},\n")
|
||||
}
|
||||
strings.write_string(sb, "}\n\n")
|
||||
@@ -366,7 +366,7 @@ print_enum_buffered :: proc(sb: ^strings.Builder, x: $T, max_name: int, comma: b
|
||||
|
||||
// Complete Encoding_Flags emitter -- every field, so ENCODE_FORMS round-trips
|
||||
// the SoT exactly (mode_32_only is read by the encoder).
|
||||
write_flags :: proc(sb: ^strings.Builder, flags: lib.Encoding_Flags) {
|
||||
write_flags :: proc(sb: ^strings.Builder, enc: union{lib.Encoding, Collected_Entry}, flags: lib.Encoding_Flags) {
|
||||
parts: [dynamic]string
|
||||
defer delete(parts)
|
||||
if flags.esc != .NONE { append(&parts, fmt.tprintf("esc=.%v", flags.esc)) }
|
||||
@@ -381,6 +381,45 @@ write_flags :: proc(sb: ^strings.Builder, flags: lib.Encoding_Flags) {
|
||||
if flags.rep_ok { append(&parts, "rep_ok=true") }
|
||||
if flags.modrm_reg_ext { append(&parts, "modrm_reg_ext=true") }
|
||||
if flags.mode_32_only { append(&parts, "mode_32_only=true") }
|
||||
|
||||
switch e in enc {
|
||||
case lib.Encoding:
|
||||
encoding_operand_count: u8 = 0
|
||||
has_implicit := false
|
||||
for op_type in e.ops {
|
||||
if op_type == .NONE { break }
|
||||
if lib.is_implicit_op_inline(op_type) {
|
||||
has_implicit = true
|
||||
} else {
|
||||
encoding_operand_count += 1
|
||||
}
|
||||
}
|
||||
if encoding_operand_count > 0 {
|
||||
append(&parts, fmt.tprintf("explicit_count=%d", encoding_operand_count))
|
||||
}
|
||||
if has_implicit {
|
||||
append(&parts, "has_implicit=true")
|
||||
}
|
||||
|
||||
case Collected_Entry:
|
||||
op_count: u8 = 0
|
||||
needs_modrm := false
|
||||
for t, i in e.ops {
|
||||
if t == .NONE { break }
|
||||
op_count += 1
|
||||
enc := e.enc[i]
|
||||
if enc == .MR || enc == .REG || enc == .VVVV {
|
||||
needs_modrm = true
|
||||
}
|
||||
}
|
||||
if op_count > 0 {
|
||||
append(&parts, fmt.tprintf("op_count=%d", op_count))
|
||||
}
|
||||
if needs_modrm {
|
||||
append(&parts, "needs_modrm=true")
|
||||
}
|
||||
}
|
||||
|
||||
strings.write_string(sb, "{")
|
||||
for part, i in parts {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,6 @@ import x86 "../"
|
||||
import "../../isa"
|
||||
import "core:fmt"
|
||||
import "core:time"
|
||||
import "core:slice"
|
||||
import "core:strings"
|
||||
import "core:mem/virtual"
|
||||
import "core:math"
|
||||
@@ -323,7 +322,7 @@ run_test :: proc(t: Test) -> bool {
|
||||
relocs: [dynamic]x86.Relocation
|
||||
defer delete(relocs)
|
||||
|
||||
encode_result := x86.encode(
|
||||
byte_count, ok := x86.encode(
|
||||
t.instructions,
|
||||
labels_copy[:len(t.labels)],
|
||||
code_buf[:],
|
||||
@@ -334,11 +333,11 @@ run_test :: proc(t: Test) -> bool {
|
||||
)
|
||||
|
||||
// Copy encoded bytes
|
||||
for i in 0..<encode_result.byte_count {
|
||||
for i in 0..<byte_count {
|
||||
append(&encoded_code, code_buf[i])
|
||||
}
|
||||
|
||||
if !encode_result.success {
|
||||
if !ok {
|
||||
fmt.printf("%s[FAIL]%s %s - encoding failed\n", RED, RESET, t.name)
|
||||
for err in encode_errors {
|
||||
fmt.printf(" Error at inst %d: %v\n", err.inst_idx, err.code)
|
||||
@@ -349,13 +348,13 @@ run_test :: proc(t: Test) -> bool {
|
||||
|
||||
// Verify expected bytes if provided
|
||||
if len(t.expected_code) > 0 {
|
||||
if int(encode_result.byte_count) != len(t.expected_code) {
|
||||
if int(byte_count) != len(t.expected_code) {
|
||||
fmt.printf("%s[FAIL]%s %s - code size %d != expected %d\n",
|
||||
RED, RESET, t.name, encode_result.byte_count, len(t.expected_code))
|
||||
RED, RESET, t.name, byte_count, len(t.expected_code))
|
||||
g_stats.failed += 1
|
||||
return false
|
||||
}
|
||||
for i in 0..<encode_result.byte_count {
|
||||
for i in 0..<byte_count {
|
||||
if encoded_code[i] != t.expected_code[i] {
|
||||
fmt.printf("%s[FAIL]%s %s - byte %d: 0x%02X != expected 0x%02X\n",
|
||||
RED, RESET, t.name, i, encoded_code[i], t.expected_code[i])
|
||||
@@ -530,7 +529,7 @@ run_test :: proc(t: Test) -> bool {
|
||||
// =========================================================================
|
||||
|
||||
if len(code_to_decode) > 0 {
|
||||
decode_result := x86.decode(
|
||||
_, ok := x86.decode(
|
||||
code_to_decode,
|
||||
nil,
|
||||
&decoded_insts,
|
||||
@@ -539,7 +538,7 @@ run_test :: proc(t: Test) -> bool {
|
||||
&decode_errors,
|
||||
)
|
||||
|
||||
if !decode_result.success {
|
||||
if !ok {
|
||||
fmt.printf("%s[FAIL]%s %s - decoding failed\n", RED, RESET, t.name)
|
||||
if len(decode_errors) > 0 {
|
||||
for err in decode_errors {
|
||||
@@ -2992,9 +2991,8 @@ run_label_map_tests :: proc() {
|
||||
defer delete(relocs)
|
||||
defer delete(errs)
|
||||
|
||||
result := x86.encode(instructions[:], lm.labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
|
||||
if !result.success {
|
||||
byte_count, ok := x86.encode(instructions[:], lm.labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
if !ok {
|
||||
fmt.printf("%s[FAIL]%s Label_Map test - encoding failed\n", RED, RESET)
|
||||
g_stats.failed += 1
|
||||
return
|
||||
@@ -3010,7 +3008,7 @@ run_label_map_tests :: proc() {
|
||||
defer delete(decoded_labels)
|
||||
defer delete(decode_errors)
|
||||
|
||||
x86.decode(code_buf[:result.byte_count], nil, &decoded_insts, &decoded_info, &decoded_labels, &decode_errors)
|
||||
x86.decode(code_buf[:byte_count], nil, &decoded_insts, &decoded_info, &decoded_labels, &decode_errors)
|
||||
|
||||
// Print with named labels (printer wants id→name; Label_Map stores name→id).
|
||||
id_to_name := make(map[u32]string, len(lm.names), context.temp_allocator)
|
||||
@@ -3051,7 +3049,7 @@ run_benchmarks :: proc() {
|
||||
bench_insts := make([dynamic]x86.Instruction)
|
||||
defer delete(bench_insts)
|
||||
|
||||
for _ in 0..<1000 {
|
||||
for _ in 0..<100 {
|
||||
insts := []x86.Instruction{
|
||||
x86.inst_r(.PUSH, x86.RBP),
|
||||
x86.inst_r_r(.MOV, x86.RBP, x86.RSP),
|
||||
@@ -3071,49 +3069,58 @@ run_benchmarks :: proc() {
|
||||
append(&bench_insts, ..insts)
|
||||
}
|
||||
|
||||
code_buf: [16 * 1024]u8
|
||||
code_buf := make([]byte, 1<<16)
|
||||
defer delete(code_buf)
|
||||
|
||||
labels: [4]x86.Label_Definition
|
||||
|
||||
relocs: [dynamic]x86.Relocation; defer delete(relocs)
|
||||
errs: [dynamic]x86.Error; defer delete(relocs)
|
||||
|
||||
insts: [dynamic]x86.Instruction; defer delete(insts)
|
||||
info: [dynamic]x86.Instruction_Info; defer delete(info)
|
||||
lbls: [dynamic]x86.Label_Definition; defer delete(lbls)
|
||||
|
||||
// Encode
|
||||
enc_start := time.now()
|
||||
enc_bytes := 0
|
||||
for _ in 0..<ITERATIONS {
|
||||
relocs: [dynamic]x86.Relocation; defer delete(relocs)
|
||||
errs: [dynamic]x86.Error; defer delete(errs)
|
||||
result := x86.encode(bench_insts[:], labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
enc_bytes += int(result.byte_count)
|
||||
clear(&relocs)
|
||||
clear(&errs)
|
||||
byte_count, _ := x86.encode(bench_insts[:], labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
enc_bytes += int(byte_count)
|
||||
}
|
||||
enc_dur := time.duration_microseconds(time.since(enc_start))
|
||||
enc_dur := time.duration_seconds(time.since(enc_start))
|
||||
|
||||
// Get encoded length for decode
|
||||
encoded_len: u32
|
||||
{
|
||||
relocs: [dynamic]x86.Relocation; defer delete(relocs)
|
||||
errs: [dynamic]x86.Error; defer delete(errs)
|
||||
result := x86.encode(bench_insts[:], labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
encoded_len = result.byte_count
|
||||
clear(&relocs)
|
||||
clear(&errs)
|
||||
byte_count, _ := x86.encode(bench_insts[:], labels[:], code_buf[:], &relocs, &errs, true, 0)
|
||||
encoded_len = byte_count
|
||||
}
|
||||
|
||||
// Decode
|
||||
dec_start := time.now()
|
||||
dec_insts := 0
|
||||
for _ in 0..<ITERATIONS {
|
||||
insts: [dynamic]x86.Instruction; defer delete(insts)
|
||||
info: [dynamic]x86.Instruction_Info; defer delete(info)
|
||||
lbls: [dynamic]x86.Label_Definition; defer delete(lbls)
|
||||
errs: [dynamic]x86.Error; defer delete(errs)
|
||||
clear(&insts)
|
||||
clear(&info)
|
||||
clear(&lbls)
|
||||
clear(&errs)
|
||||
x86.decode(code_buf[:encoded_len], nil, &insts, &info, &lbls, &errs)
|
||||
dec_insts += len(insts)
|
||||
}
|
||||
dec_dur := time.duration_microseconds(time.since(dec_start))
|
||||
dec_dur := time.duration_seconds(time.since(dec_start))
|
||||
|
||||
enc_ips := f64(ITERATIONS * len(bench_insts)) / (enc_dur / 1_000_000)
|
||||
dec_ips := f64(dec_insts) / (dec_dur / 1_000_000)
|
||||
enc_mbps := f64(enc_bytes) / (enc_dur / 1_000_000) / 1_000_000
|
||||
dec_mbps := f64(ITERATIONS * int(encoded_len)) / (dec_dur / 1_000_000) / 1_000_000
|
||||
enc_ips := f64(ITERATIONS * len(bench_insts)) / enc_dur
|
||||
dec_ips := f64(dec_insts) / dec_dur
|
||||
enc_bps := u64(f64(enc_bytes) / enc_dur)
|
||||
dec_bps := u64(f64(ITERATIONS * int(encoded_len)) / dec_dur)
|
||||
|
||||
fmt.printf(" Encoder: %.1f M insts/sec (%.1f MB/s)\n", enc_ips / 1_000_000, enc_mbps)
|
||||
fmt.printf(" Decoder: %.1f M insts/sec (%.1f MB/s)\n", dec_ips / 1_000_000, dec_mbps)
|
||||
fmt.printf(" Encoder: %.1f M insts/sec (%.1M/s)\n", enc_ips / 1_000_000, enc_bps)
|
||||
fmt.printf(" Decoder: %.1f M insts/sec (%.1M/s)\n", dec_ips / 1_000_000, dec_bps)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user