//===-- W65816InstPrinter.cpp - Convert W65816 MCInst to assembly syntax --===//
//
// 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 class prints a W65816 MCInst to a .s file.  Most instructions print
// via the table-generated routines (printInstruction); this file supplies the
// custom operand printers (hex immediates with the '$' prefix, addressing-mode
// operands, PC-relative branch targets).
//
//===----------------------------------------------------------------------===//

#include "W65816InstPrinter.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "asm-printer"

// Include the auto-generated portion of the assembly writer.
#define PRINT_ALIAS_INSTR
#include "W65816GenAsmWriter.inc"

void W65816InstPrinter::printRegName(raw_ostream &O, MCRegister Reg) {
  O << getRegisterName(Reg);
}

void W65816InstPrinter::printInst(const MCInst *MI, uint64_t Address,
                                  StringRef Annot, const MCSubtargetInfo &STI,
                                  raw_ostream &O) {
  if (!printAliasInstr(MI, Address, O))
    printInstruction(MI, Address, O);
  printAnnotation(O, Annot);
}

void W65816InstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
                                     raw_ostream &O) {
  const MCOperand &Op = MI->getOperand(OpNo);
  if (Op.isReg()) {
    O << getRegisterName(Op.getReg());
    return;
  }
  if (Op.isImm()) {
    // The '#' prefix lives in the AsmString (e.g. "lda\t#$imm") so we
    // only emit the numeric value here.  65816 immediates are at most
    // 16 bits; we print the low 16 to avoid sign-extended int64 noise
    // like `0xffffffffffffffff` for i16 -1.
    O << "0x";
    O.write_hex(Op.getImm() & 0xffff);
    return;
  }
  assert(Op.isExpr() && "unknown operand kind in printOperand");
  MAI.printExpr(O, *Op.getExpr());
}

// Address operand helpers.  We use LLVM's standard `0x` hex prefix rather
// than the 65816-native `$` prefix so that our own AsmParser (which uses
// the stock LLVM expression lexer) can round-trip our output.  Teaching
// the lexer about `$` is a future cleanup; until then the printed form
// is compatible-with-itself rather than idiomatic WDC syntax.
static void printAddrImm(const MCOperand &Op, unsigned width, raw_ostream &O,
                         const MCAsmInfo &MAI) {
  if (Op.isImm()) {
    uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1);
    O << "0x";
    O.write_hex(Op.getImm() & mask);
    return;
  }
  assert(Op.isExpr() && "unknown address operand kind");
  MAI.printExpr(O, *Op.getExpr());
}

void W65816InstPrinter::printAddrDP(const MCInst *MI, unsigned OpNo,
                                    raw_ostream &O) {
  printAddrImm(MI->getOperand(OpNo), 8, O, MAI);
}

void W65816InstPrinter::printAddrAbs(const MCInst *MI, unsigned OpNo,
                                     raw_ostream &O) {
  printAddrImm(MI->getOperand(OpNo), 16, O, MAI);
}

void W65816InstPrinter::printAddrLong(const MCInst *MI, unsigned OpNo,
                                      raw_ostream &O) {
  printAddrImm(MI->getOperand(OpNo), 24, O, MAI);
}

void W65816InstPrinter::printPCRel8(const MCInst *MI, uint64_t Address,
                                    unsigned OpNo, raw_ostream &O) {
  const MCOperand &Op = MI->getOperand(OpNo);
  if (Op.isImm()) {
    O << Op.getImm();
    return;
  }
  assert(Op.isExpr() && "unknown pc-rel operand kind");
  MAI.printExpr(O, *Op.getExpr());
}

void W65816InstPrinter::printPCRel16(const MCInst *MI, uint64_t Address,
                                     unsigned OpNo, raw_ostream &O) {
  printPCRel8(MI, Address, OpNo, O);
}

void W65816InstPrinter::printFrameMem(const MCInst *MI, unsigned OpNo,
                                      raw_ostream &O) {
  // Frame-index loads/stores are expanded into LDA/STA d,S in
  // W65816RegisterInfo::eliminateFrameIndex before they ever reach
  // the printer.  This callback exists only to satisfy tablegen.
  llvm_unreachable("W65816: frame-mem pseudo printed without expansion");
}
