//===-- W65816ELFObjectWriter.cpp - W65816 ELF Writer ---------------------===//
//
// 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 ELF object writer.  Emits objects with e_machine = EM_W65816
// (0xFF16, vendor-private slot) and a small set of R_W65816_* relocation
// types decoded by link816 and the AsmPrinter test path.
//
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/W65816FixupKinds.h"
#include "MCTargetDesc/W65816MCTargetDesc.h"

#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/ErrorHandling.h"

using namespace llvm;

// R_W65816_* relocation numbers.  These are protocol constants shared
// with link816 / omfEmit / llvm-objdump; do not renumber.  If new types
// are added, mirror them in src/link816/link816.cpp's relocWidth() and
// the cRELOC pipeline.
namespace R_W65816 {
enum : unsigned {
  R_IMM8     = 1,
  R_IMM16    = 2,
  R_IMM24    = 3,
  R_PCREL8   = 4,
  R_PCREL16  = 5,
  R_BANK16   = 6,
  R_DATA32   = 7,
  R_PCREL32  = 8,
};
}  // namespace R_W65816

namespace {

class W65816ELFObjectWriter : public MCELFObjectTargetWriter {
public:
  // EM_W65816 = 0xFF16 — vendor-private slot in the 0xFF00-0xFFFF range
  // reserved by the ELF spec for non-IANA experimental targets.  See
  // docs/USAGE.md "ELF e_machine value" section and the EM_W65816 comment
  // in llvm/include/llvm/BinaryFormat/ELF.h.  Using a non-zero EM_ value
  // is what lets llvm-dwarfdump and other generic ELF consumers stop
  // warning on our output.
  explicit W65816ELFObjectWriter(uint8_t OSABI)
      : MCELFObjectTargetWriter(/*Is64Bit=*/false, OSABI, ELF::EM_W65816,
                                /*HasRelocationAddend=*/true) {}

  ~W65816ELFObjectWriter() override = default;

protected:
  unsigned getRelocType(const MCFixup &Fixup, const MCValue &,
                        bool IsPCRel) const override {
    // R_W65816_* relocation numbers.  The (EM_W65816, R_W65816_*) pair is
    // unique, so the small integer constants below can stay stable across
    // releases.  link816 / omfEmit / llvm-objdump all decode them.
    //
    // Generic FK_Data_* fixups are also accepted — the asm parser creates
    // them for things like `.word foo` and the JMP/JML address operand
    // when no target-specific fixup kind is hinted.  Map them to the
    // matching size-based reloc; PC-relative variants pick the *_pcrel
    // forms.  Without this, every hand-written .s reference to an extern
    // symbol came through `getRelocType` as a default-value (UB) reloc
    // type — observed as type 249 — and broke link816.py.
    auto Kind = Fixup.getKind();
    switch (Kind) {
    case W65816::fixup_8:         return R_W65816::R_IMM8;
    case W65816::fixup_16:        return R_W65816::R_IMM16;
    case W65816::fixup_24:        return R_W65816::R_IMM24;
    case W65816::fixup_8_pcrel:   return R_W65816::R_PCREL8;
    case W65816::fixup_16_pcrel:  return R_W65816::R_PCREL16;
    case W65816::fixup_bank16:    return R_W65816::R_BANK16;
    case W65816::fixup_32:        return R_W65816::R_DATA32;
    case W65816::fixup_32_pcrel:  return R_W65816::R_PCREL32;
    case FK_Data_1:               return IsPCRel ? R_W65816::R_PCREL8  : R_W65816::R_IMM8;
    case FK_Data_2:               return IsPCRel ? R_W65816::R_PCREL16 : R_W65816::R_IMM16;
    // FK_Data_4 is emitted by DWARF (.debug_info / .debug_line /
    // .debug_frame section-relative addresses), .eh_frame,
    // .debug_loclists, and user `.long` directives.  Dispatch by
    // IsPCRel: in-section diffs that the assembler can't resolve
    // locally come through as PC-relative (per
    // ELFObjectWriter::recordRelocation:1329-1349), everything else
    // is absolute.  Previously this returned IMM24 (3 bytes),
    // silently truncating the 4-byte slot — corrupting any DWARF
    // address with a non-zero high byte AND off-by-one'ing the
    // .debug_line decoder because the 4th byte of the slot landed
    // on whatever followed it (most often the size byte of the
    // next line-program header → unit_length = 0).
    case FK_Data_4:               return IsPCRel ? R_W65816::R_PCREL32 : R_W65816::R_DATA32;
    default:
      llvm_unreachable("W65816: unknown fixup kind");
    }
  }
};

} // end anonymous namespace

std::unique_ptr<MCObjectTargetWriter>
llvm::createW65816ELFObjectWriter(uint8_t OSABI) {
  return std::make_unique<W65816ELFObjectWriter>(OSABI);
}
