371 lines
12 KiB
TableGen
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.
|