Files
Odin/core/rexcode/isa/arm32/tables.odin
Brendan Punsky 2b06f2f81f rexcode/arm32: make the NEON data type an instruction field
NEON reuses one operand shape across every element width, so `vadd.i8` and
`vadd.f32` are both DPR,DPR,DPR and only the type separates their encodings.
The type existed nowhere in the data: the encoder could reach the first form
of a shape and no other, and the printer reconstructed a suffix from the bit
pattern at print time. 489 of 1680 forms -- 29% of the table -- were
unreachable, and `inst_vadd(d0,d1,d2)` could only ever produce VFP vadd.f64.

Add `Data_Type` and carry `dt: [2]Data_Type` on Instruction, Encoding and
Decode_Entry. Two slots because the convert family names both ends
(`vcvt.s32.f32`); everything else leaves the second .NONE. In A64 the
arrangement belongs to each operand (`add v0.4s, v1.4s, v2.4s`); in A32 it
belongs to the instruction, which is why it goes here and not on Operand.

Instruction does not grow: it lands in bytes that were already padding, so
88 stays 88. Encoding and Decode_Entry go 21 -> 23, which is +3,360 B per
table, +6.7 KB in all.

The per-form type is derived from llvm-mc rather than hand-written: assemble
each form's canonical word, disassemble it, take the suffix. 942 forms carry
one, 38 carry two. (`.w` is the Thumb wide qualifier, not a type, and is
excluded.)

Effect: of 202 shape groups holding more than one form, 168 are now separated
by the type -- 429 of the 489 unreachable forms become selectable. `dt` left
at .NONE means "unspecified" and still takes the first matching form, so
every existing caller behaves exactly as before.

It also fixes printing. The old inference could only ever produce one type,
so the whole convert family printed `vcvt.f32` -- 13 forms sharing one string
that no assembler accepts. They now print `vcvt.f32.s32`, `vcvt.f64.f32`,
`vcvta.u32.f64`, and so on.

Verified: vadd.i8/i16/i32/i64/f32 encode to f2010802 / f2110802 / f2210802 /
f2310802 / f2010d02, matching llvm-mc exactly; all 11 rexcode suites are
identical to baseline.

Still unreachable, 60 forms in 34 groups: register lists (VLD2-4/VST2-4),
LDM/STM addressing modes, and a few lane-indexed and fixed-point convert
forms whose element size is not captured by the type alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:24:19 -04:00

72 lines
3.0 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_arm32
// =============================================================================
// 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, // 1
dt: Data_Types, // 2 -- see Data_Type in encoding_types.odin
}
#assert(size_of(Decode_Entry) == 23)
Decode_Index :: struct #packed {
start: u16,
count: u16,
}
#assert(size_of(Decode_Index) == 4)
DECODE_T32_SUB_BUCKETS :: 32
// -----------------------------------------------------------------------------
// Loaded tables (rodata, embedded from tables/*.bin at compile time)
// -----------------------------------------------------------------------------
@(rodata) ENCODE_FORMS := #load("tables/arm32.encode_forms.bin", []Encoding)
@(rodata) ENCODE_RUNS := #load("tables/arm32.encode_runs.bin", []Encode_Run)
@(rodata) DECODE_ENTRIES := #load("tables/arm32.entries.bin", []Decode_Entry)
@(rodata) DECODE_FORM_IDX := #load("tables/arm32.form_idx.bin", []u16)
@(rodata) DECODE_BUCKET_LIST := #load("tables/arm32.bucket_list.bin", []u16)
@(rodata) DECODE_INDEX_A32 := #load("tables/arm32.idx_a32.bin", []Decode_Index)
@(rodata) DECODE_INDEX_T32 := #load("tables/arm32.idx_t32.bin", []Decode_Index)
@(rodata) DECODE_INDEX_T16 := #load("tables/arm32.idx_t16.bin", []Decode_Index)
@(rodata) DECODE_INDEX_T32_SUB := #load("tables/arm32.idx_t32_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]
}