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