65816-llvm-mos/src/llvm/lib/Target/W65816/W65816InstrFormats.td
Scott Duensing a059aa8182 Checkpoint.
2026-05-01 00:52:24 -05:00

371 lines
12 KiB
TableGen

//===-- W65816InstrFormats.td - W65816 Instruction Formats -*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Instruction format definitions for the W65816. Every WDC 65816 instruction
// is 1-4 bytes: a single-byte opcode followed by 0-3 bytes of immediate data,
// direct-page offset, absolute address, long address, or PC-relative
// displacement.
//
// TSFlags encode which processor-mode bits (M, X) an instruction depends on.
// Bit 0: instruction assumes M clear (16-bit accumulator).
// Bit 1: instruction assumes M set (8-bit accumulator).
// Bit 2: instruction assumes X clear (16-bit index).
// Bit 3: instruction assumes X set (8-bit index).
// Instructions whose behavior does not depend on M/X leave all four bits 0.
// The REP/SEP scheduling pass consumes these bits to decide where to insert
// mode transitions.
//
//===----------------------------------------------------------------------===//
// Base instruction class. Subclasses set Size and fill in the opcode byte
// and operand fields.
class W65816Inst<dag outs, dag ins, string asmstr, list<dag> pattern = []>
: Instruction {
let Namespace = "W65816";
// The disassembler scaffold reads from the default "W65816" decoder table.
// Mode-ambiguous variants (LDA_Imm8, LDX_Imm8, ...) override this to
// "W65816MHigh" or "W65816XHigh" so they end up in separate tables that
// the scaffold does NOT consult, eliminating same-opcode conflicts in the
// default table. See comment in W65816InstrInfo.td.
let DecoderNamespace = "W65816";
let OutOperandList = outs;
let InOperandList = ins;
let AsmString = asmstr;
let Pattern = pattern;
// MC-layer-only instructions conservatively mark side effects so the
// scheduler can't reorder them. Lowering targets override as needed.
let mayLoad = true;
let mayStore = true;
let hasSideEffects = true;
// Mode-dependence bits consumed by the REP/SEP pass.
bit MLow = 0;
bit MHigh = 0;
bit XLow = 0;
bit XHigh = 0;
let TSFlags{0} = MLow;
let TSFlags{1} = MHigh;
let TSFlags{2} = XLow;
let TSFlags{3} = XHigh;
}
// Pseudo-instruction: expands away before emission.
class W65816Pseudo<dag outs, dag ins, string asmstr, list<dag> pattern>
: W65816Inst<outs, ins, asmstr, pattern> {
let isPseudo = 1;
let Size = 0;
let mayLoad = false;
let mayStore = false;
let hasSideEffects = false;
}
//===----------------------------------------------------------------------===//
// Operand classes.
//===----------------------------------------------------------------------===//
// Custom DiagnosticType values would require matching Match_<type> enum
// values in the parser; we rely on the generic Match_InvalidOperand
// message for now.
class W65816AsmOperand<string name> : AsmOperandClass {
let Name = name;
}
class W65816ImmOp<string name> : W65816AsmOperand<name> {
let RenderMethod = "addImmOperands";
}
class W65816AddrOp<string name> : W65816AsmOperand<name>;
class W65816PCRelOp<string name> : W65816AsmOperand<name>;
def imm8 : Operand<i16> {
let ParserMatchClass = W65816ImmOp<"Imm8">;
let OperandType = "OPERAND_IMMEDIATE";
let Type = i8;
let EncoderMethod = "encodeImm8";
let DecoderMethod = "decodeImm8";
}
def imm16 : Operand<i16> {
let ParserMatchClass = W65816ImmOp<"Imm16">;
let OperandType = "OPERAND_IMMEDIATE";
let Type = i16;
let EncoderMethod = "encodeImm16";
let DecoderMethod = "decodeImm16";
}
def addrDP : Operand<i16> {
let ParserMatchClass = W65816AddrOp<"AddrDP">;
let OperandType = "OPERAND_MEMORY";
let Type = i8;
let PrintMethod = "printAddrDP";
let EncoderMethod = "encodeAddr8";
let DecoderMethod = "decodeAddr8";
}
def addrAbs : Operand<i16> {
let ParserMatchClass = W65816AddrOp<"AddrAbs">;
let OperandType = "OPERAND_MEMORY";
let Type = i16;
let PrintMethod = "printAddrAbs";
let EncoderMethod = "encodeAddr16";
let DecoderMethod = "decodeAddr16";
}
def addrLong : Operand<i32> {
let ParserMatchClass = W65816AddrOp<"AddrLong">;
let OperandType = "OPERAND_MEMORY";
let Type = i32;
let PrintMethod = "printAddrLong";
let EncoderMethod = "encodeAddr24";
let DecoderMethod = "decodeAddr24";
}
def pcrel8 : Operand<OtherVT> {
let ParserMatchClass = W65816PCRelOp<"PCRel8">;
let OperandType = "OPERAND_PCREL";
let PrintMethod = "printPCRel8";
let EncoderMethod = "encodePCRel8";
let DecoderMethod = "decodePCRel8";
}
def pcrel16 : Operand<OtherVT> {
let ParserMatchClass = W65816PCRelOp<"PCRel16">;
let OperandType = "OPERAND_PCREL";
let PrintMethod = "printPCRel16";
let EncoderMethod = "encodePCRel16";
let DecoderMethod = "decodePCRel16";
}
//===----------------------------------------------------------------------===//
// Size-classified base instructions. Size is in bytes including opcode.
//===----------------------------------------------------------------------===//
// Single-byte implied-operand instruction.
class Inst1<bits<8> opcode, string asmstr>
: W65816Inst<(outs), (ins), asmstr> {
let Size = 1;
bits<8> Inst;
let Inst{7-0} = opcode;
}
//===----------------------------------------------------------------------===//
// Addressing-mode shorthand classes.
//
// Each class declares a concrete 16/24/32-bit instruction encoding and wires
// the named operand(s) into the bit positions following the opcode byte.
// Without the explicit `let Inst{...} = name;` assignments, tablegen emits an
// encoder that writes the opcode but leaves the operand bytes as zero.
//===----------------------------------------------------------------------===//
class InstImplied<bits<8> op, string mnem> : Inst1<op, mnem>;
class InstImm8<bits<8> op, string mnem>
: W65816Inst<(outs), (ins imm8:$imm), !strconcat(mnem, "\t#$imm")> {
let Size = 2;
bits<8> imm;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = imm;
}
class InstImm16<bits<8> op, string mnem>
: W65816Inst<(outs), (ins imm16:$imm), !strconcat(mnem, "\t#$imm")> {
let Size = 3;
bits<16> imm;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = imm;
}
class InstDP<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstAbs<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstAbsLong<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrLong:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 4;
bits<24> addr;
bits<32> Inst;
let Inst{7-0} = op;
let Inst{31-8} = addr;
}
class InstAbsX<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr, x")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstAbsY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr, y")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstDPX<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr, x")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstDPY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr, y")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
// DP indirect addressing modes (parens around the DP byte mean
// "the 16-bit pointer at $00:dp+0..1, used to form the effective
// address"). Without these, the asm parser falls through to the
// absolute opcode and silently corrupts memory at $00:dp.
//
// `(dp)` reads/writes through the pointer at DP+offset. 0x92/0xb2
// `(dp), y` same, then adds Y to form the data address. 0x91/0xb1
// `(dp, x)` adds X to dp first, then dereferences. 0x81/0xa1
class InstDPInd<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr),
!strconcat(mnem, "\t($addr )")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstDPIndY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr),
!strconcat(mnem, "\t($addr ), y")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstDPIndX<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr),
!strconcat(mnem, "\t($addr , x)")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
// Absolute indirect: `JMP (addr)` (opcode 0x6C). Reads a 16-bit
// pointer from absolute address `addr` and jumps there (bank-local).
// Used by the indirect-call trampoline in crt0/libgcc to dispatch
// through a function-pointer slot.
class InstAbsInd<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr),
!strconcat(mnem, "\t($addr )")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
// DP indirect long: `[dp]` reads a 24-bit pointer at DP+offset and
// uses it to form the data address (bank-crossing). `[dp], y` adds
// Y to that 24-bit pointer. Only available with square-bracket
// syntax — round parens are reserved for 16-bit indirection.
class InstDPIndLong<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr),
!strconcat(mnem, "\t[$addr ]")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstDPIndLongY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr),
!strconcat(mnem, "\t[$addr ], y")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
// Stack-relative addressing. Operand is an unsigned 8-bit offset added
// to S; reads / writes the byte (or word, in 16-bit M mode) at that
// stack address. Shares the addrDP operand class for parsing.
class InstStackRel<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$off), !strconcat(mnem, "\t$off, s")> {
let Size = 2;
bits<8> off;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = off;
}
// Stack-relative indirect indexed-Y: `LDA (off,S),Y`. Reads the 16-bit
// pointer stored at S+off, adds Y, then loads from that address. Used
// to dereference pointers spilled to a stack scratch slot — the only
// way the 65816 can deref a pointer not already in zero page.
class InstStackRelIndY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$off),
!strconcat(mnem, "\t($off, s), y")> {
let Size = 2;
bits<8> off;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = off;
}
class InstPCRel8<bits<8> op, string mnem>
: W65816Inst<(outs), (ins pcrel8:$dest), !strconcat(mnem, "\t$dest")> {
let Size = 2;
bits<8> dest;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = dest;
}
class InstPCRel16<bits<8> op, string mnem>
: W65816Inst<(outs), (ins pcrel16:$dest), !strconcat(mnem, "\t$dest")> {
let Size = 3;
bits<16> dest;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = dest;
}
// Branch / return / call flags are applied at the def site via `let`
// blocks (idiomatic LLVM approach) rather than through mixin classes, since
// TableGen's multi-inheritance can't override fields from unrelated bases.