mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-24 03:33:20 +01:00
6420954176
Fixes PR46575. Bump statistics version to 6. Without this patch, for a variable described with a location list the stat 'sum_all_variables(#bytes in parent scope covered by DW_AT_location)' is calculated by summing all bytes covered by the location ranges in the list and capping the result to the number of bytes in the parent scope. With the patch, only bytes which overlap with the parent DIE scope address ranges contribute to the stat. A new stat 'sum_all_variables(#bytes in any scope covered by DW_AT_location)' has been added which displays the total bytes covered when ignoring scopes.
729 lines
29 KiB
C++
729 lines
29 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/StringExtras.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/DebugInfo/DIContext.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.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;
|
|
|
|
/// 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;
|
|
|
|
namespace {
|
|
/// 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.
|
|
unsigned NumFnInlined = 0;
|
|
/// Number of out-of-line instances of this function.
|
|
unsigned NumFnOutOfLine = 0;
|
|
/// Number of inlined instances that have abstract origins.
|
|
unsigned NumAbstractOrigins = 0;
|
|
/// Number of variables and parameters with location across all inlined
|
|
/// instances.
|
|
unsigned TotalVarWithLoc = 0;
|
|
/// Number of constants with location across all inlined instances.
|
|
unsigned ConstantMembers = 0;
|
|
/// Number of arificial variables, parameters or members across all instances.
|
|
unsigned 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.
|
|
unsigned NumParams = 0;
|
|
/// Number of function parameters with source location.
|
|
unsigned NumParamSourceLocations = 0;
|
|
/// Number of function parameters with type.
|
|
unsigned NumParamTypes = 0;
|
|
/// Number of function parameters with a DW_AT_location.
|
|
unsigned NumParamLocations = 0;
|
|
/// Number of local variables.
|
|
unsigned NumLocalVars = 0;
|
|
/// Number of local variables with source location.
|
|
unsigned NumLocalVarSourceLocations = 0;
|
|
/// Number of local variables with type.
|
|
unsigned NumLocalVarTypes = 0;
|
|
/// Number of local variables with DW_AT_location.
|
|
unsigned NumLocalVarLocations = 0;
|
|
};
|
|
|
|
/// Holds accumulated global statistics about DIEs.
|
|
struct GlobalStats {
|
|
/// Total number of PC range bytes covered by DW_AT_locations.
|
|
unsigned TotalBytesCovered = 0;
|
|
/// Total number of parent DIE PC range bytes covered by DW_AT_Locations.
|
|
unsigned ScopeBytesCovered = 0;
|
|
/// Total number of PC range bytes in each variable's enclosing scope.
|
|
unsigned ScopeBytes = 0;
|
|
/// Total number of PC range bytes covered by DW_AT_locations with
|
|
/// the debug entry values (DW_OP_entry_value).
|
|
unsigned ScopeEntryValueBytesCovered = 0;
|
|
/// Total number of PC range bytes covered by DW_AT_locations of
|
|
/// formal parameters.
|
|
unsigned ParamScopeBytesCovered = 0;
|
|
/// Total number of PC range bytes in each parameter's enclosing scope.
|
|
unsigned 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).
|
|
unsigned ParamScopeEntryValueBytesCovered = 0;
|
|
/// Total number of PC range bytes covered by DW_AT_locations (only for local
|
|
/// variables).
|
|
unsigned LocalVarScopeBytesCovered = 0;
|
|
/// Total number of PC range bytes in each local variable's enclosing scope.
|
|
unsigned 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).
|
|
unsigned LocalVarScopeEntryValueBytesCovered = 0;
|
|
/// Total number of call site entries (DW_AT_call_file & DW_AT_call_line).
|
|
unsigned CallSiteEntries = 0;
|
|
/// Total number of call site DIEs (DW_TAG_call_site).
|
|
unsigned CallSiteDIEs = 0;
|
|
/// Total number of call site parameter DIEs (DW_TAG_call_site_parameter).
|
|
unsigned CallSiteParamDIEs = 0;
|
|
/// Total byte size of concrete functions. This byte size includes
|
|
/// inline functions contained in the concrete functions.
|
|
unsigned 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.
|
|
unsigned 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<unsigned> VarParamLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// Map non debug entry values coverage.
|
|
std::vector<unsigned> VarParamNonEntryValLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// The debug location statistics for formal parameters.
|
|
std::vector<unsigned> ParamLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// Map non debug entry values coverage for formal parameters.
|
|
std::vector<unsigned> ParamNonEntryValLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// The debug location statistics for local variables.
|
|
std::vector<unsigned> LocalVarLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// Map non debug entry values coverage for local variables.
|
|
std::vector<unsigned> LocalVarNonEntryValLocStats{
|
|
std::vector<unsigned>(NumOfCoverageCategories, 0)};
|
|
/// Total number of local variables and function parameters processed.
|
|
unsigned NumVarParam = 0;
|
|
/// Total number of formal parameters processed.
|
|
unsigned NumParam = 0;
|
|
/// Total number of local variables processed.
|
|
unsigned NumVar = 0;
|
|
};
|
|
} // namespace
|
|
|
|
/// Collect debug location statistics for one DIE.
|
|
static void collectLocStats(uint64_t ScopeBytesCovered, uint64_t BytesInScope,
|
|
std::vector<unsigned> &VarParamLocStats,
|
|
std::vector<unsigned> &ParamLocStats,
|
|
std::vector<unsigned> &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]++;
|
|
if (IsParam)
|
|
ParamLocStats[CoverageBucket]++;
|
|
else if (IsLocalVar)
|
|
LocalVarLocStats[CoverageBucket]++;
|
|
}
|
|
/// 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.equals("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, std::string FnPrefix,
|
|
std::string VarPrefix, uint64_t BytesInScope,
|
|
uint32_t InlineDepth,
|
|
StringMap<PerFunctionStats> &FnStatMap,
|
|
GlobalStats &GlobalStats,
|
|
LocationStats &LocStats) {
|
|
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 = Die.getTag() == dwarf::DW_TAG_formal_parameter;
|
|
bool IsLocalVar = Die.getTag() == dwarf::DW_TAG_variable;
|
|
bool IsConstantMember = Die.getTag() == dwarf::DW_TAG_member &&
|
|
Die.find(dwarf::DW_AT_const_value);
|
|
|
|
if (Die.getTag() == dwarf::DW_TAG_call_site ||
|
|
Die.getTag() == dwarf::DW_TAG_GNU_call_site) {
|
|
GlobalStats.CallSiteDIEs++;
|
|
return;
|
|
}
|
|
|
|
if (Die.getTag() == dwarf::DW_TAG_call_site_parameter ||
|
|
Die.getTag() == 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;
|
|
|
|
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, [](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) {
|
|
LocStats.NumVarParam++;
|
|
if (IsParam)
|
|
LocStats.NumParam++;
|
|
else if (IsLocalVar)
|
|
LocStats.NumVar++;
|
|
|
|
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 <= GlobalStats.ScopeBytes);
|
|
}
|
|
|
|
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 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) {
|
|
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 IsFunction = Tag == dwarf::DW_TAG_subprogram;
|
|
const bool IsBlock = Tag == dwarf::DW_TAG_lexical_block;
|
|
const bool IsInlinedFunction = Tag == dwarf::DW_TAG_inlined_subroutine;
|
|
if (IsFunction || IsInlinedFunction || IsBlock) {
|
|
|
|
// Reset VarPrefix when entering a new function.
|
|
if (Die.getTag() == dwarf::DW_TAG_subprogram ||
|
|
Die.getTag() == dwarf::DW_TAG_inlined_subroutine)
|
|
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.
|
|
if (Die.find(dwarf::DW_AT_inline))
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
Child = Child.getSibling();
|
|
}
|
|
}
|
|
|
|
/// Print human-readable output.
|
|
/// \{
|
|
static void printDatum(json::OStream &J, const char *Key, json::Value Value) {
|
|
J.attribute(Key, Value);
|
|
LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
|
|
}
|
|
|
|
static void printLocationStats(json::OStream &J, const char *Key,
|
|
std::vector<unsigned> &LocationStats) {
|
|
J.attribute(
|
|
(Twine(Key) + " with 0% of parent scope covered by DW_AT_location").str(),
|
|
LocationStats[0]);
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << Key
|
|
<< " with 0% of parent scope covered by DW_AT_location: \\"
|
|
<< LocationStats[0] << '\n');
|
|
J.attribute(
|
|
(Twine(Key) + " with (0%,10%) of parent scope covered by DW_AT_location")
|
|
.str(),
|
|
LocationStats[1]);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< Key
|
|
<< " with (0%,10%) of parent scope covered by DW_AT_location: "
|
|
<< LocationStats[1] << '\n');
|
|
for (unsigned i = 2; i < NumOfCoverageCategories - 1; ++i) {
|
|
J.attribute((Twine(Key) + " with [" + Twine((i - 1) * 10) + "%," +
|
|
Twine(i * 10) + "%) of parent scope covered by DW_AT_location")
|
|
.str(),
|
|
LocationStats[i]);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< Key << " with [" << (i - 1) * 10 << "%," << i * 10
|
|
<< "%) of parent scope covered by DW_AT_location: "
|
|
<< LocationStats[i]);
|
|
}
|
|
J.attribute(
|
|
(Twine(Key) + " with 100% of parent scope covered by DW_AT_location")
|
|
.str(),
|
|
LocationStats[NumOfCoverageCategories - 1]);
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << Key
|
|
<< " with 100% of parent scope covered by DW_AT_location: "
|
|
<< LocationStats[NumOfCoverageCategories - 1]);
|
|
}
|
|
|
|
static void printSectionSizes(json::OStream &J, const SectionSizes &Sizes) {
|
|
for (const auto &DebugSec : Sizes.DebugSectionSizes)
|
|
J.attribute((Twine("#bytes in ") + DebugSec.getKey()).str(),
|
|
int64_t(DebugSec.getValue()));
|
|
}
|
|
|
|
/// \}
|
|
|
|
/// 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;
|
|
for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units())
|
|
if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(false))
|
|
collectStatsRecursive(CUDie, "/", "g", 0, 0, Statistics, GlobalStats,
|
|
LocStats);
|
|
|
|
/// 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 = 6;
|
|
unsigned VarParamTotal = 0;
|
|
unsigned VarParamUnique = 0;
|
|
unsigned VarParamWithLoc = 0;
|
|
unsigned NumFunctions = 0;
|
|
unsigned NumInlinedFunctions = 0;
|
|
unsigned NumFuncsWithSrcLoc = 0;
|
|
unsigned NumAbstractOrigins = 0;
|
|
unsigned ParamTotal = 0;
|
|
unsigned ParamWithType = 0;
|
|
unsigned ParamWithLoc = 0;
|
|
unsigned ParamWithSrcLoc = 0;
|
|
unsigned LocalVarTotal = 0;
|
|
unsigned LocalVarWithType = 0;
|
|
unsigned LocalVarWithSrcLoc = 0;
|
|
unsigned LocalVarWithLoc = 0;
|
|
for (auto &Entry : Statistics) {
|
|
PerFunctionStats &Stats = Entry.getValue();
|
|
unsigned TotalVars = Stats.VarsInFunction.size() *
|
|
(Stats.NumFnInlined + Stats.NumFnOutOfLine);
|
|
// Count variables in global scope.
|
|
if (!Stats.IsFunction)
|
|
TotalVars =
|
|
Stats.NumLocalVars + Stats.ConstantMembers + Stats.NumArtificial;
|
|
unsigned 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);
|
|
printDatum(J, "#functions with location", NumFuncsWithSrcLoc);
|
|
printDatum(J, "#inlined functions", NumInlinedFunctions);
|
|
printDatum(J, "#inlined functions with abstract origins", NumAbstractOrigins);
|
|
|
|
// This includes local variables and formal parameters.
|
|
printDatum(J, "#unique source variables", VarParamUnique);
|
|
printDatum(J, "#source variables", VarParamTotal);
|
|
printDatum(J, "#source variables with location", VarParamWithLoc);
|
|
|
|
printDatum(J, "#call site entries", GlobalStats.CallSiteEntries);
|
|
printDatum(J, "#call site DIEs", GlobalStats.CallSiteDIEs);
|
|
printDatum(J, "#call site parameter DIEs", GlobalStats.CallSiteParamDIEs);
|
|
|
|
printDatum(J, "sum_all_variables(#bytes in parent scope)",
|
|
GlobalStats.ScopeBytes);
|
|
printDatum(J,
|
|
"sum_all_variables(#bytes in any scope covered by DW_AT_location)",
|
|
GlobalStats.TotalBytesCovered);
|
|
printDatum(J,
|
|
"sum_all_variables(#bytes in parent scope covered by "
|
|
"DW_AT_location)",
|
|
GlobalStats.ScopeBytesCovered);
|
|
printDatum(J,
|
|
"sum_all_variables(#bytes in parent scope covered by "
|
|
"DW_OP_entry_value)",
|
|
GlobalStats.ScopeEntryValueBytesCovered);
|
|
|
|
printDatum(J, "sum_all_params(#bytes in parent scope)",
|
|
GlobalStats.ParamScopeBytes);
|
|
printDatum(J,
|
|
"sum_all_params(#bytes in parent scope covered by DW_AT_location)",
|
|
GlobalStats.ParamScopeBytesCovered);
|
|
printDatum(J,
|
|
"sum_all_params(#bytes in parent scope covered by "
|
|
"DW_OP_entry_value)",
|
|
GlobalStats.ParamScopeEntryValueBytesCovered);
|
|
|
|
printDatum(J, "sum_all_local_vars(#bytes in parent scope)",
|
|
GlobalStats.LocalVarScopeBytes);
|
|
printDatum(J,
|
|
"sum_all_local_vars(#bytes in parent scope covered by "
|
|
"DW_AT_location)",
|
|
GlobalStats.LocalVarScopeBytesCovered);
|
|
printDatum(J,
|
|
"sum_all_local_vars(#bytes in parent scope covered by "
|
|
"DW_OP_entry_value)",
|
|
GlobalStats.LocalVarScopeEntryValueBytesCovered);
|
|
|
|
printDatum(J, "#bytes within functions", GlobalStats.FunctionSize);
|
|
printDatum(J, "#bytes within inlined functions",
|
|
GlobalStats.InlineFunctionSize);
|
|
|
|
// Print the summary for formal parameters.
|
|
printDatum(J, "#params", ParamTotal);
|
|
printDatum(J, "#params with source location", ParamWithSrcLoc);
|
|
printDatum(J, "#params with type", ParamWithType);
|
|
printDatum(J, "#params with binary location", ParamWithLoc);
|
|
|
|
// Print the summary for local variables.
|
|
printDatum(J, "#local vars", LocalVarTotal);
|
|
printDatum(J, "#local vars with source location", LocalVarWithSrcLoc);
|
|
printDatum(J, "#local vars with type", LocalVarWithType);
|
|
printDatum(J, "#local vars with binary location", LocalVarWithLoc);
|
|
|
|
// 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);
|
|
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);
|
|
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);
|
|
printLocationStats(J, "#local vars", LocStats.LocalVarLocStats);
|
|
printLocationStats(J, "#local vars - entry values",
|
|
LocStats.LocalVarNonEntryValLocStats);
|
|
J.objectEnd();
|
|
OS << '\n';
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << "Total Availability: "
|
|
<< (int)std::round((VarParamWithLoc * 100.0) / VarParamTotal)
|
|
<< "%\n";
|
|
llvm::dbgs() << "PC Ranges covered: "
|
|
<< (int)std::round((GlobalStats.ScopeBytesCovered * 100.0) /
|
|
GlobalStats.ScopeBytes)
|
|
<< "%\n");
|
|
return true;
|
|
}
|