//===--- unittests/DebugInfo/DWARF/DwarfGenerator.cpp -----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "DwarfGenerator.h"
#include "../lib/CodeGen/AsmPrinter/DwarfStringPool.h"
#include "llvm/ADT/Triple.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/DIE.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h"
#include "llvm/DebugInfo/DWARF/DWARFFormValue.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDwarf.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptionsCommandFlags.h"
#include "llvm/Pass.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetLoweringObjectFile.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"

using namespace llvm;
using namespace dwarf;

mc::RegisterMCTargetOptionsFlags MOF;

namespace {} // end anonymous namespace

//===----------------------------------------------------------------------===//
/// dwarfgen::DIE implementation.
//===----------------------------------------------------------------------===//
unsigned dwarfgen::DIE::computeSizeAndOffsets(unsigned Offset) {
  auto &DG = CU->getGenerator();
  return Die->computeOffsetsAndAbbrevs(DG.getAsmPrinter(), DG.getAbbrevSet(),
                                       Offset);
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, uint64_t U) {
  auto &DG = CU->getGenerator();
  Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                DIEInteger(U));
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, const MCExpr &Expr) {
  auto &DG = CU->getGenerator();
  Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                DIEExpr(&Expr));
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form,
                                 StringRef String) {
  auto &DG = CU->getGenerator();
  switch (Form) {
  case DW_FORM_string:
    Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                  new (DG.getAllocator())
                      DIEInlineString(String, DG.getAllocator()));
    break;

  case DW_FORM_strp:
    Die->addValue(
        DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
        DIEString(DG.getStringPool().getEntry(*DG.getAsmPrinter(), String)));
    break;

  case DW_FORM_GNU_str_index:
  case DW_FORM_strx:
  case DW_FORM_strx1:
  case DW_FORM_strx2:
  case DW_FORM_strx3:
  case DW_FORM_strx4:
    Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                  DIEString(DG.getStringPool().getIndexedEntry(
                      *DG.getAsmPrinter(), String)));
    break;

  default:
    llvm_unreachable("Unhandled form!");
  }
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form,
                                 dwarfgen::DIE &RefDie) {
  auto &DG = CU->getGenerator();
  Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                DIEEntry(*RefDie.Die));
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, const void *P,
                                 size_t S) {
  auto &DG = CU->getGenerator();
  DIEBlock *Block = new (DG.getAllocator()) DIEBlock;
  for (size_t I = 0; I < S; ++I)
    Block->addValue(
        DG.getAllocator(), (dwarf::Attribute)0, dwarf::DW_FORM_data1,
        DIEInteger(
            (const_cast<uint8_t *>(static_cast<const uint8_t *>(P)))[I]));

  Block->ComputeSize(DG.getAsmPrinter());
  Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                Block);
}

void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form) {
  auto &DG = CU->getGenerator();
  assert(Form == DW_FORM_flag_present);
  Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form,
                DIEInteger(1));
}

void dwarfgen::DIE::addStrOffsetsBaseAttribute() {
  auto &DG = CU->getGenerator();
  auto &MC = *DG.getMCContext();
  AsmPrinter *Asm = DG.getAsmPrinter();

  const MCSymbol *SectionStart =
      Asm->getObjFileLowering().getDwarfStrOffSection()->getBeginSymbol();

  const MCExpr *Expr =
      MCSymbolRefExpr::create(DG.getStringOffsetsStartSym(), MC);

  if (!Asm->MAI->doesDwarfUseRelocationsAcrossSections())
    Expr = MCBinaryExpr::createSub(
        Expr, MCSymbolRefExpr::create(SectionStart, MC), MC);

  addAttribute(dwarf::DW_AT_str_offsets_base, DW_FORM_sec_offset, *Expr);
}

dwarfgen::DIE dwarfgen::DIE::addChild(dwarf::Tag Tag) {
  auto &DG = CU->getGenerator();
  return dwarfgen::DIE(CU,
                       &Die->addChild(llvm::DIE::get(DG.getAllocator(), Tag)));
}

dwarfgen::DIE dwarfgen::CompileUnit::getUnitDIE() {
  return dwarfgen::DIE(this, &DU.getUnitDie());
}

//===----------------------------------------------------------------------===//
/// dwarfgen::LineTable implementation.
//===----------------------------------------------------------------------===//
DWARFDebugLine::Prologue dwarfgen::LineTable::createBasicPrologue() const {
  DWARFDebugLine::Prologue P;
  switch (Version) {
  case 2:
  case 3:
    P.TotalLength = 41;
    P.PrologueLength = 35;
    break;
  case 4:
    P.TotalLength = 42;
    P.PrologueLength = 36;
    break;
  case 5:
    P.TotalLength = 50;
    P.PrologueLength = 42;
    P.FormParams.AddrSize = AddrSize;
    break;
  default:
    llvm_unreachable("unsupported version");
  }
  if (Format == DWARF64) {
    P.TotalLength += 4;
    P.FormParams.Format = DWARF64;
  }
  P.TotalLength += getContentsSize();
  P.FormParams.Version = Version;
  P.MinInstLength = 1;
  P.MaxOpsPerInst = 1;
  P.DefaultIsStmt = 1;
  P.LineBase = -5;
  P.LineRange = 14;
  P.OpcodeBase = 13;
  P.StandardOpcodeLengths = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1};
  P.IncludeDirectories.push_back(
      DWARFFormValue::createFromPValue(DW_FORM_string, "a dir"));
  P.FileNames.push_back(DWARFDebugLine::FileNameEntry());
  P.FileNames.back().Name =
      DWARFFormValue::createFromPValue(DW_FORM_string, "a file");
  return P;
}

void dwarfgen::LineTable::setPrologue(DWARFDebugLine::Prologue NewPrologue) {
  Prologue = NewPrologue;
  CustomPrologue.clear();
}

void dwarfgen::LineTable::setCustomPrologue(
    ArrayRef<ValueAndLength> NewPrologue) {
  Prologue.reset();
  CustomPrologue = NewPrologue;
}

void dwarfgen::LineTable::addByte(uint8_t Value) {
  Contents.push_back({Value, Byte});
}

void dwarfgen::LineTable::addStandardOpcode(uint8_t Opcode,
                                            ArrayRef<ValueAndLength> Operands) {
  Contents.push_back({Opcode, Byte});
  Contents.insert(Contents.end(), Operands.begin(), Operands.end());
}

void dwarfgen::LineTable::addExtendedOpcode(uint64_t Length, uint8_t Opcode,
                                            ArrayRef<ValueAndLength> Operands) {
  Contents.push_back({0, Byte});
  Contents.push_back({Length, ULEB});
  Contents.push_back({Opcode, Byte});
  Contents.insert(Contents.end(), Operands.begin(), Operands.end());
}

void dwarfgen::LineTable::generate(MCContext &MC, AsmPrinter &Asm) const {
  MC.setDwarfVersion(Version);

  MCSymbol *EndSymbol = nullptr;
  if (!CustomPrologue.empty()) {
    writeData(CustomPrologue, Asm);
  } else if (!Prologue) {
    EndSymbol = writeDefaultPrologue(Asm);
  } else {
    writePrologue(Asm);
  }

  writeData(Contents, Asm);
  if (EndSymbol != nullptr)
    Asm.OutStreamer->emitLabel(EndSymbol);
}

void dwarfgen::LineTable::writeData(ArrayRef<ValueAndLength> Data,
                                    AsmPrinter &Asm) const {
  for (auto Entry : Data) {
    switch (Entry.Length) {
    case Byte:
    case Half:
    case Long:
    case Quad:
      Asm.OutStreamer->emitIntValue(Entry.Value, Entry.Length);
      continue;
    case ULEB:
      Asm.emitULEB128(Entry.Value);
      continue;
    case SLEB:
      Asm.emitSLEB128(Entry.Value);
      continue;
    }
    llvm_unreachable("unsupported ValueAndLength Length value");
  }
}

size_t dwarfgen::LineTable::getContentsSize() const {
  size_t Size = 0;
  for (auto Entry : Contents) {
    switch (Entry.Length) {
    case ULEB:
      Size += getULEB128Size(Entry.Value);
      break;
    case SLEB:
      Size += getSLEB128Size(Entry.Value);
      break;
    default:
      Size += Entry.Length;
      break;
    }
  }
  return Size;
}

MCSymbol *dwarfgen::LineTable::writeDefaultPrologue(AsmPrinter &Asm) const {
  MCSymbol *UnitStart = Asm.createTempSymbol("line_unit_start");
  MCSymbol *UnitEnd = Asm.createTempSymbol("line_unit_end");
  if (Format == DwarfFormat::DWARF64) {
    Asm.emitInt32((int)dwarf::DW_LENGTH_DWARF64);
    Asm.emitLabelDifference(UnitEnd, UnitStart, 8);
  } else {
    Asm.emitLabelDifference(UnitEnd, UnitStart, 4);
  }
  Asm.OutStreamer->emitLabel(UnitStart);
  Asm.emitInt16(Version);
  if (Version == 5) {
    Asm.emitInt8(AddrSize);
    Asm.emitInt8(SegSize);
  }

  MCSymbol *PrologueStart = Asm.createTempSymbol("line_prologue_start");
  MCSymbol *PrologueEnd = Asm.createTempSymbol("line_prologue_end");
  Asm.emitLabelDifference(PrologueEnd, PrologueStart,
                          Format == DwarfFormat::DWARF64 ? 8 : 4);
  Asm.OutStreamer->emitLabel(PrologueStart);

  DWARFDebugLine::Prologue DefaultPrologue = createBasicPrologue();
  writeProloguePayload(DefaultPrologue, Asm);
  Asm.OutStreamer->emitLabel(PrologueEnd);
  return UnitEnd;
}

void dwarfgen::LineTable::writePrologue(AsmPrinter &Asm) const {
  if (Format == DwarfFormat::DWARF64) {
    Asm.emitInt32((int)dwarf::DW_LENGTH_DWARF64);
    Asm.emitInt64(Prologue->TotalLength);
  } else {
    Asm.emitInt32(Prologue->TotalLength);
  }
  Asm.emitInt16(Prologue->getVersion());
  if (Version == 5) {
    Asm.emitInt8(Prologue->getAddressSize());
    Asm.emitInt8(Prologue->SegSelectorSize);
  }
  if (Format == DwarfFormat::DWARF64)
    Asm.emitInt64(Prologue->PrologueLength);
  else
    Asm.emitInt32(Prologue->PrologueLength);

  writeProloguePayload(*Prologue, Asm);
}

static void writeCString(StringRef Str, AsmPrinter &Asm) {
  Asm.OutStreamer->emitBytes(Str);
  Asm.emitInt8(0);
}

static void writeV2IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue,
                                       AsmPrinter &Asm) {
  for (auto Include : Prologue.IncludeDirectories) {
    assert(Include.getAsCString() && "expected a string form for include dir");
    writeCString(*Include.getAsCString(), Asm);
  }
  Asm.emitInt8(0);

  for (auto File : Prologue.FileNames) {
    assert(File.Name.getAsCString() && "expected a string form for file name");
    writeCString(*File.Name.getAsCString(), Asm);
    Asm.emitULEB128(File.DirIdx);
    Asm.emitULEB128(File.ModTime);
    Asm.emitULEB128(File.Length);
  }
  Asm.emitInt8(0);
}

static void writeV5IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue,
                                       AsmPrinter &Asm) {
  Asm.emitInt8(1); // directory_entry_format_count.
  // TODO: Add support for other content descriptions - we currently only
  // support a single DW_LNCT_path/DW_FORM_string.
  Asm.emitULEB128(DW_LNCT_path);
  Asm.emitULEB128(DW_FORM_string);
  Asm.emitULEB128(Prologue.IncludeDirectories.size());
  for (auto Include : Prologue.IncludeDirectories) {
    assert(Include.getAsCString() && "expected a string form for include dir");
    writeCString(*Include.getAsCString(), Asm);
  }

  Asm.emitInt8(2); // file_name_entry_format_count.
  Asm.emitULEB128(DW_LNCT_path);
  Asm.emitULEB128(DW_FORM_string);
  Asm.emitULEB128(DW_LNCT_directory_index);
  Asm.emitULEB128(DW_FORM_data1);
  Asm.emitULEB128(Prologue.FileNames.size());
  for (auto File : Prologue.FileNames) {
    assert(File.Name.getAsCString() && "expected a string form for file name");
    writeCString(*File.Name.getAsCString(), Asm);
    Asm.emitInt8(File.DirIdx);
  }
}

void dwarfgen::LineTable::writeProloguePayload(
    const DWARFDebugLine::Prologue &Prologue, AsmPrinter &Asm) const {
  Asm.emitInt8(Prologue.MinInstLength);
  if (Version >= 4)
    Asm.emitInt8(Prologue.MaxOpsPerInst);
  Asm.emitInt8(Prologue.DefaultIsStmt);
  Asm.emitInt8(Prologue.LineBase);
  Asm.emitInt8(Prologue.LineRange);
  Asm.emitInt8(Prologue.OpcodeBase);
  for (auto Length : Prologue.StandardOpcodeLengths) {
    Asm.emitInt8(Length);
  }

  if (Version < 5)
    writeV2IncludeAndFileTable(Prologue, Asm);
  else
    writeV5IncludeAndFileTable(Prologue, Asm);
}

//===----------------------------------------------------------------------===//
/// dwarfgen::Generator implementation.
//===----------------------------------------------------------------------===//

dwarfgen::Generator::Generator()
    : MAB(nullptr), MCE(nullptr), MS(nullptr), TLOF(nullptr),
      StringPool(nullptr), Abbreviations(Allocator),
      StringOffsetsStartSym(nullptr), Version(0) {}
dwarfgen::Generator::~Generator() = default;

llvm::Expected<std::unique_ptr<dwarfgen::Generator>>
dwarfgen::Generator::create(Triple TheTriple, uint16_t DwarfVersion) {
  std::unique_ptr<dwarfgen::Generator> GenUP(new dwarfgen::Generator());
  llvm::Error error = GenUP->init(TheTriple, DwarfVersion);
  if (error)
    return Expected<std::unique_ptr<dwarfgen::Generator>>(std::move(error));
  return Expected<std::unique_ptr<dwarfgen::Generator>>(std::move(GenUP));
}

llvm::Error dwarfgen::Generator::init(Triple TheTriple, uint16_t V) {
  Version = V;
  std::string ErrorStr;
  std::string TripleName;

  // Get the target.
  const Target *TheTarget =
      TargetRegistry::lookupTarget(TripleName, TheTriple, ErrorStr);
  if (!TheTarget)
    return make_error<StringError>(ErrorStr, inconvertibleErrorCode());

  TripleName = TheTriple.getTriple();

  // Create all the MC Objects.
  MRI.reset(TheTarget->createMCRegInfo(TripleName));
  if (!MRI)
    return make_error<StringError>(Twine("no register info for target ") +
                                       TripleName,
                                   inconvertibleErrorCode());

  MCTargetOptions MCOptions = mc::InitMCTargetOptionsFromFlags();
  MAI.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions));
  if (!MAI)
    return make_error<StringError>("no asm info for target " + TripleName,
                                   inconvertibleErrorCode());

  MSTI.reset(TheTarget->createMCSubtargetInfo(TripleName, "", ""));
  if (!MSTI)
    return make_error<StringError>("no subtarget info for target " + TripleName,
                                   inconvertibleErrorCode());

  MAB = TheTarget->createMCAsmBackend(*MSTI, *MRI, MCOptions);
  if (!MAB)
    return make_error<StringError>("no asm backend for target " + TripleName,
                                   inconvertibleErrorCode());

  MII.reset(TheTarget->createMCInstrInfo());
  if (!MII)
    return make_error<StringError>("no instr info info for target " +
                                       TripleName,
                                   inconvertibleErrorCode());

  TM.reset(TheTarget->createTargetMachine(TripleName, "", "", TargetOptions(),
                                          None));
  if (!TM)
    return make_error<StringError>("no target machine for target " + TripleName,
                                   inconvertibleErrorCode());

  MC.reset(new MCContext(TheTriple, MAI.get(), MRI.get(), MSTI.get()));
  TLOF = TM->getObjFileLowering();
  TLOF->Initialize(*MC, *TM);
  MC->setObjectFileInfo(TLOF);

  MCE = TheTarget->createMCCodeEmitter(*MII, *MRI, *MC);
  if (!MCE)
    return make_error<StringError>("no code emitter for target " + TripleName,
                                   inconvertibleErrorCode());

  Stream = std::make_unique<raw_svector_ostream>(FileBytes);

  MS = TheTarget->createMCObjectStreamer(
      TheTriple, *MC, std::unique_ptr<MCAsmBackend>(MAB),
      MAB->createObjectWriter(*Stream), std::unique_ptr<MCCodeEmitter>(MCE),
      *MSTI, MCOptions.MCRelaxAll, MCOptions.MCIncrementalLinkerCompatible,
      /*DWARFMustBeAtTheEnd*/ false);
  if (!MS)
    return make_error<StringError>("no object streamer for target " +
                                       TripleName,
                                   inconvertibleErrorCode());


  // Finally create the AsmPrinter we'll use to emit the DIEs.
  Asm.reset(TheTarget->createAsmPrinter(*TM, std::unique_ptr<MCStreamer>(MS)));
  if (!Asm)
    return make_error<StringError>("no asm printer for target " + TripleName,
                                   inconvertibleErrorCode());

  // Set the DWARF version correctly on all classes that we use.
  MC->setDwarfVersion(Version);
  Asm->setDwarfVersion(Version);

  StringPool = std::make_unique<DwarfStringPool>(Allocator, *Asm, StringRef());
  StringOffsetsStartSym = Asm->createTempSymbol("str_offsets_base");

  return Error::success();
}

StringRef dwarfgen::Generator::generate() {
  // Offset from the first CU in the debug info section is 0 initially.
  uint64_t SecOffset = 0;

  // Iterate over each compile unit and set the size and offsets for each
  // DIE within each compile unit. All offsets are CU relative.
  for (auto &CU : CompileUnits) {
    // Set the absolute .debug_info offset for this compile unit.
    CU->setOffset(SecOffset);
    // The DIEs contain compile unit relative offsets.
    unsigned CUOffset = 11;
    CUOffset = CU->getUnitDIE().computeSizeAndOffsets(CUOffset);
    // Update our absolute .debug_info offset.
    SecOffset += CUOffset;
    CU->setLength(CUOffset - 4);
  }
  Abbreviations.Emit(Asm.get(), TLOF->getDwarfAbbrevSection());

  StringPool->emitStringOffsetsTableHeader(*Asm, TLOF->getDwarfStrOffSection(),
                                           StringOffsetsStartSym);
  StringPool->emit(*Asm, TLOF->getDwarfStrSection(),
                   TLOF->getDwarfStrOffSection());

  MS->SwitchSection(TLOF->getDwarfInfoSection());
  for (auto &CU : CompileUnits) {
    uint16_t Version = CU->getVersion();
    auto Length = CU->getLength();
    MC->setDwarfVersion(Version);
    assert(Length != -1U);
    Asm->emitInt32(Length);
    Asm->emitInt16(Version);
    if (Version <= 4) {
      Asm->emitInt32(0);
      Asm->emitInt8(CU->getAddressSize());
    } else {
      Asm->emitInt8(dwarf::DW_UT_compile);
      Asm->emitInt8(CU->getAddressSize());
      Asm->emitInt32(0);
    }
    Asm->emitDwarfDIE(*CU->getUnitDIE().Die);
  }

  MS->SwitchSection(TLOF->getDwarfLineSection());
  for (auto &LT : LineTables)
    LT->generate(*MC, *Asm);

  MS->Finish();
  if (FileBytes.empty())
    return StringRef();
  return StringRef(FileBytes.data(), FileBytes.size());
}

bool dwarfgen::Generator::saveFile(StringRef Path) {
  if (FileBytes.empty())
    return false;
  std::error_code EC;
  raw_fd_ostream Strm(Path, EC, sys::fs::OF_None);
  if (EC)
    return false;
  Strm.write(FileBytes.data(), FileBytes.size());
  Strm.close();
  return true;
}

dwarfgen::CompileUnit &dwarfgen::Generator::addCompileUnit() {
  CompileUnits.push_back(
      std::make_unique<CompileUnit>(*this, Version, Asm->getPointerSize()));
  return *CompileUnits.back();
}

dwarfgen::LineTable &dwarfgen::Generator::addLineTable(DwarfFormat Format) {
  LineTables.push_back(
      std::make_unique<LineTable>(Version, Format, Asm->getPointerSize()));
  return *LineTables.back();
}
