mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-13 19:30:27 +00:00
Merge branch 'bill/rexcode' of https://github.com/odin-lang/Odin into bill/rexcode
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
import "../isa"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
import "core:strings"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package rexcode_ppc
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc_tablegen
|
||||
|
||||
// =============================================================================
|
||||
// PowerPC ENCODING_TABLE
|
||||
447
core/rexcode/ppc/tablegen/gen.odin
Normal file
447
core/rexcode/ppc/tablegen/gen.odin
Normal 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)
|
||||
}
|
||||
}
|
||||
4768
core/rexcode/ppc/tablegen/generated/decode_tables.odin
Normal file
4768
core/rexcode/ppc/tablegen/generated/decode_tables.odin
Normal file
File diff suppressed because it is too large
Load Diff
10060
core/rexcode/ppc/tablegen/generated/encode_tables.odin
Normal file
10060
core/rexcode/ppc/tablegen/generated/encode_tables.odin
Normal file
File diff suppressed because it is too large
Load Diff
33
core/rexcode/ppc/tablegen/generated/writer.odin
Normal file
33
core/rexcode/ppc/tablegen/generated/writer.odin
Normal 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)))
|
||||
}
|
||||
70
core/rexcode/ppc/tables.odin
Normal file
70
core/rexcode/ppc/tables.odin
Normal 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]
|
||||
}
|
||||
BIN
core/rexcode/ppc/tables/ppc.bucket_list.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.bucket_list.bin
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
core/rexcode/ppc/tables/ppc.encode_forms.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.encode_forms.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.encode_runs.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.encode_runs.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.entries.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.entries.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.form_idx.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.form_idx.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.idx_primary.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.idx_primary.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.idx_sub.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.idx_sub.bin
Normal file
Binary file not shown.
BIN
core/rexcode/ppc/tables/ppc.prefix_bits.bin
Normal file
BIN
core/rexcode/ppc/tables/ppc.prefix_bits.bin
Normal file
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc_tests
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc_tests
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package rexcode_ppc_tests
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
|
||||
package main
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user