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:
Brendan Punsky
2026-06-18 05:44:48 -04:00
committed by Flāvius
92 changed files with 9914 additions and 5421 deletions

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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