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/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 ID.str(); |
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 ID.str(); |
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 (LocalAbstractOriginFnInfo.count(Val: OffsetOfInlineFnCopy)) { |
533 | AbstractOriginVars = LocalAbstractOriginFnInfo[OffsetOfInlineFnCopy]; |
534 | AbstractOriginVarsPtr = &AbstractOriginVars; |
535 | } else { |
536 | // This means that the DW_AT_inline fn copy is out of order |
537 | // or that the abstract_origin references another CU, |
538 | // so this abstract origin instance will be processed later. |
539 | FnsWithAbstractOriginToBeProcessed.push_back(Elt: Die.getOffset()); |
540 | AbstractOriginVarsPtr = nullptr; |
541 | } |
542 | } |
543 | } |
544 | |
545 | if (IsFunction || IsInlinedFunction || IsBlock) { |
546 | // Reset VarPrefix when entering a new function. |
547 | if (IsFunction || IsInlinedFunction) |
548 | VarPrefix = "v" ; |
549 | |
550 | // Ignore forward declarations. |
551 | if (Die.find(Attr: dwarf::DW_AT_declaration)) |
552 | return; |
553 | |
554 | // Check for call sites. |
555 | if (Die.find(Attr: dwarf::DW_AT_call_file) && Die.find(Attr: dwarf::DW_AT_call_line)) |
556 | GlobalStats.CallSiteEntries++; |
557 | |
558 | // PC Ranges. |
559 | auto RangesOrError = Die.getAddressRanges(); |
560 | if (!RangesOrError) { |
561 | llvm::consumeError(Err: RangesOrError.takeError()); |
562 | return; |
563 | } |
564 | |
565 | auto Ranges = RangesOrError.get(); |
566 | uint64_t BytesInThisScope = 0; |
567 | for (auto Range : Ranges) |
568 | BytesInThisScope += Range.HighPC - Range.LowPC; |
569 | |
570 | // Count the function. |
571 | if (!IsBlock) { |
572 | // Skip over abstract origins, but collect variables |
573 | // from it so it can be used for location statistics |
574 | // for inlined instancies. |
575 | if (Die.find(Attr: dwarf::DW_AT_inline)) { |
576 | uint64_t SPOffset = Die.getOffset(); |
577 | AbstractOriginFnCUs[SPOffset] = Die.getDwarfUnit(); |
578 | collectAbstractOriginFnInfo(Die, SPOffset, GlobalAbstractOriginFnInfo, |
579 | LocalAbstractOriginFnInfo); |
580 | return; |
581 | } |
582 | |
583 | std::string FnID = constructDieID(Die); |
584 | // We've seen an instance of this function. |
585 | auto &FnStats = FnStatMap[FnID]; |
586 | FnStats.IsFunction = true; |
587 | if (IsInlinedFunction) { |
588 | FnStats.NumFnInlined++; |
589 | if (Die.findRecursively(Attrs: dwarf::DW_AT_abstract_origin)) |
590 | FnStats.NumAbstractOrigins++; |
591 | } else { |
592 | FnStats.NumFnOutOfLine++; |
593 | } |
594 | if (Die.findRecursively(Attrs: dwarf::DW_AT_decl_file) && |
595 | Die.findRecursively(Attrs: dwarf::DW_AT_decl_line)) |
596 | FnStats.HasSourceLocation = true; |
597 | // Update function prefix. |
598 | FnPrefix = FnID; |
599 | } |
600 | |
601 | if (BytesInThisScope) { |
602 | BytesInScope = BytesInThisScope; |
603 | if (IsFunction) |
604 | GlobalStats.FunctionSize += BytesInThisScope; |
605 | else if (IsInlinedFunction && InlineDepth == 0) |
606 | GlobalStats.InlineFunctionSize += BytesInThisScope; |
607 | } |
608 | } else { |
609 | // Not a scope, visit the Die itself. It could be a variable. |
610 | collectStatsForDie(Die, FnPrefix, VarPrefix, BytesInScope, InlineDepth, |
611 | FnStatMap, GlobalStats, LocStats, AbstractOriginVariables: AbstractOriginVarsPtr); |
612 | } |
613 | |
614 | // Set InlineDepth correctly for child recursion |
615 | if (IsFunction) |
616 | InlineDepth = 0; |
617 | else if (IsInlinedFunction) |
618 | ++InlineDepth; |
619 | |
620 | // Traverse children. |
621 | unsigned LexicalBlockIndex = 0; |
622 | unsigned FormalParameterIndex = 0; |
623 | DWARFDie Child = Die.getFirstChild(); |
624 | while (Child) { |
625 | std::string ChildVarPrefix = VarPrefix; |
626 | if (Child.getTag() == dwarf::DW_TAG_lexical_block) |
627 | ChildVarPrefix += toHex(Input: LexicalBlockIndex++) + '.'; |
628 | if (Child.getTag() == dwarf::DW_TAG_formal_parameter) |
629 | ChildVarPrefix += 'p' + toHex(Input: FormalParameterIndex++) + '.'; |
630 | |
631 | collectStatsRecursive( |
632 | Die: Child, FnPrefix, VarPrefix: ChildVarPrefix, BytesInScope, InlineDepth, FnStatMap, |
633 | GlobalStats, LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
634 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed, |
635 | AbstractOriginVarsPtr); |
636 | Child = Child.getSibling(); |
637 | } |
638 | |
639 | if (!IsCandidateForZeroLocCovTracking) |
640 | return; |
641 | |
642 | // After we have processed all vars of the inlined function (or function with |
643 | // an abstract_origin), we want to know how many variables have no location. |
644 | for (auto Offset : AbstractOriginVars) { |
645 | LocStats.NumVarParam++; |
646 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
647 | auto FnDie = Die.getDwarfUnit()->getDIEForOffset(Offset); |
648 | if (!FnDie) |
649 | continue; |
650 | auto Tag = FnDie.getTag(); |
651 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
652 | LocStats.NumParam++; |
653 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
654 | } else if (Tag == dwarf::DW_TAG_variable) { |
655 | LocStats.NumVar++; |
656 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
657 | } |
658 | } |
659 | } |
660 | |
661 | /// Print human-readable output. |
662 | /// \{ |
663 | static void printDatum(json::OStream &J, const char *Key, json::Value Value) { |
664 | if (Value == OverflowValue) |
665 | J.attribute(Key, Contents: "overflowed" ); |
666 | else |
667 | J.attribute(Key, Contents: Value); |
668 | |
669 | LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n'); |
670 | } |
671 | |
672 | static void printLocationStats(json::OStream &J, const char *Key, |
673 | std::vector<SaturatingUINT64> &LocationStats) { |
674 | if (LocationStats[0].Value == OverflowValue) |
675 | J.attribute(Key: (Twine(Key) + |
676 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
677 | .str(), |
678 | Contents: "overflowed" ); |
679 | else |
680 | J.attribute( |
681 | Key: (Twine(Key) + " with 0% of parent scope covered by DW_AT_location" ) |
682 | .str(), |
683 | Contents: LocationStats[0].Value); |
684 | LLVM_DEBUG( |
685 | llvm::dbgs() << Key |
686 | << " with 0% of parent scope covered by DW_AT_location: \\" |
687 | << LocationStats[0].Value << '\n'); |
688 | |
689 | if (LocationStats[1].Value == OverflowValue) |
690 | J.attribute(Key: (Twine(Key) + |
691 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
692 | .str(), |
693 | Contents: "overflowed" ); |
694 | else |
695 | J.attribute(Key: (Twine(Key) + |
696 | " with (0%,10%) of parent scope covered by DW_AT_location" ) |
697 | .str(), |
698 | Contents: LocationStats[1].Value); |
699 | LLVM_DEBUG(llvm::dbgs() |
700 | << Key |
701 | << " with (0%,10%) of parent scope covered by DW_AT_location: " |
702 | << LocationStats[1].Value << '\n'); |
703 | |
704 | for (unsigned i = 2; i < NumOfCoverageCategories - 1; ++i) { |
705 | if (LocationStats[i].Value == OverflowValue) |
706 | J.attribute(Key: (Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
707 | Twine(i * 10) + |
708 | "%) of parent scope covered by DW_AT_location" ) |
709 | .str(), |
710 | Contents: "overflowed" ); |
711 | else |
712 | J.attribute(Key: (Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
713 | Twine(i * 10) + |
714 | "%) of parent scope covered by DW_AT_location" ) |
715 | .str(), |
716 | Contents: LocationStats[i].Value); |
717 | LLVM_DEBUG(llvm::dbgs() |
718 | << Key << " with [" << (i - 1) * 10 << "%," << i * 10 |
719 | << "%) of parent scope covered by DW_AT_location: " |
720 | << LocationStats[i].Value); |
721 | } |
722 | if (LocationStats[NumOfCoverageCategories - 1].Value == OverflowValue) |
723 | J.attribute( |
724 | Key: (Twine(Key) + " with 100% of parent scope covered by DW_AT_location" ) |
725 | .str(), |
726 | Contents: "overflowed" ); |
727 | else |
728 | J.attribute( |
729 | Key: (Twine(Key) + " with 100% of parent scope covered by DW_AT_location" ) |
730 | .str(), |
731 | Contents: LocationStats[NumOfCoverageCategories - 1].Value); |
732 | LLVM_DEBUG( |
733 | llvm::dbgs() << Key |
734 | << " with 100% of parent scope covered by DW_AT_location: " |
735 | << LocationStats[NumOfCoverageCategories - 1].Value); |
736 | } |
737 | |
738 | static void printSectionSizes(json::OStream &J, const SectionSizes &Sizes) { |
739 | for (const auto &It : Sizes.DebugSectionSizes) |
740 | J.attribute(Key: (Twine("#bytes in " ) + It.first).str(), Contents: int64_t(It.second)); |
741 | } |
742 | |
743 | /// Stop tracking variables that contain abstract_origin with a location. |
744 | /// This is used for out-of-order DW_AT_inline subprograms only. |
745 | static void updateVarsWithAbstractOriginLocCovInfo( |
746 | DWARFDie FnDieWithAbstractOrigin, |
747 | AbstractOriginVarsTy &AbstractOriginVars) { |
748 | DWARFDie Child = FnDieWithAbstractOrigin.getFirstChild(); |
749 | while (Child) { |
750 | const dwarf::Tag ChildTag = Child.getTag(); |
751 | if ((ChildTag == dwarf::DW_TAG_formal_parameter || |
752 | ChildTag == dwarf::DW_TAG_variable) && |
753 | (Child.find(Attr: dwarf::DW_AT_location) || |
754 | Child.find(Attr: dwarf::DW_AT_const_value))) { |
755 | auto OffsetVar = Child.find(Attr: dwarf::DW_AT_abstract_origin); |
756 | if (OffsetVar) |
757 | llvm::erase(C&: AbstractOriginVars, V: (*OffsetVar).getRawUValue()); |
758 | } else if (ChildTag == dwarf::DW_TAG_lexical_block) |
759 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin: Child, AbstractOriginVars); |
760 | Child = Child.getSibling(); |
761 | } |
762 | } |
763 | |
764 | /// Collect zero location coverage for inlined variables which refer to |
765 | /// a DW_AT_inline copy of subprogram that is out of order in the DWARF. |
766 | /// Also cover the variables of a concrete function (represented with |
767 | /// the DW_TAG_subprogram) with an abstract_origin attribute. |
768 | static void collectZeroLocCovForVarsWithAbstractOrigin( |
769 | DWARFUnit *DwUnit, GlobalStats &GlobalStats, LocationStats &LocStats, |
770 | AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo, |
771 | FunctionsWithAbstractOriginTy &FnsWithAbstractOriginToBeProcessed) { |
772 | // The next variable is used to filter out functions that have been processed, |
773 | // leaving FnsWithAbstractOriginToBeProcessed with just CrossCU references. |
774 | FunctionsWithAbstractOriginTy ProcessedFns; |
775 | for (auto FnOffset : FnsWithAbstractOriginToBeProcessed) { |
776 | DWARFDie FnDieWithAbstractOrigin = DwUnit->getDIEForOffset(Offset: FnOffset); |
777 | auto FnCopy = FnDieWithAbstractOrigin.find(Attr: dwarf::DW_AT_abstract_origin); |
778 | AbstractOriginVarsTy AbstractOriginVars; |
779 | if (!FnCopy) |
780 | continue; |
781 | uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
782 | // If there is no entry within LocalAbstractOriginFnInfo for the given |
783 | // FnCopyRawUValue, function isn't out-of-order in DWARF. Rather, we have |
784 | // CrossCU referencing. |
785 | if (!LocalAbstractOriginFnInfo.count(Val: FnCopyRawUValue)) |
786 | continue; |
787 | AbstractOriginVars = LocalAbstractOriginFnInfo[FnCopyRawUValue]; |
788 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin, |
789 | AbstractOriginVars); |
790 | |
791 | for (auto Offset : AbstractOriginVars) { |
792 | LocStats.NumVarParam++; |
793 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
794 | auto Tag = DwUnit->getDIEForOffset(Offset).getTag(); |
795 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
796 | LocStats.NumParam++; |
797 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
798 | } else if (Tag == dwarf::DW_TAG_variable) { |
799 | LocStats.NumVar++; |
800 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
801 | } |
802 | } |
803 | ProcessedFns.push_back(Elt: FnOffset); |
804 | } |
805 | for (auto ProcessedFn : ProcessedFns) |
806 | llvm::erase(C&: FnsWithAbstractOriginToBeProcessed, V: ProcessedFn); |
807 | } |
808 | |
809 | /// Collect zero location coverage for inlined variables which refer to |
810 | /// a DW_AT_inline copy of subprogram that is in a different CU. |
811 | static void collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
812 | LocationStats &LocStats, FunctionDIECUTyMap AbstractOriginFnCUs, |
813 | AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
814 | CrossCUReferencingDIELocationTy &CrossCUReferencesToBeResolved) { |
815 | for (const auto &CrossCUReferenceToBeResolved : |
816 | CrossCUReferencesToBeResolved) { |
817 | DWARFUnit *DwUnit = CrossCUReferenceToBeResolved.DwUnit; |
818 | DWARFDie FnDIEWithCrossCUReferencing = |
819 | DwUnit->getDIEForOffset(Offset: CrossCUReferenceToBeResolved.DIEOffset); |
820 | auto FnCopy = |
821 | FnDIEWithCrossCUReferencing.find(Attr: dwarf::DW_AT_abstract_origin); |
822 | if (!FnCopy) |
823 | continue; |
824 | uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
825 | AbstractOriginVarsTy AbstractOriginVars = |
826 | GlobalAbstractOriginFnInfo[FnCopyRawUValue]; |
827 | updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin: FnDIEWithCrossCUReferencing, |
828 | AbstractOriginVars); |
829 | for (auto Offset : AbstractOriginVars) { |
830 | LocStats.NumVarParam++; |
831 | LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
832 | auto Tag = (AbstractOriginFnCUs[FnCopyRawUValue]) |
833 | ->getDIEForOffset(Offset) |
834 | .getTag(); |
835 | if (Tag == dwarf::DW_TAG_formal_parameter) { |
836 | LocStats.NumParam++; |
837 | LocStats.ParamLocStats[ZeroCoverageBucket]++; |
838 | } else if (Tag == dwarf::DW_TAG_variable) { |
839 | LocStats.NumVar++; |
840 | LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
841 | } |
842 | } |
843 | } |
844 | } |
845 | |
846 | /// \} |
847 | |
848 | /// Collect debug info quality metrics for an entire DIContext. |
849 | /// |
850 | /// Do the impossible and reduce the quality of the debug info down to a few |
851 | /// numbers. The idea is to condense the data into numbers that can be tracked |
852 | /// over time to identify trends in newer compiler versions and gauge the effect |
853 | /// of particular optimizations. The raw numbers themselves are not particularly |
854 | /// useful, only the delta between compiling the same program with different |
855 | /// compilers is. |
856 | bool dwarfdump::collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx, |
857 | const Twine &Filename, |
858 | raw_ostream &OS) { |
859 | StringRef FormatName = Obj.getFileFormatName(); |
860 | GlobalStats GlobalStats; |
861 | LocationStats LocStats; |
862 | LineStats LnStats; |
863 | StringMap<PerFunctionStats> Statistics; |
864 | // This variable holds variable information for functions with |
865 | // abstract_origin globally, across all CUs. |
866 | AbstractOriginVarsTyMap GlobalAbstractOriginFnInfo; |
867 | // This variable holds information about the CU of a function with |
868 | // abstract_origin. |
869 | FunctionDIECUTyMap AbstractOriginFnCUs; |
870 | CrossCUReferencingDIELocationTy CrossCUReferencesToBeResolved; |
871 | // Tuple representing a single source code position in the line table. Fields |
872 | // are respectively: Line, Col, File, where 'File' is an index into the Files |
873 | // vector below. |
874 | using LineTuple = std::tuple<uint32_t, uint16_t, uint16_t>; |
875 | SmallVector<std::string> Files; |
876 | DenseSet<LineTuple> UniqueLines; |
877 | DenseSet<LineTuple> UniqueNonZeroLines; |
878 | |
879 | for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units()) { |
880 | if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(ExtractUnitDIEOnly: false)) { |
881 | // This variable holds variable information for functions with |
882 | // abstract_origin, but just for the current CU. |
883 | AbstractOriginVarsTyMap LocalAbstractOriginFnInfo; |
884 | FunctionsWithAbstractOriginTy FnsWithAbstractOriginToBeProcessed; |
885 | |
886 | collectStatsRecursive( |
887 | Die: CUDie, FnPrefix: "/" , VarPrefix: "g" , BytesInScope: 0, InlineDepth: 0, FnStatMap&: Statistics, GlobalStats, LocStats, |
888 | AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
889 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
890 | |
891 | // collectZeroLocCovForVarsWithAbstractOrigin will filter out all |
892 | // out-of-order DWARF functions that have been processed within it, |
893 | // leaving FnsWithAbstractOriginToBeProcessed with only CrossCU |
894 | // references. |
895 | collectZeroLocCovForVarsWithAbstractOrigin( |
896 | DwUnit: CUDie.getDwarfUnit(), GlobalStats, LocStats, |
897 | LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
898 | |
899 | // Collect all CrossCU references into CrossCUReferencesToBeResolved. |
900 | for (auto CrossCUReferencingDIEOffset : |
901 | FnsWithAbstractOriginToBeProcessed) |
902 | CrossCUReferencesToBeResolved.push_back( |
903 | Elt: DIELocation(CUDie.getDwarfUnit(), CrossCUReferencingDIEOffset)); |
904 | } |
905 | const auto *LineTable = DICtx.getLineTableForUnit(U: CU.get()); |
906 | std::optional<uint64_t> LastFileIdxOpt; |
907 | if (LineTable) |
908 | LastFileIdxOpt = LineTable->getLastValidFileIndex(); |
909 | if (LastFileIdxOpt) { |
910 | // Each CU has its own file index; in order to track unique line entries |
911 | // across CUs, we therefore need to map each CU file index to a global |
912 | // file index, which we store here. |
913 | DenseMap<uint64_t, uint16_t> CUFileMapping; |
914 | for (uint64_t FileIdx = 0; FileIdx <= *LastFileIdxOpt; ++FileIdx) { |
915 | std::string File; |
916 | if (LineTable->getFileNameByIndex( |
917 | FileIndex: FileIdx, CompDir: CU->getCompilationDir(), |
918 | Kind: DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, |
919 | Result&: File)) { |
920 | auto ExistingFile = llvm::find(Range&: Files, Val: File); |
921 | if (ExistingFile != Files.end()) { |
922 | CUFileMapping[FileIdx] = std::distance(first: Files.begin(), last: ExistingFile); |
923 | } else { |
924 | CUFileMapping[FileIdx] = Files.size(); |
925 | Files.push_back(Elt: File); |
926 | } |
927 | } |
928 | } |
929 | for (const auto &Seq : LineTable->Sequences) { |
930 | LnStats.NumBytes += Seq.HighPC - Seq.LowPC; |
931 | // Ignore the `end_sequence` entry, since it's not interesting for us. |
932 | LnStats.NumEntries += Seq.LastRowIndex - Seq.FirstRowIndex - 1; |
933 | for (size_t RowIdx = Seq.FirstRowIndex; RowIdx < Seq.LastRowIndex - 1; |
934 | ++RowIdx) { |
935 | auto Entry = LineTable->Rows[RowIdx]; |
936 | if (Entry.IsStmt) |
937 | LnStats.NumIsStmtEntries += 1; |
938 | assert(CUFileMapping.contains(Entry.File) && |
939 | "Should have been collected earlier!" ); |
940 | uint16_t MappedFile = CUFileMapping[Entry.File]; |
941 | UniqueLines.insert(V: {Entry.Line, Entry.Column, MappedFile}); |
942 | if (Entry.Line != 0) { |
943 | UniqueNonZeroLines.insert(V: {Entry.Line, Entry.Column, MappedFile}); |
944 | } else { |
945 | auto EntryStartAddress = Entry.Address.Address; |
946 | auto EntryEndAddress = LineTable->Rows[RowIdx + 1].Address.Address; |
947 | LnStats.NumLineZeroBytes += EntryEndAddress - EntryStartAddress; |
948 | } |
949 | } |
950 | } |
951 | } |
952 | } |
953 | |
954 | LnStats.NumUniqueEntries = UniqueLines.size(); |
955 | LnStats.NumUniqueNonZeroEntries = UniqueNonZeroLines.size(); |
956 | |
957 | /// Resolve CrossCU references. |
958 | collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
959 | LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
960 | CrossCUReferencesToBeResolved); |
961 | |
962 | /// Collect the sizes of debug sections. |
963 | SectionSizes Sizes; |
964 | calculateSectionSizes(Obj, Sizes, Filename); |
965 | |
966 | /// The version number should be increased every time the algorithm is changed |
967 | /// (including bug fixes). New metrics may be added without increasing the |
968 | /// version. |
969 | unsigned Version = 9; |
970 | SaturatingUINT64 VarParamTotal = 0; |
971 | SaturatingUINT64 VarParamUnique = 0; |
972 | SaturatingUINT64 VarParamWithLoc = 0; |
973 | SaturatingUINT64 NumFunctions = 0; |
974 | SaturatingUINT64 NumInlinedFunctions = 0; |
975 | SaturatingUINT64 NumFuncsWithSrcLoc = 0; |
976 | SaturatingUINT64 NumAbstractOrigins = 0; |
977 | SaturatingUINT64 ParamTotal = 0; |
978 | SaturatingUINT64 ParamWithType = 0; |
979 | SaturatingUINT64 ParamWithLoc = 0; |
980 | SaturatingUINT64 ParamWithSrcLoc = 0; |
981 | SaturatingUINT64 LocalVarTotal = 0; |
982 | SaturatingUINT64 LocalVarWithType = 0; |
983 | SaturatingUINT64 LocalVarWithSrcLoc = 0; |
984 | SaturatingUINT64 LocalVarWithLoc = 0; |
985 | for (auto &Entry : Statistics) { |
986 | PerFunctionStats &Stats = Entry.getValue(); |
987 | uint64_t TotalVars = Stats.VarsInFunction.size() * |
988 | (Stats.NumFnInlined + Stats.NumFnOutOfLine); |
989 | // Count variables in global scope. |
990 | if (!Stats.IsFunction) |
991 | TotalVars = |
992 | Stats.NumLocalVars + Stats.ConstantMembers + Stats.NumArtificial; |
993 | uint64_t Constants = Stats.ConstantMembers; |
994 | VarParamWithLoc += Stats.TotalVarWithLoc + Constants; |
995 | VarParamTotal += TotalVars; |
996 | VarParamUnique += Stats.VarsInFunction.size(); |
997 | LLVM_DEBUG(for (auto &V |
998 | : Stats.VarsInFunction) llvm::dbgs() |
999 | << Entry.getKey() << ": " << V.getKey() << "\n" ); |
1000 | NumFunctions += Stats.IsFunction; |
1001 | NumFuncsWithSrcLoc += Stats.HasSourceLocation; |
1002 | NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined; |
1003 | NumAbstractOrigins += Stats.IsFunction * Stats.NumAbstractOrigins; |
1004 | ParamTotal += Stats.NumParams; |
1005 | ParamWithType += Stats.NumParamTypes; |
1006 | ParamWithLoc += Stats.NumParamLocations; |
1007 | ParamWithSrcLoc += Stats.NumParamSourceLocations; |
1008 | LocalVarTotal += Stats.NumLocalVars; |
1009 | LocalVarWithType += Stats.NumLocalVarTypes; |
1010 | LocalVarWithLoc += Stats.NumLocalVarLocations; |
1011 | LocalVarWithSrcLoc += Stats.NumLocalVarSourceLocations; |
1012 | } |
1013 | |
1014 | // Print summary. |
1015 | OS.SetBufferSize(1024); |
1016 | json::OStream J(OS, 2); |
1017 | J.objectBegin(); |
1018 | J.attribute(Key: "version" , Contents: Version); |
1019 | LLVM_DEBUG(llvm::dbgs() << "Variable location quality metrics\n" ; |
1020 | llvm::dbgs() << "---------------------------------\n" ); |
1021 | |
1022 | printDatum(J, Key: "file" , Value: Filename.str()); |
1023 | printDatum(J, Key: "format" , Value: FormatName); |
1024 | |
1025 | printDatum(J, Key: "#functions" , Value: NumFunctions.Value); |
1026 | printDatum(J, Key: "#functions with location" , Value: NumFuncsWithSrcLoc.Value); |
1027 | printDatum(J, Key: "#inlined functions" , Value: NumInlinedFunctions.Value); |
1028 | printDatum(J, Key: "#inlined functions with abstract origins" , |
1029 | Value: NumAbstractOrigins.Value); |
1030 | |
1031 | // This includes local variables and formal parameters. |
1032 | printDatum(J, Key: "#unique source variables" , Value: VarParamUnique.Value); |
1033 | printDatum(J, Key: "#source variables" , Value: VarParamTotal.Value); |
1034 | printDatum(J, Key: "#source variables with location" , Value: VarParamWithLoc.Value); |
1035 | |
1036 | printDatum(J, Key: "#call site entries" , Value: GlobalStats.CallSiteEntries.Value); |
1037 | printDatum(J, Key: "#call site DIEs" , Value: GlobalStats.CallSiteDIEs.Value); |
1038 | printDatum(J, Key: "#call site parameter DIEs" , |
1039 | Value: GlobalStats.CallSiteParamDIEs.Value); |
1040 | |
1041 | printDatum(J, Key: "sum_all_variables(#bytes in parent scope)" , |
1042 | Value: GlobalStats.ScopeBytes.Value); |
1043 | printDatum(J, |
1044 | Key: "sum_all_variables(#bytes in any scope covered by DW_AT_location)" , |
1045 | Value: GlobalStats.TotalBytesCovered.Value); |
1046 | printDatum(J, |
1047 | Key: "sum_all_variables(#bytes in parent scope covered by " |
1048 | "DW_AT_location)" , |
1049 | Value: GlobalStats.ScopeBytesCovered.Value); |
1050 | printDatum(J, |
1051 | Key: "sum_all_variables(#bytes in parent scope covered by " |
1052 | "DW_OP_entry_value)" , |
1053 | Value: GlobalStats.ScopeEntryValueBytesCovered.Value); |
1054 | |
1055 | printDatum(J, Key: "sum_all_params(#bytes in parent scope)" , |
1056 | Value: GlobalStats.ParamScopeBytes.Value); |
1057 | printDatum(J, |
1058 | Key: "sum_all_params(#bytes in parent scope covered by DW_AT_location)" , |
1059 | Value: GlobalStats.ParamScopeBytesCovered.Value); |
1060 | printDatum(J, |
1061 | Key: "sum_all_params(#bytes in parent scope covered by " |
1062 | "DW_OP_entry_value)" , |
1063 | Value: GlobalStats.ParamScopeEntryValueBytesCovered.Value); |
1064 | |
1065 | printDatum(J, Key: "sum_all_local_vars(#bytes in parent scope)" , |
1066 | Value: GlobalStats.LocalVarScopeBytes.Value); |
1067 | printDatum(J, |
1068 | Key: "sum_all_local_vars(#bytes in parent scope covered by " |
1069 | "DW_AT_location)" , |
1070 | Value: GlobalStats.LocalVarScopeBytesCovered.Value); |
1071 | printDatum(J, |
1072 | Key: "sum_all_local_vars(#bytes in parent scope covered by " |
1073 | "DW_OP_entry_value)" , |
1074 | Value: GlobalStats.LocalVarScopeEntryValueBytesCovered.Value); |
1075 | |
1076 | printDatum(J, Key: "#bytes within functions" , Value: GlobalStats.FunctionSize.Value); |
1077 | printDatum(J, Key: "#bytes within inlined functions" , |
1078 | Value: GlobalStats.InlineFunctionSize.Value); |
1079 | |
1080 | // Print the summary for formal parameters. |
1081 | printDatum(J, Key: "#params" , Value: ParamTotal.Value); |
1082 | printDatum(J, Key: "#params with source location" , Value: ParamWithSrcLoc.Value); |
1083 | printDatum(J, Key: "#params with type" , Value: ParamWithType.Value); |
1084 | printDatum(J, Key: "#params with binary location" , Value: ParamWithLoc.Value); |
1085 | |
1086 | // Print the summary for local variables. |
1087 | printDatum(J, Key: "#local vars" , Value: LocalVarTotal.Value); |
1088 | printDatum(J, Key: "#local vars with source location" , Value: LocalVarWithSrcLoc.Value); |
1089 | printDatum(J, Key: "#local vars with type" , Value: LocalVarWithType.Value); |
1090 | printDatum(J, Key: "#local vars with binary location" , Value: LocalVarWithLoc.Value); |
1091 | |
1092 | // Print the debug section sizes. |
1093 | printSectionSizes(J, Sizes); |
1094 | |
1095 | // Print the location statistics for variables (includes local variables |
1096 | // and formal parameters). |
1097 | printDatum(J, Key: "#variables processed by location statistics" , |
1098 | Value: LocStats.NumVarParam.Value); |
1099 | printLocationStats(J, Key: "#variables" , LocationStats&: LocStats.VarParamLocStats); |
1100 | printLocationStats(J, Key: "#variables - entry values" , |
1101 | LocationStats&: LocStats.VarParamNonEntryValLocStats); |
1102 | |
1103 | // Print the location statistics for formal parameters. |
1104 | printDatum(J, Key: "#params processed by location statistics" , |
1105 | Value: LocStats.NumParam.Value); |
1106 | printLocationStats(J, Key: "#params" , LocationStats&: LocStats.ParamLocStats); |
1107 | printLocationStats(J, Key: "#params - entry values" , |
1108 | LocationStats&: LocStats.ParamNonEntryValLocStats); |
1109 | |
1110 | // Print the location statistics for local variables. |
1111 | printDatum(J, Key: "#local vars processed by location statistics" , |
1112 | Value: LocStats.NumVar.Value); |
1113 | printLocationStats(J, Key: "#local vars" , LocationStats&: LocStats.LocalVarLocStats); |
1114 | printLocationStats(J, Key: "#local vars - entry values" , |
1115 | LocationStats&: LocStats.LocalVarNonEntryValLocStats); |
1116 | |
1117 | // Print line statistics for the object file. |
1118 | printDatum(J, Key: "#bytes with line information" , Value: LnStats.NumBytes.Value); |
1119 | printDatum(J, Key: "#bytes with line-0 locations" , Value: LnStats.NumLineZeroBytes.Value); |
1120 | printDatum(J, Key: "#line entries" , Value: LnStats.NumEntries.Value); |
1121 | printDatum(J, Key: "#line entries (is_stmt)" , Value: LnStats.NumIsStmtEntries.Value); |
1122 | printDatum(J, Key: "#line entries (unique)" , Value: LnStats.NumUniqueEntries.Value); |
1123 | printDatum(J, Key: "#line entries (unique non-0)" , |
1124 | Value: LnStats.NumUniqueNonZeroEntries.Value); |
1125 | |
1126 | J.objectEnd(); |
1127 | OS << '\n'; |
1128 | LLVM_DEBUG( |
1129 | llvm::dbgs() << "Total Availability: " |
1130 | << (VarParamTotal.Value |
1131 | ? (int)std::round((VarParamWithLoc.Value * 100.0) / |
1132 | VarParamTotal.Value) |
1133 | : 0) |
1134 | << "%\n" ; |
1135 | llvm::dbgs() << "PC Ranges covered: " |
1136 | << (GlobalStats.ScopeBytes.Value |
1137 | ? (int)std::round( |
1138 | (GlobalStats.ScopeBytesCovered.Value * 100.0) / |
1139 | GlobalStats.ScopeBytes.Value) |
1140 | : 0) |
1141 | << "%\n" ); |
1142 | return true; |
1143 | } |
1144 | |