//===-- W65816Disassembler.cpp - Disassembler for W65816 -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the W65816Disassembler class, which converts a byte
// stream into a stream of MCInsts.  W65816 instructions are 1-4 bytes long:
// a single-byte opcode followed by 0-3 bytes of little-endian operand data.
//
// For every opcode that has both an _Imm8 and _Imm16 variant (LDA, LDX, LDY,
// ADC, SBC, CMP, AND, ORA, EOR, BIT, CPX, CPY), the bytes on the wire are
// identical for both forms up to the opcode; the real operand width depends
// on the M/X processor-mode bits.  The TableGen descriptions put the _Imm8
// forms into the "W65816MHigh" / "W65816XHigh" decoder namespaces, so the
// default "W65816" table contains only the 3-byte _Imm16 forms.
//
// To resolve the ambiguity this disassembler tracks the M (accumulator/memory
// width) and X (index width) processor flags across a linear sweep: it seeds
// them to the C ABI default (M=16/X=16), watches the REP/SEP immediates as
// they decode, and — when a flag is 8-bit — consults the MHigh16 / XHigh16
// tables (2-byte: opcode + imm8) ahead of the default table so the affected
// mnemonics decode as 1-byte immediates.  This is a best-effort *linear*
// heuristic: it follows straight-line REP/SEP only and cannot see mode
// established across branches, by a caller's ABI convention, or through
// data-in-code, and there is no per-section reset hook, so a region entered
// in a different mode than the previous region left set may decode wrong.
// For clang-generated code (M=16/X=16 invariant) the default path is always
// correct; the tracking matters for hand-written 8-bit-mode asm.
//
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "TargetInfo/W65816TargetInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDecoder.h"
#include "llvm/MC/MCDecoderOps.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"

using namespace llvm;
using namespace llvm::MCD;

#define DEBUG_TYPE "w65816-disassembler"

using DecodeStatus = MCDisassembler::DecodeStatus;

namespace {

class W65816Disassembler : public MCDisassembler {
  // Tracked processor-mode bits, seeded to the C ABI default (16-bit).
  // getInstruction is called sequentially per instruction by llvm-objdump /
  // llvm-mc, so this mutable state follows the byte stream linearly.
  mutable bool AccIs8 = false; // M flag: true => 8-bit accumulator/memory
  mutable bool IdxIs8 = false; // X flag: true => 8-bit index registers

  // A copy of the subtarget with the MHigh / XHigh assembler predicates forced
  // on, used only when decoding the mode-sensitive _Imm8 tables.  The generated
  // decoder gates those entries behind FeatureMHigh / FeatureXHigh; the running
  // subtarget leaves them off, so we supply an enabled copy when (and only
  // when) our own M/X tracking has decided an 8-bit form applies.
  MCSubtargetInfo ModeSTI;

public:
  W65816Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
      : MCDisassembler(STI, Ctx), ModeSTI(STI) {
    FeatureBitset FB = ModeSTI.getFeatureBits();
    FB.set(W65816::FeatureMHigh);
    FB.set(W65816::FeatureXHigh);
    ModeSTI.setFeatureBits(FB);
  }

  DecodeStatus getInstruction(MCInst &MI, uint64_t &Size,
                              ArrayRef<uint8_t> Bytes, uint64_t Address,
                              raw_ostream &CStream) const override;
};

} // end anonymous namespace

//===----------------------------------------------------------------------===//
// Operand decoder callbacks.  The encodings are little-endian immediates
// immediately following the opcode; each callback just creates an MCOperand
// whose integer value is the raw field bits.  The printer does the pretty-
// printing (hex, '$' prefix, etc.).
//===----------------------------------------------------------------------===//

// Immediate / address operand decoders.  All five (Imm8/Imm16,
// Addr8/Addr16/Addr24) just mask the raw bits to the operand width and
// create a literal MCOperand — the printer handles per-class formatting
// (hex prefix, '$' vs '0x', etc.).  Keeping width-specific shim
// functions because the generated tables reference each by name.
static inline DecodeStatus decodeImmWidth(MCInst &Inst, uint64_t Imm,
                                          uint64_t Mask) {
  Inst.addOperand(MCOperand::createImm(Imm & Mask));
  return MCDisassembler::Success;
}

static DecodeStatus decodeImm8(MCInst &Inst, uint64_t Imm, uint64_t Address,
                               const MCDisassembler *Decoder) {
  return decodeImmWidth(Inst, Imm, 0xFF);
}

static DecodeStatus decodeImm16(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                const MCDisassembler *Decoder) {
  return decodeImmWidth(Inst, Imm, 0xFFFF);
}

static DecodeStatus decodeAddr8(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                const MCDisassembler *Decoder) {
  return decodeImmWidth(Inst, Imm, 0xFF);
}

static DecodeStatus decodeAddr16(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                 const MCDisassembler *Decoder) {
  return decodeImmWidth(Inst, Imm, 0xFFFF);
}

static DecodeStatus decodeAddr24(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                 const MCDisassembler *Decoder) {
  return decodeImmWidth(Inst, Imm, 0xFFFFFF);
}


static DecodeStatus decodePCRel8(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                 const MCDisassembler *Decoder) {
  // Sign-extend the 8-bit displacement so the printer shows the correct
  // signed offset.  Address-symbolization happens later if requested.
  int8_t Disp = static_cast<int8_t>(Imm & 0xFF);
  Inst.addOperand(MCOperand::createImm(Disp));
  return MCDisassembler::Success;
}


static DecodeStatus decodePCRel16(MCInst &Inst, uint64_t Imm, uint64_t Address,
                                  const MCDisassembler *Decoder) {
  int16_t Disp = static_cast<int16_t>(Imm & 0xFFFF);
  Inst.addOperand(MCOperand::createImm(Disp));
  return MCDisassembler::Success;
}


// The generated decoder tables reference the decode callbacks above by name,
// so this include must come AFTER their declarations.
#include "W65816GenDisassemblerTables.inc"


//===----------------------------------------------------------------------===//
// Size-dispatch: try each 1/2/3/4-byte table in turn from shortest to longest,
// accepting the first successful decode.  The tablegen tables are keyed on
// the full little-endian byte pattern, so a single-byte implied-operand
// opcode like NOP (0xEA) matches in DecoderTableW658168 and shorter-circuits
// any accidental collision with a longer encoding that happens to begin with
// the same byte.  Every W65816 opcode byte uniquely determines the
// instruction (modulo the M/X-mode ambiguity resolved by the tracking below),
// so the first successful decode is always the correct one.
//
// Mode tracking: when the tracked M (or X) flag is 8-bit, the affected
// LDA/ADC/.../CPY opcodes carry a 1-byte immediate, whose _Imm8 forms live in
// DecoderTableW65816MHigh16 / XHigh16 (2 bytes: opcode + imm8).  Those tables
// hold *only* the mode-sensitive mnemonics, so a successful decode there can
// only be one of them; we try the matching table ahead of the default 16-bit
// forms.  After every decode, REP/SEP immediates update the tracked flags.
//===----------------------------------------------------------------------===//

// REP / SEP opcode bytes and the P-register flag bits they manipulate.
static constexpr uint8_t kOpcodeSEP = 0xE2; // SEP #imm: sets the named P bits
static constexpr uint8_t kOpcodeREP = 0xC2; // REP #imm: clears the named P bits
static constexpr uint8_t kFlagM = 0x20;     // accumulator/memory width bit
static constexpr uint8_t kFlagX = 0x10;     // index-register width bit

DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
                                                ArrayRef<uint8_t> Bytes,
                                                uint64_t Address,
                                                raw_ostream &CStream) const {
  // Decode `NBytes` little-endian bytes from the stream against `Table`, using
  // the given subtarget for predicate checks (ModeSTI for the predicate-gated
  // High tables, the running STI otherwise).
  auto tryTable = [&](const uint8_t *Table, unsigned NBytes,
                      const MCSubtargetInfo &DecodeSTI,
                      MCInst &Out) -> DecodeStatus {
    if (Bytes.size() < NBytes) {
      return MCDisassembler::Fail;
    }
    uint64_t Insn = 0;
    for (unsigned I = 0; I < NBytes; ++I) {
      Insn |= static_cast<uint64_t>(Bytes[I]) << (8 * I);
    }
    return decodeInstruction(Table, Out, Insn, Address, this, DecodeSTI);
  };

  MCInst Candidate;
  DecodeStatus Result = MCDisassembler::Fail;
  uint64_t DecodedSize = 0;

  // Mode-sensitive 8-bit-immediate forms take priority when the tracked flag
  // says 8-bit; the High16 tables only contain those mnemonics, so a miss
  // simply falls through to the default size-dispatch below.
  if (AccIs8) {
    Result = tryTable(DecoderTableW65816MHigh16, 2, ModeSTI, Candidate);
  }
  if (Result == MCDisassembler::Fail && IdxIs8) {
    Result = tryTable(DecoderTableW65816XHigh16, 2, ModeSTI, Candidate);
  }
  if (Result != MCDisassembler::Fail) {
    DecodedSize = 2;
  } else {
    struct TableEntry {
      const uint8_t *Table;
      unsigned Bytes;
    };
    static const TableEntry Tables[] = {
      { DecoderTableW658168,  1 },
      { DecoderTableW6581616, 2 },
      { DecoderTableW6581624, 3 },
      { DecoderTableW6581632, 4 },
    };
    for (const TableEntry &T : Tables) {
      Result = tryTable(T.Table, T.Bytes, STI, Candidate);
      if (Result != MCDisassembler::Fail) {
        DecodedSize = T.Bytes;
        break;
      }
    }
  }

  if (Result == MCDisassembler::Fail) {
    Size = 1;
    return MCDisassembler::Fail;
  }

  // Follow REP/SEP so subsequent mode-sensitive immediates decode at the right
  // width.  SEP #imm sets (=> 8-bit) the P bits present in imm; REP #imm
  // clears (=> 16-bit) them.  Both are fixed 2-byte encodings (opcode + imm8).
  if (DecodedSize >= 2) {
    uint8_t Imm = Bytes[1];
    if (Bytes[0] == kOpcodeSEP) {
      if (Imm & kFlagM) {
        AccIs8 = true;
      }
      if (Imm & kFlagX) {
        IdxIs8 = true;
      }
    } else if (Bytes[0] == kOpcodeREP) {
      if (Imm & kFlagM) {
        AccIs8 = false;
      }
      if (Imm & kFlagX) {
        IdxIs8 = false;
      }
    }
  }

  MI = Candidate;
  Size = DecodedSize;
  return Result;
}


//===----------------------------------------------------------------------===//
// Registration.
//===----------------------------------------------------------------------===//

static MCDisassembler *createW65816Disassembler(const Target &T,
                                                const MCSubtargetInfo &STI,
                                                MCContext &Ctx) {
  return new W65816Disassembler(STI, Ctx);
}


extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816Disassembler() {
  TargetRegistry::RegisterMCDisassembler(getTheW65816Target(),
                                         createW65816Disassembler);
}
