diff --git a/cross-project-tests/debuginfo-tests/llgdb-tests/inlined-callsite-loc.cpp b/cross-project-tests/debuginfo-tests/llgdb-tests/inlined-callsite-loc.cpp new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/llgdb-tests/inlined-callsite-loc.cpp @@ -0,0 +1,23 @@ +// RUN: %clangxx %target_itanium_abi_host_triple -O0 -g %s -c -o %t.o +// RUN: %clangxx %target_itanium_abi_host_triple %t.o -o %t.out +// RUN: %test_debuginfo %s %t.out +// XFAIL: system-darwin || gdb-clang-incompatibility + +// DEBUGGER: break 21 +// DEBUGGER: r +// CHECK: Breakpoint 1 {{.*}}inlined-callsite-loc.cpp:21 +// CHECK: 16 {{.*}}return foo(); + +__attribute__((noinline)) +int foo() { return 42; } + +inline __attribute__((always_inline)) +int inlined() { + return foo(); +} + +int main() { + int ret = 0; + ret = inlined(); + return ret; +} diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h --- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h @@ -654,6 +654,10 @@ /// Emits inital debug location directive. DebugLoc emitInitialLocDirective(const MachineFunction &MF, unsigned CUID); + /// Emits additional location directives for inlined callsites. + bool emitInlinedCallsiteLocs(const DebugLoc &Loc, const DILocation *InlinedAt, + const MachineInstr *MI); + /// Process beginning of an instruction. void beginInstruction(const MachineInstr *MI) override; diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp --- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp @@ -168,6 +168,11 @@ "Stuff")), cl::init(DwarfDebug::MinimizeAddrInV5::Default)); +static cl::opt EmitInlinedCallsiteLoc( + "emit-inlined-callsite-loc", cl::Hidden, + cl::desc("Emit additional .loc for callsites of inlined functions"), + cl::init(false)); + static constexpr unsigned ULEB128PadSize = 4; void DebugLocDwarfExpression::emitOp(uint8_t Op, const char *Comment) { @@ -1954,6 +1959,45 @@ } } +bool DwarfDebug::emitInlinedCallsiteLocs(const DebugLoc &Loc, + const DILocation *InlinedAt, + const MachineInstr *MI) { + // Skip emission of callsite locs if original location is a prolog end. + if (Loc == PrologEndLoc) + return false; + + // TODO: should we also skip emission of the callsite location, + // the same line/column has been already emitted or will be emitted later? + + bool Emitted = false; + + // Emit inner inlined callsites first. + if (auto *NestedInlinedAt = InlinedAt->getInlinedAt()) + Emitted = emitInlinedCallsiteLocs(InlinedAt, NestedInlinedAt, MI); + + // No need to emit additional .loc for a callsite if the current + // location already describes the same line. + if (Loc.getLine() == InlinedAt->getLine() && + Loc.getCol() == InlinedAt->getColumn()) + return Emitted; + + auto *InlinedSP = cast(Loc.getScope())->getSubprogram(); + auto *InlinedScope = LScopes.findInlinedScope(InlinedSP, InlinedAt); + if (!InlinedScope) + return Emitted; + + // Conservatively, emit inlined callsite location iff the given + // inlined function has a single, continuous range. + // TODO: consider more complex heuristic to cover more cases. + if (InlinedScope->getRanges().size() == 1 && + InlinedScope->getRanges().begin()->first == MI) { + recordSourceLine(InlinedAt->getLine(), InlinedAt->getColumn(), + InlinedAt->getScope(), DWARF2_FLAG_IS_STMT); + return true; + } + return Emitted; +} + // Process beginning of an instruction. void DwarfDebug::beginInstruction(const MachineInstr *MI) { const MachineFunction &MF = *MI->getMF(); @@ -2059,15 +2103,22 @@ // (The new location might be an explicit line 0, which we do emit.) if (DL.getLine() == 0 && LastAsmLine == 0) return; + + bool IsCallSiteEmitted = false; + if (EmitInlinedCallsiteLoc || tuneForGDB()) + if (auto *InlinedAt = DL.getInlinedAt()) + IsCallSiteEmitted = emitInlinedCallsiteLocs(DL, InlinedAt, MI); + unsigned Flags = 0; if (DL == PrologEndLoc) { Flags |= DWARF2_FLAG_PROLOGUE_END | DWARF2_FLAG_IS_STMT; PrologEndLoc = DebugLoc(); } + // If the line changed, we call that a new statement; unless we went to // line 0 and came back, in which case it is not a new statement. unsigned OldLine = PrevInstLoc ? PrevInstLoc.getLine() : LastAsmLine; - if (DL.getLine() && DL.getLine() != OldLine) + if (DL.getLine() && (DL.getLine() != OldLine || IsCallSiteEmitted)) Flags |= DWARF2_FLAG_IS_STMT; const MDNode *Scope = DL.getScope(); diff --git a/llvm/test/DebugInfo/X86/debug-line-inlined-callsite.ll b/llvm/test/DebugInfo/X86/debug-line-inlined-callsite.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/X86/debug-line-inlined-callsite.ll @@ -0,0 +1,161 @@ +; RUN: llc -O0 < %s | FileCheck %s + +; The test was compiled from the following source code. + +; 1 inline __attribute__((always_inline)) +; 2 int inlined(int x) { +; 3 x = x + 1; +; 4 return x; +; 5 } +; 6 int test1() { +; 7 int a = 42; +; 8 return inlined(a); +; 9 } +; 10 +; 11 int test2() { +; 12 int a = 0; +; 13 return inlined(a) + inlined(a); +; 14 } +; 15 +; 16 void foo(); +; 17 +; 18 inline __attribute__((always_inline)) +; 19 void inner2() { +; 20 foo(); +; 21 } +; 22 inline __attribute__((always_inline)) +; 23 void inner() { +; 24 inner2(); +; 25 } +; 26 inline __attribute__((always_inline)) +; 27 void outer() { +; 28 inner(); +; 29 } +; 30 +; 31 void test3() { +; 32 int a = 0; // dummy src line to force the scope to be emitted. +; 33 outer(); +; 34 } + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK-LABEL: _Z5test1v: +; CHECK: .Ltmp0: +; CHECK-NEXT: .loc 0 8 10 +; CHECK-NEXT: .loc 0 3 7 + +; CHECK-LABEL: _Z5test2v: +; CHECK: .Ltmp3: +; CHECK-NEXT: .loc 0 13 10 +; CHECK-NEXT: .loc 0 3 7 +; CHECK: .Ltmp5 +; CHECK-NEXT: .loc 0 13 23 +; CHECK-NEXT: .loc 0 3 7 + +; CHECK-LABEL: _Z5test3v: +; CHECK: .Ltmp9: +; CHECK-NEXT: .loc 0 33 3 +; CHECK-NEXT: .loc 0 28 3 +; CHECK-NEXT: .loc 0 24 3 +; CHECK-NEXT: .loc 0 20 3 +; CHECK-NEXT: callq _Z3foov@PLT + +define dso_local i32 @_Z5test1v() !dbg !10 { +entry: + %x.addr.i = alloca i32, align 4 + %a = alloca i32, align 4 + store i32 42, ptr %a, align 4, !dbg !13 + %0 = load i32, ptr %a, align 4, !dbg !14 + store i32 %0, ptr %x.addr.i, align 4 + %1 = load i32, ptr %x.addr.i, align 4, !dbg !15 + %add.i = add nsw i32 %1, 1, !dbg !18 + store i32 %add.i, ptr %x.addr.i, align 4, !dbg !19 + %2 = load i32, ptr %x.addr.i, align 4, !dbg !20 + ret i32 %2, !dbg !21 +} + +define dso_local i32 @_Z5test2v() !dbg !22 { +entry: + %x.addr.i2 = alloca i32, align 4 + %x.addr.i = alloca i32, align 4 + %a = alloca i32, align 4 + store i32 0, ptr %a, align 4, !dbg !23 + %0 = load i32, ptr %a, align 4, !dbg !24 + store i32 %0, ptr %x.addr.i2, align 4 + %1 = load i32, ptr %x.addr.i2, align 4, !dbg !25 + %add.i3 = add nsw i32 %1, 1, !dbg !27 + store i32 %add.i3, ptr %x.addr.i2, align 4, !dbg !28 + %2 = load i32, ptr %x.addr.i2, align 4, !dbg !29 + %3 = load i32, ptr %a, align 4, !dbg !30 + store i32 %3, ptr %x.addr.i, align 4 + %4 = load i32, ptr %x.addr.i, align 4, !dbg !31 + %add.i = add nsw i32 %4, 1, !dbg !33 + store i32 %add.i, ptr %x.addr.i, align 4, !dbg !34 + %5 = load i32, ptr %x.addr.i, align 4, !dbg !35 + %add = add nsw i32 %2, %5, !dbg !36 + ret i32 %add, !dbg !37 +} + +define dso_local void @_Z5test3v() !dbg !38 { +entry: + %a = alloca i32, align 4 + store i32 0, ptr %a, align 4, !dbg !39 + call void @_Z3foov(), !dbg !40 + ret void, !dbg !47 +} + +declare void @_Z3foov() + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 15.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/", checksumkind: CSK_MD5, checksum: "6d2167be80e37341d72d19b8f8eab50a") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"PIC Level", i32 2} +!6 = !{i32 7, !"PIE Level", i32 2} +!7 = !{i32 7, !"uwtable", i32 2} +!8 = !{i32 7, !"frame-pointer", i32 2} +!9 = !{!"clang version 15.0.0"} +!10 = distinct !DISubprogram(name: "test1", scope: !1, file: !1, line: 6, type: !11, scopeLine: 6, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!11 = !DISubroutineType(types: !12) +!12 = !{} +!13 = !DILocation(line: 7, column: 7, scope: !10) +!14 = !DILocation(line: 8, column: 18, scope: !10) +!15 = !DILocation(line: 3, column: 7, scope: !16, inlinedAt: !17) +!16 = distinct !DISubprogram(name: "inlined", scope: !1, file: !1, line: 2, type: !11, scopeLine: 2, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!17 = distinct !DILocation(line: 8, column: 10, scope: !10) +!18 = !DILocation(line: 3, column: 9, scope: !16, inlinedAt: !17) +!19 = !DILocation(line: 3, column: 5, scope: !16, inlinedAt: !17) +!20 = !DILocation(line: 4, column: 10, scope: !16, inlinedAt: !17) +!21 = !DILocation(line: 8, column: 3, scope: !10) +!22 = distinct !DISubprogram(name: "test2", scope: !1, file: !1, line: 11, type: !11, scopeLine: 11, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!23 = !DILocation(line: 12, column: 7, scope: !22) +!24 = !DILocation(line: 13, column: 18, scope: !22) +!25 = !DILocation(line: 3, column: 7, scope: !16, inlinedAt: !26) +!26 = distinct !DILocation(line: 13, column: 10, scope: !22) +!27 = !DILocation(line: 3, column: 9, scope: !16, inlinedAt: !26) +!28 = !DILocation(line: 3, column: 5, scope: !16, inlinedAt: !26) +!29 = !DILocation(line: 4, column: 10, scope: !16, inlinedAt: !26) +!30 = !DILocation(line: 13, column: 31, scope: !22) +!31 = !DILocation(line: 3, column: 7, scope: !16, inlinedAt: !32) +!32 = distinct !DILocation(line: 13, column: 23, scope: !22) +!33 = !DILocation(line: 3, column: 9, scope: !16, inlinedAt: !32) +!34 = !DILocation(line: 3, column: 5, scope: !16, inlinedAt: !32) +!35 = !DILocation(line: 4, column: 10, scope: !16, inlinedAt: !32) +!36 = !DILocation(line: 13, column: 21, scope: !22) +!37 = !DILocation(line: 13, column: 3, scope: !22) +!38 = distinct !DISubprogram(name: "test3", scope: !1, file: !1, line: 31, type: !11, scopeLine: 31, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!39 = !DILocation(line: 32, column: 7, scope: !38) +!40 = !DILocation(line: 20, column: 3, scope: !41, inlinedAt: !42) +!41 = distinct !DISubprogram(name: "inner2", scope: !1, file: !1, line: 19, type: !11, scopeLine: 19, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!42 = distinct !DILocation(line: 24, column: 3, scope: !43, inlinedAt: !44) +!43 = distinct !DISubprogram(name: "inner", scope: !1, file: !1, line: 23, type: !11, scopeLine: 23, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!44 = distinct !DILocation(line: 28, column: 3, scope: !45, inlinedAt: !46) +!45 = distinct !DISubprogram(name: "outer", scope: !1, file: !1, line: 27, type: !11, scopeLine: 27, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!46 = distinct !DILocation(line: 33, column: 3, scope: !38) +!47 = !DILocation(line: 34, column: 1, scope: !38)