mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-19 16:42:33 +00:00
481 lines
18 KiB
Odin
481 lines
18 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_arm32_tablegen
|
|
|
|
// =============================================================================
|
|
// AArch32 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 (flattened encode)
|
|
// generated/decode_tables.odin DECODE_ENTRIES + DECODE_FORM_IDX +
|
|
// DECODE_BUCKET_LIST + A32/T32/T16/T32_SUB index
|
|
// generated/writer.odin Stage B: serialize those globals to ../../tables/*.bin
|
|
//
|
|
// It also re-emits the library loader ../tables.odin. Run:
|
|
// odin run arm32/tablegen # Stage A
|
|
// odin run arm32/tablegen/generated # Stage B
|
|
//
|
|
// AArch32 is fixed-width-per-mode (A32 = 4 bytes, T16 = 2 bytes, T32 = 4 bytes
|
|
// packed as low_halfword | high_halfword << 16). Three primary dispatch tables
|
|
// (one per mode/size) with a secondary T32 sub-bucket; the decode bucketing
|
|
// below is ported verbatim from the old tools/gen_decode_tables.odin.
|
|
|
|
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", "arm32.encode_forms.bin", "Encoding"},
|
|
{"ENCODE_RUNS", "arm32.encode_runs.bin", "Encode_Run"},
|
|
{"DECODE_ENTRIES", "arm32.entries.bin", "Decode_Entry"},
|
|
{"DECODE_FORM_IDX", "arm32.form_idx.bin", "u16"},
|
|
{"DECODE_BUCKET_LIST", "arm32.bucket_list.bin", "u16"},
|
|
{"DECODE_INDEX_A32", "arm32.idx_a32.bin", "Decode_Index"},
|
|
{"DECODE_INDEX_T32", "arm32.idx_t32.bin", "Decode_Index"},
|
|
{"DECODE_INDEX_T16", "arm32.idx_t16.bin", "Decode_Index"},
|
|
{"DECODE_INDEX_T32_SUB", "arm32.idx_t32_sub.bin", "Decode_Index"},
|
|
}
|
|
|
|
DIR_GEN :: #directory + "/generated/"
|
|
PATH_LOADER :: #directory + "/../tables.odin"
|
|
|
|
A32_BUCKETS :: 256 // bits[27:20]
|
|
T32_BUCKETS :: 128 // bits[31:25]
|
|
T16_BUCKETS :: 64 // bits[15:10]
|
|
T32_SUB_BUCKETS :: 32 // bits[24:20] of u32
|
|
|
|
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,
|
|
is_thumb32: bool,
|
|
key: u16, // primary dispatch key (8 bits A32, 7 bits T32, 6 bits T16)
|
|
ilen: u8,
|
|
form_idx: u16, // index of this form within ENCODING_TABLE[mnemonic]
|
|
}
|
|
|
|
Range :: struct { start: u16, count: u16 }
|
|
|
|
main :: proc() {
|
|
n := emit_encode_tables()
|
|
ne := emit_decode_tables()
|
|
emit_writer()
|
|
emit_loader()
|
|
fmt.printfln("arm32 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_arm32_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 (source: ENCODING_TABLE).\n\n")
|
|
strings.write_string(&sb, "import lib \"../..\"\n\n")
|
|
|
|
for m in Mnemonic { total += len(ENCODING_TABLE[m]) }
|
|
|
|
strings.write_string(&sb, "@(rodata)\n")
|
|
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")
|
|
emit_file(DIR_GEN + "encode_tables.odin", &sb)
|
|
return
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Decode side (ported from old tools/gen_decode_tables.odin)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
mode_rank :: proc(e: Entry) -> int {
|
|
if e.mode == .A32 { return 0 }
|
|
if e.is_thumb32 || e.ilen == 4 { return 1 }
|
|
return 2
|
|
}
|
|
|
|
// Variable bits within the bucket-key range. For each combination of those
|
|
// variable bits we emit a separate bucket key, so a single entry is reachable
|
|
// via every word that can match its mask. fixed_key must be sanitized via
|
|
// `bits & mask` — entry bits often carry a default value at variable positions
|
|
// (e.g. U=1 in LDR's base 0x05900000), and those defaults must not pre-set bits
|
|
// in the key or we'd skip the zero-side bucket during enumeration.
|
|
enumerate_keys :: proc(bits, mask: u32, key_shift: u32, key_bits: u32, out: ^[dynamic]u16) {
|
|
clear(out)
|
|
key_mask := (u32(1) << key_bits) - 1
|
|
fixed_key := ((bits & mask) >> key_shift) & key_mask
|
|
var_bits := (~mask >> key_shift) & key_mask
|
|
// Enumerate submasks of var_bits via the classic Gosper-style walk.
|
|
sub: u32 = 0
|
|
for {
|
|
append(out, u16(fixed_key | sub))
|
|
if var_bits == 0 { break }
|
|
if sub == var_bits { break }
|
|
sub = (sub - var_bits) & var_bits // next non-zero submask
|
|
}
|
|
}
|
|
|
|
emit_decode_tables :: proc() -> (total: int) {
|
|
all: [dynamic]Entry
|
|
defer delete(all)
|
|
|
|
for mn in Mnemonic {
|
|
for f, fi in ENCODING_TABLE[mn] {
|
|
ilen := lib.inst_size_from_bits(f.bits, f.mode)
|
|
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,
|
|
is_thumb32 = f.flags.thumb32,
|
|
ilen = ilen,
|
|
form_idx = u16(fi),
|
|
}
|
|
// Compute dispatch key per mode/size.
|
|
if e.mode == .A32 {
|
|
e.key = u16((f.bits >> 20) & 0xFF)
|
|
} else if e.is_thumb32 || ilen == 4 {
|
|
// T32 32-bit: bits[31:25] of packed u32 (top 7 bits of high halfword)
|
|
e.key = u16((f.bits >> 25) & 0x7F)
|
|
} else {
|
|
// T16: bits[15:10] of the halfword (stored in low 16 of u32)
|
|
e.key = u16((f.bits >> 10) & 0x3F)
|
|
}
|
|
append(&all, e)
|
|
}
|
|
}
|
|
|
|
// Sort: by mode group (A32 first, then T32-wide, then T16), then by key,
|
|
// then by mask popcount descending so more-specific forms match first.
|
|
slice.sort_by(all[:], proc(x, y: Entry) -> bool {
|
|
mx := mode_rank(x)
|
|
my := mode_rank(y)
|
|
if mx != my { return mx < my }
|
|
if x.key != y.key { return x.key < y.key }
|
|
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)
|
|
})
|
|
|
|
// First pass: collect (entry_idx, bucket_key) pairs across modes, expanding
|
|
// variable bits within the bucket-key range. Then group by bucket.
|
|
A32_Pair :: struct { bucket: u16, entry_idx: u16 }
|
|
a32_pairs: [dynamic]A32_Pair
|
|
t32_pairs: [dynamic]A32_Pair
|
|
t16_pairs: [dynamic]A32_Pair
|
|
t32_sub_pairs: [dynamic]A32_Pair
|
|
defer delete(a32_pairs); defer delete(t32_pairs)
|
|
defer delete(t16_pairs); defer delete(t32_sub_pairs)
|
|
|
|
keys: [dynamic]u16
|
|
defer delete(keys)
|
|
|
|
for e, i in all {
|
|
if e.mode == .A32 {
|
|
enumerate_keys(e.bits, e.mask, 20, 8, &keys)
|
|
for k in keys { append(&a32_pairs, A32_Pair{bucket = k, entry_idx = u16(i)}) }
|
|
} else if e.is_thumb32 || e.ilen == 4 {
|
|
enumerate_keys(e.bits, e.mask, 25, 7, &keys)
|
|
for k in keys { append(&t32_pairs, A32_Pair{bucket = k, entry_idx = u16(i)}) }
|
|
// Sub-bucket: bits 24:20 of word
|
|
sub_keys: [dynamic]u16
|
|
defer delete(sub_keys)
|
|
enumerate_keys(e.bits, e.mask, 20, 5, &sub_keys)
|
|
for k in keys {
|
|
for sk in sub_keys {
|
|
append(&t32_sub_pairs, A32_Pair{
|
|
bucket = k * T32_SUB_BUCKETS + sk,
|
|
entry_idx = u16(i),
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
enumerate_keys(e.bits, e.mask, 10, 6, &keys)
|
|
for k in keys { append(&t16_pairs, A32_Pair{bucket = k, entry_idx = u16(i)}) }
|
|
}
|
|
}
|
|
|
|
// Within each bucket we want most-specific (highest mask popcount) first,
|
|
// tiebreak by mnemonic, so the decoder's linear scan picks the most specific
|
|
// encoding before falling through to a more general one. Encode
|
|
// (bucket, -popcount, mnemonic) into a single u64 sort key.
|
|
Sort_Pair :: struct { sort_key: u64, entry_idx: u16, bucket: u16 }
|
|
rebuild :: proc(pairs: ^[dynamic]A32_Pair, all: []Entry) {
|
|
sortable := make([dynamic]Sort_Pair, 0, len(pairs))
|
|
defer delete(sortable)
|
|
for p in pairs^ {
|
|
e := all[p.entry_idx]
|
|
pop := u64(bits.count_ones(e.mask))
|
|
key := (u64(p.bucket) << 48) | ((255 - pop) << 32) | u64(e.mnemonic)
|
|
append(&sortable, Sort_Pair{
|
|
sort_key = key, entry_idx = p.entry_idx, bucket = p.bucket,
|
|
})
|
|
}
|
|
slice.sort_by_key(sortable[:], proc(s: Sort_Pair) -> u64 { return s.sort_key })
|
|
clear(pairs)
|
|
for s in sortable { append(pairs, A32_Pair{bucket = s.bucket, entry_idx = s.entry_idx}) }
|
|
}
|
|
rebuild(&a32_pairs, all[:])
|
|
rebuild(&t32_pairs, all[:])
|
|
rebuild(&t16_pairs, all[:])
|
|
rebuild(&t32_sub_pairs, all[:])
|
|
|
|
// Build a flat u16 dispatch list (DECODE_BUCKET_LIST). Each bucket points to
|
|
// a contiguous run of entry indices in that list.
|
|
a32_idx: [A32_BUCKETS]Range
|
|
t32_idx: [T32_BUCKETS]Range
|
|
t16_idx: [T16_BUCKETS]Range
|
|
t32_sub_idx: [T32_BUCKETS * T32_SUB_BUCKETS]Range
|
|
|
|
bucket_list: [dynamic]u16
|
|
defer delete(bucket_list)
|
|
|
|
emit_pairs :: proc(pairs: []A32_Pair, idx: []Range, list: ^[dynamic]u16) {
|
|
prev_bucket: i32 = -1
|
|
for p in pairs {
|
|
cur_bucket := i32(p.bucket)
|
|
if cur_bucket != prev_bucket {
|
|
idx[cur_bucket].start = u16(len(list))
|
|
idx[cur_bucket].count = 0
|
|
prev_bucket = cur_bucket
|
|
}
|
|
append(list, p.entry_idx)
|
|
idx[cur_bucket].count += 1
|
|
}
|
|
}
|
|
emit_pairs(a32_pairs[:], a32_idx[:], &bucket_list)
|
|
emit_pairs(t32_pairs[:], t32_idx[:], &bucket_list)
|
|
emit_pairs(t16_pairs[:], t16_idx[:], &bucket_list)
|
|
emit_pairs(t32_sub_pairs[:], t32_sub_idx[:], &bucket_list)
|
|
|
|
sb := strings.builder_make()
|
|
strings.write_string(&sb, "package rexcode_arm32_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), keyed by mode + primary key.\n\n")
|
|
strings.write_string(&sb, "import lib \"../..\"\n\n")
|
|
|
|
strings.write_string(&sb, "@(rodata)\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_u16_array(&sb, "DECODE_FORM_IDX", form_idx_slice(all[:]))
|
|
emit_u16_array(&sb, "DECODE_BUCKET_LIST", bucket_list[:])
|
|
|
|
emit_range(&sb, "DECODE_INDEX_A32", a32_idx[:])
|
|
emit_range(&sb, "DECODE_INDEX_T32", t32_idx[:])
|
|
emit_range(&sb, "DECODE_INDEX_T16", t16_idx[:])
|
|
emit_range(&sb, "DECODE_INDEX_T32_SUB", t32_sub_idx[:])
|
|
emit_file(DIR_GEN + "decode_tables.odin", &sb)
|
|
return len(all)
|
|
}
|
|
|
|
form_idx_slice :: proc(entries: []Entry) -> []u16 {
|
|
out := make([]u16, len(entries), context.temp_allocator)
|
|
for e, i in entries { out[i] = e.form_idx }
|
|
return out
|
|
}
|
|
|
|
emit_u16_array :: proc(sb: ^strings.Builder, name: string, items: []u16) {
|
|
strings.write_string(sb, "@(rodata)\n")
|
|
fmt.sbprintfln(sb, "%s := [%d]u16{{", name, len(items))
|
|
for v, i in items {
|
|
if i % 16 == 0 {
|
|
if i > 0 { strings.write_string(sb, "\n") }
|
|
strings.write_string(sb, "\t")
|
|
}
|
|
fmt.sbprintf(sb, " %d,", v)
|
|
}
|
|
strings.write_string(sb, "\n}\n\n")
|
|
}
|
|
|
|
emit_range :: proc(sb: ^strings.Builder, name: string, ranges: []Range) {
|
|
fmt.sbprintfln(sb, "%s := [%d]lib.Decode_Index{{", name, len(ranges))
|
|
for r, i in ranges {
|
|
if r.count != 0 {
|
|
fmt.sbprintfln(sb, "\t0x%02X = {{% 5d, % 3d}},", i, r.start, r.count)
|
|
}
|
|
}
|
|
strings.write_string(sb, "}\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.sets_flags { append(&parts, "sets_flags=true") }
|
|
if f.cond_in_28 { append(&parts, "cond_in_28=true") }
|
|
if f.branch { append(&parts, "branch=true") }
|
|
if f.cond_branch { append(&parts, "cond_branch=true") }
|
|
if f.writes_pc { append(&parts, "writes_pc=true") }
|
|
if f.thumb32 { append(&parts, "thumb32=true") }
|
|
if f.deprecated { append(&parts, "deprecated=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_arm32_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, // 1
|
|
}
|
|
#assert(size_of(Decode_Entry) == 21)
|
|
|
|
Decode_Index :: struct #packed {
|
|
start: u16,
|
|
count: u16,
|
|
}
|
|
#assert(size_of(Decode_Index) == 4)
|
|
|
|
DECODE_T32_SUB_BUCKETS :: 32
|
|
`
|
|
|
|
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_arm32\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)
|
|
}
|
|
}
|