//===- CompactUnwindSupportImpl.h - Compact Unwind format impl --*- 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
//
//===----------------------------------------------------------------------===//
//
// Compact Unwind format support implementation details.
//
//===----------------------------------------------------------------------===//

#ifndef LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H
#define LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H

#include "llvm/ADT/STLExtras.h"
#include "llvm/ExecutionEngine/JITLink/MachO.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Endian.h"

#define DEBUG_TYPE "jitlink_cu"

namespace llvm {
namespace jitlink {

/// Split blocks in an __LD,__compact_unwind section on record boundaries.
/// When this function returns edges within each record are guaranteed to be
/// sorted by offset.
Error splitCompactUnwindBlocks(LinkGraph &G, Section &CompactUnwindSection,
                               size_t RecordSize);

/// CRTP base for compact unwind traits classes. Automatically provides derived
/// constants.
///
/// FIXME: Passing PtrSize as a template parameter is a hack to work around a
///        bug in older MSVC compilers (until at least MSVC 15) where constexpr
///        fields in the CRTP impl class were not visible to the base class.
///        Once we no longer need to support these compilers the PtrSize
///        template argument should be removed and PointerSize should be
///        defined as a member in the CRTP Impl classes.
template <typename CRTPImpl, size_t PtrSize> struct CompactUnwindTraits {
  static constexpr size_t PointerSize = PtrSize;
  static constexpr size_t Size = 3 * PointerSize + 2 * 4;
  static constexpr size_t FnFieldOffset = 0;
  static constexpr size_t SizeFieldOffset = FnFieldOffset + PointerSize;
  static constexpr size_t EncodingFieldOffset = SizeFieldOffset + 4;
  static constexpr size_t PersonalityFieldOffset = EncodingFieldOffset + 4;
  static constexpr size_t LSDAFieldOffset =
      PersonalityFieldOffset + PointerSize;

  static uint32_t readPCRangeSize(ArrayRef<char> RecordContent) {
    assert(SizeFieldOffset + 4 <= RecordContent.size() &&
           "Truncated CU record?");
    return support::endian::read32<CRTPImpl::Endianness>(RecordContent.data() +
                                                         SizeFieldOffset);
  }

  static uint32_t readEncoding(ArrayRef<char> RecordContent) {
    assert(EncodingFieldOffset + 4 <= RecordContent.size() &&
           "Truncated CU record?");
    return support::endian::read32<CRTPImpl::Endianness>(RecordContent.data() +
                                                         EncodingFieldOffset);
  }

  static std::optional<uint32_t> encodeDWARFOffset(size_t Delta) {
    uint32_t Encoded =
        static_cast<uint32_t>(Delta) & CRTPImpl::DWARFSectionOffsetMask;
    if (Encoded != Delta)
      return std::nullopt;
    return Encoded;
  }
};

/// Architecture specific implementation of CompactUnwindManager.
template <typename CURecTraits> class CompactUnwindManager {
public:
  CompactUnwindManager(StringRef CompactUnwindSectionName,
                       StringRef UnwindInfoSectionName,
                       StringRef EHFrameSectionName)
      : CompactUnwindSectionName(CompactUnwindSectionName),
        UnwindInfoSectionName(UnwindInfoSectionName),
        EHFrameSectionName(EHFrameSectionName) {}

  // Split compact unwind records, add keep-alive edges from functions to
  // compact unwind records, and from compact unwind records to FDEs where
  // needed.
  //
  // This method must be called *after* __eh_frame has been processed: it
  // assumes that eh-frame records have been split up and keep-alive edges have
  // been inserted.
  Error prepareForPrune(LinkGraph &G) {
    Section *CUSec = G.findSectionByName(CompactUnwindSectionName);
    if (!CUSec || CUSec->empty()) {
      LLVM_DEBUG({
        dbgs() << "Compact unwind: No compact unwind info for " << G.getName()
               << "\n";
      });
      return Error::success();
    }

    LLVM_DEBUG({
      dbgs() << "Compact unwind: preparing " << G.getName() << " for prune\n";
    });

    Section *EHFrameSec = G.findSectionByName(EHFrameSectionName);

    if (auto Err = splitCompactUnwindBlocks(G, *CUSec, CURecTraits::Size))
      return Err;

    LLVM_DEBUG({
      dbgs() << "  Preparing " << CUSec->blocks_size() << " blocks in "
             << CompactUnwindSectionName << "\n";
    });

    for (auto *B : CUSec->blocks()) {

      // Find target function edge.
      Edge *PCBeginEdge = nullptr;
      for (auto &E : B->edges_at(CURecTraits::FnFieldOffset)) {
        PCBeginEdge = &E;
        break;
      }

      if (!PCBeginEdge)
        return make_error<JITLinkError>(
            "In " + G.getName() + ", compact unwind record at " +
            formatv("{0:x}", B->getAddress()) + " has no pc-begin edge");

      if (!PCBeginEdge->getTarget().isDefined())
        return make_error<JITLinkError>(
            "In " + G.getName() + ", compact unwind record at " +
            formatv("{0:x}", B->getAddress()) + " points at external symbol " +
            *PCBeginEdge->getTarget().getName());

      auto &Fn = PCBeginEdge->getTarget();

      if (!Fn.isDefined()) {
        LLVM_DEBUG({
          dbgs() << "In " << CompactUnwindSectionName << " for " << G.getName()
                 << " encountered unexpected pc-edge to undefined symbol "
                 << Fn.getName() << "\n";
        });
        continue;
      }

      uint32_t Encoding = CURecTraits::readEncoding(B->getContent());
      bool NeedsDWARF = CURecTraits::encodingSpecifiesDWARF(Encoding);

      LLVM_DEBUG({
        dbgs() << "    Found record for function ";
        if (Fn.hasName())
          dbgs() << Fn.getName();
        else
          dbgs() << "<anon @ " << Fn.getAddress() << '>';
        dbgs() << ": encoding = " << formatv("{0:x}", Encoding);
        if (NeedsDWARF)
          dbgs() << " (needs DWARF)";
        dbgs() << "\n";
      });

      auto &CURecSym =
          G.addAnonymousSymbol(*B, 0, CURecTraits::Size, false, false);

      bool KeepAliveAlreadyPresent = false;
      if (EHFrameSec) {
        Edge *KeepAliveEdge = nullptr;
        for (auto &E : Fn.getBlock().edges_at(0)) {
          if (E.getKind() == Edge::KeepAlive && E.getTarget().isDefined() &&
              &E.getTarget().getSection() == EHFrameSec) {
            KeepAliveEdge = &E;
            break;
          }
        }

        if (KeepAliveEdge) {
          // Found a keep-alive edge to an FDE in the eh-frame. Switch the keep
          // alive edge to point to the CU and if the CU needs DWARF then add
          // an extra keep-alive edge from the CU to the FDE.
          auto &FDE = KeepAliveEdge->getTarget();
          KeepAliveEdge->setTarget(CURecSym);
          KeepAliveAlreadyPresent = true;
          if (NeedsDWARF) {
            LLVM_DEBUG({
              dbgs() << "      Adding keep-alive edge to FDE at "
                     << FDE.getAddress() << "\n";
            });
            B->addEdge(Edge::KeepAlive, 0, FDE, 0);
          }
        } else {
          if (NeedsDWARF)
            return make_error<JITLinkError>(
                "In " + G.getName() + ", compact unwind recard ot " +
                formatv("{0:x}", B->getAddress()) +
                " needs DWARF, but no FDE was found");
        }
      } else {
        if (NeedsDWARF)
          return make_error<JITLinkError>(
              "In " + G.getName() + ", compact unwind recard ot " +
              formatv("{0:x}", B->getAddress()) + " needs DWARF, but no " +
              EHFrameSectionName + " section exists");
      }

      if (!KeepAliveAlreadyPresent) {
        // No FDE edge. We'll need to add a new edge from the function back
        // to the CU record.
        Fn.getBlock().addEdge(Edge::KeepAlive, 0, CURecSym, 0);
      }
    }

    return Error::success();
  }

  /// Process all __compact_unwind records and reserve space for __unwind_info.
  Error processAndReserveUnwindInfo(LinkGraph &G) {
    // Bail out early if no unwind info.
    Section *CUSec = G.findSectionByName(CompactUnwindSectionName);
    if (!CUSec)
      return Error::success();

    // The __LD/__compact_unwind section is only used as input for the linker.
    // We'll create a new __TEXT,__unwind_info section for unwind info output.
    CUSec->setMemLifetime(orc::MemLifetime::NoAlloc);

    // Find / make a mach-header to act as the base for unwind-info offsets
    // (and to report the arch / subarch to libunwind).
    if (auto Err = getOrCreateCompactUnwindBase(G))
      return Err;

    // Error out if there's already unwind-info in the graph: We have no idea
    // how to merge unwind-info sections.
    if (G.findSectionByName(UnwindInfoSectionName))
      return make_error<JITLinkError>("In " + G.getName() + ", " +
                                      UnwindInfoSectionName +
                                      " already exists");

    // Process the __compact_unwind section to build the Records vector that
    // we'll use for writing the __unwind_info section.
    if (auto Err = processCompactUnwind(G, *CUSec))
      return Err;

    // Calculate the size of __unwind_info.
    size_t UnwindInfoSectionSize =
        UnwindInfoSectionHeaderSize +
        Personalities.size() * PersonalityEntrySize +
        (NumSecondLevelPages + 1) * IndexEntrySize + NumLSDAs * LSDAEntrySize +
        NumSecondLevelPages * SecondLevelPageHeaderSize +
        Records.size() * SecondLevelPageEntrySize;

    LLVM_DEBUG({
      dbgs() << "In " << G.getName() << ", reserving "
             << formatv("{0:x}", UnwindInfoSectionSize) << " bytes for "
             << UnwindInfoSectionName << "\n";
    });

    // Create the __unwind_info section and reserve space for it.
    Section &UnwindInfoSec =
        G.createSection(UnwindInfoSectionName, orc::MemProt::Read);

    auto UnwindInfoSectionContent = G.allocateBuffer(UnwindInfoSectionSize);
    memset(UnwindInfoSectionContent.data(), 0, UnwindInfoSectionContent.size());
    auto &B = G.createMutableContentBlock(
        UnwindInfoSec, UnwindInfoSectionContent, orc::ExecutorAddr(), 8, 0);

    // Add Keep-alive edges from the __unwind_info block to all of the target
    // functions.
    for (auto &R : Records)
      B.addEdge(Edge::KeepAlive, 0, *R.Fn, 0);

    return Error::success();
  }

  Error writeUnwindInfo(LinkGraph &G) {
    Section *CUSec = G.findSectionByName(CompactUnwindSectionName);
    if (!CUSec || CUSec->empty())
      return Error::success();

    Section *UnwindInfoSec = G.findSectionByName(UnwindInfoSectionName);
    if (!UnwindInfoSec)
      return make_error<JITLinkError>("In " + G.getName() + ", " +
                                      UnwindInfoSectionName +
                                      " missing after allocation");

    if (UnwindInfoSec->blocks_size() != 1)
      return make_error<JITLinkError>(
          "In " + G.getName() + ", " + UnwindInfoSectionName +
          " contains more than one block post-allocation");

    LLVM_DEBUG(
        { dbgs() << "Writing unwind info for " << G.getName() << "...\n"; });

    mergeRecords();

    auto &UnwindInfoBlock = **UnwindInfoSec->blocks().begin();
    auto Content = UnwindInfoBlock.getMutableContent(G);
    BinaryStreamWriter Writer(
        {reinterpret_cast<uint8_t *>(Content.data()), Content.size()},
        CURecTraits::Endianness);

    // __unwind_info format, from mach-o/compact_unwind_encoding.h on Darwin:
    //
    // #define UNWIND_SECTION_VERSION 1
    // struct unwind_info_section_header
    // {
    //     uint32_t    version;            // UNWIND_SECTION_VERSION
    //     uint32_t    commonEncodingsArraySectionOffset;
    //     uint32_t    commonEncodingsArrayCount;
    //     uint32_t    personalityArraySectionOffset;
    //     uint32_t    personalityArrayCount;
    //     uint32_t    indexSectionOffset;
    //     uint32_t    indexCount;
    //     // compact_unwind_encoding_t[]
    //     // uint32_t personalities[]
    //     // unwind_info_section_header_index_entry[]
    //     // unwind_info_section_header_lsda_index_entry[]
    // };

    if (auto Err = writeHeader(G, Writer))
      return Err;

    // Skip common encodings: JITLink doesn't use them.

    if (auto Err = writePersonalities(G, Writer))
      return Err;

    // Calculate the offset to the LSDAs.
    size_t SectionOffsetToLSDAs =
        Writer.getOffset() + (NumSecondLevelPages + 1) * IndexEntrySize;

    // Calculate offset to the 1st second-level page.
    size_t SectionOffsetToSecondLevelPages =
        SectionOffsetToLSDAs + NumLSDAs * LSDAEntrySize;

    if (auto Err = writeIndexes(G, Writer, SectionOffsetToLSDAs,
                                SectionOffsetToSecondLevelPages))
      return Err;

    if (auto Err = writeLSDAs(G, Writer))
      return Err;

    if (auto Err = writeSecondLevelPages(G, Writer))
      return Err;

    LLVM_DEBUG({
      dbgs() << "    Wrote " << formatv("{0:x}", Writer.getOffset())
             << " bytes of unwind info.\n";
    });

    return Error::success();
  }

private:
  // Calculate the size of unwind-info.
  static constexpr size_t MaxPersonalities = 4;
  static constexpr size_t PersonalityShift = 28;

  static constexpr size_t UnwindInfoSectionHeaderSize = 4 * 7;
  static constexpr size_t PersonalityEntrySize = 4;
  static constexpr size_t IndexEntrySize = 3 * 4;
  static constexpr size_t LSDAEntrySize = 2 * 4;
  static constexpr size_t SecondLevelPageSize = 4096;
  static constexpr size_t SecondLevelPageHeaderSize = 8;
  static constexpr size_t SecondLevelPageEntrySize = 8;
  static constexpr size_t NumRecordsPerSecondLevelPage =
      (SecondLevelPageSize - SecondLevelPageHeaderSize) /
      SecondLevelPageEntrySize;

  struct CompactUnwindRecord {
    Symbol *Fn = nullptr;
    uint32_t Size = 0;
    uint32_t Encoding = 0;
    Symbol *LSDA = nullptr;
    Symbol *FDE = nullptr;
  };

  Error processCompactUnwind(LinkGraph &G, Section &CUSec) {
    // TODO: Reset NumLSDAs, Personalities and CompactUnwindRecords if
    // processing more than once.
    assert(NumLSDAs == 0 && "NumLSDAs should be zero");
    assert(Records.empty() && "CompactUnwindRecords vector should be empty.");
    assert(Personalities.empty() && "Personalities vector should be empty.");

    SmallVector<CompactUnwindRecord> NonUniquedRecords;
    NonUniquedRecords.reserve(CUSec.blocks_size());

    // Process __compact_unwind blocks.
    for (auto *B : CUSec.blocks()) {
      CompactUnwindRecord R;
      R.Encoding = CURecTraits::readEncoding(B->getContent());
      for (auto &E : B->edges()) {
        switch (E.getOffset()) {
        case CURecTraits::FnFieldOffset:
          // This could be the function-pointer, or the FDE keep-alive. Check
          // the type to decide.
          if (E.getKind() == Edge::KeepAlive)
            R.FDE = &E.getTarget();
          else
            R.Fn = &E.getTarget();
          break;
        case CURecTraits::PersonalityFieldOffset: {
          // Add the Personality to the Personalities map and update the
          // encoding.
          size_t PersonalityIdx = 0;
          for (; PersonalityIdx != Personalities.size(); ++PersonalityIdx)
            if (Personalities[PersonalityIdx] == &E.getTarget())
              break;
          if (PersonalityIdx == MaxPersonalities)
            return make_error<JITLinkError>(
                "In " + G.getName() +
                ", __compact_unwind contains too many personalities (max " +
                formatv("{}", MaxPersonalities) + ")");
          if (PersonalityIdx == Personalities.size())
            Personalities.push_back(&E.getTarget());

          R.Encoding |= (PersonalityIdx + 1) << PersonalityShift;
          break;
        }
        case CURecTraits::LSDAFieldOffset:
          ++NumLSDAs;
          R.LSDA = &E.getTarget();
          break;
        default:
          return make_error<JITLinkError>("In " + G.getName() +
                                          ", compact unwind record at " +
                                          formatv("{0:x}", B->getAddress()) +
                                          " has unrecognized edge at offset " +
                                          formatv("{0:x}", E.getOffset()));
        }
      }
      Records.push_back(R);
    }

    // Sort the records into ascending order.
    llvm::sort(Records, [](const CompactUnwindRecord &LHS,
                           const CompactUnwindRecord &RHS) {
      return LHS.Fn->getAddress() < RHS.Fn->getAddress();
    });

    // Calculate the number of second-level pages required.
    NumSecondLevelPages = (Records.size() + NumRecordsPerSecondLevelPage - 1) /
                          NumRecordsPerSecondLevelPage;

    // Convert personality symbols to GOT entry pointers.
    typename CURecTraits::GOTManager GOT(G);
    for (auto &Personality : Personalities)
      Personality = &GOT.getEntryForTarget(G, *Personality);

    LLVM_DEBUG({
      dbgs() << "  In " << G.getName() << ", " << CompactUnwindSectionName
             << ": raw records = " << Records.size()
             << ", personalities = " << Personalities.size()
             << ", lsdas = " << NumLSDAs << "\n";
    });

    return Error::success();
  }

  void mergeRecords() {
    SmallVector<CompactUnwindRecord> NonUniqued = std::move(Records);
    Records.reserve(NonUniqued.size());

    Records.push_back(NonUniqued.front());
    for (size_t I = 1; I != NonUniqued.size(); ++I) {
      auto &Next = NonUniqued[I];
      auto &Last = Records.back();

      bool NextNeedsDWARF = CURecTraits::encodingSpecifiesDWARF(Next.Encoding);
      bool CannotBeMerged = CURecTraits::encodingCannotBeMerged(Next.Encoding);
      if (NextNeedsDWARF || (Next.Encoding != Last.Encoding) ||
          CannotBeMerged || Next.LSDA || Last.LSDA)
        Records.push_back(Next);
    }

    // Recalculate derived values that may have changed.
    NumSecondLevelPages = (Records.size() + NumRecordsPerSecondLevelPage - 1) /
                          NumRecordsPerSecondLevelPage;
  }

  Error writeHeader(LinkGraph &G, BinaryStreamWriter &W) {
    if (!isUInt<32>(NumSecondLevelPages + 1))
      return make_error<JITLinkError>("In " + G.getName() + ", too many " +
                                      UnwindInfoSectionName +
                                      "second-level pages required");

    // Write __unwind_info header.
    size_t IndexArrayOffset = UnwindInfoSectionHeaderSize +
                              Personalities.size() * PersonalityEntrySize;

    cantFail(W.writeInteger<uint32_t>(1));
    cantFail(W.writeInteger<uint32_t>(UnwindInfoSectionHeaderSize));
    cantFail(W.writeInteger<uint32_t>(0));
    cantFail(W.writeInteger<uint32_t>(UnwindInfoSectionHeaderSize));
    cantFail(W.writeInteger<uint32_t>(Personalities.size()));
    cantFail(W.writeInteger<uint32_t>(IndexArrayOffset));
    cantFail(W.writeInteger<uint32_t>(NumSecondLevelPages + 1));

    return Error::success();
  }

  Error writePersonalities(LinkGraph &G, BinaryStreamWriter &W) {
    // Write personalities.
    for (auto *PSym : Personalities) {
      auto Delta = PSym->getAddress() - CompactUnwindBase->getAddress();
      if (!isUInt<32>(Delta))
        return makePersonalityRangeError(G, *PSym);
      cantFail(W.writeInteger<uint32_t>(Delta));
    }
    return Error::success();
  }

  Error writeIndexes(LinkGraph &G, BinaryStreamWriter &W,
                     size_t SectionOffsetToLSDAs,
                     size_t SectionOffsetToSecondLevelPages) {
    // Assume that function deltas are ok in this method -- we'll error
    // check all of them when we write the second level pages.

    // Write the header index entries.
    size_t RecordIdx = 0;
    size_t NumPreviousLSDAs = 0;
    for (auto &R : Records) {
      // If this record marks the start of a new second level page.
      if (RecordIdx % NumRecordsPerSecondLevelPage == 0) {
        auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress();
        auto SecondLevelPageOffset =
            SectionOffsetToSecondLevelPages +
            SecondLevelPageSize * (RecordIdx / NumRecordsPerSecondLevelPage);
        auto LSDAOffset =
            SectionOffsetToLSDAs + NumPreviousLSDAs * LSDAEntrySize;

        cantFail(W.writeInteger<uint32_t>(FnDelta));
        cantFail(W.writeInteger<uint32_t>(SecondLevelPageOffset));
        cantFail(W.writeInteger<uint32_t>(LSDAOffset));
      }
      if (R.LSDA)
        ++NumPreviousLSDAs;
      ++RecordIdx;
    }

    // Write the index array terminator.
    {
      auto FnEndDelta =
          Records.back().Fn->getRange().End - CompactUnwindBase->getAddress();

      if (LLVM_UNLIKELY(!isUInt<32>(FnEndDelta)))
        return make_error<JITLinkError>(
            "In " + G.getName() + " " + UnwindInfoSectionName +
            ", delta to end of functions  " +
            formatv("{0:x}", Records.back().Fn->getRange().End) +
            " exceeds 32 bits");

      cantFail(W.writeInteger<uint32_t>(FnEndDelta));
      cantFail(W.writeInteger<uint32_t>(0));
      cantFail(W.writeInteger<uint32_t>(SectionOffsetToSecondLevelPages));
    }

    return Error::success();
  }

  Error writeLSDAs(LinkGraph &G, BinaryStreamWriter &W) {
    // As with writeIndexes, assume that function deltas are ok for now.
    for (auto &R : Records) {
      if (R.LSDA) {
        auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress();
        auto LSDADelta = R.LSDA->getAddress() - CompactUnwindBase->getAddress();

        if (LLVM_UNLIKELY(!isUInt<32>(LSDADelta)))
          return make_error<JITLinkError>(
              "In " + G.getName() + " " + UnwindInfoSectionName +
              ", delta to lsda at " + formatv("{0:x}", R.LSDA->getAddress()) +
              " exceeds 32 bits");

        cantFail(W.writeInteger<uint32_t>(FnDelta));
        cantFail(W.writeInteger<uint32_t>(LSDADelta));
      }
    }

    return Error::success();
  }

  Error writeSecondLevelPages(LinkGraph &G, BinaryStreamWriter &W) {
    size_t RecordIdx = 0;

    for (auto &R : Records) {
      // When starting a new second-level page, write the page header:
      //
      //   2     : uint32_t    -- UNWIND_SECOND_LEVEL_REGULAR
      //   8     : uint16_t    -- size of second level page table header
      //   count : uint16_t    -- num entries in this second-level page
      if (RecordIdx % NumRecordsPerSecondLevelPage == 0) {
        constexpr uint32_t SecondLevelPageHeaderKind = 2;
        constexpr uint16_t SecondLevelPageHeaderSize = 8;
        uint16_t SecondLevelPageNumEntries =
            std::min(Records.size() - RecordIdx, NumRecordsPerSecondLevelPage);

        cantFail(W.writeInteger<uint32_t>(SecondLevelPageHeaderKind));
        cantFail(W.writeInteger<uint16_t>(SecondLevelPageHeaderSize));
        cantFail(W.writeInteger<uint16_t>(SecondLevelPageNumEntries));
      }

      // Write entry.
      auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress();

      if (LLVM_UNLIKELY(!isUInt<32>(FnDelta)))
        return make_error<JITLinkError>(
            "In " + G.getName() + " " + UnwindInfoSectionName +
            ", delta to function at " + formatv("{0:x}", R.Fn->getAddress()) +
            " exceeds 32 bits");

      auto Encoding = R.Encoding;

      if (LLVM_UNLIKELY(CURecTraits::encodingSpecifiesDWARF(R.Encoding))) {
        if (!EHFrameBase)
          EHFrameBase = SectionRange(R.FDE->getSection()).getStart();
        auto FDEDelta = R.FDE->getAddress() - EHFrameBase;

        if (auto EncodedFDEDelta = CURecTraits::encodeDWARFOffset(FDEDelta))
          Encoding |= *EncodedFDEDelta;
        else
          return make_error<JITLinkError>(
              "In " + G.getName() + " " + UnwindInfoSectionName +
              ", cannot encode delta " + formatv("{0:x}", FDEDelta) +
              " to FDE at " + formatv("{0:x}", R.FDE->getAddress()));
      }

      cantFail(W.writeInteger<uint32_t>(FnDelta));
      cantFail(W.writeInteger<uint32_t>(Encoding));

      ++RecordIdx;
    }

    return Error::success();
  }

  Error getOrCreateCompactUnwindBase(LinkGraph &G) {
    auto Name = G.intern("__jitlink$libunwind_dso_base");
    CompactUnwindBase = G.findAbsoluteSymbolByName(Name);
    if (!CompactUnwindBase) {
      if (auto LocalCUBase = getOrCreateLocalMachOHeader(G)) {
        CompactUnwindBase = &*LocalCUBase;
        auto &B = LocalCUBase->getBlock();
        G.addDefinedSymbol(B, 0, *Name, B.getSize(), Linkage::Strong,
                           Scope::Local, false, true);
      } else
        return LocalCUBase.takeError();
    }
    CompactUnwindBase->setLive(true);
    return Error::success();
  }

  Error makePersonalityRangeError(LinkGraph &G, Symbol &PSym) {
    std::string ErrMsg;
    {
      raw_string_ostream ErrStream(ErrMsg);
      ErrStream << "In " << G.getName() << " " << UnwindInfoSectionName
                << ", personality ";
      if (PSym.hasName())
        ErrStream << PSym.getName() << " ";
      ErrStream << "at " << PSym.getAddress()
                << " is out of 32-bit delta range of compact-unwind base at "
                << CompactUnwindBase->getAddress();
    }
    return make_error<JITLinkError>(std::move(ErrMsg));
  }

  StringRef CompactUnwindSectionName;
  StringRef UnwindInfoSectionName;
  StringRef EHFrameSectionName;
  Symbol *CompactUnwindBase = nullptr;
  orc::ExecutorAddr EHFrameBase;

  size_t NumLSDAs = 0;
  size_t NumSecondLevelPages = 0;
  SmallVector<Symbol *, MaxPersonalities> Personalities;
  SmallVector<CompactUnwindRecord> Records;
};

} // end namespace jitlink
} // end namespace llvm

#undef DEBUG_TYPE

#endif // LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H
