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

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

View File

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

View File

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

View File

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