
Split DWARF doesn't handle LTO of any form (roughly there's an assumption that each dwo file will have one CU - it's not explicitly documented, nor explicitly handled, so the ecosystem isn't really well understood/tested/etc). This had previously been handled by implementing (& disabling by default) the `-split-dwarf-cross-cu-references` flag, which would disable use of ref_addr across two dwo CUs. This worked for a while, at least in LTO (it didn't address Split DWARF+Full LTO, but that's an unlikely combination, as the benefits of Split DWARF are more limited in a full LTO build) - because the only source of cross-CU references was inlined functions, so by making those non-cross-CU (by moving the referenced inlined function DWARF description into the referencing CU) the result was one CU per dwo. But recently the Function Specialization pass was added to the ThinLTO pipeline, which caused imported functions that may not be inlined to be emitted by a backend compile. This meant foreign CU entities (not just abstract origins/cross-CU referenced entities)/standalone foreign CUs could be emitted by a backend compile. The end result was, due to a bug* in binutils dwp (I think basically it saw two CUs in a single dwo and reprocessed the offsets in the shared debug_str_offsets.dwo section) this situation lead to corrupted strings. So to make this more robust, I've generalized the definition of the `-split-dwarf-cross-cu-references` flag (perhaps it should be renamed at this point, but it's /really/ niche, doubt anyone's using it - more or less there for experimentation when we get around to figuring out spec'ing LTO+Split DWARF) to mean "single CU in a dwo file" and added more general handling for this. There's certainly some weird corner cases that could come up in terms of "how do we choose which CU to put everything in" - for now it's "first come, first served" which is probably going to be OK for ThinLTO - the base module will have the first functions and first CU, imported fragments will come after that. For LTO the choice will be fairly arbitrary - but, again, essentially whichever module comes first. * Arguably a bug in binutils dwp, but since the feature isn't well specified, I'd rather avoid dabbling in this uncertain area and ensure LLVM doesn't produce especially novel DWARF (dwos with multiple CUs) regardless of whether binutils dwp would/should be fixed. I'm not confident debuggers could read such a dwo file well, etc.
179 lines
8.5 KiB
LLVM
179 lines
8.5 KiB
LLVM
; RUN: llc -mtriple=x86_64-linux -split-dwarf-cross-cu-references -split-dwarf-file=foo.dwo -filetype=obj -o %t < %s
|
|
; RUN: llvm-objdump -r %t | FileCheck --check-prefix=CHECK --check-prefix=RELO_CROSS %s
|
|
; RUN: llvm-dwarfdump -v -debug-info %t | FileCheck --check-prefix=ALL --check-prefix=DWO --check-prefix=CROSS %s
|
|
; RUN: llvm-dwarfdump -v -debug-info %t | FileCheck --check-prefix=ALL %s
|
|
|
|
; RUN: llc -mtriple=x86_64-linux -split-dwarf-file=foo.dwo -filetype=obj -o %t < %s
|
|
; RUN: llvm-objdump -r %t | FileCheck --check-prefix=CHECK %s
|
|
; RUN: llvm-dwarfdump -v -debug-info %t | FileCheck --check-prefix=ALL --check-prefix=DWO --check-prefix=NOCROSS %s
|
|
; RUN: llvm-dwarfdump -v -debug-info %t | FileCheck --check-prefix=ALL %s
|
|
|
|
; Testing cross-CU references for types, subprograms, and variables
|
|
; Built from code something like this:
|
|
; foo.cpp:
|
|
; struct t1 { int i; };
|
|
; void f();
|
|
; __attribute__((always_inline)) void f1(t1 t) {
|
|
; f();
|
|
; }
|
|
; void foo(t1 t) {
|
|
; f1(t);
|
|
; }
|
|
; bar.cpp:
|
|
; struct t1 { int i; };
|
|
; void f1(t1);
|
|
; void bar(t1 t) {
|
|
; f1(t);
|
|
; }
|
|
; $ clang++-tot -emit-llvm -S {foo,bar}.cpp -g
|
|
; $ llvm-link-tot {foo,bar}.ll -S -o foobar.ll
|
|
; $ clang++-tot -emit-llvm foobar.ll -o foobar.opt.ll -S -c
|
|
;
|
|
; Then manually removing the original f1 definition, to simplify the DWARF a bit
|
|
; (so it only has the inlined definitions, no concrete definition)
|
|
|
|
; Check that:
|
|
; * no relocations are emitted for the debug_info.dwo section no matter what
|
|
; * one debug_info->debug_info relocation in debug_info no matter what (for
|
|
; split dwarf inlining)
|
|
; * debug_info uses relocations and ref_addr no matter what
|
|
; * debug_info.dwo uses relocations for types as well as abstract subprograms
|
|
; and variables when -split-dwarf-cross-cu-references is used
|
|
; * debug_info.dwo contains duplicate types, abstract subprograms and abstract
|
|
; variables otherwise to avoid the need for cross-cu references
|
|
|
|
; DWO: .debug_info.dwo contents:
|
|
; CHECK-NOT: .rel{{a?}}.debug_info.dwo
|
|
; CHECK: RELOCATION RECORDS FOR [.debug_info]:
|
|
; CHECK-NOT: RELOCATION RECORDS
|
|
; Expect one relocation in debug_info, from the inlined f1 in foo to its
|
|
; abstract origin in bar
|
|
; RELO_CROSS: R_X86_64_32 .debug_info
|
|
; Expect no relocations in debug_info when disabling multiple CUs in Split DWARF
|
|
; CHECK-NOT: .debug_info
|
|
; CHECK: RELOCATION RECORDS
|
|
; CHECK-NOT: .rel{{a?}}.debug_info.dwo
|
|
|
|
; ALL: Compile Unit
|
|
; ALL: DW_TAG_compile_unit
|
|
; DWO: DW_AT_name {{.*}} "foo.cpp"
|
|
; ALL: 0x[[F1:.*]]: DW_TAG_subprogram
|
|
; ALL: DW_AT_name {{.*}} "f1"
|
|
; DWO: 0x[[F1T:.*]]: DW_TAG_formal_parameter
|
|
; DWO: DW_AT_name {{.*}} "t"
|
|
; DWO: DW_AT_type [DW_FORM_ref4] {{.*}}{0x[[T1:.*]]}
|
|
; DWO: NULL
|
|
; DWO: 0x[[T1]]: DW_TAG_structure_type
|
|
; DWO: DW_AT_name {{.*}} "t1"
|
|
; ALL: DW_TAG_subprogram
|
|
; ALL: DW_AT_name {{.*}} "foo"
|
|
; DWO: DW_TAG_formal_parameter
|
|
; DWO: DW_AT_name {{.*}} "t"
|
|
; DWO: DW_AT_type [DW_FORM_ref4] {{.*}}{0x[[T1]]}
|
|
; ALL: DW_TAG_inlined_subroutine
|
|
; ALL: DW_AT_abstract_origin [DW_FORM_ref4] {{.*}}{0x[[F1]]}
|
|
; DWO: DW_TAG_formal_parameter
|
|
; DWO: DW_AT_abstract_origin [DW_FORM_ref4] {{.*}}{0x[[F1T]]}
|
|
|
|
; NOCROSS-NOT: DW_TAG_compile_unit
|
|
; CROSS: Compile Unit
|
|
; CROSS: DW_TAG_compile_unit
|
|
; CROSS: DW_AT_name {{.*}} "bar.cpp"
|
|
; ALL: DW_TAG_subprogram
|
|
; ALL: DW_AT_name {{.*}} "bar"
|
|
; DWO: DW_TAG_formal_parameter
|
|
; DWO: DW_AT_name {{.*}} "t"
|
|
; CROSS: DW_AT_type [DW_FORM_ref_addr] (0x00000000[[T1]]
|
|
; NOCROSS: DW_AT_type [DW_FORM_ref4] {{.*}}{0x[[T1]]}
|
|
; ALL: DW_TAG_inlined_subroutine
|
|
; CROSS: DW_AT_abstract_origin [DW_FORM_ref_addr] (0x00000000[[F1]]
|
|
; NOCROSS: DW_AT_abstract_origin [DW_FORM_ref4] {{.*}}{0x[[F1]]}
|
|
; DWO: DW_TAG_formal_parameter
|
|
; CROSS: DW_AT_abstract_origin [DW_FORM_ref_addr] (0x00000000[[F1T]]
|
|
; NOCROSS: DW_AT_abstract_origin [DW_FORM_ref4] {{.*}}{0x[[F1T]]
|
|
|
|
%struct.t1 = type { i32 }
|
|
|
|
; Function Attrs: nounwind readnone speculatable
|
|
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
|
|
|
|
declare void @_Z1fv() #2
|
|
|
|
; Function Attrs: noinline uwtable
|
|
define void @_Z3foo2t1(i32 %t.coerce) #3 !dbg !20 {
|
|
entry:
|
|
%t.i = alloca %struct.t1, align 4
|
|
call void @llvm.dbg.declare(metadata ptr %t.i, metadata !15, metadata !16), !dbg !21
|
|
%t = alloca %struct.t1, align 4
|
|
%agg.tmp = alloca %struct.t1, align 4
|
|
store i32 %t.coerce, ptr %t, align 4
|
|
call void @llvm.dbg.declare(metadata ptr %t, metadata !23, metadata !16), !dbg !24
|
|
call void @llvm.memcpy.p0.p0.i64(ptr align 4 %agg.tmp, ptr align 4 %t, i64 4, i1 false), !dbg !25
|
|
%0 = load i32, ptr %agg.tmp, align 4, !dbg !26
|
|
store i32 %0, ptr %t.i, align 4
|
|
call void @_Z1fv(), !dbg !27
|
|
ret void, !dbg !28
|
|
}
|
|
|
|
; Function Attrs: argmemonly nounwind
|
|
declare void @llvm.memcpy.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1) #4
|
|
|
|
; Function Attrs: noinline uwtable
|
|
define void @_Z3bar2t1(i32 %t.coerce) #3 !dbg !29 {
|
|
entry:
|
|
%t.i = alloca %struct.t1, align 4
|
|
call void @llvm.dbg.declare(metadata ptr %t.i, metadata !15, metadata !16), !dbg !30
|
|
%t = alloca %struct.t1, align 4
|
|
%agg.tmp = alloca %struct.t1, align 4
|
|
store i32 %t.coerce, ptr %t, align 4
|
|
call void @llvm.dbg.declare(metadata ptr %t, metadata !32, metadata !16), !dbg !33
|
|
call void @llvm.memcpy.p0.p0.i64(ptr align 4 %agg.tmp, ptr align 4 %t, i64 4, i1 false), !dbg !34
|
|
%0 = load i32, ptr %agg.tmp, align 4, !dbg !35
|
|
store i32 %0, ptr %t.i, align 4
|
|
call void @_Z1fv(), !dbg !36
|
|
ret void, !dbg !37
|
|
}
|
|
|
|
!llvm.dbg.cu = !{!0, !3}
|
|
!llvm.ident = !{!5, !5}
|
|
!llvm.module.flags = !{!6, !7}
|
|
|
|
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 5.0.0 (trunk 302809) (llvm/trunk 302815)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: true)
|
|
!1 = !DIFile(filename: "foo.cpp", directory: "/usr/local/google/home/blaikie/dev/scratch")
|
|
!2 = !{}
|
|
!3 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !4, producer: "clang version 5.0.0 (trunk 302809) (llvm/trunk 302815)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: true)
|
|
!4 = !DIFile(filename: "bar.cpp", directory: "/usr/local/google/home/blaikie/dev/scratch")
|
|
!5 = !{!"clang version 5.0.0 (trunk 302809) (llvm/trunk 302815)"}
|
|
!6 = !{i32 2, !"Dwarf Version", i32 4}
|
|
!7 = !{i32 2, !"Debug Info Version", i32 3}
|
|
!8 = distinct !DISubprogram(name: "f1", linkageName: "_Z2f12t1", scope: !1, file: !1, line: 3, type: !9, isLocal: false, isDefinition: true, scopeLine: 3, flags: DIFlagPrototyped, isOptimized: false, unit: !0, retainedNodes: !2)
|
|
!9 = !DISubroutineType(types: !10)
|
|
!10 = !{null, !11}
|
|
!11 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t1", file: !1, line: 1, size: 32, elements: !12, identifier: "_ZTS2t1")
|
|
!12 = !{!13}
|
|
!13 = !DIDerivedType(tag: DW_TAG_member, name: "i", scope: !11, file: !1, line: 1, baseType: !14, size: 32)
|
|
!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
|
|
!15 = !DILocalVariable(name: "t", arg: 1, scope: !8, file: !1, line: 3, type: !11)
|
|
!16 = !DIExpression()
|
|
!17 = !DILocation(line: 3, column: 43, scope: !8)
|
|
!18 = !DILocation(line: 4, column: 3, scope: !8)
|
|
!19 = !DILocation(line: 5, column: 1, scope: !8)
|
|
!20 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foo2t1", scope: !1, file: !1, line: 6, type: !9, isLocal: false, isDefinition: true, scopeLine: 6, flags: DIFlagPrototyped, isOptimized: false, unit: !0, retainedNodes: !2)
|
|
!21 = !DILocation(line: 3, column: 43, scope: !8, inlinedAt: !22)
|
|
!22 = distinct !DILocation(line: 7, column: 3, scope: !20)
|
|
!23 = !DILocalVariable(name: "t", arg: 1, scope: !20, file: !1, line: 6, type: !11)
|
|
!24 = !DILocation(line: 6, column: 13, scope: !20)
|
|
!25 = !DILocation(line: 7, column: 6, scope: !20)
|
|
!26 = !DILocation(line: 7, column: 3, scope: !20)
|
|
!27 = !DILocation(line: 4, column: 3, scope: !8, inlinedAt: !22)
|
|
!28 = !DILocation(line: 8, column: 1, scope: !20)
|
|
!29 = distinct !DISubprogram(name: "bar", linkageName: "_Z3bar2t1", scope: !4, file: !4, line: 3, type: !9, isLocal: false, isDefinition: true, scopeLine: 3, flags: DIFlagPrototyped, isOptimized: false, unit: !3, retainedNodes: !2)
|
|
!30 = !DILocation(line: 3, column: 43, scope: !8, inlinedAt: !31)
|
|
!31 = distinct !DILocation(line: 4, column: 3, scope: !29)
|
|
!32 = !DILocalVariable(name: "t", arg: 1, scope: !29, file: !4, line: 3, type: !11)
|
|
!33 = !DILocation(line: 3, column: 13, scope: !29)
|
|
!34 = !DILocation(line: 4, column: 6, scope: !29)
|
|
!35 = !DILocation(line: 4, column: 3, scope: !29)
|
|
!36 = !DILocation(line: 4, column: 3, scope: !8, inlinedAt: !31)
|
|
!37 = !DILocation(line: 5, column: 1, scope: !29)
|