diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h --- a/llvm/include/llvm/Passes/StandardInstrumentations.h +++ b/llvm/include/llvm/Passes/StandardInstrumentations.h @@ -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 #include #include @@ -390,6 +392,35 @@ 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 *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 @@ IRChangedPrinter PrintChangedIR; PseudoProbeVerifier PseudoProbeVerification; InLineChangePrinter PrintChangedDiff; + PrintCrashIRInstrumentation PrintCrashIR; VerifyInstrumentation Verify; bool VerifyEach; @@ -419,7 +451,6 @@ extern template class ChangeReporter; extern template class TextChangeReporter; - } // namespace llvm #endif diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -438,6 +438,18 @@ return !printBeforePasses().empty() || !printAfterPasses().empty(); } +// A pass for testing -print-on-crash. +// DO NOT USE THIS EXCEPT FOR TESTING! +class TriggerCrashPass : public PassInfoMixin { +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, diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -311,6 +311,7 @@ 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()) diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp --- a/llvm/lib/Passes/StandardInstrumentations.cpp +++ b/llvm/lib/Passes/StandardInstrumentations.cpp @@ -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" @@ -25,10 +26,12 @@ #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 #include @@ -117,6 +120,12 @@ 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 + 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 @@ PrintChangedDiff(PrintChanged == ChangePrinter::PrintChangedDiffVerbose), Verify(DebugLogging), VerifyEach(VerifyEach) {} +std::mutex PrintCrashIRInstrumentation::MtxLock::Mtx; +DenseSet + *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::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(); + 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 @@ 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 --- /dev/null +++ b/llvm/test/Other/print-on-crash.ll @@ -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" < %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 +}