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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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