[llvm] [BOLT] Fix debug line emission for functions in multiple compilation units (PR #151230)

Grigory Pastukhov via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 31 10:46:00 PDT 2025


https://github.com/grigorypas updated https://github.com/llvm/llvm-project/pull/151230

>From 74747d14bade85c631b685c77004ac2a32fd0899 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Thu, 24 Jul 2025 13:50:13 -0700
Subject: [PATCH 1/7] Change DwarfUnit field to vector in BinaryFunction

---
 bolt/include/bolt/Core/BinaryFunction.h | 27 ++++++---
 bolt/lib/Core/BinaryContext.cpp         | 38 +++++++-----
 bolt/lib/Core/BinaryEmitter.cpp         | 80 ++++++++++++++-----------
 bolt/lib/Core/BinaryFunction.cpp        | 13 +++-
 4 files changed, 95 insertions(+), 63 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index ae580520b9110..966559e0c6fa6 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -423,8 +423,8 @@ class BinaryFunction {
   /// Original LSDA type encoding
   unsigned LSDATypeEncoding{dwarf::DW_EH_PE_omit};
 
-  /// Containing compilation unit for the function.
-  DWARFUnit *DwarfUnit{nullptr};
+  /// All compilation units this function belongs to.
+  SmallVector<DWARFUnit *, 1> DwarfUnitVec;
 
   /// Last computed hash value. Note that the value could be recomputed using
   /// different parameters by every pass.
@@ -2414,15 +2414,24 @@ class BinaryFunction {
   void
   computeBlockHashes(HashFunction HashFunction = HashFunction::Default) const;
 
-  void setDWARFUnit(DWARFUnit *Unit) { DwarfUnit = Unit; }
+  void addDWARFUnit(DWARFUnit *Unit) { DwarfUnitVec.push_back(Unit); }
 
-  /// Return DWARF compile unit for this function.
-  DWARFUnit *getDWARFUnit() const { return DwarfUnit; }
+  void removeDWARFUnit(DWARFUnit *Unit) {
+    auto *It = std::find(DwarfUnitVec.begin(), DwarfUnitVec.end(), Unit);
+    // If found, erase it
+    if (It != DwarfUnitVec.end()) {
+      DwarfUnitVec.erase(It);
+    }
+  }
+
+  /// Return DWARF compile units for this function.
+  const SmallVector<DWARFUnit *, 1> getDWARFUnits() const {
+    return DwarfUnitVec;
+  }
 
-  /// Return line info table for this function.
-  const DWARFDebugLine::LineTable *getDWARFLineTable() const {
-    return getDWARFUnit() ? BC.DwCtx->getLineTableForUnit(getDWARFUnit())
-                          : nullptr;
+  const DWARFDebugLine::LineTable *
+  getDWARFLineTableForUnit(DWARFUnit *Unit) const {
+    return BC.DwCtx->getLineTableForUnit(Unit);
   }
 
   /// Finalize profile for the function.
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 84f1853469709..c58d99a77f8b3 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1697,22 +1697,35 @@ void BinaryContext::preprocessDebugInfo() {
 
     auto It = llvm::partition_point(
         AllRanges, [=](CURange R) { return R.HighPC <= FunctionAddress; });
-    if (It != AllRanges.end() && It->LowPC <= FunctionAddress)
-      Function.setDWARFUnit(It->Unit);
+    if (It == AllRanges.end() || It->LowPC > FunctionAddress) {
+      continue;
+    }
+    Function.addDWARFUnit(It->Unit);
+
+    // Go forward and add all units from ranges that cover the function
+    while (++It != AllRanges.end()) {
+      if (It->LowPC <= FunctionAddress && FunctionAddress < It->HighPC) {
+        Function.addDWARFUnit(It->Unit);
+      } else {
+        break;
+      }
+    }
   }
 
   // Discover units with debug info that needs to be updated.
   for (const auto &KV : BinaryFunctions) {
     const BinaryFunction &BF = KV.second;
-    if (shouldEmit(BF) && BF.getDWARFUnit())
-      ProcessedCUs.insert(BF.getDWARFUnit());
+    if (shouldEmit(BF) && !BF.getDWARFUnits().empty())
+      for (const DWARFUnit *Unit : BF.getDWARFUnits())
+        ProcessedCUs.insert(Unit);
   }
-
   // Clear debug info for functions from units that we are not going to process.
   for (auto &KV : BinaryFunctions) {
     BinaryFunction &BF = KV.second;
-    if (BF.getDWARFUnit() && !ProcessedCUs.count(BF.getDWARFUnit()))
-      BF.setDWARFUnit(nullptr);
+    for (auto *Unit : BF.getDWARFUnits()) {
+      if (!ProcessedCUs.count(Unit))
+        BF.removeDWARFUnit(Unit);
+    }
   }
 
   if (opts::Verbosity >= 1) {
@@ -1912,14 +1925,9 @@ static void printDebugInfo(raw_ostream &OS, const MCInst &Instruction,
   if (RowRef == DebugLineTableRowRef::NULL_ROW)
     return;
 
-  const DWARFDebugLine::LineTable *LineTable;
-  if (Function && Function->getDWARFUnit() &&
-      Function->getDWARFUnit()->getOffset() == RowRef.DwCompileUnitIndex) {
-    LineTable = Function->getDWARFLineTable();
-  } else {
-    LineTable = DwCtx->getLineTableForUnit(
-        DwCtx->getCompileUnitForOffset(RowRef.DwCompileUnitIndex));
-  }
+  const DWARFDebugLine::LineTable *LineTable = DwCtx->getLineTableForUnit(
+      DwCtx->getCompileUnitForOffset(RowRef.DwCompileUnitIndex));
+
   assert(LineTable && "line table expected for instruction with debug info");
 
   const DWARFDebugLine::Row &Row = LineTable->Rows[RowRef.RowIndex - 1];
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 7b5cd276fee89..34bda7403d259 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -177,7 +177,8 @@ class BinaryEmitter {
   /// Note that it does not automatically result in the insertion of the EOS
   /// marker in the line table program, but provides one to the DWARF generator
   /// when it needs it.
-  void emitLineInfoEnd(const BinaryFunction &BF, MCSymbol *FunctionEndSymbol);
+  void emitLineInfoEnd(const BinaryFunction &BF, MCSymbol *FunctionEndSymbol,
+                       DWARFUnit *Unit);
 
   /// Emit debug line info for unprocessed functions from CUs that include
   /// emitted functions.
@@ -436,8 +437,9 @@ bool BinaryEmitter::emitFunction(BinaryFunction &Function,
     Streamer.emitELFSize(StartSymbol, SizeExpr);
   }
 
-  if (opts::UpdateDebugSections && Function.getDWARFUnit())
-    emitLineInfoEnd(Function, EndSymbol);
+  // TODO: Emit line info end for all the CUs that contain the function.
+  if (opts::UpdateDebugSections && !Function.getDWARFUnits().empty())
+    emitLineInfoEnd(Function, EndSymbol, Function.getDWARFUnits().front());
 
   // Exception handling info for the function.
   emitLSDA(Function, FF);
@@ -486,7 +488,7 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
         // A symbol to be emitted before the instruction to mark its location.
         MCSymbol *InstrLabel = BC.MIB->getInstLabel(Instr);
 
-        if (opts::UpdateDebugSections && BF.getDWARFUnit()) {
+        if (opts::UpdateDebugSections && !BF.getDWARFUnits().empty()) {
           LastLocSeen = emitLineInfo(BF, Instr.getLoc(), LastLocSeen,
                                      FirstInstr, InstrLabel);
           FirstInstr = false;
@@ -679,8 +681,10 @@ void BinaryEmitter::emitConstantIslands(BinaryFunction &BF, bool EmitColdPart,
 SMLoc BinaryEmitter::emitLineInfo(const BinaryFunction &BF, SMLoc NewLoc,
                                   SMLoc PrevLoc, bool FirstInstr,
                                   MCSymbol *&InstrLabel) {
-  DWARFUnit *FunctionCU = BF.getDWARFUnit();
-  const DWARFDebugLine::LineTable *FunctionLineTable = BF.getDWARFLineTable();
+  // TODO: implment emitting into line tables corresponding to multiple CUs
+  DWARFUnit *FunctionCU = BF.getDWARFUnits().front();
+  const DWARFDebugLine::LineTable *FunctionLineTable =
+      BF.getDWARFLineTableForUnit(FunctionCU);
   assert(FunctionCU && "cannot emit line info for function without CU");
 
   DebugLineTableRowRef RowReference = DebugLineTableRowRef::fromSMLoc(NewLoc);
@@ -740,13 +744,13 @@ SMLoc BinaryEmitter::emitLineInfo(const BinaryFunction &BF, SMLoc NewLoc,
 }
 
 void BinaryEmitter::emitLineInfoEnd(const BinaryFunction &BF,
-                                    MCSymbol *FunctionEndLabel) {
-  DWARFUnit *FunctionCU = BF.getDWARFUnit();
-  assert(FunctionCU && "DWARF unit expected");
+                                    MCSymbol *FunctionEndLabel,
+                                    DWARFUnit *Unit) {
+  assert(Unit && "DWARF unit expected");
   BC.Ctx->setCurrentDwarfLoc(0, 0, 0, DWARF2_FLAG_END_SEQUENCE, 0, 0);
   const MCDwarfLoc &DwarfLoc = BC.Ctx->getCurrentDwarfLoc();
   BC.Ctx->clearDwarfLocSeen();
-  BC.getDwarfLineTable(FunctionCU->getOffset())
+  BC.getDwarfLineTable(Unit->getOffset())
       .getMCLineSections()
       .addLineEntry(MCDwarfLineEntry(FunctionEndLabel, DwarfLoc),
                     Streamer.getCurrentSectionOnly());
@@ -1115,36 +1119,40 @@ void BinaryEmitter::emitDebugLineInfoForOriginalFunctions() {
     if (Function.isEmitted())
       continue;
 
-    const DWARFDebugLine::LineTable *LineTable = Function.getDWARFLineTable();
-    if (!LineTable)
-      continue; // nothing to update for this function
+    // Loop through all CUs in the function
+    for (DWARFUnit *Unit : Function.getDWARFUnits()) {
+      const DWARFDebugLine::LineTable *LineTable =
+          Function.getDWARFLineTableForUnit(Unit);
+      if (!LineTable)
+        continue; // nothing to update for this unit
+
+      const uint64_t Address = Function.getAddress();
+      std::vector<uint32_t> Results;
+      if (!LineTable->lookupAddressRange(
+              {Address, object::SectionedAddress::UndefSection},
+              Function.getSize(), Results))
+        continue;
 
-    const uint64_t Address = Function.getAddress();
-    std::vector<uint32_t> Results;
-    if (!LineTable->lookupAddressRange(
-            {Address, object::SectionedAddress::UndefSection},
-            Function.getSize(), Results))
-      continue;
+      if (Results.empty())
+        continue;
 
-    if (Results.empty())
-      continue;
+      // The first row returned could be the last row matching the start
+      // address. Find the first row with the same address that is not the end
+      // of the sequence.
+      uint64_t FirstRow = Results.front();
+      while (FirstRow > 0) {
+        const DWARFDebugLine::Row &PrevRow = LineTable->Rows[FirstRow - 1];
+        if (PrevRow.Address.Address != Address || PrevRow.EndSequence)
+          break;
+        --FirstRow;
+      }
 
-    // The first row returned could be the last row matching the start address.
-    // Find the first row with the same address that is not the end of the
-    // sequence.
-    uint64_t FirstRow = Results.front();
-    while (FirstRow > 0) {
-      const DWARFDebugLine::Row &PrevRow = LineTable->Rows[FirstRow - 1];
-      if (PrevRow.Address.Address != Address || PrevRow.EndSequence)
-        break;
-      --FirstRow;
+      const uint64_t EndOfSequenceAddress =
+          Function.getAddress() + Function.getMaxSize();
+      BC.getDwarfLineTable(Unit->getOffset())
+          .addLineTableSequence(LineTable, FirstRow, Results.back(),
+                                EndOfSequenceAddress);
     }
-
-    const uint64_t EndOfSequenceAddress =
-        Function.getAddress() + Function.getMaxSize();
-    BC.getDwarfLineTable(Function.getDWARFUnit()->getOffset())
-        .addLineTableSequence(LineTable, FirstRow, Results.back(),
-                              EndOfSequenceAddress);
   }
 
   // For units that are completely unprocessed, use original debug line contents
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index eec68ff5a5fce..bbe04a17c0ad3 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1496,9 +1496,16 @@ Error BinaryFunction::disassemble() {
     }
 
 add_instruction:
-    if (getDWARFLineTable()) {
-      Instruction.setLoc(findDebugLineInformationForInstructionAt(
-          AbsoluteInstrAddr, getDWARFUnit(), getDWARFLineTable()));
+    // TODO: Handle multiple DWARF compilation units properly.
+    // For now, use the first unit if available.
+    if (!getDWARFUnits().empty()) {
+      DWARFUnit *FirstUnit = getDWARFUnits().front();
+      const DWARFDebugLine::LineTable *LineTable =
+          getDWARFLineTableForUnit(FirstUnit);
+      if (LineTable) {
+        Instruction.setLoc(findDebugLineInformationForInstructionAt(
+            AbsoluteInstrAddr, FirstUnit, LineTable));
+      }
     }
 
     // Record offset of the instruction for profile matching.

>From 885937d5529f3ba2b047d8658ed85aa34b515ffe Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Thu, 24 Jul 2025 20:30:10 -0700
Subject: [PATCH 2/7] Implemented multiple rows per instruction logic

---
 bolt/include/bolt/Core/BinaryContext.h   |   6 +
 bolt/include/bolt/Core/DebugData.h       | 116 +++++++++++++----
 bolt/lib/Core/BinaryContext.cpp          |  27 ++--
 bolt/lib/Core/BinaryEmitter.cpp          | 136 ++++++++++++--------
 bolt/lib/Core/BinaryFunction.cpp         |  58 ++++-----
 bolt/lib/Core/DebugData.cpp              |   2 -
 bolt/test/Inputs/multi-cu-common.h       |  10 ++
 bolt/test/Inputs/multi-cu-file1.c        |   9 ++
 bolt/test/Inputs/multi-cu-file2.c        |   8 ++
 bolt/test/Inputs/process-debug-line.sh   | 101 +++++++++++++++
 bolt/test/X86/multi-cu-debug-line.test   | 108 ++++++++++++++++
 bolt/test/perf2bolt/Inputs/perf_test.lds |  11 +-
 bolt/unittests/Core/CMakeLists.txt       |   1 +
 bolt/unittests/Core/ClusteredRows.cpp    | 152 +++++++++++++++++++++++
 14 files changed, 621 insertions(+), 124 deletions(-)
 create mode 100644 bolt/test/Inputs/multi-cu-common.h
 create mode 100644 bolt/test/Inputs/multi-cu-file1.c
 create mode 100644 bolt/test/Inputs/multi-cu-file2.c
 create mode 100755 bolt/test/Inputs/process-debug-line.sh
 create mode 100644 bolt/test/X86/multi-cu-debug-line.test
 create mode 100644 bolt/unittests/Core/ClusteredRows.cpp

diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index 91ecf89da618c..48bc9a5d1f92c 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -288,6 +288,12 @@ class BinaryContext {
   /// overwritten, but it is okay to re-generate debug info for them.
   std::set<const DWARFUnit *> ProcessedCUs;
 
+  /// DWARF-related container to manage lifecycle of groups of rows from line
+  /// tables associated with instructions. Since binary functions can span
+  /// multiple compilation units, instructions may reference debug line
+  /// information from multiple CUs.
+  ClasteredRowsContainer ClasteredRows;
+
   // Setup MCPlus target builder
   void initializeTarget(std::unique_ptr<MCPlusBuilder> TargetBuilder) {
     MIB = std::move(TargetBuilder);
diff --git a/bolt/include/bolt/Core/DebugData.h b/bolt/include/bolt/Core/DebugData.h
index 6ea3b1af1024f..048594946d8a9 100644
--- a/bolt/include/bolt/Core/DebugData.h
+++ b/bolt/include/bolt/Core/DebugData.h
@@ -135,8 +135,6 @@ struct DebugLineTableRowRef {
   uint32_t DwCompileUnitIndex;
   uint32_t RowIndex;
 
-  const static DebugLineTableRowRef NULL_ROW;
-
   bool operator==(const DebugLineTableRowRef &Rhs) const {
     return DwCompileUnitIndex == Rhs.DwCompileUnitIndex &&
            RowIndex == Rhs.RowIndex;
@@ -145,24 +143,6 @@ struct DebugLineTableRowRef {
   bool operator!=(const DebugLineTableRowRef &Rhs) const {
     return !(*this == Rhs);
   }
-
-  static DebugLineTableRowRef fromSMLoc(const SMLoc &Loc) {
-    union {
-      decltype(Loc.getPointer()) Ptr;
-      DebugLineTableRowRef Ref;
-    } U;
-    U.Ptr = Loc.getPointer();
-    return U.Ref;
-  }
-
-  SMLoc toSMLoc() const {
-    union {
-      decltype(SMLoc().getPointer()) Ptr;
-      DebugLineTableRowRef Ref;
-    } U;
-    U.Ref = *this;
-    return SMLoc::getFromPointer(U.Ptr);
-  }
 };
 
 /// Common buffer vector used for debug info handling.
@@ -210,7 +190,7 @@ class DebugRangesSectionWriter {
   static bool classof(const DebugRangesSectionWriter *Writer) {
     return Writer->getKind() == RangesWriterKind::DebugRangesWriter;
   }
-  
+
   /// Append a range to the main buffer.
   void appendToRangeBuffer(const DebugBufferVector &CUBuffer);
 
@@ -852,6 +832,100 @@ class DwarfLineTable {
   // Returns DWARF Version for this line table.
   uint16_t getDwarfVersion() const { return DwarfVersion; }
 };
+
+/// ClusteredRows represents a collection of debug line table row references.
+/// Since a Binary function can belong to multiple compilation units (CUs),
+/// a single MCInst can have multiple debug line table rows associated with it
+/// from different CUs. This class manages such clustered row references.
+///
+/// MEMORY LAYOUT AND DESIGN:
+/// This class uses a flexible array member pattern to store all
+/// DebugLineTableRowRef elements in a single contiguous memory allocation.
+/// The memory layout is:
+///
+/// +------------------+
+/// | ClusteredRows    |  <- Object header (Size + first element)
+/// | - Size           |
+/// | - Raws (element) |  <- First DebugLineTableRowRef element
+/// +------------------+
+/// | element[1]       |  <- Additional DebugLineTableRowRef elements
+/// | element[2]       |     stored immediately after the object
+/// | ...              |
+/// | element[Size-1]  |
+/// +------------------+
+///
+/// PERFORMANCE BENEFITS:
+/// - Single memory allocation: All elements are stored in one contiguous block,
+///   eliminating the need for separate heap allocations for the array.
+/// - No extra dereferencing: Elements are accessed directly via pointer
+///   arithmetic (beginPtr() + offset) rather than through an additional
+///   pointer indirection.
+/// - Cache locality: All elements are guaranteed to be adjacent in memory,
+///   improving cache performance during iteration.
+/// - Memory efficiency: No overhead from separate pointer storage or
+///   fragmented allocations.
+///
+/// The 'Raws' member serves as both the first element storage and the base
+/// address for pointer arithmetic to access subsequent elements.
+class ClusteredRows {
+public:
+  ArrayRef<DebugLineTableRowRef> getRows() const {
+    return ArrayRef<DebugLineTableRowRef>(beginPtrConst(), Size);
+  }
+  uint64_t size() const { return Size; }
+  static const ClusteredRows *fromSMLoc(const SMLoc &Loc) {
+    return reinterpret_cast<const ClusteredRows *>(Loc.getPointer());
+  }
+  SMLoc toSMLoc() const {
+    return SMLoc::getFromPointer(reinterpret_cast<const char *>(this));
+  }
+
+  template <typename T> void populate(const T Vec) {
+    assert(Vec.size() == Size && "");
+    DebugLineTableRowRef *CurRawPtr = beginPtr();
+    for (DebugLineTableRowRef RowRef : Vec) {
+      *CurRawPtr = RowRef;
+      ++CurRawPtr;
+    }
+  }
+
+private:
+  uint64_t Size;
+  DebugLineTableRowRef Raws;
+
+  ClusteredRows(uint64_t Size) : Size(Size) {}
+  static uint64_t getTotalSize(uint64_t Size) {
+    assert(Size > 0 && "Size must be greater than 0");
+    return sizeof(ClusteredRows) + (Size - 1) * sizeof(DebugLineTableRowRef);
+  }
+  const DebugLineTableRowRef *beginPtrConst() const {
+    return reinterpret_cast<const DebugLineTableRowRef *>(&Raws);
+  }
+  DebugLineTableRowRef *beginPtr() {
+    return reinterpret_cast<DebugLineTableRowRef *>(&Raws);
+  }
+
+  friend class ClasteredRowsContainer;
+};
+
+/// ClasteredRowsContainer manages the lifecycle of ClusteredRows objects.
+class ClasteredRowsContainer {
+public:
+  ClusteredRows *createClusteredRows(uint64_t Size) {
+    auto *CR = new (std::malloc(ClusteredRows::getTotalSize(Size)))
+        ClusteredRows(Size);
+    Clusters.push_back(CR);
+    return CR;
+  }
+  ~ClasteredRowsContainer() {
+    for (auto *CR : Clusters)
+      std::free(CR);
+  }
+
+private:
+  std::vector<ClusteredRows *> Clusters;
+};
+
 } // namespace bolt
 } // namespace llvm
 
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index c58d99a77f8b3..1766b0540a5cd 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1568,23 +1568,21 @@ unsigned BinaryContext::addDebugFilenameToUnit(const uint32_t DestCUID,
   DWARFCompileUnit *SrcUnit = DwCtx->getCompileUnitForOffset(SrcCUID);
   const DWARFDebugLine::LineTable *LineTable =
       DwCtx->getLineTableForUnit(SrcUnit);
-  const std::vector<DWARFDebugLine::FileNameEntry> &FileNames =
-      LineTable->Prologue.FileNames;
+  const DWARFDebugLine::FileNameEntry &FileNameEntry =
+      LineTable->Prologue.getFileNameEntry(FileIndex);
   // Dir indexes start at 1, as DWARF file numbers, and a dir index 0
   // means empty dir.
-  assert(FileIndex > 0 && FileIndex <= FileNames.size() &&
-         "FileIndex out of range for the compilation unit.");
   StringRef Dir = "";
-  if (FileNames[FileIndex - 1].DirIdx != 0) {
+  if (FileNameEntry.DirIdx != 0) {
     if (std::optional<const char *> DirName = dwarf::toString(
             LineTable->Prologue
-                .IncludeDirectories[FileNames[FileIndex - 1].DirIdx - 1])) {
+                .IncludeDirectories[FileNameEntry.DirIdx - 1])) {
       Dir = *DirName;
     }
   }
   StringRef FileName = "";
   if (std::optional<const char *> FName =
-          dwarf::toString(FileNames[FileIndex - 1].Name))
+          dwarf::toString(FileNameEntry.Name))
     FileName = *FName;
   assert(FileName != "");
   DWARFCompileUnit *DstUnit = DwCtx->getCompileUnitForOffset(DestCUID);
@@ -1920,20 +1918,25 @@ bool BinaryContext::isMarker(const SymbolRef &Symbol) const {
 static void printDebugInfo(raw_ostream &OS, const MCInst &Instruction,
                            const BinaryFunction *Function,
                            DWARFContext *DwCtx) {
-  DebugLineTableRowRef RowRef =
-      DebugLineTableRowRef::fromSMLoc(Instruction.getLoc());
-  if (RowRef == DebugLineTableRowRef::NULL_ROW)
+  const ClusteredRows *LineTableRows =
+      ClusteredRows::fromSMLoc(Instruction.getLoc());
+  if (LineTableRows == nullptr)
     return;
 
+  // File name and line number should be the same for all CUs.
+  // So it is sufficient to check the first one.
+  DebugLineTableRowRef RowRef = LineTableRows->getRows().front();
   const DWARFDebugLine::LineTable *LineTable = DwCtx->getLineTableForUnit(
       DwCtx->getCompileUnitForOffset(RowRef.DwCompileUnitIndex));
 
-  assert(LineTable && "line table expected for instruction with debug info");
+  if (!LineTable)
+    return;
 
   const DWARFDebugLine::Row &Row = LineTable->Rows[RowRef.RowIndex - 1];
   StringRef FileName = "";
+
   if (std::optional<const char *> FName =
-          dwarf::toString(LineTable->Prologue.FileNames[Row.File - 1].Name))
+          dwarf::toString(LineTable->Prologue.getFileNameEntry(Row.File).Name))
     FileName = *FName;
   OS << " # debug line " << FileName << ":" << Row.Line;
   if (Row.Column)
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 34bda7403d259..8862f0680cb7e 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -437,9 +437,9 @@ bool BinaryEmitter::emitFunction(BinaryFunction &Function,
     Streamer.emitELFSize(StartSymbol, SizeExpr);
   }
 
-  // TODO: Emit line info end for all the CUs that contain the function.
   if (opts::UpdateDebugSections && !Function.getDWARFUnits().empty())
-    emitLineInfoEnd(Function, EndSymbol, Function.getDWARFUnits().front());
+    for (DWARFUnit *Unit : Function.getDWARFUnits())
+      emitLineInfoEnd(Function, EndSymbol, Unit);
 
   // Exception handling info for the function.
   emitLSDA(Function, FF);
@@ -681,64 +681,92 @@ void BinaryEmitter::emitConstantIslands(BinaryFunction &BF, bool EmitColdPart,
 SMLoc BinaryEmitter::emitLineInfo(const BinaryFunction &BF, SMLoc NewLoc,
                                   SMLoc PrevLoc, bool FirstInstr,
                                   MCSymbol *&InstrLabel) {
-  // TODO: implment emitting into line tables corresponding to multiple CUs
-  DWARFUnit *FunctionCU = BF.getDWARFUnits().front();
-  const DWARFDebugLine::LineTable *FunctionLineTable =
-      BF.getDWARFLineTableForUnit(FunctionCU);
-  assert(FunctionCU && "cannot emit line info for function without CU");
-
-  DebugLineTableRowRef RowReference = DebugLineTableRowRef::fromSMLoc(NewLoc);
-
-  // Check if no new line info needs to be emitted.
-  if (RowReference == DebugLineTableRowRef::NULL_ROW ||
+  if (NewLoc.getPointer() == nullptr ||
       NewLoc.getPointer() == PrevLoc.getPointer())
     return PrevLoc;
+  const ClusteredRows *Cluster = ClusteredRows::fromSMLoc(NewLoc);
+
+  auto addToLineTable = [&](DebugLineTableRowRef RowReference,
+                            const DWARFUnit *TargetCU, unsigned Flags,
+                            MCSymbol *InstrLabel,
+                            const DWARFDebugLine::Row &CurrentRow) {
+    const uint64_t TargetUnitIndex = TargetCU->getOffset();
+    unsigned TargetFilenum = CurrentRow.File;
+    const uint32_t CurrentUnitIndex = RowReference.DwCompileUnitIndex;
+    // If the CU id from the current instruction location does not
+    // match the target CU id, it means that we have come across some
+    // inlined code (by BOLT).  We must look up the CU for the instruction's
+    // original function and get the line table from that.
+    if (TargetUnitIndex != CurrentUnitIndex) {
+      // Add filename from the inlined function to the current CU.
+      TargetFilenum = BC.addDebugFilenameToUnit(
+          TargetUnitIndex, CurrentUnitIndex, CurrentRow.File);
+    }
+    BC.Ctx->setCurrentDwarfLoc(TargetFilenum, CurrentRow.Line,
+                               CurrentRow.Column, Flags, CurrentRow.Isa,
+                               CurrentRow.Discriminator);
+    const MCDwarfLoc &DwarfLoc = BC.Ctx->getCurrentDwarfLoc();
+    BC.Ctx->clearDwarfLocSeen();
+    auto &MapLineEntries = BC.getDwarfLineTable(TargetUnitIndex)
+                               .getMCLineSections()
+                               .getMCLineEntries();
+    const auto *It = MapLineEntries.find(Streamer.getCurrentSectionOnly());
+    auto NewLineEntry = MCDwarfLineEntry(InstrLabel, DwarfLoc);
+
+    // Check if line table exists and has entries before doing comparison
+    if (It != MapLineEntries.end() && !It->second.empty()) {
+      // Check if the new line entry has the same debug info as the last one
+      // to avoid duplicates. We don't compare labels since different
+      // instructions can have the same line info.
+      const auto &LastEntry = It->second.back();
+      if (LastEntry.getFileNum() == NewLineEntry.getFileNum() &&
+          LastEntry.getLine() == NewLineEntry.getLine() &&
+          LastEntry.getColumn() == NewLineEntry.getColumn() &&
+          LastEntry.getFlags() == NewLineEntry.getFlags() &&
+          LastEntry.getIsa() == NewLineEntry.getIsa() &&
+          LastEntry.getDiscriminator() == NewLineEntry.getDiscriminator())
+        return;
+    }
 
-  unsigned CurrentFilenum = 0;
-  const DWARFDebugLine::LineTable *CurrentLineTable = FunctionLineTable;
-
-  // If the CU id from the current instruction location does not
-  // match the CU id from the current function, it means that we
-  // have come across some inlined code.  We must look up the CU
-  // for the instruction's original function and get the line table
-  // from that.
-  const uint64_t FunctionUnitIndex = FunctionCU->getOffset();
-  const uint32_t CurrentUnitIndex = RowReference.DwCompileUnitIndex;
-  if (CurrentUnitIndex != FunctionUnitIndex) {
-    CurrentLineTable = BC.DwCtx->getLineTableForUnit(
-        BC.DwCtx->getCompileUnitForOffset(CurrentUnitIndex));
-    // Add filename from the inlined function to the current CU.
-    CurrentFilenum = BC.addDebugFilenameToUnit(
-        FunctionUnitIndex, CurrentUnitIndex,
-        CurrentLineTable->Rows[RowReference.RowIndex - 1].File);
-  }
-
-  const DWARFDebugLine::Row &CurrentRow =
-      CurrentLineTable->Rows[RowReference.RowIndex - 1];
-  if (!CurrentFilenum)
-    CurrentFilenum = CurrentRow.File;
-
-  unsigned Flags = (DWARF2_FLAG_IS_STMT * CurrentRow.IsStmt) |
-                   (DWARF2_FLAG_BASIC_BLOCK * CurrentRow.BasicBlock) |
-                   (DWARF2_FLAG_PROLOGUE_END * CurrentRow.PrologueEnd) |
-                   (DWARF2_FLAG_EPILOGUE_BEGIN * CurrentRow.EpilogueBegin);
-
-  // Always emit is_stmt at the beginning of function fragment.
-  if (FirstInstr)
-    Flags |= DWARF2_FLAG_IS_STMT;
-
-  BC.Ctx->setCurrentDwarfLoc(CurrentFilenum, CurrentRow.Line, CurrentRow.Column,
-                             Flags, CurrentRow.Isa, CurrentRow.Discriminator);
-  const MCDwarfLoc &DwarfLoc = BC.Ctx->getCurrentDwarfLoc();
-  BC.Ctx->clearDwarfLocSeen();
+    BC.getDwarfLineTable(TargetUnitIndex)
+        .getMCLineSections()
+        .addLineEntry(NewLineEntry, Streamer.getCurrentSectionOnly());
+  };
 
   if (!InstrLabel)
     InstrLabel = BC.Ctx->createTempSymbol();
-
-  BC.getDwarfLineTable(FunctionUnitIndex)
-      .getMCLineSections()
-      .addLineEntry(MCDwarfLineEntry(InstrLabel, DwarfLoc),
-                    Streamer.getCurrentSectionOnly());
+  for (DebugLineTableRowRef RowReference : Cluster->getRows()) {
+    const DWARFDebugLine::LineTable *CurrentLineTable =
+        BC.DwCtx->getLineTableForUnit(
+            BC.DwCtx->getCompileUnitForOffset(RowReference.DwCompileUnitIndex));
+    const DWARFDebugLine::Row &CurrentRow =
+        CurrentLineTable->Rows[RowReference.RowIndex - 1];
+    unsigned Flags = (DWARF2_FLAG_IS_STMT * CurrentRow.IsStmt) |
+                     (DWARF2_FLAG_BASIC_BLOCK * CurrentRow.BasicBlock) |
+                     (DWARF2_FLAG_PROLOGUE_END * CurrentRow.PrologueEnd) |
+                     (DWARF2_FLAG_EPILOGUE_BEGIN * CurrentRow.EpilogueBegin);
+
+    // Always emit is_stmt at the beginning of function fragment.
+    if (FirstInstr)
+      Flags |= DWARF2_FLAG_IS_STMT;
+    const auto &FunctionDwarfUnits = BF.getDWARFUnits();
+    const auto *It = std::find_if(
+        FunctionDwarfUnits.begin(), FunctionDwarfUnits.end(),
+        [RowReference](const DWARFUnit *Unit) {
+          return Unit->getOffset() == RowReference.DwCompileUnitIndex;
+        });
+    if (It != FunctionDwarfUnits.end()) {
+      addToLineTable(RowReference, *It, Flags, InstrLabel, CurrentRow);
+      continue;
+    }
+    // This rows is from CU that did not contain the original function.
+    // This might happen if BOLT moved/inlined that instruction from other CUs.
+    // In this case, we need to insert it to all CUs that the function
+    // originally beloned to.
+    for (const DWARFUnit *Unit : BF.getDWARFUnits()) {
+      addToLineTable(RowReference, Unit, Flags, InstrLabel, CurrentRow);
+    }
+  }
 
   return NewLoc;
 }
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index bbe04a17c0ad3..a3a6b31451441 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -179,37 +179,29 @@ template <typename R> static bool emptyRange(const R &Range) {
 }
 
 /// Gets debug line information for the instruction located at the given
-/// address in the original binary. The SMLoc's pointer is used
-/// to point to this information, which is represented by a
-/// DebugLineTableRowRef. The returned pointer is null if no debug line
-/// information for this instruction was found.
-static SMLoc findDebugLineInformationForInstructionAt(
+/// address in the original binary. Returns an optional DebugLineTableRowRef
+/// that references the corresponding row in the DWARF line table. Since binary
+/// functions can span multiple compilation units, this function helps
+/// associate instructions with their debug line information from the
+/// appropriate CU. Returns std::nullopt if no debug line information for
+/// this instruction was found.
+static std::optional<DebugLineTableRowRef>
+findDebugLineInformationForInstructionAt(
     uint64_t Address, DWARFUnit *Unit,
     const DWARFDebugLine::LineTable *LineTable) {
-  // We use the pointer in SMLoc to store an instance of DebugLineTableRowRef,
-  // which occupies 64 bits. Thus, we can only proceed if the struct fits into
-  // the pointer itself.
-  static_assert(
-      sizeof(decltype(SMLoc().getPointer())) >= sizeof(DebugLineTableRowRef),
-      "Cannot fit instruction debug line information into SMLoc's pointer");
-
-  SMLoc NullResult = DebugLineTableRowRef::NULL_ROW.toSMLoc();
   uint32_t RowIndex = LineTable->lookupAddress(
       {Address, object::SectionedAddress::UndefSection});
   if (RowIndex == LineTable->UnknownRowIndex)
-    return NullResult;
+    return std::nullopt;
 
   assert(RowIndex < LineTable->Rows.size() &&
          "Line Table lookup returned invalid index.");
 
-  decltype(SMLoc().getPointer()) Ptr;
-  DebugLineTableRowRef *InstructionLocation =
-      reinterpret_cast<DebugLineTableRowRef *>(&Ptr);
-
-  InstructionLocation->DwCompileUnitIndex = Unit->getOffset();
-  InstructionLocation->RowIndex = RowIndex + 1;
+  DebugLineTableRowRef InstructionLocation;
+  InstructionLocation.DwCompileUnitIndex = Unit->getOffset();
+  InstructionLocation.RowIndex = RowIndex + 1;
 
-  return SMLoc::getFromPointer(Ptr);
+  return InstructionLocation;
 }
 
 static std::string buildSectionName(StringRef Prefix, StringRef Name,
@@ -1496,15 +1488,23 @@ Error BinaryFunction::disassemble() {
     }
 
 add_instruction:
-    // TODO: Handle multiple DWARF compilation units properly.
-    // For now, use the first unit if available.
     if (!getDWARFUnits().empty()) {
-      DWARFUnit *FirstUnit = getDWARFUnits().front();
-      const DWARFDebugLine::LineTable *LineTable =
-          getDWARFLineTableForUnit(FirstUnit);
-      if (LineTable) {
-        Instruction.setLoc(findDebugLineInformationForInstructionAt(
-            AbsoluteInstrAddr, FirstUnit, LineTable));
+      SmallVector<DebugLineTableRowRef, 1> Rows;
+      for (DWARFUnit *Unit : getDWARFUnits()) {
+        const DWARFDebugLine::LineTable *LineTable =
+            getDWARFLineTableForUnit(Unit);
+        if (!LineTable)
+          continue;
+        if (std::optional<DebugLineTableRowRef> RowRef =
+                findDebugLineInformationForInstructionAt(AbsoluteInstrAddr,
+                                                         Unit, LineTable))
+          Rows.emplace_back(*RowRef);
+      }
+      if (!Rows.empty()) {
+        ClusteredRows *Cluster =
+            BC.ClasteredRows.createClusteredRows(Rows.size());
+        Cluster->populate(Rows);
+        Instruction.setLoc(Cluster->toSMLoc());
       }
     }
 
diff --git a/bolt/lib/Core/DebugData.cpp b/bolt/lib/Core/DebugData.cpp
index 521eb8d91bbc0..e05f28f08572c 100644
--- a/bolt/lib/Core/DebugData.cpp
+++ b/bolt/lib/Core/DebugData.cpp
@@ -101,8 +101,6 @@ std::optional<AttrInfo> findAttributeInfo(const DWARFDie DIE,
   return findAttributeInfo(DIE, AbbrevDecl, *Index);
 }
 
-const DebugLineTableRowRef DebugLineTableRowRef::NULL_ROW{0, 0};
-
 LLVM_ATTRIBUTE_UNUSED
 static void printLE64(const std::string &S) {
   for (uint32_t I = 0, Size = S.size(); I < Size; ++I) {
diff --git a/bolt/test/Inputs/multi-cu-common.h b/bolt/test/Inputs/multi-cu-common.h
new file mode 100644
index 0000000000000..aeb8076305dce
--- /dev/null
+++ b/bolt/test/Inputs/multi-cu-common.h
@@ -0,0 +1,10 @@
+#ifndef MULTI_CU_COMMON_H
+#define MULTI_CU_COMMON_H
+
+static inline int common_inline_function(int x) {
+  int result = x * 2;
+  result += 10;
+  return result;
+}
+
+#endif // MULTI_CU_COMMON_H
diff --git a/bolt/test/Inputs/multi-cu-file1.c b/bolt/test/Inputs/multi-cu-file1.c
new file mode 100644
index 0000000000000..f3528b2acddb8
--- /dev/null
+++ b/bolt/test/Inputs/multi-cu-file1.c
@@ -0,0 +1,9 @@
+#include "multi-cu-common.h"
+#include <stdio.h>
+
+int main() {
+  int value = 5;
+  int result = common_inline_function(value);
+  printf("File1: Result is %d\n", result);
+  return 0;
+}
diff --git a/bolt/test/Inputs/multi-cu-file2.c b/bolt/test/Inputs/multi-cu-file2.c
new file mode 100644
index 0000000000000..f33af72595afe
--- /dev/null
+++ b/bolt/test/Inputs/multi-cu-file2.c
@@ -0,0 +1,8 @@
+#include "multi-cu-common.h"
+#include <stdio.h>
+
+void helper_function() {
+  int value = 10;
+  int result = common_inline_function(value);
+  printf("File2: Helper result is %d\n", result);
+}
diff --git a/bolt/test/Inputs/process-debug-line.sh b/bolt/test/Inputs/process-debug-line.sh
new file mode 100755
index 0000000000000..b30408df922eb
--- /dev/null
+++ b/bolt/test/Inputs/process-debug-line.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+# Script to process llvm-dwarfdump --debug-line output and create a normalized table
+# Usage: process-debug-line.sh <debug-line.txt>
+#
+# Output format: CU_FILE LINE COLUMN FILE_NAME [additional_info]
+# This strips addresses to make rows unique and adds context about which CU and file each line belongs to
+
+if [ $# -ne 1 ]; then
+    echo "Usage: $0 <debug-line.txt>" >&2
+    exit 1
+fi
+
+debug_line_file="$1"
+
+if [ ! -f "$debug_line_file" ]; then
+    echo "Error: File '$debug_line_file' not found" >&2
+    exit 1
+fi
+
+awk '
+BEGIN {
+    cu_count = 0
+    current_cu_file = ""
+    # Initialize file names array
+    for (i = 0; i < 100; i++) current_file_names[i] = ""
+}
+
+# Track debug_line sections (new CU)
+/^debug_line\[/ {
+    cu_count++
+    current_cu_file = ""
+    # Clear file names array for new CU
+    for (i = 0; i < 100; i++) current_file_names[i] = ""
+    next
+}
+
+# Capture file names and their indices
+/^file_names\[.*\]:/ {
+    # Extract file index using more portable regex
+    if (match($0, /file_names\[[[:space:]]*([0-9]+)\]:/, arr)) {
+        file_index = arr[1]
+    } else {
+        # Fallback parsing
+        gsub(/file_names\[/, "", $0)
+        gsub(/\]:.*/, "", $0)
+        gsub(/[[:space:]]/, "", $0)
+        file_index = $0
+    }
+
+    getline  # Read the next line which contains the actual filename
+    if (match($0, /name:[[:space:]]*"([^"]*)"/, name_arr)) {
+        filename = name_arr[1]
+        current_file_names[file_index] = filename
+
+        # Extract basename for main CU file (first .c/.cpp/.cc file we see)
+        if (current_cu_file == "" && match(filename, /([^\/]*\.(c|cpp|cc))$/, cu_arr)) {
+            current_cu_file = cu_arr[1]
+        }
+    }
+    next
+}
+
+# Process line table entries
+/^0x[0-9a-f]+/ {
+    # Parse the line entry: Address Line Column File ISA Discriminator OpIndex Flags
+    if (NF >= 4) {
+        line = $2
+        column = $3
+        file_index = $4
+
+        # Get the filename for this file index
+        filename = current_file_names[file_index]
+        if (filename == "") {
+            filename = "UNKNOWN_FILE_" file_index
+        } else {
+            # Extract just the basename using portable method
+            if (match(filename, /([^\/]*)$/, basename_arr)) {
+                filename = basename_arr[1]
+            } else {
+                # Fallback: use gsub
+                gsub(/.*\//, "", filename)
+            }
+        }
+
+        # Build additional info (flags, etc.)
+        additional_info = ""
+        for (i = 8; i <= NF; i++) {
+            if (additional_info != "") additional_info = additional_info " "
+            additional_info = additional_info $i
+        }
+
+        # Output normalized row: CU_FILE LINE COLUMN FILE_NAME [additional_info]
+        printf "%s %s %s %s", current_cu_file, line, column, filename
+        if (additional_info != "") {
+            printf " %s", additional_info
+        }
+        printf "\n"
+    }
+}
+' "$debug_line_file"
diff --git a/bolt/test/X86/multi-cu-debug-line.test b/bolt/test/X86/multi-cu-debug-line.test
new file mode 100644
index 0000000000000..430b281445db5
--- /dev/null
+++ b/bolt/test/X86/multi-cu-debug-line.test
@@ -0,0 +1,108 @@
+## Test that BOLT correctly handles debug line information for functions
+## that belong to multiple compilation units (e.g., inline functions in
+## common header files). The test covers two scenarios:
+## 1. Normal processing: .debug_line section shows lines for the function 
+##    in all CUs where it was compiled, with no duplicate rows within CUs
+## 2. Functions not processed: When BOLT doesn't process functions (using 
+##    --funcs with nonexistent function), original debug info is preserved
+
+# REQUIRES: system-linux
+
+## Compile test files with debug info
+# RUN: %clang %cflags -O0 -g %S/../Inputs/multi-cu-file1.c %S/../Inputs/multi-cu-file2.c \
+# RUN:   -I%S/../Inputs -o %t.exe -Wl,-q
+
+## Test 1: Normal BOLT processing (functions are processed/optimized)
+# RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections
+# RUN: llvm-dwarfdump --debug-line %t.bolt > %t.debug-line.txt
+# RUN: FileCheck %s --check-prefix=BASIC --input-file %t.debug-line.txt
+
+## Check that debug line information is present for both compilation units
+# BASIC: debug_line[{{.*}}]
+# BASIC: file_names[{{.*}}]:
+# BASIC: name: "{{.*}}multi-cu-file1.c"
+# BASIC: debug_line[{{.*}}]
+# BASIC: file_names[{{.*}}]:
+# BASIC: name: "{{.*}}multi-cu-file2.c"
+
+## Use our helper script to create a normalized table without addresses
+# RUN: %S/../Inputs/process-debug-line.sh %t.debug-line.txt > %t.normalized-debug-line.txt
+# RUN: FileCheck %s --check-prefix=NORMALIZED --input-file %t.normalized-debug-line.txt
+
+## Check that we have line entries for the inline function (lines 5, 6, 7) from multi-cu-common.h
+## in both compilation units
+# NORMALIZED: multi-cu-file1.c 5 {{[0-9]+}} multi-cu-common.h
+# NORMALIZED: multi-cu-file1.c 6 {{[0-9]+}} multi-cu-common.h
+# NORMALIZED: multi-cu-file1.c 7 {{[0-9]+}} multi-cu-common.h
+# NORMALIZED: multi-cu-file2.c 5 {{[0-9]+}} multi-cu-common.h
+# NORMALIZED: multi-cu-file2.c 6 {{[0-9]+}} multi-cu-common.h
+# NORMALIZED: multi-cu-file2.c 7 {{[0-9]+}} multi-cu-common.h
+
+## Verify that we have line entries for the inline function in multiple CUs
+## by checking that the header file appears multiple times in different contexts
+# RUN: grep -c "multi-cu-common.h" %t.debug-line.txt > %t.header-count.txt
+# RUN: FileCheck %s --check-prefix=MULTI-CU --input-file %t.header-count.txt
+
+## The header should appear in debug line info for multiple CUs
+# MULTI-CU: {{[2-9]|[1-9][0-9]+}}
+
+## Check that there are no duplicate line table rows within the same CU
+## This verifies the fix for the bug where duplicate entries were created
+# RUN: sort %t.normalized-debug-line.txt | uniq -c | \
+# RUN:   awk '$1 > 1 {print "DUPLICATE_ROW: " $0}' > %t.duplicates.txt
+# RUN: FileCheck %s --check-prefix=NO-DUPLICATES --input-file %t.duplicates.txt --allow-empty
+
+## Should have no duplicate normalized rows (file should be empty)
+## Note: Cross-CU duplicates are expected and valid (same function in different CUs)
+## but within-CU duplicates would indicate a bug
+# NO-DUPLICATES-NOT: DUPLICATE_ROW
+
+## Test 2: Functions not processed by BOLT (using --funcs with nonexistent function)
+## This tests the code path where BOLT preserves original debug info
+# RUN: llvm-bolt %t.exe -o %t.not-emitted.bolt --update-debug-sections --funcs=nonexistent_function
+# RUN: llvm-dwarfdump --debug-line %t.not-emitted.bolt > %t.not-emitted.debug-line.txt
+# RUN: FileCheck %s --check-prefix=PRESERVED-BASIC --input-file %t.not-emitted.debug-line.txt
+
+## Check that debug line information is still present for both compilation units when functions aren't processed
+# PRESERVED-BASIC: debug_line[{{.*}}]
+# PRESERVED-BASIC: file_names[{{.*}}]:
+# PRESERVED-BASIC: name: "{{.*}}multi-cu-file1.c"
+# PRESERVED-BASIC: debug_line[{{.*}}]
+# PRESERVED-BASIC: file_names[{{.*}}]:
+# PRESERVED-BASIC: name: "{{.*}}multi-cu-file2.c"
+
+## Create normalized output for the not-emitted case
+# RUN: %S/../Inputs/process-debug-line.sh %t.not-emitted.debug-line.txt > %t.not-emitted.normalized.txt
+# RUN: FileCheck %s --check-prefix=PRESERVED-NORMALIZED --input-file %t.not-emitted.normalized.txt
+
+## Check that we have line entries for the inline function (lines 5, 6, 7) from multi-cu-common.h
+## in both compilation units (preserved from original)
+# PRESERVED-NORMALIZED: multi-cu-file1.c 5 {{[0-9]+}} multi-cu-common.h
+# PRESERVED-NORMALIZED: multi-cu-file1.c 6 {{[0-9]+}} multi-cu-common.h
+# PRESERVED-NORMALIZED: multi-cu-file1.c 7 {{[0-9]+}} multi-cu-common.h
+# PRESERVED-NORMALIZED: multi-cu-file2.c 5 {{[0-9]+}} multi-cu-common.h
+# PRESERVED-NORMALIZED: multi-cu-file2.c 6 {{[0-9]+}} multi-cu-common.h
+# PRESERVED-NORMALIZED: multi-cu-file2.c 7 {{[0-9]+}} multi-cu-common.h
+
+## Verify that we have line entries for the inline function in multiple CUs (preserved)
+## by checking that the header file appears multiple times in different contexts
+# RUN: grep -c "multi-cu-common.h" %t.not-emitted.debug-line.txt > %t.preserved-header-count.txt
+# RUN: FileCheck %s --check-prefix=PRESERVED-MULTI-CU --input-file %t.preserved-header-count.txt
+
+## The header should appear in debug line info for multiple CUs (preserved from original)
+# PRESERVED-MULTI-CU: {{[2-9]|[1-9][0-9]+}}
+
+## Check that original debug info is preserved for main functions
+# RUN: grep "multi-cu-file1.c.*multi-cu-file1.c" %t.not-emitted.normalized.txt > %t.preserved-main.txt
+# RUN: FileCheck %s --check-prefix=PRESERVED-MAIN --input-file %t.preserved-main.txt
+
+# PRESERVED-MAIN: multi-cu-file1.c {{[0-9]+}} {{[0-9]+}} multi-cu-file1.c
+
+## Check that original debug info is preserved for file2 functions
+# RUN: grep "multi-cu-file2.c.*multi-cu-file2.c" %t.not-emitted.normalized.txt > %t.preserved-file2.txt
+# RUN: FileCheck %s --check-prefix=PRESERVED-FILE2 --input-file %t.preserved-file2.txt
+
+# PRESERVED-FILE2: multi-cu-file2.c {{[0-9]+}} {{[0-9]+}} multi-cu-file2.c
+
+## Note: We do not check for duplicates in Test 2 since we are preserving original debug info as-is
+## and the original may contain patterns that would be flagged as duplicates by our normalization
\ No newline at end of file
diff --git a/bolt/test/perf2bolt/Inputs/perf_test.lds b/bolt/test/perf2bolt/Inputs/perf_test.lds
index 66d925a05bebc..c2704d73a638c 100644
--- a/bolt/test/perf2bolt/Inputs/perf_test.lds
+++ b/bolt/test/perf2bolt/Inputs/perf_test.lds
@@ -1,13 +1,12 @@
 SECTIONS {
-  . = SIZEOF_HEADERS;
+  . = 0x400000 + SIZEOF_HEADERS;
   .interp : { *(.interp) }
   .note.gnu.build-id : { *(.note.gnu.build-id) }
-  . = 0x212e8;
   .dynsym         : { *(.dynsym) }
-  . = 0x31860;
+  . = 0x801000;
   .text : { *(.text*) }
-  . = 0x41c20;
+  . = 0x803000;
   .fini_array : { *(.fini_array) }
-  . = 0x54e18;
+  . = 0x805000;
   .data : { *(.data) }
-}
\ No newline at end of file
+}
diff --git a/bolt/unittests/Core/CMakeLists.txt b/bolt/unittests/Core/CMakeLists.txt
index 54e8ea10cda12..538add9baa798 100644
--- a/bolt/unittests/Core/CMakeLists.txt
+++ b/bolt/unittests/Core/CMakeLists.txt
@@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_bolt_unittest(CoreTests
   BinaryContext.cpp
+  ClusteredRows.cpp
   MCPlusBuilder.cpp
   MemoryMaps.cpp
   DynoStats.cpp
diff --git a/bolt/unittests/Core/ClusteredRows.cpp b/bolt/unittests/Core/ClusteredRows.cpp
new file mode 100644
index 0000000000000..5901f9ac5aaaa
--- /dev/null
+++ b/bolt/unittests/Core/ClusteredRows.cpp
@@ -0,0 +1,152 @@
+//===- bolt/unittest/Core/ClusteredRows.cpp ------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "bolt/Core/DebugData.h"
+#include "llvm/Support/SMLoc.h"
+#include "gtest/gtest.h"
+#include <vector>
+
+using namespace llvm;
+using namespace llvm::bolt;
+
+namespace {
+
+class ClusteredRowsTest : public ::testing::Test {
+protected:
+  void SetUp() override {
+    Container = std::make_unique<ClasteredRowsContainer>();
+  }
+
+  std::unique_ptr<ClasteredRowsContainer> Container;
+};
+
+TEST_F(ClusteredRowsTest, CreateSingleElement) {
+  ClusteredRows *CR = Container->createClusteredRows(1);
+  ASSERT_NE(CR, nullptr);
+  EXPECT_EQ(CR->size(), 1u);
+
+  // Test population with single element
+  std::vector<DebugLineTableRowRef> TestRefs = {
+      {42, 100}
+  };
+  CR->populate(TestRefs);
+
+  ArrayRef<DebugLineTableRowRef> Rows = CR->getRows();
+  EXPECT_EQ(Rows.size(), 1u);
+  EXPECT_EQ(Rows[0].DwCompileUnitIndex, 42u);
+  EXPECT_EQ(Rows[0].RowIndex, 100u);
+}
+
+TEST_F(ClusteredRowsTest, CreateMultipleElements) {
+  ClusteredRows *CR = Container->createClusteredRows(3);
+  ASSERT_NE(CR, nullptr);
+  EXPECT_EQ(CR->size(), 3u);
+
+  // Test population with multiple elements
+  std::vector<DebugLineTableRowRef> TestRefs = {
+      {10, 20},
+      {30, 40},
+      {50, 60}
+  };
+  CR->populate(TestRefs);
+
+  ArrayRef<DebugLineTableRowRef> Rows = CR->getRows();
+  EXPECT_EQ(Rows.size(), 3u);
+
+  EXPECT_EQ(Rows[0].DwCompileUnitIndex, 10u);
+  EXPECT_EQ(Rows[0].RowIndex, 20u);
+
+  EXPECT_EQ(Rows[1].DwCompileUnitIndex, 30u);
+  EXPECT_EQ(Rows[1].RowIndex, 40u);
+
+  EXPECT_EQ(Rows[2].DwCompileUnitIndex, 50u);
+  EXPECT_EQ(Rows[2].RowIndex, 60u);
+}
+
+TEST_F(ClusteredRowsTest, SMLoc_Conversion) {
+  ClusteredRows *CR = Container->createClusteredRows(2);
+  ASSERT_NE(CR, nullptr);
+
+  // Test SMLoc conversion
+  SMLoc Loc = CR->toSMLoc();
+  EXPECT_TRUE(Loc.isValid());
+
+  // Test round-trip conversion
+  const ClusteredRows *CR2 = ClusteredRows::fromSMLoc(Loc);
+  EXPECT_EQ(CR, CR2);
+  EXPECT_EQ(CR2->size(), 2u);
+}
+
+TEST_F(ClusteredRowsTest, PopulateWithArrayRef) {
+  ClusteredRows *CR = Container->createClusteredRows(4);
+  ASSERT_NE(CR, nullptr);
+
+  // Test population with ArrayRef
+  DebugLineTableRowRef TestArray[] = {
+      {1, 2},
+      {3, 4},
+      {5, 6},
+      {7, 8}
+  };
+  ArrayRef<DebugLineTableRowRef> TestRefs(TestArray, 4);
+  CR->populate(TestRefs);
+
+  ArrayRef<DebugLineTableRowRef> Rows = CR->getRows();
+  EXPECT_EQ(Rows.size(), 4u);
+
+  for (size_t i = 0; i < 4; ++i) {
+    EXPECT_EQ(Rows[i].DwCompileUnitIndex, TestArray[i].DwCompileUnitIndex);
+    EXPECT_EQ(Rows[i].RowIndex, TestArray[i].RowIndex);
+  }
+}
+
+TEST_F(ClusteredRowsTest, MultipleClusteredRows) {
+  // Test creating multiple ClusteredRows objects
+  ClusteredRows *CR1 = Container->createClusteredRows(2);
+  ClusteredRows *CR2 = Container->createClusteredRows(3);
+  ClusteredRows *CR3 = Container->createClusteredRows(1);
+
+  ASSERT_NE(CR1, nullptr);
+  ASSERT_NE(CR2, nullptr);
+  ASSERT_NE(CR3, nullptr);
+
+  // Ensure they are different objects
+  EXPECT_NE(CR1, CR2);
+  EXPECT_NE(CR2, CR3);
+  EXPECT_NE(CR1, CR3);
+
+  // Verify sizes
+  EXPECT_EQ(CR1->size(), 2u);
+  EXPECT_EQ(CR2->size(), 3u);
+  EXPECT_EQ(CR3->size(), 1u);
+
+  // Populate each with different data
+  std::vector<DebugLineTableRowRef> TestRefs1 = {{100, 200}, {300, 400}};
+  std::vector<DebugLineTableRowRef> TestRefs2 = {{10, 20}, {30, 40}, {50, 60}};
+  std::vector<DebugLineTableRowRef> TestRefs3 = {{999, 888}};
+
+  CR1->populate(TestRefs1);
+  CR2->populate(TestRefs2);
+  CR3->populate(TestRefs3);
+
+  // Verify data integrity
+  ArrayRef<DebugLineTableRowRef> Rows1 = CR1->getRows();
+  ArrayRef<DebugLineTableRowRef> Rows2 = CR2->getRows();
+  ArrayRef<DebugLineTableRowRef> Rows3 = CR3->getRows();
+
+  EXPECT_EQ(Rows1[0].DwCompileUnitIndex, 100u);
+  EXPECT_EQ(Rows1[1].RowIndex, 400u);
+
+  EXPECT_EQ(Rows2[1].DwCompileUnitIndex, 30u);
+  EXPECT_EQ(Rows2[2].RowIndex, 60u);
+
+  EXPECT_EQ(Rows3[0].DwCompileUnitIndex, 999u);
+  EXPECT_EQ(Rows3[0].RowIndex, 888u);
+}
+
+} // namespace

>From abcd69590b944e24605c292ae835596115f9284d Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Tue, 29 Jul 2025 14:04:57 -0700
Subject: [PATCH 3/7] Fix lint issues

---
 bolt/lib/Core/BinaryContext.cpp       |  6 ++----
 bolt/unittests/Core/ClusteredRows.cpp | 17 +++--------------
 2 files changed, 5 insertions(+), 18 deletions(-)

diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 1766b0540a5cd..df151f398bd54 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1575,14 +1575,12 @@ unsigned BinaryContext::addDebugFilenameToUnit(const uint32_t DestCUID,
   StringRef Dir = "";
   if (FileNameEntry.DirIdx != 0) {
     if (std::optional<const char *> DirName = dwarf::toString(
-            LineTable->Prologue
-                .IncludeDirectories[FileNameEntry.DirIdx - 1])) {
+            LineTable->Prologue.IncludeDirectories[FileNameEntry.DirIdx - 1])) {
       Dir = *DirName;
     }
   }
   StringRef FileName = "";
-  if (std::optional<const char *> FName =
-          dwarf::toString(FileNameEntry.Name))
+  if (std::optional<const char *> FName = dwarf::toString(FileNameEntry.Name))
     FileName = *FName;
   assert(FileName != "");
   DWARFCompileUnit *DstUnit = DwCtx->getCompileUnitForOffset(DestCUID);
diff --git a/bolt/unittests/Core/ClusteredRows.cpp b/bolt/unittests/Core/ClusteredRows.cpp
index 5901f9ac5aaaa..a75209a75dfad 100644
--- a/bolt/unittests/Core/ClusteredRows.cpp
+++ b/bolt/unittests/Core/ClusteredRows.cpp
@@ -31,9 +31,7 @@ TEST_F(ClusteredRowsTest, CreateSingleElement) {
   EXPECT_EQ(CR->size(), 1u);
 
   // Test population with single element
-  std::vector<DebugLineTableRowRef> TestRefs = {
-      {42, 100}
-  };
+  std::vector<DebugLineTableRowRef> TestRefs = {{42, 100}};
   CR->populate(TestRefs);
 
   ArrayRef<DebugLineTableRowRef> Rows = CR->getRows();
@@ -48,11 +46,7 @@ TEST_F(ClusteredRowsTest, CreateMultipleElements) {
   EXPECT_EQ(CR->size(), 3u);
 
   // Test population with multiple elements
-  std::vector<DebugLineTableRowRef> TestRefs = {
-      {10, 20},
-      {30, 40},
-      {50, 60}
-  };
+  std::vector<DebugLineTableRowRef> TestRefs = {{10, 20}, {30, 40}, {50, 60}};
   CR->populate(TestRefs);
 
   ArrayRef<DebugLineTableRowRef> Rows = CR->getRows();
@@ -87,12 +81,7 @@ TEST_F(ClusteredRowsTest, PopulateWithArrayRef) {
   ASSERT_NE(CR, nullptr);
 
   // Test population with ArrayRef
-  DebugLineTableRowRef TestArray[] = {
-      {1, 2},
-      {3, 4},
-      {5, 6},
-      {7, 8}
-  };
+  DebugLineTableRowRef TestArray[] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
   ArrayRef<DebugLineTableRowRef> TestRefs(TestArray, 4);
   CR->populate(TestRefs);
 

>From 9391b3a7861ed4495b7ad3308dd7a0d46e1a008f Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Tue, 29 Jul 2025 16:28:32 -0700
Subject: [PATCH 4/7] Modify awk script to make it work with older versions

---
 bolt/test/Inputs/process-debug-line.sh | 52 ++++++++++++++------------
 1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/bolt/test/Inputs/process-debug-line.sh b/bolt/test/Inputs/process-debug-line.sh
index b30408df922eb..44cbcd1e5984a 100755
--- a/bolt/test/Inputs/process-debug-line.sh
+++ b/bolt/test/Inputs/process-debug-line.sh
@@ -23,7 +23,9 @@ BEGIN {
     cu_count = 0
     current_cu_file = ""
     # Initialize file names array
-    for (i = 0; i < 100; i++) current_file_names[i] = ""
+    for (i = 0; i < 100; i++) {
+        current_file_names[i] = ""
+    }
 }
 
 # Track debug_line sections (new CU)
@@ -31,31 +33,34 @@ BEGIN {
     cu_count++
     current_cu_file = ""
     # Clear file names array for new CU
-    for (i = 0; i < 100; i++) current_file_names[i] = ""
+    for (i = 0; i < 100; i++) {
+        current_file_names[i] = ""
+    }
     next
 }
 
 # Capture file names and their indices
 /^file_names\[.*\]:/ {
-    # Extract file index using more portable regex
-    if (match($0, /file_names\[[[:space:]]*([0-9]+)\]:/, arr)) {
-        file_index = arr[1]
-    } else {
-        # Fallback parsing
-        gsub(/file_names\[/, "", $0)
-        gsub(/\]:.*/, "", $0)
-        gsub(/[[:space:]]/, "", $0)
-        file_index = $0
-    }
+    # Extract file index using simple string operations
+    line_copy = $0
+    gsub(/file_names\[/, "", line_copy)
+    gsub(/\]:.*/, "", line_copy)
+    gsub(/[ \t]/, "", line_copy)
+    file_index = line_copy
 
     getline  # Read the next line which contains the actual filename
-    if (match($0, /name:[[:space:]]*"([^"]*)"/, name_arr)) {
-        filename = name_arr[1]
+    # Extract filename from name: "filename" format
+    if (match($0, /name:[ \t]*"/)) {
+        filename = $0
+        gsub(/.*name:[ \t]*"/, "", filename)
+        gsub(/".*/, "", filename)
         current_file_names[file_index] = filename
 
         # Extract basename for main CU file (first .c/.cpp/.cc file we see)
-        if (current_cu_file == "" && match(filename, /([^\/]*\.(c|cpp|cc))$/, cu_arr)) {
-            current_cu_file = cu_arr[1]
+        if (current_cu_file == "" && match(filename, /\.(c|cpp|cc)$/)) {
+            cu_filename = filename
+            gsub(/.*\//, "", cu_filename)
+            current_cu_file = cu_filename
         }
     }
     next
@@ -74,19 +79,18 @@ BEGIN {
         if (filename == "") {
             filename = "UNKNOWN_FILE_" file_index
         } else {
-            # Extract just the basename using portable method
-            if (match(filename, /([^\/]*)$/, basename_arr)) {
-                filename = basename_arr[1]
-            } else {
-                # Fallback: use gsub
-                gsub(/.*\//, "", filename)
-            }
+            # Extract just the basename
+            basename = filename
+            gsub(/.*\//, "", basename)
+            filename = basename
         }
 
         # Build additional info (flags, etc.)
         additional_info = ""
         for (i = 8; i <= NF; i++) {
-            if (additional_info != "") additional_info = additional_info " "
+            if (additional_info != "") {
+                additional_info = additional_info " "
+            }
             additional_info = additional_info $i
         }
 

>From 41edb2b8c492125e89732b58dc3e7fda333bf015 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Tue, 29 Jul 2025 17:22:00 -0700
Subject: [PATCH 5/7] Minor fixes

---
 bolt/include/bolt/Core/BinaryFunction.h | 2 +-
 bolt/lib/Core/BinaryContext.cpp         | 8 +++++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index 966559e0c6fa6..ec56ff3e37dd2 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -2425,7 +2425,7 @@ class BinaryFunction {
   }
 
   /// Return DWARF compile units for this function.
-  const SmallVector<DWARFUnit *, 1> getDWARFUnits() const {
+  const SmallVector<DWARFUnit *, 1>& getDWARFUnits() const {
     return DwarfUnitVec;
   }
 
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index df151f398bd54..6cbb17bd4e926 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1718,9 +1718,15 @@ void BinaryContext::preprocessDebugInfo() {
   // Clear debug info for functions from units that we are not going to process.
   for (auto &KV : BinaryFunctions) {
     BinaryFunction &BF = KV.second;
+    // Collect units to remove to avoid iterator invalidation
+    SmallVector<DWARFUnit *, 1> UnitsToRemove;
     for (auto *Unit : BF.getDWARFUnits()) {
       if (!ProcessedCUs.count(Unit))
-        BF.removeDWARFUnit(Unit);
+        UnitsToRemove.push_back(Unit);
+    }
+    // Remove the collected units
+    for (auto *Unit : UnitsToRemove) {
+      BF.removeDWARFUnit(Unit);
     }
   }
 

>From 44bf8bb419cc79a58a7a0aabf5778fcad81de142 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Wed, 30 Jul 2025 19:41:02 -0700
Subject: [PATCH 6/7] Fixed typos

---
 bolt/include/bolt/Core/BinaryContext.h |  2 +-
 bolt/include/bolt/Core/DebugData.h     | 32 ++++++++------------------
 bolt/lib/Core/BinaryFunction.cpp       |  2 +-
 bolt/unittests/Core/ClusteredRows.cpp  |  4 ++--
 4 files changed, 13 insertions(+), 27 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index 48bc9a5d1f92c..72c8817daa714 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -292,7 +292,7 @@ class BinaryContext {
   /// tables associated with instructions. Since binary functions can span
   /// multiple compilation units, instructions may reference debug line
   /// information from multiple CUs.
-  ClasteredRowsContainer ClasteredRows;
+  ClusteredRowsContainer ClusteredRows;
 
   // Setup MCPlus target builder
   void initializeTarget(std::unique_ptr<MCPlusBuilder> TargetBuilder) {
diff --git a/bolt/include/bolt/Core/DebugData.h b/bolt/include/bolt/Core/DebugData.h
index 048594946d8a9..adbce0bb1d5b6 100644
--- a/bolt/include/bolt/Core/DebugData.h
+++ b/bolt/include/bolt/Core/DebugData.h
@@ -834,9 +834,6 @@ class DwarfLineTable {
 };
 
 /// ClusteredRows represents a collection of debug line table row references.
-/// Since a Binary function can belong to multiple compilation units (CUs),
-/// a single MCInst can have multiple debug line table rows associated with it
-/// from different CUs. This class manages such clustered row references.
 ///
 /// MEMORY LAYOUT AND DESIGN:
 /// This class uses a flexible array member pattern to store all
@@ -846,7 +843,7 @@ class DwarfLineTable {
 /// +------------------+
 /// | ClusteredRows    |  <- Object header (Size + first element)
 /// | - Size           |
-/// | - Raws (element) |  <- First DebugLineTableRowRef element
+/// | - Rows (element) |  <- First DebugLineTableRowRef element
 /// +------------------+
 /// | element[1]       |  <- Additional DebugLineTableRowRef elements
 /// | element[2]       |     stored immediately after the object
@@ -854,18 +851,7 @@ class DwarfLineTable {
 /// | element[Size-1]  |
 /// +------------------+
 ///
-/// PERFORMANCE BENEFITS:
-/// - Single memory allocation: All elements are stored in one contiguous block,
-///   eliminating the need for separate heap allocations for the array.
-/// - No extra dereferencing: Elements are accessed directly via pointer
-///   arithmetic (beginPtr() + offset) rather than through an additional
-///   pointer indirection.
-/// - Cache locality: All elements are guaranteed to be adjacent in memory,
-///   improving cache performance during iteration.
-/// - Memory efficiency: No overhead from separate pointer storage or
-///   fragmented allocations.
-///
-/// The 'Raws' member serves as both the first element storage and the base
+/// The 'Rows' member serves as both the first element storage and the base
 /// address for pointer arithmetic to access subsequent elements.
 class ClusteredRows {
 public:
@@ -891,7 +877,7 @@ class ClusteredRows {
 
 private:
   uint64_t Size;
-  DebugLineTableRowRef Raws;
+  DebugLineTableRowRef Rows;
 
   ClusteredRows(uint64_t Size) : Size(Size) {}
   static uint64_t getTotalSize(uint64_t Size) {
@@ -899,17 +885,17 @@ class ClusteredRows {
     return sizeof(ClusteredRows) + (Size - 1) * sizeof(DebugLineTableRowRef);
   }
   const DebugLineTableRowRef *beginPtrConst() const {
-    return reinterpret_cast<const DebugLineTableRowRef *>(&Raws);
+    return reinterpret_cast<const DebugLineTableRowRef *>(&Rows);
   }
   DebugLineTableRowRef *beginPtr() {
-    return reinterpret_cast<DebugLineTableRowRef *>(&Raws);
+    return reinterpret_cast<DebugLineTableRowRef *>(&Rows);
   }
 
-  friend class ClasteredRowsContainer;
+  friend class ClusteredRowsContainer;
 };
 
-/// ClasteredRowsContainer manages the lifecycle of ClusteredRows objects.
-class ClasteredRowsContainer {
+/// ClusteredRowsContainer manages the lifecycle of ClusteredRows objects.
+class ClusteredRowsContainer {
 public:
   ClusteredRows *createClusteredRows(uint64_t Size) {
     auto *CR = new (std::malloc(ClusteredRows::getTotalSize(Size)))
@@ -917,7 +903,7 @@ class ClasteredRowsContainer {
     Clusters.push_back(CR);
     return CR;
   }
-  ~ClasteredRowsContainer() {
+  ~ClusteredRowsContainer() {
     for (auto *CR : Clusters)
       std::free(CR);
   }
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index a3a6b31451441..8635cba006991 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1502,7 +1502,7 @@ Error BinaryFunction::disassemble() {
       }
       if (!Rows.empty()) {
         ClusteredRows *Cluster =
-            BC.ClasteredRows.createClusteredRows(Rows.size());
+            BC.ClusteredRows.createClusteredRows(Rows.size());
         Cluster->populate(Rows);
         Instruction.setLoc(Cluster->toSMLoc());
       }
diff --git a/bolt/unittests/Core/ClusteredRows.cpp b/bolt/unittests/Core/ClusteredRows.cpp
index a75209a75dfad..4665022c91fdd 100644
--- a/bolt/unittests/Core/ClusteredRows.cpp
+++ b/bolt/unittests/Core/ClusteredRows.cpp
@@ -19,10 +19,10 @@ namespace {
 class ClusteredRowsTest : public ::testing::Test {
 protected:
   void SetUp() override {
-    Container = std::make_unique<ClasteredRowsContainer>();
+    Container = std::make_unique<ClusteredRowsContainer>();
   }
 
-  std::unique_ptr<ClasteredRowsContainer> Container;
+  std::unique_ptr<ClusteredRowsContainer> Container;
 };
 
 TEST_F(ClusteredRowsTest, CreateSingleElement) {

>From 04663a0cf062005b1f6abae2d1e0078396e36863 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Thu, 31 Jul 2025 10:45:31 -0700
Subject: [PATCH 7/7] Made the test architecture independent

---
 bolt/test/lit.cfg.py                               |  4 ++--
 bolt/test/{X86 => }/multi-cu-debug-line.test       | 14 +++++++-------
 .../process-debug-line.sh => process-debug-line}   |  0
 3 files changed, 9 insertions(+), 9 deletions(-)
 rename bolt/test/{X86 => }/multi-cu-debug-line.test (93%)
 rename bolt/test/{Inputs/process-debug-line.sh => process-debug-line} (100%)

diff --git a/bolt/test/lit.cfg.py b/bolt/test/lit.cfg.py
index 0d05229be2bf3..508db8b890190 100644
--- a/bolt/test/lit.cfg.py
+++ b/bolt/test/lit.cfg.py
@@ -10,8 +10,7 @@
 import lit.util
 
 from lit.llvm import llvm_config
-from lit.llvm.subst import ToolSubst
-from lit.llvm.subst import FindTool
+from lit.llvm.subst import FindTool, ToolSubst
 
 # Configuration file for the 'lit' test runner.
 
@@ -127,6 +126,7 @@
         unresolved="fatal",
         extra_args=[link_fdata_cmd],
     ),
+    ToolSubst("process-debug-line", unresolved="fatal"),
     ToolSubst("merge-fdata", unresolved="fatal"),
     ToolSubst("llvm-readobj", unresolved="fatal"),
     ToolSubst("llvm-dwp", unresolved="fatal"),
diff --git a/bolt/test/X86/multi-cu-debug-line.test b/bolt/test/multi-cu-debug-line.test
similarity index 93%
rename from bolt/test/X86/multi-cu-debug-line.test
rename to bolt/test/multi-cu-debug-line.test
index 430b281445db5..a94c901bbcc5a 100644
--- a/bolt/test/X86/multi-cu-debug-line.test
+++ b/bolt/test/multi-cu-debug-line.test
@@ -1,16 +1,16 @@
 ## Test that BOLT correctly handles debug line information for functions
 ## that belong to multiple compilation units (e.g., inline functions in
 ## common header files). The test covers two scenarios:
-## 1. Normal processing: .debug_line section shows lines for the function 
+## 1. Normal processing: .debug_line section shows lines for the function
 ##    in all CUs where it was compiled, with no duplicate rows within CUs
-## 2. Functions not processed: When BOLT doesn't process functions (using 
+## 2. Functions not processed: When BOLT doesn't process functions (using
 ##    --funcs with nonexistent function), original debug info is preserved
 
 # REQUIRES: system-linux
 
 ## Compile test files with debug info
-# RUN: %clang %cflags -O0 -g %S/../Inputs/multi-cu-file1.c %S/../Inputs/multi-cu-file2.c \
-# RUN:   -I%S/../Inputs -o %t.exe -Wl,-q
+# RUN: %clang %cflags -O0 -g %S/Inputs/multi-cu-file1.c %S/Inputs/multi-cu-file2.c \
+# RUN:   -I%S/Inputs -o %t.exe -Wl,-q
 
 ## Test 1: Normal BOLT processing (functions are processed/optimized)
 # RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections
@@ -26,7 +26,7 @@
 # BASIC: name: "{{.*}}multi-cu-file2.c"
 
 ## Use our helper script to create a normalized table without addresses
-# RUN: %S/../Inputs/process-debug-line.sh %t.debug-line.txt > %t.normalized-debug-line.txt
+# RUN: process-debug-line %t.debug-line.txt > %t.normalized-debug-line.txt
 # RUN: FileCheck %s --check-prefix=NORMALIZED --input-file %t.normalized-debug-line.txt
 
 ## Check that we have line entries for the inline function (lines 5, 6, 7) from multi-cu-common.h
@@ -72,7 +72,7 @@
 # PRESERVED-BASIC: name: "{{.*}}multi-cu-file2.c"
 
 ## Create normalized output for the not-emitted case
-# RUN: %S/../Inputs/process-debug-line.sh %t.not-emitted.debug-line.txt > %t.not-emitted.normalized.txt
+# RUN: process-debug-line %t.not-emitted.debug-line.txt > %t.not-emitted.normalized.txt
 # RUN: FileCheck %s --check-prefix=PRESERVED-NORMALIZED --input-file %t.not-emitted.normalized.txt
 
 ## Check that we have line entries for the inline function (lines 5, 6, 7) from multi-cu-common.h
@@ -105,4 +105,4 @@
 # PRESERVED-FILE2: multi-cu-file2.c {{[0-9]+}} {{[0-9]+}} multi-cu-file2.c
 
 ## Note: We do not check for duplicates in Test 2 since we are preserving original debug info as-is
-## and the original may contain patterns that would be flagged as duplicates by our normalization
\ No newline at end of file
+## and the original may contain patterns that would be flagged as duplicates by our normalization
diff --git a/bolt/test/Inputs/process-debug-line.sh b/bolt/test/process-debug-line
similarity index 100%
rename from bolt/test/Inputs/process-debug-line.sh
rename to bolt/test/process-debug-line



More information about the llvm-commits mailing list