| //===-- Statistics.cpp - Debug Info quality metrics -----------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm-dwarfdump.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
| #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" |
| #include "llvm/DebugInfo/DWARF/DWARFExpression.h" |
| #include "llvm/Object/ObjectFile.h" |
| #include "llvm/Support/JSON.h" |
| |
| #define DEBUG_TYPE "dwarfdump" |
| using namespace llvm; |
| using namespace llvm::dwarfdump; |
| using namespace llvm::object; |
| |
| namespace { |
| /// This represents the number of categories of debug location coverage being |
| /// calculated. The first category is the number of variables with 0% location |
| /// coverage, but the last category is the number of variables with 100% |
| /// location coverage. |
| constexpr int NumOfCoverageCategories = 12; |
| |
| /// This is used for zero location coverage bucket. |
| constexpr unsigned ZeroCoverageBucket = 0; |
| |
| /// The UINT64_MAX is used as an indication of the overflow. |
| constexpr uint64_t OverflowValue = std::numeric_limits<uint64_t>::max(); |
| |
| /// This represents variables DIE offsets. |
| using AbstractOriginVarsTy = llvm::SmallVector<uint64_t>; |
| /// This maps function DIE offset to its variables. |
| using AbstractOriginVarsTyMap = llvm::DenseMap<uint64_t, AbstractOriginVarsTy>; |
| /// This represents function DIE offsets containing an abstract_origin. |
| using FunctionsWithAbstractOriginTy = llvm::SmallVector<uint64_t>; |
| |
| /// This represents a data type for the stats and it helps us to |
| /// detect an overflow. |
| /// NOTE: This can be implemented as a template if there is an another type |
| /// needing this. |
| struct SaturatingUINT64 { |
| /// Number that represents the stats. |
| uint64_t Value; |
| |
| SaturatingUINT64(uint64_t Value_) : Value(Value_) {} |
| |
| void operator++(int) { return *this += 1; } |
| void operator+=(uint64_t Value_) { |
| if (Value != OverflowValue) { |
| if (Value < OverflowValue - Value_) |
| Value += Value_; |
| else |
| Value = OverflowValue; |
| } |
| } |
| }; |
| |
| /// Utility struct to store the full location of a DIE - its CU and offset. |
| struct DIELocation { |
| DWARFUnit *DwUnit; |
| uint64_t DIEOffset; |
| DIELocation(DWARFUnit *_DwUnit, uint64_t _DIEOffset) |
| : DwUnit(_DwUnit), DIEOffset(_DIEOffset) {} |
| }; |
| /// This represents DWARF locations of CrossCU referencing DIEs. |
| using CrossCUReferencingDIELocationTy = llvm::SmallVector<DIELocation>; |
| |
| /// This maps function DIE offset to its DWARF CU. |
| using FunctionDIECUTyMap = llvm::DenseMap<uint64_t, DWARFUnit *>; |
| |
| /// Holds statistics for one function (or other entity that has a PC range and |
| /// contains variables, such as a compile unit). |
| struct PerFunctionStats { |
| /// Number of inlined instances of this function. |
| uint64_t NumFnInlined = 0; |
| /// Number of out-of-line instances of this function. |
| uint64_t NumFnOutOfLine = 0; |
| /// Number of inlined instances that have abstract origins. |
| uint64_t NumAbstractOrigins = 0; |
| /// Number of variables and parameters with location across all inlined |
| /// instances. |
| uint64_t TotalVarWithLoc = 0; |
| /// Number of constants with location across all inlined instances. |
| uint64_t ConstantMembers = 0; |
| /// Number of arificial variables, parameters or members across all instances. |
| uint64_t NumArtificial = 0; |
| /// List of all Variables and parameters in this function. |
| StringSet<> VarsInFunction; |
| /// Compile units also cover a PC range, but have this flag set to false. |
| bool IsFunction = false; |
| /// Function has source location information. |
| bool HasSourceLocation = false; |
| /// Number of function parameters. |
| uint64_t NumParams = 0; |
| /// Number of function parameters with source location. |
| uint64_t NumParamSourceLocations = 0; |
| /// Number of function parameters with type. |
| uint64_t NumParamTypes = 0; |
| /// Number of function parameters with a DW_AT_location. |
| uint64_t NumParamLocations = 0; |
| /// Number of local variables. |
| uint64_t NumLocalVars = 0; |
| /// Number of local variables with source location. |
| uint64_t NumLocalVarSourceLocations = 0; |
| /// Number of local variables with type. |
| uint64_t NumLocalVarTypes = 0; |
| /// Number of local variables with DW_AT_location. |
| uint64_t NumLocalVarLocations = 0; |
| }; |
| |
| /// Holds accumulated global statistics about DIEs. |
| struct GlobalStats { |
| /// Total number of PC range bytes covered by DW_AT_locations. |
| SaturatingUINT64 TotalBytesCovered = 0; |
| /// Total number of parent DIE PC range bytes covered by DW_AT_Locations. |
| SaturatingUINT64 ScopeBytesCovered = 0; |
| /// Total number of PC range bytes in each variable's enclosing scope. |
| SaturatingUINT64 ScopeBytes = 0; |
| /// Total number of PC range bytes covered by DW_AT_locations with |
| /// the debug entry values (DW_OP_entry_value). |
| SaturatingUINT64 ScopeEntryValueBytesCovered = 0; |
| /// Total number of PC range bytes covered by DW_AT_locations of |
| /// formal parameters. |
| SaturatingUINT64 ParamScopeBytesCovered = 0; |
| /// Total number of PC range bytes in each parameter's enclosing scope. |
| SaturatingUINT64 ParamScopeBytes = 0; |
| /// Total number of PC range bytes covered by DW_AT_locations with |
| /// the debug entry values (DW_OP_entry_value) (only for parameters). |
| SaturatingUINT64 ParamScopeEntryValueBytesCovered = 0; |
| /// Total number of PC range bytes covered by DW_AT_locations (only for local |
| /// variables). |
| SaturatingUINT64 LocalVarScopeBytesCovered = 0; |
| /// Total number of PC range bytes in each local variable's enclosing scope. |
| SaturatingUINT64 LocalVarScopeBytes = 0; |
| /// Total number of PC range bytes covered by DW_AT_locations with |
| /// the debug entry values (DW_OP_entry_value) (only for local variables). |
| SaturatingUINT64 LocalVarScopeEntryValueBytesCovered = 0; |
| /// Total number of call site entries (DW_AT_call_file & DW_AT_call_line). |
| SaturatingUINT64 CallSiteEntries = 0; |
| /// Total number of call site DIEs (DW_TAG_call_site). |
| SaturatingUINT64 CallSiteDIEs = 0; |
| /// Total number of call site parameter DIEs (DW_TAG_call_site_parameter). |
| SaturatingUINT64 CallSiteParamDIEs = 0; |
| /// Total byte size of concrete functions. This byte size includes |
| /// inline functions contained in the concrete functions. |
| SaturatingUINT64 FunctionSize = 0; |
| /// Total byte size of inlined functions. This is the total number of bytes |
| /// for the top inline functions within concrete functions. This can help |
| /// tune the inline settings when compiling to match user expectations. |
| SaturatingUINT64 InlineFunctionSize = 0; |
| }; |
| |
| /// Holds accumulated debug location statistics about local variables and |
| /// formal parameters. |
| struct LocationStats { |
| /// Map the scope coverage decile to the number of variables in the decile. |
| /// The first element of the array (at the index zero) represents the number |
| /// of variables with the no debug location at all, but the last element |
| /// in the vector represents the number of fully covered variables within |
| /// its scope. |
| std::vector<SaturatingUINT64> VarParamLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// Map non debug entry values coverage. |
| std::vector<SaturatingUINT64> VarParamNonEntryValLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// The debug location statistics for formal parameters. |
| std::vector<SaturatingUINT64> ParamLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// Map non debug entry values coverage for formal parameters. |
| std::vector<SaturatingUINT64> ParamNonEntryValLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// The debug location statistics for local variables. |
| std::vector<SaturatingUINT64> LocalVarLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// Map non debug entry values coverage for local variables. |
| std::vector<SaturatingUINT64> LocalVarNonEntryValLocStats{ |
| std::vector<SaturatingUINT64>(NumOfCoverageCategories, 0)}; |
| /// Total number of local variables and function parameters processed. |
| SaturatingUINT64 NumVarParam = 0; |
| /// Total number of formal parameters processed. |
| SaturatingUINT64 NumParam = 0; |
| /// Total number of local variables processed. |
| SaturatingUINT64 NumVar = 0; |
| }; |
| |
| /// Holds accumulated debug line statistics across all CUs. |
| struct LineStats { |
| SaturatingUINT64 NumBytes = 0; |
| SaturatingUINT64 NumLineZeroBytes = 0; |
| SaturatingUINT64 NumEntries = 0; |
| SaturatingUINT64 NumIsStmtEntries = 0; |
| SaturatingUINT64 NumUniqueEntries = 0; |
| SaturatingUINT64 NumUniqueNonZeroEntries = 0; |
| }; |
| } // namespace |
| |
| /// Collect debug location statistics for one DIE. |
| static void collectLocStats(uint64_t ScopeBytesCovered, uint64_t BytesInScope, |
| std::vector<SaturatingUINT64> &VarParamLocStats, |
| std::vector<SaturatingUINT64> &ParamLocStats, |
| std::vector<SaturatingUINT64> &LocalVarLocStats, |
| bool IsParam, bool IsLocalVar) { |
| auto getCoverageBucket = [ScopeBytesCovered, BytesInScope]() -> unsigned { |
| // No debug location at all for the variable. |
| if (ScopeBytesCovered == 0) |
| return 0; |
| // Fully covered variable within its scope. |
| if (ScopeBytesCovered >= BytesInScope) |
| return NumOfCoverageCategories - 1; |
| // Get covered range (e.g. 20%-29%). |
| unsigned LocBucket = 100 * (double)ScopeBytesCovered / BytesInScope; |
| LocBucket /= 10; |
| return LocBucket + 1; |
| }; |
| |
| unsigned CoverageBucket = getCoverageBucket(); |
| |
| VarParamLocStats[CoverageBucket].Value++; |
| if (IsParam) |
| ParamLocStats[CoverageBucket].Value++; |
| else if (IsLocalVar) |
| LocalVarLocStats[CoverageBucket].Value++; |
| } |
| |
| /// Construct an identifier for a given DIE from its Prefix, Name, DeclFileName |
| /// and DeclLine. The identifier aims to be unique for any unique entities, |
| /// but keeping the same among different instances of the same entity. |
| static std::string constructDieID(DWARFDie Die, |
| StringRef Prefix = StringRef()) { |
| std::string IDStr; |
| llvm::raw_string_ostream ID(IDStr); |
| ID << Prefix |
| << Die.getName(DINameKind::LinkageName); |
| |
| // Prefix + Name is enough for local variables and parameters. |
| if (!Prefix.empty() && Prefix != "g") |
| return IDStr; |
| |
| auto DeclFile = Die.findRecursively(dwarf::DW_AT_decl_file); |
| std::string File; |
| if (DeclFile) { |
| DWARFUnit *U = Die.getDwarfUnit(); |
| if (const auto *LT = U->getContext().getLineTableForUnit(U)) |
| if (LT->getFileNameByIndex( |
| dwarf::toUnsigned(DeclFile, 0), U->getCompilationDir(), |
| DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, File)) |
| File = std::string(sys::path::filename(File)); |
| } |
| ID << ":" << (File.empty() ? "/" : File); |
| ID << ":" |
| << dwarf::toUnsigned(Die.findRecursively(dwarf::DW_AT_decl_line), 0); |
| return IDStr; |
| } |
| |
| /// Return the number of bytes in the overlap of ranges A and B. |
| static uint64_t calculateOverlap(DWARFAddressRange A, DWARFAddressRange B) { |
| uint64_t Lower = std::max(A.LowPC, B.LowPC); |
| uint64_t Upper = std::min(A.HighPC, B.HighPC); |
| if (Lower >= Upper) |
| return 0; |
| return Upper - Lower; |
| } |
| |
| /// Collect debug info quality metrics for one DIE. |
| static void collectStatsForDie(DWARFDie Die, const std::string &FnPrefix, |
| const std::string &VarPrefix, |
| uint64_t BytesInScope, uint32_t InlineDepth, |
| StringMap<PerFunctionStats> &FnStatMap, |
| GlobalStats &GlobalStats, |
| LocationStats &LocStats, |
| AbstractOriginVarsTy *AbstractOriginVariables) { |
| const dwarf::Tag Tag = Die.getTag(); |
| // Skip CU node. |
| if (Tag == dwarf::DW_TAG_compile_unit) |
| return; |
| |
| bool HasLoc = false; |
| bool HasSrcLoc = false; |
| bool HasType = false; |
| uint64_t TotalBytesCovered = 0; |
| uint64_t ScopeBytesCovered = 0; |
| uint64_t BytesEntryValuesCovered = 0; |
| auto &FnStats = FnStatMap[FnPrefix]; |
| bool IsParam = Tag == dwarf::DW_TAG_formal_parameter; |
| bool IsLocalVar = Tag == dwarf::DW_TAG_variable; |
| bool IsConstantMember = Tag == dwarf::DW_TAG_member && |
| Die.find(dwarf::DW_AT_const_value); |
| |
| // For zero covered inlined variables the locstats will be |
| // calculated later. |
| bool DeferLocStats = false; |
| |
| if (Tag == dwarf::DW_TAG_call_site || Tag == dwarf::DW_TAG_GNU_call_site) { |
| GlobalStats.CallSiteDIEs++; |
| return; |
| } |
| |
| if (Tag == dwarf::DW_TAG_call_site_parameter || |
| Tag == dwarf::DW_TAG_GNU_call_site_parameter) { |
| GlobalStats.CallSiteParamDIEs++; |
| return; |
| } |
| |
| if (!IsParam && !IsLocalVar && !IsConstantMember) { |
| // Not a variable or constant member. |
| return; |
| } |
| |
| // Ignore declarations of global variables. |
| if (IsLocalVar && Die.find(dwarf::DW_AT_declaration)) |
| return; |
| |
| if (Die.findRecursively(dwarf::DW_AT_decl_file) && |
| Die.findRecursively(dwarf::DW_AT_decl_line)) |
| HasSrcLoc = true; |
| |
| if (Die.findRecursively(dwarf::DW_AT_type)) |
| HasType = true; |
| |
| if (Die.find(dwarf::DW_AT_abstract_origin)) { |
| if (Die.find(dwarf::DW_AT_location) || Die.find(dwarf::DW_AT_const_value)) { |
| if (AbstractOriginVariables) { |
| auto Offset = Die.find(dwarf::DW_AT_abstract_origin); |
| // Do not track this variable any more, since it has location |
| // coverage. |
| llvm::erase(*AbstractOriginVariables, (*Offset).getRawUValue()); |
| } |
| } else { |
| // The locstats will be handled at the end of |
| // the collectStatsRecursive(). |
| DeferLocStats = true; |
| } |
| } |
| |
| auto IsEntryValue = [&](ArrayRef<uint8_t> D) -> bool { |
| DWARFUnit *U = Die.getDwarfUnit(); |
| DataExtractor Data(toStringRef(D), |
| Die.getDwarfUnit()->getContext().isLittleEndian(), 0); |
| DWARFExpression Expression(Data, U->getAddressByteSize(), |
| U->getFormParams().Format); |
| // Consider the expression containing the DW_OP_entry_value as |
| // an entry value. |
| return llvm::any_of(Expression, [](const DWARFExpression::Operation &Op) { |
| return Op.getCode() == dwarf::DW_OP_entry_value || |
| Op.getCode() == dwarf::DW_OP_GNU_entry_value; |
| }); |
| }; |
| |
| if (Die.find(dwarf::DW_AT_const_value)) { |
| // This catches constant members *and* variables. |
| HasLoc = true; |
| ScopeBytesCovered = BytesInScope; |
| TotalBytesCovered = BytesInScope; |
| } else { |
| // Handle variables and function arguments. |
| Expected<std::vector<DWARFLocationExpression>> Loc = |
| Die.getLocations(dwarf::DW_AT_location); |
| if (!Loc) { |
| consumeError(Loc.takeError()); |
| } else { |
| HasLoc = true; |
| // Get PC coverage. |
| auto Default = find_if( |
| *Loc, [](const DWARFLocationExpression &L) { return !L.Range; }); |
| if (Default != Loc->end()) { |
| // Assume the entire range is covered by a single location. |
| ScopeBytesCovered = BytesInScope; |
| TotalBytesCovered = BytesInScope; |
| } else { |
| // Caller checks this Expected result already, it cannot fail. |
| auto ScopeRanges = cantFail(Die.getParent().getAddressRanges()); |
| for (auto Entry : *Loc) { |
| TotalBytesCovered += Entry.Range->HighPC - Entry.Range->LowPC; |
| uint64_t ScopeBytesCoveredByEntry = 0; |
| // Calculate how many bytes of the parent scope this entry covers. |
| // FIXME: In section 2.6.2 of the DWARFv5 spec it says that "The |
| // address ranges defined by the bounded location descriptions of a |
| // location list may overlap". So in theory a variable can have |
| // multiple simultaneous locations, which would make this calculation |
| // misleading because we will count the overlapped areas |
| // twice. However, clang does not currently emit DWARF like this. |
| for (DWARFAddressRange R : ScopeRanges) { |
| ScopeBytesCoveredByEntry += calculateOverlap(*Entry.Range, R); |
| } |
| ScopeBytesCovered += ScopeBytesCoveredByEntry; |
| if (IsEntryValue(Entry.Expr)) |
| BytesEntryValuesCovered += ScopeBytesCoveredByEntry; |
| } |
| } |
| } |
| } |
| |
| // Calculate the debug location statistics. |
| if (BytesInScope && !DeferLocStats) { |
| LocStats.NumVarParam.Value++; |
| if (IsParam) |
| LocStats.NumParam.Value++; |
| else if (IsLocalVar) |
| LocStats.NumVar.Value++; |
| |
| collectLocStats(ScopeBytesCovered, BytesInScope, LocStats.VarParamLocStats, |
| LocStats.ParamLocStats, LocStats.LocalVarLocStats, IsParam, |
| IsLocalVar); |
| // Non debug entry values coverage statistics. |
| collectLocStats(ScopeBytesCovered - BytesEntryValuesCovered, BytesInScope, |
| LocStats.VarParamNonEntryValLocStats, |
| LocStats.ParamNonEntryValLocStats, |
| LocStats.LocalVarNonEntryValLocStats, IsParam, IsLocalVar); |
| } |
| |
| // Collect PC range coverage data. |
| if (DWARFDie D = |
| Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin)) |
| Die = D; |
| |
| std::string VarID = constructDieID(Die, VarPrefix); |
| FnStats.VarsInFunction.insert(VarID); |
| |
| GlobalStats.TotalBytesCovered += TotalBytesCovered; |
| if (BytesInScope) { |
| GlobalStats.ScopeBytesCovered += ScopeBytesCovered; |
| GlobalStats.ScopeBytes += BytesInScope; |
| GlobalStats.ScopeEntryValueBytesCovered += BytesEntryValuesCovered; |
| if (IsParam) { |
| GlobalStats.ParamScopeBytesCovered += ScopeBytesCovered; |
| GlobalStats.ParamScopeBytes += BytesInScope; |
| GlobalStats.ParamScopeEntryValueBytesCovered += BytesEntryValuesCovered; |
| } else if (IsLocalVar) { |
| GlobalStats.LocalVarScopeBytesCovered += ScopeBytesCovered; |
| GlobalStats.LocalVarScopeBytes += BytesInScope; |
| GlobalStats.LocalVarScopeEntryValueBytesCovered += |
| BytesEntryValuesCovered; |
| } |
| assert(GlobalStats.ScopeBytesCovered.Value <= GlobalStats.ScopeBytes.Value); |
| } |
| |
| if (IsConstantMember) { |
| FnStats.ConstantMembers++; |
| return; |
| } |
| |
| FnStats.TotalVarWithLoc += (unsigned)HasLoc; |
| |
| if (Die.find(dwarf::DW_AT_artificial)) { |
| FnStats.NumArtificial++; |
| return; |
| } |
| |
| if (IsParam) { |
| FnStats.NumParams++; |
| if (HasType) |
| FnStats.NumParamTypes++; |
| if (HasSrcLoc) |
| FnStats.NumParamSourceLocations++; |
| if (HasLoc) |
| FnStats.NumParamLocations++; |
| } else if (IsLocalVar) { |
| FnStats.NumLocalVars++; |
| if (HasType) |
| FnStats.NumLocalVarTypes++; |
| if (HasSrcLoc) |
| FnStats.NumLocalVarSourceLocations++; |
| if (HasLoc) |
| FnStats.NumLocalVarLocations++; |
| } |
| } |
| |
| /// Recursively collect variables from subprogram with DW_AT_inline attribute. |
| static void collectAbstractOriginFnInfo( |
| DWARFDie Die, uint64_t SPOffset, |
| AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
| AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo) { |
| DWARFDie Child = Die.getFirstChild(); |
| while (Child) { |
| const dwarf::Tag ChildTag = Child.getTag(); |
| if (ChildTag == dwarf::DW_TAG_formal_parameter || |
| ChildTag == dwarf::DW_TAG_variable) { |
| GlobalAbstractOriginFnInfo[SPOffset].push_back(Child.getOffset()); |
| LocalAbstractOriginFnInfo[SPOffset].push_back(Child.getOffset()); |
| } else if (ChildTag == dwarf::DW_TAG_lexical_block) |
| collectAbstractOriginFnInfo(Child, SPOffset, GlobalAbstractOriginFnInfo, |
| LocalAbstractOriginFnInfo); |
| Child = Child.getSibling(); |
| } |
| } |
| |
| /// Recursively collect debug info quality metrics. |
| static void collectStatsRecursive( |
| DWARFDie Die, std::string FnPrefix, std::string VarPrefix, |
| uint64_t BytesInScope, uint32_t InlineDepth, |
| StringMap<PerFunctionStats> &FnStatMap, GlobalStats &GlobalStats, |
| LocationStats &LocStats, FunctionDIECUTyMap &AbstractOriginFnCUs, |
| AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
| AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo, |
| FunctionsWithAbstractOriginTy &FnsWithAbstractOriginToBeProcessed, |
| AbstractOriginVarsTy *AbstractOriginVarsPtr = nullptr) { |
| // Skip NULL nodes. |
| if (Die.isNULL()) |
| return; |
| |
| const dwarf::Tag Tag = Die.getTag(); |
| // Skip function types. |
| if (Tag == dwarf::DW_TAG_subroutine_type) |
| return; |
| |
| // Handle any kind of lexical scope. |
| const bool HasAbstractOrigin = |
| Die.find(dwarf::DW_AT_abstract_origin) != std::nullopt; |
| const bool IsFunction = Tag == dwarf::DW_TAG_subprogram; |
| const bool IsBlock = Tag == dwarf::DW_TAG_lexical_block; |
| const bool IsInlinedFunction = Tag == dwarf::DW_TAG_inlined_subroutine; |
| // We want to know how many variables (with abstract_origin) don't have |
| // location info. |
| const bool IsCandidateForZeroLocCovTracking = |
| (IsInlinedFunction || (IsFunction && HasAbstractOrigin)); |
| |
| AbstractOriginVarsTy AbstractOriginVars; |
| |
| // Get the vars of the inlined fn, so the locstats |
| // reports the missing vars (with coverage 0%). |
| if (IsCandidateForZeroLocCovTracking) { |
| auto OffsetFn = Die.find(dwarf::DW_AT_abstract_origin); |
| if (OffsetFn) { |
| uint64_t OffsetOfInlineFnCopy = (*OffsetFn).getRawUValue(); |
| if (LocalAbstractOriginFnInfo.count(OffsetOfInlineFnCopy)) { |
| AbstractOriginVars = LocalAbstractOriginFnInfo[OffsetOfInlineFnCopy]; |
| AbstractOriginVarsPtr = &AbstractOriginVars; |
| } else { |
| // This means that the DW_AT_inline fn copy is out of order |
| // or that the abstract_origin references another CU, |
| // so this abstract origin instance will be processed later. |
| FnsWithAbstractOriginToBeProcessed.push_back(Die.getOffset()); |
| AbstractOriginVarsPtr = nullptr; |
| } |
| } |
| } |
| |
| if (IsFunction || IsInlinedFunction || IsBlock) { |
| // Reset VarPrefix when entering a new function. |
| if (IsFunction || IsInlinedFunction) |
| VarPrefix = "v"; |
| |
| // Ignore forward declarations. |
| if (Die.find(dwarf::DW_AT_declaration)) |
| return; |
| |
| // Check for call sites. |
| if (Die.find(dwarf::DW_AT_call_file) && Die.find(dwarf::DW_AT_call_line)) |
| GlobalStats.CallSiteEntries++; |
| |
| // PC Ranges. |
| auto RangesOrError = Die.getAddressRanges(); |
| if (!RangesOrError) { |
| llvm::consumeError(RangesOrError.takeError()); |
| return; |
| } |
| |
| auto Ranges = RangesOrError.get(); |
| uint64_t BytesInThisScope = 0; |
| for (auto Range : Ranges) |
| BytesInThisScope += Range.HighPC - Range.LowPC; |
| |
| // Count the function. |
| if (!IsBlock) { |
| // Skip over abstract origins, but collect variables |
| // from it so it can be used for location statistics |
| // for inlined instancies. |
| if (Die.find(dwarf::DW_AT_inline)) { |
| uint64_t SPOffset = Die.getOffset(); |
| AbstractOriginFnCUs[SPOffset] = Die.getDwarfUnit(); |
| collectAbstractOriginFnInfo(Die, SPOffset, GlobalAbstractOriginFnInfo, |
| LocalAbstractOriginFnInfo); |
| return; |
| } |
| |
| std::string FnID = constructDieID(Die); |
| // We've seen an instance of this function. |
| auto &FnStats = FnStatMap[FnID]; |
| FnStats.IsFunction = true; |
| if (IsInlinedFunction) { |
| FnStats.NumFnInlined++; |
| if (Die.findRecursively(dwarf::DW_AT_abstract_origin)) |
| FnStats.NumAbstractOrigins++; |
| } else { |
| FnStats.NumFnOutOfLine++; |
| } |
| if (Die.findRecursively(dwarf::DW_AT_decl_file) && |
| Die.findRecursively(dwarf::DW_AT_decl_line)) |
| FnStats.HasSourceLocation = true; |
| // Update function prefix. |
| FnPrefix = FnID; |
| } |
| |
| if (BytesInThisScope) { |
| BytesInScope = BytesInThisScope; |
| if (IsFunction) |
| GlobalStats.FunctionSize += BytesInThisScope; |
| else if (IsInlinedFunction && InlineDepth == 0) |
| GlobalStats.InlineFunctionSize += BytesInThisScope; |
| } |
| } else { |
| // Not a scope, visit the Die itself. It could be a variable. |
| collectStatsForDie(Die, FnPrefix, VarPrefix, BytesInScope, InlineDepth, |
| FnStatMap, GlobalStats, LocStats, AbstractOriginVarsPtr); |
| } |
| |
| // Set InlineDepth correctly for child recursion |
| if (IsFunction) |
| InlineDepth = 0; |
| else if (IsInlinedFunction) |
| ++InlineDepth; |
| |
| // Traverse children. |
| unsigned LexicalBlockIndex = 0; |
| unsigned FormalParameterIndex = 0; |
| DWARFDie Child = Die.getFirstChild(); |
| while (Child) { |
| std::string ChildVarPrefix = VarPrefix; |
| if (Child.getTag() == dwarf::DW_TAG_lexical_block) |
| ChildVarPrefix += toHex(LexicalBlockIndex++) + '.'; |
| if (Child.getTag() == dwarf::DW_TAG_formal_parameter) |
| ChildVarPrefix += 'p' + toHex(FormalParameterIndex++) + '.'; |
| |
| collectStatsRecursive( |
| Child, FnPrefix, ChildVarPrefix, BytesInScope, InlineDepth, FnStatMap, |
| GlobalStats, LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
| LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed, |
| AbstractOriginVarsPtr); |
| Child = Child.getSibling(); |
| } |
| |
| if (!IsCandidateForZeroLocCovTracking) |
| return; |
| |
| // After we have processed all vars of the inlined function (or function with |
| // an abstract_origin), we want to know how many variables have no location. |
| for (auto Offset : AbstractOriginVars) { |
| LocStats.NumVarParam++; |
| LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
| auto FnDie = Die.getDwarfUnit()->getDIEForOffset(Offset); |
| if (!FnDie) |
| continue; |
| auto Tag = FnDie.getTag(); |
| if (Tag == dwarf::DW_TAG_formal_parameter) { |
| LocStats.NumParam++; |
| LocStats.ParamLocStats[ZeroCoverageBucket]++; |
| } else if (Tag == dwarf::DW_TAG_variable) { |
| LocStats.NumVar++; |
| LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
| } |
| } |
| } |
| |
| /// Print human-readable output. |
| /// \{ |
| static void printDatum(json::OStream &J, const char *Key, json::Value Value) { |
| if (Value == OverflowValue) |
| J.attribute(Key, "overflowed"); |
| else |
| J.attribute(Key, Value); |
| |
| LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n'); |
| } |
| |
| static void printLocationStats(json::OStream &J, const char *Key, |
| std::vector<SaturatingUINT64> &LocationStats) { |
| if (LocationStats[0].Value == OverflowValue) |
| J.attribute((Twine(Key) + |
| " with (0%,10%) of parent scope covered by DW_AT_location") |
| .str(), |
| "overflowed"); |
| else |
| J.attribute( |
| (Twine(Key) + " with 0% of parent scope covered by DW_AT_location") |
| .str(), |
| LocationStats[0].Value); |
| LLVM_DEBUG( |
| llvm::dbgs() << Key |
| << " with 0% of parent scope covered by DW_AT_location: \\" |
| << LocationStats[0].Value << '\n'); |
| |
| if (LocationStats[1].Value == OverflowValue) |
| J.attribute((Twine(Key) + |
| " with (0%,10%) of parent scope covered by DW_AT_location") |
| .str(), |
| "overflowed"); |
| else |
| J.attribute((Twine(Key) + |
| " with (0%,10%) of parent scope covered by DW_AT_location") |
| .str(), |
| LocationStats[1].Value); |
| LLVM_DEBUG(llvm::dbgs() |
| << Key |
| << " with (0%,10%) of parent scope covered by DW_AT_location: " |
| << LocationStats[1].Value << '\n'); |
| |
| for (unsigned i = 2; i < NumOfCoverageCategories - 1; ++i) { |
| if (LocationStats[i].Value == OverflowValue) |
| J.attribute((Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
| Twine(i * 10) + |
| "%) of parent scope covered by DW_AT_location") |
| .str(), |
| "overflowed"); |
| else |
| J.attribute((Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," + |
| Twine(i * 10) + |
| "%) of parent scope covered by DW_AT_location") |
| .str(), |
| LocationStats[i].Value); |
| LLVM_DEBUG(llvm::dbgs() |
| << Key << " with [" << (i - 1) * 10 << "%," << i * 10 |
| << "%) of parent scope covered by DW_AT_location: " |
| << LocationStats[i].Value); |
| } |
| if (LocationStats[NumOfCoverageCategories - 1].Value == OverflowValue) |
| J.attribute( |
| (Twine(Key) + " with 100% of parent scope covered by DW_AT_location") |
| .str(), |
| "overflowed"); |
| else |
| J.attribute( |
| (Twine(Key) + " with 100% of parent scope covered by DW_AT_location") |
| .str(), |
| LocationStats[NumOfCoverageCategories - 1].Value); |
| LLVM_DEBUG( |
| llvm::dbgs() << Key |
| << " with 100% of parent scope covered by DW_AT_location: " |
| << LocationStats[NumOfCoverageCategories - 1].Value); |
| } |
| |
| static void printSectionSizes(json::OStream &J, const SectionSizes &Sizes) { |
| for (const auto &It : Sizes.DebugSectionSizes) |
| J.attribute((Twine("#bytes in ") + It.first).str(), int64_t(It.second)); |
| } |
| |
| /// Stop tracking variables that contain abstract_origin with a location. |
| /// This is used for out-of-order DW_AT_inline subprograms only. |
| static void updateVarsWithAbstractOriginLocCovInfo( |
| DWARFDie FnDieWithAbstractOrigin, |
| AbstractOriginVarsTy &AbstractOriginVars) { |
| DWARFDie Child = FnDieWithAbstractOrigin.getFirstChild(); |
| while (Child) { |
| const dwarf::Tag ChildTag = Child.getTag(); |
| if ((ChildTag == dwarf::DW_TAG_formal_parameter || |
| ChildTag == dwarf::DW_TAG_variable) && |
| (Child.find(dwarf::DW_AT_location) || |
| Child.find(dwarf::DW_AT_const_value))) { |
| auto OffsetVar = Child.find(dwarf::DW_AT_abstract_origin); |
| if (OffsetVar) |
| llvm::erase(AbstractOriginVars, (*OffsetVar).getRawUValue()); |
| } else if (ChildTag == dwarf::DW_TAG_lexical_block) |
| updateVarsWithAbstractOriginLocCovInfo(Child, AbstractOriginVars); |
| Child = Child.getSibling(); |
| } |
| } |
| |
| /// Collect zero location coverage for inlined variables which refer to |
| /// a DW_AT_inline copy of subprogram that is out of order in the DWARF. |
| /// Also cover the variables of a concrete function (represented with |
| /// the DW_TAG_subprogram) with an abstract_origin attribute. |
| static void collectZeroLocCovForVarsWithAbstractOrigin( |
| DWARFUnit *DwUnit, GlobalStats &GlobalStats, LocationStats &LocStats, |
| AbstractOriginVarsTyMap &LocalAbstractOriginFnInfo, |
| FunctionsWithAbstractOriginTy &FnsWithAbstractOriginToBeProcessed) { |
| // The next variable is used to filter out functions that have been processed, |
| // leaving FnsWithAbstractOriginToBeProcessed with just CrossCU references. |
| FunctionsWithAbstractOriginTy ProcessedFns; |
| for (auto FnOffset : FnsWithAbstractOriginToBeProcessed) { |
| DWARFDie FnDieWithAbstractOrigin = DwUnit->getDIEForOffset(FnOffset); |
| auto FnCopy = FnDieWithAbstractOrigin.find(dwarf::DW_AT_abstract_origin); |
| AbstractOriginVarsTy AbstractOriginVars; |
| if (!FnCopy) |
| continue; |
| uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
| // If there is no entry within LocalAbstractOriginFnInfo for the given |
| // FnCopyRawUValue, function isn't out-of-order in DWARF. Rather, we have |
| // CrossCU referencing. |
| if (!LocalAbstractOriginFnInfo.count(FnCopyRawUValue)) |
| continue; |
| AbstractOriginVars = LocalAbstractOriginFnInfo[FnCopyRawUValue]; |
| updateVarsWithAbstractOriginLocCovInfo(FnDieWithAbstractOrigin, |
| AbstractOriginVars); |
| |
| for (auto Offset : AbstractOriginVars) { |
| LocStats.NumVarParam++; |
| LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
| auto Tag = DwUnit->getDIEForOffset(Offset).getTag(); |
| if (Tag == dwarf::DW_TAG_formal_parameter) { |
| LocStats.NumParam++; |
| LocStats.ParamLocStats[ZeroCoverageBucket]++; |
| } else if (Tag == dwarf::DW_TAG_variable) { |
| LocStats.NumVar++; |
| LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
| } |
| } |
| ProcessedFns.push_back(FnOffset); |
| } |
| for (auto ProcessedFn : ProcessedFns) |
| llvm::erase(FnsWithAbstractOriginToBeProcessed, ProcessedFn); |
| } |
| |
| /// Collect zero location coverage for inlined variables which refer to |
| /// a DW_AT_inline copy of subprogram that is in a different CU. |
| static void collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
| LocationStats &LocStats, FunctionDIECUTyMap AbstractOriginFnCUs, |
| AbstractOriginVarsTyMap &GlobalAbstractOriginFnInfo, |
| CrossCUReferencingDIELocationTy &CrossCUReferencesToBeResolved) { |
| for (const auto &CrossCUReferenceToBeResolved : |
| CrossCUReferencesToBeResolved) { |
| DWARFUnit *DwUnit = CrossCUReferenceToBeResolved.DwUnit; |
| DWARFDie FnDIEWithCrossCUReferencing = |
| DwUnit->getDIEForOffset(CrossCUReferenceToBeResolved.DIEOffset); |
| auto FnCopy = |
| FnDIEWithCrossCUReferencing.find(dwarf::DW_AT_abstract_origin); |
| if (!FnCopy) |
| continue; |
| uint64_t FnCopyRawUValue = (*FnCopy).getRawUValue(); |
| AbstractOriginVarsTy AbstractOriginVars = |
| GlobalAbstractOriginFnInfo[FnCopyRawUValue]; |
| updateVarsWithAbstractOriginLocCovInfo(FnDIEWithCrossCUReferencing, |
| AbstractOriginVars); |
| for (auto Offset : AbstractOriginVars) { |
| LocStats.NumVarParam++; |
| LocStats.VarParamLocStats[ZeroCoverageBucket]++; |
| auto Tag = (AbstractOriginFnCUs[FnCopyRawUValue]) |
| ->getDIEForOffset(Offset) |
| .getTag(); |
| if (Tag == dwarf::DW_TAG_formal_parameter) { |
| LocStats.NumParam++; |
| LocStats.ParamLocStats[ZeroCoverageBucket]++; |
| } else if (Tag == dwarf::DW_TAG_variable) { |
| LocStats.NumVar++; |
| LocStats.LocalVarLocStats[ZeroCoverageBucket]++; |
| } |
| } |
| } |
| } |
| |
| /// \} |
| |
| /// Collect debug info quality metrics for an entire DIContext. |
| /// |
| /// Do the impossible and reduce the quality of the debug info down to a few |
| /// numbers. The idea is to condense the data into numbers that can be tracked |
| /// over time to identify trends in newer compiler versions and gauge the effect |
| /// of particular optimizations. The raw numbers themselves are not particularly |
| /// useful, only the delta between compiling the same program with different |
| /// compilers is. |
| bool dwarfdump::collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx, |
| const Twine &Filename, |
| raw_ostream &OS) { |
| StringRef FormatName = Obj.getFileFormatName(); |
| GlobalStats GlobalStats; |
| LocationStats LocStats; |
| LineStats LnStats; |
| StringMap<PerFunctionStats> Statistics; |
| // This variable holds variable information for functions with |
| // abstract_origin globally, across all CUs. |
| AbstractOriginVarsTyMap GlobalAbstractOriginFnInfo; |
| // This variable holds information about the CU of a function with |
| // abstract_origin. |
| FunctionDIECUTyMap AbstractOriginFnCUs; |
| CrossCUReferencingDIELocationTy CrossCUReferencesToBeResolved; |
| // Tuple representing a single source code position in the line table. Fields |
| // are respectively: Line, Col, File, where 'File' is an index into the Files |
| // vector below. |
| using LineTuple = std::tuple<uint32_t, uint16_t, uint16_t>; |
| SmallVector<std::string> Files; |
| DenseSet<LineTuple> UniqueLines; |
| DenseSet<LineTuple> UniqueNonZeroLines; |
| |
| for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units()) { |
| if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(false)) { |
| // This variable holds variable information for functions with |
| // abstract_origin, but just for the current CU. |
| AbstractOriginVarsTyMap LocalAbstractOriginFnInfo; |
| FunctionsWithAbstractOriginTy FnsWithAbstractOriginToBeProcessed; |
| |
| collectStatsRecursive( |
| CUDie, "/", "g", 0, 0, Statistics, GlobalStats, LocStats, |
| AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
| LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
| |
| // collectZeroLocCovForVarsWithAbstractOrigin will filter out all |
| // out-of-order DWARF functions that have been processed within it, |
| // leaving FnsWithAbstractOriginToBeProcessed with only CrossCU |
| // references. |
| collectZeroLocCovForVarsWithAbstractOrigin( |
| CUDie.getDwarfUnit(), GlobalStats, LocStats, |
| LocalAbstractOriginFnInfo, FnsWithAbstractOriginToBeProcessed); |
| |
| // Collect all CrossCU references into CrossCUReferencesToBeResolved. |
| for (auto CrossCUReferencingDIEOffset : |
| FnsWithAbstractOriginToBeProcessed) |
| CrossCUReferencesToBeResolved.push_back( |
| DIELocation(CUDie.getDwarfUnit(), CrossCUReferencingDIEOffset)); |
| } |
| const auto *LineTable = DICtx.getLineTableForUnit(CU.get()); |
| std::optional<uint64_t> LastFileIdxOpt; |
| if (LineTable) |
| LastFileIdxOpt = LineTable->getLastValidFileIndex(); |
| if (LastFileIdxOpt) { |
| // Each CU has its own file index; in order to track unique line entries |
| // across CUs, we therefore need to map each CU file index to a global |
| // file index, which we store here. |
| DenseMap<uint64_t, uint16_t> CUFileMapping; |
| for (uint64_t FileIdx = 0; FileIdx <= *LastFileIdxOpt; ++FileIdx) { |
| std::string File; |
| if (LineTable->getFileNameByIndex( |
| FileIdx, CU->getCompilationDir(), |
| DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, |
| File)) { |
| auto ExistingFile = llvm::find(Files, File); |
| if (ExistingFile != Files.end()) { |
| CUFileMapping[FileIdx] = std::distance(Files.begin(), ExistingFile); |
| } else { |
| CUFileMapping[FileIdx] = Files.size(); |
| Files.push_back(File); |
| } |
| } |
| } |
| for (const auto &Seq : LineTable->Sequences) { |
| LnStats.NumBytes += Seq.HighPC - Seq.LowPC; |
| // Ignore the `end_sequence` entry, since it's not interesting for us. |
| LnStats.NumEntries += Seq.LastRowIndex - Seq.FirstRowIndex - 1; |
| for (size_t RowIdx = Seq.FirstRowIndex; RowIdx < Seq.LastRowIndex - 1; |
| ++RowIdx) { |
| auto Entry = LineTable->Rows[RowIdx]; |
| if (Entry.IsStmt) |
| LnStats.NumIsStmtEntries += 1; |
| assert(CUFileMapping.contains(Entry.File) && |
| "Should have been collected earlier!"); |
| uint16_t MappedFile = CUFileMapping[Entry.File]; |
| UniqueLines.insert({Entry.Line, Entry.Column, MappedFile}); |
| if (Entry.Line != 0) { |
| UniqueNonZeroLines.insert({Entry.Line, Entry.Column, MappedFile}); |
| } else { |
| auto EntryStartAddress = Entry.Address.Address; |
| auto EntryEndAddress = LineTable->Rows[RowIdx + 1].Address.Address; |
| LnStats.NumLineZeroBytes += EntryEndAddress - EntryStartAddress; |
| } |
| } |
| } |
| } |
| } |
| |
| LnStats.NumUniqueEntries = UniqueLines.size(); |
| LnStats.NumUniqueNonZeroEntries = UniqueNonZeroLines.size(); |
| |
| /// Resolve CrossCU references. |
| collectZeroLocCovForVarsWithCrossCUReferencingAbstractOrigin( |
| LocStats, AbstractOriginFnCUs, GlobalAbstractOriginFnInfo, |
| CrossCUReferencesToBeResolved); |
| |
| /// Collect the sizes of debug sections. |
| SectionSizes Sizes; |
| calculateSectionSizes(Obj, Sizes, Filename); |
| |
| /// The version number should be increased every time the algorithm is changed |
| /// (including bug fixes). New metrics may be added without increasing the |
| /// version. |
| unsigned Version = 9; |
| SaturatingUINT64 VarParamTotal = 0; |
| SaturatingUINT64 VarParamUnique = 0; |
| SaturatingUINT64 VarParamWithLoc = 0; |
| SaturatingUINT64 NumFunctions = 0; |
| SaturatingUINT64 NumInlinedFunctions = 0; |
| SaturatingUINT64 NumFuncsWithSrcLoc = 0; |
| SaturatingUINT64 NumAbstractOrigins = 0; |
| SaturatingUINT64 ParamTotal = 0; |
| SaturatingUINT64 ParamWithType = 0; |
| SaturatingUINT64 ParamWithLoc = 0; |
| SaturatingUINT64 ParamWithSrcLoc = 0; |
| SaturatingUINT64 LocalVarTotal = 0; |
| SaturatingUINT64 LocalVarWithType = 0; |
| SaturatingUINT64 LocalVarWithSrcLoc = 0; |
| SaturatingUINT64 LocalVarWithLoc = 0; |
| for (auto &Entry : Statistics) { |
| PerFunctionStats &Stats = Entry.getValue(); |
| uint64_t TotalVars = Stats.VarsInFunction.size() * |
| (Stats.NumFnInlined + Stats.NumFnOutOfLine); |
| // Count variables in global scope. |
| if (!Stats.IsFunction) |
| TotalVars = |
| Stats.NumLocalVars + Stats.ConstantMembers + Stats.NumArtificial; |
| uint64_t Constants = Stats.ConstantMembers; |
| VarParamWithLoc += Stats.TotalVarWithLoc + Constants; |
| VarParamTotal += TotalVars; |
| VarParamUnique += Stats.VarsInFunction.size(); |
| LLVM_DEBUG(for (auto &V |
| : Stats.VarsInFunction) llvm::dbgs() |
| << Entry.getKey() << ": " << V.getKey() << "\n"); |
| NumFunctions += Stats.IsFunction; |
| NumFuncsWithSrcLoc += Stats.HasSourceLocation; |
| NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined; |
| NumAbstractOrigins += Stats.IsFunction * Stats.NumAbstractOrigins; |
| ParamTotal += Stats.NumParams; |
| ParamWithType += Stats.NumParamTypes; |
| ParamWithLoc += Stats.NumParamLocations; |
| ParamWithSrcLoc += Stats.NumParamSourceLocations; |
| LocalVarTotal += Stats.NumLocalVars; |
| LocalVarWithType += Stats.NumLocalVarTypes; |
| LocalVarWithLoc += Stats.NumLocalVarLocations; |
| LocalVarWithSrcLoc += Stats.NumLocalVarSourceLocations; |
| } |
| |
| // Print summary. |
| OS.SetBufferSize(1024); |
| json::OStream J(OS, 2); |
| J.objectBegin(); |
| J.attribute("version", Version); |
| LLVM_DEBUG(llvm::dbgs() << "Variable location quality metrics\n"; |
| llvm::dbgs() << "---------------------------------\n"); |
| |
| printDatum(J, "file", Filename.str()); |
| printDatum(J, "format", FormatName); |
| |
| printDatum(J, "#functions", NumFunctions.Value); |
| printDatum(J, "#functions with location", NumFuncsWithSrcLoc.Value); |
| printDatum(J, "#inlined functions", NumInlinedFunctions.Value); |
| printDatum(J, "#inlined functions with abstract origins", |
| NumAbstractOrigins.Value); |
| |
| // This includes local variables and formal parameters. |
| printDatum(J, "#unique source variables", VarParamUnique.Value); |
| printDatum(J, "#source variables", VarParamTotal.Value); |
| printDatum(J, "#source variables with location", VarParamWithLoc.Value); |
| |
| printDatum(J, "#call site entries", GlobalStats.CallSiteEntries.Value); |
| printDatum(J, "#call site DIEs", GlobalStats.CallSiteDIEs.Value); |
| printDatum(J, "#call site parameter DIEs", |
| GlobalStats.CallSiteParamDIEs.Value); |
| |
| printDatum(J, "sum_all_variables(#bytes in parent scope)", |
| GlobalStats.ScopeBytes.Value); |
| printDatum(J, |
| "sum_all_variables(#bytes in any scope covered by DW_AT_location)", |
| GlobalStats.TotalBytesCovered.Value); |
| printDatum(J, |
| "sum_all_variables(#bytes in parent scope covered by " |
| "DW_AT_location)", |
| GlobalStats.ScopeBytesCovered.Value); |
| printDatum(J, |
| "sum_all_variables(#bytes in parent scope covered by " |
| "DW_OP_entry_value)", |
| GlobalStats.ScopeEntryValueBytesCovered.Value); |
| |
| printDatum(J, "sum_all_params(#bytes in parent scope)", |
| GlobalStats.ParamScopeBytes.Value); |
| printDatum(J, |
| "sum_all_params(#bytes in parent scope covered by DW_AT_location)", |
| GlobalStats.ParamScopeBytesCovered.Value); |
| printDatum(J, |
| "sum_all_params(#bytes in parent scope covered by " |
| "DW_OP_entry_value)", |
| GlobalStats.ParamScopeEntryValueBytesCovered.Value); |
| |
| printDatum(J, "sum_all_local_vars(#bytes in parent scope)", |
| GlobalStats.LocalVarScopeBytes.Value); |
| printDatum(J, |
| "sum_all_local_vars(#bytes in parent scope covered by " |
| "DW_AT_location)", |
| GlobalStats.LocalVarScopeBytesCovered.Value); |
| printDatum(J, |
| "sum_all_local_vars(#bytes in parent scope covered by " |
| "DW_OP_entry_value)", |
| GlobalStats.LocalVarScopeEntryValueBytesCovered.Value); |
| |
| printDatum(J, "#bytes within functions", GlobalStats.FunctionSize.Value); |
| printDatum(J, "#bytes within inlined functions", |
| GlobalStats.InlineFunctionSize.Value); |
| |
| // Print the summary for formal parameters. |
| printDatum(J, "#params", ParamTotal.Value); |
| printDatum(J, "#params with source location", ParamWithSrcLoc.Value); |
| printDatum(J, "#params with type", ParamWithType.Value); |
| printDatum(J, "#params with binary location", ParamWithLoc.Value); |
| |
| // Print the summary for local variables. |
| printDatum(J, "#local vars", LocalVarTotal.Value); |
| printDatum(J, "#local vars with source location", LocalVarWithSrcLoc.Value); |
| printDatum(J, "#local vars with type", LocalVarWithType.Value); |
| printDatum(J, "#local vars with binary location", LocalVarWithLoc.Value); |
| |
| // Print the debug section sizes. |
| printSectionSizes(J, Sizes); |
| |
| // Print the location statistics for variables (includes local variables |
| // and formal parameters). |
| printDatum(J, "#variables processed by location statistics", |
| LocStats.NumVarParam.Value); |
| printLocationStats(J, "#variables", LocStats.VarParamLocStats); |
| printLocationStats(J, "#variables - entry values", |
| LocStats.VarParamNonEntryValLocStats); |
| |
| // Print the location statistics for formal parameters. |
| printDatum(J, "#params processed by location statistics", |
| LocStats.NumParam.Value); |
| printLocationStats(J, "#params", LocStats.ParamLocStats); |
| printLocationStats(J, "#params - entry values", |
| LocStats.ParamNonEntryValLocStats); |
| |
| // Print the location statistics for local variables. |
| printDatum(J, "#local vars processed by location statistics", |
| LocStats.NumVar.Value); |
| printLocationStats(J, "#local vars", LocStats.LocalVarLocStats); |
| printLocationStats(J, "#local vars - entry values", |
| LocStats.LocalVarNonEntryValLocStats); |
| |
| // Print line statistics for the object file. |
| printDatum(J, "#bytes with line information", LnStats.NumBytes.Value); |
| printDatum(J, "#bytes with line-0 locations", LnStats.NumLineZeroBytes.Value); |
| printDatum(J, "#line entries", LnStats.NumEntries.Value); |
| printDatum(J, "#line entries (is_stmt)", LnStats.NumIsStmtEntries.Value); |
| printDatum(J, "#line entries (unique)", LnStats.NumUniqueEntries.Value); |
| printDatum(J, "#line entries (unique non-0)", |
| LnStats.NumUniqueNonZeroEntries.Value); |
| |
| J.objectEnd(); |
| OS << '\n'; |
| LLVM_DEBUG( |
| llvm::dbgs() << "Total Availability: " |
| << (VarParamTotal.Value |
| ? (int)std::round((VarParamWithLoc.Value * 100.0) / |
| VarParamTotal.Value) |
| : 0) |
| << "%\n"; |
| llvm::dbgs() << "PC Ranges covered: " |
| << (GlobalStats.ScopeBytes.Value |
| ? (int)std::round( |
| (GlobalStats.ScopeBytesCovered.Value * 100.0) / |
| GlobalStats.ScopeBytes.Value) |
| : 0) |
| << "%\n"); |
| return true; |
| } |