Files
Odin/core/rexcode/riscv/tablegen/gen.odin

353 lines
13 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_riscv_tablegen
// =============================================================================
// RISC-V 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 + OPCODE / OP_FP / RVC 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 riscv/tablegen # Stage A
// odin run riscv/tablegen/generated # Stage B
//
// Decode dispatch (ported from the old tools/gen_decode_tables.odin):
// primary opcode (bits 6-0) -> DECODE_INDEX_OPCODE [128]
// opcode 0x53 OP-FP -> by funct7 -> DECODE_INDEX_OP_FP [128]
// compressed (RVC, 2-byte) words -> DECODE_INDEX_RVC [32]
// keyed by (op[1:0] | funct3[15:13]<<2)
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
// The SoT's bit/mask literals reference these field-mask constants.
MASK_OPCODE :: lib.MASK_OPCODE
MASK_RD :: lib.MASK_RD
MASK_FUNCT3 :: lib.MASK_FUNCT3
MASK_RS1 :: lib.MASK_RS1
MASK_RS2 :: lib.MASK_RS2
MASK_FUNCT7 :: lib.MASK_FUNCT7
MASK_IMM_I :: lib.MASK_IMM_I
MASK_SHAMT5 :: lib.MASK_SHAMT5
MASK_SHAMT6 :: lib.MASK_SHAMT6
MASK_R :: lib.MASK_R
MASK_I :: lib.MASK_I
MASK_I_SHIFT :: lib.MASK_I_SHIFT
MASK_S :: lib.MASK_S
MASK_B :: lib.MASK_B
MASK_U :: lib.MASK_U
MASK_J :: lib.MASK_J
Blob :: struct { global, file, typ: string }
BLOBS := [?]Blob{
{"ENCODE_FORMS", "riscv.encode_forms.bin", "Encoding"},
{"ENCODE_RUNS", "riscv.encode_runs.bin", "Encode_Run"},
{"DECODE_ENTRIES", "riscv.entries.bin", "Decode_Entry"},
{"DECODE_INDEX_OPCODE", "riscv.idx_opcode.bin", "Decode_Index"},
{"DECODE_INDEX_OP_FP", "riscv.idx_op_fp.bin", "Decode_Index"},
{"DECODE_INDEX_RVC", "riscv.idx_rvc.bin", "Decode_Index"},
}
DIR_GEN :: #directory + "/generated/"
PATH_LOADER :: #directory + "/../tables.odin"
Entry :: struct {
mnemonic: lib.Mnemonic,
ops: [4]lib.Operand_Type,
enc: [4]lib.Operand_Encoding,
bits: u32,
mask: u32,
feature: lib.Feature,
flags: lib.Encoding_Flags,
primary_op: u8,
sub_key: u8, // funct7 for opcode 0x53
is_rvc: bool, // 2-byte compressed instruction
rvc_key: u8, // RVC dispatch key: (op[1:0] | funct3[15:13]<<2), 5 bits
}
Range :: struct { start: u16, count: u16 }
main :: proc() {
n := emit_encode_tables()
ne := emit_decode_tables()
emit_writer()
emit_loader()
fmt.printfln("riscv 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_riscv_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.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, "@(rodata)\n")
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
// -----------------------------------------------------------------------------
emit_decode_tables :: proc() -> (total: int) {
all: [dynamic]Entry
defer delete(all)
for m in Mnemonic {
for f in ENCODING_TABLE[m] {
e := Entry{
mnemonic = f.mnemonic,
ops = f.ops,
enc = f.enc,
bits = f.bits,
mask = f.mask,
feature = f.feature,
flags = f.flags,
is_rvc = lib.inst_size_from_bits(f.bits) == 2,
}
if e.is_rvc {
// RVC: 5-bit dispatch (op[1:0] + funct3[15:13]<<2).
e.rvc_key = u8((f.bits & 0x3) | ((f.bits >> 13) & 0x7) << 2)
} else {
e.primary_op = u8(f.bits & 0x7F)
if e.primary_op == 0x53 {
e.sub_key = u8((f.bits >> 25) & 0x7F)
}
}
append(&all, e)
}
}
slice.sort_by(all[:], proc(a, b: Entry) -> bool {
if a.is_rvc != b.is_rvc { return !a.is_rvc } // non-RVC first
if a.is_rvc {
if a.rvc_key != b.rvc_key { return a.rvc_key < b.rvc_key }
} else {
if a.primary_op != b.primary_op { return a.primary_op < b.primary_op }
if a.sub_key != b.sub_key { return a.sub_key < b.sub_key }
}
ac := bits.count_ones(a.mask)
bc := bits.count_ones(b.mask)
if ac != bc { return ac > bc }
return u16(a.mnemonic) < u16(b.mnemonic)
})
primary_idx: [128]Range
op_fp_idx: [128]Range
rvc_idx: [32]Range
for e, i in all {
if e.is_rvc {
push(&rvc_idx[e.rvc_key], u16(i))
} else {
push(&primary_idx[e.primary_op], u16(i))
if e.primary_op == 0x53 {
push(&op_fp_idx[e.sub_key], u16(i))
}
}
}
sb := strings.builder_make()
strings.write_string(&sb, "package rexcode_riscv_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 primary opcode / funct7 / RVC 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.flags)
}
strings.write_string(&sb, "}\n\n")
emit_range(&sb, "DECODE_INDEX_OPCODE", primary_idx[:])
emit_range(&sb, "DECODE_INDEX_OP_FP", op_fp_idx[:])
emit_range(&sb, "DECODE_INDEX_RVC", rvc_idx[:])
emit_file(DIR_GEN + "decode_tables.odin", &sb)
return len(all)
}
push :: proc(r: ^Range, i: u16) { if r.count == 0 { r.start = i }; r.count += 1 }
emit_range :: proc(sb: ^strings.Builder, name: string, ranges: []Range) {
strings.write_string(sb, "@(rodata)\n")
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 = {{% 4d, % 3d}},", i, r.start, r.count)
}
}
strings.write_string(sb, "}\n\n")
}
// -----------------------------------------------------------------------------
// Shared row + flags formatting (compact, matching riscv's original generator)
// -----------------------------------------------------------------------------
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, flags: lib.Encoding_Flags) {
fmt.sbprintf(sb, "\t{{ .%v, {{.%v,.%v,.%v,.%v}}, {{.%v,.%v,.%v,.%v}}, 0x%08X, 0x%08X, .%v, {{%s}} }},\n",
mn, ops[0], ops[1], ops[2], ops[3], enc[0], enc[1], enc[2], enc[3], bits, mask, feature, flags_lit(flags))
}
flags_lit :: proc(f: lib.Encoding_Flags) -> string {
parts: [dynamic]string
defer delete(parts)
if f.rv32_only { append(&parts, "rv32_only=true") }
if f.rv64_only { append(&parts, "rv64_only=true") }
if f.branch { append(&parts, "branch=true") }
if f.fp_round { append(&parts, "fp_round=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_riscv_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
flags: Encoding_Flags, // 1
}
#assert(size_of(Decode_Entry) == 20)
Decode_Index :: struct #packed {
start: u16,
count: u16,
}
#assert(size_of(Decode_Index) == 4)
`
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_riscv\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)
}
}