[DWARF] Allow cross-CU references of subprogram definitions

This allows a call site tag in CU A to reference a callee DIE in CU B
without resorting to creating an incomplete duplicate DIE for the callee
inside of CU A.

We already allow cross-CU references of subprogram declarations, so it
doesn't seem like definitions ought to be special.

This improves entry value evaluation and tail call frame synthesis in
the LTO setting. During LTO, it's common for cross-module inlining to
produce a call in some CU A where the callee resides in a different CU,
and there is no declaration subprogram for the callee anywhere. In this
case llvm would (unnecessarily, I think) emit an empty DW_TAG_subprogram
in order to fill in the call site tag. That empty 'definition' defeats
entry value evaluation etc., because the debugger can't figure out what
it means.

As a follow-up, maybe we could add a DWARF verifier check that a
DW_TAG_subprogram at least has a DW_AT_name attribute.

rdar://46577651

Differential Revision: https://reviews.llvm.org/D70350
This commit is contained in:
Vedant Kumar 2019-11-15 15:37:29 -08:00
parent e8d955f29d
commit 30038da15b
5 changed files with 249 additions and 7 deletions

View File

@ -964,8 +964,8 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(
addAddress(CallSiteDIE, getDwarf5OrGNUAttr(dwarf::DW_AT_call_target),
MachineLocation(CallReg));
} else {
DIE *CalleeDIE = getOrCreateSubprogramDIE(CalleeSP);
assert(CalleeDIE && "Could not create DIE for call site entry origin");
DIE *CalleeDIE = getDIE(CalleeSP);
assert(CalleeDIE && "Could not find DIE for call site entry origin");
addDIEEntry(CallSiteDIE, getDwarf5OrGNUAttr(dwarf::DW_AT_call_origin),
*CalleeDIE);
}

View File

@ -535,6 +535,14 @@ void DwarfDebug::constructAbstractSubprogramScopeDIE(DwarfCompileUnit &SrcCU,
}
}
DIE &DwarfDebug::constructSubprogramDefinitionDIE(const DISubprogram *SP) {
DICompileUnit *Unit = SP->getUnit();
assert(SP->isDefinition() && "Subprogram not a definition");
assert(Unit && "Subprogram definition without parent unit");
auto &CU = getOrCreateDwarfCompileUnit(Unit);
return *CU.getOrCreateSubprogramDIE(SP);
}
/// Try to interpret values loaded into registers that forward parameters
/// for \p CallMI. Store parameters with interpreted value into \p Params.
static void collectCallSiteParameters(const MachineInstr *CallMI,
@ -745,6 +753,15 @@ void DwarfDebug::constructCallSiteEntryDIEs(const DISubprogram &SP,
if (!CalleeDecl || !CalleeDecl->getSubprogram())
continue;
CalleeSP = CalleeDecl->getSubprogram();
if (CalleeSP->isDefinition()) {
// Ensure that a subprogram DIE for the callee is available in the
// appropriate CU.
constructSubprogramDefinitionDIE(CalleeSP);
} else {
assert(CU.getDIE(CalleeSP) &&
"Expected declaration subprogram DIE for callee");
}
}
// TODO: Omit call site entries for runtime calls (objc_msgSend, etc).

View File

@ -437,6 +437,9 @@ class DwarfDebug : public DebugHandlerBase {
/// Construct a DIE for this abstract scope.
void constructAbstractSubprogramScopeDIE(DwarfCompileUnit &SrcCU, LexicalScope *Scope);
/// Construct a DIE for the subprogram definition \p SP and return it.
DIE &constructSubprogramDefinitionDIE(const DISubprogram *SP);
/// Construct DIEs for call site entries describing the calls in \p MF.
void constructCallSiteEntryDIEs(const DISubprogram &SP, DwarfCompileUnit &CU,
DIE &ScopeDIE, const MachineFunction &MF);

View File

@ -188,8 +188,9 @@ int64_t DwarfUnit::getDefaultLowerBound() const {
/// Check whether the DIE for this MDNode can be shared across CUs.
bool DwarfUnit::isShareableAcrossCUs(const DINode *D) const {
// When the MDNode can be part of the type system, the DIE can be shared
// across CUs.
// When the MDNode can be part of the type system (this includes subprogram
// declarations *and* subprogram definitions, even local definitions), the
// DIE must be shared across CUs.
// Combining type units and cross-CU DIE sharing is lower value (since
// cross-CU DIE sharing is used in LTO and removes type redundancy at that
// level already) but may be implementable for some value in projects
@ -197,9 +198,7 @@ bool DwarfUnit::isShareableAcrossCUs(const DINode *D) const {
// together.
if (isDwoUnit() && !DD->shareAcrossDWOCUs())
return false;
return (isa<DIType>(D) ||
(isa<DISubprogram>(D) && !cast<DISubprogram>(D)->isDefinition())) &&
!DD->generateTypeUnits();
return (isa<DIType>(D) || isa<DISubprogram>(D)) && !DD->generateTypeUnits();
}
DIE *DwarfUnit::getDIE(const DINode *D) const {

View File

@ -0,0 +1,223 @@
; RUN: llc -mtriple=x86_64-apple-darwin -filetype=obj < %s -o %t.o
; RUN: llvm-dwarfdump %t.o | FileCheck %s -implicit-check-not=DW_TAG_subprogram
; RUN: llvm-dwarfdump --verify %t.o
; This test checks that cross-CU references within call site tags to subprogram
; definitions are well-formed. There are 5 cases checked in this test. Each set
; of checks is numbered and has a brief summary.
; Instructions to regenerate the IR:
; clang -O1 -g -emit-llvm -o a.bc -c a.c
; clang -O1 -g -emit-llvm -o b.bc -c b.c
; llvm-link -o linked.bc a.bc b.bc
; opt -O1 linked.bc -o merged.bc
; Source:
; // a.c
; __attribute__((optnone)) void noinline_func_in_a() {}
;
; __attribute__((optnone)) static void foo() {}
; __attribute__((always_inline)) void always_inline_helper_in_a_that_calls_foo() {
; foo();
; }
;
; extern void func_from_b();
; void call_func_in_b_from_a() {
; func_from_b();
; }
;
; // b.c
; extern void noinline_func_in_a();
; void call_noinline_func_in_a_from_b() {
; noinline_func_in_a();
; }
;
; __attribute__((optnone)) void foo() {}
; extern void always_inline_helper_in_a_that_calls_foo();
; void call_both_foos_from_b() {
; foo();
; always_inline_helper_in_a_that_calls_foo();
; }
;
; __attribute__((optnone)) void func_from_b() {}
; void call_func_in_b_from_b() {
; func_from_b();
; }
; === CU for a.c ===
; CHECK: DW_TAG_compile_unit
; CHECK: DW_AT_name ("a.c")
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("func_from_b")
; CHECK: DW_AT_declaration (true)
; CHECK: 0x{{0+}}[[NOINLINE_FUNC_IN_A:.*]]: DW_TAG_subprogram
; CHECK: DW_AT_name ("noinline_func_in_a")
; 1) Check that "always_inline_helper_in_a_that_calls_foo" calls the "foo" in
; a.c, and *not* the "foo" in b.c.
; CHECK: 0x{{0+}}[[ALWAYS_INLINE_HELPER_IN_A:.*]]: DW_TAG_subprogram
; CHECK: DW_AT_abstract_origin ({{.*}} "always_inline_helper_in_a_that_calls_foo")
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[FOO_IN_A:.*]])
; CHECK: 0x{{0+}}[[FOO_IN_A]]: DW_TAG_subprogram
; CHECK: DW_AT_name ("foo")
; 2) Check that "call_func_in_b_from_a" has a cross-CU ref into b.c.
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("call_func_in_b_from_a")
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[FUNC_FROM_B:.*]])
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("always_inline_helper_in_a_that_calls_foo")
; CHECK: DW_AT_inline (DW_INL_inlined)
; === CU for b.c ===
; CHECK: DW_TAG_compile_unit
; CHECK: DW_AT_name ("b.c")
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("noinline_func_in_a")
; CHECK: DW_AT_declaration (true)
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("always_inline_helper_in_a_that_calls_foo")
; CHECK: DW_AT_declaration (true)
; 3) Validate the cross-CU ref from "call_func_in_b_from_a" in a.c.
; CHECK: 0x{{0+}}[[FUNC_FROM_B]]: DW_TAG_subprogram
; CHECK: DW_AT_name ("func_from_b")
; 4) Validate the cross-CU ref from "call_noinline_func_in_a_from_b" in b.c.
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("call_noinline_func_in_a_from_b")
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[NOINLINE_FUNC_IN_A]])
; CHECK: 0x{{0+}}[[FOO_IN_B:.*]]: DW_TAG_subprogram
; CHECK: DW_AT_name ("foo")
; 5) Validate that we correctly emit a cross-CU ref when the call is inlined
; from another CU.
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("call_both_foos_from_b")
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[FOO_IN_B]])
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[FOO_IN_A]])
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("call_func_in_b_from_b")
; CHECK: DW_TAG_call_site
; CHECK-NEXT: DW_AT_call_origin (0x{{0+}}[[FUNC_FROM_B]])
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.14.0"
define void @noinline_func_in_a() local_unnamed_addr #0 !dbg !17 {
entry:
ret void, !dbg !20
}
define void @always_inline_helper_in_a_that_calls_foo() local_unnamed_addr #1 !dbg !21 {
entry:
tail call fastcc void @foo.2(), !dbg !22
ret void, !dbg !23
}
define internal fastcc void @foo.2() unnamed_addr #0 !dbg !24 {
entry:
ret void, !dbg !25
}
define void @call_func_in_b_from_a() local_unnamed_addr !dbg !26 {
entry:
tail call void @func_from_b() #3, !dbg !27
ret void, !dbg !28
}
define void @call_noinline_func_in_a_from_b() local_unnamed_addr !dbg !29 {
entry:
tail call void @noinline_func_in_a() #3, !dbg !30
ret void, !dbg !31
}
define void @foo() local_unnamed_addr #0 !dbg !32 {
entry:
ret void, !dbg !33
}
define void @call_both_foos_from_b() local_unnamed_addr !dbg !34 {
entry:
tail call void @foo(), !dbg !35
tail call fastcc void @foo.2() #3, !dbg !36
ret void, !dbg !38
}
define void @func_from_b() local_unnamed_addr #0 !dbg !39 {
entry:
ret void, !dbg !40
}
define void @call_func_in_b_from_b() local_unnamed_addr !dbg !41 {
entry:
tail call void @func_from_b(), !dbg !42
ret void, !dbg !43
}
attributes #0 = { noinline }
attributes #1 = { alwaysinline }
!llvm.dbg.cu = !{!0, !7}
!llvm.ident = !{!12, !12}
!llvm.module.flags = !{!13, !14, !15, !16}
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 10.0.0 (git@github.com:llvm/llvm-project.git 310e85309f870ee7347ef979d7d8da9bf28e92ea)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !3, nameTableKind: None)
!1 = !DIFile(filename: "a.c", directory: "/Users/vsk/tmp/lto-entry-vals")
!2 = !{}
!3 = !{!4}
!4 = !DISubprogram(name: "func_from_b", scope: !1, file: !1, line: 8, type: !5, spFlags: DISPFlagOptimized, retainedNodes: !2)
!5 = !DISubroutineType(types: !6)
!6 = !{null, null}
!7 = distinct !DICompileUnit(language: DW_LANG_C99, file: !8, producer: "clang version 10.0.0 (git@github.com:llvm/llvm-project.git 310e85309f870ee7347ef979d7d8da9bf28e92ea)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !9, nameTableKind: None)
!8 = !DIFile(filename: "b.c", directory: "/Users/vsk/tmp/lto-entry-vals")
!9 = !{!10, !11}
!10 = !DISubprogram(name: "noinline_func_in_a", scope: !8, file: !8, line: 1, type: !5, spFlags: DISPFlagOptimized, retainedNodes: !2)
!11 = !DISubprogram(name: "always_inline_helper_in_a_that_calls_foo", scope: !8, file: !8, line: 7, type: !5, spFlags: DISPFlagOptimized, retainedNodes: !2)
!12 = !{!"clang version 10.0.0 (git@github.com:llvm/llvm-project.git 310e85309f870ee7347ef979d7d8da9bf28e92ea)"}
!13 = !{i32 7, !"Dwarf Version", i32 4}
!14 = !{i32 2, !"Debug Info Version", i32 3}
!15 = !{i32 1, !"wchar_size", i32 4}
!16 = !{i32 7, !"PIC Level", i32 2}
!17 = distinct !DISubprogram(name: "noinline_func_in_a", scope: !1, file: !1, line: 1, type: !18, scopeLine: 1, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
!18 = !DISubroutineType(types: !19)
!19 = !{null}
!20 = !DILocation(line: 1, column: 53, scope: !17)
!21 = distinct !DISubprogram(name: "always_inline_helper_in_a_that_calls_foo", scope: !1, file: !1, line: 4, type: !18, scopeLine: 4, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
!22 = !DILocation(line: 5, column: 3, scope: !21)
!23 = !DILocation(line: 6, column: 1, scope: !21)
!24 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !18, scopeLine: 3, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
!25 = !DILocation(line: 3, column: 45, scope: !24)
!26 = distinct !DISubprogram(name: "call_func_in_b_from_a", scope: !1, file: !1, line: 9, type: !18, scopeLine: 9, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
!27 = !DILocation(line: 10, column: 3, scope: !26)
!28 = !DILocation(line: 11, column: 1, scope: !26)
!29 = distinct !DISubprogram(name: "call_noinline_func_in_a_from_b", scope: !8, file: !8, line: 2, type: !18, scopeLine: 2, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !7, retainedNodes: !2)
!30 = !DILocation(line: 3, column: 3, scope: !29)
!31 = !DILocation(line: 4, column: 1, scope: !29)
!32 = distinct !DISubprogram(name: "foo", scope: !8, file: !8, line: 6, type: !18, scopeLine: 6, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !7, retainedNodes: !2)
!33 = !DILocation(line: 6, column: 38, scope: !32)
!34 = distinct !DISubprogram(name: "call_both_foos_from_b", scope: !8, file: !8, line: 8, type: !18, scopeLine: 8, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !7, retainedNodes: !2)
!35 = !DILocation(line: 9, column: 3, scope: !34)
!36 = !DILocation(line: 5, column: 3, scope: !21, inlinedAt: !37)
!37 = distinct !DILocation(line: 10, column: 3, scope: !34)
!38 = !DILocation(line: 11, column: 1, scope: !34)
!39 = distinct !DISubprogram(name: "func_from_b", scope: !8, file: !8, line: 13, type: !18, scopeLine: 13, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !7, retainedNodes: !2)
!40 = !DILocation(line: 13, column: 46, scope: !39)
!41 = distinct !DISubprogram(name: "call_func_in_b_from_b", scope: !8, file: !8, line: 14, type: !18, scopeLine: 14, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !7, retainedNodes: !2)
!42 = !DILocation(line: 15, column: 3, scope: !41)
!43 = !DILocation(line: 16, column: 1, scope: !41)