[llvm] [BOLT] Fix shifted DWARF inline-scope ranges; track scope boundaries (PR #207291)
Rafael Auler via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 16:46:59 PDT 2026
https://github.com/rafaelauler updated https://github.com/llvm/llvm-project/pull/207291
>From 879f762ed59c6f4efcada0441bf16cda7cd7417d Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Wed, 24 Jun 2026 12:00:28 -0700
Subject: [PATCH 1/3] [BOLT] Fix shifted DWARF inline-scope ranges; track scope
boundaries
Summary:
BOLT updated DWARF lexical-scope ranges (DW_TAG_inlined_subroutine /
lexical_block low_pc/high_pc and DW_AT_ranges) via
translateInputToOutputRange(), which mapped a boundary using its input
offset relative to the start of the containing basic block:
OutAddr = BB.getOutputAddressRange().first + (InputOffset - BB.getOffset())
This assumes intra-block byte offsets are preserved input->output. Any
pass that changes instruction sizes within a block ahead of a scope
boundary breaks that assumption. With --plt=all, each `call foo at PLT`
(5 bytes, e8+rel32) is rewritten to `call *foo at GOT(%rip)` (6 bytes,
ff 15+rel32); N such calls before a boundary shift its emitted low_pc/
high_pc N bytes too early, onto the preceding instruction. The range
stays within the parent so `llvm-dwarfdump --verify` does not catch it;
symbolizers then attribute samples on those instructions to the wrong
inlined frames.
Fix is to resolve scope-range boundaries through the precise per-instruction
input-to-output AddressMap (the same map BAT already uses) instead of
input-relative block offsets. Boundary instructions are arbitrary (not
just calls/branches), so they are normally absent from that map; to make
them resolvable, disassembly now keeps an offset for the boundary
instructions (only the boundaries, to keep the AddressMap small) and
those offsets are emitted as AddressMap entries whenever an address map
is required (requiresAddressMap(), which includes --update-debug-sections
via requiresPreciseAddressMap()), not only under BAT.
Boundary collection (BinaryContext::collectDebugScopeBoundaries) runs in
readDebugInfo() after preprocessDebugInfo(). It streams the DIEs of the
CUs that will be updated directly from the already-loaded DWARFContext
with DWARFDebugInfoEntry::extractFast, decoding one DIE at a time into a
single reusable entry, so no DIE forest is materialized and no second
DWARF context is created. For split DWARF the .dwo DIEs are already
extracted by preprocessDWODebugInfo(), so the cached array is reused
instead. Boundaries are stored per function in a sorted, deduplicated
vector (BinaryFunction::DebugScopeBoundaryOffsets), queried during
disassembly with a monotonic cursor, and freed once the function is
disassembled, so the feature adds no global state.
The behavior is on by default and can be disabled with
--accurate-debug-ranges=0, which falls back to the old block-relative
mapping.
Test on big binaries with split dwarf, -lite=0:
wall Max RSS
-accurate-debug-ranges=0 1070.1s 94.91
-accurate-debug-ranges=1 1173.0s 100.07
delta +102.9s +5.16 (+4.6% CPU,
+5.4% RSS)
Test on big binaries with split dwarf, lite=1:
-accurate-debug-ranges=0 433.9s 83.23
-accurate-debug-ranges=1 434.7s 83.79 (+0.7% RSS)
--time-rewrite shows the boundary-collection phase itself is cheap:
"read debug ranges" is 5.8s of 4960s total user time on one binary
(0.1%) and 18.8s of 6307s on another binary; the feature's
cost is dominated by emitting/translating the extra AddressMap entries,
not by collection.
---
bolt/include/bolt/Core/BinaryContext.h | 15 +-
bolt/include/bolt/Core/BinaryFunction.h | 58 +-
bolt/lib/Core/BinaryContext.cpp | 81 +++
bolt/lib/Core/BinaryEmitter.cpp | 6 +-
bolt/lib/Core/BinaryFunction.cpp | 54 +-
bolt/lib/Rewrite/RewriteInstance.cpp | 14 +
bolt/test/X86/dwarf-inline-range-plt-shift.s | 614 +++++++++++++++++++
7 files changed, 814 insertions(+), 28 deletions(-)
create mode 100644 bolt/test/X86/dwarf-inline-range-plt-shift.s
diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index 73ccb911039de..670832288ecbd 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -1446,6 +1446,11 @@ class BinaryContext {
/// Populate some internal data structures with debug info.
void preprocessDebugInfo();
+ /// Record DWARF lexical-scope range boundaries into the containing functions'
+ /// BinaryFunction::DebugScopeBoundaryOffsets. Relies on preprocessDebugInfo's
+ /// actions: populated ProcessedCUs and pre-extracted DIEs for split dwarf.
+ void collectDebugScopeBoundaries();
+
/// Add a filename entry from SrcCUID to DestCUID.
unsigned addDebugFilenameToUnit(const uint32_t DestCUID,
const uint32_t SrcCUID, unsigned FileIndex);
@@ -1513,16 +1518,6 @@ class BinaryContext {
/// count of profiled functions.
uint64_t getHotThreshold() const;
- /// Return true if instruction \p Inst requires an offset for further
- /// processing (e.g. assigning a profile).
- bool keepOffsetForInstruction(const MCInst &Inst) const {
- if (MIB->isCall(Inst) || MIB->isBranch(Inst) || MIB->isReturn(Inst) ||
- MIB->isPrefix(Inst) || MIB->isIndirectBranch(Inst)) {
- return true;
- }
- return false;
- }
-
/// Return true if the function should be emitted to the output file.
bool shouldEmit(const BinaryFunction &Function) const;
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index 84fbd5661fd0a..df42eecb98512 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -295,6 +295,18 @@ class BinaryFunction {
/// Offsets of indirect branches with unknown destinations.
std::set<uint64_t> UnknownIndirectBranchOffsets;
+ /// Offsets of instructions that begin or end a DWARF lexical scope
+ /// (inlined_subroutine / lexical_block low_pc/high_pc and DW_AT_ranges).
+ /// Populated (unsorted, with possible duplicates) before disassembly, sorted
+ /// and deduplicated at the start of disassembly, and cleared once disassembly
+ /// of this function is done.
+ std::vector<uint64_t> DebugScopeBoundaryOffsets;
+
+ /// Cursor into the sorted DebugScopeBoundaryOffsets. Disassembly queries
+ /// offsets in monotonically increasing order, so isDebugScopeBoundaryOffset()
+ /// advances this cursor instead of binary-searching from scratch.
+ size_t DebugScopeBoundaryCursor = 0;
+
/// A set of local and global symbols corresponding to secondary entry points.
/// Each additional function entry point has a corresponding entry in the map.
/// The key is a local symbol corresponding to a basic block and the value
@@ -1348,6 +1360,42 @@ class BinaryFunction {
return InternalRefDataRelocations;
}
+ /// Append function-relative \p Offset as a DWARF lexical-scope boundary. The
+ /// list is sorted later (sortDebugScopeBoundaryOffsets) before it is queried.
+ void addDebugScopeBoundaryOffset(uint64_t Offset) {
+ DebugScopeBoundaryOffsets.push_back(Offset);
+ }
+
+ /// Sort and deduplicate the collected scope boundaries and reset the query
+ /// cursor. Call once, before the first isDebugScopeBoundaryOffset() query.
+ void sortDebugScopeBoundaryOffsets() {
+ llvm::sort(DebugScopeBoundaryOffsets);
+ DebugScopeBoundaryOffsets.erase(llvm::unique(DebugScopeBoundaryOffsets),
+ DebugScopeBoundaryOffsets.end());
+ DebugScopeBoundaryCursor = 0;
+ }
+
+ /// Return true if function-relative \p Offset begins/ends a DWARF scope.
+ /// Requires sortDebugScopeBoundaryOffsets() to have been called, and that
+ /// queries arrive with non-decreasing \p Offset. This is designed to be
+ /// used in lock-step with the disassembly loop, don't use it outside of it.
+ bool isDebugScopeBoundaryOffset(uint64_t Offset) {
+ const size_t Size = DebugScopeBoundaryOffsets.size();
+ while (DebugScopeBoundaryCursor < Size &&
+ DebugScopeBoundaryOffsets[DebugScopeBoundaryCursor] < Offset)
+ ++DebugScopeBoundaryCursor;
+ return DebugScopeBoundaryCursor < Size &&
+ DebugScopeBoundaryOffsets[DebugScopeBoundaryCursor] == Offset;
+ }
+
+ /// Return true if an "Offset" annotation should be kept for instruction
+ /// \p Inst located at function-relative \p Offset. Offsets are kept for
+ /// control-flow instructions (profile matching) and for instructions that
+ /// begin/end a DWARF lexical scope (needed to translate scope ranges
+ /// precisely; see DebugScopeBoundaryOffsets).
+ bool keepOffsetForInstruction(const MCInst &Inst,
+ uint64_t Offset);
+
/// Return the name of the section this function originated from.
std::optional<StringRef> getOriginSectionName() const {
if (!OriginSection)
@@ -2439,15 +2487,17 @@ class BinaryFunction {
/// is corrupted. If it is unable to fix it, it returns false.
bool finalizeCFIState();
- /// Return true if this function needs an address-translation table after
- /// its code emission.
- bool requiresAddressTranslation() const;
-
/// Return true if the linker needs to generate an address map for this
/// function. Used for keeping track of the mapping from input to out
/// addresses of basic blocks.
bool requiresAddressMap() const;
+ /// Return true if this function needs an address-translation table after
+ /// its code emission, or to update any metadata accurately (debug info,
+ /// SDT probes). This is gated since it incurs extra cost for the linker
+ /// to keep track of more addresses.
+ bool requiresPreciseAddressMap() const;
+
/// Adjust branch instructions to match the CFG.
///
/// As it comes to internal branches, the CFG represents "the ultimate source
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 130c523bcda54..9d07775508da6 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -49,6 +49,7 @@ using namespace llvm;
namespace opts {
extern cl::opt<bool> LargeCodeModel;
+extern cl::opt<bool> UpdateDebugSections;
static cl::opt<bool>
NoHugePages("no-huge-pages",
@@ -1962,6 +1963,86 @@ void BinaryContext::preprocessDebugInfo() {
}
}
+void BinaryContext::collectDebugScopeBoundaries() {
+ // Record DWARF lexical-scope boundaries (inlined_subroutine / lexical_block)
+ // into each containing BinaryFunction, so disassembly keeps an offset for the
+ // boundary instructions (see BinaryFunction::DebugScopeBoundaryOffsets) and
+ // translateInputToOutputRange() can map scope ranges precisely.
+
+ auto recordRange = [&](uint64_t LowPC, uint64_t HighPC) {
+ BinaryFunction *BF = getBinaryFunctionContainingAddress(LowPC);
+ if (!BF)
+ return;
+ BF->addDebugScopeBoundaryOffset(LowPC - BF->getAddress());
+ // Mark HighPC only if it lies strictly inside the same function.
+ if (HighPC > LowPC && getBinaryFunctionContainingAddress(HighPC) == BF)
+ BF->addDebugScopeBoundaryOffset(HighPC - BF->getAddress());
+ };
+
+ // Record the boundaries of a single scope DIE.
+ auto processScopeDie = [&](const DWARFDie &Die) {
+ const dwarf::Tag Tag = Die.getTag();
+ if (Tag != dwarf::DW_TAG_inlined_subroutine &&
+ Tag != dwarf::DW_TAG_lexical_block &&
+ Tag != dwarf::DW_TAG_try_block &&
+ Tag != dwarf::DW_TAG_catch_block)
+ return;
+ if (Expected<DWARFAddressRangesVector> Ranges = Die.getAddressRanges()) {
+ for (const DWARFAddressRange &R : *Ranges)
+ recordRange(R.LowPC, R.HighPC);
+ } else {
+ consumeError(Ranges.takeError());
+ }
+ };
+
+ for (const std::unique_ptr<DWARFUnit> &CUPtr : DwCtx->compile_units()) {
+ DWARFUnit *CU = CUPtr.get();
+ if (!ProcessedCUs.count(CU))
+ continue;
+
+ // Extract only the CU DIE (the .dwo's, for split DWARF). This is cheap (one
+ // DIE) and sets the unit's range/addr/str-offset bases that getAddressRanges
+ // needs, without materializing the full DIE array.
+ DWARFDie CUDie = CU->getNonSkeletonUnitDIE(/*ExtractUnitDIEOnly=*/true);
+ if (!CUDie)
+ continue;
+ DWARFUnit *DIEUnit = CUDie.getDwarfUnit();
+
+ // For split DWARF, preprocessDWODebugInfo already fully extracts the .dwo's
+ // DIE array.
+ if (DIEUnit->isDWOUnit()) {
+ for (const DWARFDebugInfoEntry &Entry : DIEUnit->dies())
+ processScopeDie(DWARFDie(DIEUnit, &Entry));
+ continue;
+ }
+ // Walk the unit's DIEs by streaming them one at a time. Track nesting depth
+ // with a counter: a DIE with children descends a level (++), a null entry
+ // (sibling-chain terminator) ascends (--), the unit-end offset limits the
+ // walk. This is done to avoid recording all DIEs in a vector like
+ // DWARFUnit's extractDIEsToVector() does, since that is more work than
+ // needed if we just want to lookup specific tags.
+ DWARFDataExtractor DebugInfoData = DIEUnit->getDebugInfoExtractor();
+ uint64_t DIEOffset = DIEUnit->getOffset() + DIEUnit->getHeaderSize();
+ const uint64_t NextCUOffset = DIEUnit->getNextUnitOffset();
+ DWARFDebugInfoEntry DIEEntry;
+ int32_t CurrentDepth = 1;
+ while (CurrentDepth > 0 && DIEOffset < NextCUOffset &&
+ DIEEntry.extractFast(*DIEUnit, &DIEOffset, DebugInfoData,
+ NextCUOffset, 0)) {
+ const DWARFAbbreviationDeclaration *Abbrev =
+ DIEEntry.getAbbreviationDeclarationPtr();
+ if (!Abbrev) {
+ // End of the current sibling chain.
+ --CurrentDepth;
+ continue;
+ }
+ processScopeDie(DWARFDie(DIEUnit, &DIEEntry));
+ if (Abbrev->hasChildren())
+ ++CurrentDepth;
+ }
+ }
+}
+
bool BinaryContext::shouldEmit(const BinaryFunction &Function) const {
if (Function.isPseudo())
return false;
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index a555c7185448e..9243b6701b918 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -481,8 +481,10 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
}
// Prepare to tag this location with a label if we need to keep track of
- // the location of calls/returns for BOLT address translation maps
- if (BF.requiresAddressTranslation() && BC.MIB->getOffset(Instr)) {
+ // an instruction's output address to augment the IO address map (BAT,
+ // SDT/probe address translation, or --update-debug-sections DWARF range
+ // updates).
+ if (BF.requiresPreciseAddressMap() && BC.MIB->getOffset(Instr)) {
const uint32_t Offset = *BC.MIB->getOffset(Instr);
if (!InstrLabel)
InstrLabel = BC.Ctx->createTempSymbol();
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 200e286d8e80e..6c23bbd1ff8c8 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1302,6 +1302,15 @@ BinaryFunction::getInstructionSequenceLength(uint64_t Offset,
return Current - Offset;
}
+bool BinaryFunction::keepOffsetForInstruction(const MCInst &Inst,
+ uint64_t Offset) {
+ MCPlusBuilder &MIB = *BC.MIB;
+ if (MIB.isCall(Inst) || MIB.isBranch(Inst) || MIB.isReturn(Inst) ||
+ MIB.isPrefix(Inst) || MIB.isIndirectBranch(Inst))
+ return true;
+ return isDebugScopeBoundaryOffset(Offset);
+}
+
Error BinaryFunction::disassemble() {
NamedRegionTimer T("disassemble", "Disassemble function", "buildfuncs",
"Build Binary Functions", opts::TimeBuild);
@@ -1327,6 +1336,10 @@ Error BinaryFunction::disassemble() {
// corresponding %pcrel_hi.
LabelsMapType InstructionLabels;
+ // Scope boundaries were appended unsorted during preprocessDebugInfo(); sort
+ // them so keepOffsetForInstruction() can query them below.
+ sortDebugScopeBoundaryOffsets();
+
uint64_t Size = 0; // instruction size
for (uint64_t Offset = 0; Offset < getSize(); Offset += Size) {
MCInst Instruction;
@@ -1550,8 +1563,9 @@ Error BinaryFunction::disassemble() {
}
}
- // Record offset of the instruction for profile matching.
- if (BC.keepOffsetForInstruction(Instruction))
+ // Record offset of the instruction for profile matching, for control-flow
+ // instructions, and for instructions on a DWARF lexical-scope boundary.
+ if (keepOffsetForInstruction(Instruction, Offset))
MIB->setOffset(Instruction, static_cast<uint32_t>(Offset));
if (BC.isX86() && BC.MIB->isNoop(Instruction)) {
@@ -1564,6 +1578,9 @@ Error BinaryFunction::disassemble() {
addInstruction(Offset, std::move(Instruction));
}
+ // Scope-boundary markers are only consulted while assigning offsets above.
+ clearList(DebugScopeBoundaryOffsets);
+
for (auto [Offset, Label] : InstructionLabels) {
InstrMapType::iterator II = Instructions.find(Offset);
assert(II != Instructions.end() && "reference to non-existing instruction");
@@ -2590,10 +2607,16 @@ void BinaryFunction::postProcessCFG() {
// The final cleanup of intermediate structures.
clearList(IgnoredBranches);
- // Remove "Offset" annotations, unless we need an address-translation table
- // later. This has no cost, since annotations are allocated by a bumpptr
- // allocator and won't be released anyway until late in the pipeline.
- if (!requiresAddressTranslation() && !opts::Instrument) {
+ // Remove "Offset" annotations, unless we need to build an instruction
+ // accurate IO address map later (used for BAT, SDT/pseudo-probe address
+ // translation, or --update-debug-sections, which relies on per-instruction
+ // offsets to translate DWARF scope ranges).
+ // This has no cost in memory for the BF object, since annotations are
+ // allocated by a bumpptr allocator and won't be released anyway until late
+ // in the pipeline, but the IO address map that is derived from these offsets
+ // does add non-trivial overhead and can get expensive -- that's why we don't
+ // add offsets to every single instruction.
+ if (!requiresPreciseAddressMap() && !opts::Instrument) {
for (BinaryBasicBlock &BB : blocks())
for (MCInst &Inst : BB)
BC.MIB->clearOffset(Inst);
@@ -3261,16 +3284,16 @@ bool BinaryFunction::finalizeCFIState() {
return true;
}
-bool BinaryFunction::requiresAddressTranslation() const {
- return opts::EnableBAT || hasSDTMarker() || hasPseudoProbe();
+bool BinaryFunction::requiresPreciseAddressMap() const {
+ return opts::UpdateDebugSections || opts::EnableBAT || hasSDTMarker() ||
+ hasPseudoProbe();
}
bool BinaryFunction::requiresAddressMap() const {
if (isInjected())
return false;
- return opts::UpdateDebugSections || isMultiEntry() ||
- requiresAddressTranslation();
+ return isMultiEntry() || requiresPreciseAddressMap();
}
uint64_t BinaryFunction::getInstructionCount() const {
@@ -4666,8 +4689,6 @@ uint64_t BinaryFunction::translateInputToOutputAddress(uint64_t Address) const {
return *OutputAddress;
}
- // FIXME: #18950828 - we rely on relative offsets inside basic blocks to stay
- // intact. Instead we can use pseudo instructions and/or annotations.
const uint64_t Offset = Address - getAddress();
const BinaryBasicBlock *BB = getBasicBlockContainingOffset(Offset);
if (!BB) {
@@ -4735,6 +4756,15 @@ BinaryFunction::translateInputToOutputRange(DebugAddressRange InRange) const {
// to /p Offset. The output address should fall within the same basic
// block boundaries.
auto translateBlockOffset = [&](const uint64_t Offset) {
+ // Prefer the precise per-instruction input-to-output address map when
+ // available. The block-relative fallback below assumes intra-block byte
+ // offsets are preserved from input to output, which is not true after a
+ // size-changing pass (e.g. PLT optimization growing call at PLT -> call
+ // *@GOT) runs ahead of the boundary, and silently shifts the range.
+ if (BC.hasIOAddressMap())
+ if (std::optional<uint64_t> OutAddr = BC.getIOAddressMap().lookup(
+ getAddress() + BB.getOffset() + Offset))
+ return *OutAddr;
const uint64_t OutAddress = BB.getOutputAddressRange().first + Offset;
return std::min(OutAddress, BB.getOutputAddressRange().second);
};
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 9e00d48e03853..49ea2ec198efe 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -265,6 +265,14 @@ static cl::opt<std::string> DWPPathName("dwp",
cl::Hidden, cl::init(""),
cl::cat(BoltCategory));
+static cl::opt<bool> AccurateDebugRanges(
+ "accurate-debug-ranges",
+ cl::desc("with --update-debug-sections, track DWARF lexical-scope "
+ "boundaries so scope ranges are translated precisely (instead of "
+ "via input-relative block offsets). Disable to trade range "
+ "accuracy for lower memory/time."),
+ cl::init(true), cl::Hidden, cl::cat(BoltCategory));
+
static cl::opt<bool>
UseGnuStack("use-gnu-stack",
cl::desc("use GNU_STACK program header for new segment (workaround for "
@@ -3586,6 +3594,12 @@ void RewriteInstance::readDebugInfo() {
return;
BC->preprocessDebugInfo();
+
+ if (opts::AccurateDebugRanges) {
+ NamedRegionTimer T("readDebugRanges", "read debug ranges", TimerGroupName,
+ TimerGroupDesc, opts::TimeRewrite);
+ BC->collectDebugScopeBoundaries();
+ }
}
void RewriteInstance::preprocessProfileData() {
diff --git a/bolt/test/X86/dwarf-inline-range-plt-shift.s b/bolt/test/X86/dwarf-inline-range-plt-shift.s
new file mode 100644
index 0000000000000..12ef0ddaca0da
--- /dev/null
+++ b/bolt/test/X86/dwarf-inline-range-plt-shift.s
@@ -0,0 +1,614 @@
+## Regression test for inlined_subroutine address-range corruption.
+##
+## BOLT updates DWARF lexical-scope ranges (DW_TAG_inlined_subroutine /
+## lexical_block low_pc/high_pc and DW_AT_ranges) via
+## BinaryFunction::translateInputToOutputRange(), which maps a boundary using
+## its input offset relative to the start of the containing basic block:
+##
+## OutAddr = BB.getOutputAddressRange().first + (InputOffset - BB.getOffset())
+##
+## This assumes intra-block byte offsets are preserved input->output. Any pass
+## that changes instruction sizes within a block with scope boundary breaks that
+## assumption. Here --plt=all rewrites three `call ext at PLT` (5 bytes, e8+rel32)
+## into `call *ext at GOT(%rip)` (6 bytes, ff 15+rel32), growing the block by +3B
+## before the inlined copy of inl() begins. As a result the inlined_subroutine's
+## low_pc is emitted 3 bytes too early -- landing inside the preceding converted
+## call instruction instead of on inl()'s first instruction (the `leaq`).
+##
+## The range stays within the parent, so `llvm-dwarfdump --verify` does not catch
+## it; we check the exact low_pc against the disassembled instruction address.
+##
+## Source (compiled with -O2 -g -gdwarf-4):
+## extern long exta(long), extb(long), extc(long); // resolved from a DSO -> PLT
+## static inline __attribute__((always_inline)) long inl(long x){ return x*3+7; }
+## __attribute__((noinline)) long foo(long a){
+## long t = exta(a); t = extb(t); t = extc(t); return inl(t);
+## }
+## int main(int argc, char **argv){ return (int)foo(argc); }
+##
+## The fix is enabled through --accurate-debug-ranges and should track all dwarf
+## inline scopes, translating them accordingly.
+
+# REQUIRES: system-linux
+
+## Build a DSO so the exta/extb/extc calls go through the PLT.
+# RUN: echo 'long exta(long x){return x+1;} long extb(long x){return x+2;} long extc(long x){return x+3;}' \
+# RUN: | %clang %cflags -fpic -shared -xc - -o %t.so
+# RUN: %clang %cflags -gdwarf-4 %s %t.so -o %t.exe -Wl,-q -no-pie
+# RUN: llvm-bolt %t.exe -o %t.bolt --plt=all --update-debug-sections
+## Concatenate disassembly then debug-info so FileCheck can capture the
+## instruction address and compare it against the inline scope's low_pc.
+# RUN: llvm-objdump -d --no-show-raw-insn %t.bolt > %t.out
+# RUN: llvm-dwarfdump --debug-info %t.bolt >> %t.out
+# RUN: FileCheck %s < %t.out
+
+## inl() is inlined into foo() right after the three (now GOT-indirect) calls.
+## Its inlined_subroutine must start exactly at inl()'s first instruction.
+# CHECK-LABEL: <foo>:
+# CHECK: [[INL:[0-9a-f]+]]:{{.*}}leaq (%rax,%rax,2), %rax
+# CHECK: DW_TAG_inlined_subroutine
+# CHECK: DW_AT_abstract_origin {{.*}}"inl"
+# CHECK-NEXT: DW_AT_low_pc {{.*}}0x{{0*}}[[INL]])
+
+ .att_syntax
+ .file "t.c"
+ .text
+ .globl foo # -- Begin function foo
+ .p2align 4, 0x90
+ .type foo, at function
+foo: # @foo
+.Lfunc_begin0:
+ .file 1 "." "t.c"
+ .loc 1 9 0 # t.c:9:0
+ .cfi_startproc
+# %bb.0: # %entry
+ #DEBUG_VALUE: foo:a <- $rdi
+ pushq %rax
+ .cfi_def_cfa_offset 16
+.Ltmp0:
+ .loc 1 10 12 prologue_end # t.c:10:12
+ callq exta at PLT
+.Ltmp1:
+ #DEBUG_VALUE: foo:a <- [DW_OP_LLVM_entry_value 1] $rdi
+ #DEBUG_VALUE: foo:t <- $rax
+ .loc 1 11 7 # t.c:11:7
+ movq %rax, %rdi
+ callq extb at PLT
+.Ltmp2:
+ #DEBUG_VALUE: foo:t <- $rax
+ .loc 1 12 7 # t.c:12:7
+ movq %rax, %rdi
+ callq extc at PLT
+.Ltmp3:
+ #DEBUG_VALUE: foo:t <- $rax
+ #DEBUG_VALUE: inl:x <- $rax
+ .loc 1 6 16 # t.c:6:16 @[ t.c:13:10 ]
+ leaq (%rax,%rax,2), %rax
+.Ltmp4:
+ addq $7, %rax
+.Ltmp5:
+ .loc 1 13 3 epilogue_begin # t.c:13:3
+ popq %rcx
+ .cfi_def_cfa_offset 8
+ retq
+.Ltmp6:
+.Lfunc_end0:
+ .size foo, .Lfunc_end0-foo
+ .cfi_endproc
+ # -- End function
+ .globl main # -- Begin function main
+ .p2align 4, 0x90
+ .type main, at function
+main: # @main
+.Lfunc_begin1:
+ .cfi_startproc
+# %bb.0: # %entry
+ #DEBUG_VALUE: main:argc <- $edi
+ #DEBUG_VALUE: main:argv <- $rsi
+ .loc 1 16 51 prologue_end # t.c:16:51
+ movslq %edi, %rdi
+.Ltmp7:
+ #DEBUG_VALUE: main:argc <- [DW_OP_LLVM_entry_value 1] $edi
+ .loc 1 16 47 is_stmt 0 # t.c:16:47
+ jmp foo # TAILCALL
+.Ltmp8:
+.Lfunc_end1:
+ .size main, .Lfunc_end1-main
+ .cfi_endproc
+ # -- End function
+ .section .debug_loc,"", at progbits
+.Ldebug_loc0:
+ .quad .Lfunc_begin0-.Lfunc_begin0
+ .quad .Ltmp1-.Lfunc_begin0
+ .short 1 # Loc expr size
+ .byte 85 # DW_OP_reg5
+ .quad .Ltmp1-.Lfunc_begin0
+ .quad .Lfunc_end0-.Lfunc_begin0
+ .short 4 # Loc expr size
+ .byte 243 # DW_OP_GNU_entry_value
+ .byte 1 # 1
+ .byte 85 # DW_OP_reg5
+ .byte 159 # DW_OP_stack_value
+ .quad 0
+ .quad 0
+.Ldebug_loc1:
+ .quad .Ltmp1-.Lfunc_begin0
+ .quad .Ltmp4-.Lfunc_begin0
+ .short 1 # Loc expr size
+ .byte 80 # DW_OP_reg0
+ .quad 0
+ .quad 0
+.Ldebug_loc2:
+ .quad .Ltmp3-.Lfunc_begin0
+ .quad .Ltmp4-.Lfunc_begin0
+ .short 1 # Loc expr size
+ .byte 80 # DW_OP_reg0
+ .quad 0
+ .quad 0
+.Ldebug_loc3:
+ .quad .Lfunc_begin1-.Lfunc_begin0
+ .quad .Ltmp7-.Lfunc_begin0
+ .short 1 # Loc expr size
+ .byte 85 # super-register DW_OP_reg5
+ .quad .Ltmp7-.Lfunc_begin0
+ .quad .Lfunc_end1-.Lfunc_begin0
+ .short 4 # Loc expr size
+ .byte 243 # DW_OP_GNU_entry_value
+ .byte 1 # 1
+ .byte 85 # super-register DW_OP_reg5
+ .byte 159 # DW_OP_stack_value
+ .quad 0
+ .quad 0
+ .section .debug_abbrev,"", at progbits
+ .byte 1 # Abbreviation Code
+ .byte 17 # DW_TAG_compile_unit
+ .byte 1 # DW_CHILDREN_yes
+ .byte 37 # DW_AT_producer
+ .byte 14 # DW_FORM_strp
+ .byte 19 # DW_AT_language
+ .byte 5 # DW_FORM_data2
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 16 # DW_AT_stmt_list
+ .byte 23 # DW_FORM_sec_offset
+ .byte 27 # DW_AT_comp_dir
+ .byte 14 # DW_FORM_strp
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 18 # DW_AT_high_pc
+ .byte 6 # DW_FORM_data4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 2 # Abbreviation Code
+ .byte 36 # DW_TAG_base_type
+ .byte 0 # DW_CHILDREN_no
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 62 # DW_AT_encoding
+ .byte 11 # DW_FORM_data1
+ .byte 11 # DW_AT_byte_size
+ .byte 11 # DW_FORM_data1
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 3 # Abbreviation Code
+ .byte 46 # DW_TAG_subprogram
+ .byte 1 # DW_CHILDREN_yes
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 39 # DW_AT_prototyped
+ .byte 25 # DW_FORM_flag_present
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 32 # DW_AT_inline
+ .byte 11 # DW_FORM_data1
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 4 # Abbreviation Code
+ .byte 5 # DW_TAG_formal_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 5 # Abbreviation Code
+ .byte 46 # DW_TAG_subprogram
+ .byte 1 # DW_CHILDREN_yes
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 18 # DW_AT_high_pc
+ .byte 6 # DW_FORM_data4
+ .byte 64 # DW_AT_frame_base
+ .byte 24 # DW_FORM_exprloc
+ .ascii "\227B" # DW_AT_GNU_all_call_sites
+ .byte 25 # DW_FORM_flag_present
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 39 # DW_AT_prototyped
+ .byte 25 # DW_FORM_flag_present
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 63 # DW_AT_external
+ .byte 25 # DW_FORM_flag_present
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 6 # Abbreviation Code
+ .byte 5 # DW_TAG_formal_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 2 # DW_AT_location
+ .byte 23 # DW_FORM_sec_offset
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 7 # Abbreviation Code
+ .byte 52 # DW_TAG_variable
+ .byte 0 # DW_CHILDREN_no
+ .byte 2 # DW_AT_location
+ .byte 23 # DW_FORM_sec_offset
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 8 # Abbreviation Code
+ .byte 29 # DW_TAG_inlined_subroutine
+ .byte 1 # DW_CHILDREN_yes
+ .byte 49 # DW_AT_abstract_origin
+ .byte 19 # DW_FORM_ref4
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 18 # DW_AT_high_pc
+ .byte 6 # DW_FORM_data4
+ .byte 88 # DW_AT_call_file
+ .byte 11 # DW_FORM_data1
+ .byte 89 # DW_AT_call_line
+ .byte 11 # DW_FORM_data1
+ .byte 87 # DW_AT_call_column
+ .byte 11 # DW_FORM_data1
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 9 # Abbreviation Code
+ .byte 5 # DW_TAG_formal_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 2 # DW_AT_location
+ .byte 23 # DW_FORM_sec_offset
+ .byte 49 # DW_AT_abstract_origin
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 10 # Abbreviation Code
+ .ascii "\211\202\001" # DW_TAG_GNU_call_site
+ .byte 1 # DW_CHILDREN_yes
+ .byte 49 # DW_AT_abstract_origin
+ .byte 19 # DW_FORM_ref4
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 11 # Abbreviation Code
+ .ascii "\212\202\001" # DW_TAG_GNU_call_site_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 2 # DW_AT_location
+ .byte 24 # DW_FORM_exprloc
+ .ascii "\221B" # DW_AT_GNU_call_site_value
+ .byte 24 # DW_FORM_exprloc
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 12 # Abbreviation Code
+ .ascii "\211\202\001" # DW_TAG_GNU_call_site
+ .byte 0 # DW_CHILDREN_no
+ .byte 49 # DW_AT_abstract_origin
+ .byte 19 # DW_FORM_ref4
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 13 # Abbreviation Code
+ .byte 46 # DW_TAG_subprogram
+ .byte 1 # DW_CHILDREN_yes
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 39 # DW_AT_prototyped
+ .byte 25 # DW_FORM_flag_present
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 60 # DW_AT_declaration
+ .byte 25 # DW_FORM_flag_present
+ .byte 63 # DW_AT_external
+ .byte 25 # DW_FORM_flag_present
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 14 # Abbreviation Code
+ .byte 5 # DW_TAG_formal_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 15 # Abbreviation Code
+ .byte 5 # DW_TAG_formal_parameter
+ .byte 0 # DW_CHILDREN_no
+ .byte 2 # DW_AT_location
+ .byte 24 # DW_FORM_exprloc
+ .byte 3 # DW_AT_name
+ .byte 14 # DW_FORM_strp
+ .byte 58 # DW_AT_decl_file
+ .byte 11 # DW_FORM_data1
+ .byte 59 # DW_AT_decl_line
+ .byte 11 # DW_FORM_data1
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 16 # Abbreviation Code
+ .ascii "\211\202\001" # DW_TAG_GNU_call_site
+ .byte 1 # DW_CHILDREN_yes
+ .byte 49 # DW_AT_abstract_origin
+ .byte 19 # DW_FORM_ref4
+ .ascii "\225B" # DW_AT_GNU_tail_call
+ .byte 25 # DW_FORM_flag_present
+ .byte 17 # DW_AT_low_pc
+ .byte 1 # DW_FORM_addr
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 17 # Abbreviation Code
+ .byte 15 # DW_TAG_pointer_type
+ .byte 0 # DW_CHILDREN_no
+ .byte 73 # DW_AT_type
+ .byte 19 # DW_FORM_ref4
+ .byte 0 # EOM(1)
+ .byte 0 # EOM(2)
+ .byte 0 # EOM(3)
+ .section .debug_info,"", at progbits
+.Lcu_begin0:
+ .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
+.Ldebug_info_start0:
+ .short 4 # DWARF version number
+ .long .debug_abbrev # Offset Into Abbrev. Section
+ .byte 8 # Address Size (in bytes)
+ .byte 1 # Abbrev [1] 0xb:0x16c DW_TAG_compile_unit
+ .long .Linfo_string0 # DW_AT_producer
+ .short 29 # DW_AT_language
+ .long .Linfo_string1 # DW_AT_name
+ .long .Lline_table_start0 # DW_AT_stmt_list
+ .long .Linfo_string2 # DW_AT_comp_dir
+ .quad .Lfunc_begin0 # DW_AT_low_pc
+ .long .Lfunc_end1-.Lfunc_begin0 # DW_AT_high_pc
+ .byte 2 # Abbrev [2] 0x2a:0x7 DW_TAG_base_type
+ .long .Linfo_string3 # DW_AT_name
+ .byte 5 # DW_AT_encoding
+ .byte 4 # DW_AT_byte_size
+ .byte 3 # Abbrev [3] 0x31:0x18 DW_TAG_subprogram
+ .long .Linfo_string4 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 5 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 73 # DW_AT_type
+ .byte 1 # DW_AT_inline
+ .byte 4 # Abbrev [4] 0x3d:0xb DW_TAG_formal_parameter
+ .long .Linfo_string6 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 5 # DW_AT_decl_line
+ .long 73 # DW_AT_type
+ .byte 0 # End Of Children Mark
+ .byte 2 # Abbrev [2] 0x49:0x7 DW_TAG_base_type
+ .long .Linfo_string5 # DW_AT_name
+ .byte 5 # DW_AT_encoding
+ .byte 8 # DW_AT_byte_size
+ .byte 5 # Abbrev [5] 0x50:0x85 DW_TAG_subprogram
+ .quad .Lfunc_begin0 # DW_AT_low_pc
+ .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
+ .byte 1 # DW_AT_frame_base
+ .byte 87
+ # DW_AT_GNU_all_call_sites
+ .long .Linfo_string10 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 9 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 73 # DW_AT_type
+ # DW_AT_external
+ .byte 6 # Abbrev [6] 0x69:0xf DW_TAG_formal_parameter
+ .long .Ldebug_loc0 # DW_AT_location
+ .long .Linfo_string12 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 9 # DW_AT_decl_line
+ .long 73 # DW_AT_type
+ .byte 7 # Abbrev [7] 0x78:0xf DW_TAG_variable
+ .long .Ldebug_loc1 # DW_AT_location
+ .long .Linfo_string13 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 10 # DW_AT_decl_line
+ .long 73 # DW_AT_type
+ .byte 8 # Abbrev [8] 0x87:0x1e DW_TAG_inlined_subroutine
+ .long 49 # DW_AT_abstract_origin
+ .quad .Ltmp3 # DW_AT_low_pc
+ .long .Ltmp5-.Ltmp3 # DW_AT_high_pc
+ .byte 1 # DW_AT_call_file
+ .byte 13 # DW_AT_call_line
+ .byte 10 # DW_AT_call_column
+ .byte 9 # Abbrev [9] 0x9b:0x9 DW_TAG_formal_parameter
+ .long .Ldebug_loc2 # DW_AT_location
+ .long 61 # DW_AT_abstract_origin
+ .byte 0 # End Of Children Mark
+ .byte 10 # Abbrev [10] 0xa5:0x15 DW_TAG_GNU_call_site
+ .long 213 # DW_AT_abstract_origin
+ .quad .Ltmp1 # DW_AT_low_pc
+ .byte 11 # Abbrev [11] 0xb2:0x7 DW_TAG_GNU_call_site_parameter
+ .byte 1 # DW_AT_location
+ .byte 85
+ .byte 3 # DW_AT_GNU_call_site_value
+ .byte 243
+ .byte 1
+ .byte 85
+ .byte 0 # End Of Children Mark
+ .byte 12 # Abbrev [12] 0xba:0xd DW_TAG_GNU_call_site
+ .long 230 # DW_AT_abstract_origin
+ .quad .Ltmp2 # DW_AT_low_pc
+ .byte 12 # Abbrev [12] 0xc7:0xd DW_TAG_GNU_call_site
+ .long 247 # DW_AT_abstract_origin
+ .quad .Ltmp3 # DW_AT_low_pc
+ .byte 0 # End Of Children Mark
+ .byte 13 # Abbrev [13] 0xd5:0x11 DW_TAG_subprogram
+ .long .Linfo_string7 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 1 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 73 # DW_AT_type
+ # DW_AT_declaration
+ # DW_AT_external
+ .byte 14 # Abbrev [14] 0xe0:0x5 DW_TAG_formal_parameter
+ .long 73 # DW_AT_type
+ .byte 0 # End Of Children Mark
+ .byte 13 # Abbrev [13] 0xe6:0x11 DW_TAG_subprogram
+ .long .Linfo_string8 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 2 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 73 # DW_AT_type
+ # DW_AT_declaration
+ # DW_AT_external
+ .byte 14 # Abbrev [14] 0xf1:0x5 DW_TAG_formal_parameter
+ .long 73 # DW_AT_type
+ .byte 0 # End Of Children Mark
+ .byte 13 # Abbrev [13] 0xf7:0x11 DW_TAG_subprogram
+ .long .Linfo_string9 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 3 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 73 # DW_AT_type
+ # DW_AT_declaration
+ # DW_AT_external
+ .byte 14 # Abbrev [14] 0x102:0x5 DW_TAG_formal_parameter
+ .long 73 # DW_AT_type
+ .byte 0 # End Of Children Mark
+ .byte 5 # Abbrev [5] 0x108:0x5d DW_TAG_subprogram
+ .quad .Lfunc_begin1 # DW_AT_low_pc
+ .long .Lfunc_end1-.Lfunc_begin1 # DW_AT_high_pc
+ .byte 1 # DW_AT_frame_base
+ .byte 87
+ # DW_AT_GNU_all_call_sites
+ .long .Linfo_string11 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 16 # DW_AT_decl_line
+ # DW_AT_prototyped
+ .long 42 # DW_AT_type
+ # DW_AT_external
+ .byte 6 # Abbrev [6] 0x121:0xf DW_TAG_formal_parameter
+ .long .Ldebug_loc3 # DW_AT_location
+ .long .Linfo_string14 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 16 # DW_AT_decl_line
+ .long 42 # DW_AT_type
+ .byte 15 # Abbrev [15] 0x130:0xd DW_TAG_formal_parameter
+ .byte 1 # DW_AT_location
+ .byte 84
+ .long .Linfo_string15 # DW_AT_name
+ .byte 1 # DW_AT_decl_file
+ .byte 16 # DW_AT_decl_line
+ .long 357 # DW_AT_type
+ .byte 16 # Abbrev [16] 0x13d:0x27 DW_TAG_GNU_call_site
+ .long 80 # DW_AT_abstract_origin
+ # DW_AT_GNU_tail_call
+ .quad .Ltmp8 # DW_AT_low_pc
+ .byte 11 # Abbrev [11] 0x14a:0x19 DW_TAG_GNU_call_site_parameter
+ .byte 1 # DW_AT_location
+ .byte 85
+ .byte 21 # DW_AT_GNU_call_site_value
+ .byte 243
+ .byte 1
+ .byte 85
+ .byte 16
+ .ascii "\377\377\377\377\017"
+ .byte 26
+ .byte 18
+ .byte 16
+ .byte 31
+ .byte 37
+ .byte 48
+ .byte 32
+ .byte 30
+ .byte 16
+ .byte 32
+ .byte 36
+ .byte 33
+ .byte 0 # End Of Children Mark
+ .byte 0 # End Of Children Mark
+ .byte 17 # Abbrev [17] 0x165:0x5 DW_TAG_pointer_type
+ .long 362 # DW_AT_type
+ .byte 17 # Abbrev [17] 0x16a:0x5 DW_TAG_pointer_type
+ .long 367 # DW_AT_type
+ .byte 2 # Abbrev [2] 0x16f:0x7 DW_TAG_base_type
+ .long .Linfo_string16 # DW_AT_name
+ .byte 6 # DW_AT_encoding
+ .byte 1 # DW_AT_byte_size
+ .byte 0 # End Of Children Mark
+.Ldebug_info_end0:
+ .section .debug_str,"MS", at progbits,1
+.Linfo_string0:
+ .asciz ".........clang version 23........................................................................................................................" # string offset=0 ;
+.Linfo_string1:
+ .asciz "t.c" # string offset=146 ; t.c
+.Linfo_string2:
+ .asciz "." # string offset=150 ; .
+.Linfo_string3:
+ .asciz "int" # string offset=164 ; int
+.Linfo_string4:
+ .asciz "inl" # string offset=168 ; inl
+.Linfo_string5:
+ .asciz "long" # string offset=172 ; long
+.Linfo_string6:
+ .asciz "x" # string offset=177 ; x
+.Linfo_string7:
+ .asciz "exta" # string offset=179 ; exta
+.Linfo_string8:
+ .asciz "extb" # string offset=184 ; extb
+.Linfo_string9:
+ .asciz "extc" # string offset=189 ; extc
+.Linfo_string10:
+ .asciz "foo" # string offset=194 ; foo
+.Linfo_string11:
+ .asciz "main" # string offset=198 ; main
+.Linfo_string12:
+ .asciz "a" # string offset=203 ; a
+.Linfo_string13:
+ .asciz "t" # string offset=205 ; t
+.Linfo_string14:
+ .asciz "argc" # string offset=207 ; argc
+.Linfo_string15:
+ .asciz "argv" # string offset=212 ; argv
+.Linfo_string16:
+ .asciz "char" # string offset=217 ; char
+ .ident "clang version 23"
+ .section ".note.GNU-stack","", at progbits
+ .addrsig
+ .section .debug_line,"", at progbits
+.Lline_table_start0:
>From 170ad9e8b52fb89d1d3de0ad261cc3cbff1eaeb2 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Mon, 6 Jul 2026 15:13:01 -0700
Subject: [PATCH 2/3] Formatting
---
bolt/include/bolt/Core/BinaryFunction.h | 3 +--
bolt/lib/Core/BinaryContext.cpp | 7 +++----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index df42eecb98512..d1c71479cc7d6 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -1393,8 +1393,7 @@ class BinaryFunction {
/// control-flow instructions (profile matching) and for instructions that
/// begin/end a DWARF lexical scope (needed to translate scope ranges
/// precisely; see DebugScopeBoundaryOffsets).
- bool keepOffsetForInstruction(const MCInst &Inst,
- uint64_t Offset);
+ bool keepOffsetForInstruction(const MCInst &Inst, uint64_t Offset);
/// Return the name of the section this function originated from.
std::optional<StringRef> getOriginSectionName() const {
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 9d07775508da6..733dfe6fa0535 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1983,8 +1983,7 @@ void BinaryContext::collectDebugScopeBoundaries() {
auto processScopeDie = [&](const DWARFDie &Die) {
const dwarf::Tag Tag = Die.getTag();
if (Tag != dwarf::DW_TAG_inlined_subroutine &&
- Tag != dwarf::DW_TAG_lexical_block &&
- Tag != dwarf::DW_TAG_try_block &&
+ Tag != dwarf::DW_TAG_lexical_block && Tag != dwarf::DW_TAG_try_block &&
Tag != dwarf::DW_TAG_catch_block)
return;
if (Expected<DWARFAddressRangesVector> Ranges = Die.getAddressRanges()) {
@@ -2001,8 +2000,8 @@ void BinaryContext::collectDebugScopeBoundaries() {
continue;
// Extract only the CU DIE (the .dwo's, for split DWARF). This is cheap (one
- // DIE) and sets the unit's range/addr/str-offset bases that getAddressRanges
- // needs, without materializing the full DIE array.
+ // DIE) and sets the unit's range/addr/str-offset bases that
+ // getAddressRanges needs, without materializing the full DIE array.
DWARFDie CUDie = CU->getNonSkeletonUnitDIE(/*ExtractUnitDIEOnly=*/true);
if (!CUDie)
continue;
>From 60ea4b3f69eb3bb087bcf358748283c2514ac6d0 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Mon, 13 Jul 2026 15:08:00 -0700
Subject: [PATCH 3/3] Change DebugScopeBoundaryOffsets to be stored in
SparseBitVector instead of sorted vector
---
bolt/include/bolt/Core/BinaryFunction.h | 43 ++++++-------------------
bolt/lib/Core/BinaryContext.cpp | 6 ++--
bolt/lib/Core/BinaryFunction.cpp | 10 ++----
3 files changed, 17 insertions(+), 42 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index d1c71479cc7d6..029a514d2b4b5 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -39,6 +39,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SparseBitVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/iterator.h"
#include "llvm/ADT/iterator_range.h"
@@ -297,15 +298,9 @@ class BinaryFunction {
/// Offsets of instructions that begin or end a DWARF lexical scope
/// (inlined_subroutine / lexical_block low_pc/high_pc and DW_AT_ranges).
- /// Populated (unsorted, with possible duplicates) before disassembly, sorted
- /// and deduplicated at the start of disassembly, and cleared once disassembly
- /// of this function is done.
- std::vector<uint64_t> DebugScopeBoundaryOffsets;
-
- /// Cursor into the sorted DebugScopeBoundaryOffsets. Disassembly queries
- /// offsets in monotonically increasing order, so isDebugScopeBoundaryOffset()
- /// advances this cursor instead of binary-searching from scratch.
- size_t DebugScopeBoundaryCursor = 0;
+ /// Populated before disassembly and cleared once disassembly of this
+ /// function is done.
+ SparseBitVector<> DebugScopeBoundaryOffsets;
/// A set of local and global symbols corresponding to secondary entry points.
/// Each additional function entry point has a corresponding entry in the map.
@@ -1360,32 +1355,14 @@ class BinaryFunction {
return InternalRefDataRelocations;
}
- /// Append function-relative \p Offset as a DWARF lexical-scope boundary. The
- /// list is sorted later (sortDebugScopeBoundaryOffsets) before it is queried.
- void addDebugScopeBoundaryOffset(uint64_t Offset) {
- DebugScopeBoundaryOffsets.push_back(Offset);
- }
-
- /// Sort and deduplicate the collected scope boundaries and reset the query
- /// cursor. Call once, before the first isDebugScopeBoundaryOffset() query.
- void sortDebugScopeBoundaryOffsets() {
- llvm::sort(DebugScopeBoundaryOffsets);
- DebugScopeBoundaryOffsets.erase(llvm::unique(DebugScopeBoundaryOffsets),
- DebugScopeBoundaryOffsets.end());
- DebugScopeBoundaryCursor = 0;
+ /// Add function-relative \p Offset as a DWARF lexical-scope boundary.
+ void addDebugScopeBoundaryOffset(uint32_t Offset) {
+ DebugScopeBoundaryOffsets.set(Offset);
}
/// Return true if function-relative \p Offset begins/ends a DWARF scope.
- /// Requires sortDebugScopeBoundaryOffsets() to have been called, and that
- /// queries arrive with non-decreasing \p Offset. This is designed to be
- /// used in lock-step with the disassembly loop, don't use it outside of it.
- bool isDebugScopeBoundaryOffset(uint64_t Offset) {
- const size_t Size = DebugScopeBoundaryOffsets.size();
- while (DebugScopeBoundaryCursor < Size &&
- DebugScopeBoundaryOffsets[DebugScopeBoundaryCursor] < Offset)
- ++DebugScopeBoundaryCursor;
- return DebugScopeBoundaryCursor < Size &&
- DebugScopeBoundaryOffsets[DebugScopeBoundaryCursor] == Offset;
+ bool isDebugScopeBoundaryOffset(uint32_t Offset) {
+ return DebugScopeBoundaryOffsets.test(Offset);
}
/// Return true if an "Offset" annotation should be kept for instruction
@@ -1393,7 +1370,7 @@ class BinaryFunction {
/// control-flow instructions (profile matching) and for instructions that
/// begin/end a DWARF lexical scope (needed to translate scope ranges
/// precisely; see DebugScopeBoundaryOffsets).
- bool keepOffsetForInstruction(const MCInst &Inst, uint64_t Offset);
+ bool keepOffsetForInstruction(const MCInst &Inst, uint32_t Offset);
/// Return the name of the section this function originated from.
std::optional<StringRef> getOriginSectionName() const {
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 733dfe6fa0535..a243b383d5ccc 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1973,10 +1973,12 @@ void BinaryContext::collectDebugScopeBoundaries() {
BinaryFunction *BF = getBinaryFunctionContainingAddress(LowPC);
if (!BF)
return;
- BF->addDebugScopeBoundaryOffset(LowPC - BF->getAddress());
+ BF->addDebugScopeBoundaryOffset(
+ static_cast<uint32_t>(LowPC - BF->getAddress()));
// Mark HighPC only if it lies strictly inside the same function.
if (HighPC > LowPC && getBinaryFunctionContainingAddress(HighPC) == BF)
- BF->addDebugScopeBoundaryOffset(HighPC - BF->getAddress());
+ BF->addDebugScopeBoundaryOffset(
+ static_cast<uint32_t>(HighPC - BF->getAddress()));
};
// Record the boundaries of a single scope DIE.
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 6c23bbd1ff8c8..63a5bfe96471f 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1303,7 +1303,7 @@ BinaryFunction::getInstructionSequenceLength(uint64_t Offset,
}
bool BinaryFunction::keepOffsetForInstruction(const MCInst &Inst,
- uint64_t Offset) {
+ uint32_t Offset) {
MCPlusBuilder &MIB = *BC.MIB;
if (MIB.isCall(Inst) || MIB.isBranch(Inst) || MIB.isReturn(Inst) ||
MIB.isPrefix(Inst) || MIB.isIndirectBranch(Inst))
@@ -1336,10 +1336,6 @@ Error BinaryFunction::disassemble() {
// corresponding %pcrel_hi.
LabelsMapType InstructionLabels;
- // Scope boundaries were appended unsorted during preprocessDebugInfo(); sort
- // them so keepOffsetForInstruction() can query them below.
- sortDebugScopeBoundaryOffsets();
-
uint64_t Size = 0; // instruction size
for (uint64_t Offset = 0; Offset < getSize(); Offset += Size) {
MCInst Instruction;
@@ -1565,7 +1561,7 @@ Error BinaryFunction::disassemble() {
// Record offset of the instruction for profile matching, for control-flow
// instructions, and for instructions on a DWARF lexical-scope boundary.
- if (keepOffsetForInstruction(Instruction, Offset))
+ if (keepOffsetForInstruction(Instruction, static_cast<uint32_t>(Offset)))
MIB->setOffset(Instruction, static_cast<uint32_t>(Offset));
if (BC.isX86() && BC.MIB->isNoop(Instruction)) {
@@ -1579,7 +1575,7 @@ Error BinaryFunction::disassemble() {
}
// Scope-boundary markers are only consulted while assigning offsets above.
- clearList(DebugScopeBoundaryOffsets);
+ DebugScopeBoundaryOffsets.clear();
for (auto [Offset, Label] : InstructionLabels) {
InstrMapType::iterator II = Instructions.find(Offset);
More information about the llvm-commits
mailing list