llvm.org GIT mirror llvm / eadc313
llvm-dwarfdump: Add an option to collect debug info quality metrics. At the last LLVM dev meeting we had a debug info for optimized code BoF session. In that session I presented some graphs that showed how the quality of the debug info produced by LLVM changed over the last couple of years. This is a cleaned up version of the patch I used to collect the this data. It is implemented as an extension of llvm-dwarfdump, adding a new --statistics option. The intended use-case is to automatically run this on the debug info produced by, e.g., our bots, to identify eyebrow-raising changes or regressions introduced by new transformations that we could act on. In the current form, two kinds of data are being collected: - The number of variables that have a debug location versus the number of variables in total (this takes into account inlined instances of the same function, so if a variable is completely missing form only one instance it will be found). - The PC range covered by variable location descriptions versus the PC range of all variables' containing lexical scopes. The output format is versioned and extensible, so I'm looking forward to both bug fixes and ideas for other data that would be interesting to track. Differential Revision: https://reviews.llvm.org/D36627 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@315101 91177308-0d34-0410-b5e6-96231b3b80d8 Adrian Prantl 1 year, 11 months ago
7 changed file(s) with 398 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
8383 Only recurse to a maximum depth of when dumping debug info
8484 entries.
8585
86 .. option:: --statistics
87
88 Collect debug info quality metrics and print the results
89 as machine-readable single-line JSON output.
90
8691 .. option:: -x, --regex
8792
8893 Treat any strings as regular expressions when searching
901901 return std::partition(std::begin(Range), std::end(Range), P);
902902 }
903903
904 /// Provide wrappers to std::lower_bound which take ranges instead of having to
905 /// pass begin/end explicitly.
906 template
907 auto lower_bound(R &&Range, ForwardIt I) -> decltype(std::begin(Range)) {
908 return std::lower_bound(std::begin(Range), std::end(Range), I);
909 }
910
904911 /// \brief Given a range of type R, iterate the entire range and return a
905912 /// SmallVector with elements of the vector. This is useful, for example,
906913 /// when you want to iterate a range and then sort the results.
0 ; RUN: llc -O0 %s -o - -filetype=obj \
1 ; RUN: | llvm-dwarfdump -statistics - | FileCheck %s
2
3 ; int GlobalConst = 42;
4 ; int Global;
5 ;
6 ; struct S {
7 ; static const int constant = 24;
8 ; } s;
9 ;
10 ; int __attribute__((always_inline)) square(int i) { return i * i; }
11 ; int cube(int i) {
12 ; int squared = square(i);
13 ; return squared*i;
14 ; }
15
16 ; GlobalConst,Global,s,s.constant,square::i,cube::i,cube::squared
17 ; CHECK: "unique source variables":7
18 ; +1 extra inline i.
19 ; CHECK: "source variables":8
20 ; -1 square::i
21 ; CHECK: "variables with location":7
22 ; CHECK: "scope bytes total":[[BYTES:[0-9]+]]
23 ; Because of the dbg.value in the middle of the function, the pc range coverage
24 ; must be below 100%.
25 ; CHECK-NOT: "scope bytes covered":0
26 ; CHECK-NOT "scope bytes covered":[[BYTES]]
27 ; CHECK: "scope bytes covered":
28
29 ; ModuleID = '/tmp/quality.cpp'
30 source_filename = "/tmp/quality.cpp"
31 target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
32 target triple = "x86_64-apple-macosx10.12.0"
33
34 %struct.S = type { i8 }
35
36 @GlobalConst = global i32 42, align 4, !dbg !0
37 @Global = global i32 0, align 4, !dbg !6
38 @s = global %struct.S zeroinitializer, align 1, !dbg !9
39
40 ; Function Attrs: alwaysinline nounwind ssp uwtable
41 define i32 @_Z6squarei(i32 %i) #0 !dbg !20 {
42 entry:
43 %i.addr = alloca i32, align 4
44 store i32 %i, i32* %i.addr, align 4
45 ; Modified to loose debug info for i here.
46 call void @llvm.dbg.declare(metadata i32* undef, metadata !23, metadata !24), !dbg !25
47 %0 = load i32, i32* %i.addr, align 4, !dbg !26
48 %1 = load i32, i32* %i.addr, align 4, !dbg !27
49 %mul = mul nsw i32 %0, %1, !dbg !28
50 ret i32 %mul, !dbg !29
51 }
52
53 ; Function Attrs: nounwind readnone speculatable
54 declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
55 declare void @llvm.dbg.value(metadata, metadata, metadata) #1
56
57 ; Function Attrs: noinline nounwind optnone ssp uwtable
58 define i32 @_Z4cubei(i32 %i) #2 !dbg !30 {
59 entry:
60 %i.addr.i = alloca i32, align 4
61 call void @llvm.dbg.declare(metadata i32* %i.addr.i, metadata !23, metadata !24), !dbg !31
62 %i.addr = alloca i32, align 4
63 %squared = alloca i32, align 4
64 store i32 %i, i32* %i.addr, align 4
65 call void @llvm.dbg.declare(metadata i32* %i.addr, metadata !33, metadata !24), !dbg !34
66 %0 = load i32, i32* %i.addr, align 4, !dbg !37
67 store i32 %0, i32* %i.addr.i, align 4
68 %1 = load i32, i32* %i.addr.i, align 4, !dbg !38
69 %2 = load i32, i32* %i.addr.i, align 4, !dbg !39
70 %mul.i = mul nsw i32 %1, %2, !dbg !40
71 ; Modified to cover only about 50% of the lexical scope.
72 call void @llvm.dbg.value(metadata i32 %mul.i, metadata !35, metadata !24), !dbg !36
73 store i32 %mul.i, i32* %squared, align 4, !dbg !36
74 %3 = load i32, i32* %squared, align 4, !dbg !41
75 call void @llvm.dbg.value(metadata i32 %3, metadata !35, metadata !24), !dbg !36
76 %4 = load i32, i32* %i.addr, align 4, !dbg !42
77 %mul = mul nsw i32 %3, %4, !dbg !43
78 ret i32 %mul, !dbg !44
79 }
80
81 attributes #0 = { alwaysinline nounwind ssp uwtable }
82 attributes #1 = { nounwind readnone speculatable }
83 attributes #2 = { noinline nounwind optnone ssp uwtable }
84
85 !llvm.dbg.cu = !{!2}
86 !llvm.module.flags = !{!15, !16, !17, !18}
87 !llvm.ident = !{!19}
88
89 !0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
90 !1 = distinct !DIGlobalVariable(name: "GlobalConst", scope: !2, file: !3, line: 1, type: !8, isLocal: false, isDefinition: true)
91 !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "clang version 6.0.0 (trunk 310529) (llvm/trunk 310534)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5)
92 !3 = !DIFile(filename: "/tmp/quality.cpp", directory: "/Volumes/Data/llvm")
93 !4 = !{}
94 !5 = !{!0, !6, !9}
95 !6 = !DIGlobalVariableExpression(var: !7, expr: !DIExpression())
96 !7 = distinct !DIGlobalVariable(name: "Global", scope: !2, file: !3, line: 2, type: !8, isLocal: false, isDefinition: true)
97 !8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
98 !9 = !DIGlobalVariableExpression(var: !10, expr: !DIExpression())
99 !10 = distinct !DIGlobalVariable(name: "s", scope: !2, file: !3, line: 6, type: !11, isLocal: false, isDefinition: true)
100 !11 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", file: !3, line: 4, size: 8, elements: !12, identifier: "_ZTS1S")
101 !12 = !{!13}
102 !13 = !DIDerivedType(tag: DW_TAG_member, name: "constant", scope: !11, file: !3, line: 5, baseType: !14, flags: DIFlagStaticMember, extraData: i32 24)
103 !14 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !8)
104 !15 = !{i32 2, !"Dwarf Version", i32 4}
105 !16 = !{i32 2, !"Debug Info Version", i32 3}
106 !17 = !{i32 1, !"wchar_size", i32 4}
107 !18 = !{i32 7, !"PIC Level", i32 2}
108 !19 = !{!"clang version 6.0.0 (trunk 310529) (llvm/trunk 310534)"}
109 !20 = distinct !DISubprogram(name: "square", linkageName: "_Z6squarei", scope: !3, file: !3, line: 8, type: !21, isLocal: false, isDefinition: true, scopeLine: 8, flags: DIFlagPrototyped, isOptimized: false, unit: !2, variables: !4)
110 !21 = !DISubroutineType(types: !22)
111 !22 = !{!8, !8}
112 !23 = !DILocalVariable(name: "i", arg: 1, scope: !20, file: !3, line: 8, type: !8)
113 !24 = !DIExpression()
114 !25 = !DILocation(line: 8, column: 47, scope: !20)
115 !26 = !DILocation(line: 8, column: 59, scope: !20)
116 !27 = !DILocation(line: 8, column: 63, scope: !20)
117 !28 = !DILocation(line: 8, column: 61, scope: !20)
118 !29 = !DILocation(line: 8, column: 52, scope: !20)
119 !30 = distinct !DISubprogram(name: "cube", linkageName: "_Z4cubei", scope: !3, file: !3, line: 9, type: !21, isLocal: false, isDefinition: true, scopeLine: 9, flags: DIFlagPrototyped, isOptimized: false, unit: !2, variables: !4)
120 !31 = !DILocation(line: 8, column: 47, scope: !20, inlinedAt: !32)
121 !32 = distinct !DILocation(line: 10, column: 17, scope: !30)
122 !33 = !DILocalVariable(name: "i", arg: 1, scope: !30, file: !3, line: 9, type: !8)
123 !34 = !DILocation(line: 9, column: 14, scope: !30)
124 !35 = !DILocalVariable(name: "squared", scope: !30, file: !3, line: 10, type: !8)
125 !36 = !DILocation(line: 10, column: 7, scope: !30)
126 !37 = !DILocation(line: 10, column: 24, scope: !30)
127 !38 = !DILocation(line: 8, column: 59, scope: !20, inlinedAt: !32)
128 !39 = !DILocation(line: 8, column: 63, scope: !20, inlinedAt: !32)
129 !40 = !DILocation(line: 8, column: 61, scope: !20, inlinedAt: !32)
130 !41 = !DILocation(line: 11, column: 10, scope: !30)
131 !42 = !DILocation(line: 11, column: 18, scope: !30)
132 !43 = !DILocation(line: 11, column: 17, scope: !30)
133 !44 = !DILocation(line: 11, column: 3, scope: !30)
1313 HELP: -show-children
1414 HELP: -show-form
1515 HELP: -show-parents
16 HELP: -statistics
1617 HELP: -summarize-types
1718 HELP-NOT: -reverse-iterate
1819
77 )
88
99 add_llvm_tool(llvm-dwarfdump
10 Statistics.cpp
1011 llvm-dwarfdump.cpp
1112 )
1213
0 #include "llvm/ADT/DenseMap.h"
1 #include "llvm/DebugInfo/DIContext.h"
2 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
3 #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
4 #include "llvm/Object/ObjectFile.h"
5
6 #define DEBUG_TYPE "dwarfdump"
7 using namespace llvm;
8 using namespace object;
9
10 /// Holds statistics for one function (or other entity that has a PC range and
11 /// contains variables, such as a compile unit).
12 struct PerFunctionStats {
13 /// Number of inlined instances of this function.
14 unsigned NumFnInlined = 0;
15 /// Number of variables with location across all inlined instances.
16 unsigned TotalVarWithLoc = 0;
17 /// Number of constants with location across all inlined instances.
18 unsigned ConstantMembers = 0;
19 /// List of all Variables in this function.
20 SmallDenseSet VarsInFunction;
21 /// Compile units also cover a PC range, but have this flag set to false.
22 bool IsFunction = false;
23 };
24
25 /// Holds accumulated global statistics about local variables.
26 struct GlobalStats {
27 /// Total number of PC range bytes covered by DW_AT_locations.
28 unsigned ScopeBytesCovered = 0;
29 /// Total number of PC range bytes in each variable's enclosing scope,
30 /// starting from the first definition of the variable.
31 unsigned ScopeBytesFromFirstDefinition = 0;
32 };
33
34 /// Extract the low pc from a Die.
35 static uint64_t getLowPC(DWARFDie Die) {
36 if (Die.getAddressRanges().size())
37 return Die.getAddressRanges()[0].LowPC;
38 return dwarf::toAddress(Die.find(dwarf::DW_AT_low_pc), 0);
39 }
40
41 /// Collect debug info quality metrics for one DIE.
42 static void collectStatsForDie(DWARFDie Die, std::string Prefix,
43 uint64_t ScopeLowPC, uint64_t BytesInScope,
44 StringMap &FnStatMap,
45 GlobalStats &GlobalStats) {
46 bool HasLoc = false;
47 uint64_t BytesCovered = 0;
48 uint64_t OffsetToFirstDefinition = 0;
49 if (Die.find(dwarf::DW_AT_const_value)) {
50 // This catches constant members *and* variables.
51 HasLoc = true;
52 BytesCovered = BytesInScope;
53 } else if (Die.getTag() == dwarf::DW_TAG_variable ||
54 Die.getTag() == dwarf::DW_TAG_formal_parameter) {
55 // Handle variables and function arguments.
56 auto FormValue = Die.find(dwarf::DW_AT_location);
57 HasLoc = FormValue.hasValue();
58 if (HasLoc) {
59 // Get PC coverage.
60 if (auto DebugLocOffset = FormValue->getAsSectionOffset()) {
61 auto *DebugLoc = Die.getDwarfUnit()->getContext().getDebugLoc();
62 if (auto List = DebugLoc->getLocationListAtOffset(*DebugLocOffset)) {
63 for (auto Entry : List->Entries)
64 BytesCovered += Entry.End - Entry.Begin;
65 if (List->Entries.size()) {
66 uint64_t FirstDef = List->Entries[0].Begin;
67 uint64_t UnitOfs = getLowPC(Die.getDwarfUnit()->getUnitDIE());
68 // Ranges sometimes start before the lexical scope.
69 if (UnitOfs + FirstDef >= ScopeLowPC)
70 OffsetToFirstDefinition = UnitOfs + FirstDef - ScopeLowPC;
71 // Or even after it. Count that as a failure.
72 if (OffsetToFirstDefinition > BytesInScope)
73 OffsetToFirstDefinition = 0;
74 }
75 }
76 assert(BytesInScope);
77 } else {
78 // Assume the entire range is covered by a single location.
79 BytesCovered = BytesInScope;
80 }
81 }
82 } else {
83 // Not a variable or constant member.
84 return;
85 }
86
87 // Collect PC range coverage data.
88 auto &FnStats = FnStatMap[Prefix];
89 if (DWARFDie D =
90 Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin))
91 Die = D;
92 // This is a unique ID for the variable inside the current object file.
93 unsigned CanonicalDieOffset = Die.getOffset();
94 FnStats.VarsInFunction.insert(CanonicalDieOffset);
95 if (BytesInScope) {
96 FnStats.TotalVarWithLoc += (unsigned)HasLoc;
97 // Adjust for the fact the variables often start their lifetime in the
98 // middle of the scope.
99 BytesInScope -= OffsetToFirstDefinition;
100 // Turns out we have a lot of ranges that extend past the lexical scope.
101 GlobalStats.ScopeBytesCovered += std::min(BytesInScope, BytesCovered);
102 GlobalStats.ScopeBytesFromFirstDefinition += BytesInScope;
103 assert(GlobalStats.ScopeBytesCovered <=
104 GlobalStats.ScopeBytesFromFirstDefinition);
105 } else {
106 FnStats.ConstantMembers++;
107 }
108 }
109
110 /// Recursively collect debug info quality metrics.
111 static void collectStatsRecursive(DWARFDie Die, std::string Prefix,
112 uint64_t ScopeLowPC, uint64_t BytesInScope,
113 StringMap &FnStatMap,
114 GlobalStats &GlobalStats) {
115 // Handle any kind of lexical scope.
116 if (Die.getTag() == dwarf::DW_TAG_subprogram ||
117 Die.getTag() == dwarf::DW_TAG_inlined_subroutine ||
118 Die.getTag() == dwarf::DW_TAG_lexical_block) {
119 // Ignore forward declarations.
120 if (Die.find(dwarf::DW_AT_declaration))
121 return;
122
123 // Count the function.
124 if (Die.getTag() != dwarf::DW_TAG_lexical_block) {
125 StringRef Name = Die.getName(DINameKind::LinkageName);
126 if (Name.empty())
127 Name = Die.getName(DINameKind::ShortName);
128 Prefix = Name;
129 // Skip over abstract origins.
130 if (Die.find(dwarf::DW_AT_inline))
131 return;
132 // We've seen an (inlined) instance of this function.
133 auto &FnStats = FnStatMap[Name];
134 FnStats.NumFnInlined++;
135 FnStats.IsFunction = true;
136 }
137
138 // PC Ranges.
139 auto Ranges = Die.getAddressRanges();
140 uint64_t BytesInThisScope = 0;
141 for (auto Range : Ranges)
142 BytesInThisScope += Range.HighPC - Range.LowPC;
143 ScopeLowPC = getLowPC(Die);
144
145 if (BytesInThisScope)
146 BytesInScope = BytesInThisScope;
147 } else {
148 // Not a scope, visit the Die itself. It could be a variable.
149 collectStatsForDie(Die, Prefix, ScopeLowPC, BytesInScope, FnStatMap,
150 GlobalStats);
151 }
152
153 // Traverse children.
154 DWARFDie Child = Die.getFirstChild();
155 while (Child) {
156 collectStatsRecursive(Child, Prefix, ScopeLowPC, BytesInScope, FnStatMap,
157 GlobalStats);
158 Child = Child.getSibling();
159 }
160 }
161
162 /// Print machine-readable output.
163 /// The machine-readable format is single-line JSON output.
164 /// \{
165 static void printDatum(raw_ostream &OS, const char *Key, StringRef Value) {
166 OS << ",\"" << Key << "\":\"" << Value << '"';
167 DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
168 }
169 static void printDatum(raw_ostream &OS, const char *Key, uint64_t Value) {
170 OS << ",\"" << Key << "\":" << Value;
171 DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
172 }
173 /// \}
174
175 /// Collect debug info quality metrics for an entire DIContext.
176 ///
177 /// Do the impossible and reduce the quality of the debug info down to a few
178 /// numbers. The idea is to condense the data into numbers that can be tracked
179 /// over time to identify trends in newer compiler versions and gauge the effect
180 /// of particular optimizations. The raw numbers themselves are not particularly
181 /// useful, only the delta between compiling the same program with different
182 /// compilers is.
183 bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
184 Twine Filename, raw_ostream &OS) {
185 StringRef FormatName = Obj.getFileFormatName();
186 GlobalStats GlobalStats;
187 StringMap Statistics;
188 for (const auto &CU : static_cast(&DICtx)->compile_units())
189 if (DWARFDie CUDie = CU->getUnitDIE(false))
190 collectStatsRecursive(CUDie, "/", 0, 0, Statistics, GlobalStats);
191
192 /// The version number should be increased every time the algorithm is changed
193 /// (including bug fixes). New metrics may be added without increasing the
194 /// version.
195 unsigned Version = 1;
196 unsigned VarTotal = 0;
197 unsigned VarUnique = 0;
198 unsigned VarWithLoc = 0;
199 unsigned NumFunctions = 0;
200 unsigned NumInlinedFunctions = 0;
201 for (auto &Entry : Statistics) {
202 PerFunctionStats &Stats = Entry.getValue();
203 unsigned TotalVars = Stats.VarsInFunction.size() * Stats.NumFnInlined;
204 unsigned Constants = Stats.ConstantMembers;
205 VarWithLoc += Stats.TotalVarWithLoc + Constants;
206 VarTotal += TotalVars + Constants;
207 VarUnique += Stats.VarsInFunction.size();
208 DEBUG(for (auto V : Stats.VarsInFunction)
209 llvm::dbgs() << Entry.getKey() << ": " << V << "\n");
210 NumFunctions += Stats.IsFunction;
211 NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined;
212 }
213
214 // Print summary.
215 OS.SetBufferSize(1024);
216 OS << "{\"version\":\"" << Version << '"';
217 DEBUG(llvm::dbgs() << "Variable location quality metrics\n";
218 llvm::dbgs() << "---------------------------------\n");
219 printDatum(OS, "file", Filename.str());
220 printDatum(OS, "format", FormatName);
221 printDatum(OS, "source functions", NumFunctions);
222 printDatum(OS, "inlined functions", NumInlinedFunctions);
223 printDatum(OS, "unique source variables", VarUnique);
224 printDatum(OS, "source variables", VarTotal);
225 printDatum(OS, "variables with location", VarWithLoc);
226 printDatum(OS, "scope bytes total",
227 GlobalStats.ScopeBytesFromFirstDefinition);
228 printDatum(OS, "scope bytes covered", GlobalStats.ScopeBytesCovered);
229 OS << "}\n";
230 DEBUG(
231 llvm::dbgs() << "Total Availability: "
232 << (int)std::round((VarWithLoc * 100.0) / VarTotal) << "%\n";
233 llvm::dbgs() << "PC Ranges covered: "
234 << (int)std::round((GlobalStats.ScopeBytesCovered * 100.0) /
235 GlobalStats.ScopeBytesFromFirstDefinition)
236 << "%\n");
237 return true;
238 }
200200 SummarizeTypes("summarize-types",
201201 desc("Abbreviate the description of type unit entries"),
202202 cat(DwarfDumpCategory));
203 static cl::opt
204 Statistics("statistics",
205 cl::desc("Emit JSON-formatted debug info quality metrics."),
206 cat(DwarfDumpCategory));
203207 static opt Verify("verify", desc("Verify the DWARF debug info"),
204208 cat(DwarfDumpCategory));
205209 static opt Quiet("quiet", desc("Use with -verify to not emit to STDOUT."),
299303 }
300304 }
301305 }
306
307 bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
308 Twine Filename, raw_ostream &OS);
302309
303310 static bool dumpObjectFile(ObjectFile &Obj, DWARFContext &DICtx, Twine Filename,
304311 raw_ostream &OS) {
535542 return handleFile(Object, verifyObjectFile, OS);
536543 }))
537544 exit(1);
538 } else
545 } else if (Statistics)
546 for (auto Object : Objects)
547 handleFile(Object, collectStatsForObjectFile, OS);
548 else
539549 for (auto Object : Objects)
540550 handleFile(Object, dumpObjectFile, OS);
541551