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

via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 3 13:20:17 PDT 2023


https://github.com/maksfb created https://github.com/llvm/llvm-project/pull/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.

>From c85b4c9ef261f0854195a0d4997b71d5d84a7d13 Mon Sep 17 00:00:00 2001
From: Maksim Panchenko <maks at fb.com>
Date: Tue, 31 Oct 2023 11:26:08 -0700
Subject: [PATCH] [BOLT][DWARF] Refactor address ranges processing

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.
---
 bolt/include/bolt/Core/BinaryFunction.h |  13 +-
 bolt/include/bolt/Core/DebugData.h      |  19 ++
 bolt/lib/Core/BinaryFunction.cpp        | 224 +++++++-----------------
 bolt/lib/Rewrite/DWARFRewriter.cpp      |  94 +++++++++-
 4 files changed, 182 insertions(+), 168 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index c67ddccbf4892a7..003de6fd2bfa8f2 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -2309,15 +2309,10 @@ class BinaryFunction {
   /// 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;
diff --git a/bolt/include/bolt/Core/DebugData.h b/bolt/include/bolt/Core/DebugData.h
index 3fdc0534c000ae9..a2776c1e5ae884e 100644
--- a/bolt/include/bolt/Core/DebugData.h
+++ b/bolt/include/bolt/Core/DebugData.h
@@ -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
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 2e1fa739e53f20b..973a45877a7003f 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -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;
+  }
+
+  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;
   }
 
-  // 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)) {
+  uint64_t InputOffset = InRange.LowPC - getAddress();
+  const uint64_t InputEndOffset =
+      std::min(InRange.HighPC - getAddress(), getSize());
+
+  auto BBI = llvm::upper_bound(BasicBlockOffsets,
+                               BasicBlockOffset(InputOffset, nullptr),
+                               CompareBasicBlockOffsets());
+  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;
-      continue;
+                 << *this << " : [0x" << Twine::utohexstr(InRange.LowPC)
+                 << ", 0x" << Twine::utohexstr(InRange.HighPC) << "]\n");
+      break;
     }
-    uint64_t InputOffset = Range.LowPC - getAddress();
-    const uint64_t InputEndOffset =
-        std::min(Range.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()) {
-        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;
-        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) {
-          OutputRanges.back().HighPC =
-              std::max(OutputRanges.back().HighPC, EndAddress);
-        } else {
-          OutputRanges.emplace_back(StartAddress,
-                                    std::max(StartAddress, EndAddress));
-        }
-        PrevEndAddress = OutputRanges.back().HighPC;
-      }
+    // Skip the block if it wasn't emitted.
+    if (!BB.getOutputAddressRange().first)
+      continue;
 
-      InputOffset = BB->getEndOffset();
-      ++BBI;
-    } while (InputOffset < InputEndOffset);
-  }
+    // 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;
+    };
 
-  // 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 OutLowPC = BB.getOutputAddressRange().first;
+    if (InputOffset > BB.getOffset())
+      OutLowPC = translateBlockOffset(InputOffset - BB.getOffset());
+
+    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;
diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp
index 0ac8e3956b90f61..3d561e86812d131 100644
--- a/bolt/lib/Rewrite/DWARFRewriter.cpp
+++ b/bolt/lib/Rewrite/DWARFRewriter.cpp
@@ -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"



More information about the llvm-commits mailing list