//===- PassManager.h - Pass Management Interface ----------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef MLIR_PASS_PASSMANAGER_H #define MLIR_PASS_PASSMANAGER_H #include "mlir/IR/Dialect.h" #include "mlir/IR/OperationSupport.h" #include "mlir/Support/Timing.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator.h" #include "llvm/Support/raw_ostream.h" #include #include namespace mlir { class AnalysisManager; class MLIRContext; class Operation; class Pass; class PassInstrumentation; class PassInstrumentor; namespace detail { struct OpPassManagerImpl; class OpToOpPassAdaptor; class PassCrashReproducerGenerator; struct PassExecutionState; } // namespace detail //===----------------------------------------------------------------------===// // OpPassManager //===----------------------------------------------------------------------===// /// This class represents a pass manager that runs passes on either a specific /// operation type, or any isolated operation. This pass manager can not be run /// on an operation directly, but must be run either as part of a top-level /// `PassManager`(e.g. when constructed via `nest` calls), or dynamically within /// a pass by using the `Pass::runPipeline` API. class OpPassManager { public: /// This enum represents the nesting behavior of the pass manager. enum class Nesting { /// Implicit nesting behavior. This allows for adding passes operating on /// operations different from this pass manager, in which case a new pass /// manager is implicitly nested for the operation type of the new pass. Implicit, /// Explicit nesting behavior. This requires that any passes added to this /// pass manager support its operation type. Explicit }; /// Construct a new op-agnostic ("any") pass manager with the given operation /// type and nesting behavior. This is the same as invoking: /// `OpPassManager(getAnyOpAnchorName(), nesting)`. OpPassManager(Nesting nesting = Nesting::Explicit); /// Construct a new pass manager with the given anchor operation type and /// nesting behavior. OpPassManager(StringRef name, Nesting nesting = Nesting::Explicit); OpPassManager(OperationName name, Nesting nesting = Nesting::Explicit); OpPassManager(OpPassManager &&rhs); OpPassManager(const OpPassManager &rhs); ~OpPassManager(); OpPassManager &operator=(const OpPassManager &rhs); OpPassManager &operator=(OpPassManager &&rhs); /// Iterator over the passes in this pass manager. using pass_iterator = llvm::pointee_iterator>::iterator>; pass_iterator begin(); pass_iterator end(); iterator_range getPasses() { return {begin(), end()}; } using const_pass_iterator = llvm::pointee_iterator>::const_iterator>; const_pass_iterator begin() const; const_pass_iterator end() const; iterator_range getPasses() const { return {begin(), end()}; } /// Returns true if the pass manager has no passes. bool empty() const { return begin() == end(); } /// Nest a new operation pass manager for the given operation kind under this /// pass manager. OpPassManager &nest(OperationName nestedName); OpPassManager &nest(StringRef nestedName); template OpPassManager &nest() { return nest(OpT::getOperationName()); } /// Nest a new op-agnostic ("any") pass manager under this pass manager. /// Note: This is the same as invoking `nest(getAnyOpAnchorName())`. OpPassManager &nestAny(); /// Add the given pass to this pass manager. If this pass has a concrete /// operation type, it must be the same type as this pass manager. void addPass(std::unique_ptr pass); /// Clear the pipeline, but not the other options set on this OpPassManager. void clear(); /// Add the given pass to a nested pass manager for the given operation kind /// `OpT`. template void addNestedPass(std::unique_ptr pass) { nest().addPass(std::move(pass)); } /// Returns the number of passes held by this manager. size_t size() const; /// Return the operation name that this pass manager operates on, or /// std::nullopt if this is an op-agnostic pass manager. std::optional getOpName(MLIRContext &context) const; /// Return the operation name that this pass manager operates on, or /// std::nullopt if this is an op-agnostic pass manager. std::optional getOpName() const; /// Return the name used to anchor this pass manager. This is either the name /// of an operation, or the result of `getAnyOpAnchorName()` in the case of an /// op-agnostic pass manager. StringRef getOpAnchorName() const; /// Return the string name used to anchor op-agnostic pass managers that /// operate generically on any viable operation. static StringRef getAnyOpAnchorName() { return "any"; } /// Returns the internal implementation instance. detail::OpPassManagerImpl &getImpl(); /// Prints out the passes of the pass manager as the textual representation /// of pipelines. When `pretty` is true, the printed pipeline is formatted /// for readability. /// /// Note: The quality of the string representation depends entirely on the /// the correctness of per-pass overrides of Pass::printAsTextualPipeline. void printAsTextualPipeline(raw_ostream &os, bool pretty = false) const; /// Raw dump of the pass manager to llvm::errs(). void dump(); /// Merge the pass statistics of this class into 'other'. void mergeStatisticsInto(OpPassManager &other); /// Register dependent dialects for the current pass manager. /// This is forwarding to every pass in this PassManager, see the /// documentation for the same method on the Pass class. void getDependentDialects(DialectRegistry &dialects) const; /// Enable or disable the implicit nesting on this particular PassManager. /// This will also apply to any newly nested PassManager built from this /// instance. void setNesting(Nesting nesting); /// Return the current nesting mode. Nesting getNesting(); private: /// Initialize all of the passes within this pass manager with the given /// initialization generation. The initialization generation is used to detect /// if a pass manager has already been initialized. LogicalResult initialize(MLIRContext *context, unsigned newInitGeneration); /// Compute a hash of the pipeline, so that we can detect changes (a pass is /// added...). llvm::hash_code hash(); /// A pointer to an internal implementation instance. std::unique_ptr impl; /// Allow access to initialize. friend detail::OpToOpPassAdaptor; /// Allow access to the constructor. friend class PassManager; friend class Pass; /// Allow access. friend detail::OpPassManagerImpl; }; //===----------------------------------------------------------------------===// // PassManager //===----------------------------------------------------------------------===// /// An enum describing the different display modes for the information within /// the pass manager. enum class PassDisplayMode { // In this mode the results are displayed in a list sorted by total, // with each pass/analysis instance aggregated into one unique result. List, // In this mode the results are displayed in a nested pipeline view that // mirrors the internal pass pipeline that is being executed in the pass // manager. 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::string &error)>; std::string makeReproducer(StringRef anchorName, const llvm::iterator_range &passes, Operation *op, StringRef outputFile, bool disableThreads = false, bool verifyPasses = false); /// The main pass manager and pipeline builder. class PassManager : public OpPassManager { public: /// Create a new pass manager under the given context with a specific nesting /// style. The created pass manager can schedule operations that match /// `operationName`. PassManager(MLIRContext *ctx, StringRef operationName = PassManager::getAnyOpAnchorName(), Nesting nesting = Nesting::Explicit); PassManager(OperationName operationName, Nesting nesting = Nesting::Explicit); ~PassManager(); /// Create a new pass manager under the given context with a specific nesting /// style. The created pass manager can schedule operations that match /// `OperationTy`. template static PassManager on(MLIRContext *ctx, Nesting nesting = Nesting::Explicit) { return PassManager(ctx, OperationTy::getOperationName(), nesting); } /// Run the passes within this manager on the provided operation. The /// specified operation must have the same name as the one provided the pass /// manager on construction. LogicalResult run(Operation *op); /// Return an instance of the context. MLIRContext *getContext() const { return context; } /// Enable support for the pass manager to generate a reproducer on the event /// of a crash or a pass failure. `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 enableCrashReproducerGeneration(StringRef outputFile, bool genLocalReproducer = false); /// 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 /// pass manager will attempt to generate a local reproducer that contains the /// smallest pipeline. void enableCrashReproducerGeneration(ReproducerStreamFactory factory, bool genLocalReproducer = false); /// Runs the verifier after each individual pass. void enableVerifier(bool enabled = true); //===--------------------------------------------------------------------===// // Instrumentations //===--------------------------------------------------------------------===// /// Add the provided instrumentation to the pass manager. void addInstrumentation(std::unique_ptr pi); //===--------------------------------------------------------------------===// // IR Printing /// A configuration struct provided to the IR printer instrumentation. class IRPrinterConfig { public: using PrintCallbackFn = function_ref; /// Initialize the configuration. /// * 'printModuleScope' signals if the top-level module IR should always be /// printed. This should only be set to true when multi-threading is /// disabled, otherwise we may try to print IR that is being modified /// asynchronously. /// * 'printAfterOnlyOnChange' signals that when printing the IR after a /// pass, in the case of a non-failure, we should first check if any /// potential mutations were made. This allows for reducing the number of /// logs that don't contain meaningful changes. /// * 'printAfterOnlyOnFailure' signals that when printing the IR after a /// pass, we only print in the case of a failure. /// - This option should *not* be used with the other `printAfter` flags /// above. /// * 'opPrintingFlags' sets up the printing flags to use when printing the /// IR. explicit IRPrinterConfig( bool printModuleScope = false, bool printAfterOnlyOnChange = false, bool printAfterOnlyOnFailure = false, OpPrintingFlags opPrintingFlags = OpPrintingFlags()); virtual ~IRPrinterConfig(); /// A hook that may be overridden by a derived config that checks if the IR /// of 'operation' should be dumped *before* the pass 'pass' has been /// executed. If the IR should be dumped, 'printCallback' should be invoked /// with the stream to dump into. virtual void printBeforeIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback); /// A hook that may be overridden by a derived config that checks if the IR /// of 'operation' should be dumped *after* the pass 'pass' has been /// executed. If the IR should be dumped, 'printCallback' should be invoked /// with the stream to dump into. virtual void printAfterIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback); /// Returns true if the IR should always be printed at the top-level scope. bool shouldPrintAtModuleScope() const { return printModuleScope; } /// Returns true if the IR should only printed after a pass if the IR /// "changed". bool shouldPrintAfterOnlyOnChange() const { return printAfterOnlyOnChange; } /// Returns true if the IR should only printed after a pass if the pass /// "failed". bool shouldPrintAfterOnlyOnFailure() const { return printAfterOnlyOnFailure; } /// Returns the printing flags to be used to print the IR. OpPrintingFlags getOpPrintingFlags() const { return opPrintingFlags; } private: /// A flag that indicates if the IR should be printed at module scope. bool printModuleScope; /// A flag that indicates that the IR after a pass should only be printed if /// a change is detected. bool printAfterOnlyOnChange; /// A flag that indicates that the IR after a pass should only be printed if /// the pass failed. bool printAfterOnlyOnFailure; /// Flags to control printing behavior. OpPrintingFlags opPrintingFlags; }; /// Add an instrumentation to print the IR before and after pass execution, /// using the provided configuration. void enableIRPrinting(std::unique_ptr config); /// Add an instrumentation to print the IR before and after pass execution, /// using the provided fields to generate a default configuration: /// * 'shouldPrintBeforePass' and 'shouldPrintAfterPass' correspond to filter /// functions that take a 'Pass *' and `Operation *`. These function should /// return true if the IR should be printed or not. /// * 'printModuleScope' signals if the module IR should be printed, even /// for non module passes. /// * 'printAfterOnlyOnChange' signals that when printing the IR after a /// pass, in the case of a non-failure, we should first check if any /// potential mutations were made. /// * 'printAfterOnlyOnFailure' signals that when printing the IR after a /// pass, we only print in the case of a failure. /// - This option should *not* be used with the other `printAfter` flags /// above. /// * 'out' corresponds to the stream to output the printed IR to. /// * 'opPrintingFlags' sets up the printing flags to use when printing the /// IR. void enableIRPrinting( std::function shouldPrintBeforePass = [](Pass *, Operation *) { return true; }, std::function shouldPrintAfterPass = [](Pass *, Operation *) { return true; }, bool printModuleScope = true, bool printAfterOnlyOnChange = true, bool printAfterOnlyOnFailure = false, raw_ostream &out = llvm::errs(), OpPrintingFlags opPrintingFlags = OpPrintingFlags()); /// Similar to `enableIRPrinting` above, except that instead of printing /// the IR to a single output stream, the instrumentation will print the /// output of each pass to a separate file. The files will be organized into a /// directory tree rooted at `printTreeDir`. The directories mirror the /// nesting structure of the IR. For example, if the IR is congruent to the /// pass-pipeline "builtin.module(passA,passB,func.func(passC,passD),passE)", /// and `printTreeDir=/tmp/pipeline_output`, then then the tree file tree /// created will look like: /// /// ``` /// /tmp/pass_output /// ├── builtin_module_the_symbol_name /// │ ├── 0_passA.mlir /// │ ├── 1_passB.mlir /// │ ├── 2_passE.mlir /// │ ├── func_func_my_func_name /// │ │ ├── 1_0_passC.mlir /// │ │ ├── 1_1__passD.mlir /// │ ├── func_func_my_other_func_name /// │ │ ├── 1_0_passC.mlir /// │ │ ├── 1_1_passD.mlir /// ``` /// /// The subdirectories are given names that reflect the parent operation name /// and symbol name (if present). The output MLIR files are prefixed using an /// atomic counter to indicate the order the passes were printed in and to /// prevent any potential name collisions. void enableIRPrintingToFileTree( std::function shouldPrintBeforePass = [](Pass *, Operation *) { return true; }, std::function shouldPrintAfterPass = [](Pass *, Operation *) { return true; }, bool printModuleScope = true, bool printAfterOnlyOnChange = true, bool printAfterOnlyOnFailure = false, llvm::StringRef printTreeDir = ".pass_manager_output", OpPrintingFlags opPrintingFlags = OpPrintingFlags()); //===--------------------------------------------------------------------===// // Pass Timing /// Add an instrumentation to time the execution of passes and the computation /// of analyses. Timing will be reported by nesting timers into the provided /// `timingScope`. /// /// Note: Timing should be enabled after all other instrumentations to avoid /// any potential "ghost" timing from other instrumentations being /// unintentionally included in the timing results. void enableTiming(TimingScope &timingScope); /// Add an instrumentation to time the execution of passes and the computation /// of analyses. The pass manager will take ownership of the timing manager /// passed to the function and timing will be reported by nesting timers into /// the timing manager's root scope. /// /// Note: Timing should be enabled after all other instrumentations to avoid /// any potential "ghost" timing from other instrumentations being /// unintentionally included in the timing results. void enableTiming(std::unique_ptr tm); /// Add an instrumentation to time the execution of passes and the computation /// of analyses. Creates a temporary TimingManager owned by this PassManager /// which will be used to report timing. /// /// Note: Timing should be enabled after all other instrumentations to avoid /// any potential "ghost" timing from other instrumentations being /// unintentionally included in the timing results. void enableTiming(); //===--------------------------------------------------------------------===// // Pass Statistics /// Prompts the pass manager to print the statistics collected for each of the /// held passes after each call to 'run'. void enableStatistics(PassDisplayMode displayMode = PassDisplayMode::Pipeline); private: /// Dump the statistics of the passes within this pass manager. void dumpStatistics(); /// Run the pass manager with crash recovery enabled. LogicalResult runWithCrashRecovery(Operation *op, AnalysisManager am); /// Run the passes of the pass manager, and return the result. LogicalResult runPasses(Operation *op, AnalysisManager am); /// Context this PassManager was initialized with. MLIRContext *context; /// Flag that specifies if pass statistics should be dumped. std::optional passStatisticsMode; /// A manager for pass instrumentations. std::unique_ptr instrumentor; /// An optional crash reproducer generator, if this pass manager is setup to /// generate reproducers. std::unique_ptr crashReproGenerator; /// Hash keys used to detect when reinitialization is necessary. llvm::hash_code initializationKey = llvm::hash_code(static_cast(-1)); llvm::hash_code pipelineInitializationKey = llvm::hash_code(static_cast(-1)); /// Flag that specifies if pass timing is enabled. bool passTiming : 1; /// A flag that indicates if the IR should be verified in between passes. bool verifyPasses : 1; }; /// Register a set of useful command-line options that can be used to configure /// a pass manager. The values of these options can be applied via the /// 'applyPassManagerCLOptions' method below. void registerPassManagerCLOptions(); /// Apply any values provided to the pass manager options that were registered /// with 'registerPassManagerOptions'. LogicalResult applyPassManagerCLOptions(PassManager &pm); /// Apply any values provided to the timing manager options that were registered /// with `registerDefaultTimingManagerOptions`. This is a handy helper function /// if you do not want to bother creating your own timing manager and passing it /// to the pass manager. void applyDefaultTimingPassManagerCLOptions(PassManager &pm); } // namespace mlir #endif // MLIR_PASS_PASSMANAGER_H