//===-- 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";
}

// Like imm16, but the encoder emits a fixup_bank16 instead of fixup_16
// so the linker / OMF Loader patches the operand with (bankByte, 0) of
// the resolved symbol.  Used for the high half of `&symbol` 32-bit
// pointer constants — see LDA_Imm16_Bank below.
def imm16_bank : Operand<i16> {
  let ParserMatchClass = W65816ImmOp<"Imm16">;
  let OperandType      = "OPERAND_IMMEDIATE";
  let Type             = i16;
  let EncoderMethod    = "encodeImm16Bank";
  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;
}

// Parallel to InstImm16 but the operand uses the imm16_bank class so
// the encoder attaches a fixup_bank16.  Marked isCodeGenOnly so the
// asm matcher never picks it (hand-written .s can't currently express
// a "bank-of-symbol" reference; bring that in via syntax like
// `lda #^foo` later if needed).
class InstImm16Bank<bits<8> op, string mnem>
    : W65816Inst<(outs), (ins imm16_bank:$imm), !strconcat(mnem, "\t#$imm")> {
  let Size = 3;
  let isCodeGenOnly = 1;
  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;
}

// Absolute Long Indexed X.  EA = addr_long + X.  The bank comes from
// the operand's high byte, NOT from DBR — useful for accessing data
// in a known bank (typically bank 0) regardless of the caller's DBR
// state.  4-byte instruction.
class InstAbsLongX<bits<8> op, string mnem>
    : W65816Inst<(outs), (ins addrLong:$addr), !strconcat(mnem, "\t$addr, x")> {
  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
//
// `${addr}` (with curly braces) is the tablegen-recognized form of
// an operand placeholder; with bare `$addr ` you need a trailing
// space to delimit the placeholder from `)` / `]`, which then leaks
// into the printed output as `(0x42 )`.  The curly form delimits
// cleanly and round-trips without the cosmetic space.
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;
}

// Absolute indexed-indirect-X: `JMP (addr, X)` (opcode 0x7C).  Adds X
// to addr first, then reads the 16-bit pointer at that location and
// jumps there.  Useful for jump tables when the base address is known
// at assembly time.
class InstAbsIndX<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;
}

// Absolute long-indirect: `JMP [addr]` (opcode 0xDC).  Reads a 24-bit
// pointer from absolute address `addr` and jumps to that full bank+
// offset address.  Square brackets distinguish from the 16-bit
// indirect form.
class InstAbsIndLong<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.
