Adrian Prantl | 59f30b8 | 2017-10-06 20:24:34 +0000 | [diff] [blame] | 1 | #include "llvm/ADT/DenseMap.h" |
| 2 | #include "llvm/DebugInfo/DIContext.h" |
| 3 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
| 4 | #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" |
| 5 | #include "llvm/Object/ObjectFile.h" |
| 6 | |
| 7 | #define DEBUG_TYPE "dwarfdump" |
| 8 | using namespace llvm; |
| 9 | using namespace object; |
| 10 | |
| 11 | /// Holds statistics for one function (or other entity that has a PC range and |
| 12 | /// contains variables, such as a compile unit). |
| 13 | struct PerFunctionStats { |
| 14 | /// Number of inlined instances of this function. |
| 15 | unsigned NumFnInlined = 0; |
| 16 | /// Number of variables with location across all inlined instances. |
| 17 | unsigned TotalVarWithLoc = 0; |
| 18 | /// Number of constants with location across all inlined instances. |
| 19 | unsigned ConstantMembers = 0; |
| 20 | /// List of all Variables in this function. |
| 21 | SmallDenseSet<uint32_t, 4> VarsInFunction; |
| 22 | /// Compile units also cover a PC range, but have this flag set to false. |
| 23 | bool IsFunction = false; |
| 24 | }; |
| 25 | |
| 26 | /// Holds accumulated global statistics about local variables. |
| 27 | struct GlobalStats { |
| 28 | /// Total number of PC range bytes covered by DW_AT_locations. |
| 29 | unsigned ScopeBytesCovered = 0; |
| 30 | /// Total number of PC range bytes in each variable's enclosing scope, |
| 31 | /// starting from the first definition of the variable. |
| 32 | unsigned ScopeBytesFromFirstDefinition = 0; |
| 33 | }; |
| 34 | |
| 35 | /// Extract the low pc from a Die. |
| 36 | static uint64_t getLowPC(DWARFDie Die) { |
| 37 | if (Die.getAddressRanges().size()) |
| 38 | return Die.getAddressRanges()[0].LowPC; |
| 39 | return dwarf::toAddress(Die.find(dwarf::DW_AT_low_pc), 0); |
| 40 | } |
| 41 | |
| 42 | /// Collect debug info quality metrics for one DIE. |
| 43 | static void collectStatsForDie(DWARFDie Die, std::string Prefix, |
| 44 | uint64_t ScopeLowPC, uint64_t BytesInScope, |
| 45 | StringMap<PerFunctionStats> &FnStatMap, |
| 46 | GlobalStats &GlobalStats) { |
| 47 | bool HasLoc = false; |
| 48 | uint64_t BytesCovered = 0; |
| 49 | uint64_t OffsetToFirstDefinition = 0; |
| 50 | if (Die.find(dwarf::DW_AT_const_value)) { |
| 51 | // This catches constant members *and* variables. |
| 52 | HasLoc = true; |
| 53 | BytesCovered = BytesInScope; |
| 54 | } else if (Die.getTag() == dwarf::DW_TAG_variable || |
| 55 | Die.getTag() == dwarf::DW_TAG_formal_parameter) { |
| 56 | // Handle variables and function arguments. |
| 57 | auto FormValue = Die.find(dwarf::DW_AT_location); |
| 58 | HasLoc = FormValue.hasValue(); |
| 59 | if (HasLoc) { |
| 60 | // Get PC coverage. |
| 61 | if (auto DebugLocOffset = FormValue->getAsSectionOffset()) { |
| 62 | auto *DebugLoc = Die.getDwarfUnit()->getContext().getDebugLoc(); |
| 63 | if (auto List = DebugLoc->getLocationListAtOffset(*DebugLocOffset)) { |
| 64 | for (auto Entry : List->Entries) |
| 65 | BytesCovered += Entry.End - Entry.Begin; |
| 66 | if (List->Entries.size()) { |
| 67 | uint64_t FirstDef = List->Entries[0].Begin; |
| 68 | uint64_t UnitOfs = getLowPC(Die.getDwarfUnit()->getUnitDIE()); |
| 69 | // Ranges sometimes start before the lexical scope. |
| 70 | if (UnitOfs + FirstDef >= ScopeLowPC) |
| 71 | OffsetToFirstDefinition = UnitOfs + FirstDef - ScopeLowPC; |
| 72 | // Or even after it. Count that as a failure. |
| 73 | if (OffsetToFirstDefinition > BytesInScope) |
| 74 | OffsetToFirstDefinition = 0; |
| 75 | } |
| 76 | } |
| 77 | assert(BytesInScope); |
| 78 | } else { |
| 79 | // Assume the entire range is covered by a single location. |
| 80 | BytesCovered = BytesInScope; |
| 81 | } |
| 82 | } |
| 83 | } else { |
| 84 | // Not a variable or constant member. |
| 85 | return; |
| 86 | } |
| 87 | |
| 88 | // Collect PC range coverage data. |
| 89 | auto &FnStats = FnStatMap[Prefix]; |
| 90 | if (DWARFDie D = |
| 91 | Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin)) |
| 92 | Die = D; |
| 93 | // This is a unique ID for the variable inside the current object file. |
| 94 | unsigned CanonicalDieOffset = Die.getOffset(); |
| 95 | FnStats.VarsInFunction.insert(CanonicalDieOffset); |
| 96 | if (BytesInScope) { |
| 97 | FnStats.TotalVarWithLoc += (unsigned)HasLoc; |
| 98 | // Adjust for the fact the variables often start their lifetime in the |
| 99 | // middle of the scope. |
| 100 | BytesInScope -= OffsetToFirstDefinition; |
| 101 | // Turns out we have a lot of ranges that extend past the lexical scope. |
| 102 | GlobalStats.ScopeBytesCovered += std::min(BytesInScope, BytesCovered); |
| 103 | GlobalStats.ScopeBytesFromFirstDefinition += BytesInScope; |
| 104 | assert(GlobalStats.ScopeBytesCovered <= |
| 105 | GlobalStats.ScopeBytesFromFirstDefinition); |
| 106 | } else { |
| 107 | FnStats.ConstantMembers++; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /// Recursively collect debug info quality metrics. |
| 112 | static void collectStatsRecursive(DWARFDie Die, std::string Prefix, |
| 113 | uint64_t ScopeLowPC, uint64_t BytesInScope, |
| 114 | StringMap<PerFunctionStats> &FnStatMap, |
| 115 | GlobalStats &GlobalStats) { |
| 116 | // Handle any kind of lexical scope. |
| 117 | if (Die.getTag() == dwarf::DW_TAG_subprogram || |
| 118 | Die.getTag() == dwarf::DW_TAG_inlined_subroutine || |
| 119 | Die.getTag() == dwarf::DW_TAG_lexical_block) { |
| 120 | // Ignore forward declarations. |
| 121 | if (Die.find(dwarf::DW_AT_declaration)) |
| 122 | return; |
| 123 | |
| 124 | // Count the function. |
| 125 | if (Die.getTag() != dwarf::DW_TAG_lexical_block) { |
| 126 | StringRef Name = Die.getName(DINameKind::LinkageName); |
| 127 | if (Name.empty()) |
| 128 | Name = Die.getName(DINameKind::ShortName); |
| 129 | Prefix = Name; |
| 130 | // Skip over abstract origins. |
| 131 | if (Die.find(dwarf::DW_AT_inline)) |
| 132 | return; |
| 133 | // We've seen an (inlined) instance of this function. |
| 134 | auto &FnStats = FnStatMap[Name]; |
| 135 | FnStats.NumFnInlined++; |
| 136 | FnStats.IsFunction = true; |
| 137 | } |
| 138 | |
| 139 | // PC Ranges. |
| 140 | auto Ranges = Die.getAddressRanges(); |
| 141 | uint64_t BytesInThisScope = 0; |
| 142 | for (auto Range : Ranges) |
| 143 | BytesInThisScope += Range.HighPC - Range.LowPC; |
| 144 | ScopeLowPC = getLowPC(Die); |
| 145 | |
| 146 | if (BytesInThisScope) |
| 147 | BytesInScope = BytesInThisScope; |
| 148 | } else { |
| 149 | // Not a scope, visit the Die itself. It could be a variable. |
| 150 | collectStatsForDie(Die, Prefix, ScopeLowPC, BytesInScope, FnStatMap, |
| 151 | GlobalStats); |
| 152 | } |
| 153 | |
| 154 | // Traverse children. |
| 155 | DWARFDie Child = Die.getFirstChild(); |
| 156 | while (Child) { |
| 157 | collectStatsRecursive(Child, Prefix, ScopeLowPC, BytesInScope, FnStatMap, |
| 158 | GlobalStats); |
| 159 | Child = Child.getSibling(); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /// Print machine-readable output. |
| 164 | /// The machine-readable format is single-line JSON output. |
| 165 | /// \{ |
| 166 | static void printDatum(raw_ostream &OS, const char *Key, StringRef Value) { |
| 167 | OS << ",\"" << Key << "\":\"" << Value << '"'; |
| 168 | DEBUG(llvm::dbgs() << Key << ": " << Value << '\n'); |
| 169 | } |
| 170 | static void printDatum(raw_ostream &OS, const char *Key, uint64_t Value) { |
| 171 | OS << ",\"" << Key << "\":" << Value; |
| 172 | DEBUG(llvm::dbgs() << Key << ": " << Value << '\n'); |
| 173 | } |
| 174 | /// \} |
| 175 | |
| 176 | /// Collect debug info quality metrics for an entire DIContext. |
| 177 | /// |
| 178 | /// Do the impossible and reduce the quality of the debug info down to a few |
| 179 | /// numbers. The idea is to condense the data into numbers that can be tracked |
| 180 | /// over time to identify trends in newer compiler versions and gauge the effect |
| 181 | /// of particular optimizations. The raw numbers themselves are not particularly |
| 182 | /// useful, only the delta between compiling the same program with different |
| 183 | /// compilers is. |
| 184 | bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx, |
| 185 | Twine Filename, raw_ostream &OS) { |
| 186 | StringRef FormatName = Obj.getFileFormatName(); |
| 187 | GlobalStats GlobalStats; |
| 188 | StringMap<PerFunctionStats> Statistics; |
| 189 | for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units()) |
| 190 | if (DWARFDie CUDie = CU->getUnitDIE(false)) |
| 191 | collectStatsRecursive(CUDie, "/", 0, 0, Statistics, GlobalStats); |
| 192 | |
| 193 | /// The version number should be increased every time the algorithm is changed |
| 194 | /// (including bug fixes). New metrics may be added without increasing the |
| 195 | /// version. |
| 196 | unsigned Version = 1; |
| 197 | unsigned VarTotal = 0; |
| 198 | unsigned VarUnique = 0; |
| 199 | unsigned VarWithLoc = 0; |
| 200 | unsigned NumFunctions = 0; |
| 201 | unsigned NumInlinedFunctions = 0; |
| 202 | for (auto &Entry : Statistics) { |
| 203 | PerFunctionStats &Stats = Entry.getValue(); |
| 204 | unsigned TotalVars = Stats.VarsInFunction.size() * Stats.NumFnInlined; |
| 205 | unsigned Constants = Stats.ConstantMembers; |
| 206 | VarWithLoc += Stats.TotalVarWithLoc + Constants; |
| 207 | VarTotal += TotalVars + Constants; |
| 208 | VarUnique += Stats.VarsInFunction.size(); |
| 209 | DEBUG(for (auto V : Stats.VarsInFunction) |
| 210 | llvm::dbgs() << Entry.getKey() << ": " << V << "\n"); |
| 211 | NumFunctions += Stats.IsFunction; |
| 212 | NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined; |
| 213 | } |
| 214 | |
| 215 | // Print summary. |
| 216 | OS.SetBufferSize(1024); |
| 217 | OS << "{\"version\":\"" << Version << '"'; |
| 218 | DEBUG(llvm::dbgs() << "Variable location quality metrics\n"; |
| 219 | llvm::dbgs() << "---------------------------------\n"); |
| 220 | printDatum(OS, "file", Filename.str()); |
| 221 | printDatum(OS, "format", FormatName); |
| 222 | printDatum(OS, "source functions", NumFunctions); |
| 223 | printDatum(OS, "inlined functions", NumInlinedFunctions); |
| 224 | printDatum(OS, "unique source variables", VarUnique); |
| 225 | printDatum(OS, "source variables", VarTotal); |
| 226 | printDatum(OS, "variables with location", VarWithLoc); |
| 227 | printDatum(OS, "scope bytes total", |
| 228 | GlobalStats.ScopeBytesFromFirstDefinition); |
| 229 | printDatum(OS, "scope bytes covered", GlobalStats.ScopeBytesCovered); |
| 230 | OS << "}\n"; |
| 231 | DEBUG( |
| 232 | llvm::dbgs() << "Total Availability: " |
| 233 | << (int)std::round((VarWithLoc * 100.0) / VarTotal) << "%\n"; |
| 234 | llvm::dbgs() << "PC Ranges covered: " |
| 235 | << (int)std::round((GlobalStats.ScopeBytesCovered * 100.0) / |
| 236 | GlobalStats.ScopeBytesFromFirstDefinition) |
| 237 | << "%\n"); |
| 238 | return true; |
| 239 | } |