//===-- 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.  This
// scaffold disassembler reads exclusively from the default table, so those
// mnemonics always decode as 3-byte (16-bit) immediates.  A future mode-
// tracking pass will consult the MHigh / XHigh tables when MReg / XReg are
// set by preceding REP/SEP instructions.
//
//===----------------------------------------------------------------------===//

#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 {
public:
  W65816Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
      : MCDisassembler(STI, Ctx) {}

  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 documented above), so the
// first successful decode is always the correct one.
//===----------------------------------------------------------------------===//

DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
                                                ArrayRef<uint8_t> Bytes,
                                                uint64_t Address,
                                                raw_ostream &CStream) const {
  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) {
    if (Bytes.size() < T.Bytes) {
      continue;
    }

    uint64_t Insn = 0;
    for (unsigned I = 0; I < T.Bytes; ++I) {
      Insn |= static_cast<uint64_t>(Bytes[I]) << (8 * I);
    }

    MCInst Candidate;
    DecodeStatus Result = decodeInstruction(T.Table, Candidate, Insn, Address,
                                            this, STI);
    if (Result != MCDisassembler::Fail) {
      MI = Candidate;
      Size = T.Bytes;
      return Result;
    }
  }

  Size = 1;
  return MCDisassembler::Fail;
}


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