//===- yaml2xcoff - Convert YAML to a xcoff object file -------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// The xcoff component of yaml2obj.
///
//===----------------------------------------------------------------------===//

#include "llvm/ADT/DenseMap.h"
#include "llvm/BinaryFormat/XCOFF.h"
#include "llvm/MC/StringTableBuilder.h"
#include "llvm/Object/XCOFFObjectFile.h"
#include "llvm/ObjectYAML/ObjectYAML.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/LEB128.h"

using namespace llvm;

namespace {

constexpr unsigned DefaultSectionAlign = 4;
constexpr int16_t MaxSectionIndex = INT16_MAX;
constexpr uint32_t MaxRawDataSize = UINT32_MAX;

class XCOFFWriter {
public:
  XCOFFWriter(XCOFFYAML::Object &Obj, raw_ostream &OS, yaml::ErrorHandler EH)
      : Obj(Obj), W(OS, support::big), ErrHandler(EH),
        Strings(StringTableBuilder::XCOFF) {
    Is64Bit = Obj.Header.Magic == (llvm::yaml::Hex16)XCOFF::XCOFF64;
  }
  bool writeXCOFF();

private:
  bool nameShouldBeInStringTable(StringRef SymbolName);
  bool initFileHeader(uint64_t CurrentOffset);
  bool initSectionHeader(uint64_t &CurrentOffset);
  bool initRelocations(uint64_t &CurrentOffset);
  bool assignAddressesAndIndices();
  void writeFileHeader();
  void writeSectionHeader();
  bool writeSectionData();
  bool writeRelocations();
  bool writeSymbols();

  XCOFFYAML::Object &Obj;
  bool Is64Bit = false;
  support::endian::Writer W;
  yaml::ErrorHandler ErrHandler;
  StringTableBuilder Strings;
  uint64_t StartOffset;
  // Map the section name to its corrresponding section index.
  DenseMap<StringRef, int16_t> SectionIndexMap = {
      {StringRef("N_DEBUG"), XCOFF::N_DEBUG},
      {StringRef("N_ABS"), XCOFF::N_ABS},
      {StringRef("N_UNDEF"), XCOFF::N_UNDEF}};
  XCOFFYAML::FileHeader InitFileHdr = Obj.Header;
  std::vector<XCOFFYAML::Section> InitSections = Obj.Sections;
};

static void writeName(StringRef StrName, support::endian::Writer W) {
  char Name[XCOFF::NameSize];
  memset(Name, 0, XCOFF::NameSize);
  char SrcName[] = "";
  memcpy(Name, StrName.size() ? StrName.data() : SrcName, StrName.size());
  ArrayRef<char> NameRef(Name, XCOFF::NameSize);
  W.write(NameRef);
}

bool XCOFFWriter::nameShouldBeInStringTable(StringRef SymbolName) {
  return SymbolName.size() > XCOFF::NameSize;
}

bool XCOFFWriter::initRelocations(uint64_t &CurrentOffset) {
  for (uint16_t I = 0, E = InitSections.size(); I < E; ++I) {
    if (!InitSections[I].Relocations.empty()) {
      InitSections[I].NumberOfRelocations = InitSections[I].Relocations.size();
      InitSections[I].FileOffsetToRelocations = CurrentOffset;
      CurrentOffset += InitSections[I].NumberOfRelocations *
                       XCOFF::RelocationSerializationSize32;
      if (CurrentOffset > MaxRawDataSize) {
        ErrHandler("maximum object size of" + Twine(MaxRawDataSize) +
                   "exceeded when writing relocation data");
        return false;
      }
    }
  }
  return true;
}

bool XCOFFWriter::initSectionHeader(uint64_t &CurrentOffset) {
  uint64_t CurrentSecAddr = 0;
  for (uint16_t I = 0, E = InitSections.size(); I < E; ++I) {
    if (CurrentOffset > MaxRawDataSize) {
      ErrHandler("maximum object size of" + Twine(MaxRawDataSize) +
                 "exceeded when writing section data");
      return false;
    }

    // Assign indices for sections.
    if (InitSections[I].SectionName.size() &&
        !SectionIndexMap[InitSections[I].SectionName]) {
      // The section index starts from 1.
      SectionIndexMap[InitSections[I].SectionName] = I + 1;
      if ((I + 1) > MaxSectionIndex) {
        ErrHandler("exceeded the maximum permitted section index of " +
                   Twine(MaxSectionIndex));
        return false;
      }
    }

    // Calculate the physical/virtual address. This field should contain 0 for
    // all sections except the text, data and bss sections.
    if (InitSections[I].Flags != XCOFF::STYP_TEXT &&
        InitSections[I].Flags != XCOFF::STYP_DATA &&
        InitSections[I].Flags != XCOFF::STYP_BSS)
      InitSections[I].Address = 0;
    else
      InitSections[I].Address = CurrentSecAddr;

    // Calculate the FileOffsetToData and data size for sections.
    if (InitSections[I].SectionData.binary_size()) {
      InitSections[I].FileOffsetToData = CurrentOffset;
      CurrentOffset += InitSections[I].SectionData.binary_size();
      // Ensure the offset is aligned to DefaultSectionAlign.
      CurrentOffset = alignTo(CurrentOffset, DefaultSectionAlign);
      InitSections[I].Size = CurrentOffset - InitSections[I].FileOffsetToData;
      CurrentSecAddr += InitSections[I].Size;
    }
  }
  return initRelocations(CurrentOffset);
}

bool XCOFFWriter::initFileHeader(uint64_t CurrentOffset) {
  // The default format of the object file is XCOFF32.
  InitFileHdr.Magic = XCOFF::XCOFF32;
  InitFileHdr.NumberOfSections = Obj.Sections.size();
  InitFileHdr.NumberOfSymTableEntries = Obj.Symbols.size();

  for (const XCOFFYAML::Symbol &YamlSym : Obj.Symbols) {
    // Add the number of auxiliary symbols to the total number.
    InitFileHdr.NumberOfSymTableEntries += YamlSym.NumberOfAuxEntries;
    if (nameShouldBeInStringTable(YamlSym.SymbolName))
      Strings.add(YamlSym.SymbolName);
  }
  // Finalize the string table.
  Strings.finalize();

  // Calculate SymbolTableOffset for the file header.
  if (InitFileHdr.NumberOfSymTableEntries) {
    InitFileHdr.SymbolTableOffset = CurrentOffset;
    CurrentOffset +=
        InitFileHdr.NumberOfSymTableEntries * XCOFF::SymbolTableEntrySize;
    if (CurrentOffset > MaxRawDataSize) {
      ErrHandler("maximum object size of" + Twine(MaxRawDataSize) +
                 "exceeded when writing symbols");
      return false;
    }
  }
  // TODO: Calculate FileOffsetToLineNumbers when line number supported.
  return true;
}

bool XCOFFWriter::assignAddressesAndIndices() {
  Strings.clear();
  uint64_t CurrentOffset =
      XCOFF::FileHeaderSize32 /* TODO: + auxiliaryHeaderSize() */ +
      InitSections.size() * XCOFF::SectionHeaderSize32;

  // Calculate section header info.
  if (!initSectionHeader(CurrentOffset))
    return false;
  // Calculate file header info.
  return initFileHeader(CurrentOffset);
}

void XCOFFWriter::writeFileHeader() {
  W.write<uint16_t>(Obj.Header.Magic ? Obj.Header.Magic : InitFileHdr.Magic);
  W.write<uint16_t>(Obj.Header.NumberOfSections ? Obj.Header.NumberOfSections
                                                : InitFileHdr.NumberOfSections);
  W.write<int32_t>(Obj.Header.TimeStamp);
  W.write<uint32_t>(Obj.Header.SymbolTableOffset
                        ? Obj.Header.SymbolTableOffset
                        : InitFileHdr.SymbolTableOffset);
  W.write<int32_t>(Obj.Header.NumberOfSymTableEntries
                       ? Obj.Header.NumberOfSymTableEntries
                       : InitFileHdr.NumberOfSymTableEntries);
  W.write<uint16_t>(Obj.Header.AuxHeaderSize);
  W.write<uint16_t>(Obj.Header.Flags);
}

void XCOFFWriter::writeSectionHeader() {
  for (uint16_t I = 0, E = Obj.Sections.size(); I < E; ++I) {
    XCOFFYAML::Section YamlSec = Obj.Sections[I];
    XCOFFYAML::Section DerivedSec = InitSections[I];
    writeName(YamlSec.SectionName, W);
    // Virtual address is the same as physical address.
    uint32_t SectionAddress =
        YamlSec.Address ? YamlSec.Address : DerivedSec.Address;
    W.write<uint32_t>(SectionAddress); // Physical address
    W.write<uint32_t>(SectionAddress); // Virtual address
    W.write<uint32_t>(YamlSec.Size ? YamlSec.Size : DerivedSec.Size);
    W.write<uint32_t>(YamlSec.FileOffsetToData ? YamlSec.FileOffsetToData
                                               : DerivedSec.FileOffsetToData);
    W.write<uint32_t>(YamlSec.FileOffsetToRelocations
                          ? YamlSec.FileOffsetToRelocations
                          : DerivedSec.FileOffsetToRelocations);
    W.write<uint32_t>(YamlSec.FileOffsetToLineNumbers);
    W.write<uint16_t>(YamlSec.NumberOfRelocations
                          ? YamlSec.NumberOfRelocations
                          : DerivedSec.NumberOfRelocations);
    W.write<uint16_t>(YamlSec.NumberOfLineNumbers);
    W.write<int32_t>(YamlSec.Flags);
  }
}

bool XCOFFWriter::writeSectionData() {
  for (uint16_t I = 0, E = Obj.Sections.size(); I < E; ++I) {
    XCOFFYAML::Section YamlSec = Obj.Sections[I];
    if (YamlSec.SectionData.binary_size()) {
      // Fill the padding size with zeros.
      int64_t PaddingSize =
          InitSections[I].FileOffsetToData - (W.OS.tell() - StartOffset);
      if (PaddingSize < 0) {
        ErrHandler("redundant data was written before section data");
        return false;
      }
      if (PaddingSize > 0)
        W.OS.write_zeros(PaddingSize);
      YamlSec.SectionData.writeAsBinary(W.OS);
    }
  }
  return true;
}

bool XCOFFWriter::writeRelocations() {
  for (uint16_t I = 0, E = Obj.Sections.size(); I < E; ++I) {
    XCOFFYAML::Section YamlSec = Obj.Sections[I];
    if (!YamlSec.Relocations.empty()) {
      int64_t PaddingSize =
          InitSections[I].FileOffsetToRelocations - (W.OS.tell() - StartOffset);
      if (PaddingSize < 0) {
        ErrHandler("redundant data was written before relocations");
        return false;
      }
      if (PaddingSize > 0)
        W.OS.write_zeros(PaddingSize);
      for (const XCOFFYAML::Relocation &YamlRel : YamlSec.Relocations) {
        W.write<uint32_t>(YamlRel.VirtualAddress);
        W.write<uint32_t>(YamlRel.SymbolIndex);
        W.write<uint8_t>(YamlRel.Info);
        W.write<uint8_t>(YamlRel.Type);
      }
    }
  }
  return true;
}

bool XCOFFWriter::writeSymbols() {
  int64_t PaddingSize =
      (uint64_t)InitFileHdr.SymbolTableOffset - (W.OS.tell() - StartOffset);
  if (PaddingSize < 0) {
    ErrHandler("redundant data was written before symbols");
    return false;
  }
  if (PaddingSize > 0)
    W.OS.write_zeros(PaddingSize);
  for (const XCOFFYAML::Symbol &YamlSym : Obj.Symbols) {
    if (nameShouldBeInStringTable(YamlSym.SymbolName)) {
      // For XCOFF32: A value of 0 indicates that the symbol name is in the
      // string table.
      W.write<int32_t>(0);
      W.write<uint32_t>(Strings.getOffset(YamlSym.SymbolName));
    } else {
      writeName(YamlSym.SymbolName, W);
    }
    W.write<uint32_t>(YamlSym.Value);
    W.write<int16_t>(
        YamlSym.SectionName.size() ? SectionIndexMap[YamlSym.SectionName] : 0);
    W.write<uint16_t>(YamlSym.Type);
    W.write<uint8_t>(YamlSym.StorageClass);
    W.write<uint8_t>(YamlSym.NumberOfAuxEntries);

    // Now output the auxiliary entry.
    for (uint8_t I = 0, E = YamlSym.NumberOfAuxEntries; I < E; ++I) {
      // TODO: Auxiliary entry is not supported yet.
      // The auxiliary entries for a symbol follow its symbol table entry. The
      // length of each auxiliary entry is the same as a symbol table entry (18
      // bytes). The format and quantity of auxiliary entries depend on the
      // storage class (n_sclass) and type (n_type) of the symbol table entry.
      W.OS.write_zeros(18);
    }
  }
  return true;
}

bool XCOFFWriter::writeXCOFF() {
  if (Is64Bit) {
    ErrHandler("only XCOFF32 is currently supported");
    return false;
  }
  if (!assignAddressesAndIndices())
    return false;
  StartOffset = W.OS.tell();
  writeFileHeader();
  if (!Obj.Sections.empty()) {
    writeSectionHeader();
    if (!writeSectionData())
      return false;
    if (!writeRelocations())
      return false;
  }
  if (!Obj.Symbols.empty() && !writeSymbols())
    return false;
  // Write the string table.
  if (Strings.getSize() > 4)
    Strings.write(W.OS);
  return true;
}

} // end anonymous namespace

namespace llvm {
namespace yaml {

bool yaml2xcoff(XCOFFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH) {
  XCOFFWriter Writer(Doc, Out, EH);
  return Writer.writeXCOFF();
}

} // namespace yaml
} // namespace llvm
