A new option -print-on-crash that prints the IR as it was upon entering the last...
authorJamie Schmeiser <schmeise@ca.ibm.com>
Tue, 23 Mar 2021 13:27:30 +0000 (09:27 -0400)
committerJamie Schmeiser <schmeise@ca.ibm.com>
Tue, 23 Mar 2021 13:29:17 +0000 (09:29 -0400)
Summary:
The IR is saved in its print form before each pass is started and a
signal handler is registered.  If the compilation crashes, the signal
handler will print the saved IR to dbgs().  This option
can be modified using -print-module-scope to get the IR for the complete
module.  Note that this option only works with the new pass manager.

Author: Jamie Schmeiser <schmeise@ca.ibm.com>
Reviewed By: aeubanks (Arthur Eubanks) yrouban (Yevgeny Rouban)
Differential Revision: https://reviews.llvm.org/D86657

llvm/include/llvm/Passes/StandardInstrumentations.h
llvm/lib/Passes/PassBuilder.cpp
llvm/lib/Passes/PassRegistry.def
llvm/lib/Passes/StandardInstrumentations.cpp
llvm/test/Other/print-on-crash.ll [new file with mode: 0644]

index f023a29..1717f09 100644 (file)
@@ -15,6 +15,7 @@
 #ifndef LLVM_PASSES_STANDARDINSTRUMENTATIONS_H
 #define LLVM_PASSES_STANDARDINSTRUMENTATIONS_H
 
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
@@ -25,6 +26,7 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Transforms/IPO/SampleProfileProbe.h"
 
+#include <mutex>
 #include <string>
 #include <utility>
 
@@ -390,6 +392,35 @@ public:
   void registerCallbacks(PassInstrumentationCallbacks &PIC);
 };
 
+// Print IR on crash.
+class PrintCrashIRInstrumentation {
+public:
+  PrintCrashIRInstrumentation()
+      : SavedIR("*** Dump of IR Before Last Pass Unknown ***") {}
+  ~PrintCrashIRInstrumentation();
+  void registerCallbacks(PassInstrumentationCallbacks &PIC);
+  void reportCrashIR();
+
+protected:
+  std::string SavedIR;
+
+private:
+  // All of the crash reporters that will report on a crash.
+  static DenseSet<PrintCrashIRInstrumentation *> *CrashReporters;
+  // Crash handler registered when print-on-crash is specified.
+  static void SignalHandler(void *);
+  // Exception-safe locking
+  class MtxLock {
+  public:
+    MtxLock() { Mtx.lock(); }
+    ~MtxLock() { Mtx.unlock(); }
+
+  protected:
+    // Avoid races when creating/destroying the crash IR printers.
+    static std::mutex Mtx;
+  };
+};
+
 /// This class provides an interface to register all the standard pass
 /// instrumentations and manages their state (if any).
 class StandardInstrumentations {
@@ -402,6 +433,7 @@ class StandardInstrumentations {
   IRChangedPrinter PrintChangedIR;
   PseudoProbeVerifier PseudoProbeVerification;
   InLineChangePrinter PrintChangedDiff;
+  PrintCrashIRInstrumentation PrintCrashIR;
   VerifyInstrumentation Verify;
 
   bool VerifyEach;
@@ -419,7 +451,6 @@ extern template class TextChangeReporter<std::string>;
 
 extern template class ChangeReporter<ChangedIRData>;
 extern template class TextChangeReporter<ChangedIRData>;
-
 } // namespace llvm
 
 #endif
index 3a32527..3119445 100644 (file)
@@ -438,6 +438,18 @@ bool shouldPopulateClassToPassNames() {
   return !printBeforePasses().empty() || !printAfterPasses().empty();
 }
 
+// A pass for testing -print-on-crash.
+// DO NOT USE THIS EXCEPT FOR TESTING!
+class TriggerCrashPass : public PassInfoMixin<TriggerCrashPass> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+};
+
+// DO NOT USE THIS EXCEPT FOR TESTING!
+PreservedAnalyses TriggerCrashPass::run(Function &, FunctionAnalysisManager &) {
+  __builtin_trap();
+}
+
 } // namespace
 
 PassBuilder::PassBuilder(bool DebugLogging, TargetMachine *TM,
index 579143d..ca3ed7f 100644 (file)
@@ -311,6 +311,7 @@ FUNCTION_PASS("sroa", SROA())
 FUNCTION_PASS("strip-gc-relocates", StripGCRelocates())
 FUNCTION_PASS("structurizecfg", StructurizeCFGPass())
 FUNCTION_PASS("tailcallelim", TailCallElimPass())
+FUNCTION_PASS("trigger-crash", TriggerCrashPass())
 FUNCTION_PASS("unify-loop-exits", UnifyLoopExitsPass())
 FUNCTION_PASS("vector-combine", VectorCombinePass())
 FUNCTION_PASS("verify", VerifierPass())
index 6019570..ff2a014 100644 (file)
@@ -15,6 +15,7 @@
 #include "llvm/Passes/StandardInstrumentations.h"
 #include "llvm/ADT/Any.h"
 #include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Analysis/CallGraphSCCPass.h"
 #include "llvm/Analysis/LazyCallGraph.h"
 #include "llvm/IR/PrintPasses.h"
 #include "llvm/IR/Verifier.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Program.h"
+#include "llvm/Support/Signals.h"
 #include "llvm/Support/raw_ostream.h"
 #include <unordered_set>
 #include <vector>
@@ -117,6 +120,12 @@ static cl::opt<std::string>
     DiffBinary("print-changed-diff-path", cl::Hidden, cl::init("diff"),
                cl::desc("system diff used by change reporters"));
 
+// An option to print the IR that was being processed when a pass crashes.
+static cl::opt<bool>
+    PrintCrashIR("print-on-crash",
+                 cl::desc("Print the last form of the IR before crash"),
+                 cl::init(false), cl::Hidden);
+
 namespace {
 
 // Perform a system based diff between \p Before and \p After, using
@@ -1153,6 +1162,68 @@ StandardInstrumentations::StandardInstrumentations(bool DebugLogging,
       PrintChangedDiff(PrintChanged == ChangePrinter::PrintChangedDiffVerbose),
       Verify(DebugLogging), VerifyEach(VerifyEach) {}
 
+std::mutex PrintCrashIRInstrumentation::MtxLock::Mtx;
+DenseSet<PrintCrashIRInstrumentation *>
+    *PrintCrashIRInstrumentation::CrashReporters = nullptr;
+
+void PrintCrashIRInstrumentation::reportCrashIR() { dbgs() << SavedIR; }
+
+void PrintCrashIRInstrumentation::SignalHandler(void *) {
+  // Called by signal handlers so do not lock here
+  // Are any of PrintCrashIRInstrumentation objects still alive?
+  if (!CrashReporters)
+    return;
+
+  assert(PrintCrashIR && "Did not expect to get here without option set.");
+  for (auto I : *CrashReporters)
+    I->reportCrashIR();
+}
+
+PrintCrashIRInstrumentation::~PrintCrashIRInstrumentation() {
+  if (!PrintCrashIR)
+    return;
+
+  MtxLock Lock;
+  assert(CrashReporters && "Expected CrashReporters to be set");
+
+  // Was this registered?
+  DenseSet<PrintCrashIRInstrumentation *>::iterator I =
+      CrashReporters->find(this);
+  if (I == CrashReporters->end())
+    return;
+  CrashReporters->erase(I);
+  if (!CrashReporters->empty())
+    return;
+  delete CrashReporters;
+  CrashReporters = nullptr;
+}
+
+void PrintCrashIRInstrumentation::registerCallbacks(
+    PassInstrumentationCallbacks &PIC) {
+  if (!PrintCrashIR)
+    return;
+
+  {
+    MtxLock Lock;
+    if (!CrashReporters) {
+      CrashReporters = new DenseSet<PrintCrashIRInstrumentation *>();
+      sys::AddSignalHandler(SignalHandler, nullptr);
+    }
+    CrashReporters->insert(this);
+  }
+  PIC.registerBeforeNonSkippedPassCallback([this](StringRef PassID, Any IR) {
+    assert((MtxLock(), CrashReporters && CrashReporters->find(this) !=
+                                             CrashReporters->end()) &&
+           "Expected CrashReporters to be set and containing this");
+    SavedIR.clear();
+    SmallString<80> Banner =
+        formatv("*** Dump of {0}IR Before Last Pass {1} Started ***",
+                llvm::forcePrintModuleIR() ? "Module " : "", PassID);
+    raw_string_ostream OS(SavedIR);
+    unwrapAndPrint(OS, IR, Banner, llvm::forcePrintModuleIR());
+  });
+}
+
 void StandardInstrumentations::registerCallbacks(
     PassInstrumentationCallbacks &PIC) {
   PrintIR.registerCallbacks(PIC);
@@ -1166,6 +1237,7 @@ void StandardInstrumentations::registerCallbacks(
   if (VerifyEach)
     Verify.registerCallbacks(PIC);
   PrintChangedDiff.registerCallbacks(PIC);
+  PrintCrashIR.registerCallbacks(PIC);
 }
 
 namespace llvm {
diff --git a/llvm/test/Other/print-on-crash.ll b/llvm/test/Other/print-on-crash.ll
new file mode 100644 (file)
index 0000000..42da462
--- /dev/null
@@ -0,0 +1,28 @@
+; A test that the hidden option -print-on-crash properly sets a signal handler
+; which gets called when a pass crashes.  The trigger-crash pass calls
+; __builtin_trap.
+
+; RUN: not --crash opt -print-on-crash -passes=trigger-crash < %s 2>&1 | FileCheck %s --check-prefix=CHECK_SIMPLE
+
+; A test that the signal handler set by the  hidden option -print-on-crash
+; is not called when no pass crashes.
+
+; RUN: opt -print-on-crash -passes="default<O2>" < %s 2>&1 | FileCheck %s --check-prefix=CHECK_NO_CRASH
+
+; RUN: not --crash opt -print-on-crash -print-module-scope -passes=trigger-crash < %s 2>&1 | FileCheck %s --check-prefix=CHECK_MODULE
+
+; The input corresponds to "int main() { return 0; }" but is irrelevant.
+
+; CHECK_SIMPLE: *** Dump of IR Before Last Pass {{.*}} Started ***
+; CHECK_SIMPLE: @main
+; CHECK_SIMPLE: entry:
+; CHECK_NO_CRASH-NOT: *** Dump of IR
+; CHECK_MODULE: *** Dump of Module IR Before Last Pass {{.*}} Started ***
+; CHECK_MODULE: ; ModuleID = {{.*}}
+
+define i32 @main() {
+entry:
+  %retval = alloca i32, align 4
+  store i32 0, i32* %retval, align 4
+  ret i32 0
+}