mirror of
https://github.com/odin-lang/Odin.git
synced 2026-09-03 02:33:37 +00:00
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>
This commit is contained in:
@@ -152,6 +152,9 @@ find_and_decode :: proc(word: u32, mode: Mode, ilen: u32, inst: ^Instruction, in
|
||||
// back to the index within ENCODING_TABLE[mnemonic]. Stored as
|
||||
// (form_idx + 1) so a zero hint means "not set".
|
||||
inst.form_id = DECODE_FORM_IDX[entry_idx] + 1
|
||||
// Carry the `.i32` suffix out with the instruction, so a decoded
|
||||
// instruction re-encodes to the same form without needing form_id.
|
||||
inst.dt = e.dt
|
||||
|
||||
// Cond: A32 entries with bits[31:28] variable in mask take cond from word
|
||||
if mode == .A32 && (e.mask >> 28) == 0 {
|
||||
|
||||
@@ -160,6 +160,7 @@ encode_one_inline :: #force_inline proc(
|
||||
if inst.form_id != 0 && int(inst.form_id) - 1 < len(forms) {
|
||||
f := &forms[inst.form_id - 1]
|
||||
if f.mode == inst.mode &&
|
||||
(inst.dt[0] == .NONE || f.dt == inst.dt) &&
|
||||
(want_len == 0 || inst_size_from_bits(f.bits, f.mode) == want_len) &&
|
||||
encoding_matches_inline(inst, f) &&
|
||||
inst.flags.sets_flags == f.flags.sets_flags &&
|
||||
@@ -170,6 +171,11 @@ encode_one_inline :: #force_inline proc(
|
||||
if form == nil {
|
||||
for &f in forms {
|
||||
if f.mode != inst.mode { continue }
|
||||
// The `.i32` / `.s32.f32` suffix. NEON reuses one operand shape
|
||||
// across every element width, so without this the scan can only
|
||||
// ever reach the first form of a shape. .NONE means the caller did
|
||||
// not say, and every form of the shape stays eligible.
|
||||
if inst.dt[0] != .NONE && f.dt != inst.dt { continue }
|
||||
if want_len > 0 && inst_size_from_bits(f.bits, f.mode) != want_len { continue }
|
||||
if !encoding_matches_inline(inst, &f) { continue }
|
||||
if inst.flags.sets_flags && !f.flags.sets_flags { continue }
|
||||
|
||||
@@ -397,6 +397,30 @@ Operand_Encoding :: enum u8 {
|
||||
|
||||
// ---- Encoding struct -------------------------------------------------------
|
||||
|
||||
// The data type an A32 mnemonic carries as a suffix: `vadd.i32`, `vcvt.s32.f32`,
|
||||
// `vstrb.8`. Unlike A64 -- where the arrangement belongs to each operand
|
||||
// (`add v0.4s, v1.4s, v2.4s`) -- in A32 it belongs to the INSTRUCTION, which is
|
||||
// why it lives here and on Instruction rather than on Operand.
|
||||
//
|
||||
// It is not decoration: NEON reuses one operand shape across every element
|
||||
// width, so `vadd.i8` and `vadd.f32` are both DPR,DPR,DPR and the type is the
|
||||
// only thing that separates their encodings. Without it the encoder can only
|
||||
// ever reach the first form of each shape.
|
||||
Data_Type :: enum u8 {
|
||||
NONE,
|
||||
S8, S16, S32, S64, // signed integer
|
||||
U8, U16, U32, U64, // unsigned integer
|
||||
I8, I16, I32, I64, // sign-agnostic integer
|
||||
F16, F32, F64, // floating point
|
||||
P8, P16, // polynomial
|
||||
BF16, // bfloat16
|
||||
SZ8, SZ16, SZ32, SZ64, // bare element size (`vmov.32`, `vstrb.8`)
|
||||
}
|
||||
|
||||
// Two slots because the convert family names both ends: `vcvt.s32.f32`.
|
||||
// Everything else leaves dt[1] as .NONE.
|
||||
Data_Types :: [2]Data_Type
|
||||
|
||||
Encoding :: struct #packed {
|
||||
mnemonic: Mnemonic, // 2
|
||||
ops: [4]Operand_Type, // 4
|
||||
@@ -406,8 +430,9 @@ Encoding :: struct #packed {
|
||||
feature: Feature, // 1
|
||||
mode: Mode, // 1
|
||||
flags: Encoding_Flags, // 1
|
||||
dt: Data_Types, // 2
|
||||
}
|
||||
#assert(size_of(Encoding) == 21)
|
||||
#assert(size_of(Encoding) == 23)
|
||||
|
||||
// ---- Length introspection --------------------------------------------------
|
||||
//
|
||||
|
||||
@@ -35,7 +35,13 @@ Instruction :: struct #packed {
|
||||
// bits). User-constructed instructions leave it at 0; the encoder then
|
||||
// falls back to first-shape-match. Stored as u16 over the two padding bytes.
|
||||
form_id: u16,
|
||||
_: [7]u8,
|
||||
// The `.i32` / `.s32.f32` suffix. Zero (.NONE) means "unspecified": the
|
||||
// encoder then takes the first form of the matching shape, which is what
|
||||
// every instruction did before this field existed. Set it and the encoder
|
||||
// picks the encoding for that type. Fits in what was already padding, so
|
||||
// Instruction does not grow.
|
||||
dt: Data_Types,
|
||||
_: [5]u8,
|
||||
}
|
||||
#assert(size_of(Instruction) == 88)
|
||||
|
||||
|
||||
@@ -108,21 +108,26 @@ sbprint :: proc(
|
||||
|
||||
strings.write_string(sb, " ")
|
||||
|
||||
// Compute the .<dt> suffix from the matched decoder entry (if any)
|
||||
// and from the operand kinds when no entry is available.
|
||||
// The `.<dt>` suffix. It is data on the instruction now, so there is
|
||||
// nothing to reconstruct: read it straight off. This is also what
|
||||
// makes the convert family print correctly -- `vcvt.s32.f32` names
|
||||
// both ends, and the old bit-pattern inference only ever produced one.
|
||||
dt_suffix := ""
|
||||
if i < len(inst_info) {
|
||||
dt := inst.dt
|
||||
if dt[0] == .NONE && i < len(inst_info) {
|
||||
de_idx := int(inst_info[i].decode_entry)
|
||||
if de_idx < len(DECODE_ENTRIES) {
|
||||
dt_suffix = infer_dt_suffix(&DECODE_ENTRIES[de_idx], inst)
|
||||
dt = DECODE_ENTRIES[de_idx].dt
|
||||
}
|
||||
}
|
||||
if dt_suffix == "" {
|
||||
if dt[0] == .NONE {
|
||||
dt_suffix = infer_dt_suffix_from_inst(inst)
|
||||
}
|
||||
|
||||
write_mnemonic(sb, inst.mnemonic, inst.cond, inst.flags.sets_flags, opts.uppercase)
|
||||
if dt_suffix != "" {
|
||||
if dt[0] != .NONE {
|
||||
write_data_type(sb, dt, opts.uppercase)
|
||||
} else if dt_suffix != "" {
|
||||
strings.write_string(sb, dt_suffix)
|
||||
}
|
||||
|
||||
@@ -391,6 +396,35 @@ wprintln :: proc(
|
||||
// Writers
|
||||
// =============================================================================
|
||||
|
||||
// `.i32`, `.s32.f32`, `.8`. Both slots print when the second is set.
|
||||
@(private="file")
|
||||
write_data_type :: proc(sb: ^strings.Builder, dt: Data_Types, uppercase: bool) {
|
||||
for d in dt {
|
||||
if d == .NONE { continue }
|
||||
strings.write_byte(sb, '.')
|
||||
name := DATA_TYPE_NAMES[d]
|
||||
for i in 0 ..< len(name) {
|
||||
c := name[i]
|
||||
if uppercase && c >= 'a' && c <= 'z' {
|
||||
strings.write_byte(sb, c - 'a' + 'A')
|
||||
} else {
|
||||
strings.write_byte(sb, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(rodata, private="file")
|
||||
DATA_TYPE_NAMES := [Data_Type]string{
|
||||
.NONE = "",
|
||||
.S8 = "s8", .S16 = "s16", .S32 = "s32", .S64 = "s64",
|
||||
.U8 = "u8", .U16 = "u16", .U32 = "u32", .U64 = "u64",
|
||||
.I8 = "i8", .I16 = "i16", .I32 = "i32", .I64 = "i64",
|
||||
.F16 = "f16", .F32 = "f32", .F64 = "f64",
|
||||
.P8 = "p8", .P16 = "p16", .BF16 = "bf16",
|
||||
.SZ8 = "8", .SZ16 = "16", .SZ32 = "32", .SZ64 = "64",
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, cond: u8, sets_flags: bool, uppercase: bool) {
|
||||
name, _ := reflect.enum_name_from_value(m)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,7 @@ Entry :: struct {
|
||||
feature: lib.Feature,
|
||||
mode: lib.Mode,
|
||||
flags: lib.Encoding_Flags,
|
||||
dt: lib.Data_Types,
|
||||
is_thumb32: bool,
|
||||
key: u16, // primary dispatch key (8 bits A32, 7 bits T32, 6 bits T16)
|
||||
ilen: u8,
|
||||
@@ -101,7 +102,7 @@ emit_encode_tables :: proc() -> (total: int) {
|
||||
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)
|
||||
write_row(&sb, f.mnemonic, f.ops, f.enc, f.bits, f.mask, f.feature, f.mode, f.flags, f.dt)
|
||||
}
|
||||
}
|
||||
strings.write_string(&sb, "}\n\n")
|
||||
@@ -170,6 +171,7 @@ emit_decode_tables :: proc() -> (total: int) {
|
||||
feature = f.feature,
|
||||
mode = f.mode,
|
||||
flags = f.flags,
|
||||
dt = f.dt,
|
||||
is_thumb32 = f.flags.thumb32,
|
||||
ilen = ilen,
|
||||
form_idx = u16(fi),
|
||||
@@ -301,7 +303,7 @@ emit_decode_tables :: proc() -> (total: int) {
|
||||
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)
|
||||
write_row(&sb, e.mnemonic, e.ops, e.enc, e.bits, e.mask, e.feature, e.mode, e.flags, e.dt)
|
||||
}
|
||||
strings.write_string(&sb, "}\n\n")
|
||||
|
||||
@@ -351,10 +353,11 @@ emit_range :: proc(sb: ^strings.Builder, name: string, ranges: []Range) {
|
||||
|
||||
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",
|
||||
feature: lib.Feature, mode: lib.Mode, flags: lib.Encoding_Flags,
|
||||
dt: lib.Data_Types) {
|
||||
fmt.sbprintf(sb, "\t{{ .%v, {{.%v,.%v,.%v,.%v}}, {{.%v,.%v,.%v,.%v}}, 0x%08X, 0x%08X, .%v, .%v, {{%s}}, {{.%v,.%v}} }},\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))
|
||||
bits, mask, feature, mode, flags_lit(flags), dt[0], dt[1])
|
||||
}
|
||||
|
||||
flags_lit :: proc(f: lib.Encoding_Flags) -> string {
|
||||
@@ -412,8 +415,9 @@ Decode_Entry :: struct #packed {
|
||||
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) == 21)
|
||||
#assert(size_of(Decode_Entry) == 23)
|
||||
|
||||
Decode_Index :: struct #packed {
|
||||
start: u16,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,8 +32,9 @@ Decode_Entry :: struct #packed {
|
||||
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) == 21)
|
||||
#assert(size_of(Decode_Entry) == 23)
|
||||
|
||||
Decode_Index :: struct #packed {
|
||||
start: u16,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user