//===-- W65816AsmBackend.cpp - W65816 Assembler Backend -------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// W65816 assembler backend.  Implements applyFixup for the
// R_W65816_* relocation family, BRA -> BRL relaxation when the 8-bit
// signed displacement won't fit, and writeNopData using 65816 NOP
// ($EA) bytes.
//
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/W65816FixupKinds.h"
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"

// W65816::BRA / W65816::BRL opcodes are exported by W65816MCTargetDesc.h
// (which already includes the generated header).

// W65816 NOP machine encoding (single byte).
static constexpr unsigned char kOpcodeNOP = 0xEA;

// Signed 8-bit branch displacement range for Bxx / BRA fixups.
static constexpr int kBranch8Min = -128;
static constexpr int kBranch8Max =  127;

using namespace llvm;

namespace {

class W65816AsmBackend : public MCAsmBackend {
  uint8_t OSABI;

public:
  W65816AsmBackend(const MCSubtargetInfo &STI, uint8_t OSABI)
      : MCAsmBackend(llvm::endianness::little), OSABI(OSABI) {}
  ~W65816AsmBackend() override = default;

  void applyFixup(const MCFragment &F, const MCFixup &Fixup,
                  const MCValue &Target, uint8_t *Data, uint64_t Value,
                  bool IsResolved) override {
    maybeAddReloc(F, Fixup, Target, Value, IsResolved);
    if (!IsResolved) {
      // Leave the bytes zero; the linker will fill them via the recorded
      // relocation.
      return;
    }

    // Per MCAsmBackend's `applyFixup` contract (MCAsmBackend.h),
    // Data already points to the first byte of the fixup site —
    // MCAssembler::layout passes us
    // `Contents.data() + Fixup.getOffset()` (see MCAssembler.cpp
    // ~line 748).  So we index from Data[0], NOT Data[Offset + i].
    //
    // Earlier versions of this code mistakenly indexed
    // `Data[Offset + i]`, which silently OOB-wrote past the fixup
    // site by `Fixup.getOffset()` bytes.  It went unnoticed because
    // most W65816 fixups hit the early-return path above
    // (IsResolved=false → deferred to link816), so the patch loop
    // rarely ran.  When DWARF FK_Data_4 fixups at non-zero
    // section offsets (unit_length=0, header_length=8, etc.) hit
    // this code with IsResolved=true (in-section diff resolved at
    // layout time), the OOB writes scribbled MC allocator state
    // and crashed the layout pass.
    unsigned Width;
    switch (Fixup.getKind()) {
    case W65816::fixup_8:
    case W65816::fixup_8_pcrel:
    case FK_Data_1:
      Width = 1;
      break;
    case W65816::fixup_16:
    case W65816::fixup_16_pcrel:
    case FK_Data_2:
      Width = 2;
      break;
    case W65816::fixup_24:
      Width = 3;
      break;
    case W65816::fixup_32:
    case W65816::fixup_32_pcrel:
    case FK_Data_4:
      Width = 4;
      break;
    case FK_Data_8:
      Width = 8;
      break;
    case W65816::fixup_bank16:
      // Patch 2 bytes with (bank, 0) where bank = (Value >> 16) & 0xFF.
      // The OMF cRELOC at load time supersedes this static patch with
      // the actual placed bank; this branch is the in-static-link
      // value when target and patch are in the same segment.
      Data[0] = static_cast<uint8_t>((Value >> 16) & 0xff);
      Data[1] = 0;
      return;
    default:
      // Unknown fixup kind — leave bytes alone.  Any reloc still
      // needed has already been recorded via maybeAddReloc above.
      return;
    }

    // PCRel8 (Bxx / BRA) takes a signed 8-bit offset.  If the resolved
    // displacement won't fit, the encoded byte is meaningless — the
    // branch would land somewhere unintended.  Diagnose explicitly
    // instead of silently truncating.
    if (Fixup.getKind() == W65816::fixup_8_pcrel) {
      int64_t Signed = static_cast<int64_t>(Value);
      if (Signed < kBranch8Min || Signed > kBranch8Max) {
        getContext().reportError(
            Fixup.getLoc(),
            "branch target out of range for 8-bit PC-relative branch "
            "(offset " + Twine(Signed) + " bytes); use a long branch (BRL) "
            "or restructure the code");
        return;  // don't patch — leave zero, error already issued
      }
    }

    // Little-endian patch.
    for (unsigned i = 0; i < Width; ++i) {
      Data[i] = static_cast<uint8_t>((Value >> (8 * i)) & 0xff);
    }
  }

  std::unique_ptr<MCObjectTargetWriter>
  createObjectTargetWriter() const override {
    return createW65816ELFObjectWriter(OSABI);
  }

  MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override {
    // clang-format off
    const static MCFixupKindInfo Infos[W65816::NumTargetFixupKinds] = {
      // name             offset bits flags
      {"fixup_8",             0,  8, 0},
      {"fixup_16",            0, 16, 0},
      {"fixup_24",            0, 24, 0},
      {"fixup_8_pcrel",       0,  8, 0},
      {"fixup_16_pcrel",      0, 16, 0},
      {"fixup_bank16",        0, 16, 0},
      {"fixup_32",            0, 32, 0},
      {"fixup_32_pcrel",      0, 32, 0},
    };
    // clang-format on
    static_assert(std::size(Infos) == W65816::NumTargetFixupKinds,
                  "Not all fixup kinds added to Infos array");

    if (Kind < FirstTargetFixupKind)
      return MCAsmBackend::getFixupKindInfo(Kind);

    return Infos[Kind - FirstTargetFixupKind];
  }

  bool writeNopData(raw_ostream &OS, uint64_t Count,
                    const MCSubtargetInfo *STI) const override {
    // The 65816 NOP is a single 0xEA byte.
    for (uint64_t I = 0; I < Count; ++I)
      OS << static_cast<char>(kOpcodeNOP);
    return true;
  }

  // ----------------------------------------------------------------
  // Relaxation: BRA (signed-8 displacement) -> BRL (signed-16).  When
  // the assembler determines that a forward/backward BRA's target lies
  // beyond +/-128 bytes, it asks us first via mayNeedRelaxation /
  // fixupNeedsRelaxation, then via relaxInstruction to materialise the
  // longer form.  Both BRA (0x80 dd) and BRL (0x82 dd dd) have the
  // same operand semantics (PC-relative) so the rewrite is just an
  // opcode swap with the fixup kind upgraded from fixup_8_pcrel to
  // fixup_16_pcrel.
  //
  // We do NOT relax conditional Bxx instructions yet: the 65816 has
  // no long conditional branch, so the standard trick is to invert
  // and span: `BNE l: ... -> BEQ skip; BRL l; skip:`.  That requires
  // emitting two instructions in place of one and shifting all
  // subsequent fixup offsets, which the layered MCAsmBackend API
  // doesn't support cleanly.  A higher-level codegen pass (or a
  // pre-emit MIR pass) is the right place for that.  Until then,
  // out-of-range conditional branches still error out via the
  // applyFixup diagnostic above.
  bool mayNeedRelaxation(unsigned Opcode, ArrayRef<MCOperand> Operands,
                         const MCSubtargetInfo &STI) const override {
    return Opcode == W65816::BRA;
  }

  bool fixupNeedsRelaxationAdvanced(const MCFragment &F, const MCFixup &Fixup,
                                    const MCValue &Target, uint64_t Value,
                                    bool Resolved) const override {
    if (Fixup.getKind() != W65816::fixup_8_pcrel)
      return false;
    int64_t Signed = static_cast<int64_t>(Value);
    return Signed < kBranch8Min || Signed > kBranch8Max;
  }

  void relaxInstruction(MCInst &Inst,
                        const MCSubtargetInfo &STI) const override {
    if (Inst.getOpcode() == W65816::BRA) {
      Inst.setOpcode(W65816::BRL);
      // Operand stays the same (the symbol/expression).  The encoder
      // will pick the BRL encoding (3 bytes) and emit fixup_16_pcrel.
    }
  }
};

} // end anonymous namespace

MCAsmBackend *llvm::createW65816MCAsmBackend(const Target &T,
                                             const MCSubtargetInfo &STI,
                                             const MCRegisterInfo &MRI,
                                             const MCTargetOptions &Options) {
  return new W65816AsmBackend(STI, ELF::ELFOSABI_STANDALONE);
}
