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 } 240