[Mlir-commits] [mlir] 03e29a4 - [mlir][Pass] Enable the option for reproducer generation without crashing (#75421)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Jan 3 09:36:47 PST 2024
Author: Puyan Lotfi
Date: 2024-01-03T12:36:43-05:00
New Revision: 03e29a49d9827532499234e3e460e2b5b29a11a7
URL: https://github.com/llvm/llvm-project/commit/03e29a49d9827532499234e3e460e2b5b29a11a7
DIFF: https://github.com/llvm/llvm-project/commit/03e29a49d9827532499234e3e460e2b5b29a11a7.diff
LOG: [mlir][Pass] Enable the option for reproducer generation without crashing (#75421)
This PR adds API `makeReproducer` and cl::opt flag
`--mlir-generate-reproducer=<filename>` in order to allow for mlir
reproducer dumps even when the pipeline doesn't crash.
This PR also decouples the code that handles generation of an MLIR
reproducer from the crash recovery portion. The purpose is to allow for
generating reproducers outside of the context of a compiler 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 issues
using offline tools, where being able to dump the reproducer from a
runtime compiler would be helpful.
Added:
mlir/test/Pass/crashless-reproducer.mlir
Modified:
mlir/include/mlir/Pass/PassManager.h
mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h
mlir/lib/Pass/Pass.cpp
mlir/lib/Pass/PassCrashRecovery.cpp
mlir/lib/Pass/PassDetail.h
mlir/lib/Tools/mlir-opt/MlirOptMain.cpp
Removed:
################################################################################
diff --git a/mlir/include/mlir/Pass/PassManager.h b/mlir/include/mlir/Pass/PassManager.h
index d5f1ea0fe0350d..1b2e6a3bc82bb4 100644
--- a/mlir/include/mlir/Pass/PassManager.h
+++ b/mlir/include/mlir/Pass/PassManager.h
@@ -207,6 +207,27 @@ enum class PassDisplayMode {
Pipeline,
};
+/// Streams on which to output crash reproducer.
+struct ReproducerStream {
+ virtual ~ReproducerStream() = default;
+
+ /// Description of the reproducer stream.
+ virtual StringRef description() = 0;
+
+ /// Stream on which to output reproducer.
+ virtual raw_ostream &os() = 0;
+};
+
+/// Method type for constructing ReproducerStream.
+using ReproducerStreamFactory =
+ std::function<std::unique_ptr<ReproducerStream>(std::string &error)>;
+
+std::string
+makeReproducer(StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes,
+ Operation *op, StringRef outputFile, bool disableThreads = false,
+ bool verifyPasses = false);
+
/// The main pass manager and pipeline builder.
class PassManager : public OpPassManager {
public:
@@ -243,21 +264,6 @@ class PassManager : public OpPassManager {
void enableCrashReproducerGeneration(StringRef outputFile,
bool genLocalReproducer = false);
- /// Streams on which to output crash reproducer.
- struct ReproducerStream {
- virtual ~ReproducerStream() = default;
-
- /// Description of the reproducer stream.
- virtual StringRef description() = 0;
-
- /// Stream on which to output reproducer.
- virtual raw_ostream &os() = 0;
- };
-
- /// Method type for constructing ReproducerStream.
- using ReproducerStreamFactory =
- std::function<std::unique_ptr<ReproducerStream>(std::string &error)>;
-
/// Enable support for the pass manager to generate a reproducer on the event
/// of a crash or a pass failure. `factory` is used to construct the streams
/// to write the generated reproducer to. If `genLocalReproducer` is true, the
diff --git a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h
index e255d9fa70b659..6e90fad1618d21 100644
--- a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h
+++ b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h
@@ -173,6 +173,9 @@ class MlirOptMainConfig {
}
bool shouldVerifyRoundtrip() const { return verifyRoundtripFlag; }
+ /// Reproducer file generation (no crash required).
+ StringRef getReproducerFilename() const { return generateReproducerFileFlag; }
+
protected:
/// Allow operation with no registered dialects.
/// This option is for convenience during testing only and discouraged in
@@ -228,6 +231,9 @@ class MlirOptMainConfig {
/// Verify that the input IR round-trips perfectly.
bool verifyRoundtripFlag = false;
+
+ /// The reproducer output filename (no crash required).
+ std::string generateReproducerFileFlag = "";
};
/// This defines the function type used to setup the pass manager. This can be
diff --git a/mlir/lib/Pass/Pass.cpp b/mlir/lib/Pass/Pass.cpp
index 810d6a357d52c0..5ee0ae6525d179 100644
--- a/mlir/lib/Pass/Pass.cpp
+++ b/mlir/lib/Pass/Pass.cpp
@@ -382,16 +382,22 @@ StringRef OpPassManager::getOpAnchorName() const {
/// Prints out the passes of the pass manager as the textual representation
/// of pipelines.
-void OpPassManager::printAsTextualPipeline(raw_ostream &os) const {
- os << getOpAnchorName() << "(";
+void printAsTextualPipeline(
+ raw_ostream &os, StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes) {
+ os << anchorName << "(";
llvm::interleave(
- impl->passes,
- [&](const std::unique_ptr<Pass> &pass) {
- pass->printAsTextualPipeline(os);
- },
+ passes, [&](mlir::Pass &pass) { pass.printAsTextualPipeline(os); },
[&]() { os << ","; });
os << ")";
}
+void OpPassManager::printAsTextualPipeline(raw_ostream &os) const {
+ StringRef anchorName = getOpAnchorName();
+ ::printAsTextualPipeline(
+ os, anchorName,
+ {MutableArrayRef<std::unique_ptr<Pass>>{impl->passes}.begin(),
+ MutableArrayRef<std::unique_ptr<Pass>>{impl->passes}.end()});
+}
void OpPassManager::dump() {
llvm::errs() << "Pass Manager with " << impl->passes.size() << " passes:\n";
diff --git a/mlir/lib/Pass/PassCrashRecovery.cpp b/mlir/lib/Pass/PassCrashRecovery.cpp
index df1a0762ae34a5..3f3928ec8b61b6 100644
--- a/mlir/lib/Pass/PassCrashRecovery.cpp
+++ b/mlir/lib/Pass/PassCrashRecovery.cpp
@@ -38,7 +38,7 @@ namespace detail {
/// reproducers when a signal is raised, such as a segfault.
struct RecoveryReproducerContext {
RecoveryReproducerContext(std::string passPipelineStr, Operation *op,
- PassManager::ReproducerStreamFactory &streamFactory,
+ ReproducerStreamFactory &streamFactory,
bool verifyPasses);
~RecoveryReproducerContext();
@@ -67,7 +67,7 @@ struct RecoveryReproducerContext {
/// The factory for the reproducer output stream to use when generating the
/// reproducer.
- PassManager::ReproducerStreamFactory &streamFactory;
+ ReproducerStreamFactory &streamFactory;
/// Various pass manager and context flags.
bool disableThreads;
@@ -92,7 +92,7 @@ llvm::ManagedStatic<llvm::SmallSetVector<RecoveryReproducerContext *, 1>>
RecoveryReproducerContext::RecoveryReproducerContext(
std::string passPipelineStr, Operation *op,
- PassManager::ReproducerStreamFactory &streamFactory, bool verifyPasses)
+ ReproducerStreamFactory &streamFactory, bool verifyPasses)
: pipelineElements(std::move(passPipelineStr)),
preCrashOperation(op->clone()), streamFactory(streamFactory),
disableThreads(!op->getContext()->isMultithreadingEnabled()),
@@ -106,22 +106,24 @@ RecoveryReproducerContext::~RecoveryReproducerContext() {
disable();
}
-void RecoveryReproducerContext::generate(std::string &description) {
+static void appendReproducer(std::string &description, Operation *op,
+ const ReproducerStreamFactory &factory,
+ const std::string &pipelineElements,
+ bool disableThreads, bool verifyPasses) {
llvm::raw_string_ostream descOS(description);
// Try to create a new output stream for this crash reproducer.
std::string error;
- std::unique_ptr<PassManager::ReproducerStream> stream = streamFactory(error);
+ std::unique_ptr<ReproducerStream> stream = factory(error);
if (!stream) {
descOS << "failed to create output stream: " << error;
return;
}
descOS << "reproducer generated at `" << stream->description() << "`";
- std::string pipeline = (preCrashOperation->getName().getStringRef() + "(" +
- pipelineElements + ")")
- .str();
- AsmState state(preCrashOperation);
+ std::string pipeline =
+ (op->getName().getStringRef() + "(" + pipelineElements + ")").str();
+ AsmState state(op);
state.attachResourcePrinter(
"mlir_reproducer", [&](Operation *op, AsmResourceBuilder &builder) {
builder.buildString("pipeline", pipeline);
@@ -130,7 +132,12 @@ void RecoveryReproducerContext::generate(std::string &description) {
});
// Output the .mlir module.
- preCrashOperation->print(stream->os(), state);
+ op->print(stream->os(), state);
+}
+
+void RecoveryReproducerContext::generate(std::string &description) {
+ appendReproducer(description, preCrashOperation, streamFactory,
+ pipelineElements, disableThreads, verifyPasses);
}
void RecoveryReproducerContext::disable() {
@@ -175,12 +182,11 @@ void RecoveryReproducerContext::registerSignalHandler() {
//===----------------------------------------------------------------------===//
struct PassCrashReproducerGenerator::Impl {
- Impl(PassManager::ReproducerStreamFactory &streamFactory,
- bool localReproducer)
+ Impl(ReproducerStreamFactory &streamFactory, bool localReproducer)
: streamFactory(streamFactory), localReproducer(localReproducer) {}
/// The factory to use when generating a crash reproducer.
- PassManager::ReproducerStreamFactory streamFactory;
+ ReproducerStreamFactory streamFactory;
/// Flag indicating if reproducer generation should be localized to the
/// failing pass.
@@ -198,7 +204,7 @@ struct PassCrashReproducerGenerator::Impl {
};
PassCrashReproducerGenerator::PassCrashReproducerGenerator(
- PassManager::ReproducerStreamFactory &streamFactory, bool localReproducer)
+ ReproducerStreamFactory &streamFactory, bool localReproducer)
: impl(std::make_unique<Impl>(streamFactory, localReproducer)) {}
PassCrashReproducerGenerator::~PassCrashReproducerGenerator() = default;
@@ -382,9 +388,9 @@ struct CrashReproducerInstrumentation : public PassInstrumentation {
//===----------------------------------------------------------------------===//
namespace {
-/// This class represents a default instance of PassManager::ReproducerStream
+/// This class represents a default instance of mlir::ReproducerStream
/// that is backed by a file.
-struct FileReproducerStream : public PassManager::ReproducerStream {
+struct FileReproducerStream : public mlir::ReproducerStream {
FileReproducerStream(std::unique_ptr<llvm::ToolOutputFile> outputFile)
: outputFile(std::move(outputFile)) {}
~FileReproducerStream() override { outputFile->keep(); }
@@ -418,22 +424,45 @@ LogicalResult PassManager::runWithCrashRecovery(Operation *op,
return passManagerResult;
}
-void PassManager::enableCrashReproducerGeneration(StringRef outputFile,
- bool genLocalReproducer) {
+static ReproducerStreamFactory
+makeReproducerStreamFactory(StringRef outputFile) {
// Capture the filename by value in case outputFile is out of scope when
// invoked.
std::string filename = outputFile.str();
- enableCrashReproducerGeneration(
- [filename](std::string &error) -> std::unique_ptr<ReproducerStream> {
- std::unique_ptr<llvm::ToolOutputFile> outputFile =
- mlir::openOutputFile(filename, &error);
- if (!outputFile) {
- error = "Failed to create reproducer stream: " + error;
- return nullptr;
- }
- return std::make_unique<FileReproducerStream>(std::move(outputFile));
- },
- genLocalReproducer);
+ return [filename](std::string &error) -> std::unique_ptr<ReproducerStream> {
+ std::unique_ptr<llvm::ToolOutputFile> outputFile =
+ mlir::openOutputFile(filename, &error);
+ if (!outputFile) {
+ error = "Failed to create reproducer stream: " + error;
+ return nullptr;
+ }
+ return std::make_unique<FileReproducerStream>(std::move(outputFile));
+ };
+}
+
+void printAsTextualPipeline(
+ raw_ostream &os, StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes);
+
+std::string mlir::makeReproducer(
+ StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes,
+ Operation *op, StringRef outputFile, bool disableThreads,
+ bool verifyPasses) {
+
+ std::string description;
+ std::string pipelineStr;
+ llvm::raw_string_ostream passOS(pipelineStr);
+ ::printAsTextualPipeline(passOS, anchorName, passes);
+ appendReproducer(description, op, makeReproducerStreamFactory(outputFile),
+ pipelineStr, disableThreads, verifyPasses);
+ return description;
+}
+
+void PassManager::enableCrashReproducerGeneration(StringRef outputFile,
+ bool genLocalReproducer) {
+ enableCrashReproducerGeneration(makeReproducerStreamFactory(outputFile),
+ genLocalReproducer);
}
void PassManager::enableCrashReproducerGeneration(
diff --git a/mlir/lib/Pass/PassDetail.h b/mlir/lib/Pass/PassDetail.h
index 0e964b6d6d36bc..5cc726295c9f11 100644
--- a/mlir/lib/Pass/PassDetail.h
+++ b/mlir/lib/Pass/PassDetail.h
@@ -98,9 +98,8 @@ class OpToOpPassAdaptor
class PassCrashReproducerGenerator {
public:
- PassCrashReproducerGenerator(
- PassManager::ReproducerStreamFactory &streamFactory,
- bool localReproducer);
+ PassCrashReproducerGenerator(ReproducerStreamFactory &streamFactory,
+ bool localReproducer);
~PassCrashReproducerGenerator();
/// Initialize the generator in preparation for reproducer generation. The
diff --git a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp
index d7d47619ef4ac9..5395aa2b502d78 100644
--- a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp
+++ b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp
@@ -151,6 +151,16 @@ struct MlirOptMainConfigCLOptions : public MlirOptMainConfig {
static cl::list<std::string> passPlugins(
"load-pass-plugin", cl::desc("Load passes from plugin library"));
+
+ static cl::opt<std::string, /*ExternalStorage=*/true>
+ generateReproducerFile(
+ "mlir-generate-reproducer",
+ llvm::cl::desc(
+ "Generate an mlir reproducer at the provided filename"
+ " (no crash required)"),
+ cl::location(generateReproducerFileFlag), cl::init(""),
+ cl::value_desc("filename"));
+
/// Set the callback to load a pass plugin.
passPlugins.setCallback([&](const std::string &pluginPath) {
auto plugin = PassPlugin::load(pluginPath);
@@ -384,6 +394,14 @@ performActions(raw_ostream &os,
if (failed(pm.run(*op)))
return failure();
+ // Generate reproducers if requested
+ if (!config.getReproducerFilename().empty()) {
+ StringRef anchorName = pm.getAnyOpAnchorName();
+ const auto &passes = pm.getPasses();
+ makeReproducer(anchorName, passes, op.get(),
+ config.getReproducerFilename());
+ }
+
// Print the output.
TimingScope outputTiming = timing.nest("Output");
if (config.shouldEmitBytecode()) {
diff --git a/mlir/test/Pass/crashless-reproducer.mlir b/mlir/test/Pass/crashless-reproducer.mlir
new file mode 100644
index 00000000000000..d874d90b8f3dbd
--- /dev/null
+++ b/mlir/test/Pass/crashless-reproducer.mlir
@@ -0,0 +1,10 @@
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(builtin.module(test-module-pass))' --mlir-generate-reproducer=%t -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(any(builtin.module(test-module-pass)))"
More information about the Mlir-commits
mailing list