1 #include "llvm/ADT/DenseMap.h"
2 #include "llvm/DebugInfo/DIContext.h"
3 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
4 #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
5 #include "llvm/Object/ObjectFile.h"
7 #define DEBUG_TYPE "dwarfdump"
9 using namespace object
;
11 /// Holds statistics for one function (or other entity that has a PC range and
12 /// contains variables, such as a compile unit).
13 struct PerFunctionStats
{
14 /// Number of inlined instances of this function.
15 unsigned NumFnInlined
= 0;
16 /// Number of variables with location across all inlined instances.
17 unsigned TotalVarWithLoc
= 0;
18 /// Number of constants with location across all inlined instances.
19 unsigned ConstantMembers
= 0;
20 /// List of all Variables in this function.
21 SmallDenseSet
<uint32_t, 4> VarsInFunction
;
22 /// Compile units also cover a PC range, but have this flag set to false.
23 bool IsFunction
= false;
26 /// Holds accumulated global statistics about local variables.
28 /// Total number of PC range bytes covered by DW_AT_locations.
29 unsigned ScopeBytesCovered
= 0;
30 /// Total number of PC range bytes in each variable's enclosing scope,
31 /// starting from the first definition of the variable.
32 unsigned ScopeBytesFromFirstDefinition
= 0;
35 /// Extract the low pc from a Die.
36 static uint64_t getLowPC(DWARFDie Die
) {
37 if (Die
.getAddressRanges().size())
38 return Die
.getAddressRanges()[0].LowPC
;
39 return dwarf::toAddress(Die
.find(dwarf::DW_AT_low_pc
), 0);
42 /// Collect debug info quality metrics for one DIE.
43 static void collectStatsForDie(DWARFDie Die
, std::string Prefix
,
44 uint64_t ScopeLowPC
, uint64_t BytesInScope
,
45 StringMap
<PerFunctionStats
> &FnStatMap
,
46 GlobalStats
&GlobalStats
) {
48 uint64_t BytesCovered
= 0;
49 uint64_t OffsetToFirstDefinition
= 0;
50 if (Die
.find(dwarf::DW_AT_const_value
)) {
51 // This catches constant members *and* variables.
53 BytesCovered
= BytesInScope
;
54 } else if (Die
.getTag() == dwarf::DW_TAG_variable
||
55 Die
.getTag() == dwarf::DW_TAG_formal_parameter
) {
56 // Handle variables and function arguments.
57 auto FormValue
= Die
.find(dwarf::DW_AT_location
);
58 HasLoc
= FormValue
.hasValue();
61 if (auto DebugLocOffset
= FormValue
->getAsSectionOffset()) {
62 auto *DebugLoc
= Die
.getDwarfUnit()->getContext().getDebugLoc();
63 if (auto List
= DebugLoc
->getLocationListAtOffset(*DebugLocOffset
)) {
64 for (auto Entry
: List
->Entries
)
65 BytesCovered
+= Entry
.End
- Entry
.Begin
;
66 if (List
->Entries
.size()) {
67 uint64_t FirstDef
= List
->Entries
[0].Begin
;
68 uint64_t UnitOfs
= getLowPC(Die
.getDwarfUnit()->getUnitDIE());
69 // Ranges sometimes start before the lexical scope.
70 if (UnitOfs
+ FirstDef
>= ScopeLowPC
)
71 OffsetToFirstDefinition
= UnitOfs
+ FirstDef
- ScopeLowPC
;
72 // Or even after it. Count that as a failure.
73 if (OffsetToFirstDefinition
> BytesInScope
)
74 OffsetToFirstDefinition
= 0;
79 // Assume the entire range is covered by a single location.
80 BytesCovered
= BytesInScope
;
84 // Not a variable or constant member.
88 // Collect PC range coverage data.
89 auto &FnStats
= FnStatMap
[Prefix
];
91 Die
.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin
))
93 // This is a unique ID for the variable inside the current object file.
94 unsigned CanonicalDieOffset
= Die
.getOffset();
95 FnStats
.VarsInFunction
.insert(CanonicalDieOffset
);
97 FnStats
.TotalVarWithLoc
+= (unsigned)HasLoc
;
98 // Adjust for the fact the variables often start their lifetime in the
99 // middle of the scope.
100 BytesInScope
-= OffsetToFirstDefinition
;
101 // Turns out we have a lot of ranges that extend past the lexical scope.
102 GlobalStats
.ScopeBytesCovered
+= std::min(BytesInScope
, BytesCovered
);
103 GlobalStats
.ScopeBytesFromFirstDefinition
+= BytesInScope
;
104 assert(GlobalStats
.ScopeBytesCovered
<=
105 GlobalStats
.ScopeBytesFromFirstDefinition
);
107 FnStats
.ConstantMembers
++;
111 /// Recursively collect debug info quality metrics.
112 static void collectStatsRecursive(DWARFDie Die
, std::string Prefix
,
113 uint64_t ScopeLowPC
, uint64_t BytesInScope
,
114 StringMap
<PerFunctionStats
> &FnStatMap
,
115 GlobalStats
&GlobalStats
) {
116 // Handle any kind of lexical scope.
117 if (Die
.getTag() == dwarf::DW_TAG_subprogram
||
118 Die
.getTag() == dwarf::DW_TAG_inlined_subroutine
||
119 Die
.getTag() == dwarf::DW_TAG_lexical_block
) {
120 // Ignore forward declarations.
121 if (Die
.find(dwarf::DW_AT_declaration
))
124 // Count the function.
125 if (Die
.getTag() != dwarf::DW_TAG_lexical_block
) {
126 StringRef Name
= Die
.getName(DINameKind::LinkageName
);
128 Name
= Die
.getName(DINameKind::ShortName
);
130 // Skip over abstract origins.
131 if (Die
.find(dwarf::DW_AT_inline
))
133 // We've seen an (inlined) instance of this function.
134 auto &FnStats
= FnStatMap
[Name
];
135 FnStats
.NumFnInlined
++;
136 FnStats
.IsFunction
= true;
140 auto Ranges
= Die
.getAddressRanges();
141 uint64_t BytesInThisScope
= 0;
142 for (auto Range
: Ranges
)
143 BytesInThisScope
+= Range
.HighPC
- Range
.LowPC
;
144 ScopeLowPC
= getLowPC(Die
);
146 if (BytesInThisScope
)
147 BytesInScope
= BytesInThisScope
;
149 // Not a scope, visit the Die itself. It could be a variable.
150 collectStatsForDie(Die
, Prefix
, ScopeLowPC
, BytesInScope
, FnStatMap
,
154 // Traverse children.
155 DWARFDie Child
= Die
.getFirstChild();
157 collectStatsRecursive(Child
, Prefix
, ScopeLowPC
, BytesInScope
, FnStatMap
,
159 Child
= Child
.getSibling();
163 /// Print machine-readable output.
164 /// The machine-readable format is single-line JSON output.
166 static void printDatum(raw_ostream
&OS
, const char *Key
, StringRef Value
) {
167 OS
<< ",\"" << Key
<< "\":\"" << Value
<< '"';
168 DEBUG(llvm::dbgs() << Key
<< ": " << Value
<< '\n');
170 static void printDatum(raw_ostream
&OS
, const char *Key
, uint64_t Value
) {
171 OS
<< ",\"" << Key
<< "\":" << Value
;
172 DEBUG(llvm::dbgs() << Key
<< ": " << Value
<< '\n');
176 /// Collect debug info quality metrics for an entire DIContext.
178 /// Do the impossible and reduce the quality of the debug info down to a few
179 /// numbers. The idea is to condense the data into numbers that can be tracked
180 /// over time to identify trends in newer compiler versions and gauge the effect
181 /// of particular optimizations. The raw numbers themselves are not particularly
182 /// useful, only the delta between compiling the same program with different
184 bool collectStatsForObjectFile(ObjectFile
&Obj
, DWARFContext
&DICtx
,
185 Twine Filename
, raw_ostream
&OS
) {
186 StringRef FormatName
= Obj
.getFileFormatName();
187 GlobalStats GlobalStats
;
188 StringMap
<PerFunctionStats
> Statistics
;
189 for (const auto &CU
: static_cast<DWARFContext
*>(&DICtx
)->compile_units())
190 if (DWARFDie CUDie
= CU
->getUnitDIE(false))
191 collectStatsRecursive(CUDie
, "/", 0, 0, Statistics
, GlobalStats
);
193 /// The version number should be increased every time the algorithm is changed
194 /// (including bug fixes). New metrics may be added without increasing the
196 unsigned Version
= 1;
197 unsigned VarTotal
= 0;
198 unsigned VarUnique
= 0;
199 unsigned VarWithLoc
= 0;
200 unsigned NumFunctions
= 0;
201 unsigned NumInlinedFunctions
= 0;
202 for (auto &Entry
: Statistics
) {
203 PerFunctionStats
&Stats
= Entry
.getValue();
204 unsigned TotalVars
= Stats
.VarsInFunction
.size() * Stats
.NumFnInlined
;
205 unsigned Constants
= Stats
.ConstantMembers
;
206 VarWithLoc
+= Stats
.TotalVarWithLoc
+ Constants
;
207 VarTotal
+= TotalVars
+ Constants
;
208 VarUnique
+= Stats
.VarsInFunction
.size();
209 DEBUG(for (auto V
: Stats
.VarsInFunction
)
210 llvm::dbgs() << Entry
.getKey() << ": " << V
<< "\n");
211 NumFunctions
+= Stats
.IsFunction
;
212 NumInlinedFunctions
+= Stats
.IsFunction
* Stats
.NumFnInlined
;
216 OS
.SetBufferSize(1024);
217 OS
<< "{\"version\":\"" << Version
<< '"';
218 DEBUG(llvm::dbgs() << "Variable location quality metrics\n";
219 llvm::dbgs() << "---------------------------------\n");
220 printDatum(OS
, "file", Filename
.str());
221 printDatum(OS
, "format", FormatName
);
222 printDatum(OS
, "source functions", NumFunctions
);
223 printDatum(OS
, "inlined functions", NumInlinedFunctions
);
224 printDatum(OS
, "unique source variables", VarUnique
);
225 printDatum(OS
, "source variables", VarTotal
);
226 printDatum(OS
, "variables with location", VarWithLoc
);
227 printDatum(OS
, "scope bytes total",
228 GlobalStats
.ScopeBytesFromFirstDefinition
);
229 printDatum(OS
, "scope bytes covered", GlobalStats
.ScopeBytesCovered
);
232 llvm::dbgs() << "Total Availability: "
233 << (int)std::round((VarWithLoc
* 100.0) / VarTotal
) << "%\n";
234 llvm::dbgs() << "PC Ranges covered: "
235 << (int)std::round((GlobalStats
.ScopeBytesCovered
* 100.0) /
236 GlobalStats
.ScopeBytesFromFirstDefinition
)