Skip to content

Commit bf483dd

Browse files
authored
[DWARF] Emit a worst-case prologue_end flag for pathological inputs (#107849)
prologue_end usually indicates where the end of the function-initialization lies, and is where debuggers usually choose to put the initial breakpoint for a function. Our current algorithm piggy-backs it on the first available source-location: which doesn't necessarily have anything to do with the start of the function. To avoid this in heavily-optimised code that lacks many useful source locations, pick a worst-case "if all else fails" prologue_end location, of the first instruction that appears to do meaningful computation. It'll be given the function-scope line number, which should run-on from the start of the function anyway. This means if your code is completely inverted by the optimiser, you can at least put a breakpoint at the _start_ like you expect, even if it's difficult to then step through. This patch also attempts to preserve some good behaviour we have without optimisations -- at O0, if the prologue immediately falls into a loop body without any computation happening, then prologue_end lands at the start of that loop. This is desirable; but does mean we need to do more work to detect and support those situations.
1 parent eea8b44 commit bf483dd

9 files changed

+405
-33
lines changed

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,16 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
20622062
unsigned LastAsmLine =
20632063
Asm->OutStreamer->getContext().getCurrentDwarfLoc().getLine();
20642064

2065+
if (!DL && MI == PrologEndLoc) {
2066+
// In rare situations, we might want to place the end of the prologue
2067+
// somewhere that doesn't have a source location already. It should be in
2068+
// the entry block.
2069+
assert(MI->getParent() == &*MI->getMF()->begin());
2070+
recordSourceLine(SP->getScopeLine(), 0, SP,
2071+
DWARF2_FLAG_PROLOGUE_END | DWARF2_FLAG_IS_STMT);
2072+
return;
2073+
}
2074+
20652075
bool PrevInstInSameSection =
20662076
(!PrevInstBB ||
20672077
PrevInstBB->getSectionID() == MI->getParent()->getSectionID());
@@ -2137,32 +2147,109 @@ static std::pair<const MachineInstr *, bool>
21372147
findPrologueEndLoc(const MachineFunction *MF) {
21382148
// First known non-DBG_VALUE and non-frame setup location marks
21392149
// the beginning of the function body.
2140-
const MachineInstr *LineZeroLoc = nullptr;
2150+
const auto &TII = *MF->getSubtarget().getInstrInfo();
2151+
const MachineInstr *NonTrivialInst = nullptr;
21412152
const Function &F = MF->getFunction();
21422153

21432154
// Some instructions may be inserted into prologue after this function. Must
21442155
// keep prologue for these cases.
21452156
bool IsEmptyPrologue =
21462157
!(F.hasPrologueData() || F.getMetadata(LLVMContext::MD_func_sanitize));
2147-
for (const auto &MBB : *MF) {
2148-
for (const auto &MI : MBB) {
2149-
if (!MI.isMetaInstruction()) {
2150-
if (!MI.getFlag(MachineInstr::FrameSetup) && MI.getDebugLoc()) {
2151-
// Scan forward to try to find a non-zero line number. The
2152-
// prologue_end marks the first breakpoint in the function after the
2153-
// frame setup, and a compiler-generated line 0 location is not a
2154-
// meaningful breakpoint. If none is found, return the first
2155-
// location after the frame setup.
2156-
if (MI.getDebugLoc().getLine())
2157-
return std::make_pair(&MI, IsEmptyPrologue);
2158-
2159-
LineZeroLoc = &MI;
2160-
}
2161-
IsEmptyPrologue = false;
2162-
}
2158+
2159+
// Helper lambda to examine each instruction and potentially return it
2160+
// as the prologue_end point.
2161+
auto ExamineInst = [&](const MachineInstr &MI)
2162+
-> std::optional<std::pair<const MachineInstr *, bool>> {
2163+
// Is this instruction trivial data shuffling or frame-setup?
2164+
bool isCopy = (TII.isCopyInstr(MI) ? true : false);
2165+
bool isTrivRemat = TII.isTriviallyReMaterializable(MI);
2166+
bool isFrameSetup = MI.getFlag(MachineInstr::FrameSetup);
2167+
2168+
if (!isFrameSetup && MI.getDebugLoc()) {
2169+
// Scan forward to try to find a non-zero line number. The
2170+
// prologue_end marks the first breakpoint in the function after the
2171+
// frame setup, and a compiler-generated line 0 location is not a
2172+
// meaningful breakpoint. If none is found, return the first
2173+
// location after the frame setup.
2174+
if (MI.getDebugLoc().getLine())
2175+
return std::make_pair(&MI, IsEmptyPrologue);
2176+
}
2177+
2178+
// Keep track of the first "non-trivial" instruction seen, i.e. anything
2179+
// that doesn't involve shuffling data around or is a frame-setup.
2180+
if (!isCopy && !isTrivRemat && !isFrameSetup && !NonTrivialInst)
2181+
NonTrivialInst = &MI;
2182+
2183+
IsEmptyPrologue = false;
2184+
return std::nullopt;
2185+
};
2186+
2187+
// Examine all the instructions at the start of the function. This doesn't
2188+
// necessarily mean just the entry block: unoptimised code can fall-through
2189+
// into an initial loop, and it makes sense to put the initial breakpoint on
2190+
// the first instruction of such a loop. However, if we pass branches, we're
2191+
// better off synthesising an early prologue_end.
2192+
auto CurBlock = MF->begin();
2193+
auto CurInst = CurBlock->begin();
2194+
while (true) {
2195+
// Skip empty blocks, in rare cases the entry can be empty.
2196+
if (CurInst == CurBlock->end()) {
2197+
++CurBlock;
2198+
CurInst = CurBlock->begin();
2199+
continue;
21632200
}
2201+
2202+
// Check whether this non-meta instruction a good position for prologue_end.
2203+
if (!CurInst->isMetaInstruction()) {
2204+
auto FoundInst = ExamineInst(*CurInst);
2205+
if (FoundInst)
2206+
return *FoundInst;
2207+
}
2208+
2209+
// Try to continue searching, but use a backup-location if substantive
2210+
// computation is happening.
2211+
auto NextInst = std::next(CurInst);
2212+
if (NextInst != CurInst->getParent()->end()) {
2213+
// Continue examining the current block.
2214+
CurInst = NextInst;
2215+
continue;
2216+
}
2217+
2218+
// We've reached the end of the block. Did we just look at a terminator?
2219+
if (CurInst->isTerminator()) {
2220+
// Some kind of "real" control flow is occurring. At the very least
2221+
// we would have to start exploring the CFG, a good signal that the
2222+
// prologue is over.
2223+
break;
2224+
}
2225+
2226+
// If we've already fallen through into a loop, don't fall through
2227+
// further, use a backup-location.
2228+
if (CurBlock->pred_size() > 1)
2229+
break;
2230+
2231+
// Fall-through from entry to the next block. This is common at -O0 when
2232+
// there's no initialisation in the function. Bail if we're also at the
2233+
// end of the function.
2234+
if (++CurBlock == MF->end())
2235+
break;
2236+
CurInst = CurBlock->begin();
2237+
}
2238+
2239+
// We couldn't find any source-location, suggesting all meaningful information
2240+
// got optimised away. Set the prologue_end to be the first non-trivial
2241+
// instruction, which will get the scope line number. This is better than
2242+
// nothing.
2243+
// Only do this in the entry block, as we'll be giving it the scope line for
2244+
// the function. Return IsEmptyPrologue==true if we've picked the first
2245+
// instruction.
2246+
if (NonTrivialInst && NonTrivialInst->getParent() == &*MF->begin()) {
2247+
IsEmptyPrologue = NonTrivialInst == &*MF->begin()->begin();
2248+
return std::make_pair(NonTrivialInst, IsEmptyPrologue);
21642249
}
2165-
return std::make_pair(LineZeroLoc, IsEmptyPrologue);
2250+
2251+
// If the entry path is empty, just don't have a prologue_end at all.
2252+
return std::make_pair(nullptr, IsEmptyPrologue);
21662253
}
21672254

21682255
/// Register a source line with debug info. Returns the unique label that was
@@ -2194,12 +2281,21 @@ DwarfDebug::emitInitialLocDirective(const MachineFunction &MF, unsigned CUID) {
21942281
bool IsEmptyPrologue = PrologEnd.second;
21952282

21962283
// If the prolog is empty, no need to generate scope line for the proc.
2197-
if (IsEmptyPrologue)
2198-
// In degenerate cases, we can have functions with no source locations
2199-
// at all. These want a scope line, to avoid a totally empty function.
2200-
// Thus, only skip scope line if there's location to place prologue_end.
2201-
if (PrologEndLoc)
2202-
return PrologEndLoc;
2284+
if (IsEmptyPrologue) {
2285+
// If there's nowhere to put a prologue_end flag, emit a scope line in case
2286+
// there are simply no source locations anywhere in the function.
2287+
if (PrologEndLoc) {
2288+
// Avoid trying to assign prologue_end to a line-zero location.
2289+
// Instructions with no DebugLoc at all are fine, they'll be given the
2290+
// scope line nuumber.
2291+
const DebugLoc &DL = PrologEndLoc->getDebugLoc();
2292+
if (!DL || DL->getLine() != 0)
2293+
return PrologEndLoc;
2294+
2295+
// Later, don't place the prologue_end flag on this line-zero location.
2296+
PrologEndLoc = nullptr;
2297+
}
2298+
}
22032299

22042300
// Ensure the compile unit is created if the function is called before
22052301
// beginFunction().

llvm/test/CodeGen/X86/no-non-zero-debug-loc-prologue.ll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
; RUN: llc -filetype=asm -mtriple=x86_64-apple-macosx12.0.0 -O0 %s -o - | FileCheck %s
1+
; RUN: llc -filetype=asm -mtriple=x86_64-apple-macosx12.0.0 -O0 %s -o - | FileCheck %s --implicit-check-not=prologue_end
22
; CHECK: Lfunc_begin0:
33
; CHECK-NEXT: .file{{.+}}
44
; CHECK-NEXT: .loc 1 1 0 ## test-small.c:1:0{{$}}
55
; CHECK-NEXT: .cfi_startproc
66
; CHECK-NEXT: ## %bb.{{[0-9]+}}:
7-
; CHECK-NEXT: .loc 1 0 1 prologue_end{{.*}}
7+
; CHECK-NEXT: .loc 1 0 1
88
define void @test() #0 !dbg !9 {
99
ret void, !dbg !12
1010
}
@@ -19,4 +19,4 @@ define void @test() #0 !dbg !9 {
1919
!9 = distinct !DISubprogram(name: "test", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !7)
2020
!10 = !DIFile(filename: "test-small.c", directory: "/Users/shubham/Development/test")
2121
!11 = !DISubroutineType(types: !7)
22-
!12 = !DILocation(line: 0, column: 1, scope: !9)
22+
!12 = !DILocation(line: 0, column: 1, scope: !9)

llvm/test/CodeGen/X86/pseudo_cmov_lower2.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ declare void @llvm.dbg.value(metadata, metadata, metadata)
200200
; minus the DEBUG_VALUE line and changes in labels..
201201
define double @foo1_g(float %p1, double %p2, double %p3) nounwind !dbg !4 {
202202
; CHECK-LABEL: foo1_g:
203-
; CHECK: .file 1 "." "test.c"
204-
; CHECK-NEXT: .loc 1 3 0
205203
; CHECK: # %bb.0: # %entry
204+
; CHECK-NEXT: .file 1 "." "test.c"
205+
; CHECK-NEXT: .loc 1 3 0 prologue_end
206206
; CHECK-NEXT: xorps %xmm3, %xmm3
207207
; CHECK-NEXT: ucomiss %xmm3, %xmm0
208208
; CHECK-NEXT: movsd {{.*#+}} xmm0 = [1.25E+0,0.0E+0]
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# RUN: llc %s -start-before=livedebugvalues -o - | \
2+
# RUN: FileCheck %s --implicit-check-not=prologue_end
3+
#
4+
## When picking a "backup" location of the first non-trivial instruction in
5+
## a function, don't select a location outside of the entry block. We have to
6+
## give it the function's scope-line, and installing that outside of the entry
7+
## block is liable to be misleading.
8+
##
9+
## Produced from the C below with "clang -O2 -g -mllvm
10+
## -stop-before=livedebugvalues", then modified to unrotate and shift early
11+
## insts into the loop block. This means the MIR is meaningless, we only test
12+
## whether the scope-line will leak into the loop block or not.
13+
##
14+
## int glob = 0;
15+
## int foo(int arg, int sum) {
16+
## arg += sum;
17+
## while (arg) {
18+
## glob--;
19+
## arg %= glob;
20+
## }
21+
## return 0;
22+
## }
23+
#
24+
# CHECK-LABEL: foo:
25+
# CHECK: .loc 0 2 0
26+
# CHECK: # %bb.0:
27+
# CHECK-NEXT: movl %edi, %edx
28+
# CHECK-NEXT: .loc 0 0 0 is_stmt 0
29+
# CHECK-NEXT: .Ltmp0:
30+
# CHECK-NEXT: .p2align 4
31+
# CHECK-NEXT: .LBB0_1:
32+
# CHECK-LABEL: addl %esi, %edx
33+
34+
35+
--- |
36+
; ModuleID = 'out2.ll'
37+
source_filename = "foo.c"
38+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
39+
target triple = "x86_64-unknown-linux-gnu"
40+
41+
@glob = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0
42+
43+
define dso_local noundef i32 @foo(i32 noundef %arg, i32 noundef %sum) local_unnamed_addr !dbg !9 {
44+
entry:
45+
%add = add nsw i32 %sum, %arg
46+
br label %while.body.preheader
47+
48+
while.body.preheader: ; preds = %entry
49+
%glob.promoted = load i32, ptr @glob, align 4
50+
br label %while.body, !dbg !13
51+
52+
while.body: ; preds = %while.body, %while.body.preheader
53+
%arg.addr.06 = phi i32 [ %rem, %while.body ], [ %add, %while.body.preheader ]
54+
%dec35 = phi i32 [ %dec, %while.body ], [ %glob.promoted, %while.body.preheader ]
55+
%dec = add nsw i32 %dec35, -1, !dbg !14
56+
%0 = add i32 %dec35, -1, !dbg !16
57+
%rem = srem i32 %arg.addr.06, %0, !dbg !16
58+
%tobool.not = icmp eq i32 %rem, 0, !dbg !13
59+
br i1 %tobool.not, label %while.cond.while.end_crit_edge, label %while.body, !dbg !13
60+
61+
while.cond.while.end_crit_edge: ; preds = %while.body
62+
store i32 %dec, ptr @glob, align 4, !dbg !14
63+
br label %while.end, !dbg !13
64+
65+
while.end: ; preds = %while.cond.while.end_crit_edge
66+
ret i32 0, !dbg !17
67+
}
68+
69+
!llvm.dbg.cu = !{!2}
70+
!llvm.module.flags = !{!6, !7}
71+
!llvm.ident = !{!8}
72+
73+
!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
74+
!1 = distinct !DIGlobalVariable(name: "glob", scope: !2, file: !3, line: 1, type: !5, isLocal: false, isDefinition: true)
75+
!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
76+
!3 = !DIFile(filename: "foo.c", directory: "")
77+
!4 = !{!0}
78+
!5 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
79+
!6 = !{i32 7, !"Dwarf Version", i32 5}
80+
!7 = !{i32 2, !"Debug Info Version", i32 3}
81+
!8 = !{!"clang"}
82+
!9 = distinct !DISubprogram(name: "foo", scope: !3, file: !3, line: 2, type: !10, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !12)
83+
!10 = !DISubroutineType(types: !11)
84+
!11 = !{!5, !5, !5}
85+
!12 = !{}
86+
!13 = !DILocation(line: 4, column: 3, scope: !9)
87+
!14 = !DILocation(line: 5, column: 9, scope: !15)
88+
!15 = distinct !DILexicalBlock(scope: !9, file: !3, line: 4, column: 15)
89+
!16 = !DILocation(line: 6, column: 9, scope: !15)
90+
!17 = !DILocation(line: 8, column: 3, scope: !9)
91+
92+
...
93+
---
94+
name: foo
95+
alignment: 16
96+
tracksRegLiveness: true
97+
debugInstrRef: true
98+
tracksDebugUserValues: true
99+
liveins:
100+
- { reg: '$edi' }
101+
- { reg: '$esi' }
102+
frameInfo:
103+
maxAlignment: 1
104+
maxCallFrameSize: 0
105+
isCalleeSavedInfoValid: true
106+
machineFunctionInfo:
107+
amxProgModel: None
108+
body: |
109+
bb.0.entry:
110+
liveins: $edi, $esi
111+
112+
$edx = MOV32rr $edi
113+
114+
bb.1.while.body (align 16):
115+
successors: %bb.2(0x04000000), %bb.1(0x7c000000)
116+
liveins: $ecx, $edx, $esi
117+
118+
renamable $edx = nsw ADD32rr killed renamable $edx, renamable $esi, implicit-def dead $eflags
119+
renamable $ecx = MOV32rm $rip, 1, $noreg, @glob, $noreg :: (dereferenceable load (s32) from @glob)
120+
renamable $ecx = DEC32r killed renamable $ecx, implicit-def dead $eflags
121+
$eax = MOV32rr killed $edx
122+
CDQ implicit-def $eax, implicit-def $edx, implicit $eax
123+
IDIV32r renamable $ecx, implicit-def dead $eax, implicit-def $edx, implicit-def dead $eflags, implicit $eax, implicit $edx
124+
TEST32rr renamable $edx, renamable $edx, implicit-def $eflags
125+
JCC_1 %bb.1, 5, implicit killed $eflags
126+
127+
bb.2.while.cond.while.end_crit_edge:
128+
liveins: $ecx, $esi
129+
130+
MOV32mr $rip, 1, $noreg, @glob, $noreg, killed renamable $ecx, debug-location !14 :: (store (s32) into @glob)
131+
$eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, debug-location !17
132+
RET64 $eax, debug-location !17
133+
134+
...

llvm/test/DebugInfo/MIR/X86/empty-inline.mir

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
#
1414
# CHECK: Address Line Column File ISA Discriminator OpIndex Flags
1515
# CHECK-NEXT: ---
16-
# CHECK-NEXT: 25 0 1 0 0 0 is_stmt
17-
# CHECK-NEXT: 29 28 1 0 0 0 is_stmt prologue_end
16+
# CHECK-NEXT: 25 0 1 0 0 0 is_stmt prologue_end
17+
# CHECK-NEXT: 29 28 1 0 0 0 is_stmt
1818
# CHECK-NEXT: 29 28 1 0 0 0 is_stmt end_sequence
19+
1920
--- |
2021
source_filename = "t.ll"
2122
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"

0 commit comments

Comments
 (0)