Use INSTRUCTION_TABLE now

This commit is contained in:
gingerBill
2026-08-26 14:14:02 +01:00
parent 02f7bf5634
commit 82fd8791bf
8 changed files with 3732 additions and 8515 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -32,10 +32,11 @@ Clobber :: lib.Clobber
Blob :: struct { global, file, typ: string }
BLOBS := [?]Blob{
{"ENCODE_FORMS", "arm64.encode_forms.bin", "Encoding"},
{"ENCODE_RUNS", "arm64.encode_runs.bin", "Encode_Run"},
{"DECODE_ENTRIES", "arm64.entries.bin", "Decode_Entry"},
{"DECODE_INDEX_OP0", "arm64.idx_op0.bin", "Decode_Index"},
{"ENCODE_FORMS", "arm64.encode_forms.bin", "Encoding"},
{"CLOBBER_FORMS", "arm64.clobber_forms.bin", "Clobber"},
{"ENCODE_RUNS", "arm64.encode_runs.bin", "Encode_Run"},
{"DECODE_ENTRIES", "arm64.entries.bin", "Decode_Entry"},
{"DECODE_INDEX_OP0", "arm64.idx_op0.bin", "Decode_Index"},
}
DIR_GEN :: #directory + "/generated/"
@@ -73,16 +74,29 @@ emit_encode_tables :: proc() -> (total: int) {
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]) }
for m in Mnemonic { total += len(INSTRUCTION_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]
forms := INSTRUCTION_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)
e := f.encoding
write_row(&sb, e.mnemonic, e.ops, e.enc, e.bits, e.mask, e.feature, e.flags)
}
}
strings.write_string(&sb, "}\n\n")
strings.write_string(&sb, "@(rodata)\n")
fmt.sbprintfln(&sb, "CLOBBER_FORMS := [%d]lib.Clobber{{", total)
for m in Mnemonic {
forms := INSTRUCTION_TABLE[m]
if len(forms) == 0 { continue }
fmt.sbprintfln(&sb, "\t// .%v", m)
for f in forms {
write_clobber(&sb, f.clobber)
}
}
strings.write_string(&sb, "}\n\n")
@@ -93,7 +107,7 @@ emit_encode_tables :: proc() -> (total: int) {
strings.write_string(&sb, "ENCODE_RUNS := [lib.Mnemonic]lib.Encode_Run{\n")
start := 0
for m in Mnemonic {
c := len(ENCODING_TABLE[m])
c := len(INSTRUCTION_TABLE[m])
name := reflect.enum_string(m)
fmt.sbprintf(&sb, "\t.%s", name)
for _ in 0..<run_w-len(name) { strings.write_byte(&sb, ' ') }
@@ -105,6 +119,72 @@ emit_encode_tables :: proc() -> (total: int) {
return
}
write_clobber :: proc(sb: ^strings.Builder, clobber: lib.Clobber) {
strings.write_string(sb, "\t{")
defer strings.write_string(sb, "},\n")
n := 0
for field in reflect.struct_fields_zipped(lib.Clobber) {
value := reflect.struct_field_value(clobber, field)
if reflect.is_bit_set(field.type) {
if !reflect.is_nil(value) {
if n > 0 { strings.write_string(sb, ", ") }
defer n += 1
value_int := value
switch type_info_of(value.id).size {
case 1: value_int.id = u8
case 2: value_int.id = u16
case 4: value_int.id = u32
case 8: value_int.id = u64
}
bits := reflect.as_u64(value_int) or_else panic("cannot get bits")
bs := reflect.type_info_base(field.type).variant.(reflect.Type_Info_Bit_Set)
et := reflect.type_info_base(bs.elem)
lower := bs.lower
upper := bs.upper
strings.write_string(sb, field.name)
strings.write_string(sb, "={")
defer strings.write_byte(sb, '}')
e, is_enum := et.variant.(reflect.Type_Info_Enum)
commas := 0
loop: for i in transmute(bit_set[0..<64])bits {
elem := i64(i) + lower
if commas > 0 { strings.write_string(sb, ", ") }
if is_enum {
for ev, evi in e.values {
v := u64(ev)
if v == u64(elem) {
strings.write_string(sb, ".")
strings.write_string(sb, e.names[evi])
commas += 1
continue loop
}
}
} else {
fmt.sbprintf(sb, "%d", elem)
commas += 1
}
}
}
} else if reflect.is_boolean(field.type) {
if reflect.as_bool(value) or_else false {
if n > 0 { strings.write_string(sb, ", ") }
defer n += 1
strings.write_string(sb, field.name)
strings.write_string(sb, "=true")
}
} else {
panic("unhandled type")
}
}
}
// -----------------------------------------------------------------------------
// Decode side
// -----------------------------------------------------------------------------
@@ -119,13 +199,14 @@ emit_decode_tables :: proc() -> (total: int) {
all: [dynamic]Entry
defer delete(all)
for mn in Mnemonic {
for f in ENCODING_TABLE[mn] {
op0_static := u8((f.bits >> 25) & 0xF)
op0_mask := u8((f.mask >> 25) & 0xF)
for form in INSTRUCTION_TABLE[mn] {
e := form.encoding
op0_static := u8((e.bits >> 25) & 0xF)
op0_mask := u8((e.mask >> 25) & 0xF)
// Enumerate every 4-bit bucket B such that (B & op0_mask) == op0_static.
for b: u8 = 0; b < 16; b += 1 {
if (b & op0_mask) != op0_static { continue }
append(&all, Entry{f.mnemonic, f.ops, f.enc, f.bits, f.mask, f.feature, f.flags, b})
append(&all, Entry{e.mnemonic, e.ops, e.enc, e.bits, e.mask, e.feature, e.flags, b})
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@ w :: proc(file: string, data: []u8) {
main :: proc() {
w(TABLES + "arm64.encode_forms.bin", raw(&ENCODE_FORMS, size_of(ENCODE_FORMS)))
w(TABLES + "arm64.clobber_forms.bin", raw(&CLOBBER_FORMS, size_of(CLOBBER_FORMS)))
w(TABLES + "arm64.encode_runs.bin", raw(&ENCODE_RUNS, size_of(ENCODE_RUNS)))
w(TABLES + "arm64.entries.bin", raw(&DECODE_ENTRIES, size_of(DECODE_ENTRIES)))
w(TABLES + "arm64.idx_op0.bin", raw(&DECODE_INDEX_OP0, size_of(DECODE_INDEX_OP0)))

View File

@@ -1,5 +1,91 @@
package rexcode_arm64_tablegen
// =============================================================================
// rexcode · AArch64 INSTRUCTION_TABLE (v1: base integer + FP scalar)
// =============================================================================
//
// One []Form per Mnemonic. A Form fuses an instruction form's Encoding with its
// Clobber, so INSTRUCTION_TABLE[m][i] carries both the bit pattern and the
// read/write + side-effect summary for form i of mnemonic m. This replaces the
// old pair of index-aligned tables (ENCODING_TABLE[m][i] / CLOBBER_TABLE[m][i]);
// co-locating the two in one struct removes the alignment invariant, so a form's
// encoding and clobber can no longer drift out of sync.
//
//
// --- Encoding: (bits, mask) model --------------------------------------------
//
// Same shape as MIPS/RISC-V. `bits` carries the static opcode pattern; `mask`
// covers exactly the bits that are fixed by the form. Operand-driven fields
// (Rd/Rn/Rm/imm*/sh/cond/...) land in zero positions of `bits` and are ORed in
// by the encoder.
//
// Sections (each follows the ARM ARM "C4.1.x Data-processing /" division):
// §1 Data-processing -- immediate (Add/Sub imm, Mov-wide, PC-rel)
// §2 Data-processing -- shifted reg (Add/Sub/AND/ORR/EOR/BIC/ORN/EON)
// §3 Data-processing -- extended reg (Add/Sub extended)
// §4 Data-processing -- 2-source (LSLV/LSRV/ASRV/RORV, UDIV/SDIV)
// §5 Data-processing -- 3-source (MADD/MSUB/SMADDL/.../UMULH)
// §6 Data-processing -- 1-source (CLZ/CLS/RBIT/REV/REV16/REV32)
// §7 Conditional (CSEL/CSINC/CSINV/CSNEG)
// §8 Branches (B/BL/BR/BLR/RET, B.cond, CBZ, TBZ)
// §9 Loads / stores (LDR/STR families, LDP/STP, LDUR)
// §10 System (NOP/HINT/ISB/MSR/MRS/SVC/...)
// §11 FP scalar (FMOV/FADD/FCVT/FCMP/FCSEL/...)
//
// Logical-immediate forms (AND/ORR/EOR/ANDS imm) use the bitmask-immediate
// encoding (N:imms:immr); they're deferred to a follow-up turn that adds the
// bitmask encoder helper.
//
//
// --- Clobber: read/write + side-effect summary -------------------------------
//
// One Clobber per Form -- a read/write + side-effect summary for every form of
// every mnemonic. Derivation (from each form's operand TYPES, operand ROLES,
// and Encoding_Flags):
//
// written / read -- operand slots (0..3) whose register / memory base is a
// def / use. A slot counts only if its TYPE is register-
// or memory-like; immediate slots never appear. Direction
// comes from the ROLE, refined by instruction class:
// * RD/VD/PD/RT/RT2/ZA_TILE* are destinations, EXCEPT
// the data operand of a store (RT/VD on ST*), which is
// a source; and the status result of STXR/STXP (RD),
// which stays a def while RT/RT2 become sources.
// * A pre/post-index base (OFFSET_BASE_PRE/POST) is both
// read and written (address write-back).
// * A destination register re-listed as a later source
// slot (SVE destructive Zdn, accumulate) is read.
// * Branches define no GPR operand; targets/modifiers
// are reads.
// implicit_wr/rd -- LR on BL/BLR/RET, LR+SP on PAC*SP / RETAA, X16/X17 on
// PAC*1716, etc.
// nzcv_wr -- set for sets_flags forms (ADDS/SUBS/ANDS/CMP/CCMP/FCMP,
// SVE compares, WHILE*, PTRUES, ...).
// nzcv_rd -- any COND operand (CSEL/FCSEL/B.cond/CCMP), plus the
// carry readers ADC/ADCS/SBC/SBCS/NGC/NGCS; CCMP/CCMN both.
// fpsr_wr/reads_fpcr -- per FP-op family (see notes); saturating integer ops
// (SQ*/UQ*/SUQ*/USQ*/SQRDML*) set QC.
// reads_mem/writes_mem -- loads / stores / RMW atomics / MOPS.
// side_effects -- control flow, barriers, atomics, cache/TLB, PAC, BTI,
// wait, privileged system access, FFR, etc.
//
// Heuristic / best-effort fields (structurally faithful, semantically approximate):
// * FPSR exception-bit sets and reads_fpcr are assigned per mnemonic family,
// not per-corner-case; treat them as "flags this class may raise".
// * Accumulate detection (destination is read-modified) is name-driven for
// the families that fold the accumulator into the destination (MLA/MLS/DOT/
// FMLA/FCMLA/SQRDML*/MOPA, shift-accumulate, bit-insert, AES/SHA/SM3/SM4,
// PAC in-place). Multiply-adds with a separate Ra/Va operand (MADD/FMADD/...)
// are correctly NOT dest-reads.
// * LD1-4/ST1-4 register lists occupy a single operand slot, so max operand
// count stays at 4 (matches Operand_Set / the encoder's model).
// * Acquire/release accesses carry the FENCE side effect to model their
// one-way ordering (distinct from the register/memory read/write sets).
// * Requires the companion clobber_types.odin (adds X16/X17 to Clobber_Reg
// and FFR to Side_Effect).
// =============================================================================
Form :: struct {
using encoding: Encoding,
clobber: Clobber,

View File

@@ -44,10 +44,11 @@ Decode_Index :: struct #packed {
// Loaded tables (rodata, embedded from tables/*.bin at compile time)
// -----------------------------------------------------------------------------
@(rodata) ENCODE_FORMS := #load("tables/arm64.encode_forms.bin", []Encoding)
@(rodata) ENCODE_RUNS := #load("tables/arm64.encode_runs.bin", []Encode_Run)
@(rodata) DECODE_ENTRIES := #load("tables/arm64.entries.bin", []Decode_Entry)
@(rodata) DECODE_INDEX_OP0 := #load("tables/arm64.idx_op0.bin", []Decode_Index)
@(rodata) ENCODE_FORMS := #load("tables/arm64.encode_forms.bin", []Encoding)
@(rodata) CLOBBER_FORMS := #load("tables/arm64.clobber_forms.bin", []Clobber)
@(rodata) ENCODE_RUNS := #load("tables/arm64.encode_runs.bin", []Encode_Run)
@(rodata) DECODE_ENTRIES := #load("tables/arm64.entries.bin", []Decode_Entry)
@(rodata) DECODE_INDEX_OP0 := #load("tables/arm64.idx_op0.bin", []Decode_Index)
// -----------------------------------------------------------------------------
// Accessors

Binary file not shown.