Skip to content

Commit 0b5166a

Browse files
committed
[KeyInstr][DwarfDebug] Add is_stmt emission support
Interpret Key Instructions metadata to determine is_stmt placement. The lowest rank (highest precedent) instructions in each {InlinedAt, atomGroup} set are candidates for is_stmt. Only the last instruction in each set in a given block gets is_stmt. Calls always get is_stmt.
1 parent 8fa3b52 commit 0b5166a

File tree

8 files changed

+663
-12
lines changed

8 files changed

+663
-12
lines changed

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp

Lines changed: 198 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "DwarfExpression.h"
1818
#include "DwarfUnit.h"
1919
#include "llvm/ADT/APInt.h"
20+
#include "llvm/ADT/ScopeExit.h"
2021
#include "llvm/ADT/Statistic.h"
2122
#include "llvm/ADT/StringExtras.h"
2223
#include "llvm/ADT/Twine.h"
@@ -170,6 +171,9 @@ static cl::opt<DwarfDebug::MinimizeAddrInV5> MinimizeAddrInV5Option(
170171
"Stuff")),
171172
cl::init(DwarfDebug::MinimizeAddrInV5::Default));
172173

174+
static cl::opt<bool> KeyInstructionsAreStmts("dwarf-use-key-instructions",
175+
cl::Hidden, cl::init(false));
176+
173177
static constexpr unsigned ULEB128PadSize = 4;
174178

175179
void DebugLocDwarfExpression::emitOp(uint8_t Op, const char *Comment) {
@@ -2070,6 +2074,10 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
20702074
unsigned LastAsmLine =
20712075
Asm->OutStreamer->getContext().getCurrentDwarfLoc().getLine();
20722076

2077+
bool IsKey = false;
2078+
if (KeyInstructionsAreStmts && DL && DL.getLine())
2079+
IsKey = KeyInstructions.contains(MI);
2080+
20732081
if (!DL && MI == PrologEndLoc) {
20742082
// In rare situations, we might want to place the end of the prologue
20752083
// somewhere that doesn't have a source location already. It should be in
@@ -2088,13 +2096,18 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
20882096
// If we have an ongoing unspecified location, nothing to do here.
20892097
if (!DL)
20902098
return;
2091-
// We have an explicit location, same as the previous location.
2092-
// But we might be coming back to it after a line 0 record.
2093-
if ((LastAsmLine == 0 && DL.getLine() != 0) || Flags) {
2094-
// Reinstate the source location but not marked as a statement.
2095-
RecordSourceLine(DL, Flags);
2099+
2100+
// Skip this if the instruction is Key, else we might accidentally miss an
2101+
// is_stmt.
2102+
if (!IsKey) {
2103+
// We have an explicit location, same as the previous location.
2104+
// But we might be coming back to it after a line 0 record.
2105+
if ((LastAsmLine == 0 && DL.getLine() != 0) || Flags) {
2106+
// Reinstate the source location but not marked as a statement.
2107+
RecordSourceLine(DL, Flags);
2108+
}
2109+
return;
20962110
}
2097-
return;
20982111
}
20992112

21002113
if (!DL) {
@@ -2141,11 +2154,17 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
21412154
Flags |= DWARF2_FLAG_PROLOGUE_END | DWARF2_FLAG_IS_STMT;
21422155
PrologEndLoc = nullptr;
21432156
}
2144-
// If the line changed, we call that a new statement; unless we went to
2145-
// line 0 and came back, in which case it is not a new statement.
2146-
unsigned OldLine = PrevInstLoc ? PrevInstLoc.getLine() : LastAsmLine;
2147-
if (DL.getLine() && (DL.getLine() != OldLine || ForceIsStmt))
2148-
Flags |= DWARF2_FLAG_IS_STMT;
2157+
2158+
if (KeyInstructionsAreStmts) {
2159+
if (IsKey)
2160+
Flags |= DWARF2_FLAG_IS_STMT;
2161+
} else {
2162+
// If the line changed, we call that a new statement; unless we went to
2163+
// line 0 and came back, in which case it is not a new statement.
2164+
unsigned OldLine = PrevInstLoc ? PrevInstLoc.getLine() : LastAsmLine;
2165+
if (DL.getLine() && (DL.getLine() != OldLine || ForceIsStmt))
2166+
Flags |= DWARF2_FLAG_IS_STMT;
2167+
}
21492168

21502169
RecordSourceLine(DL, Flags);
21512170

@@ -2338,6 +2357,170 @@ DwarfDebug::emitInitialLocDirective(const MachineFunction &MF, unsigned CUID) {
23382357
return PrologEndLoc;
23392358
}
23402359

2360+
void DwarfDebug::findKeyInstructions(const MachineFunction *MF) {
2361+
// New function - reset KeyInstructions.
2362+
KeyInstructions.clear();
2363+
2364+
// The current candidate is_stmt instructions for each source atom.
2365+
// Map {(InlinedAt, Group): (Rank, Instructions)}.
2366+
DenseMap<std::pair<DILocation *, uint32_t>,
2367+
std::pair<uint16_t, SmallVector<const MachineInstr *>>>
2368+
GroupCandidates;
2369+
2370+
// For each instruction:
2371+
// * Skip insts without DebugLoc, AtomGroup or AtomRank, and line zeros.
2372+
// * Check if insts in this group have been seen already in GroupCandidates.
2373+
// * If this instr rank is equal, add this instruction to KeyInstructions.
2374+
// Remove existing instructions from KeyInstructions if they have the
2375+
// same parent.
2376+
// * If this instr rank is higher (lower precedence), ignore it.
2377+
// * If this instr rank is lower (higher precedence), erase existing
2378+
// instructions from KeyInstructions. Add this instr to KeyInstructions.
2379+
2380+
for (auto &MBB : *MF) {
2381+
// Rather than apply is_stmt directly to Key Instructions, we "float"
2382+
// is_stmt up to the 1st instruction with the same line number in a
2383+
// contiguous block. That instruction is called the "buoy". The
2384+
// buoy gets reset if we encouner an instruction with an atom
2385+
// group.
2386+
const MachineInstr *Buoy = nullptr;
2387+
// The atom group number associated with Buoy which may be 0 if we haven't
2388+
// encountered an atom group yet in this blob of instructions with the same
2389+
// line number.
2390+
uint64_t BuoyAtom = 0;
2391+
2392+
for (auto &MI : MBB) {
2393+
if (MI.isMetaInstruction())
2394+
continue;
2395+
2396+
if (!MI.getDebugLoc() || !MI.getDebugLoc().getLine())
2397+
continue;
2398+
2399+
// Reset the Buoy to this instruciton if it has a different line number.
2400+
if (!Buoy ||
2401+
Buoy->getDebugLoc().getLine() != MI.getDebugLoc().getLine()) {
2402+
Buoy = &MI;
2403+
BuoyAtom = 0;
2404+
}
2405+
2406+
// Call instructions are handled specially - we always mark them as key
2407+
// regardless of atom info.
2408+
const auto &TII =
2409+
*MI.getParent()->getParent()->getSubtarget().getInstrInfo();
2410+
if (MI.isCall() || TII.isTailCall(MI)) {
2411+
assert(MI.getDebugLoc() && "Unexpectedly missing DL");
2412+
2413+
// Calls are always key.
2414+
KeyInstructions.insert(Buoy);
2415+
2416+
uint64_t Group = MI.getDebugLoc()->getAtomGroup();
2417+
uint8_t Rank = MI.getDebugLoc()->getAtomRank();
2418+
if (Group && Rank) {
2419+
auto *InlinedAt = MI.getDebugLoc()->getInlinedAt();
2420+
auto &[CandidateRank, CandidateInsts] = GroupCandidates[{InlinedAt, Group}];
2421+
2422+
// This looks similar to the non-call handling code, except that
2423+
// we don't put the call into CandidateInsts so that they can't be
2424+
// made un-key. As a result, we also have to take special care not
2425+
// to erase the is_stmt from the buoy, and prevent that happening
2426+
// in the future.
2427+
2428+
if (CandidateRank == Rank) {
2429+
// We've seen other instructions in this group of this rank. Discard
2430+
// ones we've seen in this block, keep the others.
2431+
assert(!CandidateInsts.empty());
2432+
SmallVector<const MachineInstr *> Insts;
2433+
Insts.reserve(CandidateInsts.size());
2434+
for (auto &PrevInst : CandidateInsts) {
2435+
if (PrevInst->getParent() != MI.getParent())
2436+
Insts.push_back(PrevInst);
2437+
else if (PrevInst != Buoy)
2438+
KeyInstructions.erase(PrevInst);
2439+
}
2440+
2441+
if (Insts.empty()) {
2442+
CandidateInsts.clear();
2443+
CandidateRank = 0;
2444+
} else {
2445+
CandidateInsts = std::move(Insts);
2446+
}
2447+
2448+
} else if (CandidateRank > Rank) {
2449+
// We've seen other instructions in this group of lower precedence
2450+
// (higher rank). Discard them.
2451+
for (auto *Supplanted : CandidateInsts) {
2452+
// Don't erase the is_stmt we're using for this call.
2453+
if (Supplanted != Buoy)
2454+
KeyInstructions.erase(Supplanted);
2455+
}
2456+
CandidateInsts.clear();
2457+
CandidateRank = 0;
2458+
}
2459+
}
2460+
2461+
// Avoid floating any future is_stmts up to the call.
2462+
Buoy = nullptr;
2463+
continue;
2464+
}
2465+
2466+
auto *InlinedAt = MI.getDebugLoc()->getInlinedAt();
2467+
uint64_t Group = MI.getDebugLoc()->getAtomGroup();
2468+
uint8_t Rank = MI.getDebugLoc()->getAtomRank();
2469+
if (!Group || !Rank)
2470+
continue;
2471+
2472+
// Don't let is_stmts float past instructions from different source atoms.
2473+
if (BuoyAtom && BuoyAtom != Group) {
2474+
Buoy = &MI;
2475+
BuoyAtom = MI.getDebugLoc()->getAtomGroup();
2476+
}
2477+
2478+
auto &[CandidateRank, CandidateInsts] = GroupCandidates[{InlinedAt, Group}];
2479+
2480+
if (CandidateRank == 0) {
2481+
// This is the first time we're seeing an instruction in this atom
2482+
// group. Add it to the map.
2483+
assert(CandidateInsts.empty());
2484+
CandidateRank = Rank;
2485+
CandidateInsts.push_back(Buoy);
2486+
2487+
} else if (CandidateRank == Rank) {
2488+
// We've seen other instructions in this group of this rank. Discard
2489+
// ones we've seen in this block, keep the others, add this one.
2490+
assert(!CandidateInsts.empty());
2491+
SmallVector<const MachineInstr *> Insts;
2492+
Insts.reserve(CandidateInsts.size() + 1);
2493+
for (auto &PrevInst : CandidateInsts) {
2494+
if (PrevInst->getParent() != MI.getParent())
2495+
Insts.push_back(PrevInst);
2496+
else
2497+
KeyInstructions.erase(PrevInst);
2498+
}
2499+
Insts.push_back(Buoy);
2500+
CandidateInsts = std::move(Insts);
2501+
2502+
} else if (CandidateRank > Rank) {
2503+
// We've seen other instructions in this group of lower precedence
2504+
// (higher rank). Discard them, add this one.
2505+
assert(!CandidateInsts.empty());
2506+
CandidateRank = Rank;
2507+
for (auto *Supplanted : CandidateInsts)
2508+
KeyInstructions.erase(Supplanted);
2509+
CandidateInsts = {Buoy};
2510+
2511+
} else {
2512+
// We've seen other instructions in this group with higher precedence
2513+
// (lower rank). Discard this one.
2514+
assert(Rank != 0 && CandidateRank < Rank && CandidateRank != 0);
2515+
continue;
2516+
}
2517+
KeyInstructions.insert(Buoy);
2518+
assert(!BuoyAtom || BuoyAtom == MI.getDebugLoc()->getAtomGroup());
2519+
BuoyAtom = MI.getDebugLoc()->getAtomGroup();
2520+
}
2521+
}
2522+
}
2523+
23412524
/// For the function \p MF, finds the set of instructions which may represent a
23422525
/// change in line number from one or more of the preceding MBBs. Stores the
23432526
/// resulting set of instructions, which should have is_stmt set, in
@@ -2496,7 +2679,10 @@ void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) {
24962679
PrologEndLoc = emitInitialLocDirective(
24972680
*MF, Asm->OutStreamer->getContext().getDwarfCompileUnitID());
24982681

2499-
findForceIsStmtInstrs(MF);
2682+
if (KeyInstructionsAreStmts)
2683+
findKeyInstructions(MF);
2684+
else
2685+
findForceIsStmtInstrs(MF);
25002686
}
25012687

25022688
unsigned

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ class DwarfDebug : public DebugHandlerBase {
464464
};
465465

466466
private:
467+
/// Instructions which should get is_stmt applied because they implement key
468+
/// functionality for a source atom.
469+
SmallDenseSet<const MachineInstr *> KeyInstructions;
470+
467471
/// Force the use of DW_AT_ranges even for single-entry range lists.
468472
MinimizeAddrInV5 MinimizeAddr = MinimizeAddrInV5::Disabled;
469473

@@ -701,6 +705,11 @@ class DwarfDebug : public DebugHandlerBase {
701705

702706
void findForceIsStmtInstrs(const MachineFunction *MF);
703707

708+
/// Find instructions which should get is_stmt applied because they implement
709+
/// key functionality for a source atom, store results in
710+
/// DwarfDebug::KeyInstructions.
711+
void findKeyInstructions(const MachineFunction *MF);
712+
704713
protected:
705714
/// Gather pre-function debug information.
706715
void beginFunctionImpl(const MachineFunction *MF) override;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
; RUN: llc %s --filetype=obj -o - --dwarf-use-key-instructions \
2+
; RUN: | llvm-objdump -d - --no-show-raw-insn \
3+
; RUN: | FileCheck %s --check-prefix=OBJ
4+
5+
; RUN: llc %s --filetype=obj -o - --dwarf-use-key-instructions \
6+
; RUN: | llvm-dwarfdump - --debug-line \
7+
; RUN: | FileCheck %s --check-prefix=DBG
8+
9+
; OBJ: 0000000000000000 <_Z1fPiii>:
10+
; OBJ-NEXT: 0: pushq %rbp
11+
; OBJ-NEXT: 1: pushq %r14
12+
; OBJ-NEXT: 3: pushq %rbx
13+
; OBJ-NEXT: 4: movl %edx, %ebx
14+
; OBJ-NEXT: 6: movl %esi, %ebp
15+
; OBJ-NEXT: 8: movq %rdi, %r14
16+
; OBJ-NEXT: b: callq 0x10 <_Z1fPiii+0x10>
17+
; OBJ-NEXT: 10: addl %ebx, %ebp
18+
; OBJ-NEXT: 12: movl %ebp, (%r14)
19+
; OBJ-NEXT: 15: movl %ebp, %eax
20+
; OBJ-NEXT: 17: popq %rbx
21+
; OBJ-NEXT: 18: popq %r14
22+
; OBJ-NEXT: 1a: popq %rbp
23+
24+
; DBG: Address Line Column File ISA Discriminator OpIndex Flags
25+
; DBG-NEXT: ------------------ ------ ------ ------ --- ------------- ------- -------------
26+
; DBG-NEXT: 0x0000000000000000 3 0 0 0 0 0 is_stmt
27+
; DBG-NEXT: 0x000000000000000b 4 0 0 0 0 0 is_stmt prologue_end
28+
; DBG-NEXT: 0x0000000000000010 6 0 0 0 0 0
29+
; DBG-NEXT: 0x0000000000000012 5 0 0 0 0 0 is_stmt
30+
; DBG-NEXT: 0x0000000000000015 7 0 0 0 0 0 is_stmt
31+
; DBG-NEXT: 0x0000000000000017 7 0 0 0 0 0 epilogue_begin
32+
; DBG-NEXT: 0x000000000000001c 7 0 0 0 0 0 end_sequence
33+
34+
;; 1. [[gnu::nodebug]] void prologue_end();
35+
;; 2.
36+
;; 3. int f(int *a, int b, int c) {
37+
;; 4. prologue_end();
38+
;; 5. *a =
39+
;; 6. b + c;
40+
;; 7. return *a;
41+
;; 8. }
42+
43+
;; The add and store are in the same goup (1). The add (line 6) has lower
44+
;; precedence (rank 2) so should not get is_stmt applied.
45+
target triple = "x86_64-unknown-linux-gnu"
46+
47+
define hidden noundef i32 @_Z1fPiii(ptr %a, i32 %b, i32 %c) local_unnamed_addr !dbg !11 {
48+
entry:
49+
tail call void @_Z12prologue_endv(), !dbg !DILocation(line: 4, scope: !11)
50+
%add = add nsw i32 %c, %b, !dbg !DILocation(line: 6, scope: !11, atomGroup: 1, atomRank: 2)
51+
store i32 %add, ptr %a, align 4, !dbg !DILocation(line: 5, scope: !11, atomGroup: 1, atomRank: 1)
52+
ret i32 %add, !dbg !DILocation(line: 7, scope: !11, atomGroup: 2, atomRank: 1)
53+
}
54+
55+
declare void @_Z12prologue_endv() local_unnamed_addr #1
56+
57+
!llvm.dbg.cu = !{!0}
58+
!llvm.module.flags = !{!2, !3}
59+
!llvm.ident = !{!10}
60+
61+
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_17, file: !1, producer: "clang version 19.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
62+
!1 = !DIFile(filename: "test.cpp", directory: "/")
63+
!2 = !{i32 7, !"Dwarf Version", i32 5}
64+
!3 = !{i32 2, !"Debug Info Version", i32 3}
65+
!10 = !{!"clang version 19.0.0"}
66+
!11 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 3, type: !12, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
67+
!12 = !DISubroutineType(types: !13)
68+
!13 = !{}

0 commit comments

Comments
 (0)