[llvm] 7ff811a - [DWARFLinker] Patch DW_AT_LLVM_stmt_sequence in the parallel linker (#195388)
via llvm-commits
llvm-commits at lists.llvm.org
Tue May 5 10:07:03 PDT 2026
Author: Jonas Devlieghere
Date: 2026-05-05T10:06:57-07:00
New Revision: 7ff811a88e3157aa32dddb147d9b264ef8870c75
URL: https://github.com/llvm/llvm-project/commit/7ff811a88e3157aa32dddb147d9b264ef8870c75
DIFF: https://github.com/llvm/llvm-project/commit/7ff811a88e3157aa32dddb147d9b264ef8870c75.diff
LOG: [DWARFLinker] Patch DW_AT_LLVM_stmt_sequence in the parallel linker (#195388)
Mirror dsymutil's stmt-sequence rewriting in the parallel linker so each
attribute ends up pointing at the DW_LNE_set_address that opens its
containing output sequence, with the correct offset in the combined
.debug_line.
At DIE cloning time we resolve each attribute's input offset to the
address of its first row and record the pair (DIEValue, address) on the
CompileUnit, alongside a DebugOffsetPatch on the .debug_info section so
combination adds the CU's .debug_line start offset. The line-table
emitter then fills a map from row address to the byte offset of the
sequence-opening DW_LNE_set_address.
After emission, each recorded attribute is rewritten by relocating its
input address through the CU's function ranges and looking the result up
in the map. When resolution fails the DWARF max-offset sentinel is
written instead, and the patch applier preserves it unchanged.
First-row lookups share a lazy per-CU cache to keep resolution O(1) per
attribute.
Added:
Modified:
llvm/include/llvm/DWARFLinker/Utils.h
llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.cpp
llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.h
llvm/lib/DWARFLinker/Parallel/DebugLineSectionEmitter.h
llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
llvm/lib/DWARFLinker/Utils.cpp
llvm/test/tools/dsymutil/AArch64/stmt-seq-macho.test
llvm/unittests/DWARFLinkerParallel/DWARFLinkerTest.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/DWARFLinker/Utils.h b/llvm/include/llvm/DWARFLinker/Utils.h
index 8bf5ea1025a1e..8bc45706107f7 100644
--- a/llvm/include/llvm/DWARFLinker/Utils.h
+++ b/llvm/include/llvm/DWARFLinker/Utils.h
@@ -9,8 +9,11 @@
#ifndef LLVM_DWARFLINKER_UTILS_H
#define LLVM_DWARFLINKER_UTILS_H
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
@@ -18,6 +21,21 @@
namespace llvm {
namespace dwarf_linker {
+/// Build a map from an input DW_AT_LLVM_stmt_sequence byte offset to
+/// the first-row index (in \p LT.Rows) of the corresponding line-table
+/// sequence. Seeds the map from \p LT.Sequences (the DWARF parser's
+/// discovered sequences), then augments it by walking row boundaries
+/// (DW_LNE_end_sequence markers) and matching them against the sorted
+/// input offsets in \p SortedStmtSeqOffsets, using the parser's results
+/// as ground-truth anchors. This recovers sequences the parser may not
+/// have registered and keeps the classic and parallel DWARFLinkers in
+/// lockstep. Caller passes \p SortedStmtSeqOffsets sorted ascending
+/// and deduplicated.
+void buildStmtSeqOffsetToFirstRowIndex(
+ const DWARFDebugLine::LineTable <,
+ ArrayRef<uint64_t> SortedStmtSeqOffsets,
+ DenseMap<uint64_t, uint64_t> &SeqOffToFirstRow);
+
/// This function calls \p Iteration() until it returns false.
/// If number of iterations exceeds \p MaxCounter then an Error is returned.
/// This function should be used for loops which assumed to have number of
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 06c0bfb5d4a2f..95cddaa7684d4 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -473,113 +473,20 @@ void DWARFLinker::cleanupAuxiliarryData(LinkContext &Context) {
static void constructSeqOffsettoOrigRowMapping(
CompileUnit &Unit, const DWARFDebugLine::LineTable <,
- DenseMap<uint64_t, unsigned> &SeqOffToOrigRow) {
-
- // Use std::map for ordered iteration.
- std::map<uint64_t, unsigned> LineTableMapping;
-
- // First, trust the sequences that the DWARF parser did identify.
- for (const DWARFDebugLine::Sequence &Seq : LT.Sequences)
- LineTableMapping[Seq.StmtSeqOffset] = Seq.FirstRowIndex;
-
- // Second, manually find sequence boundaries and match them to the
- // sorted attributes to handle sequences the parser might have missed.
+ DenseMap<uint64_t, uint64_t> &SeqOffToOrigRow) {
+ // Collect this unit's DW_AT_LLVM_stmt_sequence attribute values
+ // (input offsets), sorted ascending and deduplicated, to drive the
+ // shared mapping builder.
auto StmtAttrs = Unit.getStmtSeqListAttributes();
- llvm::sort(StmtAttrs, [](const PatchLocation &A, const PatchLocation &B) {
- return A.get() < B.get();
- });
-
- std::vector<unsigned> SeqStartRows;
- SeqStartRows.push_back(0);
- for (auto [I, Row] : llvm::enumerate(ArrayRef(LT.Rows).drop_back()))
- if (Row.EndSequence)
- SeqStartRows.push_back(I + 1);
-
- // While SeqOffToOrigRow parsed from CU could be the ground truth,
- // e.g.
- //
- // SeqOff Row
- // 0x08 9
- // 0x14 15
- //
- // The StmtAttrs and SeqStartRows may not match perfectly, e.g.
- //
- // StmtAttrs SeqStartRows
- // 0x04 3
- // 0x08 5
- // 0x10 9
- // 0x12 11
- // 0x14 15
- //
- // In this case, we don't want to assign 5 to 0x08, since we know 0x08
- // maps to 9. If we do a dummy 1:1 mapping 0x10 will be mapped to 9
- // which is incorrect. The expected behavior is ignore 5, realign the
- // table based on the result from the line table:
- //
- // StmtAttrs SeqStartRows
- // 0x04 3
- // -- 5
- // 0x08 9 <- LineTableMapping ground truth
- // 0x10 11
- // 0x12 --
- // 0x14 15 <- LineTableMapping ground truth
-
- ArrayRef StmtAttrsRef(StmtAttrs);
- ArrayRef SeqStartRowsRef(SeqStartRows);
-
- // Dummy last element to make sure StmtAttrsRef and SeqStartRowsRef always
- // run out first.
- constexpr uint64_t DummyKey = UINT64_MAX;
- constexpr unsigned DummyVal = UINT32_MAX;
- LineTableMapping[DummyKey] = DummyVal;
-
- for (auto [NextSeqOff, NextRow] : LineTableMapping) {
- // Explict capture to avoid capturing structured bindings and make C++17
- // happy.
- auto StmtAttrSmallerThanNext = [N = NextSeqOff](const PatchLocation &SA) {
- return SA.get() < N;
- };
- auto SeqStartSmallerThanNext = [N = NextRow](const unsigned &Row) {
- return Row < N;
- };
- // If both StmtAttrs and SeqStartRows points to value not in
- // the LineTableMapping yet, we do a dummy one to one mapping and
- // move the pointer.
- while (!StmtAttrsRef.empty() && !SeqStartRowsRef.empty() &&
- StmtAttrSmallerThanNext(StmtAttrsRef.front()) &&
- SeqStartSmallerThanNext(SeqStartRowsRef.front())) {
- SeqOffToOrigRow[StmtAttrsRef.consume_front().get()] =
- SeqStartRowsRef.consume_front();
- }
- // One of the pointer points to the value at or past Next in the
- // LineTableMapping, We move the pointer to re-align with the
- // LineTableMapping
- StmtAttrsRef = StmtAttrsRef.drop_while(StmtAttrSmallerThanNext);
- SeqStartRowsRef = SeqStartRowsRef.drop_while(SeqStartSmallerThanNext);
- // Use the LineTableMapping's result as the ground truth and move
- // on.
- if (NextSeqOff != DummyKey) {
- SeqOffToOrigRow[NextSeqOff] = NextRow;
- }
- // Move the pointers if they are pointed at Next.
- // It is possible that they point to later entries in LineTableMapping.
- // Therefore we only increment the pointers after we validate they are
- // pointing to the `Next` entry. e.g.
- //
- // LineTableMapping
- // SeqOff Row
- // 0x08 9 <- NextSeqOff/NextRow
- // 0x14 15
- //
- // StmtAttrs SeqStartRows
- // 0x14 13 <- StmtAttrsRef.front() / SeqStartRowsRef.front()
- // 0x16 15
- // -- 17
- if (!StmtAttrsRef.empty() && StmtAttrsRef.front().get() == NextSeqOff)
- StmtAttrsRef.consume_front();
- if (!SeqStartRowsRef.empty() && SeqStartRowsRef.front() == NextRow)
- SeqStartRowsRef.consume_front();
- }
+ SmallVector<uint64_t> SortedOffsets;
+ SortedOffsets.reserve(StmtAttrs.size());
+ for (const PatchLocation &P : StmtAttrs)
+ SortedOffsets.push_back(P.get());
+ llvm::sort(SortedOffsets);
+ SortedOffsets.erase(llvm::unique(SortedOffsets), SortedOffsets.end());
+
+ dwarf_linker::buildStmtSeqOffsetToFirstRowIndex(LT, SortedOffsets,
+ SeqOffToOrigRow);
}
std::pair<bool, std::optional<int64_t>>
@@ -2506,7 +2413,7 @@ Error DWARFLinker::DIECloner::generateLineTableForUnit(CompileUnit &Unit) {
"must have an offset for each row");
// Create a map of stmt sequence offsets to original row indices.
- DenseMap<uint64_t, unsigned> SeqOffToOrigRow;
+ DenseMap<uint64_t, uint64_t> SeqOffToOrigRow;
// The DWARF parser's discovery of sequences can be incomplete. To
// ensure all DW_AT_LLVM_stmt_sequence attributes can be patched, we
// build a map from both the parser's results and a manual
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index a0b3f53eafaed..80113504e8199 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -514,8 +514,39 @@ size_t DIEAttributeCloner::cloneScalarAttr(
} else if (AttrSpec.Attr == dwarf::DW_AT_declaration && Value)
AttrInfo.IsDeclaration = true;
- return Generator.addScalarAttribute(AttrSpec.Attr, ResultingForm, Value)
- .second;
+ // DW_AT_LLVM_stmt_sequence refers to line info in this unit's
+ // .debug_line contribution, which only exists for compile units.
+ // DependencyTracker can route a module-scope subprogram to a type
+ // unit when ODR deduplication applies (see
+ // DependencyTracker.cpp: DW_TAG_subprogram case), so drop the
+ // attribute on that path — mirroring how cloneBlockAttr handles
+ // type-unit placement.
+ if (AttrSpec.Attr == dwarf::DW_AT_LLVM_stmt_sequence &&
+ !OutUnit.isCompileUnit())
+ return 0;
+
+ auto Result =
+ Generator.addScalarAttribute(AttrSpec.Attr, ResultingForm, Value);
+ // Record DW_AT_LLVM_stmt_sequence so the attribute value can be
+ // rewritten with the correct .debug_line offset after the line table
+ // for this CU has been emitted. We also register a DebugOffsetPatch so
+ // that the final-section offset of .debug_line gets added when the
+ // section is placed in the combined output.
+ if (AttrSpec.Attr == dwarf::DW_AT_LLVM_stmt_sequence) {
+ // Record the attribute's raw input stmt-sequence offset. Resolution
+ // to a first-row index — including the boundary-walk fallback for
+ // sequences the DWARF parser may not have registered — happens in
+ // a post-cloning pass (buildStmtSeqOffsetToFirstRowIndex), so that
+ // matches the classic linker's behaviour.
+ OutUnit.getAsCompileUnit()->noteStmtSeqListAttribute(&Result.first, Value);
+ DebugInfoOutputSection.notePatchWithOffsetUpdate(
+ DebugOffsetPatch{
+ AttrOutOffset,
+ &OutUnit->getOrCreateSectionDescriptor(DebugSectionKind::DebugLine),
+ /*AddLocalValue=*/true},
+ PatchesOffsets);
+ }
+ return Result.second;
}
size_t DIEAttributeCloner::cloneBlockAttr(
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index 2287b36d36cef..ebe914c4f8c52 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -100,6 +100,7 @@ void CompileUnit::maybeResetToLoadedStage() {
Abbreviations.clear();
OutUnitDIE = nullptr;
DebugAddrIndexMap.clear();
+ StmtSeqListAttributes.clear();
llvm::fill(OutDieOffsetArray, 0);
llvm::fill(TypeEntries, nullptr);
@@ -254,6 +255,7 @@ void CompileUnit::cleanupDataAfterClonning() {
OutDieOffsetArray = SmallVector<uint64_t>();
TypeEntries = SmallVector<TypeEntry *>();
Dependencies.reset(nullptr);
+ StmtSeqListAttributes.clear();
getOrigUnit().clear();
}
@@ -1563,90 +1565,183 @@ Error CompileUnit::cloneAndEmitLineTable(const Triple &TargetTriple) {
OutLineTable.Rows.clear();
OutLineTable.Sequences = InputLineTable->Sequences;
- } else {
- // This vector is the output line table.
- std::vector<DWARFDebugLine::Row> NewRows;
- NewRows.reserve(InputLineTable->Rows.size());
-
- // Current sequence of rows being extracted, before being inserted
- // in NewRows.
- std::vector<DWARFDebugLine::Row> Seq;
-
- const auto &FunctionRanges = getFunctionRanges();
- std::optional<AddressRangeValuePair> CurrRange;
-
- // FIXME: This logic is meant to generate exactly the same output as
- // Darwin's classic dsymutil. There is a nicer way to implement this
- // by simply putting all the relocated line info in NewRows and simply
- // sorting NewRows before passing it to emitLineTableForUnit. This
- // should be correct as sequences for a function should stay
- // together in the sorted output. There are a few corner cases that
- // look suspicious though, and that required to implement the logic
- // this way. Revisit that once initial validation is finished.
-
- // Iterate over the object file line info and extract the sequences
- // that correspond to linked functions.
- for (DWARFDebugLine::Row Row : InputLineTable->Rows) {
- // Check whether we stepped out of the range. The range is
- // half-open, but consider accept the end address of the range if
- // it is marked as end_sequence in the input (because in that
- // case, the relocation offset is accurate and that entry won't
- // serve as the start of another function).
- if (!CurrRange || !CurrRange->Range.contains(Row.Address.Address)) {
- // We just stepped out of a known range. Insert a end_sequence
- // corresponding to the end of the range.
- uint64_t StopAddress =
- CurrRange ? CurrRange->Range.end() + CurrRange->Value : -1ULL;
- CurrRange = FunctionRanges.getRangeThatContains(Row.Address.Address);
- if (StopAddress != -1ULL && !Seq.empty()) {
- // Insert end sequence row with the computed end address, but
- // the same line as the previous one.
- auto NextLine = Seq.back();
- NextLine.Address.Address = StopAddress;
- NextLine.EndSequence = 1;
- NextLine.PrologueEnd = 0;
- NextLine.BasicBlock = 0;
- NextLine.EpilogueBegin = 0;
- Seq.push_back(NextLine);
- insertLineSequence(Seq, NewRows);
- }
+ return emitDebugLine(TargetTriple, OutLineTable);
+ }
- if (!CurrRange)
- continue;
+ SmallVector<uint64_t> OrigRowIndices;
+ filterLineTableRows(*InputLineTable, OutLineTable.Rows, OrigRowIndices);
+
+ if (StmtSeqListAttributes.empty())
+ return emitDebugLine(TargetTriple, OutLineTable);
+
+ // When DW_AT_LLVM_stmt_sequence attributes on this CU need their values
+ // rewritten to point at the correct output sequence, have the emitter
+ // record, for every row that originated from an input row, the byte
+ // offset of the DW_LNE_set_address that opens the sequence containing
+ // that row. Keying the map on the input row index (rather than on an
+ // output address) avoids collisions when two input sequences would
+ // relocate to the same output address — e.g. ICF folding two functions
+ // from the same CU to a single output range.
+ //
+ // The patching below MUST run before emitDebugInfo() serializes the
+ // DIE bytes and before OutputSections::applyPatches() runs for this
+ // unit's .debug_info — it writes a local offset into the DIEValue that
+ // the serializer then emits, and a DebugOffsetPatch (registered at DIE
+ // cloning time) later adds the CU's .debug_line start offset to reach
+ // the final absolute value.
+ DenseMap<uint64_t, uint64_t> RowIndexToSeqStartOffset;
+ if (Error Err = emitDebugLine(TargetTriple, OutLineTable, OrigRowIndices,
+ &RowIndexToSeqStartOffset))
+ return Err;
+
+ DenseMap<uint64_t, uint64_t> SeqOffsetToFirstRowIndex =
+ buildStmtSeqOffsetToFirstRowIndex(*InputLineTable);
+ patchStmtSeqAttributes(SeqOffsetToFirstRowIndex, RowIndexToSeqStartOffset);
+ return Error::success();
+}
+
+void CompileUnit::filterLineTableRows(
+ const DWARFDebugLine::LineTable &InputLineTable,
+ std::vector<DWARFDebugLine::Row> &NewRows,
+ SmallVectorImpl<uint64_t> &NewRowIndices) {
+ NewRows.reserve(InputLineTable.Rows.size());
+ NewRowIndices.reserve(InputLineTable.Rows.size());
+
+ // Current sequence of rows being extracted, before being inserted
+ // in NewRows. Kept in lockstep with SeqIndices, which stores the
+ // originating input row index (or InvalidRowIndex for manufactured
+ // end-of-range rows).
+ std::vector<DWARFDebugLine::Row> Seq;
+ SmallVector<uint64_t> SeqIndices;
+ constexpr uint64_t InvalidRowIndex = std::numeric_limits<uint64_t>::max();
+
+ const auto &FunctionRanges = getFunctionRanges();
+ std::optional<AddressRangeValuePair> CurrRange;
+
+ // FIXME: This logic is meant to generate exactly the same output as
+ // Darwin's classic dsymutil. There is a nicer way to implement this
+ // by simply putting all the relocated line info in NewRows and simply
+ // sorting NewRows before passing it to emitLineTableForUnit. This
+ // should be correct as sequences for a function should stay
+ // together in the sorted output. There are a few corner cases that
+ // look suspicious though, and that required to implement the logic
+ // this way. Revisit that once initial validation is finished.
+
+ // Iterate over the object file line info and extract the sequences
+ // that correspond to linked functions.
+ for (auto [InputRowIdx, InputRow] : llvm::enumerate(InputLineTable.Rows)) {
+ DWARFDebugLine::Row Row = InputRow;
+ // Check whether we stepped out of the range. The range is
+ // half-open, but consider accept the end address of the range if
+ // it is marked as end_sequence in the input (because in that
+ // case, the relocation offset is accurate and that entry won't
+ // serve as the start of another function).
+ if (!CurrRange || !CurrRange->Range.contains(Row.Address.Address)) {
+ // We just stepped out of a known range. Insert a end_sequence
+ // corresponding to the end of the range.
+ uint64_t StopAddress =
+ CurrRange ? CurrRange->Range.end() + CurrRange->Value : -1ULL;
+ CurrRange = FunctionRanges.getRangeThatContains(Row.Address.Address);
+ if (StopAddress != -1ULL && !Seq.empty()) {
+ // Insert end sequence row with the computed end address, but
+ // the same line as the previous one. This row is synthesised
+ // and has no input counterpart, so tag it with
+ // InvalidRowIndex.
+ auto NextLine = Seq.back();
+ NextLine.Address.Address = StopAddress;
+ NextLine.EndSequence = 1;
+ NextLine.PrologueEnd = 0;
+ NextLine.BasicBlock = 0;
+ NextLine.EpilogueBegin = 0;
+ Seq.push_back(NextLine);
+ SeqIndices.push_back(InvalidRowIndex);
+ insertLineSequence(Seq, SeqIndices, NewRows, NewRowIndices);
}
- // Ignore empty sequences.
- if (Row.EndSequence && Seq.empty())
+ if (!CurrRange)
continue;
+ }
- // Relocate row address and add it to the current sequence.
- Row.Address.Address += CurrRange->Value;
- Seq.emplace_back(Row);
+ // Ignore empty sequences.
+ if (Row.EndSequence && Seq.empty())
+ continue;
- if (Row.EndSequence)
- insertLineSequence(Seq, NewRows);
- }
+ // Relocate row address and add it to the current sequence.
+ Row.Address.Address += CurrRange->Value;
+ Seq.emplace_back(Row);
+ SeqIndices.push_back(InputRowIdx);
- OutLineTable.Rows = std::move(NewRows);
+ if (Row.EndSequence)
+ insertLineSequence(Seq, SeqIndices, NewRows, NewRowIndices);
+ }
+}
+
+void CompileUnit::patchStmtSeqAttributes(
+ const DenseMap<uint64_t, uint64_t> &SeqOffsetToFirstRowIndex,
+ const DenseMap<uint64_t, uint64_t> &RowIndexToSeqStartOffset) {
+ const uint64_t InvalidOffset = getFormParams().getDwarfMaxOffset();
+
+ for (const CompileUnit::StmtSeqPatch &Patch : StmtSeqListAttributes) {
+ uint64_t NewStmtSeq = InvalidOffset;
+ auto RowIt = SeqOffsetToFirstRowIndex.find(Patch.InputStmtSeqOffset);
+ if (RowIt != SeqOffsetToFirstRowIndex.end()) {
+ auto OffIt = RowIndexToSeqStartOffset.find(RowIt->second);
+ if (OffIt != RowIndexToSeqStartOffset.end())
+ NewStmtSeq = OffIt->second;
+ }
+ // When resolution fails, the InvalidOffset sentinel must survive the
+ // combination-time section-offset fixup. The patch applier preserves
+ // InvalidOffset as-is so consumers see a clean invalid marker rather
+ // than StartOffset - 1.
+ *Patch.Value = DIEValue(Patch.Value->getAttribute(), Patch.Value->getForm(),
+ DIEInteger(NewStmtSeq));
}
+}
- return emitDebugLine(TargetTriple, OutLineTable);
+DenseMap<uint64_t, uint64_t> CompileUnit::buildStmtSeqOffsetToFirstRowIndex(
+ const DWARFDebugLine::LineTable &InputLineTable) const {
+ // Collect this CU's stmt-sequence attribute values (input offsets),
+ // sorted ascending and deduplicated.
+ SmallVector<uint64_t> StmtAttrs;
+ StmtAttrs.reserve(StmtSeqListAttributes.size());
+ for (const StmtSeqPatch &Patch : StmtSeqListAttributes)
+ StmtAttrs.push_back(Patch.InputStmtSeqOffset);
+ llvm::sort(StmtAttrs);
+ StmtAttrs.erase(llvm::unique(StmtAttrs), StmtAttrs.end());
+
+ DenseMap<uint64_t, uint64_t> Result;
+ dwarf_linker::buildStmtSeqOffsetToFirstRowIndex(InputLineTable, StmtAttrs,
+ Result);
+ return Result;
}
void CompileUnit::insertLineSequence(std::vector<DWARFDebugLine::Row> &Seq,
- std::vector<DWARFDebugLine::Row> &Rows) {
+ SmallVectorImpl<uint64_t> &SeqIndices,
+ std::vector<DWARFDebugLine::Row> &Rows,
+ SmallVectorImpl<uint64_t> &RowIndices) {
+ assert(Seq.size() == SeqIndices.size() &&
+ "Seq and SeqIndices must be kept in lockstep");
+ assert(Rows.size() == RowIndices.size() &&
+ "Rows and RowIndices must be kept in lockstep");
if (Seq.empty())
return;
+ auto ClearSeq = [&] {
+ Seq.clear();
+ SeqIndices.clear();
+ };
+
if (!Rows.empty() && Rows.back().Address < Seq.front().Address) {
llvm::append_range(Rows, Seq);
- Seq.clear();
+ llvm::append_range(RowIndices, SeqIndices);
+ ClearSeq();
return;
}
object::SectionedAddress Front = Seq.front().Address;
auto InsertPoint = partition_point(
Rows, [=](const DWARFDebugLine::Row &O) { return O.Address < Front; });
+ size_t InsertIdx = std::distance(Rows.begin(), InsertPoint);
// FIXME: this only removes the unneeded end_sequence if the
// sequences have been inserted in order. Using a global sort like
@@ -1655,12 +1750,17 @@ void CompileUnit::insertLineSequence(std::vector<DWARFDebugLine::Row> &Seq,
if (InsertPoint != Rows.end() && InsertPoint->Address == Front &&
InsertPoint->EndSequence) {
*InsertPoint = Seq.front();
+ RowIndices[InsertIdx] = SeqIndices.front();
Rows.insert(InsertPoint + 1, Seq.begin() + 1, Seq.end());
+ RowIndices.insert(RowIndices.begin() + InsertIdx + 1,
+ SeqIndices.begin() + 1, SeqIndices.end());
} else {
Rows.insert(InsertPoint, Seq.begin(), Seq.end());
+ RowIndices.insert(RowIndices.begin() + InsertIdx, SeqIndices.begin(),
+ SeqIndices.end());
}
- Seq.clear();
+ ClearSeq();
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
index 26d95574d2bec..9edea8a0509ac 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
@@ -407,6 +407,18 @@ class alignas(8) CompileUnit : public DwarfUnit {
/// Returns function ranges of this unit.
const RangesTy &getFunctionRanges() const { return Ranges; }
+ /// Record that a DW_AT_LLVM_stmt_sequence attribute on this unit
+ /// references the input line-table sequence whose header sits at
+ /// \p InputStmtSeqOffset. Resolution of that offset to an input
+ /// first-row index (via parser results plus a manual boundary-based
+ /// fallback) happens in a post-cloning pass, before \p V is rewritten
+ /// to the byte offset of the matching output sequence. Keying on row
+ /// index rather than address avoids collisions when two input
+ /// sequences would relocate to the same output address (e.g. ICF).
+ void noteStmtSeqListAttribute(DIEValue *V, uint64_t InputStmtSeqOffset) {
+ StmtSeqListAttributes.push_back({V, InputStmtSeqOffset});
+ }
+
/// Clone and emit this compilation unit.
Error
cloneAndEmit(std::optional<std::reference_wrapper<const Triple>> TargetTriple,
@@ -647,9 +659,47 @@ class alignas(8) CompileUnit : public DwarfUnit {
SectionDescriptor &OutRangeSection);
/// Insert the new line info sequence \p Seq into the current
- /// set of already linked line info \p Rows.
+ /// set of already linked line info \p Rows. \p SeqIndices carries the
+ /// input Row index that each entry in \p Seq originated from (or the
+ /// invalid-row-index sentinel for manufactured end-of-range rows), and
+ /// is kept in lockstep with \p RowIndices.
void insertLineSequence(std::vector<DWARFDebugLine::Row> &Seq,
- std::vector<DWARFDebugLine::Row> &Rows);
+ SmallVectorImpl<uint64_t> &SeqIndices,
+ std::vector<DWARFDebugLine::Row> &Rows,
+ SmallVectorImpl<uint64_t> &RowIndices);
+
+ /// Filter \p InputLineTable's rows to those covered by this unit's
+ /// function ranges, relocating addresses in the process, and store the
+ /// result in \p NewRows. \p NewRowIndices is populated in lockstep with
+ /// \p NewRows and carries, for each output row, the index of the input
+ /// row it originated from — or InvalidRowIndex for manufactured
+ /// end-of-range rows.
+ void filterLineTableRows(const DWARFDebugLine::LineTable &InputLineTable,
+ std::vector<DWARFDebugLine::Row> &NewRows,
+ SmallVectorImpl<uint64_t> &NewRowIndices);
+
+ /// Rewrite every DW_AT_LLVM_stmt_sequence DIEValue recorded on this
+ /// unit with the local .debug_line offset of the output sequence
+ /// containing the corresponding input first row.
+ /// \p SeqOffsetToFirstRowIndex maps an input stmt-sequence offset to
+ /// its first-row index (built by buildStmtSeqOffsetToFirstRowIndex so
+ /// that sequences missed by the DWARF parser are recovered from row
+ /// boundaries). \p RowIndexToSeqStartOffset maps an input first-row
+ /// index to the byte offset of the output DW_LNE_set_address that
+ /// opens the matching output sequence.
+ void patchStmtSeqAttributes(
+ const DenseMap<uint64_t, uint64_t> &SeqOffsetToFirstRowIndex,
+ const DenseMap<uint64_t, uint64_t> &RowIndexToSeqStartOffset);
+
+ /// Build a map from input stmt-sequence offset to the first-row index
+ /// of the corresponding sequence in \p InputLineTable. Seeds the map
+ /// from \p InputLineTable.Sequences (the DWARF parser's results), then
+ /// augments it by manually walking row boundaries and realigning them
+ /// against the recorded DW_AT_LLVM_stmt_sequence values so that
+ /// sequences missed by the parser still resolve. Mirrors the
+ /// classic DWARFLinker's constructSeqOffsettoOrigRowMapping.
+ DenseMap<uint64_t, uint64_t> buildStmtSeqOffsetToFirstRowIndex(
+ const DWARFDebugLine::LineTable &InputLineTable) const;
/// Emits body for both macro sections.
void emitMacroTableImpl(const DWARFDebugMacro *MacroTable,
@@ -721,6 +771,18 @@ class alignas(8) CompileUnit : public DwarfUnit {
/// The DW_AT_low_pc of each DW_TAG_label.
using LabelMapTy = SmallDenseMap<uint64_t, uint64_t, 1>;
LabelMapTy Labels;
+
+ /// Recorded DW_AT_LLVM_stmt_sequence attributes for this unit. Each
+ /// entry pairs the DIEValue holding the attribute with the input-side
+ /// byte offset of the referenced line-table sequence. The value is
+ /// rewritten with the matching output offset after the line table has
+ /// been emitted; resolution from input offset to input first-row
+ /// index (including the parser-miss fallback) happens at patch time.
+ struct StmtSeqPatch {
+ DIEValue *Value = nullptr;
+ uint64_t InputStmtSeqOffset = 0;
+ };
+ SmallVector<StmtSeqPatch, 4> StmtSeqListAttributes;
std::mutex LabelsMutex;
/// This field keeps current stage of overall compile unit processing.
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.cpp
index c992a2321c14b..f596d40e9f7de 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.cpp
@@ -118,11 +118,14 @@ Error DwarfUnit::emitDebugInfo(const Triple &TargetTriple) {
return Error::success();
}
-Error DwarfUnit::emitDebugLine(const Triple &TargetTriple,
- const DWARFDebugLine::LineTable &OutLineTable) {
+Error DwarfUnit::emitDebugLine(
+ const Triple &TargetTriple, const DWARFDebugLine::LineTable &OutLineTable,
+ ArrayRef<uint64_t> OrigRowIndices,
+ DenseMap<uint64_t, uint64_t> *RowIndexToSeqStartOffset) {
DebugLineSectionEmitter DebugLineEmitter(TargetTriple, *this);
- return DebugLineEmitter.emit(OutLineTable);
+ return DebugLineEmitter.emit(OutLineTable, OrigRowIndices,
+ RowIndexToSeqStartOffset);
}
Error DwarfUnit::emitDebugStringOffsetSection() {
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.h
index e415edc25ff0e..18e14495d9645 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerUnit.h
@@ -95,9 +95,18 @@ class DwarfUnit : public OutputSections {
/// Emit .debug_info section for unit DIEs.
Error emitDebugInfo(const Triple &TargetTriple);
- /// Emit .debug_line section.
- Error emitDebugLine(const Triple &TargetTriple,
- const DWARFDebugLine::LineTable &OutLineTable);
+ /// Emit .debug_line section. When \p OrigRowIndices is non-empty it
+ /// must be the same length as \p OutLineTable.Rows and carry the input
+ /// row index each output row originated from (or an invalid-row
+ /// sentinel for manufactured end-of-range rows); if
+ /// \p RowIndexToSeqStartOffset is non-null, the emitter populates it
+ /// with an entry for each real row mapping input row index to the
+ /// byte offset of the DW_LNE_set_address that opens the output
+ /// sequence containing the row.
+ Error emitDebugLine(
+ const Triple &TargetTriple, const DWARFDebugLine::LineTable &OutLineTable,
+ ArrayRef<uint64_t> OrigRowIndices = {},
+ DenseMap<uint64_t, uint64_t> *RowIndexToSeqStartOffset = nullptr);
/// Emit the .debug_str_offsets section for current unit.
Error emitDebugStringOffsetSection();
diff --git a/llvm/lib/DWARFLinker/Parallel/DebugLineSectionEmitter.h b/llvm/lib/DWARFLinker/Parallel/DebugLineSectionEmitter.h
index 022eae8963173..5539f02af79e3 100644
--- a/llvm/lib/DWARFLinker/Parallel/DebugLineSectionEmitter.h
+++ b/llvm/lib/DWARFLinker/Parallel/DebugLineSectionEmitter.h
@@ -26,7 +26,9 @@ class DebugLineSectionEmitter {
DebugLineSectionEmitter(const Triple &TheTriple, DwarfUnit &U)
: TheTriple(TheTriple), U(U) {}
- Error emit(const DWARFDebugLine::LineTable &LineTable) {
+ Error emit(const DWARFDebugLine::LineTable &LineTable,
+ ArrayRef<uint64_t> OrigRowIndices = {},
+ DenseMap<uint64_t, uint64_t> *RowIndexToSeqStartOffset = nullptr) {
// FIXME: remove dependence on MCDwarfLineAddr::encode.
// As we reuse MCDwarfLineAddr::encode, we need to create/initialize
// some MC* classes.
@@ -45,7 +47,8 @@ class DebugLineSectionEmitter {
emitLineTablePrologue(LineTable.Prologue, OutSection);
// Emit rows.
- emitLineTableRows(LineTable, OutSection);
+ emitLineTableRows(LineTable, OutSection, OrigRowIndices,
+ RowIndexToSeqStartOffset);
uint64_t OffsetAfterEnd = OutSection.OS.tell();
// Update unit length field with actual length value.
@@ -292,8 +295,10 @@ class DebugLineSectionEmitter {
emitLineTablePrologueV5IncludeAndFileTable(P, Section);
}
- void emitLineTableRows(const DWARFDebugLine::LineTable &LineTable,
- SectionDescriptor &Section) {
+ void emitLineTableRows(
+ const DWARFDebugLine::LineTable &LineTable, SectionDescriptor &Section,
+ ArrayRef<uint64_t> OrigRowIndices = {},
+ DenseMap<uint64_t, uint64_t> *RowIndexToSeqStartOffset = nullptr) {
MCDwarfLineTableParams Params;
Params.DWARF2LineOpcodeBase = LineTable.Prologue.OpcodeBase;
@@ -321,10 +326,21 @@ class DebugLineSectionEmitter {
uint64_t Address = -1ULL;
unsigned RowsSinceLastSequence = 0;
-
- for (const DWARFDebugLine::Row &Row : LineTable.Rows) {
+ // Offset of the DW_LNE_set_address opcode that opens the sequence
+ // currently being emitted. Recorded per input row index so that
+ // DW_AT_LLVM_stmt_sequence attributes can be resolved by row — which
+ // is collision-free under ICF, unlike keying by output address.
+ uint64_t CurrentSeqStartOffset = 0;
+ constexpr uint64_t InvalidRowIndex = std::numeric_limits<uint64_t>::max();
+ assert(
+ (!RowIndexToSeqStartOffset ||
+ OrigRowIndices.size() == LineTable.Rows.size()) &&
+ "OrigRowIndices must be supplied alongside RowIndexToSeqStartOffset");
+
+ for (auto [Idx, Row] : llvm::enumerate(LineTable.Rows)) {
int64_t AddressDelta;
if (Address == -1ULL) {
+ CurrentSeqStartOffset = Section.OS.tell();
Section.emitIntVal(dwarf::DW_LNS_extended_op, 1);
encodeULEB128(Section.getFormParams().AddrSize + 1, Section.OS);
Section.emitIntVal(dwarf::DW_LNE_set_address, 1);
@@ -335,6 +351,11 @@ class DebugLineSectionEmitter {
AddressDelta =
(Row.Address.Address - Address) / LineTable.Prologue.MinInstLength;
}
+ if (RowIndexToSeqStartOffset) {
+ uint64_t InputRowIdx = OrigRowIndices[Idx];
+ if (InputRowIdx != InvalidRowIndex)
+ (*RowIndexToSeqStartOffset)[InputRowIdx] = CurrentSeqStartOffset;
+ }
// FIXME: code copied and transformed from
// MCDwarf.cpp::EmitDwarfLineTable. We should find a way to share this
diff --git a/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp b/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
index 9bcd2b895be88..079be51ace24a 100644
--- a/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
@@ -460,9 +460,21 @@ void OutputSections::applyPatches(
uint64_t FinalValue = Patch.SectionPtr.getPointer()->StartOffset;
// Check whether we need to read value from the original location.
- if (Patch.SectionPtr.getInt())
- FinalValue +=
+ if (Patch.SectionPtr.getInt()) {
+ uint64_t LocalValue =
Section.getIntVal(Patch.PatchOffset, Format.getDwarfOffsetByteSize());
+ // DebugOffsetPatch treats the DWARF "invalid offset" sentinel
+ // (0xffffffff for DWARF32) as pass-through: callers that can't
+ // resolve the target write that value and expect it to survive
+ // section combination unchanged. Adding StartOffset would turn it
+ // into a plausible-looking but meaningless offset. Callers that
+ // genuinely want `StartOffset + MaxOffset` don't exist today and
+ // would need a
diff erent patch type.
+ if (LocalValue == Format.getDwarfMaxOffset())
+ FinalValue = LocalValue;
+ else
+ FinalValue += LocalValue;
+ }
Section.apply(Patch.PatchOffset, dwarf::DW_FORM_sec_offset, FinalValue);
});
diff --git a/llvm/lib/DWARFLinker/Utils.cpp b/llvm/lib/DWARFLinker/Utils.cpp
index 52508c998532d..d699b25e3c349 100644
--- a/llvm/lib/DWARFLinker/Utils.cpp
+++ b/llvm/lib/DWARFLinker/Utils.cpp
@@ -7,3 +7,103 @@
//===----------------------------------------------------------------------===//
#include "llvm/DWARFLinker/Utils.h"
+#include "llvm/ADT/STLExtras.h"
+#include <limits>
+#include <map>
+
+namespace llvm {
+namespace dwarf_linker {
+
+void buildStmtSeqOffsetToFirstRowIndex(
+ const DWARFDebugLine::LineTable <,
+ ArrayRef<uint64_t> SortedStmtSeqOffsets,
+ DenseMap<uint64_t, uint64_t> &SeqOffToFirstRow) {
+ // Use std::map for ordered iteration by input stmt-sequence offset.
+ std::map<uint64_t, uint64_t> LineTableMapping;
+ for (const DWARFDebugLine::Sequence &Seq : LT.Sequences)
+ LineTableMapping[Seq.StmtSeqOffset] = Seq.FirstRowIndex;
+
+ if (LT.Rows.empty()) {
+ for (const auto &[Off, Row] : LineTableMapping)
+ SeqOffToFirstRow[Off] = Row;
+ return;
+ }
+
+ // Row indices that look like sequence starts: row 0, plus every row
+ // immediately following an end_sequence marker.
+ SmallVector<uint64_t> SeqStartRows;
+ SeqStartRows.push_back(0);
+ for (auto [I, Row] : llvm::enumerate(ArrayRef(LT.Rows).drop_back()))
+ if (Row.EndSequence)
+ SeqStartRows.push_back(I + 1);
+
+ ArrayRef<uint64_t> StmtAttrsRef(SortedStmtSeqOffsets);
+ ArrayRef<uint64_t> SeqStartRowsRef(SeqStartRows);
+
+ // While SeqOffToFirstRow parsed from LT could be the ground truth, e.g.
+ //
+ // SeqOff Row
+ // 0x08 9
+ // 0x14 15
+ //
+ // The StmtAttrs and SeqStartRows may not match perfectly, e.g.
+ //
+ // StmtAttrs SeqStartRows
+ // 0x04 3
+ // 0x08 5
+ // 0x10 9
+ // 0x12 11
+ // 0x14 15
+ //
+ // In this case, we don't want to assign 5 to 0x08, since we know 0x08
+ // maps to 9. If we do a dummy 1:1 mapping 0x10 will be mapped to 9
+ // which is incorrect. The expected behavior is ignore 5, realign the
+ // table based on the result from the line table:
+ //
+ // StmtAttrs SeqStartRows
+ // 0x04 3
+ // -- 5
+ // 0x08 9 <- LineTableMapping ground truth
+ // 0x10 11
+ // 0x12 --
+ // 0x14 15 <- LineTableMapping ground truth
+
+ // Dummy trailing anchor so both refs always drain before we run out
+ // of map entries to walk.
+ constexpr uint64_t DummyKey = std::numeric_limits<uint64_t>::max();
+ constexpr uint64_t DummyVal = std::numeric_limits<uint64_t>::max();
+ LineTableMapping[DummyKey] = DummyVal;
+
+ for (auto [NextSeqOff, NextRow] : LineTableMapping) {
+ auto StmtAttrSmallerThanNext = [N = NextSeqOff](uint64_t SA) {
+ return SA < N;
+ };
+ auto SeqStartSmallerThanNext = [N = NextRow](uint64_t Row) {
+ return Row < N;
+ };
+ // While both lists still point strictly before the next anchor,
+ // pair them up 1:1 — this captures sequences the parser missed.
+ while (!StmtAttrsRef.empty() && !SeqStartRowsRef.empty() &&
+ StmtAttrSmallerThanNext(StmtAttrsRef.front()) &&
+ SeqStartSmallerThanNext(SeqStartRowsRef.front())) {
+ SeqOffToFirstRow[StmtAttrsRef.consume_front()] =
+ SeqStartRowsRef.consume_front();
+ }
+ // Either list may now be ahead of or at the anchor: drop entries we
+ // can't safely pair, then use the parser's (NextSeqOff,NextRow)
+ // mapping as ground truth.
+ StmtAttrsRef = StmtAttrsRef.drop_while(StmtAttrSmallerThanNext);
+ SeqStartRowsRef = SeqStartRowsRef.drop_while(SeqStartSmallerThanNext);
+ if (NextSeqOff != DummyKey)
+ SeqOffToFirstRow[NextSeqOff] = NextRow;
+ // Advance each list past the anchor only if it was pointing exactly
+ // at it.
+ if (!StmtAttrsRef.empty() && StmtAttrsRef.front() == NextSeqOff)
+ StmtAttrsRef = StmtAttrsRef.drop_front();
+ if (!SeqStartRowsRef.empty() && SeqStartRowsRef.front() == NextRow)
+ SeqStartRowsRef = SeqStartRowsRef.drop_front();
+ }
+}
+
+} // namespace dwarf_linker
+} // namespace llvm
diff --git a/llvm/test/tools/dsymutil/AArch64/stmt-seq-macho.test b/llvm/test/tools/dsymutil/AArch64/stmt-seq-macho.test
index 5cb3557b5becd..b3ecb3968125c 100644
--- a/llvm/test/tools/dsymutil/AArch64/stmt-seq-macho.test
+++ b/llvm/test/tools/dsymutil/AArch64/stmt-seq-macho.test
@@ -1,10 +1,11 @@
-## Test that verifies DW_AT_LLVM_stmt_sequence attributes are correctly patched in the dSYM
-## even when line table sequences need to be reordered due to address ordering.
-##
-## This test uses two object files (a.o and b.o) linked with b.o first so that
-## b's functions get lower addresses than a's functions. When dsymutil processes
-## the debug info, it must correctly handle the case where line table sequences
-## from
diff erent compilation units need to be interleaved by address.
+## Test that verifies DW_AT_LLVM_stmt_sequence attributes are correctly
+## patched in the dSYM. Each input object (a.o and b.o) carries one
+## compile unit whose line table has two sequences (one per function).
+## After linking, dsymutil merges each CU's two function sequences into a
+## single output sequence (adjacent addresses). We verify that every
+## DW_AT_LLVM_stmt_sequence attribute in the output points at a real
+## DW_LNE_set_address opcode in the emitted .debug_line, for both the
+## classic and parallel linker backends.
# RUN: rm -rf %t && split-file %s %t && cd %t
# RUN: yaml2obj %t/stmt_seq_macho.exe.yaml -o %t/stmt_seq_macho.exe
@@ -26,7 +27,15 @@
# RUN: cat %t/stmt_seq_macho.dSYM.txt | FileCheck %s -check-prefix=CHECK_NO_INVALID_OFFSET
# CHECK_NO_INVALID_OFFSET-NOT: DW_AT_LLVM_stmt_sequence{{.*}}0xfffffff
-## FIXME: Support --linker parallel
+# RUN: dsymutil --linker parallel --flat --verify-dwarf=none -oso-prepend-path %t %t/stmt_seq_macho.exe -o %t/stmt_seq_macho.dSYM
+# RUN: llvm-dwarfdump --debug-info --debug-line -v %t/stmt_seq_macho.dSYM > %t/stmt_seq_macho.dSYM.txt
+# RUN: cat %t/stmt_seq_macho.dSYM.txt | grep "DW_AT_LLVM_stmt_sequence" | \
+# RUN: sed 's/.*(\(0x[0-9a-f]*\)).*/\1/' | sort -u > %t/stmt_offsets.txt
+# RUN: cat %t/stmt_seq_macho.dSYM.txt | grep "DW_LNE_set_address" | \
+# RUN: sed 's/^\(0x[0-9a-f]*\):.*/\1/' | sort -u > %t/setaddr_offsets.txt
+# RUN: comm -23 %t/stmt_offsets.txt %t/setaddr_offsets.txt > %t/bad_offsets.txt
+# RUN: test ! -s %t/bad_offsets.txt
+# RUN: cat %t/stmt_seq_macho.dSYM.txt | FileCheck %s -check-prefix=CHECK_NO_INVALID_OFFSET
#--- stmt_seq_macho.cpp
// This file is split into a.cpp and b.cpp by the gen script.
diff --git a/llvm/unittests/DWARFLinkerParallel/DWARFLinkerTest.cpp b/llvm/unittests/DWARFLinkerParallel/DWARFLinkerTest.cpp
index 50c91396d6a1c..0c415b98ffbe1 100644
--- a/llvm/unittests/DWARFLinkerParallel/DWARFLinkerTest.cpp
+++ b/llvm/unittests/DWARFLinkerParallel/DWARFLinkerTest.cpp
@@ -6,7 +6,9 @@
//
//===----------------------------------------------------------------------===//
+#include "llvm/ADT/DenseMap.h"
#include "llvm/DWARFLinker/Utils.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h"
#include "gtest/gtest.h"
using namespace llvm;
@@ -32,4 +34,120 @@ TEST(DWARFLinker, PathTest) {
EXPECT_FALSE(isInToolchainDir("/Foo/not-an.xctoolchain/Bar/Baz"));
}
+// Helpers for building DWARFDebugLine::LineTable fixtures. Only the fields
+// that buildStmtSeqOffsetToFirstRowIndex inspects are populated; everything
+// else defaults to Row's/Sequence's own defaults.
+namespace {
+void addRows(DWARFDebugLine::LineTable <, unsigned Count,
+ ArrayRef<unsigned> EndSequenceIndices) {
+ for (unsigned I = 0; I < Count; ++I)
+ LT.Rows.emplace_back();
+ for (unsigned I : EndSequenceIndices)
+ LT.Rows[I].EndSequence = 1;
+}
+
+void addParsedSequence(DWARFDebugLine::LineTable <, uint64_t StmtSeqOffset,
+ unsigned FirstRowIndex) {
+ DWARFDebugLine::Sequence S;
+ S.StmtSeqOffset = StmtSeqOffset;
+ S.FirstRowIndex = FirstRowIndex;
+ LT.Sequences.push_back(S);
+}
+} // namespace
+
+TEST(DWARFLinker, BuildStmtSeqOffsetToFirstRowIndex_ParserOnly) {
+ // Single parser-registered sequence covering rows [0..3). The attribute
+ // at offset 0x10 resolves straight from LT.Sequences.
+ DWARFDebugLine::LineTable LT;
+ addRows(LT, /*Count=*/3, /*EndSequenceIndices=*/{2});
+ addParsedSequence(LT, /*StmtSeqOffset=*/0x10, /*FirstRowIndex=*/0);
+
+ DenseMap<uint64_t, uint64_t> Result;
+ buildStmtSeqOffsetToFirstRowIndex(LT, /*SortedStmtSeqOffsets=*/{0x10},
+ Result);
+
+ EXPECT_EQ(Result.lookup(0x10), 0u);
+ EXPECT_EQ(Result.size(), 1u);
+}
+
+TEST(DWARFLinker, BuildStmtSeqOffsetToFirstRowIndex_ParserMissFallback) {
+ // Two real sequences in the table (rows [0..3) and [3..5)), but the
+ // parser only registered the second one. The fallback must recover
+ // the first from the end_sequence boundary at row 2.
+ DWARFDebugLine::LineTable LT;
+ addRows(LT, /*Count=*/5, /*EndSequenceIndices=*/{2, 4});
+ addParsedSequence(LT, /*StmtSeqOffset=*/0x20, /*FirstRowIndex=*/3);
+
+ DenseMap<uint64_t, uint64_t> Result;
+ buildStmtSeqOffsetToFirstRowIndex(LT, /*SortedStmtSeqOffsets=*/{0x10, 0x20},
+ Result);
+
+ EXPECT_EQ(Result.lookup(0x10), 0u); // recovered from row boundaries
+ EXPECT_EQ(Result.lookup(0x20), 3u); // parser-registered ground truth
+}
+
+TEST(DWARFLinker, BuildStmtSeqOffsetToFirstRowIndex_RealignIgnoresStale) {
+ // SeqStartRows is seeded with row 0 and then every row immediately
+ // following an end_sequence marker. For 16 rows with end_sequence at
+ // {2, 4, 8, 10, 14} that's {0, 3, 5, 9, 11, 15}.
+ //
+ // StmtAttrs {0x04, 0x08, 0x10, 0x12, 0x14}
+ // SeqStartRows {0, 3, 5, 9, 11, 15}
+ // Parser {0x08 -> 9, 0x14 -> 15}
+ //
+ // Walk:
+ // anchor (0x08, 9): (0x04, 0) < anchor -> pair 0x04 -> 0
+ // 0x08 not < 0x08 -> exit while
+ // drop seq-starts < 9 -> {9, 11, 15}
+ // ground truth -> 0x08 -> 9
+ // anchor (0x14, 15): (0x10, 11) < anchor -> pair 0x10 -> 11
+ // (0x12, 15) not < 15 -> exit while
+ // drop stmt-attrs<0x14-> {0x14}
+ // ground truth -> 0x14 -> 15
+ //
+ // 0x12 is dropped because no safe row to pair it with remains.
+ DWARFDebugLine::LineTable LT;
+ addRows(LT, /*Count=*/16, /*EndSequenceIndices=*/{2, 4, 8, 10, 14});
+ addParsedSequence(LT, 0x08, 9);
+ addParsedSequence(LT, 0x14, 15);
+
+ DenseMap<uint64_t, uint64_t> Result;
+ buildStmtSeqOffsetToFirstRowIndex(LT, {0x04, 0x08, 0x10, 0x12, 0x14}, Result);
+
+ EXPECT_EQ(Result.lookup(0x04), 0u);
+ EXPECT_EQ(Result.lookup(0x08), 9u);
+ EXPECT_EQ(Result.lookup(0x10), 11u);
+ EXPECT_FALSE(Result.contains(0x12));
+ EXPECT_EQ(Result.lookup(0x14), 15u);
+}
+
+TEST(DWARFLinker, BuildStmtSeqOffsetToFirstRowIndex_EmptyRows) {
+ // When LT.Rows is empty the builder just copies the parser-registered
+ // sequences over and returns — no fallback work possible.
+ DWARFDebugLine::LineTable LT;
+ addParsedSequence(LT, 0x10, 0);
+ addParsedSequence(LT, 0x20, 42);
+
+ DenseMap<uint64_t, uint64_t> Result;
+ buildStmtSeqOffsetToFirstRowIndex(LT, {0x10, 0x20, 0x30}, Result);
+
+ EXPECT_EQ(Result.lookup(0x10), 0u);
+ EXPECT_EQ(Result.lookup(0x20), 42u);
+ EXPECT_FALSE(Result.contains(0x30)); // no way to recover without rows
+}
+
+TEST(DWARFLinker, BuildStmtSeqOffsetToFirstRowIndex_NoAttributes) {
+ // No stmt-seq attributes to resolve: the builder still seeds the map
+ // from parser-registered sequences (harmless, helps shared callers).
+ DWARFDebugLine::LineTable LT;
+ addRows(LT, /*Count=*/3, /*EndSequenceIndices=*/{2});
+ addParsedSequence(LT, 0x10, 0);
+
+ DenseMap<uint64_t, uint64_t> Result;
+ buildStmtSeqOffsetToFirstRowIndex(LT, /*SortedStmtSeqOffsets=*/{}, Result);
+
+ EXPECT_EQ(Result.lookup(0x10), 0u);
+ EXPECT_EQ(Result.size(), 1u);
+}
+
} // anonymous namespace
More information about the llvm-commits
mailing list