mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 12:06:06 +00:00

I'm planning to remove StringRef::equals in favor of StringRef::operator==. - StringRef::operator==/!= outnumber StringRef::equals by a factor of 70 under llvm/ in terms of their usage. - The elimination of StringRef::equals brings StringRef closer to std::string_view, which has operator== but not equals. - S == "foo" is more readable than S.equals("foo"), especially for !Long.Expression.equals("str") vs Long.Expression != "str".
1064 lines
43 KiB
C++
1064 lines
43 KiB
C++
//===-- 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/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;
|
|
};
|
|
} // 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 ID.str();
|
|
|
|
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 ID.str();
|
|
}
|
|
|
|
/// 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;
|
|
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;
|
|
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));
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
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;
|
|
}
|