The arm64 pass turned up the same class of bug elsewhere: mnemonics named
after an encoding rather than after what an assembler accepts, and forms
that no caller can reach because the thing that tells them apart is not
checked.
mips
* The printer mapped every `_` to `.`, but MSA spells the sign qualifier
with an underscore and only the element size with a dot: `adds_s.b`,
`max_s.h`, `copy_u.w`. `adds.s.b` is rejected by an assembler. 91
mnemonics were printing text that would not reassemble. The name alone
cannot decide it -- MSA's ADDS_S_D and the FP convert CVT_S_D have the
same shape and want opposite treatment -- so the family is read off the
form's feature.
* `encode` now takes `features: Feature_Set = FEATURES_ALL` and skips
forms outside it, mirroring `decode`, which has had that parameter all
along. That asymmetry was the reason 12 mnemonics carried an ISA-variant
suffix: with no way to say which MIPS you were targeting, the pre-R6 and
R6 encodings of `mul` had to be two enum members. They are now one
mnemonic with two forms. Eight of the twelve did not even need the
feature filter -- pre-R6 MADD takes rs,rt while the PS2 MMI MADD takes
rd,rs,rt, so operand matching alone separates them. Verified against
llvm-mc: pre-R6 `mul` 712a4002, R6 `mul` 012a4098, `madd $t1,$t2`
712a0000. The printer's hand-written override table is gone.
arm32
* 20 `*_LANE` mnemonics folded into their base. The lane form differs from
the base in an operand TYPE already (DPR_ELEM vs DPR), so the matcher
could always tell them apart; the split only cost us the printed name,
which was the enum name verbatim -- `vqdmulh_lane`, which no assembler
takes. VMOV/VLD1-4/VST1-4 are left alone: their lane forms collide with
the base because register lists and lane indices are not modelled.
riscv
* ZEXT_H and REV8 each carry an RV32 and an RV64 encoding with identical
operands, and the forms were already tagged rv32_only / rv64_only -- the
encoder just never looked. `encode` now takes `xlen: XLEN = .RV64` and
filters, so the RV64 encodings are reachable at all: zext.h 0805c53b and
rev8 6b85d513, both confirmed against llvm-mc.
mos6502
* SAX_NMOS folded into SAX. The undocumented NMOS store-A&X and the
HuC6280 register swap share the mnemonic `sax`; one takes a memory
operand and the other takes none, so they are just two form sets.
Verified: every rexcode suite matches HEAD exactly, all 13 packages build,
and MIPS mnemonics llvm-mc does not recognise drop from 448 to 354.
Still open: arm32 has 201 form signatures no caller can select, because the
NEON data type (.i8/.i16/.f32) is not an operand -- `inst_vadd(d0,d1,d2)`
always yields the first form, and only a decoder-supplied form_id hint can
pick another. 38 arm32 mnemonics still carry encoding-shaped names
(VPADD_F, VCEQ_Z, VLDRB_GATHER, VMOV_Q_R, ...).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Encoder
* LSR/ASR by immediate used the generic IMM12 encoding, which writes bits
10-21 -- straight into the imms field the UBFM/SBFM base pattern already
fills, so immr stayed 0 and every shift encoded as #0 (`asr x0,x1,#7`
gave 9340fc20, not 9347fc20). They need immr alone, since imms is the
constant 31/63 fixed in the form: new ENC_SHIFT_IMMR.
* LDP/STP and friends borrowed the single-register addressing encodings,
which put an UNSCALED 9-bit displacement at bits 20:12 and OR a pre/post
marker into bits 11:10. The pair forms want a SCALED 7-bit value at
21:15, and bits 11:10 are part of Rt2 -- so `ldp x0,x1,[x2,#16]!` came
back with Rt2=3. New OFFSET_PAIR_4/8/16 (the scale does not follow from
the register type: LDPSW pairs X registers but loads words, STGP scales
by 16), with the addressing mode read from bits[24:23] where the
architecture keeps it. 26 forms retargeted.
Decoder
* Vector operands came back with size=4 always, so a decoded V register
lost its arrangement and disassembly printed a bare `v0` that no
assembler would take. Reconstruct it from the form's operand type.
* Vd/Vn/Vm/Va hardcoded REG_V, but SVE forms use those same slots with
Z_REG_* operands -- `add z0.d, z0.d, z0.d` decoded as a V register.
Take the class from the operand type, as every other slot already does.
Printer
* V/Z registers now print their arrangement (`add v0.4s, v1.4s, v2.4s`,
`add z0.d, ...`). Element views (op_v_elem_*) moved from 1/2/4/8 to odd
codes 1/3/5/7, because an element-D view and an 8B arrangement were both
size 8 and could not be told apart.
* MOVZ/MOVN/MOVK print the hw index as `lsl #16`, omitted when zero.
* BC_COND folds its condition into the mnemonic like B_COND already did,
instead of printing it twice.
Table (each bit pattern re-derived from llvm-mc)
* BTI_J and BTI_C had each other's encodings.
* FCMLA's mask left size bit 22 free, so .4s and .2d were indistinguishable
and .2d decoded as .4s.
* BFDOT carried the Q=0 pattern for its .4s/.8h form; PMULLB/PMULLT were
missing the size field; TLBI PAALL/PAALLOS had the wrong CRm/op2.
* RDSVL's imm6 sits at bits 10:5, not where IMM6 puts it: ENC_IMM6_LO.
Nine test expectations that asserted the wrong values were corrected.
specgen.lua
Was already dead before the mnemonic work -- it wrote to encoding_table.odin
and spliced a SPECGEN region, neither of which survived the merge into
instruction_table.odin. Retargeted, taught the canonical names, and made it
emit Form literals (Encoding + Clobber). It can no longer own whole
`.MNEM = { ... }` blocks either, since ADD now holds integer, NEON and SVE
forms together, so it MERGES: a form is added only when no (bits, mask)
match exists, and existing rows are never rewritten -- their hand-maintained
Clobber data has to survive a regeneration.
Verified: all 11 rexcode suites match HEAD exactly (arm64 461/461); the three
generator stages stay idempotent; a 73-case differential against llvm-mc is
byte-exact for both encode and decode round-trip. Over the whole decode table,
canonical-form disassembly re-assembled by llvm-mc goes from 594 byte-exact /
1818 unassemblable to 1737 / 678. Re-running specgen re-derives 1130 forms
from llvm-mc and finds every one already present, which independently confirms
those bit patterns.
Still open: multi-vector register lists ({z0.b, z1.b}) and lane indices
(v0.s[2]) are not modelled, so those forms print without them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Mnemonic enum had one member per encoding form -- ADD_IMM, ADD_SR,
ADD_ER, ADD_V for what an assembler just calls ADD; LDR, LDR_LIT, LDR_PRE,
LDR_POST, LDR_REG, LDR_V for LDR; SVE_ADD_Z / SVE_ADD_PRED / SVE_AND_P for
names SVE spells ADD and AND. The encoder never needed that: like x86, it
already resolves a mnemonic by scanning its run of forms and matching
operand types, so the split bought nothing and cost a printer that had to
strip suffixes back off at runtime -- incompletely, so ADD_V printed
"add.v", LDR_PRE "ldr.pre" and FCVT_H_S "fcvt.h.s".
Collapse the enum to the names assemblers accept: 1104 -> 785 mnemonics,
with the variants becoming forms under one name (ADD now has 13, LDR 15).
LSLV/LSRV/ASRV/RORV fold into LSL/LSR/ASR/ROR. Form order within a run is
precedence, and the original declaration order is already the order an
assembler resolves: "add w0,w1,w2" takes the shifted-register form, and
only the extended form can encode SP.
This needed one structural change. The matcher was blind to addressing
mode -- `case .MEM: return op.kind == .MEMORY` -- which is precisely why
LDR/LDR_PRE/LDR_POST/LDR_REG had to be separate mnemonics; all 20 merge
collisions were this and nothing else. Split Operand_Type.MEM into
mode-specific types (MEM_OFFSET/PRE/POST/REG/EXT plus four SVE), matching
how W_REG/W_SHIFTED/W_EXTENDED are already distinct types over one
register class. The decoder derives Address_Mode from `enc`, so it is
unaffected.
Encodings are unchanged: the multiset of (ops, enc, bits, mask, feature,
flags) over all forms is identical before and after except for two entries
deliberately dropped. NOT_V_ALIAS duplicated NOT_V byte for byte, and
MOV_V_ALIAS was wrong -- it encoded VN where the ORR-based MOV alias needs
VN_VM_DUP, so "mov v1.8b, v2.8b" would have emitted "orr v1.8b, v2.8b,
v0.8b".
AMX_* keeps its prefix: Apple's coprocessor is undocumented with no
assembler spelling, so there is no canonical name to collapse to and bare
"set"/"clr"/"ldx" would mislead. The two-token system instructions keep
theirs too and print with a space (dc zva, tlbi vae1, bti j).
Verified: all 11 rexcode suites match HEAD exactly (arm64 461/461); the
three generator stages round-trip idempotently; 754 of 785 mnemonics are
accepted by llvm-mc, the rest being AMX (24), TME (4, no +tme in this LLVM
build), B_COND/BC_COND and TBL2; and a 39-case encode/print differential
against llvm-mc matches 34, with the 5 others confirmed byte-identical at
HEAD (pre-existing LSR/ASR immediate and LDP pre-index packing bugs, and
printer gaps for vector arrangements and MOVZ/MOVK shifts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>