Commit Graph

6 Commits

Author SHA1 Message Date
Brendan Punsky
dcaab1aa85 rexcode/arm64: system registers get a type instead of being bare i64
They were plain i64 constants handed to op_imm, so any integer typed as
one and `inst_mrs(X0, 999999)` compiled fine. Worse, the printer could
not tell a system register from an immediate and had to recover the
distinction by mnemonic and slot -- MSR's other form holds a PSTATE
field selector in the same position, so it keyed off whether operand 1
was a register.

System_Register is now its own type with its own Operand_Kind, union
member and op_sysreg constructor, exactly as Cond is. The printer's slot
logic is gone: the operand knows what it is, so naming it is a case in
the same switch that prints every other operand kind. MSR's PSTATE
selector is typed PSTATE_FIELD, which is what it always was.

It cannot join `Register` itself: that is a u16 with the class in its
high byte, leaving 8 bits for the number, and a system register needs
15. Widening it would break `Memory`, which packs two registers plus a
displacement and a mode into exactly 64 bits.

The constants are also reorganised. They had accreted into overlapping
sections -- two "ID registers" groups, three cache groups, a "Batch 5:
comprehensive sysreg sweep" banner, and a "hmm let me recompute" note
left in a comment. All 231 are now grouped by architectural function
(18 groups, alphabetical within each) with their five fields aligned.

Verified unchanged against llvm-mc: 222 registers byte-exact through
MRS, 8 write-only through MSR, and the PSTATE form still decodes as an
immediate rather than a register.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA
2026-08-27 21:13:12 -04:00
Brendan Punsky
5627cc94d6 rexcode/arm64: add the cset/csetm/cinc/cinv/cneg aliases
These are what an assembler writes -- and what one prints back -- for
CSINC/CSINV/CSNEG with the condition inverted, and none of the five
existed. A disassembly of `cset w0, eq` came out as
`csinc w0, wzr, wzr, ne`.

Two constraints the table could not state before:

  - The condition is stored inverted, so COND_HI_INV packs `cond ~ 1`
    and reads it back the same way. The printer needs nothing; the
    decoder hands it a plain condition operand.

  - cinc/cinv/cneg are only the alias when Rn == Rm, which is a
    cross-field equality no mask expresses. One operand fills both
    slots on the way in (RN_RM), and decode checks the two fields agree
    before accepting the entry -- reached only on a mask match, so it
    costs nothing in the scan.

The aliases also require cond != 111x. That one *is* expressible: the
14 legal values are covered exactly by three masked patterns (0xxx,
10xx, 110x), so AL and NV fall through to the underlying instruction
the way llvm-mc does. COND_NOT_AL rejects them on the encode side.

Verified against llvm-mc across all five mnemonics, both widths and all
14 conditions: 140/140 of our printed strings assemble to exactly our
bytes. Disassembly agrees except for cs/hs and cc/lo, which is the
package's existing spelling of those two conditions and shows up on
CSEL and B.cond alike. AL/NV and Rn != Rm both fall through correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA
2026-08-27 08:59:59 -04:00
Brendan Punsky
dd925287ad rexcode/arm64: fix the encode, decode and print bugs the mnemonic pass exposed
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>
2026-08-26 18:24:26 -04:00
Brendan Punsky
f4bd6d74f4 rexcode/arm64: mnemonics are assembler mnemonics, not per-encoding names
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>
2026-08-26 17:51:36 -04:00
Brendan Punsky
fae15847a3 rexcode: buffer-sizing helpers across all ISAs + naming-contract doc
Roll the encode/decode buffer-sizing helpers (added for x86 in 49787b7de) out
to every other ISA, and document them in the cross-arch naming contract.

Per arch (arm32, arm64, mips, riscv, ppc, ppc_vle, rsp, mos6502, mos65816):
  - encode_max_code_size / encode_max_relocation_count now key off the
    []Instruction slice (were int counts); bodies unchanged (* MAX_INST_SIZE).
  - encode_reserve(code, relocs, instructions): grows the caller's code []u8 by
    length and reserves relocs by capacity; allocates no new buffers.
  - decode_max_instruction_count / decode_estimate_instruction_count: exact
    ceiling and typical estimate, keyed off the min/avg instruction size per
    arch (fixed-4: arm64/mips/ppc/rsp; min-2: arm32/riscv/ppc_vle; min-1: mos).
  - decode_reserve(instructions, inst_info, label_defs, data, exact=false).

docs/cross_arch_design.md: helpers added to the naming contract.

No behavior change to the existing size helpers (signature only). All 10 ISAs
check + test green (x86 2282, arm32 600, arm64 461, mips 281, riscv 154, ppc 31,
ppc_vle 281, rsp 70, mos6502 148, mos65816 53).
2026-06-19 04:11:30 -04:00
Brendan Punsky
95df04fbe1 rexcode: re-house ISA packages under core:rexcode/isa/<arch>
Move all ten ISA packages (x86, arm32, arm64, mips, riscv, ppc, ppc_vle,
rsp, mos6502, mos65816) from core/rexcode/<arch> to core/rexcode/isa/<arch>,
so the import pattern is now `import "core:rexcode/isa/x86"`. The shared
core stays at core:rexcode/isa.

Mechanical: relative `import "../isa"` / "../../isa" -> absolute
"core:rexcode/isa" (the only path that survives the move; the "../" and
"../.." self/generated imports move with their packages). build.lua now
builds paths as <root>/isa/<name>; stale `cd <arch>` hints in the verify
tools and the doc.odin paths updated.

WASM stays at core/rexcode/wasm for now -- it is an IR, not an ISA, and
will move under the forthcoming core:rexcode/ir once that layer lands.

All 10 arches gen/builders/check/test green; import core:rexcode/isa/x86
verified working; wasm still compiles.
2026-06-18 19:03:27 -04:00