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

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

View File

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

View File

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

View File

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