1 | //===-- Statistics.cpp - Debug Info quality metrics -----------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm-dwarfdump.h" |
10 | #include "llvm/ADT/DenseMap.h" |
11 | #include "llvm/ADT/DenseSet.h" |
12 | #include "llvm/ADT/StringSet.h" |
13 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
14 | #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" |
15 | #include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" |
16 | #include "llvm/Object/ObjectFile.h" |
17 | #include "llvm/Support/JSON.h" |
18 | |
19 | #define DEBUG_TYPE "dwarfdump" |
20 | using namespace llvm; |
21 | using namespace llvm::dwarfdump; |
22 | using namespace llvm::object; |
23 | |
24 | namespace { |
25 | /// This represents the number of categories of debug location coverage being |
26 | /// calculated. The first category is the number of variables with 0% location |
27 | /// coverage, but the last category is the number of variables with 100% |
28 | /// location coverage. |
29 | constexpr int NumOfCoverageCategories = 12; |
30 | |
31 | /// This is used for zero location coverage bucket. |
32 | constexpr unsigned ZeroCoverageBucket = 0; |
33 | |
34 | /// The UINT64_MAX is used as an indication of the overflow. |
35 | constexpr uint64_t OverflowValue = std::numeric_limits<uint64_t>::max(); |
36 | |
37 | /// This represents variables DIE offsets. |
38 | using AbstractOriginVarsTy = llvm::SmallVector<uint64_t>; |
39 | /// This maps function DIE offset to its variables. |
40 | using AbstractOriginVarsTyMap = llvm::DenseMap<uint64_t, AbstractOriginVarsTy>; |
41 | /// This represents function DIE offsets containing an abstract_origin. |
42 | using FunctionsWithAbstractOriginTy = llvm::SmallVector<uint64_t>; |
43 | |
44 | /// This represents a data type for the stats and it helps us to |
45 | /// detect an overflow. |
46 | /// NOTE: This can be implemented as a template if there is an another type |
47 | /// needing this. |
48 | struct SaturatingUINT64 { |
49 | /// Number that represents the stats. |
50 | uint64_t Value; |
51 | |
52 | SaturatingUINT64(uint64_t Value_) : Value(Value_) {} |
53 | |
54 | void operator++(int) { return *this += 1; } |
55 | void operator+=(uint64_t Value_) { |
56 | if (Value != OverflowValue) { |
57 | if (Value < OverflowValue - Value_) |
58 | Value += Value_; |
59 | else |
60 | Value = OverflowValue; |
61 | } |
62 | } |
63 | }; |
64 | |
65 | /// Utility struct to store the full location of a DIE - its CU and offset. |
66 | struct DIELocation { |
67 | DWARFUnit *DwUnit; |
68 | uint64_t DIEOffset; |
69 | DIELocation(DWARFUnit *_DwUnit, uint64_t _DIEOffset) |
70 | : DwUnit(_DwUnit), DIEOffset(_DIEOffset) {} |
71 | }; |
72 | /// This represents DWARF locations of CrossCU referencing DIEs. |
73 | using CrossCUReferencingDIELocationTy = llvm::SmallVector<DIELocation>; |
74 | |
75 | /// This maps function DIE offset to its DWARF CU. |
76 | using FunctionDIECUTyMap = llvm::DenseMap<uint64_t, DWARFUnit *>; |
77 | |
78 | /// Holds statistics for one function (or other entity that has a PC range and |
79 | /// contains variables, such as a compile unit). |
80 | struct PerFunctionStats { |
81 | /// Number of inlined instances of this function. |
82 | uint64_t NumFnInlined = 0; |
83 | /// Number of out-of-line instances of this function. |
84 | uint64_t NumFnOutOfLine = 0; |
85 | /// Number of inlined instances that have abstract origins. |
86 | uint64_t NumAbstractOrigins = 0; |
87 | /// Number of variables and parameters with location across all inlined |
88 | /// instances. |
89 | uint64_t TotalVarWithLoc = 0; |
90 | /// Number of constants with location across all inlined instances. |
91 | uint64_t ConstantMembers = 0; |
92 | /// Number of arificial variables, parameters or members across all instances. |
93 | uint64_t NumArtificial = 0; |
94 | /// List of all Variables and parameters in this function. |
95 | StringSet<> VarsInFunction; |
96 | /// Compile units also cover a PC range, but have this flag set to false. |
97 | bool IsFunction = false; |
98 | /// Function has source location information. |
99 | bool HasSourceLocation = false; |
100 | /// Number of function parameters. |
101 | uint64_t NumParams = 0; |
102 | /// Number of function parameters with source location. |
103 | uint64_t NumParamSourceLocations = 0; |
104 | /// Number of function parameters with type. |
105 | uint64_t NumParamTypes = 0; |
106 | /// Number of function parameters with a DW_AT_location. |
107 | uint64_t NumParamLocations = 0; |
108 | /// Number of local variables. |
109 | uint64_t NumLocalVars = 0; |
110 | /// Number of local variables with source location. |
111 | uint64_t NumLocalVarSourceLocations = 0; |
112 | /// Number of local variables with type. |
113 | uint64_t NumLocalVarTypes = 0; |
114 | /// Number of local variables with DW_AT_location. |
115 | uint64_t NumLocalVarLocations = 0; |
116 | }; |
117 | |
118 | /// Holds accumulated global statistics about DIEs. |
119 | struct GlobalStats { |
120 | /// Total number of PC range bytes covered by DW_AT_locations. |
121 | SaturatingUINT64 TotalBytesCovered = 0; |
122 | /// Total number of parent DIE PC range bytes covered by DW_AT_Locations. |
123 | SaturatingUINT64 ScopeBytesCovered = 0; |
124 | /// Total number of PC range bytes in each variable's enclosing scope. |
125 | SaturatingUINT64 ScopeBytes = 0; |
126 | /// Total number of PC range bytes covered by DW_AT_locations with |
127 | /// the debug entry values (DW_OP_entry_value). |
128 | SaturatingUINT64 ScopeEntryValueBytesCovered = 0; |
129 | /// Total number of PC range bytes covered by DW_AT_locations of |
130 | /// formal parameters. |
131 | SaturatingUINT64 ParamScopeBytesCovered = 0; |
132 | /// Total number of PC range bytes in each parameter's enclosing scope. |
133 | SaturatingUINT64 ParamScopeBytes = 0; |
134 | /// Total number of PC range bytes covered by DW_AT_locations with |
135 | /// the debug entry values (DW_OP_entry_value) (only for parameters). |
136 | SaturatingUINT64 ParamScopeEntryValueBytesCovered = 0; |
137 | /// Total number of PC range bytes covered by DW_AT_locations (only for local |
138 | /// variables). |
139 | SaturatingUINT64 LocalVarScopeBytesCovered = 0; |
140 | /// Total number of PC range bytes in each local variable's enclosing scope. |
141 | SaturatingUINT64 LocalVarScopeBytes = 0; |
142 | /// Total number of PC range bytes covered by DW_AT_locations with |
143 | /// the debug entry values (DW_OP_entry_value) (only for local variables). |
144 | SaturatingUINT64 LocalVarScopeEntryValueBytesCovered = 0; |
145 | /// Total number of call site entries (DW_AT_call_file & DW_AT_call_line). |
146 | SaturatingUINT64 CallSiteEntries = 0; |
147 | /// Total number of call site DIEs (DW_TAG_call_site). |
148 | SaturatingUINT64 CallSiteDIEs = 0; |
149 | /// Total number of call site parameter DIEs (DW_TAG_call_site_parameter). |
150 | SaturatingUINT64 CallSiteParamDIEs = 0; |
151 | /// Total byte size of concrete functions. This byte size includes |
152 | /// inline functions contained in the concrete functions. |
153 | SaturatingUINT64 FunctionSize = 0; |
154 | /// Total byte size of inlined functions. This is the total number of bytes |
155 | /// for the top inline functions within concrete functions. This can help |
156 | /// tune the inline settings when compiling to match user expectations. |
157 | SaturatingUINT64 InlineFunctionSize = 0; |
158 | }; |
159 | |
160 | /// Holds accumulated debug location statistics about local variables and |
161 | /// formal parameters. |
162 | struct LocationStats { |
163 | /// Map the scope coverage decile to the number of variables in the decile. |
164 | /// The first element of the array (at the index zero) represents the number |
165 | /// of variables with the no debug location at all, but the last element |
166 | /// in the vector represents the number of fully covered variables within |
167 | /// its scope. |
168 | std::vector<SaturatingUINT64> VarParamLocStats{ |
169 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
170 | /// Map non debug entry values coverage. |
171 | std::vector<SaturatingUINT64> VarParamNonEntryValLocStats{ |
172 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
173 | /// The debug location statistics for formal parameters. |
174 | std::vector<SaturatingUINT64> ParamLocStats{ |
175 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
176 | /// Map non debug entry values coverage for formal parameters. |
177 | std::vector<SaturatingUINT64> ParamNonEntryValLocStats{ |
178 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
179 | /// The debug location statistics for local variables. |
180 | std::vector<SaturatingUINT64> LocalVarLocStats{ |
181 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
182 | /// Map non debug entry values coverage for local variables. |
183 | std::vector<SaturatingUINT64> LocalVarNonEntryValLocStats{ |
184 | std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
185 | /// Total number of local variables and function parameters processed. |
186 | SaturatingUINT64 NumVarParam = 0; |
187 | /// Total number of formal parameters processed. |
188 | SaturatingUINT64 NumParam = 0; |
189 | /// Total number of local variables processed. |
190 | SaturatingUINT64 NumVar = 0; |
191 | }; |
192 | |
193 | /// Holds accumulated debug line statistics across all CUs. |
194 | struct LineStats { |
195 | SaturatingUINT64 NumBytes = 0; |
196 | SaturatingUINT64 NumLineZeroBytes = 0; |
197 | SaturatingUINT64 NumEntries = 0; |
198 | SaturatingUINT64 NumIsStmtEntries = 0; |
199 | SaturatingUINT64 NumUniqueEntries = 0; |
200 | SaturatingUINT64 NumUniqueNonZeroEntries = 0; |
201 | }; |
202 | } // namespace |
203 | |
204 | /// Collect debug location statistics for one DIE. |
205 | static void collectLocStats(uint64_t ScopeBytesCovered, uint64_t BytesInScope, |
206 | std::vector<SaturatingUINT64> &VarParamLocStats, |
207 | std::vector<SaturatingUINT64> &ParamLocStats, |
208 | std::vector<SaturatingUINT64> &LocalVarLocStats, |
209 | bool IsParam, bool IsLocalVar) { |
210 | auto getCoverageBucket = [ScopeBytesCovered, BytesInScope]() -> unsigned { |
211 | // No debug location at all for the variable. |
212 | if (ScopeBytesCovered == 0) |
213 | return 0; |
214 | // Fully covered variable within its scope. |
215 | if (ScopeBytesCovered >= BytesInScope) |
216 | return NumOfCoverageCategories - 1; |
217 | // Get covered range (e.g. 20%-29%). |
218 | unsigned LocBucket = 100 * (double)ScopeBytesCovered / BytesInScope; |
219 | LocBucket /= 10; |
220 | return LocBucket + 1; |
221 | }; |
222 | |
223 | unsigned CoverageBucket = getCoverageBucket(); |
224 | |
225 | VarParamLocStats[CoverageBucket].Value++; |
226 | if (IsParam) |
227 | ParamLocStats[CoverageBucket].Value++; |
228 | else if (IsLocalVar) |
229 | LocalVarLocStats[CoverageBucket].Value++; |
230 | } |
231 | |
232 | /// Construct an identifier for a given DIE from its Prefix, Name, DeclFileName |
233 | /// and DeclLine. The identifier aims to be unique for any unique entities, |
234 | /// but keeping the same among different instances of the same entity. |
235 | static std::string constructDieID(DWARFDie Die, |
236 | StringRef Prefix = StringRef()) { |
237 | std::string IDStr; |
238 | llvm::raw_string_ostream ID(IDStr); |
239 | ID << Prefix |
240 | << Die.getName(Kind: DINameKind::LinkageName); |
241 | |
242 | // Prefix + Name is enough for local variables and parameters. |
243 | if (!Prefix.empty() && Prefix != "g" ) |
244 | return IDStr; |
245 | |
246 | auto DeclFile = Die.findRecursively(Attrs: dwarf::DW_AT_decl_file); |
247 | std::string File; |
248 | if (DeclFile) { |
249 | DWARFUnit *U = Die.getDwarfUnit(); |
250 | if (const auto *LT = U->getContext().getLineTableForUnit(U)) |
251 | if (LT->getFileNameByIndex( |
252 | FileIndex: dwarf::toUnsigned(V: DeclFile, Default: 0), CompDir: U->getCompilationDir(), |
253 | Kind: DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, Result&: File)) |
254 | File = std::string(sys::path::filename(path: File)); |
255 | } |
256 | ID << ":" << (File.empty() ? "/" : File); |
257 | ID << ":" |
258 | << dwarf::toUnsigned(V: Die.findRecursively(Attrs: dwarf::DW_AT_decl_line), Default: 0); |
259 | return IDStr; |
260 | } |
261 | |
262 | /// Return the number of bytes in the overlap of ranges A and B. |
263 | static uint64_t calculateOverlap(DWARFAddressRange A, DWARFAddressRange B) { |
264 | uint64_t Lower = std::max(a: A.LowPC, b: B.LowPC); |
265 | uint64_t Upper = std::min(a: A.HighPC, b: B.HighPC); |
266 | if (Lower >= Upper) |
267 | return 0; |
268 | return Upper - Lower; |
269 | } |
270 | |
271 | /// Collect debug info quality metrics for one DIE. |
272 | static void collectStatsForDie(DWARFDie Die, const std::string &FnPrefix, |
273 | const std::string &VarPrefix, |
274 | uint64_t BytesInScope, uint32_t InlineDepth, |
275 | StringMap<PerFunctionStats> &FnStatMap, |
276 | GlobalStats &GlobalStats, |
277 | LocationStats &LocStats, |
278 | AbstractOriginVarsTy *AbstractOriginVariables) { |
279 | const dwarf::Tag Tag = Die.getTag(); |
280 | // Skip CU node. |
281 | if (Tag == dwarf::DW_TAG_compile_unit) |
282 | return; |
283 | |
284 | bool HasLoc = false; |
285 | bool HasSrcLoc = false; |
286 | bool HasType = false; |
287 | uint64_t TotalBytesCovered = 0; |
288 | uint64_t ScopeBytesCovered = 0; |
289 | uint64_t BytesEntryValuesCovered = 0; |
290 | auto &FnStats = FnStatMap[FnPrefix]; |
291 | bool IsParam = Tag == dwarf::DW_TAG_formal_parameter; |
292 | bool IsLocalVar = Tag == dwarf::DW_TAG_variable; |
293 | bool IsConstantMember = Tag == dwarf::DW_TAG_member && |
294 | Die.find(Attr: dwarf::DW_AT_const_value); |
295 | |
296 | // For zero covered inlined variables the locstats will be |
297 | // calculated later. |
298 | bool DeferLocStats = false; |
299 | |
300 | if (Tag == dwarf::DW_TAG_call_site || Tag == dwarf::DW_TAG_GNU_call_site) { |
301 | GlobalStats.CallSiteDIEs++; |
302 | return; |
303 | } |
304 | |
305 | if (Tag == dwarf::DW_TAG_call_site_parameter || |
306 | Tag == dwarf::DW_TAG_GNU_call_site_parameter) { |
307 | GlobalStats.CallSiteParamDIEs++; |
308 | return; |
309 | } |
310 | |
311 | if (!IsParam && !IsLocalVar && !IsConstantMember) { |
312 | // Not a variable or constant member. |
313 | return; |
314 | } |
315 | |
316 | // Ignore declarations of global variables. |
317 | if (IsLocalVar && Die.find(Attr: dwarf::DW_AT_declaration)) |
318 | return; |
319 | |
320 | if (Die.findRecursively(Attrs: dwarf::DW_AT_decl_file) && |
321 | Die.findRecursively(Attrs: dwarf::DW_AT_decl_line)) |
322 | HasSrcLoc = true; |
323 | |
324 | if (Die.findRecursively(Attrs: dwarf::DW_AT_type)) |
325 | HasType = true; |
326 | |
327 | if (Die.find(Attr: dwarf::DW_AT_abstract_origin)) { |
328 | if (Die.find(Attr: dwarf::DW_AT_location) || Die.find(Attr: dwarf::DW_AT_const_value)) { |
329 | if (AbstractOriginVariables) { |
330 | auto Offset = Die.find(Attr: dwarf::DW_AT_abstract_origin); |
331 | // Do not track this variable any more, since it has location |
332 | // coverage. |
333 | llvm::erase(C&: *AbstractOriginVariables, V: (*Offset).getRawUValue()); |
334 | } |
335 | } else { |
336 | // The locstats will be handled at the end of |
337 | // the collectStatsRecursive(). |
338 | DeferLocStats = true; |
339 | } |
340 | } |
341 | |
342 | auto IsEntryValue = [&](ArrayRef<uint8_t> D) -> bool { |
343 | DWARFUnit *U = Die.getDwarfUnit(); |
344 | DataExtractor Data(toStringRef(Input: D), |
345 | Die.getDwarfUnit()->getContext().isLittleEndian(), 0); |
346 | DWARFExpression Expression(Data, U->getAddressByteSize(), |
347 | U->getFormParams().Format); |
348 | // Consider the expression containing the DW_OP_entry_value as |
349 | // an entry value. |
350 | return llvm::any_of(Range&: Expression, P: [](const DWARFExpression::Operation &Op) { |
351 | return Op.getCode() == dwarf::DW_OP_entry_value || |
352 | Op.getCode() == dwarf::DW_OP_GNU_entry_value; |
353 | }); |
354 | }; |
355 | |
356 | if (Die.find(Attr: dwarf::DW_AT_const_value)) { |
357 | // This catches constant members *and* variables. |
358 | HasLoc = true; |
359 | ScopeBytesCovered = BytesInScope; |
360 | TotalBytesCovered = BytesInScope; |
361 | } else { |
362 | // Handle variables and function arguments. |
363 | Expected<std::vector<DWARFLocationExpression>> Loc = |
364 | Die.getLocations(Attr: dwarf::DW_AT_location); |
365 | if (!Loc) { |
366 | consumeError(Err: Loc.takeError()); |
367 | } else { |
368 | HasLoc = true; |
369 | // Get PC coverage. |
370 | auto Default = find_if( |
371 | Range&: *Loc, P: [](const DWARFLocationExpression &L) { return !L.Range; }); |
372 | if (Default != Loc->end()) { |
373 | // Assume the entire range is covered by a single location. |
374 | ScopeBytesCovered = BytesInScope; |
375 | TotalBytesCovered = BytesInScope; |
376 | } else { |
377 | // Caller checks this Expected result already, it cannot fail. |
378 | auto ScopeRanges = cantFail(ValOrErr: Die.getParent().getAddressRanges()); |
379 | for (auto Entry : *Loc) { |
380 | TotalBytesCovered += Entry.Range->HighPC - Entry.Range->LowPC; |
381 | uint64_t ScopeBytesCoveredByEntry = 0; |
382 | // Calculate how many bytes of the parent scope this entry covers. |
383 | // FIXME: In section 2.6.2 of the DWARFv5 spec it says that "The |
384 | // address ranges defined by the bounded location descriptions of a |
385 | // location list may overlap". So in theory a variable can have |
386 | // multiple simultaneous locations, which would make this calculation |
387 | // misleading because we will count the overlapped areas |
388 | // twice. However, clang does not currently emit DWARF like this. |
389 | for (DWARFAddressRange R : ScopeRanges) { |
390 | ScopeBytesCoveredByEntry += calculateOverlap(A: *Entry.Range, B: R); |
391 | } |
392 | ScopeBytesCovered += ScopeBytesCoveredByEntry; |
393 | if (IsEntryValue(Entry.Expr)) |
394 | BytesEntryValuesCovered += ScopeBytesCoveredByEntry; |
395 | } |
396 | } |
397 | } |
398 | } |
399 | |
400 | // Calculate the debug location statistics. |
401 | if (BytesInScope && !DeferLocStats) { |
402 | LocStats.NumVarParam.Value++; |
403 | if (IsParam) |
404 | LocStats.NumParam.Value++; |
405 | else if (IsLocalVar) |
406 | LocStats.NumVar.Value++; |
407 | |
408 | collectLocStats(ScopeBytesCovered, BytesInScope, VarParamLocStats&: LocStats.VarParamLocStats, |
409 | ParamLocStats&: LocStats.ParamLocStats, LocalVarLocStats&: LocStats.LocalVarLocStats, IsParam, |
410 | IsLocalVar); |
411 | // Non debug entry values coverage statistics. |
412 | collectLocStats(ScopeBytesCovered: ScopeBytesCovered - BytesEntryValuesCovered, BytesInScope, |
413 | VarParamLocStats&: LocStats.VarParamNonEntryValLocStats, |
414 | ParamLocStats&: LocStats.ParamNonEntryValLocStats, |
415 | LocalVarLocStats&: LocStats.LocalVarNonEntryValLocStats, IsParam, IsLocalVar); |
416 | } |
417 | |
418 | // Collect PC range coverage data. |
419 | if (DWARFDie D = |
420 | Die.getAttributeValueAsReferencedDie(Attr: dwarf::DW_AT_abstract_origin)) |
421 | Die = D; |
422 | |
423 | std::string VarID = constructDieID(Die, Prefix: VarPrefix); |
424 | FnStats.VarsInFunction.insert(key: VarID); |
425 | |
426 | GlobalStats.TotalBytesCovered += TotalBytesCovered; |
427 | if (BytesInScope) { |
428 | GlobalStats.ScopeBytesCovered += ScopeBytesCovered; |
429 | GlobalStats.ScopeBytes += BytesInScope; |
430 | GlobalStats.ScopeEntryValueBytesCovered += BytesEntryValuesCovered; |
431 | if (IsParam) { |
432 | GlobalStats.ParamScopeBytesCovered += ScopeBytesCovered; |
433 | GlobalStats.ParamScopeBytes += BytesInScope; |
434 | GlobalStats.ParamScopeEntryValueBytesCovered += BytesEntryValuesCovered; |
435 | } else if (IsLocalVar) { |
436 | GlobalStats.LocalVarScopeBytesCovered += ScopeBytesCovered; |
437 | GlobalStats.LocalVarScopeBytes += BytesInScope; |
438 | GlobalStats.LocalVarScopeEntryValueBytesCovered += |
439 | BytesEntryValuesCovered; |
440 | } |
441 | assert(GlobalStats.ScopeBytesCovered.Value <= GlobalStats.ScopeBytes.Value); |
442 | } |
443 | |
444 | if (IsConstantMember) { |
445 | FnStats.ConstantMembers++; |
446 | return; |
447 | } |
448 | |
449 | FnStats.TotalVarWithLoc += (unsigned)HasLoc; |
450 | |
451 | if (Die.find(Attr: dwarf::DW_AT_artificial)) { |
452 | FnStats.NumArtificial++; |
453 | return; |
454 | } |
455 | |
456 | if (IsParam) { |
457 | FnStats.NumParams++; |
458 | if (HasType) |
459 | FnStats.NumParamTypes++; |
460 | if (HasSrcLoc) |
461 | FnStats.NumParamSourceLocations++; |
462 | if (HasLoc) |
463 | FnStats.NumParamLocations++; |
464 | } else if (IsLocalVar) { |
465 | FnStats.NumLocalVars++; |
466 | if (HasType) |
467 | FnStats.NumLocalVarTypes++; |
468 | if (HasSrcLoc) |
469 | FnStats.NumLocalVarSourceLocations++; |
470 | if (HasLoc) |
471 | FnStats.NumLocalVarLocations++; |
472 | } |
473 | } |
474 | |
475 | /// Recursively collect variables from subprogram with DW_AT_inline attribute. |
476 | static void collectAbstractOriginFnInfo( |
477 | DWARFDie Die, uint64_t SPOffset, |
478 | AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
479 | AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo) { |
480 | DWARFDie Child = Die.getFirstChild(); |
481 | while (Child) { |
482 | const dwarf::Tag ChildTag = Child.getTag(); |
483 | if (ChildTag == dwarf::DW_TAG_formal_parameter || |
484 | ChildTag == dwarf::DW_TAG_variable) { |
485 | GlobalAbstractOriginFnInfo[SPOffset].push_back(Elt: Child.getOffset()); |
486 | LocalAbstractOriginFnInfo[SPOffset].push_back(Elt: Child.getOffset()); |
487 | } else if (ChildTag == dwarf::DW_TAG_lexical_block) |
488 | collectAbstractOriginFnInfo(Die: Child, SPOffset, GlobalAbstractOriginFnInfo, |
489 | LocalAbstractOriginFnInfo); |
490 | Child = Child.getSibling(); |
491 | } |
492 | } |
493 | |
494 | /// Recursively collect debug info quality metrics. |
495 | static void collectStatsRecursive( |
496 | DWARFDie Die, std::string FnPrefix, std::string VarPrefix, |
497 | uint64_t BytesInScope, uint32_t InlineDepth, |
498 | StringMap<PerFunctionStats> &FnStatMap, GlobalStats &GlobalStats, |
499 | LocationStats &LocStats, FunctionDIECUTyMap &AbstractOriginFnCUs, |
500 | AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
501 | AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo, |
502 | FunctionsWithAbstractOriginTy &FnsWithAbstractOriginToBeProcessed, |
503 | AbstractOriginVarsTy *AbstractOriginVarsPtr = nullptr) { |
504 | // Skip NULL nodes. |
505 | if (Die.isNULL()) |
506 | return; |
507 | |
508 | const dwarf::Tag Tag = Die.getTag(); |
509 | // Skip function types. |
510 | if (Tag == dwarf::DW_TAG_subroutine_type) |
511 | return; |
512 | |
513 | // Handle any kind of lexical scope. |
514 | const bool HasAbstractOrigin = |
515 | Die.find(Attr: dwarf::DW_AT_abstract_origin) != std::nullopt; |
516 | const bool IsFunction = Tag == dwarf::DW_TAG_subprogram; |
517 | const bool IsBlock = Tag == dwarf::DW_TAG_lexical_block; |
518 | const bool IsInlinedFunction = Tag == dwarf::DW_TAG_inlined_subroutine; |
519 | // We want to know how many variables (with abstract_origin) don't have |
520 | // location info. |
521 | const bool IsCandidateForZeroLocCovTracking = |
522 | (IsInlinedFunction || (IsFunction && HasAbstractOrigin)); |
523 | |
524 | AbstractOriginVarsTy AbstractOriginVars; |
525 | |
526 | // Get the vars of the inlined fn, so the locstats |
527 | // reports the missing vars (with coverage 0%). |
528 | if (IsCandidateForZeroLocCovTracking) { |
529 | auto OffsetFn = Die.find(Attr: dwarf::DW_AT_abstract_origin); |
530 | if (OffsetFn) { |
531 | uint64_t OffsetOfInlineFnCopy = (*OffsetFn).getRawUValue(); |
532 | if (auto It = LocalAbstractOriginFnInfo.find(Val: OffsetOfInlineFnCopy); |
533 | It != LocalAbstractOriginFnInfo.end()) { |
534 | AbstractOriginVars = It->second; |
535 | AbstractOriginVarsPtr = &AbstractOriginVars; |
536 | } else { |
537 | // This means that the DW_AT_inline fn copy is out of order |
538 | // or that the abstract_origin references another CU, |
539 | // so this abstract origin instance will be processed later. |
540 | FnsWithAbstractOriginToBeProcessed.push_back(Elt: Die.getOffset()); |
541 | AbstractOriginVarsPtr = nullptr; |
542 | } |
543 | } |
544 | } |
545 | |
546 | if (IsFunction || IsInlinedFunction || IsBlock) { |
547 | // Reset VarPrefix when entering a new function. |
548 | if (IsFunction || IsInlinedFunction) |
549 | VarPrefix = "v" ; |
550 | |
551 | // Ignore forward declarations. |
552 | if (Die.find(Attr: dwarf::DW_AT_declaration)) |
553 | return; |
554 | |
555 | // Check for call sites. |
556 | if (Die.find(Attr: dwarf::DW_AT_call_file) && Die.find(Attr: dwarf::DW_AT_call_line)) |
557 | GlobalStats.CallSiteEntries++; |
558 | |
559 | // PC Ranges. |
560 | auto RangesOrError = Die.getAddressRanges(); |
561 | if (!RangesOrError) { |
562 | llvm::consumeError(Err: RangesOrError.takeError()); |
563 | return; |
564 | } |
565 | |
566 | auto Ranges = RangesOrError.get(); |
567 | uint64_t BytesInThisScope = 0; |
568 | for (auto Range : Ranges) |
569 | BytesInThisScope += Range.HighPC - Range.LowPC; |
570 | |
571 | // Count the function. |
572 | if (!IsBlock) { |
573 | // Skip over abstract origins, but collect variables |
574 | // from it so it can be used for location statistics |
575 | // for inlined instancies. |
576 | if (Die.find(Attr: dwarf::DW_AT_inline)) { |
577 | uint64_t SPOffset = Die.getOffset(); |
578 | AbstractOriginFnCUs[SPOffset] = Die.getDwarfUnit(); |
579 | collectAbstractOriginFnInfo(Die, SPOffset, GlobalAbstractOriginFnInfo, |
580 | LocalAbstractOriginFnInfo); |
581 | return; |
582 | } |
583 | |
584 | std::string FnID = constructDieID(Die); |
585 | // We've seen an instance of this function. |
586 | auto &FnStats = FnStatMap[FnID]; |
587 | FnStats.IsFunction = true; |
588 | if (IsInlinedFunction) { |
589 | FnStats.NumFnInlined++; |
590 | if (Die.findRecursively(Attrs: dwarf::DW_AT_abstract_origin)) |
591 | FnStats.NumAbstractOrigins++; |
592 | } else { |
593 | FnStats.NumFnOutOfLine++; |
594 | } |
595 | if (Die.findRecursively(Attrs: dwarf::DW_AT_decl_file) && |
596 | Die.findRecursively(Attrs: dwarf::DW_AT_decl_line)) |
597 | FnStats.HasSourceLocation = true; |
598 | // Update function prefix. |
599 | FnPrefix = FnID; |
600 | } |
601 | |
602 | if (BytesInThisScope) { |
603 | BytesInScope = BytesInThisScope; |
604 | if (IsFunction) |
605 | GlobalStats.FunctionSize += BytesInThisScope; |
606 | else if (IsInlinedFunction && InlineDepth == 0) |
607 | GlobalStats.InlineFunctionSize += BytesInThisScope; |
608 | } |
609 | } else { |
610 | // Not a scope, visit the Die itself. It could be a variable. |
611 | collectStatsForDie(Die, FnPrefix, VarPrefix, BytesInScope, InlineDepth, |
612 | FnStatMap, GlobalStats, LocStats, AbstractOriginVariables: AbstractOriginVarsPtr); |
613 | } |
614 | |
615 | // Set InlineDepth correctly for child recursion |
616 | if (IsFunction) |
617 | InlineDepth = 0; |
618 | else if (IsInlinedFunction) |
619 | ++InlineDepth; |
620 | |
621 | // Traverse children. |
622 | unsigned LexicalBlockIndex = 0; |
623 | unsigned FormalParameterIndex = 0; |
624 | DWARFDie Child = Die.getFirstChild(); |
625 | while (Child) { |
626 | std::string ChildVarPrefix = VarPrefix; |
627 | if (Child.getTag() == dwarf::DW_TAG_lexical_block) |
628 | ChildVarPrefix += toHex(Input: LexicalBlockIndex++) + '.'; |
629 | if (Child.getTag() == dwarf::DW_TAG_formal_parameter) |
630 | ChildVarPrefix += 'p' + toHex(Input: FormalParameterIndex++) + '.'; |
631 | |
632 | collectStatsRecursive( |
633 | Die: Child, FnPrefix, VarPrefix: ChildVarPrefix, BytesInScope, InlineDepth, FnStatMap, |
634 | GlobalStats, LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
635 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed, |
636 | AbstractOriginVarsPtr); |
637 | Child = Child.getSibling(); |
638 | } |
639 | |
640 | if (!IsCandidateForZeroLocCovTracking) |
641 | return; |
642 | |
643 | // After we have processed all vars of the inlined function (or function with |
644 | // an abstract_origin), we want to know how many variables have no location. |
645 | for (auto Offset : AbstractOriginVars) { |
646 | LocStats.NumVarParam++; |
647 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
648 | auto FnDie = Die.getDwarfUnit()->getDIEForOffset(Offset); |
649 | if (!FnDie) |
650 | continue; |
651 | auto Tag = FnDie.getTag(); |
652 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
653 | LocStats.NumParam++; |
654 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
655 | } else if (Tag == dwarf::DW_TAG_variable) { |
656 | LocStats.NumVar++; |
657 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
658 | } |
659 | } |
660 | } |
661 | |
662 | /// Print human-readable output. |
663 | /// \{ |
664 | static void printDatum(json::OStream &J, const char *Key, json::Value Value) { |
665 | if (Value == OverflowValue) |
666 | J.attribute(Key, Contents: "overflowed" ); |
667 | else |
668 | J.attribute(Key, Contents: Value); |
669 | |
670 | LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n'); |
671 | } |
672 | |
673 | static void printLocationStats(json::OStream &J, const char *Key, |
674 | std::vector<SaturatingUINT64> &LocationStats) { |
675 | if (LocationStats[0].Value == OverflowValue) |
676 | J.attribute(Key: (Twine(Key) + |
677 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
678 | .str(), |
679 | Contents: "overflowed" ); |
680 | else |
681 | J.attribute( |
682 | Key: (Twine(Key) + " with 0% of parent scope covered by DW_AT_location" ) |
683 | .str(), |
684 | Contents: LocationStats[0].Value); |
685 | LLVM_DEBUG( |
686 | llvm::dbgs() << Key |
687 | << " with 0% of parent scope covered by DW_AT_location: \\" |
688 | << LocationStats[0].Value << '\n'); |
689 | |
690 | if (LocationStats[1].Value == OverflowValue) |
691 | J.attribute(Key: (Twine(Key) + |
692 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
693 | .str(), |
694 | Contents: "overflowed" ); |
695 | else |
696 | J.attribute(Key: (Twine(Key) + |
697 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
698 | .str(), |
699 | Contents: LocationStats[1].Value); |
700 | LLVM_DEBUG(llvm::dbgs() |
701 | << Key |
702 | << " with (0%,10%) of parent scope covered by DW_AT_location: " |
703 | << LocationStats[1].Value << '\n'); |
704 | |
705 | for (unsigned i = 2; i < NumOfCoverageCategories - 1; ++i) { |
706 | if (LocationStats[i].Value == OverflowValue) |
707 | J.attribute(Key: (Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
708 | Twine(i * 10) + |
709 | "%) of parent scope covered by DW_AT_location" ) |
710 | .str(), |
711 | Contents: "overflowed" ); |
712 | else |
713 | J.attribute(Key: (Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
714 | Twine(i * 10) + |
715 | "%) of parent scope covered by DW_AT_location" ) |
716 | .str(), |
717 | Contents: LocationStats[i].Value); |
718 | LLVM_DEBUG(llvm::dbgs() |
719 | << Key << " with [" << (i - 1) * 10 << "%," << i * 10 |
720 | << "%) of parent scope covered by DW_AT_location: " |
721 | << LocationStats[i].Value); |
722 | } |
723 | if (LocationStats[NumOfCoverageCategories - 1].Value == OverflowValue) |
724 | J.attribute( |
725 | Key: (Twine(Key) + " with 100% of parent scope covered by DW_AT_location" ) |
726 | .str(), |
727 | Contents: "overflowed" ); |
728 | else |
729 | J.attribute( |
730 | Key: (Twine(Key) + " with 100% of parent scope covered by DW_AT_location" ) |
731 | .str(), |
732 | Contents: LocationStats[NumOfCoverageCategories - 1].Value); |
733 | LLVM_DEBUG( |
734 | llvm::dbgs() << Key |
735 | << " with 100% of parent scope covered by DW_AT_location: " |
736 | << LocationStats[NumOfCoverageCategories - 1].Value); |
737 | } |
738 | |
739 | static void printSectionSizes(json::OStream &J, const SectionSizes &Sizes) { |
740 | for (const auto &It : Sizes.DebugSectionSizes) |
741 | J.attribute(Key: (Twine("#bytes in " ) + It.first).str(), Contents: int64_t(It.second)); |
742 | } |
743 | |
744 | /// Stop tracking variables that contain abstract_origin with a location. |
745 | /// This is used for out-of-order DW_AT_inline subprograms only. |
746 | static void updateVarsWithAbstractOriginLocCovInfo( |
747 | DWARFDie FnDieWithAbstractOrigin, |
748 | AbstractOriginVarsTy &AbstractOriginVars) { |
749 | DWARFDie Child = FnDieWithAbstractOrigin.getFirstChild(); |
750 | while (Child) { |
751 | const dwarf::Tag ChildTag = Child.getTag(); |
752 | if ((ChildTag == dwarf::DW_TAG_formal_parameter || |
753 | ChildTag == dwarf::DW_TAG_variable) && |
754 | (Child.find(Attr: dwarf::DW_AT_location) || |
755 | Child.find(Attr: dwarf::DW_AT_const_value))) { |
756 | auto OffsetVar = Child.find(Attr: dwarf::DW_AT_abstract_origin); |
757 | if (OffsetVar) |
758 | llvm::erase(C&: AbstractOriginVars, V: (*OffsetVar).getRawUValue()); |
759 | } else if (ChildTag == dwarf::DW_TAG_lexical_block) |
760 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin: Child, AbstractOriginVars); |
761 | Child = Child.getSibling(); |
762 | } |
763 | } |
764 | |
765 | /// Collect zero location coverage for inlined variables which refer to |
766 | /// a DW_AT_inline copy of subprogram that is out of order in the DWARF. |
767 | /// Also cover the variables of a concrete function (represented with |
768 | /// the DW_TAG_subprogram) with an abstract_origin attribute. |
769 | static void collectZeroLocCovForVarsWithAbstractOrigin( |
770 | DWARFUnit *DwUnit, GlobalStats &GlobalStats, LocationStats &LocStats, |
771 | AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo, |
772 | FunctionsWithAbstractOriginTy &FnsWithAbstractOriginToBeProcessed) { |
773 | // The next variable is used to filter out functions that have been processed, |
774 | // leaving FnsWithAbstractOriginToBeProcessed with just CrossCU references. |
775 | FunctionsWithAbstractOriginTy ProcessedFns; |
776 | for (auto FnOffset : FnsWithAbstractOriginToBeProcessed) { |
777 | DWARFDie FnDieWithAbstractOrigin = DwUnit->getDIEForOffset(Offset: FnOffset); |
778 | auto FnCopy = FnDieWithAbstractOrigin.find(Attr: dwarf::DW_AT_abstract_origin); |
779 | AbstractOriginVarsTy AbstractOriginVars; |
780 | if (!FnCopy) |
781 | continue; |
782 | uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
783 | // If there is no entry within LocalAbstractOriginFnInfo for the given |
784 | // FnCopyRawUValue, function isn't out-of-order in DWARF. Rather, we have |
785 | // CrossCU referencing. |
786 | auto It = LocalAbstractOriginFnInfo.find(Val: FnCopyRawUValue); |
787 | if (It == LocalAbstractOriginFnInfo.end()) |
788 | continue; |
789 | AbstractOriginVars = It->second; |
790 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin, |
791 | AbstractOriginVars); |
792 | |
793 | for (auto Offset : AbstractOriginVars) { |
794 | LocStats.NumVarParam++; |
795 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
796 | auto Tag = DwUnit->getDIEForOffset(Offset).getTag(); |
797 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
798 | LocStats.NumParam++; |
799 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
800 | } else if (Tag == dwarf::DW_TAG_variable) { |
801 | LocStats.NumVar++; |
802 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
803 | } |
804 | } |
805 | ProcessedFns.push_back(Elt: FnOffset); |
806 | } |
807 | for (auto ProcessedFn : ProcessedFns) |
808 | llvm::erase(C&: FnsWithAbstractOriginToBeProcessed, V: ProcessedFn); |
809 | } |
810 | |
811 | /// Collect zero location coverage for inlined variables which refer to |
812 | /// a DW_AT_inline copy of subprogram that is in a different CU. |
813 | static void collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
814 | LocationStats &LocStats, FunctionDIECUTyMap AbstractOriginFnCUs, |
815 | AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
816 | CrossCUReferencingDIELocationTy &CrossCUReferencesToBeResolved) { |
817 | for (const auto &CrossCUReferenceToBeResolved : |
818 | CrossCUReferencesToBeResolved) { |
819 | DWARFUnit *DwUnit = CrossCUReferenceToBeResolved.DwUnit; |
820 | DWARFDie FnDIEWithCrossCUReferencing = |
821 | DwUnit->getDIEForOffset(Offset: CrossCUReferenceToBeResolved.DIEOffset); |
822 | auto FnCopy = |
823 | FnDIEWithCrossCUReferencing.find(Attr: dwarf::DW_AT_abstract_origin); |
824 | if (!FnCopy) |
825 | continue; |
826 | uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
827 | AbstractOriginVarsTy AbstractOriginVars = |
828 | GlobalAbstractOriginFnInfo[FnCopyRawUValue]; |
829 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin: FnDIEWithCrossCUReferencing, |
830 | AbstractOriginVars); |
831 | for (auto Offset : AbstractOriginVars) { |
832 | LocStats.NumVarParam++; |
833 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
834 | auto Tag = (AbstractOriginFnCUs[FnCopyRawUValue]) |
835 | ->getDIEForOffset(Offset) |
836 | .getTag(); |
837 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
838 | LocStats.NumParam++; |
839 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
840 | } else if (Tag == dwarf::DW_TAG_variable) { |
841 | LocStats.NumVar++; |
842 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
843 | } |
844 | } |
845 | } |
846 | } |
847 | |
848 | /// \} |
849 | |
850 | /// Collect debug info quality metrics for an entire DIContext. |
851 | /// |
852 | /// Do the impossible and reduce the quality of the debug info down to a few |
853 | /// numbers. The idea is to condense the data into numbers that can be tracked |
854 | /// over time to identify trends in newer compiler versions and gauge the effect |
855 | /// of particular optimizations. The raw numbers themselves are not particularly |
856 | /// useful, only the delta between compiling the same program with different |
857 | /// compilers is. |
858 | bool dwarfdump::collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx, |
859 | const Twine &Filename, |
860 | raw_ostream &OS) { |
861 | StringRef FormatName = Obj.getFileFormatName(); |
862 | GlobalStats GlobalStats; |
863 | LocationStats LocStats; |
864 | LineStats LnStats; |
865 | StringMap<PerFunctionStats> Statistics; |
866 | // This variable holds variable information for functions with |
867 | // abstract_origin globally, across all CUs. |
868 | AbstractOriginVarsTyMap GlobalAbstractOriginFnInfo; |
869 | // This variable holds information about the CU of a function with |
870 | // abstract_origin. |
871 | FunctionDIECUTyMap AbstractOriginFnCUs; |
872 | CrossCUReferencingDIELocationTy CrossCUReferencesToBeResolved; |
873 | // Tuple representing a single source code position in the line table. Fields |
874 | // are respectively: Line, Col, File, where 'File' is an index into the Files |
875 | // vector below. |
876 | using LineTuple = std::tuple<uint32_t, uint16_t, uint16_t>; |
877 | SmallVector<std::string> Files; |
878 | DenseSet<LineTuple> UniqueLines; |
879 | DenseSet<LineTuple> UniqueNonZeroLines; |
880 | |
881 | for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units()) { |
882 | if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(ExtractUnitDIEOnly: false)) { |
883 | // This variable holds variable information for functions with |
884 | // abstract_origin, but just for the current CU. |
885 | AbstractOriginVarsTyMap LocalAbstractOriginFnInfo; |
886 | FunctionsWithAbstractOriginTy FnsWithAbstractOriginToBeProcessed; |
887 | |
888 | collectStatsRecursive( |
889 | Die: CUDie, FnPrefix: "/" , VarPrefix: "g" , BytesInScope: 0, InlineDepth: 0, FnStatMap&: Statistics, GlobalStats, LocStats, |
890 | AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
891 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
892 | |
893 | // collectZeroLocCovForVarsWithAbstractOrigin will filter out all |
894 | // out-of-order DWARF functions that have been processed within it, |
895 | // leaving FnsWithAbstractOriginToBeProcessed with only CrossCU |
896 | // references. |
897 | collectZeroLocCovForVarsWithAbstractOrigin( |
898 | DwUnit: CUDie.getDwarfUnit(), GlobalStats, LocStats, |
899 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
900 | |
901 | // Collect all CrossCU references into CrossCUReferencesToBeResolved. |
902 | for (auto CrossCUReferencingDIEOffset : |
903 | FnsWithAbstractOriginToBeProcessed) |
904 | CrossCUReferencesToBeResolved.push_back( |
905 | Elt: DIELocation(CUDie.getDwarfUnit(), CrossCUReferencingDIEOffset)); |
906 | } |
907 | const auto *LineTable = DICtx.getLineTableForUnit(U: CU.get()); |
908 | std::optional<uint64_t> LastFileIdxOpt; |
909 | if (LineTable) |
910 | LastFileIdxOpt = LineTable->getLastValidFileIndex(); |
911 | if (LastFileIdxOpt) { |
912 | // Each CU has its own file index; in order to track unique line entries |
913 | // across CUs, we therefore need to map each CU file index to a global |
914 | // file index, which we store here. |
915 | DenseMap<uint64_t, uint16_t> CUFileMapping; |
916 | for (uint64_t FileIdx = 0; FileIdx <= *LastFileIdxOpt; ++FileIdx) { |
917 | std::string File; |
918 | if (LineTable->getFileNameByIndex( |
919 | FileIndex: FileIdx, CompDir: CU->getCompilationDir(), |
920 | Kind: DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, |
921 | Result&: File)) { |
922 | auto ExistingFile = llvm::find(Range&: Files, Val: File); |
923 | if (ExistingFile != Files.end()) { |
924 | CUFileMapping[FileIdx] = std::distance(first: Files.begin(), last: ExistingFile); |
925 | } else { |
926 | CUFileMapping[FileIdx] = Files.size(); |
927 | Files.push_back(Elt: File); |
928 | } |
929 | } |
930 | } |
931 | for (const auto &Seq : LineTable->Sequences) { |
932 | LnStats.NumBytes += Seq.HighPC - Seq.LowPC; |
933 | // Ignore the `end_sequence` entry, since it's not interesting for us. |
934 | LnStats.NumEntries += Seq.LastRowIndex - Seq.FirstRowIndex - 1; |
935 | for (size_t RowIdx = Seq.FirstRowIndex; RowIdx < Seq.LastRowIndex - 1; |
936 | ++RowIdx) { |
937 | auto Entry = LineTable->Rows[RowIdx]; |
938 | if (Entry.IsStmt) |
939 | LnStats.NumIsStmtEntries += 1; |
940 | assert(CUFileMapping.contains(Entry.File) && |
941 | "Should have been collected earlier!" ); |
942 | uint16_t MappedFile = CUFileMapping[Entry.File]; |
943 | UniqueLines.insert(V: {Entry.Line, Entry.Column, MappedFile}); |
944 | if (Entry.Line != 0) { |
945 | UniqueNonZeroLines.insert(V: {Entry.Line, Entry.Column, MappedFile}); |
946 | } else { |
947 | auto EntryStartAddress = Entry.Address.Address; |
948 | auto EntryEndAddress = LineTable->Rows[RowIdx + 1].Address.Address; |
949 | LnStats.NumLineZeroBytes += EntryEndAddress - EntryStartAddress; |
950 | } |
951 | } |
952 | } |
953 | } |
954 | } |
955 | |
956 | LnStats.NumUniqueEntries = UniqueLines.size(); |
957 | LnStats.NumUniqueNonZeroEntries = UniqueNonZeroLines.size(); |
958 | |
959 | /// Resolve CrossCU references. |
960 | collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
961 | LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
962 | CrossCUReferencesToBeResolved); |
963 | |
964 | /// Collect the sizes of debug sections. |
965 | SectionSizes Sizes; |
966 | calculateSectionSizes(Obj, Sizes, Filename); |
967 | |
968 | /// The version number should be increased every time the algorithm is changed |
969 | /// (including bug fixes). New metrics may be added without increasing the |
970 | /// version. |
971 | unsigned Version = 9; |
972 | SaturatingUINT64 VarParamTotal = 0; |
973 | SaturatingUINT64 VarParamUnique = 0; |
974 | SaturatingUINT64 VarParamWithLoc = 0; |
975 | SaturatingUINT64 NumFunctions = 0; |
976 | SaturatingUINT64 NumOutOfLineFunctions = 0; |
977 | SaturatingUINT64 NumInlinedFunctions = 0; |
978 | SaturatingUINT64 NumFuncsWithSrcLoc = 0; |
979 | SaturatingUINT64 NumAbstractOrigins = 0; |
980 | SaturatingUINT64 ParamTotal = 0; |
981 | SaturatingUINT64 ParamWithType = 0; |
982 | SaturatingUINT64 ParamWithLoc = 0; |
983 | SaturatingUINT64 ParamWithSrcLoc = 0; |
984 | SaturatingUINT64 LocalVarTotal = 0; |
985 | SaturatingUINT64 LocalVarWithType = 0; |
986 | SaturatingUINT64 LocalVarWithSrcLoc = 0; |
987 | SaturatingUINT64 LocalVarWithLoc = 0; |
988 | for (auto &Entry : Statistics) { |
989 | PerFunctionStats &Stats = Entry.getValue(); |
990 | uint64_t TotalVars = Stats.VarsInFunction.size() * |
991 | (Stats.NumFnInlined + Stats.NumFnOutOfLine); |
992 | // Count variables in global scope. |
993 | if (!Stats.IsFunction) |
994 | TotalVars = |
995 | Stats.NumLocalVars + Stats.ConstantMembers + Stats.NumArtificial; |
996 | uint64_t Constants = Stats.ConstantMembers; |
997 | VarParamWithLoc += Stats.TotalVarWithLoc + Constants; |
998 | VarParamTotal += TotalVars; |
999 | VarParamUnique += Stats.VarsInFunction.size(); |
1000 | LLVM_DEBUG(for (auto &V |
1001 | : Stats.VarsInFunction) llvm::dbgs() |
1002 | << Entry.getKey() << ": " << V.getKey() << "\n" ); |
1003 | NumFunctions += Stats.IsFunction; |
1004 | NumFuncsWithSrcLoc += Stats.HasSourceLocation; |
1005 | NumOutOfLineFunctions += Stats.IsFunction * Stats.NumFnOutOfLine; |
1006 | NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined; |
1007 | NumAbstractOrigins += Stats.IsFunction * Stats.NumAbstractOrigins; |
1008 | ParamTotal += Stats.NumParams; |
1009 | ParamWithType += Stats.NumParamTypes; |
1010 | ParamWithLoc += Stats.NumParamLocations; |
1011 | ParamWithSrcLoc += Stats.NumParamSourceLocations; |
1012 | LocalVarTotal += Stats.NumLocalVars; |
1013 | LocalVarWithType += Stats.NumLocalVarTypes; |
1014 | LocalVarWithLoc += Stats.NumLocalVarLocations; |
1015 | LocalVarWithSrcLoc += Stats.NumLocalVarSourceLocations; |
1016 | } |
1017 | |
1018 | // Print summary. |
1019 | OS.SetBufferSize(1024); |
1020 | json::OStream J(OS, 2); |
1021 | J.objectBegin(); |
1022 | J.attribute(Key: "version" , Contents: Version); |
1023 | LLVM_DEBUG(llvm::dbgs() << "Variable location quality metrics\n" ; |
1024 | llvm::dbgs() << "---------------------------------\n" ); |
1025 | |
1026 | printDatum(J, Key: "file" , Value: Filename.str()); |
1027 | printDatum(J, Key: "format" , Value: FormatName); |
1028 | |
1029 | printDatum(J, Key: "#functions" , Value: NumFunctions.Value); |
1030 | printDatum(J, Key: "#functions with location" , Value: NumFuncsWithSrcLoc.Value); |
1031 | printDatum(J, Key: "#out-of-line functions" , Value: NumOutOfLineFunctions.Value); |
1032 | printDatum(J, Key: "#inlined functions" , Value: NumInlinedFunctions.Value); |
1033 | printDatum(J, Key: "#inlined functions with abstract origins" , |
1034 | Value: NumAbstractOrigins.Value); |
1035 | |
1036 | // This includes local variables and formal parameters. |
1037 | printDatum(J, Key: "#unique source variables" , Value: VarParamUnique.Value); |
1038 | printDatum(J, Key: "#source variables" , Value: VarParamTotal.Value); |
1039 | printDatum(J, Key: "#source variables with location" , Value: VarParamWithLoc.Value); |
1040 | |
1041 | printDatum(J, Key: "#call site entries" , Value: GlobalStats.CallSiteEntries.Value); |
1042 | printDatum(J, Key: "#call site DIEs" , Value: GlobalStats.CallSiteDIEs.Value); |
1043 | printDatum(J, Key: "#call site parameter DIEs" , |
1044 | Value: GlobalStats.CallSiteParamDIEs.Value); |
1045 | |
1046 | printDatum(J, Key: "sum_all_variables(#bytes in parent scope)" , |
1047 | Value: GlobalStats.ScopeBytes.Value); |
1048 | printDatum(J, |
1049 | Key: "sum_all_variables(#bytes in any scope covered by DW_AT_location)" , |
1050 | Value: GlobalStats.TotalBytesCovered.Value); |
1051 | printDatum(J, |
1052 | Key: "sum_all_variables(#bytes in parent scope covered by " |
1053 | "DW_AT_location)" , |
1054 | Value: GlobalStats.ScopeBytesCovered.Value); |
1055 | printDatum(J, |
1056 | Key: "sum_all_variables(#bytes in parent scope covered by " |
1057 | "DW_OP_entry_value)" , |
1058 | Value: GlobalStats.ScopeEntryValueBytesCovered.Value); |
1059 | |
1060 | printDatum(J, Key: "sum_all_params(#bytes in parent scope)" , |
1061 | Value: GlobalStats.ParamScopeBytes.Value); |
1062 | printDatum(J, |
1063 | Key: "sum_all_params(#bytes in parent scope covered by DW_AT_location)" , |
1064 | Value: GlobalStats.ParamScopeBytesCovered.Value); |
1065 | printDatum(J, |
1066 | Key: "sum_all_params(#bytes in parent scope covered by " |
1067 | "DW_OP_entry_value)" , |
1068 | Value: GlobalStats.ParamScopeEntryValueBytesCovered.Value); |
1069 | |
1070 | printDatum(J, Key: "sum_all_local_vars(#bytes in parent scope)" , |
1071 | Value: GlobalStats.LocalVarScopeBytes.Value); |
1072 | printDatum(J, |
1073 | Key: "sum_all_local_vars(#bytes in parent scope covered by " |
1074 | "DW_AT_location)" , |
1075 | Value: GlobalStats.LocalVarScopeBytesCovered.Value); |
1076 | printDatum(J, |
1077 | Key: "sum_all_local_vars(#bytes in parent scope covered by " |
1078 | "DW_OP_entry_value)" , |
1079 | Value: GlobalStats.LocalVarScopeEntryValueBytesCovered.Value); |
1080 | |
1081 | printDatum(J, Key: "#bytes within functions" , Value: GlobalStats.FunctionSize.Value); |
1082 | printDatum(J, Key: "#bytes within inlined functions" , |
1083 | Value: GlobalStats.InlineFunctionSize.Value); |
1084 | |
1085 | // Print the summary for formal parameters. |
1086 | printDatum(J, Key: "#params" , Value: ParamTotal.Value); |
1087 | printDatum(J, Key: "#params with source location" , Value: ParamWithSrcLoc.Value); |
1088 | printDatum(J, Key: "#params with type" , Value: ParamWithType.Value); |
1089 | printDatum(J, Key: "#params with binary location" , Value: ParamWithLoc.Value); |
1090 | |
1091 | // Print the summary for local variables. |
1092 | printDatum(J, Key: "#local vars" , Value: LocalVarTotal.Value); |
1093 | printDatum(J, Key: "#local vars with source location" , Value: LocalVarWithSrcLoc.Value); |
1094 | printDatum(J, Key: "#local vars with type" , Value: LocalVarWithType.Value); |
1095 | printDatum(J, Key: "#local vars with binary location" , Value: LocalVarWithLoc.Value); |
1096 | |
1097 | // Print the debug section sizes. |
1098 | printSectionSizes(J, Sizes); |
1099 | |
1100 | // Print the location statistics for variables (includes local variables |
1101 | // and formal parameters). |
1102 | printDatum(J, Key: "#variables processed by location statistics" , |
1103 | Value: LocStats.NumVarParam.Value); |
1104 | printLocationStats(J, Key: "#variables" , LocationStats&: LocStats.VarParamLocStats); |
1105 | printLocationStats(J, Key: "#variables - entry values" , |
1106 | LocationStats&: LocStats.VarParamNonEntryValLocStats); |
1107 | |
1108 | // Print the location statistics for formal parameters. |
1109 | printDatum(J, Key: "#params processed by location statistics" , |
1110 | Value: LocStats.NumParam.Value); |
1111 | printLocationStats(J, Key: "#params" , LocationStats&: LocStats.ParamLocStats); |
1112 | printLocationStats(J, Key: "#params - entry values" , |
1113 | LocationStats&: LocStats.ParamNonEntryValLocStats); |
1114 | |
1115 | // Print the location statistics for local variables. |
1116 | printDatum(J, Key: "#local vars processed by location statistics" , |
1117 | Value: LocStats.NumVar.Value); |
1118 | printLocationStats(J, Key: "#local vars" , LocationStats&: LocStats.LocalVarLocStats); |
1119 | printLocationStats(J, Key: "#local vars - entry values" , |
1120 | LocationStats&: LocStats.LocalVarNonEntryValLocStats); |
1121 | |
1122 | // Print line statistics for the object file. |
1123 | printDatum(J, Key: "#bytes with line information" , Value: LnStats.NumBytes.Value); |
1124 | printDatum(J, Key: "#bytes with line-0 locations" , Value: LnStats.NumLineZeroBytes.Value); |
1125 | printDatum(J, Key: "#line entries" , Value: LnStats.NumEntries.Value); |
1126 | printDatum(J, Key: "#line entries (is_stmt)" , Value: LnStats.NumIsStmtEntries.Value); |
1127 | printDatum(J, Key: "#line entries (unique)" , Value: LnStats.NumUniqueEntries.Value); |
1128 | printDatum(J, Key: "#line entries (unique non-0)" , |
1129 | Value: LnStats.NumUniqueNonZeroEntries.Value); |
1130 | |
1131 | J.objectEnd(); |
1132 | OS << '\n'; |
1133 | LLVM_DEBUG( |
1134 | llvm::dbgs() << "Total Availability: " |
1135 | << (VarParamTotal.Value |
1136 | ? (int)std::round((VarParamWithLoc.Value * 100.0) / |
1137 | VarParamTotal.Value) |
1138 | : 0) |
1139 | << "%\n" ; |
1140 | llvm::dbgs() << "PC Ranges covered: " |
1141 | << (GlobalStats.ScopeBytes.Value |
1142 | ? (int)std::round( |
1143 | (GlobalStats.ScopeBytesCovered.Value * 100.0) / |
1144 | GlobalStats.ScopeBytes.Value) |
1145 | : 0) |
1146 | << "%\n" ); |
1147 | return true; |
1148 | } |
1149 | |