summaryrefslogtreecommitdiffstats
path: root/llvm/tools/llvm-dwarfdump
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/tools/llvm-dwarfdump')
-rw-r--r--llvm/tools/llvm-dwarfdump/CMakeLists.txt1
-rw-r--r--llvm/tools/llvm-dwarfdump/Statistics.cpp239
-rw-r--r--llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp12
3 files changed, 251 insertions, 1 deletions
diff --git a/llvm/tools/llvm-dwarfdump/CMakeLists.txt b/llvm/tools/llvm-dwarfdump/CMakeLists.txt
index 23fee30bfa4..77620e0faaf 100644
--- a/llvm/tools/llvm-dwarfdump/CMakeLists.txt
+++ b/llvm/tools/llvm-dwarfdump/CMakeLists.txt
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
)
add_llvm_tool(llvm-dwarfdump
+ Statistics.cpp
llvm-dwarfdump.cpp
)
diff --git a/llvm/tools/llvm-dwarfdump/Statistics.cpp b/llvm/tools/llvm-dwarfdump/Statistics.cpp
new file mode 100644
index 00000000000..9a7454a5262
--- /dev/null
+++ b/llvm/tools/llvm-dwarfdump/Statistics.cpp
@@ -0,0 +1,239 @@
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/DebugInfo/DIContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
+#include "llvm/Object/ObjectFile.h"
+
+#define DEBUG_TYPE "dwarfdump"
+using namespace llvm;
+using namespace object;
+
+/// 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 variables with location across all inlined instances.
+ unsigned TotalVarWithLoc = 0;
+ /// Number of constants with location across all inlined instances.
+ unsigned ConstantMembers = 0;
+ /// List of all Variables in this function.
+ SmallDenseSet<uint32_t, 4> VarsInFunction;
+ /// Compile units also cover a PC range, but have this flag set to false.
+ bool IsFunction = false;
+};
+
+/// Holds accumulated global statistics about local variables.
+struct GlobalStats {
+ /// Total number of PC range bytes covered by DW_AT_locations.
+ unsigned ScopeBytesCovered = 0;
+ /// Total number of PC range bytes in each variable's enclosing scope,
+ /// starting from the first definition of the variable.
+ unsigned ScopeBytesFromFirstDefinition = 0;
+};
+
+/// Extract the low pc from a Die.
+static uint64_t getLowPC(DWARFDie Die) {
+ if (Die.getAddressRanges().size())
+ return Die.getAddressRanges()[0].LowPC;
+ return dwarf::toAddress(Die.find(dwarf::DW_AT_low_pc), 0);
+}
+
+/// Collect debug info quality metrics for one DIE.
+static void collectStatsForDie(DWARFDie Die, std::string Prefix,
+ uint64_t ScopeLowPC, uint64_t BytesInScope,
+ StringMap<PerFunctionStats> &FnStatMap,
+ GlobalStats &GlobalStats) {
+ bool HasLoc = false;
+ uint64_t BytesCovered = 0;
+ uint64_t OffsetToFirstDefinition = 0;
+ if (Die.find(dwarf::DW_AT_const_value)) {
+ // This catches constant members *and* variables.
+ HasLoc = true;
+ BytesCovered = BytesInScope;
+ } else if (Die.getTag() == dwarf::DW_TAG_variable ||
+ Die.getTag() == dwarf::DW_TAG_formal_parameter) {
+ // Handle variables and function arguments.
+ auto FormValue = Die.find(dwarf::DW_AT_location);
+ HasLoc = FormValue.hasValue();
+ if (HasLoc) {
+ // Get PC coverage.
+ if (auto DebugLocOffset = FormValue->getAsSectionOffset()) {
+ auto *DebugLoc = Die.getDwarfUnit()->getContext().getDebugLoc();
+ if (auto List = DebugLoc->getLocationListAtOffset(*DebugLocOffset)) {
+ for (auto Entry : List->Entries)
+ BytesCovered += Entry.End - Entry.Begin;
+ if (List->Entries.size()) {
+ uint64_t FirstDef = List->Entries[0].Begin;
+ uint64_t UnitOfs = getLowPC(Die.getDwarfUnit()->getUnitDIE());
+ // Ranges sometimes start before the lexical scope.
+ if (UnitOfs + FirstDef >= ScopeLowPC)
+ OffsetToFirstDefinition = UnitOfs + FirstDef - ScopeLowPC;
+ // Or even after it. Count that as a failure.
+ if (OffsetToFirstDefinition > BytesInScope)
+ OffsetToFirstDefinition = 0;
+ }
+ }
+ assert(BytesInScope);
+ } else {
+ // Assume the entire range is covered by a single location.
+ BytesCovered = BytesInScope;
+ }
+ }
+ } else {
+ // Not a variable or constant member.
+ return;
+ }
+
+ // Collect PC range coverage data.
+ auto &FnStats = FnStatMap[Prefix];
+ if (DWARFDie D =
+ Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin))
+ Die = D;
+ // This is a unique ID for the variable inside the current object file.
+ unsigned CanonicalDieOffset = Die.getOffset();
+ FnStats.VarsInFunction.insert(CanonicalDieOffset);
+ if (BytesInScope) {
+ FnStats.TotalVarWithLoc += (unsigned)HasLoc;
+ // Adjust for the fact the variables often start their lifetime in the
+ // middle of the scope.
+ BytesInScope -= OffsetToFirstDefinition;
+ // Turns out we have a lot of ranges that extend past the lexical scope.
+ GlobalStats.ScopeBytesCovered += std::min(BytesInScope, BytesCovered);
+ GlobalStats.ScopeBytesFromFirstDefinition += BytesInScope;
+ assert(GlobalStats.ScopeBytesCovered <=
+ GlobalStats.ScopeBytesFromFirstDefinition);
+ } else {
+ FnStats.ConstantMembers++;
+ }
+}
+
+/// Recursively collect debug info quality metrics.
+static void collectStatsRecursive(DWARFDie Die, std::string Prefix,
+ uint64_t ScopeLowPC, uint64_t BytesInScope,
+ StringMap<PerFunctionStats> &FnStatMap,
+ GlobalStats &GlobalStats) {
+ // Handle any kind of lexical scope.
+ if (Die.getTag() == dwarf::DW_TAG_subprogram ||
+ Die.getTag() == dwarf::DW_TAG_inlined_subroutine ||
+ Die.getTag() == dwarf::DW_TAG_lexical_block) {
+ // Ignore forward declarations.
+ if (Die.find(dwarf::DW_AT_declaration))
+ return;
+
+ // Count the function.
+ if (Die.getTag() != dwarf::DW_TAG_lexical_block) {
+ StringRef Name = Die.getName(DINameKind::LinkageName);
+ if (Name.empty())
+ Name = Die.getName(DINameKind::ShortName);
+ Prefix = Name;
+ // Skip over abstract origins.
+ if (Die.find(dwarf::DW_AT_inline))
+ return;
+ // We've seen an (inlined) instance of this function.
+ auto &FnStats = FnStatMap[Name];
+ FnStats.NumFnInlined++;
+ FnStats.IsFunction = true;
+ }
+
+ // PC Ranges.
+ auto Ranges = Die.getAddressRanges();
+ uint64_t BytesInThisScope = 0;
+ for (auto Range : Ranges)
+ BytesInThisScope += Range.HighPC - Range.LowPC;
+ ScopeLowPC = getLowPC(Die);
+
+ if (BytesInThisScope)
+ BytesInScope = BytesInThisScope;
+ } else {
+ // Not a scope, visit the Die itself. It could be a variable.
+ collectStatsForDie(Die, Prefix, ScopeLowPC, BytesInScope, FnStatMap,
+ GlobalStats);
+ }
+
+ // Traverse children.
+ DWARFDie Child = Die.getFirstChild();
+ while (Child) {
+ collectStatsRecursive(Child, Prefix, ScopeLowPC, BytesInScope, FnStatMap,
+ GlobalStats);
+ Child = Child.getSibling();
+ }
+}
+
+/// Print machine-readable output.
+/// The machine-readable format is single-line JSON output.
+/// \{
+static void printDatum(raw_ostream &OS, const char *Key, StringRef Value) {
+ OS << ",\"" << Key << "\":\"" << Value << '"';
+ DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
+}
+static void printDatum(raw_ostream &OS, const char *Key, uint64_t Value) {
+ OS << ",\"" << Key << "\":" << Value;
+ DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
+}
+/// \}
+
+/// 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 collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
+ Twine Filename, raw_ostream &OS) {
+ StringRef FormatName = Obj.getFileFormatName();
+ GlobalStats GlobalStats;
+ StringMap<PerFunctionStats> Statistics;
+ for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units())
+ if (DWARFDie CUDie = CU->getUnitDIE(false))
+ collectStatsRecursive(CUDie, "/", 0, 0, Statistics, GlobalStats);
+
+ /// 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 = 1;
+ unsigned VarTotal = 0;
+ unsigned VarUnique = 0;
+ unsigned VarWithLoc = 0;
+ unsigned NumFunctions = 0;
+ unsigned NumInlinedFunctions = 0;
+ for (auto &Entry : Statistics) {
+ PerFunctionStats &Stats = Entry.getValue();
+ unsigned TotalVars = Stats.VarsInFunction.size() * Stats.NumFnInlined;
+ unsigned Constants = Stats.ConstantMembers;
+ VarWithLoc += Stats.TotalVarWithLoc + Constants;
+ VarTotal += TotalVars + Constants;
+ VarUnique += Stats.VarsInFunction.size();
+ DEBUG(for (auto V : Stats.VarsInFunction)
+ llvm::dbgs() << Entry.getKey() << ": " << V << "\n");
+ NumFunctions += Stats.IsFunction;
+ NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined;
+ }
+
+ // Print summary.
+ OS.SetBufferSize(1024);
+ OS << "{\"version\":\"" << Version << '"';
+ DEBUG(llvm::dbgs() << "Variable location quality metrics\n";
+ llvm::dbgs() << "---------------------------------\n");
+ printDatum(OS, "file", Filename.str());
+ printDatum(OS, "format", FormatName);
+ printDatum(OS, "source functions", NumFunctions);
+ printDatum(OS, "inlined functions", NumInlinedFunctions);
+ printDatum(OS, "unique source variables", VarUnique);
+ printDatum(OS, "source variables", VarTotal);
+ printDatum(OS, "variables with location", VarWithLoc);
+ printDatum(OS, "scope bytes total",
+ GlobalStats.ScopeBytesFromFirstDefinition);
+ printDatum(OS, "scope bytes covered", GlobalStats.ScopeBytesCovered);
+ OS << "}\n";
+ DEBUG(
+ llvm::dbgs() << "Total Availability: "
+ << (int)std::round((VarWithLoc * 100.0) / VarTotal) << "%\n";
+ llvm::dbgs() << "PC Ranges covered: "
+ << (int)std::round((GlobalStats.ScopeBytesCovered * 100.0) /
+ GlobalStats.ScopeBytesFromFirstDefinition)
+ << "%\n");
+ return true;
+}
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
index 9e6fc773139..0bd6ca73b1b 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -201,6 +201,10 @@ static opt<bool>
SummarizeTypes("summarize-types",
desc("Abbreviate the description of type unit entries"),
cat(DwarfDumpCategory));
+static cl::opt<bool>
+ Statistics("statistics",
+ cl::desc("Emit JSON-formatted debug info quality metrics."),
+ cat(DwarfDumpCategory));
static opt<bool> Verify("verify", desc("Verify the DWARF debug info"),
cat(DwarfDumpCategory));
static opt<bool> Quiet("quiet", desc("Use with -verify to not emit to STDOUT."),
@@ -301,6 +305,9 @@ static void filterByName(const StringSet<> &Names,
}
}
+bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
+ Twine Filename, raw_ostream &OS);
+
static bool dumpObjectFile(ObjectFile &Obj, DWARFContext &DICtx, Twine Filename,
raw_ostream &OS) {
logAllUnhandledErrors(DICtx.loadRegisterInfo(Obj), errs(),
@@ -536,7 +543,10 @@ int main(int argc, char **argv) {
return handleFile(Object, verifyObjectFile, OS);
}))
exit(1);
- } else
+ } else if (Statistics)
+ for (auto Object : Objects)
+ handleFile(Object, collectStatsForObjectFile, OS);
+ else
for (auto Object : Objects)
handleFile(Object, dumpObjectFile, OS);
OpenPOWER on IntegriCloud