//===------------ MachOBuilder.h -- Build MachO Objects ---------*- 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
//
//===----------------------------------------------------------------------===//
//
// Build MachO object files for interaction with the ObjC runtime and debugger.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H
#define LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H

#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/MathExtras.h"

#include <list>
#include <map>
#include <vector>

namespace llvm {
namespace orc {

template <typename MachOStruct>
size_t writeMachOStruct(MutableArrayRef<char> Buf, size_t Offset, MachOStruct S,
                        bool SwapStruct) {
  if (SwapStruct)
    MachO::swapStruct(S);
  assert(Offset + sizeof(MachOStruct) <= Buf.size() && "Buffer overflow");
  memcpy(&Buf[Offset], reinterpret_cast<const char *>(&S), sizeof(MachOStruct));
  return Offset + sizeof(MachOStruct);
}

/// Base type for MachOBuilder load command wrappers.
struct MachOBuilderLoadCommandBase {
  virtual ~MachOBuilderLoadCommandBase() {}
  virtual size_t size() const = 0;
  virtual size_t write(MutableArrayRef<char> Buf, size_t Offset,
                       bool SwapStruct) = 0;
};

/// MachOBuilder load command wrapper type.
template <MachO::LoadCommandType LCType> struct MachOBuilderLoadCommandImplBase;

#define HANDLE_LOAD_COMMAND(Name, Value, LCStruct)                             \
  template <>                                                                  \
  struct MachOBuilderLoadCommandImplBase<MachO::Name>                          \
      : public MachO::LCStruct, public MachOBuilderLoadCommandBase {           \
    using CmdStruct = LCStruct;                                                \
    MachOBuilderLoadCommandImplBase() {                                        \
      memset(&rawStruct(), 0, sizeof(CmdStruct));                              \
      cmd = Value;                                                             \
      cmdsize = sizeof(CmdStruct);                                             \
    }                                                                          \
    template <typename... ArgTs>                                               \
    MachOBuilderLoadCommandImplBase(ArgTs &&...Args)                           \
        : CmdStruct{Value, sizeof(CmdStruct), std::forward<ArgTs>(Args)...} {} \
    CmdStruct &rawStruct() { return static_cast<CmdStruct &>(*this); }         \
    size_t size() const override { return cmdsize; }                           \
    size_t write(MutableArrayRef<char> Buf, size_t Offset,                     \
                 bool SwapStruct) override {                                   \
      return writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct);           \
    }                                                                          \
  };

#include "llvm/BinaryFormat/MachO.def"

#undef HANDLE_LOAD_COMMAND

template <MachO::LoadCommandType LCType>
struct MachOBuilderLoadCommand
    : public MachOBuilderLoadCommandImplBase<LCType> {
public:
  MachOBuilderLoadCommand() = default;

  template <typename... ArgTs>
  MachOBuilderLoadCommand(ArgTs &&...Args)
      : MachOBuilderLoadCommandImplBase<LCType>(std::forward<ArgTs>(Args)...) {}
};

template <>
struct MachOBuilderLoadCommand<MachO::LC_ID_DYLIB>
    : public MachOBuilderLoadCommandImplBase<MachO::LC_ID_DYLIB> {

  MachOBuilderLoadCommand(std::string Name, uint32_t Timestamp,
                          uint32_t CurrentVersion,
                          uint32_t CompatibilityVersion)
      : MachOBuilderLoadCommandImplBase(
            MachO::dylib{24, Timestamp, CurrentVersion, CompatibilityVersion}),
        Name(std::move(Name)) {
    cmdsize += (this->Name.size() + 1 + 3) & ~0x3;
  }

  size_t write(MutableArrayRef<char> Buf, size_t Offset,
               bool SwapStruct) override {
    Offset = writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct);
    strcpy(Buf.data() + Offset, Name.data());
    return Offset + ((Name.size() + 1 + 3) & ~0x3);
  }

  std::string Name;
};

template <>
struct MachOBuilderLoadCommand<MachO::LC_LOAD_DYLIB>
    : public MachOBuilderLoadCommandImplBase<MachO::LC_LOAD_DYLIB> {

  MachOBuilderLoadCommand(std::string Name, uint32_t Timestamp,
                          uint32_t CurrentVersion,
                          uint32_t CompatibilityVersion)
      : MachOBuilderLoadCommandImplBase(
            MachO::dylib{24, Timestamp, CurrentVersion, CompatibilityVersion}),
        Name(std::move(Name)) {
    cmdsize += (this->Name.size() + 1 + 3) & ~0x3;
  }

  size_t write(MutableArrayRef<char> Buf, size_t Offset,
               bool SwapStruct) override {
    Offset = writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct);
    strcpy(Buf.data() + Offset, Name.data());
    return Offset + ((Name.size() + 1 + 3) & ~0x3);
  }

  std::string Name;
};

template <>
struct MachOBuilderLoadCommand<MachO::LC_RPATH>
    : public MachOBuilderLoadCommandImplBase<MachO::LC_RPATH> {
  MachOBuilderLoadCommand(std::string Path)
      : MachOBuilderLoadCommandImplBase(12u), Path(std::move(Path)) {
    cmdsize += (this->Path.size() + 1 + 3) & ~0x3;
  }

  size_t write(MutableArrayRef<char> Buf, size_t Offset,
               bool SwapStruct) override {
    Offset = writeMachOStruct(Buf, Offset, rawStruct(), SwapStruct);
    strcpy(Buf.data() + Offset, Path.data());
    return Offset + ((Path.size() + 1 + 3) & ~0x3);
  }

  std::string Path;
};

// Builds MachO objects.
template <typename MachOTraits> class MachOBuilder {
private:
  struct SymbolContainer {
    size_t SymbolIndexBase = 0;
    std::vector<typename MachOTraits::NList> Symbols;
  };

  struct StringTableEntry {
    StringRef S;
    size_t Offset;
  };

  using StringTable = std::vector<StringTableEntry>;

  static bool swapStruct() {
    return MachOTraits::Endianness != llvm::endianness::native;
  }

public:
  using StringId = size_t;

  struct Section;

  // Points to either an nlist entry (as a (symbol-container, index) pair), or
  // a section.
  class RelocTarget {
  public:
    RelocTarget(const Section &S) : S(&S), Idx(~0U) {}
    RelocTarget(SymbolContainer &SC, size_t Idx) : SC(&SC), Idx(Idx) {}

    bool isSymbol() { return Idx != ~0U; }

    uint32_t getSymbolNum() {
      assert(isSymbol() && "Target is not a symbol");
      return SC->SymbolIndexBase + Idx;
    }

    uint32_t getSectionId() {
      assert(!isSymbol() && "Target is not a section");
      return S->SectionNumber;
    }

    typename MachOTraits::NList &nlist() {
      assert(isSymbol() && "Target is not a symbol");
      return SC->Symbols[Idx];
    }

  private:
    union {
      const Section *S;
      SymbolContainer *SC;
    };
    size_t Idx;
  };

  struct Reloc : public MachO::relocation_info {
    RelocTarget Target;

    Reloc(int32_t Offset, RelocTarget Target, bool PCRel, unsigned Length,
          unsigned Type)
        : Target(Target) {
      assert(Type < 16 && "Relocation type out of range");
      r_address = Offset; // Will slide to account for sec addr during layout
      r_symbolnum = 0;
      r_pcrel = PCRel;
      r_length = Length;
      r_extern = Target.isSymbol();
      r_type = Type;
    }

    MachO::relocation_info &rawStruct() {
      return static_cast<MachO::relocation_info &>(*this);
    }
  };

  struct SectionContent {
    const char *Data = nullptr;
    size_t Size = 0;
  };

  struct Section : public MachOTraits::Section, public RelocTarget {
    MachOBuilder &Builder;
    SectionContent Content;
    size_t SectionNumber = 0;
    SymbolContainer SC;
    std::vector<Reloc> Relocs;

    Section(MachOBuilder &Builder, StringRef SecName, StringRef SegName)
        : RelocTarget(*this), Builder(Builder) {
      memset(&rawStruct(), 0, sizeof(typename MachOTraits::Section));
      assert(SecName.size() <= 16 && "SecName too long");
      assert(SegName.size() <= 16 && "SegName too long");
      memcpy(this->sectname, SecName.data(), SecName.size());
      memcpy(this->segname, SegName.data(), SegName.size());
    }

    RelocTarget addSymbol(int32_t Offset, StringRef Name, uint8_t Type,
                          uint16_t Desc) {
      StringId SI = Builder.addString(Name);
      typename MachOTraits::NList Sym;
      Sym.n_strx = SI;
      Sym.n_type = Type | MachO::N_SECT;
      Sym.n_sect = MachO::NO_SECT; // Will be filled in later.
      Sym.n_desc = Desc;
      Sym.n_value = Offset;
      SC.Symbols.push_back(Sym);
      return {SC, SC.Symbols.size() - 1};
    }

    void addReloc(int32_t Offset, RelocTarget Target, bool PCRel,
                  unsigned Length, unsigned Type) {
      Relocs.push_back({Offset, Target, PCRel, Length, Type});
    }

    auto &rawStruct() {
      return static_cast<typename MachOTraits::Section &>(*this);
    }
  };

  struct Segment : public MachOBuilderLoadCommand<MachOTraits::SegmentCmd> {
    MachOBuilder &Builder;
    std::vector<std::unique_ptr<Section>> Sections;

    Segment(MachOBuilder &Builder, StringRef SegName)
        : MachOBuilderLoadCommand<MachOTraits::SegmentCmd>(), Builder(Builder) {
      assert(SegName.size() <= 16 && "SegName too long");
      memcpy(this->segname, SegName.data(), SegName.size());
      this->maxprot =
          MachO::VM_PROT_READ | MachO::VM_PROT_WRITE | MachO::VM_PROT_EXECUTE;
      this->initprot = this->maxprot;
    }

    Section &addSection(StringRef SecName, StringRef SegName) {
      Sections.push_back(std::make_unique<Section>(Builder, SecName, SegName));
      return *Sections.back();
    }

    size_t write(MutableArrayRef<char> Buf, size_t Offset,
                 bool SwapStruct) override {
      Offset = MachOBuilderLoadCommand<MachOTraits::SegmentCmd>::write(
          Buf, Offset, SwapStruct);
      for (auto &Sec : Sections)
        Offset = writeMachOStruct(Buf, Offset, Sec->rawStruct(), SwapStruct);
      return Offset;
    }
  };

  MachOBuilder(size_t PageSize) : PageSize(PageSize) {
    memset((char *)&Header, 0, sizeof(Header));
    Header.magic = MachOTraits::Magic;
  }

  template <MachO::LoadCommandType LCType, typename... ArgTs>
  MachOBuilderLoadCommand<LCType> &addLoadCommand(ArgTs &&...Args) {
    static_assert(LCType != MachOTraits::SegmentCmd,
                  "Use addSegment to add segment load command");
    auto LC = std::make_unique<MachOBuilderLoadCommand<LCType>>(
        std::forward<ArgTs>(Args)...);
    auto &Tmp = *LC;
    LoadCommands.push_back(std::move(LC));
    return Tmp;
  }

  StringId addString(StringRef Str) {
    if (Strings.empty() && !Str.empty())
      addString("");
    return Strings.insert(std::make_pair(Str, Strings.size())).first->second;
  }

  Segment &addSegment(StringRef SegName) {
    Segments.push_back(Segment(*this, SegName));
    return Segments.back();
  }

  RelocTarget addSymbol(StringRef Name, uint8_t Type, uint8_t Sect,
                        uint16_t Desc, typename MachOTraits::UIntPtr Value) {
    StringId SI = addString(Name);
    typename MachOTraits::NList Sym;
    Sym.n_strx = SI;
    Sym.n_type = Type;
    Sym.n_sect = Sect;
    Sym.n_desc = Desc;
    Sym.n_value = Value;
    SC.Symbols.push_back(Sym);
    return {SC, SC.Symbols.size() - 1};
  }

  // Call to perform layout on the MachO. Returns the total size of the
  // resulting file.
  // This method will automatically insert some load commands (e.g.
  // LC_SYMTAB) and fill in load command fields.
  size_t layout() {

    // Build symbol table and add LC_SYMTAB command.
    makeStringTable();
    MachOBuilderLoadCommand<MachOTraits::SymTabCmd> *SymTabLC = nullptr;
    if (!StrTab.empty())
      SymTabLC = &addLoadCommand<MachOTraits::SymTabCmd>();

    // Lay out header, segment load command, and other load commands.
    size_t Offset = sizeof(Header);
    for (auto &Seg : Segments) {
      Seg.cmdsize +=
          Seg.Sections.size() * sizeof(typename MachOTraits::Section);
      Seg.nsects = Seg.Sections.size();
      Offset += Seg.cmdsize;
    }
    for (auto &LC : LoadCommands)
      Offset += LC->size();

    Header.sizeofcmds = Offset - sizeof(Header);

    // Lay out content, set segment / section addrs and offsets.
    size_t SegVMAddr = 0;
    for (auto &Seg : Segments) {
      Seg.vmaddr = SegVMAddr;
      Seg.fileoff = Offset;
      for (auto &Sec : Seg.Sections) {
        Offset = alignTo(Offset, 1ULL << Sec->align);
        if (Sec->Content.Size)
          Sec->offset = Offset;
        Sec->size = Sec->Content.Size;
        Sec->addr = SegVMAddr + Sec->offset - Seg.fileoff;
        Offset += Sec->Content.Size;
      }
      size_t SegContentSize = Offset - Seg.fileoff;
      Seg.filesize = SegContentSize;
      Seg.vmsize = Header.filetype == MachO::MH_OBJECT
                       ? SegContentSize
                       : alignTo(SegContentSize, PageSize);
      SegVMAddr += Seg.vmsize;
    }

    // Set string table offsets for non-section symbols.
    for (auto &Sym : SC.Symbols)
      Sym.n_strx = StrTab[Sym.n_strx].Offset;

    // Number sections, set symbol section numbers and string table offsets,
    // count relocations.
    size_t NumSymbols = SC.Symbols.size();
    size_t SectionNumber = 0;
    for (auto &Seg : Segments) {
      for (auto &Sec : Seg.Sections) {
        ++SectionNumber;
        Sec->SectionNumber = SectionNumber;
        Sec->SC.SymbolIndexBase = NumSymbols;
        NumSymbols += Sec->SC.Symbols.size();
        for (auto &Sym : Sec->SC.Symbols) {
          Sym.n_sect = SectionNumber;
          Sym.n_strx = StrTab[Sym.n_strx].Offset;
          Sym.n_value += Sec->addr;
        }
      }
    }

    // Handle relocations
    bool OffsetAlignedForRelocs = false;
    for (auto &Seg : Segments) {
      for (auto &Sec : Seg.Sections) {
        if (!Sec->Relocs.empty()) {
          if (!OffsetAlignedForRelocs) {
            Offset = alignTo(Offset, sizeof(MachO::relocation_info));
            OffsetAlignedForRelocs = true;
          }
          Sec->reloff = Offset;
          Sec->nreloc = Sec->Relocs.size();
          Offset += Sec->Relocs.size() * sizeof(MachO::relocation_info);
          for (auto &R : Sec->Relocs)
            R.r_symbolnum = R.Target.isSymbol() ? R.Target.getSymbolNum()
                                                : R.Target.getSectionId();
        }
      }
    }

    // Calculate offset to start of nlist and update symtab command.
    if (NumSymbols > 0) {
      Offset = alignTo(Offset, sizeof(typename MachOTraits::NList));
      SymTabLC->symoff = Offset;
      SymTabLC->nsyms = NumSymbols;

      // Calculate string table bounds and update symtab command.
      if (!StrTab.empty()) {
        Offset += NumSymbols * sizeof(typename MachOTraits::NList);
        size_t StringTableSize =
            StrTab.back().Offset + StrTab.back().S.size() + 1;

        SymTabLC->stroff = Offset;
        SymTabLC->strsize = StringTableSize;
        Offset += StringTableSize;
      }
    }

    return Offset;
  }

  void write(MutableArrayRef<char> Buffer) {
    size_t Offset = 0;
    Offset = writeHeader(Buffer, Offset);
    Offset = writeSegments(Buffer, Offset);
    Offset = writeLoadCommands(Buffer, Offset);
    Offset = writeSectionContent(Buffer, Offset);
    Offset = writeRelocations(Buffer, Offset);
    Offset = writeSymbols(Buffer, Offset);
    Offset = writeStrings(Buffer, Offset);
  }

  typename MachOTraits::Header Header;

private:
  void makeStringTable() {
    if (Strings.empty())
      return;

    StrTab.resize(Strings.size());
    for (auto &[Str, Idx] : Strings)
      StrTab[Idx] = {Str, 0};
    size_t Offset = 0;
    for (auto &Elem : StrTab) {
      Elem.Offset = Offset;
      Offset += Elem.S.size() + 1;
    }
  }

  size_t writeHeader(MutableArrayRef<char> Buf, size_t Offset) {
    Header.ncmds = Segments.size() + LoadCommands.size();
    return writeMachOStruct(Buf, Offset, Header, swapStruct());
  }

  size_t writeSegments(MutableArrayRef<char> Buf, size_t Offset) {
    for (auto &Seg : Segments)
      Offset = Seg.write(Buf, Offset, swapStruct());
    return Offset;
  }

  size_t writeLoadCommands(MutableArrayRef<char> Buf, size_t Offset) {
    for (auto &LC : LoadCommands)
      Offset = LC->write(Buf, Offset, swapStruct());
    return Offset;
  }

  size_t writeSectionContent(MutableArrayRef<char> Buf, size_t Offset) {
    for (auto &Seg : Segments) {
      for (auto &Sec : Seg.Sections) {
        if (!Sec->Content.Data) {
          assert(Sec->Relocs.empty() &&
                 "Cant' have relocs for zero-fill segment");
          continue;
        }
        while (Offset != Sec->offset)
          Buf[Offset++] = '\0';

        assert(Offset + Sec->Content.Size <= Buf.size() && "Buffer overflow");
        memcpy(&Buf[Offset], Sec->Content.Data, Sec->Content.Size);
        Offset += Sec->Content.Size;
      }
    }
    return Offset;
  }

  size_t writeRelocations(MutableArrayRef<char> Buf, size_t Offset) {
    for (auto &Seg : Segments) {
      for (auto &Sec : Seg.Sections) {
        if (!Sec->Relocs.empty()) {
          while (Offset % sizeof(MachO::relocation_info))
            Buf[Offset++] = '\0';
        }
        for (auto &R : Sec->Relocs) {
          assert(Offset + sizeof(MachO::relocation_info) <= Buf.size() &&
                 "Buffer overflow");
          memcpy(&Buf[Offset], reinterpret_cast<const char *>(&R.rawStruct()),
                 sizeof(MachO::relocation_info));
          Offset += sizeof(MachO::relocation_info);
        }
      }
    }
    return Offset;
  }

  size_t writeSymbols(MutableArrayRef<char> Buf, size_t Offset) {

    // Count symbols.
    size_t NumSymbols = SC.Symbols.size();
    for (auto &Seg : Segments)
      for (auto &Sec : Seg.Sections)
        NumSymbols += Sec->SC.Symbols.size();

    // If none then return.
    if (NumSymbols == 0)
      return Offset;

    // Align to nlist entry size.
    while (Offset % sizeof(typename MachOTraits::NList))
      Buf[Offset++] = '\0';

    // Write non-section symbols.
    for (auto &Sym : SC.Symbols)
      Offset = writeMachOStruct(Buf, Offset, Sym, swapStruct());

    // Write section symbols.
    for (auto &Seg : Segments) {
      for (auto &Sec : Seg.Sections) {
        for (auto &Sym : Sec->SC.Symbols) {
          Offset = writeMachOStruct(Buf, Offset, Sym, swapStruct());
        }
      }
    }
    return Offset;
  }

  size_t writeStrings(MutableArrayRef<char> Buf, size_t Offset) {
    for (auto &Elem : StrTab) {
      assert(Offset + Elem.S.size() + 1 <= Buf.size() && "Buffer overflow");
      memcpy(&Buf[Offset], Elem.S.data(), Elem.S.size());
      Offset += Elem.S.size();
      Buf[Offset++] = '\0';
    }
    return Offset;
  }

  size_t PageSize;
  std::list<Segment> Segments;
  std::vector<std::unique_ptr<MachOBuilderLoadCommandBase>> LoadCommands;
  SymbolContainer SC;

  // Maps strings to their "id" (addition order).
  std::map<StringRef, size_t> Strings;
  StringTable StrTab;
};

struct MachO64LE {
  using UIntPtr = uint64_t;
  using Header = MachO::mach_header_64;
  using Section = MachO::section_64;
  using NList = MachO::nlist_64;
  using Relocation = MachO::relocation_info;

  static constexpr llvm::endianness Endianness = llvm::endianness::little;
  static constexpr uint32_t Magic = MachO::MH_MAGIC_64;
  static constexpr MachO::LoadCommandType SegmentCmd = MachO::LC_SEGMENT_64;
  static constexpr MachO::LoadCommandType SymTabCmd = MachO::LC_SYMTAB;
};

} // namespace orc
} // namespace llvm

#endif // LLVM_EXECUTIONENGINE_ORC_MACHOBUILDER_H
