Merge branch 'bill/rexcode' of https://github.com/odin-lang/Odin into bill/rexcode

This commit is contained in:
gingerBill
2026-06-15 13:35:28 +01:00
319 changed files with 43305 additions and 14840 deletions

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
import "../isa"
@@ -127,7 +129,7 @@ try_bucket :: proc(word, prefix: u32, prefixed: bool, mode: Mode, r: Decode_Inde
// the 8-bit template at bits 24..21 LSB). Only the IMM18+R fields
// (bits 0..18 LSB) are variable in the prefix.
if prefixed {
expected_prefix := PREFIX_BITS_TABLE[e.mnemonic]
expected_prefix := PREFIX_BITS_TABLE[u16(e.mnemonic)]
// Mask covers primary (bits 26..31) and template/R (bits 19..25).
// IMM18 occupies bits 0..17 — leave those free.
prefix_mask: u32 = 0xFFFC0000

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================
@@ -101,7 +103,7 @@ encode_one_inline :: #force_inline proc(
relocs: ^[dynamic]Relocation,
errors: ^[dynamic]Error,
) -> bool {
forms := ENCODING_TABLE[inst.mnemonic]
forms := encoding_forms(inst.mnemonic)
if len(forms) == 0 {
append(errors, Error{inst_idx = u32(inst_idx), code = .INVALID_MNEMONIC})
return false
@@ -142,7 +144,7 @@ encode_one_inline :: #force_inline proc(
// Emit bytes. PowerPC is big-endian on the wire.
if form.flags.prefixed {
prefix := PREFIX_BITS_TABLE[inst.mnemonic]
prefix := PREFIX_BITS_TABLE[u16(inst.mnemonic)]
write_u32_be(code, pc, prefix)
write_u32_be(code, pc + 4, word)
inst.length = 8

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
import "../isa"

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
import "core:strings"

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================

View File

@@ -1,4 +1,6 @@
package rexcode_ppc
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tablegen
// =============================================================================
// PowerPC ENCODING_TABLE

View File

@@ -0,0 +1,447 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tablegen
// =============================================================================
// POWERPC TABLE GENERATOR (Stage A)
// =============================================================================
//
// Reads the single-source-of-truth ENCODING_TABLE (encoding_table.odin, this
// package) and emits human-readable, type-checked Odin into ./generated/:
//
// generated/encode_tables.odin ENCODE_FORMS + ENCODE_RUNS + PREFIX_BITS_TABLE
// generated/decode_tables.odin DECODE_ENTRIES + DECODE_FORM_IDX +
// DECODE_BUCKET_LIST + primary/sub index tables
// generated/writer.odin Stage B: serialize those globals to ../../tables/*.bin
//
// It also re-emits the library loader ../tables.odin. Run:
// odin run ppc/tablegen # Stage A
// odin run ppc/tablegen/generated # Stage B
//
// PowerPC dispatch is two-level (one DECODE_ENTRIES array, two index tables):
// Primary (64 buckets): key = bits[26:31] (6-bit primary opcode).
// Secondary (16384): key = primary*256 + bits[1:8] (low XO bits, skip Rc).
// Each bucket points to a contiguous run in DECODE_BUCKET_LIST of entry indices,
// sorted by mask popcount descending so the most-specific match wins first.
import "core:fmt"
import "core:os"
import "core:strings"
import "core:slice"
import "core:reflect"
import "core:math/bits"
import lib "../"
// Package-scope aliases so the moved SoT resolves Mnemonic/Encoding unqualified.
Encoding :: lib.Encoding
Mnemonic :: lib.Mnemonic
Blob :: struct { global, file, typ: string }
BLOBS := [?]Blob{
{"ENCODE_FORMS", "ppc.encode_forms.bin", "Encoding"},
{"ENCODE_RUNS", "ppc.encode_runs.bin", "Encode_Run"},
{"PREFIX_BITS_TABLE", "ppc.prefix_bits.bin", "u32"},
{"DECODE_ENTRIES", "ppc.entries.bin", "Decode_Entry"},
{"DECODE_FORM_IDX", "ppc.form_idx.bin", "u16"},
{"DECODE_BUCKET_LIST", "ppc.bucket_list.bin", "u16"},
{"DECODE_INDEX_PRIMARY", "ppc.idx_primary.bin", "Decode_Index"},
{"DECODE_INDEX_SUB", "ppc.idx_sub.bin", "Decode_Index"},
}
DIR_GEN :: #directory + "/generated/"
PATH_LOADER :: #directory + "/../tables.odin"
PRIMARY_BUCKETS :: 64 // bits[26:31] of word
SUB_BITS :: 8 // bits[1:8] of word (low XO bits, skipping Rc)
SUB_BUCKETS :: PRIMARY_BUCKETS * (1 << SUB_BITS) // 64 * 256 = 16384
Entry :: struct {
mnemonic: lib.Mnemonic,
ops: [4]lib.Operand_Type,
enc: [4]lib.Operand_Encoding,
bits: u32,
mask: u32,
feature: lib.Feature,
mode: lib.Mode,
flags: lib.Encoding_Flags,
form_idx: u16,
}
Range :: struct { start: u32, count: u16 }
Pair :: struct { bucket: u32, entry_idx: u16 }
main :: proc() {
n := emit_encode_tables()
ne := emit_decode_tables()
emit_writer()
emit_loader()
fmt.printfln("ppc tablegen: %d encode forms, %d decode entries", n, ne)
}
// -----------------------------------------------------------------------------
// Encode side
// -----------------------------------------------------------------------------
emit_encode_tables :: proc() -> (total: int) {
sb := strings.builder_make()
strings.write_string(&sb, "package rexcode_ppc_generated\n\n")
strings.write_string(&sb, "// GENERATED by ../gen.odin -- DO NOT EDIT.\n")
strings.write_string(&sb, "// Flattened encode forms + per-mnemonic run index + prefix words (source: ENCODING_TABLE).\n\n")
strings.write_string(&sb, "import lib \"../..\"\n\n")
for m in Mnemonic { total += len(ENCODING_TABLE[m]) }
fmt.sbprintfln(&sb, "ENCODE_FORMS := [%d]lib.Encoding{{", total)
for m in Mnemonic {
forms := ENCODING_TABLE[m]
if len(forms) == 0 { continue }
fmt.sbprintfln(&sb, "\t// .%v", m)
for f in forms {
write_row(&sb, f.mnemonic, f.ops, f.enc, f.bits, f.mask, f.feature, f.mode, f.flags)
}
}
strings.write_string(&sb, "}\n\n")
run_w := 0
for m in Mnemonic { run_w = max(run_w, len(reflect.enum_string(m))) }
strings.write_string(&sb, "ENCODE_RUNS := [lib.Mnemonic]lib.Encode_Run{\n")
start := 0
for m in Mnemonic {
c := len(ENCODING_TABLE[m])
name := reflect.enum_string(m)
fmt.sbprintf(&sb, "\t.%s", name)
for _ in 0..<run_w-len(name) { strings.write_byte(&sb, ' ') }
fmt.sbprintfln(&sb, " = {{% 5d, % 3d}},", start, c)
start += c
}
strings.write_string(&sb, "}\n\n")
// PREFIX_BITS_TABLE: dense [len(Mnemonic)]u32, indexed by mnemonic ordinal.
// (Non-prefixed mnemonics are 0; the library reads it as PREFIX_BITS_TABLE[u16(mn)].)
nmn := len(Mnemonic)
fmt.sbprintfln(&sb, "PREFIX_BITS_TABLE := [%d]u32{{", nmn)
for m in Mnemonic {
v := PREFIX_BITS_TABLE[m]
if v != 0 {
fmt.sbprintfln(&sb, "\t%d = 0x%08X, // .%v", u16(m), v, m)
}
}
strings.write_string(&sb, "}\n")
emit_file(DIR_GEN + "encode_tables.odin", &sb)
return
}
// -----------------------------------------------------------------------------
// Decode side (ported from the old tools/gen_decode_tables.odin)
// -----------------------------------------------------------------------------
emit_decode_tables :: proc() -> (total: int) {
all: [dynamic]Entry
defer delete(all)
for mn in Mnemonic {
for f, fi in ENCODING_TABLE[mn] {
append(&all, Entry{
mnemonic = mn,
ops = f.ops,
enc = f.enc,
bits = f.bits,
mask = f.mask,
feature = f.feature,
mode = f.mode,
flags = f.flags,
form_idx = u16(fi),
})
}
}
// Sort the global entries array: by primary opcode, then mask popcount
// descending so within-bucket scan picks the most specific first.
slice.sort_by(all[:], proc(x, y: Entry) -> bool {
px := (x.bits >> 26) & 0x3F
py := (y.bits >> 26) & 0x3F
if px != py { return px < py }
xc := bits.count_ones(x.mask)
yc := bits.count_ones(y.mask)
if xc != yc { return xc > yc }
return u16(x.mnemonic) < u16(y.mnemonic)
})
// For each entry, enumerate the (primary, sub-key) pairs it can match.
primary_pairs: [dynamic]Pair
sub_pairs: [dynamic]Pair
defer delete(primary_pairs); defer delete(sub_pairs)
keys: [dynamic]u32
defer delete(keys)
for e, i in all {
// Primary key
enumerate_keys(e.bits, e.mask, 26, 6, &keys)
for k in keys {
append(&primary_pairs, Pair{bucket = k, entry_idx = u16(i)})
}
// Sub key: primary << SUB_BITS | bits[1..SUB_BITS+1)
prim_keys: [dynamic]u32
sub_only: [dynamic]u32
defer delete(prim_keys); defer delete(sub_only)
enumerate_keys(e.bits, e.mask, 26, 6, &prim_keys)
enumerate_keys(e.bits, e.mask, 1, SUB_BITS, &sub_only)
for pk in prim_keys {
for sk in sub_only {
key := pk * (1 << SUB_BITS) + sk
append(&sub_pairs, Pair{bucket = key, entry_idx = u16(i)})
}
}
}
// Re-sort pair lists: primary order, then mask popcount descending.
rebuild :: proc(pairs: ^[dynamic]Pair, all: []Entry) {
Sort_Pair :: struct { sort_key: u64, entry_idx: u16, bucket: u32 }
sortable := make([dynamic]Sort_Pair, 0, len(pairs), context.temp_allocator)
for pp in pairs^ {
e := all[pp.entry_idx]
pop := u64(bits.count_ones(e.mask))
// (bucket << 40) | ((63 - pop) << 32) | mnemonic
key := (u64(pp.bucket) << 40) | ((63 - pop) << 32) | u64(e.mnemonic)
append(&sortable, Sort_Pair{sort_key = key, entry_idx = pp.entry_idx, bucket = pp.bucket})
}
slice.sort_by_key(sortable[:], proc(s: Sort_Pair) -> u64 { return s.sort_key })
clear(pairs)
for s in sortable { append(pairs, Pair{bucket = s.bucket, entry_idx = s.entry_idx}) }
}
rebuild(&primary_pairs, all[:])
rebuild(&sub_pairs, all[:])
// Build flat u16 dispatch list. Each bucket points to a contiguous run.
primary_idx: [PRIMARY_BUCKETS]Range
sub_idx: [SUB_BUCKETS]Range
bucket_list: [dynamic]u16
defer delete(bucket_list)
emit_pairs :: proc(pairs: []Pair, idx: []Range, list: ^[dynamic]u16) {
prev_bucket: i64 = -1
for pp in pairs {
cur := i64(pp.bucket)
if cur != prev_bucket {
idx[cur].start = u32(len(list))
idx[cur].count = 0
prev_bucket = cur
}
append(list, pp.entry_idx)
idx[cur].count += 1
}
}
emit_pairs(primary_pairs[:], primary_idx[:], &bucket_list)
emit_pairs(sub_pairs[:], sub_idx[:], &bucket_list)
sb := strings.builder_make()
strings.write_string(&sb, "package rexcode_ppc_generated\n\n")
strings.write_string(&sb, "// GENERATED by ../gen.odin -- DO NOT EDIT.\n")
strings.write_string(&sb, "// Reverse decode tables (source: ENCODING_TABLE), two-level primary+sub dispatch.\n\n")
strings.write_string(&sb, "import lib \"../..\"\n\n")
fmt.sbprintfln(&sb, "DECODE_ENTRIES := [%d]lib.Decode_Entry{{", len(all))
for e in all {
write_row(&sb, e.mnemonic, e.ops, e.enc, e.bits, e.mask, e.feature, e.mode, e.flags)
}
strings.write_string(&sb, "}\n\n")
emit_form_idx(&sb, all[:])
emit_bucket_list(&sb, bucket_list[:])
emit_range_table(&sb, "DECODE_INDEX_PRIMARY", primary_idx[:])
emit_range_table(&sb, "DECODE_INDEX_SUB", sub_idx[:])
emit_file(DIR_GEN + "decode_tables.odin", &sb)
return len(all)
}
enumerate_keys :: proc(b, mask: u32, key_shift: u32, key_bits: u32, out: ^[dynamic]u32) {
clear(out)
key_mask := (u32(1) << key_bits) - 1
fixed_key := ((b & mask) >> key_shift) & key_mask
var_bits := (~mask >> key_shift) & key_mask
sub: u32 = 0
for {
append(out, fixed_key | sub)
if var_bits == 0 { break }
if sub == var_bits { break }
sub = (sub - var_bits) & var_bits
}
}
emit_form_idx :: proc(sb: ^strings.Builder, entries: []Entry) {
all_zero := true
for e in entries {
if e.form_idx != 0 { all_zero = false; break }
}
if all_zero {
fmt.sbprintf(sb, "DECODE_FORM_IDX: [%d]u16\n\n", len(entries))
} else {
fmt.sbprintf(sb, "DECODE_FORM_IDX := [%d]u16{{", len(entries))
for e, i in entries {
if i % 64 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "%d, ", e.form_idx)
}
strings.write_string(sb, "\n}\n\n")
}
}
emit_bucket_list :: proc(sb: ^strings.Builder, items: []u16) {
fmt.sbprintf(sb, "DECODE_BUCKET_LIST := [%d]u16{{", len(items))
for v, i in items {
if i % 64 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "% 4d, ", v)
}
strings.write_string(sb, "\n}\n\n")
}
emit_range_table :: proc(sb: ^strings.Builder, name: string, ranges: []Range) {
fmt.sbprintf(sb, "%s := [%d]lib.Decode_Index{{", name, len(ranges))
amount_set := 0
for r, i in ranges {
if r.count != 0 {
if amount_set % 16 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "0x%04X = {{% 5d, % 4d, 0}}, ", i, r.start, r.count)
amount_set += 1
}
}
strings.write_string(sb, "\n}\n\n")
}
// -----------------------------------------------------------------------------
// Shared row + flags formatting
// -----------------------------------------------------------------------------
write_row :: proc(sb: ^strings.Builder, mn: lib.Mnemonic, ops: [4]lib.Operand_Type,
enc: [4]lib.Operand_Encoding, bits, mask: u32, feature: lib.Feature,
mode: lib.Mode, flags: lib.Encoding_Flags) {
fmt.sbprintf(sb, "\t{{ .%v, {{.%v,.%v,.%v,.%v}}, {{.%v,.%v,.%v,.%v}}, 0x%08X, 0x%08X, .%v, .%v, {{%s}} }},\n",
mn, ops[0], ops[1], ops[2], ops[3], enc[0], enc[1], enc[2], enc[3], bits, mask, feature, mode, flags_lit(flags))
}
flags_lit :: proc(f: lib.Encoding_Flags) -> string {
parts: [dynamic]string
defer delete(parts)
if f.branch { append(&parts, "branch=true") }
if f.cond_branch { append(&parts, "cond_branch=true") }
if f.writes_lr { append(&parts, "writes_lr=true") }
if f.sets_cr0 { append(&parts, "sets_cr0=true") }
if f.sets_cr1 { append(&parts, "sets_cr1=true") }
if f.abs_branch { append(&parts, "abs_branch=true") }
if f.has_oe { append(&parts, "has_oe=true") }
if f.prefixed { append(&parts, "prefixed=true") }
if f.vle { append(&parts, "vle=true") }
if f.vle_short { append(&parts, "vle_short=true") }
return strings.join(parts[:], ", ", context.temp_allocator)
}
// -----------------------------------------------------------------------------
// Stage B writer + the library loader
// -----------------------------------------------------------------------------
emit_writer :: proc() {
sb := strings.builder_make()
strings.write_string(&sb, "package rexcode_ppc_generated\n\n")
strings.write_string(&sb, "// GENERATED by ../gen.odin -- DO NOT EDIT.\n")
strings.write_string(&sb, "// Stage B: serialize the typed tables above to raw blobs under ../../tables/.\n\n")
strings.write_string(&sb, "import \"core:os\"\nimport \"core:fmt\"\n\n")
strings.write_string(&sb, "TABLES :: #directory + \"/../../tables/\"\n\n")
strings.write_string(&sb, "raw :: #force_inline proc \"contextless\" (p: rawptr, n: int) -> []u8 {\n\treturn (cast([^]u8)p)[:n]\n}\n\n")
strings.write_string(&sb, "w :: proc(file: string, data: []u8) {\n")
strings.write_string(&sb, "\tif err := os.write_entire_file(file, data); err != nil {\n")
strings.write_string(&sb, "\t\tfmt.eprintfln(\"rexcode tablegen: failed to write %s: %v\", file, err)\n\t\tos.exit(1)\n\t}\n}\n\n")
strings.write_string(&sb, "main :: proc() {\n")
for b in BLOBS {
fmt.sbprintfln(&sb, "\tw(TABLES + \"%s\", raw(&%s, size_of(%s)))", b.file, b.global, b.global)
}
strings.write_string(&sb, "}\n")
emit_file(DIR_GEN + "writer.odin", &sb)
}
LOADER_TYPES :: `// -----------------------------------------------------------------------------
// Subsidiary table types (generated scaffolding)
// -----------------------------------------------------------------------------
// Companion run index: ENCODE_RUNS[mnemonic] -> contiguous run in ENCODE_FORMS.
Encode_Run :: struct {
start: u32,
count: u32,
}
Decode_Entry :: struct #packed {
mnemonic: Mnemonic, // 2
ops: [4]Operand_Type, // 4
enc: [4]Operand_Encoding, // 4
bits: u32, // 4
mask: u32, // 4
feature: Feature, // 1
mode: Mode, // 1
flags: Encoding_Flags, // 2
}
#assert(size_of(Decode_Entry) == 22)
Decode_Index :: struct #packed {
start: u32,
count: u16,
_: u16,
}
#assert(size_of(Decode_Index) == 8)
DECODE_SUB_BUCKETS :: 256 // per primary
`
LOADER_ACCESSORS :: `// -----------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------
// Per-mnemonic encode forms: the run of ENCODE_FORMS belonging to ` + "`m`" + `.
// Replaces the old ENCODING_TABLE[m] slice; the returned view is into rodata.
@(private, require_results)
encoding_forms :: #force_inline proc "contextless" (m: Mnemonic) -> []Encoding {
r := ENCODE_RUNS[u16(m)]
return ENCODE_FORMS[r.start:][:r.count]
}
`
emit_loader :: proc() {
sb := strings.builder_make()
strings.write_string(&sb, "package rexcode_ppc\n\n")
strings.write_string(&sb, "// =============================================================================\n")
strings.write_string(&sb, "// GENERATED FILE - DO NOT EDIT\n")
strings.write_string(&sb, "// =============================================================================\n")
strings.write_string(&sb, "//\n")
strings.write_string(&sb, "// Loads the flat binary encode/decode tables into @(rodata). Produced by tablegen:\n")
strings.write_string(&sb, "//\n")
strings.write_string(&sb, "// odin run tablegen # Stage A: ENCODING_TABLE -> generated/ + this file\n")
strings.write_string(&sb, "// odin run tablegen/generated # Stage B: typed Odin literals -> tables/*.bin\n")
strings.write_string(&sb, "//\n")
strings.write_string(&sb, "// The .bin blobs are raw, host-endian, packed struct images.\n\n")
strings.write_string(&sb, LOADER_TYPES)
strings.write_string(&sb, "\n// -----------------------------------------------------------------------------\n")
strings.write_string(&sb, "// Loaded tables (rodata, embedded from tables/*.bin at compile time)\n")
strings.write_string(&sb, "// -----------------------------------------------------------------------------\n\n")
gmax, fmax := 0, 0
for b in BLOBS { gmax = max(gmax, len(b.global)); fmax = max(fmax, len(b.file)) }
for b in BLOBS {
fmt.sbprintf(&sb, "@(rodata) %s", b.global)
for _ in 0..<gmax-len(b.global) { strings.write_byte(&sb, ' ') }
path := fmt.tprintf("\"tables/%s\",", b.file)
fmt.sbprintf(&sb, " := #load(%s", path)
for _ in 0..<fmax-len(b.file) { strings.write_byte(&sb, ' ') }
fmt.sbprintfln(&sb, " []%s)", b.typ)
}
strings.write_string(&sb, "\n")
strings.write_string(&sb, LOADER_ACCESSORS)
emit_file(PATH_LOADER, &sb)
}
GEN_ATTRIB :: "// rexcode · Brendan Punsky (dotbmp@github), original author\n\n"
emit_file :: proc(path: string, sb: ^strings.Builder) {
if err := os.write_entire_file(path, transmute([]u8)strings.concatenate({GEN_ATTRIB, strings.to_string(sb^)})); err != nil {
fmt.eprintfln("rexcode tablegen: failed to write %s: %v", path, err)
os.exit(1)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_generated
// GENERATED by ../gen.odin -- DO NOT EDIT.
// Stage B: serialize the typed tables above to raw blobs under ../../tables/.
import "core:os"
import "core:fmt"
TABLES :: #directory + "/../../tables/"
raw :: #force_inline proc "contextless" (p: rawptr, n: int) -> []u8 {
return (cast([^]u8)p)[:n]
}
w :: proc(file: string, data: []u8) {
if err := os.write_entire_file(file, data); err != nil {
fmt.eprintfln("rexcode tablegen: failed to write %s: %v", file, err)
os.exit(1)
}
}
main :: proc() {
w(TABLES + "ppc.encode_forms.bin", raw(&ENCODE_FORMS, size_of(ENCODE_FORMS)))
w(TABLES + "ppc.encode_runs.bin", raw(&ENCODE_RUNS, size_of(ENCODE_RUNS)))
w(TABLES + "ppc.prefix_bits.bin", raw(&PREFIX_BITS_TABLE, size_of(PREFIX_BITS_TABLE)))
w(TABLES + "ppc.entries.bin", raw(&DECODE_ENTRIES, size_of(DECODE_ENTRIES)))
w(TABLES + "ppc.form_idx.bin", raw(&DECODE_FORM_IDX, size_of(DECODE_FORM_IDX)))
w(TABLES + "ppc.bucket_list.bin", raw(&DECODE_BUCKET_LIST, size_of(DECODE_BUCKET_LIST)))
w(TABLES + "ppc.idx_primary.bin", raw(&DECODE_INDEX_PRIMARY, size_of(DECODE_INDEX_PRIMARY)))
w(TABLES + "ppc.idx_sub.bin", raw(&DECODE_INDEX_SUB, size_of(DECODE_INDEX_SUB)))
}

View File

@@ -0,0 +1,70 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc
// =============================================================================
// GENERATED FILE - DO NOT EDIT
// =============================================================================
//
// Loads the flat binary encode/decode tables into @(rodata). Produced by tablegen:
//
// odin run tablegen # Stage A: ENCODING_TABLE -> generated/ + this file
// odin run tablegen/generated # Stage B: typed Odin literals -> tables/*.bin
//
// The .bin blobs are raw, host-endian, packed struct images.
// -----------------------------------------------------------------------------
// Subsidiary table types (generated scaffolding)
// -----------------------------------------------------------------------------
// Companion run index: ENCODE_RUNS[mnemonic] -> contiguous run in ENCODE_FORMS.
Encode_Run :: struct {
start: u32,
count: u32,
}
Decode_Entry :: struct #packed {
mnemonic: Mnemonic, // 2
ops: [4]Operand_Type, // 4
enc: [4]Operand_Encoding, // 4
bits: u32, // 4
mask: u32, // 4
feature: Feature, // 1
mode: Mode, // 1
flags: Encoding_Flags, // 2
}
#assert(size_of(Decode_Entry) == 22)
Decode_Index :: struct #packed {
start: u32,
count: u16,
_: u16,
}
#assert(size_of(Decode_Index) == 8)
DECODE_SUB_BUCKETS :: 256 // per primary
// -----------------------------------------------------------------------------
// Loaded tables (rodata, embedded from tables/*.bin at compile time)
// -----------------------------------------------------------------------------
@(rodata) ENCODE_FORMS := #load("tables/ppc.encode_forms.bin", []Encoding)
@(rodata) ENCODE_RUNS := #load("tables/ppc.encode_runs.bin", []Encode_Run)
@(rodata) PREFIX_BITS_TABLE := #load("tables/ppc.prefix_bits.bin", []u32)
@(rodata) DECODE_ENTRIES := #load("tables/ppc.entries.bin", []Decode_Entry)
@(rodata) DECODE_FORM_IDX := #load("tables/ppc.form_idx.bin", []u16)
@(rodata) DECODE_BUCKET_LIST := #load("tables/ppc.bucket_list.bin", []u16)
@(rodata) DECODE_INDEX_PRIMARY := #load("tables/ppc.idx_primary.bin", []Decode_Index)
@(rodata) DECODE_INDEX_SUB := #load("tables/ppc.idx_sub.bin", []Decode_Index)
// -----------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------
// Per-mnemonic encode forms: the run of ENCODE_FORMS belonging to `m`.
// Replaces the old ENCODING_TABLE[m] slice; the returned view is into rodata.
@(private, require_results)
encoding_forms :: #force_inline proc "contextless" (m: Mnemonic) -> []Encoding {
r := ENCODE_RUNS[u16(m)]
return ENCODE_FORMS[r.start:][:r.count]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
import "core:fmt"

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
import "core:fmt"
@@ -15,7 +17,8 @@ run_decode_sweep :: proc() {
ok_total, missing_mn_total, wrong_mn_total: int
for mn in p.Mnemonic {
forms := p.ENCODING_TABLE[mn]
_run := p.ENCODE_RUNS[u16(mn)]
forms := p.ENCODE_FORMS[_run.start:][:_run.count]
for &f in forms {
// Build a canonical word — base bits | safe-fill operands.
// Safe-fill values mirror dump_verify_input.odin.
@@ -32,7 +35,7 @@ run_decode_sweep :: proc() {
buf[3] = u8(word)
ilen := 4
if f.flags.prefixed {
pfx := p.PREFIX_BITS_TABLE[mn]
pfx := p.PREFIX_BITS_TABLE[u16(mn)]
buf[0] = u8(pfx >> 24); buf[1] = u8(pfx >> 16); buf[2] = u8(pfx >> 8); buf[3] = u8(pfx)
buf[4] = u8(word >> 24); buf[5] = u8(word >> 16); buf[6] = u8(word >> 8); buf[7] = u8(word)
ilen = 8

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
// Full encode→decode→re-encode sweep across the entire ENCODING_TABLE.
@@ -30,7 +32,8 @@ run_full_sweep :: proc() {
defer delete(fail_samples)
for mn in p.Mnemonic {
forms := p.ENCODING_TABLE[mn]
_run := p.ENCODE_RUNS[u16(mn)]
forms := p.ENCODE_FORMS[_run.start:][:_run.count]
for &f, fi in forms {
test_form(mn, fi, &f, &fail_samples)
}

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
import "core:fmt"

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
import "core:fmt"

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ppc_tests
import "core:fmt"
@@ -8,7 +10,8 @@ ok_count, fail_count: int
@(private="file")
check :: proc(name: string, mn: p.Mnemonic, idx: int, want_bits, want_mask: u32) {
enc := p.ENCODING_TABLE[mn]
_run := p.ENCODE_RUNS[u16(mn)]
enc := p.ENCODE_FORMS[_run.start:][:_run.count]
if idx >= len(enc) {
fmt.printf(" [FAIL] %s: entry %d not present (have %d)\n", name, idx, len(enc))
fail_count += 1

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package main
// =============================================================================
@@ -38,7 +40,8 @@ main :: proc() {
n_main, n_spe: int
for mn in p.Mnemonic {
for &f in p.ENCODING_TABLE[mn] {
_run := p.ENCODE_RUNS[u16(mn)]
for &f in p.ENCODE_FORMS[_run.start:][:_run.count] {
bits := fill_safe_operands(&f)
hex_buf := &main_hex
meta_buf := &main_meta
@@ -53,7 +56,7 @@ main :: proc() {
// For prefixed (POWER10 8-byte) instructions, emit the PREFIX
// word first, then the SUFFIX word, both BE.
if f.flags.prefixed {
pfx := p.PREFIX_BITS_TABLE[mn] | prefix_safe_fill(&f)
pfx := p.PREFIX_BITS_TABLE[u16(mn)] | prefix_safe_fill(&f)
fmt.sbprintf(hex_buf, "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x\n",
(pfx >> 24) & 0xFF, (pfx >> 16) & 0xFF, (pfx >> 8) & 0xFF, pfx & 0xFF,
(bits >> 24) & 0xFF, (bits >> 16) & 0xFF, (bits >> 8) & 0xFF, bits & 0xFF)

View File

@@ -1,378 +0,0 @@
package main
// =============================================================================
// PowerPC DECODE-TABLE GENERATOR
// =============================================================================
//
// Two-level dispatch (one DECODE_ENTRIES array, two index tables):
//
// Primary (64 buckets):
// key = bits[26:31] of the instruction word (6 bits = primary opcode).
// Covers all PowerPC instructions including 8-byte prefixed (whose SUFFIX
// word's primary opcode is also used here — the suffix's primary is
// distinct from the prefix's primary=1).
//
// Secondary (64 * 256 = 16384 buckets):
// key = primary * 256 + bits[1:8] (low 8 bits of the XO field, skipping
// the Rc bit at position 0). Most PPC entries have XO covered by the
// mask in bits 1..10, so secondary buckets are sparsely populated and
// scan length is small.
//
// At decode time:
// 1. Compute primary key. If primary == 1 (prefixed-instruction prefix),
// the next 4 bytes are the SUFFIX word and we redo dispatch on the
// suffix's primary.
// 2. Compute secondary key. If DECODE_INDEX_SUB[sub_key].count > 0, scan
// that bucket. Else fall back to DECODE_INDEX_PRIMARY[primary].
// 3. Within a bucket, entries are sorted by mask popcount descending so
// the most-specific match wins on first hit.
//
// Run with: cd ppc && odin run tools/gen_decode_tables.odin -file
// Output: ./decoding_tables.odin
import "core:fmt"
import "core:os"
import "core:slice"
import "core:strings"
import "core:reflect"
import "core:math/bits"
import p ".."
Entry :: struct {
mnemonic: p.Mnemonic,
ops: [4]p.Operand_Type,
enc: [4]p.Operand_Encoding,
bits: u32,
mask: u32,
feature: p.Feature,
mode: p.Mode,
flags: p.Encoding_Flags,
form_idx: u16,
}
Range :: struct {
start: u32,
count: u16,
_: u16,
}
PRIMARY_BUCKETS :: 64 // bits[26:31] of word
SUB_BITS :: 8 // bits[1:8] of word (low XO bits, skipping Rc)
SUB_BUCKETS :: PRIMARY_BUCKETS * (1 << SUB_BITS) // 64 * 256 = 16384
Pair :: struct { bucket: u32, entry_idx: u16 }
main :: proc() {
fmt.println("Generating PowerPC decoder tables from ENCODING_TABLE...")
all: [dynamic]Entry
defer delete(all)
for mn in p.Mnemonic {
for f, fi in p.ENCODING_TABLE[mn] {
e := Entry{
mnemonic = mn,
ops = f.ops,
enc = f.enc,
bits = f.bits,
mask = f.mask,
feature = f.feature,
mode = f.mode,
flags = f.flags,
form_idx = u16(fi),
}
append(&all, e)
}
}
// Sort the global entries array: by primary opcode, then mask popcount
// descending so within-bucket scan picks the most specific first.
slice.sort_by(all[:], proc(x, y: Entry) -> bool {
px := (x.bits >> 26) & 0x3F
py := (y.bits >> 26) & 0x3F
if px != py { return px < py }
xc := bits.count_ones(x.mask)
yc := bits.count_ones(y.mask)
if xc != yc { return xc > yc }
return u16(x.mnemonic) < u16(y.mnemonic)
})
// For each entry, enumerate the (primary, sub-key) pairs it can match.
// enumerate_keys handles variable bits in the bucket-key field.
primary_pairs: [dynamic]Pair
sub_pairs: [dynamic]Pair
defer delete(primary_pairs); defer delete(sub_pairs)
keys: [dynamic]u32
defer delete(keys)
for e, i in all {
// Primary key
enumerate_keys(e.bits, e.mask, 26, 6, &keys)
for k in keys {
append(&primary_pairs, Pair{bucket = k, entry_idx = u16(i)})
}
// Sub key: primary << SUB_BITS | bits[1..SUB_BITS+1)
// (enumerate_keys handles variable bits in both ranges; we combine
// primary and XO low bits into a single 14-bit key.)
prim_keys: [dynamic]u32
sub_only: [dynamic]u32
defer delete(prim_keys); defer delete(sub_only)
enumerate_keys(e.bits, e.mask, 26, 6, &prim_keys)
enumerate_keys(e.bits, e.mask, 1, SUB_BITS, &sub_only)
for pk in prim_keys {
for sk in sub_only {
key := pk * (1 << SUB_BITS) + sk
append(&sub_pairs, Pair{bucket = key, entry_idx = u16(i)})
}
}
}
// Re-sort pair lists: primary order, then mask popcount descending
rebuild :: proc(pairs: ^[dynamic]Pair, all: []Entry) {
Sort_Pair :: struct { sort_key: u64, entry_idx: u16, bucket: u32 }
sortable := make([dynamic]Sort_Pair, 0, len(pairs), context.temp_allocator)
for pp in pairs^ {
e := all[pp.entry_idx]
pop := u64(bits.count_ones(e.mask))
// (bucket << 40) | ((63 - pop) << 32) | mnemonic
key := (u64(pp.bucket) << 40) | ((63 - pop) << 32) | u64(e.mnemonic)
append(&sortable, Sort_Pair{sort_key = key, entry_idx = pp.entry_idx, bucket = pp.bucket})
}
slice.sort_by_key(sortable[:], proc(s: Sort_Pair) -> u64 { return s.sort_key })
clear(pairs)
for s in sortable { append(pairs, Pair{bucket = s.bucket, entry_idx = s.entry_idx}) }
}
rebuild(&primary_pairs, all[:])
rebuild(&sub_pairs, all[:])
// Build flat u16 dispatch list. Each bucket points to a contiguous run.
primary_idx: [PRIMARY_BUCKETS]Range
sub_idx: [SUB_BUCKETS]Range
bucket_list: [dynamic]u16
defer delete(bucket_list)
emit_pairs :: proc(pairs: []Pair, idx: []Range, list: ^[dynamic]u16) {
prev_bucket: i64 = -1
for pp in pairs {
cur := i64(pp.bucket)
if cur != prev_bucket {
idx[cur].start = u32(len(list))
idx[cur].count = 0
prev_bucket = cur
}
append(list, pp.entry_idx)
idx[cur].count += 1
}
}
emit_pairs(primary_pairs[:], primary_idx[:], &bucket_list)
emit_pairs(sub_pairs[:], sub_idx[:], &bucket_list)
sb: strings.Builder
strings.builder_init(&sb)
defer strings.builder_destroy(&sb)
emit_header(&sb)
emit_entries(&sb, all[:])
emit_form_idx(&sb, all[:])
emit_bucket_list(&sb, bucket_list[:])
emit_range_table(&sb, "DECODE_INDEX_PRIMARY", primary_idx[:])
emit_range_table(&sb, "DECODE_INDEX_SUB", sub_idx[:])
err := os.write_entire_file("decoding_tables.odin", transmute([]u8)strings.to_string(sb))
if err != nil {
fmt.eprintfln("FAILED to write decoding_tables.odin: %v", err)
os.exit(1)
}
// Stats
max_primary, max_sub: u16
pop_primary, pop_sub: int
for r in primary_idx { if r.count > max_primary { max_primary = r.count }; if r.count > 0 { pop_primary += 1 } }
for r in sub_idx { if r.count > max_sub { max_sub = r.count }; if r.count > 0 { pop_sub += 1 } }
fmt.printfln("OK — %d entries: PRIMARY %d/%d buckets (max=%d); SUB %d/%d buckets (max=%d); bucket_list %d entries",
len(all), pop_primary, PRIMARY_BUCKETS, max_primary,
pop_sub, SUB_BUCKETS, max_sub, len(bucket_list))
}
enumerate_keys :: proc(b, mask: u32, key_shift: u32, key_bits: u32, out: ^[dynamic]u32) {
clear(out)
key_mask := (u32(1) << key_bits) - 1
fixed_key := ((b & mask) >> key_shift) & key_mask
var_bits := (~mask >> key_shift) & key_mask
sub: u32 = 0
for {
append(out, fixed_key | sub)
if var_bits == 0 { break }
if sub == var_bits { break }
sub = (sub - var_bits) & var_bits
}
}
emit_header :: proc(sb: ^strings.Builder) {
strings.write_string(sb, `package rexcode_ppc
// =============================================================================
// GENERATED FILE — DO NOT EDIT
// =============================================================================
//
// Generated by tools/gen_decode_tables.odin from ENCODING_TABLE.
// Regenerate with: cd ppc && odin run tools/gen_decode_tables.odin -file
//
Decode_Entry :: struct #packed {
mnemonic: Mnemonic,
ops: [4]Operand_Type,
enc: [4]Operand_Encoding,
bits: u32,
mask: u32,
feature: Feature,
mode: Mode,
flags: Encoding_Flags,
}
Decode_Index :: struct #packed {
start: u32,
count: u16,
_: u16,
}
DECODE_SUB_BUCKETS :: 256 // per primary
`)
}
print_enum_buffered :: proc(sb: ^strings.Builder, x: $T, max_name: int, comma: bool) {
fmt.sbprintf(sb, ".%v", x)
if comma {
strings.write_string(sb, ", ")
} else {
return
}
for n := max_name-len(reflect.enum_string(x)); n > 0; n -= 1 {
strings.write_byte(sb, ' ')
}
}
print_enum_space :: proc(sb: ^strings.Builder, x: $T, max_name: int) {
for n := max_name-len(reflect.enum_string(x)); n > 0; n -= 1 {
strings.write_byte(sb, ' ')
}
}
max_enum_name_len :: proc($T: typeid) -> int {
max_len := 0
for name in reflect.enum_field_names(T) {
max_len = max(max_len, len(name))
}
return max_len
}
emit_entries :: proc(sb: ^strings.Builder, entries: []Entry) {
fmt.sbprintfln(sb, "@(rodata)")
fmt.sbprintfln(sb, "DECODE_ENTRIES := [%d]Decode_Entry{{", len(entries))
max_mnemonic_len := max_enum_name_len(p.Mnemonic)
max_op_len := max_enum_name_len(p.Operand_Type)
max_enc_len := max_enum_name_len(p.Operand_Encoding)
max_feature_len := max_enum_name_len(p.Feature)
max_mode_len := max_enum_name_len(p.Mode)
for e in entries {
strings.write_string(sb, "\t{")
defer strings.write_string(sb, "},\n")
print_enum_buffered(sb, e.mnemonic, max_mnemonic_len, true)
flags_str := encode_flags_literal(e.flags)
strings.write_string(sb, "{")
for op_type, i in e.ops {
print_enum_buffered(sb, op_type, max_op_len, i+1 < len(e.ops))
}
strings.write_string(sb, "}, ")
print_enum_space(sb, e.ops[len(e.ops)-1], max_op_len)
strings.write_string(sb, "{")
for enc_type, i in e.enc {
print_enum_buffered(sb, enc_type, max_enc_len, i+1 < len(e.enc))
}
strings.write_string(sb, "}, ")
print_enum_space(sb, e.enc[len(e.enc)-1], max_enc_len)
fmt.sbprintf(sb, "0x%08X, 0x%08X, ", e.bits, e.mask)
print_enum_buffered(sb, e.feature, max_feature_len, true)
print_enum_buffered(sb, e.mode, max_mode_len, true)
fmt.sbprintf(sb, "{{%s}}", flags_str)
}
strings.write_string(sb, "}\n\n")
}
encode_flags_literal :: proc(f: p.Encoding_Flags) -> string {
sb: strings.Builder
strings.builder_init(&sb)
first := true
w :: proc(sb: ^strings.Builder, first: ^bool, s: string) {
if !first^ { strings.write_string(sb, ", ") }
strings.write_string(sb, s)
first^ = false
}
if f.branch { w(&sb, &first, "branch=true") }
if f.cond_branch { w(&sb, &first, "cond_branch=true") }
if f.writes_lr { w(&sb, &first, "writes_lr=true") }
if f.sets_cr0 { w(&sb, &first, "sets_cr0=true") }
if f.sets_cr1 { w(&sb, &first, "sets_cr1=true") }
if f.abs_branch { w(&sb, &first, "abs_branch=true") }
if f.has_oe { w(&sb, &first, "has_oe=true") }
if f.prefixed { w(&sb, &first, "prefixed=true") }
return strings.to_string(sb)
}
emit_range_table :: proc(sb: ^strings.Builder, name: string, ranges: []Range) {
fmt.sbprintfln(sb, "@(rodata)")
fmt.sbprintf(sb, "%s := [%d]Decode_Index{{", name, len(ranges))
amount_set := 0
for r, i in ranges {
if r.count != 0 {
if amount_set % 16 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "0x%04X = {{% 5d, % 4d, 0}}, ", i, r.start, r.count)
amount_set += 1
}
}
strings.write_string(sb, "\n}\n\n")
}
emit_form_idx :: proc(sb: ^strings.Builder, entries: []Entry) {
fmt.sbprintfln(sb, "@(rodata)")
all_zero := true
for e in entries {
if e.form_idx != 0 {
all_zero = false
break
}
}
if all_zero {
fmt.sbprintf(sb, "DECODE_FORM_IDX: [%d]u16\n\n", len(entries))
} else {
fmt.sbprintf(sb, "DECODE_FORM_IDX := [%d]u16{{", len(entries))
for e, i in entries {
if i % 64 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "%d, ", e.form_idx)
}
strings.write_string(sb, "\n}\n\n")
}
}
emit_bucket_list :: proc(sb: ^strings.Builder, items: []u16) {
fmt.sbprintfln(sb, "@(rodata)")
fmt.sbprintf(sb, "DECODE_BUCKET_LIST := [%d]u16{{", len(items))
for v, i in items {
if i % 64 == 0 { strings.write_string(sb, "\n\t") }
fmt.sbprintf(sb, "% 4d, ", v)
}
strings.write_string(sb, "\n}\n\n")
}

View File

@@ -1,4 +1,6 @@
#!/bin/bash
# rexcode · Brendan Punsky (dotbmp@github), original author
# Per-line llvm-mc disassembly wrapper. Reads a .hex manifest, runs each line
# through llvm-mc independently so output lines align 1:1 with input lines.
# (llvm-mc's stream mode is greedy: it consumes prefix bytes when a line looks

View File

@@ -1,3 +1,5 @@
// rexcode · Brendan Punsky (dotbmp@github), original author
package main
// =============================================================================