Files
Odin/core/rexcode/isa/mos6502/mnemonics.odin
Brendan Punsky 5cba6402f6 rexcode: fix the mnemonic problems in arm32, mips, riscv and mos6502
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>
2026-08-26 20:46:33 -04:00

130 lines
4.5 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_mos6502
// =============================================================================
// MOS 6502 family mnemonics
// =============================================================================
//
// Covers the four CPU tiers we target:
// - NMOS official 6502 (56 mnemonics)
// - NMOS undocumented opcodes (~24, widely used on NES & Apple II)
// - 65C02 additions (Rockwell/WDC), incl. RMB/SMB/BBR/BBS bit ops
// - HuC6280 (PC Engine) additions: block xfer, swap regs, MMR ops
//
// The undocumented NMOS SAX (store A&X) and the HuC6280 SAX (swap A,X)
// share the assembler mnemonic `sax`. They are one Mnemonic with two sets
// of forms: the NMOS one takes a memory operand and the HuC6280 one takes
// none, so the operand shape alone picks the right encoding.
Mnemonic :: enum u16 {
INVALID = 0,
// -------------------------------------------------------------------------
// NMOS official
// -------------------------------------------------------------------------
// Arithmetic / logical
ADC, AND, ASL,
BIT,
CMP, CPX, CPY,
DEC, DEX, DEY,
EOR,
INC, INX, INY,
LSR,
ORA,
ROL, ROR,
SBC,
// Branches
BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS,
// Jumps / subroutines / interrupts
JMP, JSR, RTI, RTS,
BRK,
// Flag ops
CLC, CLD, CLI, CLV,
SEC, SED, SEI,
// Loads / stores
LDA, LDX, LDY,
STA, STX, STY,
// Stack / transfer
PHA, PHP, PLA, PLP,
TAX, TAY, TSX, TXA, TXS, TYA,
// NOP
NOP,
// -------------------------------------------------------------------------
// NMOS undocumented (common subset used on NES & Apple II)
// -------------------------------------------------------------------------
LAX, // LDA + LDX (load A and X from memory)
DCP, // DEC + CMP (memory)
ISC, // INC + SBC (also ISB)
RLA, // ROL + AND
RRA, // ROR + ADC
SLO, // ASL + ORA
SRE, // LSR + EOR
ALR, // AND #imm + LSR A
ANC, // AND #imm with carry from N
ARR, // AND #imm + ROR A
AXS, // X = (A AND X) - imm (also SBX)
LAS, // LDA / TSX / AND with stack pointer
ANE, // A = (A | $EE) AND X AND imm -- unstable (also XAA)
LXA, // A = X = (A | $EE) AND imm -- unstable
SHA, // store A AND X AND high+1
SHX, // store X AND high+1
SHY, // store Y AND high+1
TAS, // S = A AND X; store S AND high+1
JAM, // halt the CPU (also KIL / HLT)
USBC, // same as SBC #imm but at $EB (an undocumented alias)
DOP, // double NOP (skips one operand byte)
TOP, // triple NOP (skips two operand bytes)
// -------------------------------------------------------------------------
// 65C02 additions (Rockwell + WDC)
// -------------------------------------------------------------------------
BRA, // branch always
INA, // INC A
DEA, // DEC A
PHX, PHY,
PLX, PLY,
STZ, // store zero
TRB, TSB, // test-and-reset / test-and-set bits
STP, WAI, // WDC: stop, wait-for-interrupt
// 65C02 Rockwell bit ops -- 32 distinct opcodes
RMB0, RMB1, RMB2, RMB3, RMB4, RMB5, RMB6, RMB7,
SMB0, SMB1, SMB2, SMB3, SMB4, SMB5, SMB6, SMB7,
BBR0, BBR1, BBR2, BBR3, BBR4, BBR5, BBR6, BBR7,
BBS0, BBS1, BBS2, BBS3, BBS4, BBS5, BBS6, BBS7,
// -------------------------------------------------------------------------
// HuC6280 (PC Engine / TurboGrafx-16)
// -------------------------------------------------------------------------
SXY, // swap X, Y
SAX, // swap A,X (HuC6280) / store A AND X (NMOS undocumented)
SAY, // swap A, Y
CLA, CLX, CLY, // clear A / X / Y
CSH, CSL, // CPU speed high / low (7.16 MHz vs 1.79 MHz)
SET, // set T flag (next op uses zp address as accumulator)
ST0, ST1, ST2, // store immediate to MMR0/1/2
TAM, TMA, // transfer A to/from MMR (immediate-selected)
TST, // bit test of immediate against memory
BSR, // branch to subroutine (PC-relative)
// HuC6280 block transfer instructions (7-byte encoding)
// src(2) -> dst(2), len(2) bytes
TII, // transfer increment-increment (memcpy ascending)
TDD, // transfer decrement-decrement (memcpy descending)
TIN, // transfer increment-no-change (fill from src)
TIA, // transfer increment-alternate (interleaved)
TAI, // transfer alternate-increment (interleaved)
}