[Mlir-commits] [mlir] [mlir][Pass] Enable the option for reproducer generation without crashing (PR #75421)

Puyan Lotfi llvmlistbot at llvm.org
Wed Dec 13 18:11:39 PST 2023


https://github.com/plotfi updated https://github.com/llvm/llvm-project/pull/75421

>From 02d37a9c97a8d5fef55096c2f788f04a50feda3a Mon Sep 17 00:00:00 2001
From: Puyan Lotfi <puyan at puyan.org>
Date: Wed, 13 Dec 2023 17:46:42 -0800
Subject: [PATCH] [mlir][Pass] Enable the option for reproducer generation
 without crashing

Adding API `enableReproducerGeneration` (with an optional
alwaysGenerateReproducer parameter) and cl::opt flag
`-mlir-pass-pipeline-always-generate-reproducer` in order to allow for
mlir reproducer dumps even when the pipeline doesn't crash.

This will be useful for frameworks and runtimes that use MLIR where it
is needed to reproduce the pipeline behavior for reasons outside of
diagnosing crashes. An example is for diagnosing performance issue using
offline tools, where being able to dump the reproducer from a runtime
compiler would be helpful.
---
 mlir/include/mlir/Pass/PassManager.h     | 20 ++++++
 mlir/lib/Pass/PassCrashRecovery.cpp      | 77 ++++++++++++++++--------
 mlir/lib/Pass/PassDetail.h               |  4 +-
 mlir/lib/Pass/PassManagerOptions.cpp     | 17 +++++-
 mlir/test/Pass/crashless-reproducer.mlir | 13 ++++
 5 files changed, 101 insertions(+), 30 deletions(-)
 create mode 100644 mlir/test/Pass/crashless-reproducer.mlir

diff --git a/mlir/include/mlir/Pass/PassManager.h b/mlir/include/mlir/Pass/PassManager.h
index d5f1ea0fe0350d..e6461d90abea98 100644
--- a/mlir/include/mlir/Pass/PassManager.h
+++ b/mlir/include/mlir/Pass/PassManager.h
@@ -243,6 +243,16 @@ class PassManager : public OpPassManager {
   void enableCrashReproducerGeneration(StringRef outputFile,
                                        bool genLocalReproducer = false);
 
+  /// Enable support for the pass manager to generate a reproducer. With this
+  /// invocation the reproducer is generated depending on the parameter
+  /// `alwaysGenerateReproducer`. `outputFile` is a .mlir filename used to
+  /// write the generated reproducer. If `genLocalReproducer` is true, the pass
+  /// manager will attempt to generate a local reproducer that contains the
+  /// smallest pipeline.
+  void enableReproducerGeneration(StringRef outputFile,
+                                  bool genLocalReproducer = false,
+                                  bool alwaysGenerateReproducer = false);
+
   /// Streams on which to output crash reproducer.
   struct ReproducerStream {
     virtual ~ReproducerStream() = default;
@@ -266,6 +276,16 @@ class PassManager : public OpPassManager {
   void enableCrashReproducerGeneration(ReproducerStreamFactory factory,
                                        bool genLocalReproducer = false);
 
+  /// Enable support for the pass manager to generate a reproducer. With this
+  /// invocation the reproducer is generated depending on the parameter
+  /// `alwaysGenerateReproducer`. `factory` is used to construct the streams
+  /// to write the generated reproducer to. If `genLocalReproducer` is true, the
+  /// pass manager will attempt to generate a local reproducer that contains the
+  /// smallest pipeline.
+  void enableReproducerGeneration(ReproducerStreamFactory factory,
+                                  bool genLocalReproducer = false,
+                                  bool alwaysGenerateReproducer = false);
+
   /// Runs the verifier after each individual pass.
   void enableVerifier(bool enabled = true);
 
diff --git a/mlir/lib/Pass/PassCrashRecovery.cpp b/mlir/lib/Pass/PassCrashRecovery.cpp
index df1a0762ae34a5..224584fcc517d1 100644
--- a/mlir/lib/Pass/PassCrashRecovery.cpp
+++ b/mlir/lib/Pass/PassCrashRecovery.cpp
@@ -176,8 +176,9 @@ void RecoveryReproducerContext::registerSignalHandler() {
 
 struct PassCrashReproducerGenerator::Impl {
   Impl(PassManager::ReproducerStreamFactory &streamFactory,
-       bool localReproducer)
-      : streamFactory(streamFactory), localReproducer(localReproducer) {}
+       bool localReproducer, bool alwaysGenerateReproducer = false)
+      : streamFactory(streamFactory), localReproducer(localReproducer),
+        alwaysGenerateReproducer(alwaysGenerateReproducer) {}
 
   /// The factory to use when generating a crash reproducer.
   PassManager::ReproducerStreamFactory streamFactory;
@@ -195,11 +196,17 @@ struct PassCrashReproducerGenerator::Impl {
 
   /// Various pass manager flags that get emitted when generating a reproducer.
   bool pmFlagVerifyPasses = false;
+
+  /// Flag indicating if reproducer generation should occur regardless of
+  /// a crash or failing pass.
+  bool alwaysGenerateReproducer = false;
 };
 
 PassCrashReproducerGenerator::PassCrashReproducerGenerator(
-    PassManager::ReproducerStreamFactory &streamFactory, bool localReproducer)
-    : impl(std::make_unique<Impl>(streamFactory, localReproducer)) {}
+    PassManager::ReproducerStreamFactory &streamFactory, bool localReproducer,
+    bool alwaysGenerateReproducer)
+    : impl(std::make_unique<Impl>(streamFactory, localReproducer,
+                                  alwaysGenerateReproducer)) {}
 PassCrashReproducerGenerator::~PassCrashReproducerGenerator() = default;
 
 void PassCrashReproducerGenerator::initialize(
@@ -235,13 +242,10 @@ void PassCrashReproducerGenerator::finalize(Operation *rootOp,
     return;
 
   // If the pass manager execution succeeded, we don't generate any reproducers.
-  if (succeeded(executionResult))
+  const bool executionResultSucceeded = succeeded(executionResult);
+  if (executionResultSucceeded && !impl->alwaysGenerateReproducer)
     return impl->activeContexts.clear();
 
-  InFlightDiagnostic diag = emitError(rootOp->getLoc())
-                            << "Failures have been detected while "
-                               "processing an MLIR pass pipeline";
-
   // If we are generating a global reproducer, we include all of the running
   // passes in the error message for the only active context.
   if (!impl->localReproducer) {
@@ -251,13 +255,19 @@ void PassCrashReproducerGenerator::finalize(Operation *rootOp,
     std::string description;
     impl->activeContexts.front()->generate(description);
 
-    // Emit an error to the user.
-    Diagnostic &note = diag.attachNote() << "Pipeline failed while executing [";
-    llvm::interleaveComma(impl->runningPasses, note,
-                          [&](const std::pair<Pass *, Operation *> &value) {
-                            formatPassOpReproducerMessage(note, value);
-                          });
-    note << "]: " << description;
+    if (!executionResultSucceeded) {
+      InFlightDiagnostic diag = emitError(rootOp->getLoc())
+                                << "Failures have been detected while "
+                                   "processing an MLIR pass pipeline";
+      // Emit an error to the user.
+      Diagnostic &note = diag.attachNote()
+                         << "Pipeline failed while executing [";
+      llvm::interleaveComma(impl->runningPasses, note,
+                            [&](const std::pair<Pass *, Operation *> &value) {
+                              formatPassOpReproducerMessage(note, value);
+                            });
+      note << "]: " << description;
+    }
     impl->runningPasses.clear();
     impl->activeContexts.clear();
     return;
@@ -274,10 +284,15 @@ void PassCrashReproducerGenerator::finalize(Operation *rootOp,
   std::string description;
   reproducerContext.generate(description);
 
-  // Emit an error to the user.
-  Diagnostic &note = diag.attachNote() << "Pipeline failed while executing ";
-  formatPassOpReproducerMessage(note, impl->runningPasses.back());
-  note << ": " << description;
+  if (!executionResultSucceeded) {
+    InFlightDiagnostic diag = emitError(rootOp->getLoc())
+                              << "Failures have been detected while "
+                                 "processing an MLIR pass pipeline";
+    // Emit an error to the user.
+    Diagnostic &note = diag.attachNote() << "Pipeline failed while executing ";
+    formatPassOpReproducerMessage(note, impl->runningPasses.back());
+    note << ": " << description;
+  }
 
   impl->activeContexts.clear();
   impl->runningPasses.clear();
@@ -420,10 +435,21 @@ LogicalResult PassManager::runWithCrashRecovery(Operation *op,
 
 void PassManager::enableCrashReproducerGeneration(StringRef outputFile,
                                                   bool genLocalReproducer) {
+  enableReproducerGeneration(outputFile, genLocalReproducer);
+}
+
+void PassManager::enableCrashReproducerGeneration(
+    ReproducerStreamFactory factory, bool genLocalReproducer) {
+  enableReproducerGeneration(factory, genLocalReproducer);
+}
+
+void PassManager::enableReproducerGeneration(StringRef outputFile,
+                                             bool genLocalReproducer,
+                                             bool alwaysGenerateReproducer) {
   // Capture the filename by value in case outputFile is out of scope when
   // invoked.
   std::string filename = outputFile.str();
-  enableCrashReproducerGeneration(
+  enableReproducerGeneration(
       [filename](std::string &error) -> std::unique_ptr<ReproducerStream> {
         std::unique_ptr<llvm::ToolOutputFile> outputFile =
             mlir::openOutputFile(filename, &error);
@@ -433,11 +459,12 @@ void PassManager::enableCrashReproducerGeneration(StringRef outputFile,
         }
         return std::make_unique<FileReproducerStream>(std::move(outputFile));
       },
-      genLocalReproducer);
+      genLocalReproducer, alwaysGenerateReproducer);
 }
 
-void PassManager::enableCrashReproducerGeneration(
-    ReproducerStreamFactory factory, bool genLocalReproducer) {
+void PassManager::enableReproducerGeneration(ReproducerStreamFactory factory,
+                                             bool genLocalReproducer,
+                                             bool alwaysGenerateReproducer) {
   assert(!crashReproGenerator &&
          "crash reproducer has already been initialized");
   if (genLocalReproducer && getContext()->isMultithreadingEnabled())
@@ -446,7 +473,7 @@ void PassManager::enableCrashReproducerGeneration(
         "pass-manager without disabling multi-threading first.");
 
   crashReproGenerator = std::make_unique<PassCrashReproducerGenerator>(
-      factory, genLocalReproducer);
+      factory, genLocalReproducer, alwaysGenerateReproducer);
   addInstrumentation(
       std::make_unique<CrashReproducerInstrumentation>(*crashReproGenerator));
 }
diff --git a/mlir/lib/Pass/PassDetail.h b/mlir/lib/Pass/PassDetail.h
index 0e964b6d6d36bc..0cddaf8a054039 100644
--- a/mlir/lib/Pass/PassDetail.h
+++ b/mlir/lib/Pass/PassDetail.h
@@ -99,8 +99,8 @@ class OpToOpPassAdaptor
 class PassCrashReproducerGenerator {
 public:
   PassCrashReproducerGenerator(
-      PassManager::ReproducerStreamFactory &streamFactory,
-      bool localReproducer);
+      PassManager::ReproducerStreamFactory &streamFactory, bool localReproducer,
+      bool alwaysGenerateReproducer = false);
   ~PassCrashReproducerGenerator();
 
   /// Initialize the generator in preparation for reproducer generation. The
diff --git a/mlir/lib/Pass/PassManagerOptions.cpp b/mlir/lib/Pass/PassManagerOptions.cpp
index ffc53b7e3ed023..d77aed772fee85 100644
--- a/mlir/lib/Pass/PassManagerOptions.cpp
+++ b/mlir/lib/Pass/PassManagerOptions.cpp
@@ -29,6 +29,10 @@ struct PassManagerOptions {
       llvm::cl::desc("When generating a crash reproducer, attempt to generated "
                      "a reproducer with the smallest pipeline."),
       llvm::cl::init(false)};
+  llvm::cl::opt<bool> alwaysGenerateReproducer{
+      "mlir-pass-pipeline-always-generate-reproducer",
+      llvm::cl::desc("Generating a reproducer even if a crash did not occur "),
+      llvm::cl::init(false)};
 
   //===--------------------------------------------------------------------===//
   // IR Printing
@@ -135,9 +139,16 @@ LogicalResult mlir::applyPassManagerCLOptions(PassManager &pm) {
     return failure();
 
   // Generate a reproducer on crash/failure.
-  if (options->reproducerFile.getNumOccurrences())
-    pm.enableCrashReproducerGeneration(options->reproducerFile,
-                                       options->localReproducer);
+  if (options->reproducerFile.getNumOccurrences()) {
+    if (options->alwaysGenerateReproducer) {
+      pm.enableReproducerGeneration(options->reproducerFile,
+                                    options->localReproducer,
+                                    true /*alwaysGenerateReproducer*/);
+    } else {
+      pm.enableCrashReproducerGeneration(options->reproducerFile,
+                                         options->localReproducer);
+    }
+  }
 
   // Enable statistics dumping.
   if (options->passStatistics)
diff --git a/mlir/test/Pass/crashless-reproducer.mlir b/mlir/test/Pass/crashless-reproducer.mlir
new file mode 100644
index 00000000000000..e34efaeace89ea
--- /dev/null
+++ b/mlir/test/Pass/crashless-reproducer.mlir
@@ -0,0 +1,13 @@
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(builtin.module(test-module-pass))' \
+// RUN:   -mlir-pass-pipeline-crash-reproducer=%t \
+// RUN:   -mlir-pass-pipeline-always-generate-reproducer=true -verify-diagnostics
+
+// RUN: cat %t | FileCheck -check-prefix=REPRO %s
+
+module @inner_mod1 {
+  module @foo {}
+}
+
+// REPRO: module @inner_mod1
+// REPRO: module @foo {
+// REPRO: pipeline: "builtin.module(builtin.module(test-module-pass))"



More information about the Mlir-commits mailing list