Unverified Commit 6e26246c authored by maksfb's avatar maksfb Committed by GitHub
Browse files

[BOLT][DWARF] Refactor address ranges processing (#71225)

Create BinaryFunction::translateInputToOutputRange() and use it for
updating DWARF debug ranges and location lists while de-duplicating the
existing code. Additionally, move DWARF-specific code out of
BinaryFunction and add print functions to facilitate debugging.

Note that this change is deliberately kept "bug-level" compatible with
the existing solution to keep it NFCI and make it easier to track any
possible regressions in the future updates to the ranges-handling code.
parent 5aa2c65a
Loading
Loading
Loading
Loading
+4 −9
Original line number Diff line number Diff line
@@ -2309,15 +2309,10 @@ public:
  /// removed.
  uint64_t translateInputToOutputAddress(uint64_t Address) const;

  /// Take address ranges corresponding to the input binary and translate
  /// them to address ranges in the output binary.
  DebugAddressRangesVector translateInputToOutputRanges(
      const DWARFAddressRangesVector &InputRanges) const;

  /// Similar to translateInputToOutputRanges() but operates on location lists
  /// and moves associated data to output location lists.
  DebugLocationsVector
  translateInputToOutputLocationList(const DebugLocationsVector &InputLL) const;
  /// Translate a contiguous range of addresses in the input binary into a set
  /// of ranges in the output binary.
  DebugAddressRangesVector
  translateInputToOutputRange(DebugAddressRange InRange) const;

  /// Return true if the function is an AArch64 linker inserted veneer
  bool isAArch64Veneer() const;
+19 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include "llvm/CodeGen/DIE.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/MC/MCDwarf.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdint>
@@ -95,6 +96,12 @@ static inline bool operator<(const DebugAddressRange &LHS,
  return std::tie(LHS.LowPC, LHS.HighPC) < std::tie(RHS.LowPC, RHS.HighPC);
}

inline raw_ostream &operator<<(raw_ostream &OS,
                               const DebugAddressRange &Range) {
  OS << formatv("[{0:x}, {1:x})", Range.LowPC, Range.HighPC);
  return OS;
}

/// DebugAddressRangesVector - represents a set of absolute address ranges.
using DebugAddressRangesVector = SmallVector<DebugAddressRange, 2>;

@@ -106,6 +113,18 @@ struct DebugLocationEntry {
  SmallVector<uint8_t, 4> Expr;
};

inline raw_ostream &operator<<(raw_ostream &OS,
                               const DebugLocationEntry &Entry) {
  OS << formatv("[{0:x}, {1:x}) : [", Entry.LowPC, Entry.HighPC);
  const char *Sep = "";
  for (unsigned Byte : Entry.Expr) {
    OS << Sep << Byte;
    Sep = ", ";
  }
  OS << "]";
  return OS;
}

using DebugLocationsVector = SmallVector<DebugLocationEntry, 4>;

/// References a row in a DWARFDebugLine::LineTable by the DWARF
+67 −157
Original line number Diff line number Diff line
@@ -4251,92 +4251,88 @@ uint64_t BinaryFunction::translateInputToOutputAddress(uint64_t Address) const {
                  BB->getOutputAddressRange().second);
}

DebugAddressRangesVector BinaryFunction::translateInputToOutputRanges(
    const DWARFAddressRangesVector &InputRanges) const {
  DebugAddressRangesVector OutputRanges;
DebugAddressRangesVector
BinaryFunction::translateInputToOutputRange(DebugAddressRange InRange) const {
  DebugAddressRangesVector OutRanges;

  // The function was removed from the output. Return an empty range.
  if (isFolded())
    return OutputRanges;
    return OutRanges;

  // If the function hasn't changed return the same ranges.
  // If the function hasn't changed return the same range.
  if (!isEmitted()) {
    OutputRanges.resize(InputRanges.size());
    llvm::transform(InputRanges, OutputRanges.begin(),
                    [](const DWARFAddressRange &Range) {
                      return DebugAddressRange(Range.LowPC, Range.HighPC);
                    });
    return OutputRanges;
    OutRanges.emplace_back(InRange);
    return OutRanges;
  }

  // Even though we will merge ranges in a post-processing pass, we attempt to
  // merge them in a main processing loop as it improves the processing time.
  uint64_t PrevEndAddress = 0;
  for (const DWARFAddressRange &Range : InputRanges) {
    if (!containsAddress(Range.LowPC)) {
      LLVM_DEBUG(
          dbgs() << "BOLT-DEBUG: invalid debug address range detected for "
                 << *this << " : [0x" << Twine::utohexstr(Range.LowPC) << ", 0x"
                 << Twine::utohexstr(Range.HighPC) << "]\n");
      PrevEndAddress = 0;
      continue;
  if (!containsAddress(InRange.LowPC))
    return OutRanges;

  // Special case of an empty range [X, X). Some tools expect X to be updated.
  if (InRange.LowPC == InRange.HighPC) {
    if (uint64_t NewPC = translateInputToOutputAddress(InRange.LowPC))
      OutRanges.push_back(DebugAddressRange{NewPC, NewPC});
    return OutRanges;
  }
    uint64_t InputOffset = Range.LowPC - getAddress();

  uint64_t InputOffset = InRange.LowPC - getAddress();
  const uint64_t InputEndOffset =
        std::min(Range.HighPC - getAddress(), getSize());
      std::min(InRange.HighPC - getAddress(), getSize());

  auto BBI = llvm::upper_bound(BasicBlockOffsets,
                               BasicBlockOffset(InputOffset, nullptr),
                               CompareBasicBlockOffsets());
    --BBI;
    do {
      const BinaryBasicBlock *BB = BBI->second;
      if (InputOffset < BB->getOffset() || InputOffset >= BB->getEndOffset()) {
  assert(BBI != BasicBlockOffsets.begin());

  // Iterate over blocks in the input order using BasicBlockOffsets.
  for (--BBI; InputOffset < InputEndOffset && BBI != BasicBlockOffsets.end();
       InputOffset = BBI->second->getEndOffset(), ++BBI) {
    const BinaryBasicBlock &BB = *BBI->second;
    if (InputOffset < BB.getOffset() || InputOffset >= BB.getEndOffset()) {
      LLVM_DEBUG(
          dbgs() << "BOLT-DEBUG: invalid debug address range detected for "
                   << *this << " : [0x" << Twine::utohexstr(Range.LowPC)
                   << ", 0x" << Twine::utohexstr(Range.HighPC) << "]\n");
        PrevEndAddress = 0;
                 << *this << " : [0x" << Twine::utohexstr(InRange.LowPC)
                 << ", 0x" << Twine::utohexstr(InRange.HighPC) << "]\n");
      break;
    }

      // Skip the range if the block was deleted.
      if (const uint64_t OutputStart = BB->getOutputAddressRange().first) {
        const uint64_t StartAddress =
            OutputStart + InputOffset - BB->getOffset();
        uint64_t EndAddress = BB->getOutputAddressRange().second;
        if (InputEndOffset < BB->getEndOffset())
          EndAddress = StartAddress + InputEndOffset - InputOffset;
    // Skip the block if it wasn't emitted.
    if (!BB.getOutputAddressRange().first)
      continue;

        if (StartAddress == PrevEndAddress) {
          OutputRanges.back().HighPC =
              std::max(OutputRanges.back().HighPC, EndAddress);
        } else {
          OutputRanges.emplace_back(StartAddress,
                                    std::max(StartAddress, EndAddress));
        }
        PrevEndAddress = OutputRanges.back().HighPC;
      }
    // Find output address for an instruction with an offset greater or equal
    // to /p Offset. The output address should fall within the same basic
    // block boundaries.
    auto translateBlockOffset = [&](const uint64_t Offset) {
      const uint64_t OutAddress = BB.getOutputAddressRange().first + Offset;
      return OutAddress;
    };

      InputOffset = BB->getEndOffset();
      ++BBI;
    } while (InputOffset < InputEndOffset);
  }
    uint64_t OutLowPC = BB.getOutputAddressRange().first;
    if (InputOffset > BB.getOffset())
      OutLowPC = translateBlockOffset(InputOffset - BB.getOffset());

  // Post-processing pass to sort and merge ranges.
  llvm::sort(OutputRanges);
  DebugAddressRangesVector MergedRanges;
  PrevEndAddress = 0;
  for (const DebugAddressRange &Range : OutputRanges) {
    if (Range.LowPC <= PrevEndAddress) {
      MergedRanges.back().HighPC =
          std::max(MergedRanges.back().HighPC, Range.HighPC);
    } else {
      MergedRanges.emplace_back(Range.LowPC, Range.HighPC);
    uint64_t OutHighPC = BB.getOutputAddressRange().second;
    if (InputEndOffset < BB.getEndOffset()) {
      assert(InputEndOffset >= BB.getOffset());
      OutHighPC = translateBlockOffset(InputEndOffset - BB.getOffset());
    }
    PrevEndAddress = MergedRanges.back().HighPC;

    // Check if we can expand the last translated range.
    if (!OutRanges.empty() && OutRanges.back().HighPC == OutLowPC)
      OutRanges.back().HighPC = std::max(OutRanges.back().HighPC, OutHighPC);
    else
      OutRanges.emplace_back(OutLowPC, std::max(OutLowPC, OutHighPC));
  }

  return MergedRanges;
  LLVM_DEBUG({
    dbgs() << "BOLT-DEBUG: translated address range " << InRange << " -> ";
    for (const DebugAddressRange &R : OutRanges)
      dbgs() << R << ' ';
    dbgs() << '\n';
  });

  return OutRanges;
}

MCInst *BinaryFunction::getInstructionAtOffset(uint64_t Offset) {
@@ -4367,92 +4363,6 @@ MCInst *BinaryFunction::getInstructionAtOffset(uint64_t Offset) {
  }
}

DebugLocationsVector BinaryFunction::translateInputToOutputLocationList(
    const DebugLocationsVector &InputLL) const {
  DebugLocationsVector OutputLL;

  if (isFolded())
    return OutputLL;

  // If the function hasn't changed - there's nothing to update.
  if (!isEmitted())
    return InputLL;

  uint64_t PrevEndAddress = 0;
  SmallVectorImpl<uint8_t> *PrevExpr = nullptr;
  for (const DebugLocationEntry &Entry : InputLL) {
    const uint64_t Start = Entry.LowPC;
    const uint64_t End = Entry.HighPC;
    if (!containsAddress(Start)) {
      LLVM_DEBUG(dbgs() << "BOLT-DEBUG: invalid debug address range detected "
                           "for "
                        << *this << " : [0x" << Twine::utohexstr(Start)
                        << ", 0x" << Twine::utohexstr(End) << "]\n");
      continue;
    }
    uint64_t InputOffset = Start - getAddress();
    const uint64_t InputEndOffset = std::min(End - getAddress(), getSize());
    auto BBI = llvm::upper_bound(BasicBlockOffsets,
                                 BasicBlockOffset(InputOffset, nullptr),
                                 CompareBasicBlockOffsets());
    --BBI;
    do {
      const BinaryBasicBlock *BB = BBI->second;
      if (InputOffset < BB->getOffset() || InputOffset >= BB->getEndOffset()) {
        LLVM_DEBUG(dbgs() << "BOLT-DEBUG: invalid debug address range detected "
                             "for "
                          << *this << " : [0x" << Twine::utohexstr(Start)
                          << ", 0x" << Twine::utohexstr(End) << "]\n");
        PrevEndAddress = 0;
        break;
      }

      // Skip the range if the block was deleted.
      if (const uint64_t OutputStart = BB->getOutputAddressRange().first) {
        const uint64_t StartAddress =
            OutputStart + InputOffset - BB->getOffset();
        uint64_t EndAddress = BB->getOutputAddressRange().second;
        if (InputEndOffset < BB->getEndOffset())
          EndAddress = StartAddress + InputEndOffset - InputOffset;

        if (StartAddress == PrevEndAddress && Entry.Expr == *PrevExpr) {
          OutputLL.back().HighPC = std::max(OutputLL.back().HighPC, EndAddress);
        } else {
          OutputLL.emplace_back(DebugLocationEntry{
              StartAddress, std::max(StartAddress, EndAddress), Entry.Expr});
        }
        PrevEndAddress = OutputLL.back().HighPC;
        PrevExpr = &OutputLL.back().Expr;
      }

      ++BBI;
      InputOffset = BB->getEndOffset();
    } while (InputOffset < InputEndOffset);
  }

  // Sort and merge adjacent entries with identical location.
  llvm::stable_sort(
      OutputLL, [](const DebugLocationEntry &A, const DebugLocationEntry &B) {
        return A.LowPC < B.LowPC;
      });
  DebugLocationsVector MergedLL;
  PrevEndAddress = 0;
  PrevExpr = nullptr;
  for (const DebugLocationEntry &Entry : OutputLL) {
    if (Entry.LowPC <= PrevEndAddress && *PrevExpr == Entry.Expr) {
      MergedLL.back().HighPC = std::max(Entry.HighPC, MergedLL.back().HighPC);
    } else {
      const uint64_t Begin = std::max(Entry.LowPC, PrevEndAddress);
      const uint64_t End = std::max(Begin, Entry.HighPC);
      MergedLL.emplace_back(DebugLocationEntry{Begin, End, Entry.Expr});
    }
    PrevEndAddress = MergedLL.back().HighPC;
    PrevExpr = &MergedLL.back().Expr;
  }

  return MergedLL;
}

void BinaryFunction::printLoopInfo(raw_ostream &OS) const {
  if (!opts::shouldPrint(*this))
    return;
+92 −2
Original line number Diff line number Diff line
@@ -88,6 +88,96 @@ static void printDie(DWARFUnit &DU, uint64_t DIEOffset) {
  }
}

using namespace bolt;

/// Take a set of DWARF address ranges corresponding to the input binary and
/// translate them to a set of address ranges in the output binary.
static DebugAddressRangesVector
translateInputToOutputRanges(const BinaryFunction &BF,
                             const DWARFAddressRangesVector &InputRanges) {
  DebugAddressRangesVector OutputRanges;

  // If the function hasn't changed return the same ranges.
  if (!BF.isEmitted()) {
    OutputRanges.resize(InputRanges.size());
    llvm::transform(InputRanges, OutputRanges.begin(),
                    [](const DWARFAddressRange &Range) {
                      return DebugAddressRange(Range.LowPC, Range.HighPC);
                    });
    return OutputRanges;
  }

  for (const DWARFAddressRange &Range : InputRanges)
    llvm::append_range(OutputRanges, BF.translateInputToOutputRange(
                                         {Range.LowPC, Range.HighPC}));

  // Post-processing pass to sort and merge ranges.
  llvm::sort(OutputRanges);
  DebugAddressRangesVector MergedRanges;
  uint64_t PrevHighPC = 0;
  for (const DebugAddressRange &Range : OutputRanges) {
    if (Range.LowPC <= PrevHighPC) {
      MergedRanges.back().HighPC =
          std::max(MergedRanges.back().HighPC, Range.HighPC);
    } else {
      MergedRanges.emplace_back(Range.LowPC, Range.HighPC);
    }
    PrevHighPC = MergedRanges.back().HighPC;
  }

  return MergedRanges;
}

/// Similar to translateInputToOutputRanges() but operates on location lists.
static DebugLocationsVector
translateInputToOutputLocationList(const BinaryFunction &BF,
                                   const DebugLocationsVector &InputLL) {
  DebugLocationsVector OutputLL;

  // If the function hasn't changed - there's nothing to update.
  if (!BF.isEmitted())
    return InputLL;

  for (const DebugLocationEntry &Entry : InputLL) {
    DebugAddressRangesVector OutRanges =
        BF.translateInputToOutputRange({Entry.LowPC, Entry.HighPC});
    if (!OutRanges.empty() && !OutputLL.empty()) {
      if (OutRanges.front().LowPC == OutputLL.back().HighPC &&
          Entry.Expr == OutputLL.back().Expr) {
        OutputLL.back().HighPC =
            std::max(OutputLL.back().HighPC, OutRanges.front().HighPC);
        OutRanges.erase(OutRanges.begin());
      }
    }
    llvm::transform(OutRanges, std::back_inserter(OutputLL),
                    [&Entry](const DebugAddressRange &R) {
                      return DebugLocationEntry{R.LowPC, R.HighPC, Entry.Expr};
                    });
  }

  // Sort and merge adjacent entries with identical locations.
  llvm::stable_sort(
      OutputLL, [](const DebugLocationEntry &A, const DebugLocationEntry &B) {
        return A.LowPC < B.LowPC;
      });
  DebugLocationsVector MergedLL;
  uint64_t PrevHighPC = 0;
  const SmallVectorImpl<uint8_t> *PrevExpr = nullptr;
  for (const DebugLocationEntry &Entry : OutputLL) {
    if (Entry.LowPC <= PrevHighPC && *PrevExpr == Entry.Expr) {
      MergedLL.back().HighPC = std::max(Entry.HighPC, MergedLL.back().HighPC);
    } else {
      const uint64_t Begin = std::max(Entry.LowPC, PrevHighPC);
      const uint64_t End = std::max(Begin, Entry.HighPC);
      MergedLL.emplace_back(DebugLocationEntry{Begin, End, Entry.Expr});
    }
    PrevHighPC = MergedLL.back().HighPC;
    PrevExpr = &MergedLL.back().Expr;
  }

  return MergedLL;
}

namespace llvm {
namespace bolt {
/// Emits debug information into .debug_info or .debug_types section.
@@ -861,7 +951,7 @@ void DWARFRewriter::updateUnitDebugInfo(
              : nullptr;
      DebugAddressRangesVector OutputRanges;
      if (Function) {
        OutputRanges = Function->translateInputToOutputRanges(*RangesOrError);
        OutputRanges = translateInputToOutputRanges(*Function, *RangesOrError);
        LLVM_DEBUG(if (OutputRanges.empty() != RangesOrError->empty()) {
          dbgs() << "BOLT-DEBUG: problem with DIE at 0x"
                 << Twine::utohexstr(Die->getOffset()) << " in CU at 0x"
@@ -1022,7 +1112,7 @@ void DWARFRewriter::updateUnitDebugInfo(
            DebugLocationsVector OutputLL;
            if (const BinaryFunction *Function =
                    BC.getBinaryFunctionContainingAddress(Address)) {
              OutputLL = Function->translateInputToOutputLocationList(InputLL);
              OutputLL = translateInputToOutputLocationList(*Function, InputLL);
              LLVM_DEBUG(if (OutputLL.empty()) {
                dbgs() << "BOLT-DEBUG: location list translated to an empty "
                          "one at 0x"