[Debugify] Export per-pass debug info loss statistics

Add a -debugify-export option to opt. This exports per-pass `debugify`
loss statistics to a file in CSV format.

For some interesting numbers on debug value loss during an -O2 build
of the sqlite3 amalgamation, see the review thread.

Differential Revision: https://reviews.llvm.org/D49003

llvm-svn: 337787
diff --git a/llvm/tools/opt/Debugify.cpp b/llvm/tools/opt/Debugify.cpp
index b56a94b..6c3cdc7 100644
--- a/llvm/tools/opt/Debugify.cpp
+++ b/llvm/tools/opt/Debugify.cpp
@@ -209,7 +209,7 @@
 bool checkDebugifyMetadata(Module &M,
                            iterator_range<Module::iterator> Functions,
                            StringRef NameOfWrappedPass, StringRef Banner,
-                           bool Strip) {
+                           bool Strip, DebugifyStatsMap *StatsMap) {
   // Skip modules without debugify metadata.
   NamedMDNode *NMD = M.getNamedMetadata("llvm.debugify");
   if (!NMD) {
@@ -227,6 +227,11 @@
   unsigned OriginalNumVars = getDebugifyOperand(1);
   bool HasErrors = false;
 
+  // Track debug info loss statistics if able.
+  DebugifyStatistics *Stats = nullptr;
+  if (StatsMap && !NameOfWrappedPass.empty())
+    Stats = &StatsMap->operator[](NameOfWrappedPass);
+
   BitVector MissingLines{OriginalNumLines, true};
   BitVector MissingVars{OriginalNumVars, true};
   for (Function &F : Functions) {
@@ -276,6 +281,14 @@
   for (unsigned Idx : MissingVars.set_bits())
     dbg() << "WARNING: Missing variable " << Idx + 1 << "\n";
 
+  // Update DI loss statistics.
+  if (Stats) {
+    Stats->NumDbgLocsExpected += OriginalNumLines;
+    Stats->NumDbgLocsMissing += MissingLines.count();
+    Stats->NumDbgValuesExpected += OriginalNumVars;
+    Stats->NumDbgValuesMissing += MissingVars.count();
+  }
+
   dbg() << Banner;
   if (!NameOfWrappedPass.empty())
     dbg() << " [" << NameOfWrappedPass << "]";
@@ -331,11 +344,13 @@
 struct CheckDebugifyModulePass : public ModulePass {
   bool runOnModule(Module &M) override {
     return checkDebugifyMetadata(M, M.functions(), NameOfWrappedPass,
-                                 "CheckModuleDebugify", Strip);
+                                 "CheckModuleDebugify", Strip, StatsMap);
   }
 
-  CheckDebugifyModulePass(bool Strip = false, StringRef NameOfWrappedPass = "")
-      : ModulePass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass) {}
+  CheckDebugifyModulePass(bool Strip = false, StringRef NameOfWrappedPass = "",
+                          DebugifyStatsMap *StatsMap = nullptr)
+      : ModulePass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass),
+        StatsMap(StatsMap) {}
 
   void getAnalysisUsage(AnalysisUsage &AU) const override {
     AU.setPreservesAll();
@@ -346,6 +361,7 @@
 private:
   bool Strip;
   StringRef NameOfWrappedPass;
+  DebugifyStatsMap *StatsMap;
 };
 
 /// FunctionPass for checking debug info inserted by -debugify-function, used
@@ -356,12 +372,14 @@
     auto FuncIt = F.getIterator();
     return checkDebugifyMetadata(M, make_range(FuncIt, std::next(FuncIt)),
                                  NameOfWrappedPass, "CheckFunctionDebugify",
-                                 Strip);
+                                 Strip, StatsMap);
   }
 
   CheckDebugifyFunctionPass(bool Strip = false,
-                            StringRef NameOfWrappedPass = "")
-      : FunctionPass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass) {}
+                            StringRef NameOfWrappedPass = "",
+                            DebugifyStatsMap *StatsMap = nullptr)
+      : FunctionPass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass),
+        StatsMap(StatsMap) {}
 
   void getAnalysisUsage(AnalysisUsage &AU) const override {
     AU.setPreservesAll();
@@ -372,10 +390,32 @@
 private:
   bool Strip;
   StringRef NameOfWrappedPass;
+  DebugifyStatsMap *StatsMap;
 };
 
 } // end anonymous namespace
 
+void exportDebugifyStats(llvm::StringRef Path, const DebugifyStatsMap &Map) {
+  std::error_code EC;
+  raw_fd_ostream OS{Path, EC};
+  if (EC) {
+    errs() << "Could not open file: " << EC.message() << ", " << Path << '\n';
+    return;
+  }
+
+  OS << "Pass Name" << ',' << "# of missing debug values" << ','
+     << "# of missing locations" << ',' << "Missing/Expected value ratio" << ','
+     << "Missing/Expected location ratio" << '\n';
+  for (const auto &Entry : Map) {
+    StringRef Pass = Entry.first;
+    DebugifyStatistics Stats = Entry.second;
+
+    OS << Pass << ',' << Stats.NumDbgValuesMissing << ','
+       << Stats.NumDbgLocsMissing << ',' << Stats.getMissingValueRatio() << ','
+       << Stats.getEmptyLocationRatio() << '\n';
+  }
+}
+
 ModulePass *createDebugifyModulePass() { return new DebugifyModulePass(); }
 
 FunctionPass *createDebugifyFunctionPass() {
@@ -388,18 +428,21 @@
 }
 
 ModulePass *createCheckDebugifyModulePass(bool Strip,
-                                          StringRef NameOfWrappedPass) {
-  return new CheckDebugifyModulePass(Strip, NameOfWrappedPass);
+                                          StringRef NameOfWrappedPass,
+                                          DebugifyStatsMap *StatsMap) {
+  return new CheckDebugifyModulePass(Strip, NameOfWrappedPass, StatsMap);
 }
 
 FunctionPass *createCheckDebugifyFunctionPass(bool Strip,
-                                              StringRef NameOfWrappedPass) {
-  return new CheckDebugifyFunctionPass(Strip, NameOfWrappedPass);
+                                              StringRef NameOfWrappedPass,
+                                              DebugifyStatsMap *StatsMap) {
+  return new CheckDebugifyFunctionPass(Strip, NameOfWrappedPass, StatsMap);
 }
 
 PreservedAnalyses NewPMCheckDebugifyPass::run(Module &M,
                                               ModuleAnalysisManager &) {
-  checkDebugifyMetadata(M, M.functions(), "", "CheckModuleDebugify", false);
+  checkDebugifyMetadata(M, M.functions(), "", "CheckModuleDebugify", false,
+                        nullptr);
   return PreservedAnalyses::all();
 }
 
diff --git a/llvm/tools/opt/Debugify.h b/llvm/tools/opt/Debugify.h
index 14ea5d3..d1a60c7 100644
--- a/llvm/tools/opt/Debugify.h
+++ b/llvm/tools/opt/Debugify.h
@@ -14,7 +14,10 @@
 #ifndef LLVM_TOOLS_OPT_DEBUGIFY_H
 #define LLVM_TOOLS_OPT_DEBUGIFY_H
 
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/IR/PassManager.h"
+#include "llvm/Support/raw_ostream.h"
 
 llvm::ModulePass *createDebugifyModulePass();
 llvm::FunctionPass *createDebugifyFunctionPass();
@@ -23,13 +26,46 @@
   llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM);
 };
 
+/// Track how much `debugify` information has been lost.
+struct DebugifyStatistics {
+  /// Number of missing dbg.values.
+  unsigned NumDbgValuesMissing = 0;
+
+  /// Number of dbg.values expected.
+  unsigned NumDbgValuesExpected = 0;
+
+  /// Number of instructions with empty debug locations.
+  unsigned NumDbgLocsMissing = 0;
+
+  /// Number of instructions expected to have debug locations.
+  unsigned NumDbgLocsExpected = 0;
+
+  /// Get the ratio of missing/expected dbg.values.
+  float getMissingValueRatio() const {
+    return float(NumDbgValuesMissing) / float(NumDbgLocsExpected);
+  }
+
+  /// Get the ratio of missing/expected instructions with locations.
+  float getEmptyLocationRatio() const {
+    return float(NumDbgLocsMissing) / float(NumDbgLocsExpected);
+  }
+};
+
+/// Map pass names to a per-pass DebugifyStatistics instance.
+using DebugifyStatsMap = llvm::MapVector<llvm::StringRef, DebugifyStatistics>;
+
+/// Export per-pass debugify statistics to the file specified by \p Path.
+void exportDebugifyStats(llvm::StringRef Path, const DebugifyStatsMap &Map);
+
 llvm::ModulePass *
 createCheckDebugifyModulePass(bool Strip = false,
-                              llvm::StringRef NameOfWrappedPass = "");
+                              llvm::StringRef NameOfWrappedPass = "",
+                              DebugifyStatsMap *StatsMap = nullptr);
 
 llvm::FunctionPass *
 createCheckDebugifyFunctionPass(bool Strip = false,
-                                llvm::StringRef NameOfWrappedPass = "");
+                                llvm::StringRef NameOfWrappedPass = "",
+                                DebugifyStatsMap *StatsMap = nullptr);
 
 struct NewPMCheckDebugifyPass
     : public llvm::PassInfoMixin<NewPMCheckDebugifyPass> {
diff --git a/llvm/tools/opt/opt.cpp b/llvm/tools/opt/opt.cpp
index 98ad700..6e287b6 100644
--- a/llvm/tools/opt/opt.cpp
+++ b/llvm/tools/opt/opt.cpp
@@ -217,6 +217,11 @@
     cl::desc(
         "Start each pass with debugify and end it with check-debugify"));
 
+static cl::opt<std::string>
+    DebugifyExport("debugify-export",
+                   cl::desc("Export per-pass debugify statistics to this file"),
+                   cl::value_desc("filename"), cl::init(""));
+
 static cl::opt<bool>
 PrintBreakpoints("print-breakpoints-for-testing",
                  cl::desc("Print select breakpoints location for testing"));
@@ -267,34 +272,45 @@
                     cl::value_desc("filename"));
 
 class OptCustomPassManager : public legacy::PassManager {
+  DebugifyStatsMap DIStatsMap;
+
 public:
   using super = legacy::PassManager;
 
   void add(Pass *P) override {
+    // Wrap each pass with (-check)-debugify passes if requested, making
+    // exceptions for passes which shouldn't see -debugify instrumentation.
     bool WrapWithDebugify = DebugifyEach && !P->getAsImmutablePass() &&
                             !isIRPrintingPass(P) && !isBitcodeWriterPass(P);
     if (!WrapWithDebugify) {
       super::add(P);
       return;
     }
+
+    // Apply -debugify/-check-debugify before/after each pass and collect
+    // debug info loss statistics.
     PassKind Kind = P->getPassKind();
+    StringRef Name = P->getPassName();
+
     // TODO: Implement Debugify for BasicBlockPass, LoopPass.
     switch (Kind) {
       case PT_Function:
         super::add(createDebugifyFunctionPass());
         super::add(P);
-        super::add(createCheckDebugifyFunctionPass(true, P->getPassName()));
+        super::add(createCheckDebugifyFunctionPass(true, Name, &DIStatsMap));
         break;
       case PT_Module:
         super::add(createDebugifyModulePass());
         super::add(P);
-        super::add(createCheckDebugifyModulePass(true, P->getPassName()));
+        super::add(createCheckDebugifyModulePass(true, Name, &DIStatsMap));
         break;
       default:
         super::add(P);
         break;
     }
   }
+
+  const DebugifyStatsMap &getDebugifyStatsMap() const { return DIStatsMap; }
 };
 
 static inline void addPass(legacy::PassManagerBase &PM, Pass *P) {
@@ -839,6 +855,9 @@
     Out->os() << BOS->str();
   }
 
+  if (DebugifyEach && !DebugifyExport.empty())
+    exportDebugifyStats(DebugifyExport, Passes.getDebugifyStatsMap());
+
   // Declare success.
   if (!NoOutput || PrintBreakpoints)
     Out->keep();