Skip to content

Commit fe18ab9

Browse files
authored
[DebugInfo] Don't apply is_stmt on MBB branches that preserve lines (#108251)
This patch follows on from the changes made in #105524, by adding an additional heuristic that prevents us from applying the start-of-MBB is_stmt flag when we can see that, for all direct branches to the MBB, the last line stepped on before the branch is the same as the first line of the MBB. This is mainly to prevent certain pathological cases, such as macros that expand to multiple basic blocks that all have the same source location, from giving us repeated steps on the same line. This approach is not comprehensive, since it relies on analyzeBranch to read edges, but the default fallback of applying is_stmt may lead only to useless steps in some cases, rather than skipping useful steps altogether.
1 parent 6256f4b commit fe18ab9

File tree

3 files changed

+208
-2
lines changed

3 files changed

+208
-2
lines changed

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,7 +2075,8 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
20752075
bool PrevInstInSameSection =
20762076
(!PrevInstBB ||
20772077
PrevInstBB->getSectionID() == MI->getParent()->getSectionID());
2078-
if (DL == PrevInstLoc && PrevInstInSameSection) {
2078+
bool ForceIsStmt = ForceIsStmtInstrs.contains(MI);
2079+
if (DL == PrevInstLoc && PrevInstInSameSection && !ForceIsStmt) {
20792080
// If we have an ongoing unspecified location, nothing to do here.
20802081
if (!DL)
20812082
return;
@@ -2132,7 +2133,7 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
21322133
// If the line changed, we call that a new statement; unless we went to
21332134
// line 0 and came back, in which case it is not a new statement.
21342135
unsigned OldLine = PrevInstLoc ? PrevInstLoc.getLine() : LastAsmLine;
2135-
if (DL.getLine() && DL.getLine() != OldLine)
2136+
if (DL.getLine() && (DL.getLine() != OldLine || ForceIsStmt))
21362137
Flags |= DWARF2_FLAG_IS_STMT;
21372138

21382139
const MDNode *Scope = DL.getScope();
@@ -2308,6 +2309,143 @@ DwarfDebug::emitInitialLocDirective(const MachineFunction &MF, unsigned CUID) {
23082309
return PrologEndLoc;
23092310
}
23102311

2312+
/// For the function \p MF, finds the set of instructions which may represent a
2313+
/// change in line number from one or more of the preceding MBBs. Stores the
2314+
/// resulting set of instructions, which should have is_stmt set, in
2315+
/// ForceIsStmtInstrs.
2316+
void DwarfDebug::findForceIsStmtInstrs(const MachineFunction *MF) {
2317+
ForceIsStmtInstrs.clear();
2318+
2319+
// For this function, we try to find MBBs where the last source line in every
2320+
// block predecessor matches the first line seen in the block itself; for
2321+
// every such MBB, we set is_stmt=false on the first line in the block, and
2322+
// for every other block we set is_stmt=true on the first line.
2323+
// For example, if we have the block %bb.3, which has 2 predecesors %bb.1 and
2324+
// %bb.2:
2325+
// bb.1:
2326+
// $r3 = MOV64ri 12, debug-location !DILocation(line: 4)
2327+
// JMP %bb.3, debug-location !DILocation(line: 5)
2328+
// bb.2:
2329+
// $r3 = MOV64ri 24, debug-location !DILocation(line: 5)
2330+
// JMP %bb.3
2331+
// bb.3:
2332+
// $r2 = MOV64ri 1
2333+
// $r1 = ADD $r2, $r3, debug-location !DILocation(line: 5)
2334+
// When we examine %bb.3, we first check to see if it contains any
2335+
// instructions with debug locations, and select the first such instruction;
2336+
// in this case, the ADD, with line=5. We then examine both of its
2337+
// predecessors to see what the last debug-location in them is. For each
2338+
// predecessor, if they do not contain any debug-locations, or if the last
2339+
// debug-location before jumping to %bb.3 does not have line=5, then the ADD
2340+
// in %bb.3 must use IsStmt. In this case, all predecessors have a
2341+
// debug-location with line=5 as the last debug-location before jumping to
2342+
// %bb.3, so we do not set is_stmt for the ADD instruction - we know that
2343+
// whichever MBB we have arrived from, the line has not changed.
2344+
2345+
const auto *TII = MF->getSubtarget().getInstrInfo();
2346+
2347+
// We only need to the predecessors of MBBs that could have is_stmt set by
2348+
// this logic.
2349+
SmallDenseSet<MachineBasicBlock *, 4> PredMBBsToExamine;
2350+
SmallDenseMap<MachineBasicBlock *, MachineInstr *> PotentialIsStmtMBBInstrs;
2351+
// We use const_cast even though we won't actually modify MF, because some
2352+
// methods we need take a non-const MBB.
2353+
for (auto &MBB : *const_cast<MachineFunction *>(MF)) {
2354+
if (MBB.empty() || MBB.pred_empty())
2355+
continue;
2356+
for (auto &MI : MBB) {
2357+
if (MI.getDebugLoc() && MI.getDebugLoc()->getLine()) {
2358+
for (auto *Pred : MBB.predecessors())
2359+
PredMBBsToExamine.insert(Pred);
2360+
PotentialIsStmtMBBInstrs.insert({&MBB, &MI});
2361+
break;
2362+
}
2363+
}
2364+
}
2365+
2366+
// For each predecessor MBB, we examine the last line seen before each branch
2367+
// or logical fallthrough. We use analyzeBranch to handle cases where
2368+
// different branches have different outgoing lines (i.e. if there are
2369+
// multiple branches that each have their own source location); otherwise we
2370+
// just use the last line in the block.
2371+
for (auto *MBB : PredMBBsToExamine) {
2372+
auto CheckMBBEdge = [&](MachineBasicBlock *Succ, unsigned OutgoingLine) {
2373+
auto MBBInstrIt = PotentialIsStmtMBBInstrs.find(Succ);
2374+
if (MBBInstrIt == PotentialIsStmtMBBInstrs.end())
2375+
return;
2376+
MachineInstr *MI = MBBInstrIt->second;
2377+
if (MI->getDebugLoc()->getLine() == OutgoingLine)
2378+
return;
2379+
PotentialIsStmtMBBInstrs.erase(MBBInstrIt);
2380+
ForceIsStmtInstrs.insert(MI);
2381+
};
2382+
// If this block is empty, we conservatively assume that its fallthrough
2383+
// successor needs is_stmt; we could check MBB's predecessors to see if it
2384+
// has a consistent entry line, but this seems unlikely to be worthwhile.
2385+
if (MBB->empty()) {
2386+
for (auto *Succ : MBB->successors())
2387+
CheckMBBEdge(Succ, 0);
2388+
continue;
2389+
}
2390+
// If MBB has no successors that are in the "potential" set, due to one or
2391+
// more of them having confirmed is_stmt, we can skip this check early.
2392+
if (none_of(MBB->successors(), [&](auto *SuccMBB) {
2393+
return PotentialIsStmtMBBInstrs.contains(SuccMBB);
2394+
}))
2395+
continue;
2396+
// If we can't determine what DLs this branch's successors use, just treat
2397+
// all the successors as coming from the last DebugLoc.
2398+
SmallVector<MachineBasicBlock *, 2> SuccessorBBs;
2399+
auto MIIt = MBB->rbegin();
2400+
{
2401+
MachineBasicBlock *TBB = nullptr, *FBB = nullptr;
2402+
SmallVector<MachineOperand, 4> Cond;
2403+
bool AnalyzeFailed = TII->analyzeBranch(*MBB, TBB, FBB, Cond);
2404+
// For a conditional branch followed by unconditional branch where the
2405+
// unconditional branch has a DebugLoc, that loc is the outgoing loc to
2406+
// the the false destination only; otherwise, both destinations share an
2407+
// outgoing loc.
2408+
if (!AnalyzeFailed && !Cond.empty() && FBB != nullptr &&
2409+
MBB->back().getDebugLoc() && MBB->back().getDebugLoc()->getLine()) {
2410+
unsigned FBBLine = MBB->back().getDebugLoc()->getLine();
2411+
assert(MIIt->isBranch() && "Bad result from analyzeBranch?");
2412+
CheckMBBEdge(FBB, FBBLine);
2413+
++MIIt;
2414+
SuccessorBBs.push_back(TBB);
2415+
} else {
2416+
// For all other cases, all successors share the last outgoing DebugLoc.
2417+
SuccessorBBs.assign(MBB->succ_begin(), MBB->succ_end());
2418+
}
2419+
}
2420+
2421+
// If we don't find an outgoing loc, this block will start with a line 0.
2422+
// It is possible that we have a block that has no DebugLoc, but acts as a
2423+
// simple passthrough between two blocks that end and start with the same
2424+
// line, e.g.:
2425+
// bb.1:
2426+
// JMP %bb.2, debug-location !10
2427+
// bb.2:
2428+
// JMP %bb.3
2429+
// bb.3:
2430+
// $r1 = ADD $r2, $r3, debug-location !10
2431+
// If these blocks were merged into a single block, we would not attach
2432+
// is_stmt to the ADD, but with this logic that only checks the immediate
2433+
// predecessor, we will; we make this tradeoff because doing a full dataflow
2434+
// analysis would be expensive, and these situations are probably not common
2435+
// enough for this to be worthwhile.
2436+
unsigned LastLine = 0;
2437+
while (MIIt != MBB->rend()) {
2438+
if (auto DL = MIIt->getDebugLoc(); DL && DL->getLine()) {
2439+
LastLine = DL->getLine();
2440+
break;
2441+
}
2442+
++MIIt;
2443+
}
2444+
for (auto *Succ : SuccessorBBs)
2445+
CheckMBBEdge(Succ, LastLine);
2446+
}
2447+
}
2448+
23112449
// Gather pre-function debug information. Assumes being called immediately
23122450
// after the function entry point has been emitted.
23132451
void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) {
@@ -2326,6 +2464,8 @@ void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) {
23262464
// Record beginning of function.
23272465
PrologEndLoc = emitInitialLocDirective(
23282466
*MF, Asm->OutStreamer->getContext().getDwarfCompileUnitID());
2467+
2468+
findForceIsStmtInstrs(MF);
23292469
}
23302470

23312471
unsigned

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ class DwarfDebug : public DebugHandlerBase {
382382
SmallPtrSet<const MDNode *, 2>>;
383383
DenseMap<const DILocalScope *, MDNodeSet> LocalDeclsPerLS;
384384

385+
SmallDenseSet<const MachineInstr *> ForceIsStmtInstrs;
386+
385387
/// If nonnull, stores the current machine function we're processing.
386388
const MachineFunction *CurFn = nullptr;
387389

@@ -694,6 +696,8 @@ class DwarfDebug : public DebugHandlerBase {
694696
/// Emit the reference to the section.
695697
void emitSectionReference(const DwarfCompileUnit &CU);
696698

699+
void findForceIsStmtInstrs(const MachineFunction *MF);
700+
697701
protected:
698702
/// Gather pre-function debug information.
699703
void beginFunctionImpl(const MachineFunction *MF) override;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
;; Checks that when an instruction at the start of a BasicBlock has the same
2+
;; DebugLoc as the instruction at the end of the previous BasicBlock, we add
3+
;; is_stmt to the new line, to ensure that we still step on it if we arrive from
4+
;; a BasicBlock other than the immediately preceding one, unless all known
5+
;; predecessor BasicBlocks end with the same line.
6+
7+
; RUN: %llc_dwarf -mtriple=x86_64-unknown-linux -O0 -filetype=obj < %s | llvm-dwarfdump --debug-line - | FileCheck %s
8+
9+
; CHECK: {{0x[0-9a-f]+}} 13 5 {{.+}} is_stmt
10+
; CHECK-NEXT: {{0x[0-9a-f]+}} 13 5 {{.+}} is_stmt
11+
12+
define void @_Z1fi(i1 %cond) !dbg !21 {
13+
entry:
14+
br i1 %cond, label %if.then2, label %if.else4
15+
16+
if.then2: ; preds = %entry
17+
br label %if.end8, !dbg !24
18+
19+
if.else4: ; preds = %entry
20+
%0 = load i32, ptr null, align 4, !dbg !24
21+
%call5 = call i1 null(i32 %0)
22+
ret void
23+
24+
if.end8: ; preds = %if.then2
25+
ret void
26+
}
27+
28+
; CHECK: {{0x[0-9a-f]+}} 113 5 {{.+}} is_stmt
29+
; CHECK-NOT: {{0x[0-9a-f]+}} 113 5
30+
31+
define void @_Z1gi(i1 %cond) !dbg !31 {
32+
entry:
33+
br i1 %cond, label %if.then2, label %if.else4, !dbg !34
34+
35+
if.then2: ; preds = %entry
36+
br label %if.end8, !dbg !34
37+
38+
if.else4: ; preds = %entry
39+
%0 = load i32, ptr null, align 4, !dbg !34
40+
%call5 = call i1 null(i32 %0)
41+
ret void
42+
43+
if.end8: ; preds = %if.then2
44+
ret void
45+
}
46+
47+
!llvm.dbg.cu = !{!0}
48+
!llvm.module.flags = !{!20}
49+
50+
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 20.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
51+
!1 = !DIFile(filename: "test.cpp", directory: "/home/gbtozers/dev/upstream-llvm")
52+
!20 = !{i32 2, !"Debug Info Version", i32 3}
53+
!21 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 7, type: !22, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
54+
!22 = distinct !DISubroutineType(types: !23)
55+
!23 = !{null}
56+
!24 = !DILocation(line: 13, column: 5, scope: !25)
57+
!25 = distinct !DILexicalBlock(scope: !21, file: !1, line: 11, column: 27)
58+
!31 = distinct !DISubprogram(name: "g", linkageName: "_Z1gi", scope: !1, file: !1, line: 107, type: !32, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
59+
!32 = distinct !DISubroutineType(types: !33)
60+
!33 = !{null}
61+
!34 = !DILocation(line: 113, column: 5, scope: !35)
62+
!35 = distinct !DILexicalBlock(scope: !31, file: !1, line: 111, column: 27)

0 commit comments

Comments
 (0)