| # Pass Infrastructure | |
| [TOC] | |
| Passes represent the basic infrastructure for transformation and optimization. | |
| This document provides an overview of the pass infrastructure in MLIR and how to | |
| use it. | |
| See [MLIR specification](LangRef.md) for more information about MLIR and its | |
| core aspects, such as the IR structure and operations. | |
| See [MLIR Rewrites](Tutorials/QuickstartRewrites.md) for a quick start on graph | |
| rewriting in MLIR. If a transformation involves pattern matching operation DAGs, | |
| this is a great place to start. | |
| ## Operation Pass | |
| In MLIR, the main unit of abstraction and transformation is an | |
| [operation](LangRef.md/#operations). As such, the pass manager is designed to | |
| work on instances of operations at different levels of nesting. In the following | |
| paragraphs, we refer to the operation that a pass operates on as the "current | |
| operation". | |
| The structure of the [pass manager](#pass-manager), and the concept of nesting, | |
| is detailed further below. All passes in MLIR derive from `OperationPass` and | |
| adhere to the following restrictions; any noncompliance will lead to problematic | |
| behavior in multithreaded and other advanced scenarios: | |
| * Must not inspect the state of operations that are siblings of the current | |
| operation. Must neither access operations nested under those siblings. | |
| * Other threads may be modifying these operations in parallel. | |
| * Inspecting the state of ancestor/parent operations is permitted. | |
| * Must not modify the state of operations other than the operations that are | |
| nested under the current operation. This includes adding, modifying or | |
| removing other operations from an ancestor/parent block. | |
| * Other threads may be operating on these operations simultaneously. | |
| * As an exception, the attributes of the current operation may be modified | |
| freely. This is the only way that the current operation may be modified. | |
| (I.e., modifying operands, etc. is not allowed.) | |
| * Must not maintain mutable pass state across invocations of `runOnOperation`. | |
| A pass may be run on many different operations with no guarantee of | |
| execution order. | |
| * When multithreading, a specific pass instance may not even execute on | |
| all operations within the IR. As such, a pass should not rely on running | |
| on all operations. | |
| * Must not maintain any global mutable state, e.g. static variables within the | |
| source file. All mutable state should be maintained by an instance of the | |
| pass. | |
| * Must be copy-constructible | |
| * Multiple instances of the pass may be created by the pass manager to | |
| process operations in parallel. | |
| ### Op-Agnostic Operation Passes | |
| By default, an operation pass is `op-agnostic`, meaning that it operates on the | |
| operation type of the pass manager that it is added to. This means a pass may operate | |
| on many different types of operations. Agnostic passes should be written such that | |
| they do not make assumptions on the operation they run on. Examples of this type of pass are | |
| [Canonicalization](Passes.md/#-canonicalize) and [Common Sub-Expression Elimination](Passes.md/#-cse). | |
| To create an agnostic operation pass, a derived class must adhere to the following: | |
| * Inherit from the CRTP class `OperationPass`. | |
| * Override the virtual `void runOnOperation()` method. | |
| A simple pass may look like: | |
| ```c++ | |
| /// Here we utilize the CRTP `PassWrapper` utility class to provide some | |
| /// necessary utility hooks. This is only necessary for passes defined directly | |
| /// in C++. Passes defined declaratively use a cleaner mechanism for providing | |
| /// these utilities. | |
| struct MyOperationPass : public PassWrapper<MyOperationPass, OperationPass<>> { | |
| void runOnOperation() override { | |
| // Get the current operation being operated on. | |
| Operation *op = getOperation(); | |
| ... | |
| } | |
| }; | |
| ``` | |
| ### Filtered Operation Pass | |
| If a pass needs to constrain its execution to specific types or classes of operations, | |
| additional filtering may be applied on top. This transforms a once `agnostic` pass into | |
| one more specific to a certain context. There are various ways in which to filter the | |
| execution of a pass, and different contexts in which filtering may apply: | |
| ### Operation Pass: Static Schedule Filtering | |
| Static filtering allows for applying additional constraints on the operation types a | |
| pass may be scheduled on. This type of filtering generally allows for building more | |
| constrained passes that can only be scheduled on operations that satisfy the necessary | |
| constraints. For example, this allows for specifying passes that only run on operations | |
| of a certain, those that provide a certain interface, trait, or some other constraint that | |
| applies to all instances of that operation type. Below is an example of a pass that only | |
| permits scheduling on operations that implement `FunctionOpInterface`: | |
| ```c++ | |
| struct MyFunctionPass : ... { | |
| /// This method is used to provide additional static filtering, and returns if the | |
| /// pass may be scheduled on the given operation type. | |
| bool canScheduleOn(RegisteredOperationName opInfo) const override { | |
| return opInfo.hasInterface<FunctionOpInterface>(); | |
| } | |
| void runOnOperation() { | |
| // Here we can freely cast to FunctionOpInterface, because our `canScheduleOn` ensures | |
| // that our pass is only executed on operations implementing that interface. | |
| FunctionOpInterface op = cast<FunctionOpInterface>(getOperation()); | |
| } | |
| }; | |
| ``` | |
| When a pass with static filtering is added to an [`op-specific` pass manager](#oppassmanager), | |
| it asserts that the operation type of the pass manager satisfies the static constraints of the | |
| pass. When added to an [`op-agnostic` pass manager](#oppassmanager), that pass manager, and all | |
| passes contained within, inherits the static constraints of the pass. For example, if the pass | |
| filters on `FunctionOpInterface`, as in the `MyFunctionPass` example above, only operations that | |
| implement `FunctionOpInterface` will be considered when executing **any** passes within the pass | |
| manager. This invariant is important to keep in mind, as each pass added to an `op-agnostic` pass | |
| manager further constrains the operations that may be scheduled on it. Consider the following example: | |
| ```mlir | |
| func.func @foo() { | |
| // ... | |
| return | |
| } | |
| module @someModule { | |
| // ... | |
| } | |
| ``` | |
| If we were to apply the op-agnostic pipeline, `any(cse,my-function-pass)`, to the above MLIR snippet | |
| it would only run on the `foo` function operation. This is because the `my-function-pass` has a | |
| static filtering constraint to only schedule on operations implementing `FunctionOpInterface`. Remember | |
| that this constraint is inherited by the entire pass manager, so we never consider `someModule` for | |
| any of the passes, including `cse` which normally can be scheduled on any operation. | |
| #### Operation Pass: Static Filtering By Op Type | |
| In the above section, we detailed a general mechanism for statically filtering the types of operations | |
| that a pass may be scheduled on. Sugar is provided on top of that mechanism to simplify the definition | |
| of passes that are restricted to scheduling on a single operation type. In these cases, a pass simply | |
| needs to provide the type of operation to the `OperationPass` base class. This will automatically | |
| instill filtering on that operation type: | |
| ```c++ | |
| /// Here we utilize the CRTP `PassWrapper` utility class to provide some | |
| /// necessary utility hooks. This is only necessary for passes defined directly | |
| /// in C++. Passes defined declaratively use a cleaner mechanism for providing | |
| /// these utilities. | |
| struct MyFunctionPass : public PassWrapper<MyOperationPass, OperationPass<func::FuncOp>> { | |
| void runOnOperation() { | |
| // Get the current operation being operated on. | |
| func::FuncOp op = getOperation(); | |
| } | |
| }; | |
| ``` | |
| #### Operation Pass: Static Filtering By Interface | |
| In the above section, we detailed a general mechanism for statically filtering the types of operations | |
| that a pass may be scheduled on. Sugar is provided on top of that mechanism to simplify the definition | |
| of passes that are restricted to scheduling on a specific operation interface. In these cases, a pass | |
| simply needs to inherit from the `InterfacePass` base class. This class is similar to `OperationPass`, | |
| but expects the type of interface to operate on. This will automatically instill filtering on that | |
| interface type: | |
| ```c++ | |
| /// Here we utilize the CRTP `PassWrapper` utility class to provide some | |
| /// necessary utility hooks. This is only necessary for passes defined directly | |
| /// in C++. Passes defined declaratively use a cleaner mechanism for providing | |
| /// these utilities. | |
| struct MyFunctionPass : public PassWrapper<MyOperationPass, InterfacePass<FunctionOpInterface>> { | |
| void runOnOperation() { | |
| // Get the current operation being operated on. | |
| FunctionOpInterface op = getOperation(); | |
| } | |
| }; | |
| ``` | |
| ### Dependent Dialects | |
| Dialects must be loaded in the MLIRContext before entities from these dialects | |
| (operations, types, attributes, ...) can be created. Dialects must also be | |
| loaded before starting the execution of a multi-threaded pass pipeline. To this | |
| end, a pass that may create an entity from a dialect that isn't guaranteed to | |
| already be loaded must express this by overriding the `getDependentDialects()` | |
| method and declare this list of Dialects explicitly. | |
| See also the `dependentDialects` field in the | |
| [TableGen Specification](#tablegen-specification). | |
| ### Initialization | |
| In certain situations, a Pass may contain state that is constructed dynamically, | |
| but is potentially expensive to recompute in successive runs of the Pass. One | |
| such example is when using [`PDL`-based](Dialects/PDLOps.md) | |
| [patterns](PatternRewriter.md), which are compiled into a bytecode during | |
| runtime. In these situations, a pass may override the following hook to | |
| initialize this heavy state: | |
| * `LogicalResult initialize(MLIRContext *context)` | |
| This hook is executed once per run of a full pass pipeline, meaning that it does | |
| not have access to the state available during a `runOnOperation` call. More | |
| concretely, all necessary accesses to an `MLIRContext` should be driven via the | |
| provided `context` parameter, and methods that utilize "per-run" state such as | |
| `getContext`/`getOperation`/`getAnalysis`/etc. must not be used. | |
| In case of an error during initialization, the pass is expected to emit an error | |
| diagnostic and return a `failure()` which will abort the pass pipeline execution. | |
| ## Analysis Management | |
| An important concept, along with transformation passes, are analyses. These are | |
| conceptually similar to transformation passes, except that they compute | |
| information on a specific operation without modifying it. In MLIR, analyses are | |
| not passes but free-standing classes that are computed lazily on-demand and | |
| cached to avoid unnecessary recomputation. An analysis in MLIR must adhere to | |
| the following: | |
| * Provide a valid constructor taking either an `Operation*` or `Operation*` | |
| and `AnalysisManager &`. | |
| * The provided `AnalysisManager &` should be used to query any necessary | |
| analysis dependencies. | |
| * Must not modify the given operation. | |
| An analysis may provide additional hooks to control various behavior: | |
| * `bool isInvalidated(const AnalysisManager::PreservedAnalyses &)` | |
| Given a preserved analysis set, the analysis returns true if it should truly be | |
| invalidated. This allows for more fine-tuned invalidation in cases where an | |
| analysis wasn't explicitly marked preserved, but may be preserved (or | |
| invalidated) based upon other properties such as analyses sets. If the analysis | |
| uses any other analysis as a dependency, it must also check if the dependency | |
| was invalidated. | |
| ### Querying Analyses | |
| The base `OperationPass` class provides utilities for querying and preserving | |
| analyses for the current operation being processed. | |
| * OperationPass automatically provides the following utilities for querying | |
| analyses: | |
| * `getAnalysis<>` | |
| - Get an analysis for the current operation, constructing it if | |
| necessary. | |
| * `getCachedAnalysis<>` | |
| - Get an analysis for the current operation, if it already exists. | |
| * `getCachedParentAnalysis<>` | |
| - Get an analysis for a given parent operation, if it exists. | |
| * `getCachedChildAnalysis<>` | |
| - Get an analysis for a given child operation, if it exists. | |
| * `getChildAnalysis<>` | |
| - Get an analysis for a given child operation, constructing it if | |
| necessary. | |
| Using the example passes defined above, let's see some examples: | |
| ```c++ | |
| /// An interesting analysis. | |
| struct MyOperationAnalysis { | |
| // Compute this analysis with the provided operation. | |
| MyOperationAnalysis(Operation *op); | |
| }; | |
| struct MyOperationAnalysisWithDependency { | |
| MyOperationAnalysisWithDependency(Operation *op, AnalysisManager &am) { | |
| // Request other analysis as dependency | |
| MyOperationAnalysis &otherAnalysis = am.getAnalysis<MyOperationAnalysis>(); | |
| ... | |
| } | |
| bool isInvalidated(const AnalysisManager::PreservedAnalyses &pa) { | |
| // Check if analysis or its dependency were invalidated | |
| return !pa.isPreserved<MyOperationAnalysisWithDependency>() || | |
| !pa.isPreserved<MyOperationAnalysis>(); | |
| } | |
| }; | |
| void MyOperationPass::runOnOperation() { | |
| // Query MyOperationAnalysis for the current operation. | |
| MyOperationAnalysis &myAnalysis = getAnalysis<MyOperationAnalysis>(); | |
| // Query a cached instance of MyOperationAnalysis for the current operation. | |
| // It will not be computed if it doesn't exist. | |
| auto optionalAnalysis = getCachedAnalysis<MyOperationAnalysis>(); | |
| if (optionalAnalysis) | |
| ... | |
| // Query a cached instance of MyOperationAnalysis for the parent operation of | |
| // the current operation. It will not be computed if it doesn't exist. | |
| auto optionalAnalysis = getCachedParentAnalysis<MyOperationAnalysis>(); | |
| if (optionalAnalysis) | |
| ... | |
| } | |
| ``` | |
| ### Preserving Analyses | |
| Analyses that are constructed after being queried by a pass are cached to avoid | |
| unnecessary computation if they are requested again later. To avoid stale | |
| analyses, all analyses are assumed to be invalidated by a pass. To avoid | |
| invalidation, a pass must specifically mark analyses that are known to be | |
| preserved. | |
| * All Pass classes automatically provide the following utilities for | |
| preserving analyses: | |
| * `markAllAnalysesPreserved` | |
| * `markAnalysesPreserved<>` | |
| ```c++ | |
| void MyOperationPass::runOnOperation() { | |
| // Mark all analyses as preserved. This is useful if a pass can guarantee | |
| // that no transformation was performed. | |
| markAllAnalysesPreserved(); | |
| // Mark specific analyses as preserved. This is used if some transformation | |
| // was performed, but some analyses were either unaffected or explicitly | |
| // preserved. | |
| markAnalysesPreserved<MyAnalysis, MyAnalyses...>(); | |
| } | |
| ``` | |
| ## Pass Failure | |
| Passes in MLIR are allowed to gracefully fail. This may happen if some invariant | |
| of the pass was broken, potentially leaving the IR in some invalid state. If | |
| such a situation occurs, the pass can directly signal a failure to the pass | |
| manager via the `signalPassFailure` method. If a pass signaled a failure when | |
| executing, no other passes in the pipeline will execute and the top-level call | |
| to `PassManager::run` will return `failure`. | |
| ```c++ | |
| void MyOperationPass::runOnOperation() { | |
| // Signal failure on a broken invariant. | |
| if (some_broken_invariant) | |
| return signalPassFailure(); | |
| } | |
| ``` | |
| ## Pass Manager | |
| The above sections introduced the different types of passes and their | |
| invariants. This section introduces the concept of a PassManager, and how it can | |
| be used to configure and schedule a pass pipeline. There are two main classes | |
| related to pass management, the `PassManager` and the `OpPassManager`. The | |
| `PassManager` class acts as the top-level entry point, and contains various | |
| configurations used for the entire pass pipeline. The `OpPassManager` class is | |
| used to schedule passes to run at a specific level of nesting. The top-level | |
| `PassManager` also functions as an `OpPassManager`. | |
| ### OpPassManager | |
| An `OpPassManager` is essentially a collection of passes anchored to execute on | |
| operations at a given level of nesting. A pass manager may be `op-specific` | |
| (anchored on a specific operation type), or `op-agnostic` (not restricted to any | |
| specific operation, and executed on any viable operation type). Operation types that | |
| anchor pass managers must adhere to the following requirement: | |
| * Must be registered and marked | |
| [`IsolatedFromAbove`](Traits/#isolatedfromabove). | |
| * Passes are expected not to modify operations at or above the current | |
| operation being processed. If the operation is not isolated, it may | |
| inadvertently modify or traverse the SSA use-list of an operation it is | |
| not supposed to. | |
| Passes can be added to a pass manager via `addPass`. | |
| An `OpPassManager` is generally created by explicitly nesting a pipeline within | |
| another existing `OpPassManager` via the `nest<OpT>` or `nestAny` methods. The | |
| former method takes the operation type that the nested pass manager will operate on. | |
| The latter method nests an `op-agnostic` pass manager, that may run on any viable | |
| operation type. Nesting in this sense, corresponds to the | |
| [structural](Tutorials/UnderstandingTheIRStructure.md) nesting within | |
| [Regions](LangRef.md/#regions) of the IR. | |
| For example, the following `.mlir`: | |
| ```mlir | |
| module { | |
| spirv.module "Logical" "GLSL450" { | |
| func @foo() { | |
| ... | |
| } | |
| } | |
| } | |
| ``` | |
| Has the nesting structure of: | |
| ``` | |
| `builtin.module` | |
| `spirv.module` | |
| `spirv.func` | |
| ``` | |
| Below is an example of constructing a pipeline that operates on the above | |
| structure: | |
| ```c++ | |
| // Create a top-level `PassManager` class. | |
| auto pm = PassManager::on<ModuleOp>(ctx); | |
| // Add a pass on the top-level module operation. | |
| pm.addPass(std::make_unique<MyModulePass>()); | |
| // Nest a pass manager that operates on `spirv.module` operations nested | |
| // directly under the top-level module. | |
| OpPassManager &nestedModulePM = pm.nest<spirv::ModuleOp>(); | |
| nestedModulePM.addPass(std::make_unique<MySPIRVModulePass>()); | |
| // Nest a pass manager that operates on functions within the nested SPIRV | |
| // module. | |
| OpPassManager &nestedFunctionPM = nestedModulePM.nest<func::FuncOp>(); | |
| nestedFunctionPM.addPass(std::make_unique<MyFunctionPass>()); | |
| // Nest an op-agnostic pass manager. This will operate on any viable | |
| // operation, e.g. func.func, spirv.func, spirv.module, builtin.module, etc. | |
| OpPassManager &nestedAnyPM = nestedModulePM.nestAny(); | |
| nestedAnyPM.addPass(createCanonicalizePass()); | |
| nestedAnyPM.addPass(createCSEPass()); | |
| // Run the pass manager on the top-level module. | |
| ModuleOp m = ...; | |
| if (failed(pm.run(m))) | |
| ... // One of the passes signaled a failure. | |
| ``` | |
| The above pass manager contains the following pipeline structure: | |
| ``` | |
| OpPassManager<ModuleOp> | |
| MyModulePass | |
| OpPassManager<spirv::ModuleOp> | |
| MySPIRVModulePass | |
| OpPassManager<func::FuncOp> | |
| MyFunctionPass | |
| OpPassManager<> | |
| Canonicalizer | |
| CSE | |
| ``` | |
| These pipelines are then run over a single operation at a time. This means that, | |
| for example, given a series of consecutive passes on func::FuncOp, it will execute all | |
| on the first function, then all on the second function, etc. until the entire | |
| program has been run through the passes. This provides several benefits: | |
| * This improves the cache behavior of the compiler, because it is only | |
| touching a single function at a time, instead of traversing the entire | |
| program. | |
| * This improves multi-threading performance by reducing the number of jobs | |
| that need to be scheduled, as well as increasing the efficiency of each job. | |
| An entire function pipeline can be run on each function asynchronously. | |
| ## Dynamic Pass Pipelines | |
| In some situations it may be useful to run a pass pipeline within another pass, | |
| to allow configuring or filtering based on some invariants of the current | |
| operation being operated on. For example, the | |
| [Inliner Pass](Passes.md/#-inline) may want to run | |
| intraprocedural simplification passes while it is inlining to produce a better | |
| cost model, and provide more optimal inlining. To enable this, passes may run an | |
| arbitrary `OpPassManager` on the current operation being operated on or any | |
| operation nested within the current operation via the `LogicalResult | |
| Pass::runPipeline(OpPassManager &, Operation *)` method. This method returns | |
| whether the dynamic pipeline succeeded or failed, similarly to the result of the | |
| top-level `PassManager::run` method. A simple example is shown below: | |
| ```c++ | |
| void MyModulePass::runOnOperation() { | |
| ModuleOp module = getOperation(); | |
| if (hasSomeSpecificProperty(module)) { | |
| OpPassManager dynamicPM("builtin.module"); | |
| ...; // Build the dynamic pipeline. | |
| if (failed(runPipeline(dynamicPM, module))) | |
| return signalPassFailure(); | |
| } | |
| } | |
| ``` | |
| Note: though above the dynamic pipeline was constructed within the | |
| `runOnOperation` method, this is not necessary and pipelines should be cached | |
| when possible as the `OpPassManager` class can be safely copy constructed. | |
| The mechanism described in this section should be used whenever a pass pipeline | |
| should run in a nested fashion, i.e. when the nested pipeline cannot be | |
| scheduled statically along with the rest of the main pass pipeline. More | |
| specifically, a `PassManager` should generally never need to be constructed | |
| within a `Pass`. Using `runPipeline` also ensures that all analyses, | |
| [instrumentations](#pass-instrumentation), and other pass manager related | |
| components are integrated with the dynamic pipeline being executed. | |
| ## Instance Specific Pass Options | |
| MLIR provides a builtin mechanism for passes to specify options that configure | |
| its behavior. These options are parsed at pass construction time independently | |
| for each instance of the pass. Options are defined using the `Option<>` and | |
| `ListOption<>` classes, and generally follow the | |
| [LLVM command line](https://llvm.org/docs/CommandLine.html) flag definition | |
| rules. One major distinction from the LLVM command line functionality is that | |
| all `ListOption`s are comma-separated, and delimited sub-ranges within individual | |
| elements of the list may contain commas that are not treated as separators for the | |
| top-level list. | |
| ```c++ | |
| struct MyPass ... { | |
| /// Make sure that we have a valid default constructor and copy constructor to | |
| /// ensure that the options are initialized properly. | |
| MyPass() = default; | |
| MyPass(const MyPass& pass) {} | |
| /// Any parameters after the description are forwarded to llvm::cl::list and | |
| /// llvm::cl::opt respectively. | |
| Option<int> exampleOption{*this, "flag-name", llvm::cl::desc("...")}; | |
| ListOption<int> exampleListOption{*this, "list-flag-name", llvm::cl::desc("...")}; | |
| }; | |
| ``` | |
| For pass pipelines, the `PassPipelineRegistration` templates take an additional | |
| template parameter for an optional `Option` struct definition. This struct | |
| should inherit from `mlir::PassPipelineOptions` and contain the desired pipeline | |
| options. When using `PassPipelineRegistration`, the constructor now takes a | |
| function with the signature `void (OpPassManager &pm, const MyPipelineOptions&)` | |
| which should construct the passes from the options and pass them to the pm: | |
| ```c++ | |
| struct MyPipelineOptions : public PassPipelineOptions { | |
| // The structure of these options is the same as those for pass options. | |
| Option<int> exampleOption{*this, "flag-name", llvm::cl::desc("...")}; | |
| ListOption<int> exampleListOption{*this, "list-flag-name", | |
| llvm::cl::desc("...")}; | |
| }; | |
| void registerMyPasses() { | |
| PassPipelineRegistration<MyPipelineOptions>( | |
| "example-pipeline", "Run an example pipeline.", | |
| [](OpPassManager &pm, const MyPipelineOptions &pipelineOptions) { | |
| // Initialize the pass manager. | |
| }); | |
| } | |
| ``` | |
| ## Pass Statistics | |
| Statistics are a way to keep track of what the compiler is doing and how | |
| effective various transformations are. It is often useful to see what effect | |
| specific transformations have on a particular input, and how often they trigger. | |
| Pass statistics are specific to each pass instance, which allow for seeing the | |
| effect of placing a particular transformation at specific places within the pass | |
| pipeline. For example, they help answer questions like "What happens if I run | |
| CSE again here?". | |
| Statistics can be added to a pass by using the 'Pass::Statistic' class. This | |
| class takes as a constructor arguments: the parent pass, a name, and a | |
| description. This class acts like an atomic unsigned integer, and may be | |
| incremented and updated accordingly. These statistics rely on the same | |
| infrastructure as | |
| [`llvm::Statistic`](http://llvm.org/docs/ProgrammersManual.html#the-statistic-class-stats-option) | |
| and thus have similar usage constraints. Collected statistics can be dumped by | |
| the [pass manager](#pass-manager) programmatically via | |
| `PassManager::enableStatistics`; or via `-mlir-pass-statistics` and | |
| `-mlir-pass-statistics-display` on the command line. | |
| An example is shown below: | |
| ```c++ | |
| struct MyPass ... { | |
| /// Make sure that we have a valid default constructor and copy constructor to | |
| /// ensure that the options are initialized properly. | |
| MyPass() = default; | |
| MyPass(const MyPass& pass) {} | |
| StringRef getArgument() const final { | |
| // This is the argument used to refer to the pass in | |
| // the textual format (on the commandline for example). | |
| return "argument"; | |
| } | |
| StringRef getDescription() const final { | |
| // This is a brief description of the pass. | |
| return "description"; | |
| } | |
| /// Define the statistic to track during the execution of MyPass. | |
| Statistic exampleStat{this, "exampleStat", "An example statistic"}; | |
| void runOnOperation() { | |
| ... | |
| // Update the statistic after some invariant was hit. | |
| ++exampleStat; | |
| ... | |
| } | |
| }; | |
| ``` | |
| The collected statistics may be aggregated in two types of views: | |
| A pipeline view that models the structure of the pass manager, this is the | |
| default view: | |
| ```shell | |
| $ mlir-opt -pass-pipeline='any(func.func(my-pass,my-pass))' foo.mlir -mlir-pass-statistics | |
| ===-------------------------------------------------------------------------=== | |
| ... Pass statistics report ... | |
| ===-------------------------------------------------------------------------=== | |
| 'func.func' Pipeline | |
| MyPass | |
| (S) 15 exampleStat - An example statistic | |
| VerifierPass | |
| MyPass | |
| (S) 6 exampleStat - An example statistic | |
| VerifierPass | |
| VerifierPass | |
| ``` | |
| A list view that aggregates the statistics of all instances of a specific pass | |
| together: | |
| ```shell | |
| $ mlir-opt -pass-pipeline='any(func.func(my-pass,my-pass))' foo.mlir -mlir-pass-statistics -mlir-pass-statistics-display=list | |
| ===-------------------------------------------------------------------------=== | |
| ... Pass statistics report ... | |
| ===-------------------------------------------------------------------------=== | |
| MyPass | |
| (S) 21 exampleStat - An example statistic | |
| ``` | |
| ## Pass Registration | |
| Briefly shown in the example definitions of the various pass types is the | |
| `PassRegistration` class. This mechanism allows for registering pass classes so | |
| that they may be created within a | |
| [textual pass pipeline description](#textual-pass-pipeline-specification). An | |
| example registration is shown below: | |
| ```c++ | |
| void registerMyPass() { | |
| PassRegistration<MyPass>(); | |
| } | |
| ``` | |
| * `MyPass` is the name of the derived pass class. | |
| * The pass `getArgument()` method is used to get the identifier that will be | |
| used to refer to the pass. | |
| * The pass `getDescription()` method provides a short summary describing the | |
| pass. | |
| For passes that cannot be default-constructed, `PassRegistration` accepts an | |
| optional argument that takes a callback to create the pass: | |
| ```c++ | |
| void registerMyPass() { | |
| PassRegistration<MyParametricPass>( | |
| []() -> std::unique_ptr<Pass> { | |
| std::unique_ptr<Pass> p = std::make_unique<MyParametricPass>(/*options*/); | |
| /*... non-trivial-logic to configure the pass ...*/; | |
| return p; | |
| }); | |
| } | |
| ``` | |
| This variant of registration can be used, for example, to accept the | |
| configuration of a pass from command-line arguments and pass it to the pass | |
| constructor. | |
| Note: Make sure that the pass is copy-constructible in a way that does not share | |
| data as the [pass manager](#pass-manager) may create copies of the pass to run | |
| in parallel. | |
| ### Pass Pipeline Registration | |
| Described above is the mechanism used for registering a specific derived pass | |
| class. On top of that, MLIR allows for registering custom pass pipelines in a | |
| similar fashion. This allows for custom pipelines to be available to tools like | |
| mlir-opt in the same way that passes are, which is useful for encapsulating | |
| common pipelines like the "-O1" series of passes. Pipelines are registered via a | |
| similar mechanism to passes in the form of `PassPipelineRegistration`. Compared | |
| to `PassRegistration`, this class takes an additional parameter in the form of a | |
| pipeline builder that modifies a provided `OpPassManager`. | |
| ```c++ | |
| void pipelineBuilder(OpPassManager &pm) { | |
| pm.addPass(std::make_unique<MyPass>()); | |
| pm.addPass(std::make_unique<MyOtherPass>()); | |
| } | |
| void registerMyPasses() { | |
| // Register an existing pipeline builder function. | |
| PassPipelineRegistration<>( | |
| "argument", "description", pipelineBuilder); | |
| // Register an inline pipeline builder. | |
| PassPipelineRegistration<>( | |
| "argument", "description", [](OpPassManager &pm) { | |
| pm.addPass(std::make_unique<MyPass>()); | |
| pm.addPass(std::make_unique<MyOtherPass>()); | |
| }); | |
| } | |
| ``` | |
| ### Textual Pass Pipeline Specification | |
| The previous sections detailed how to register passes and pass pipelines with a | |
| specific argument and description. Once registered, these can be used to | |
| configure a pass manager from a string description. This is especially useful | |
| for tools like `mlir-opt`, that configure pass managers from the command line, | |
| or as options to passes that utilize | |
| [dynamic pass pipelines](#dynamic-pass-pipelines). | |
| To support the ability to describe the full structure of pass pipelines, MLIR | |
| supports a custom textual description of pass pipelines. The textual description | |
| includes the nesting structure, the arguments of the passes and pass pipelines | |
| to run, and any options for those passes and pipelines. A textual pipeline is | |
| defined as a series of names, each of which may in itself recursively contain a | |
| nested pipeline description. The syntax for this specification is as follows: | |
| ```ebnf | |
| pipeline ::= op-anchor `(` pipeline-element (`,` pipeline-element)* `)` | |
| pipeline-element ::= pipeline | (pass-name | pass-pipeline-name) options? | |
| options ::= '{' (key ('=' value)?)+ '}' | |
| ``` | |
| * `op-anchor` | |
| * This corresponds to the mnemonic name that anchors the execution of the | |
| pass manager. This is either the name of an operation to run passes on, | |
| e.g. `func.func` or `builtin.module`, or `any`, for op-agnostic pass | |
| managers that execute on any viable operation (i.e. any operation that | |
| can be used to anchor a pass manager). | |
| * `pass-name` | `pass-pipeline-name` | |
| * This corresponds to the argument of a registered pass or pass pipeline, | |
| e.g. `cse` or `canonicalize`. | |
| * `options` | |
| * Options are specific key value pairs representing options defined by a | |
| pass or pass pipeline, as described in the | |
| ["Instance Specific Pass Options"](#instance-specific-pass-options) | |
| section. See this section for an example usage in a textual pipeline. | |
| For example, the following pipeline: | |
| ```shell | |
| $ mlir-opt foo.mlir -cse -canonicalize -convert-func-to-llvm='use-bare-ptr-memref-call-conv=1' | |
| ``` | |
| Can also be specified as (via the `-pass-pipeline` flag): | |
| ```shell | |
| # Anchor the cse and canonicalize passes on the `func.func` operation. | |
| $ mlir-opt foo.mlir -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm{use-bare-ptr-memref-call-conv=1})' | |
| # Anchor the cse and canonicalize passes on "any" viable root operation. | |
| $ mlir-opt foo.mlir -pass-pipeline='builtin.module(any(cse,canonicalize),convert-func-to-llvm{use-bare-ptr-memref-call-conv=1})' | |
| ``` | |
| In order to support round-tripping a pass to the textual representation using | |
| `OpPassManager::printAsTextualPipeline(raw_ostream&)`, override `StringRef | |
| Pass::getArgument()` to specify the argument used when registering a pass. | |
| ## Declarative Pass Specification | |
| Some aspects of a Pass may be specified declaratively, in a form similar to | |
| [operations](DefiningDialects/Operations.md). This specification simplifies several mechanisms | |
| used when defining passes. It can be used for generating pass registration | |
| calls, defining boilerplate pass utilities, and generating pass documentation. | |
| Consider the following pass specified in C++: | |
| ```c++ | |
| struct MyPass : PassWrapper<MyPass, OperationPass<ModuleOp>> { | |
| MyPass() = default; | |
| MyPass(const MyPass &) {} | |
| ... | |
| // Specify any options. | |
| Option<bool> option{ | |
| *this, "example-option", | |
| llvm::cl::desc("An example option"), llvm::cl::init(true)}; | |
| ListOption<int64_t> listOption{ | |
| *this, "example-list", | |
| llvm::cl::desc("An example list option")}; | |
| // Specify any statistics. | |
| Statistic statistic{this, "example-statistic", "An example statistic"}; | |
| }; | |
| /// Expose this pass to the outside world. | |
| std::unique_ptr<Pass> foo::createMyPass() { | |
| return std::make_unique<MyPass>(); | |
| } | |
| /// Register this pass. | |
| void foo::registerMyPass() { | |
| PassRegistration<MyPass>(); | |
| } | |
| ``` | |
| This pass may be specified declaratively as so: | |
| ```tablegen | |
| def MyPass : Pass<"my-pass", "ModuleOp"> { | |
| let summary = "My Pass Summary"; | |
| let description = [{ | |
| Here we can now give a much larger description of `MyPass`, including all of | |
| its various constraints and behavior. | |
| }]; | |
| // Specify any options. | |
| let options = [ | |
| Option<"option", "example-option", "bool", /*default=*/"true", | |
| "An example option">, | |
| ListOption<"listOption", "example-list", "int64_t", | |
| "An example list option"> | |
| ]; | |
| // Specify any statistics. | |
| let statistics = [ | |
| Statistic<"statistic", "example-statistic", "An example statistic"> | |
| ]; | |
| } | |
| ``` | |
| Using the `gen-pass-decls` generator, we can generate most of the boilerplate | |
| above automatically. This generator takes as an input a `-name` parameter, that | |
| provides a tag for the group of passes that are being generated. This generator | |
| produces code with multiple purposes: | |
| The first is to register the declared passes with the global registry. For | |
| each pass, the generator produces a `registerPassName` where | |
| `PassName` is the name of the definition specified in tablegen. It also | |
| generates a `registerGroupPasses`, where `Group` is the tag provided via the | |
| `-name` input parameter, that registers all of the passes present. | |
| These declarations can be enabled for the whole group of passes by | |
| defining the `GEN_PASS_REGISTRATION` macro, or on a per-pass basis by | |
| defining `GEN_PASS_REGISTRATION_PASSNAME` where `PASSNAME` is the | |
| uppercase version of the name of the pass (similar to pass def and | |
| decls). | |
| ```c++ | |
| // Tablegen options: -gen-pass-decls -name="Example" | |
| // Passes.h | |
| namespace foo { | |
| #define GEN_PASS_REGISTRATION | |
| #include "Passes.h.inc" | |
| } // namespace foo | |
| void registerMyPasses() { | |
| // Register all of the passes. | |
| foo::registerExamplePasses(); | |
| // Or | |
| // Register `MyPass` specifically. | |
| foo::registerMyPass(); | |
| } | |
| ``` | |
| The second is to provide a way to configure the pass options. These classes are | |
| named in the form of `MyPassOptions`, where `MyPass` is the name of the pass | |
| definition in tablegen. The configurable parameters reflect the options declared | |
| in the tablegen file. These declarations can be enabled for the whole group of | |
| passes by defining the `GEN_PASS_DECL` macro, or on a per-pass basis by defining | |
| `GEN_PASS_DECL_PASSNAME` where `PASSNAME` is the uppercase version of the name | |
| specified in tablegen. | |
| ```c++ | |
| // .h.inc | |
| #ifdef GEN_PASS_DECL_MYPASS | |
| struct MyPassOptions { | |
| bool option = true; | |
| ::llvm::ArrayRef<int64_t> listOption; | |
| }; | |
| #undef GEN_PASS_DECL_MYPASS | |
| #endif // GEN_PASS_DECL_MYPASS | |
| ``` | |
| The autogenerated file will also contain the declarations of the default | |
| constructors. | |
| ```c++ | |
| // .h.inc | |
| #ifdef GEN_PASS_DECL_MYPASS | |
| ... | |
| std::unique_ptr<::mlir::Pass> createMyPass(); | |
| std::unique_ptr<::mlir::Pass> createMyPass(const MyPassOptions &options); | |
| #undef GEN_PASS_DECL_MYPASS | |
| #endif // GEN_PASS_DECL_MYPASS | |
| ``` | |
| The last purpose of this generator is to emit a base class for each of the | |
| passes, containing most of the boiler plate related to pass definitions. These | |
| classes are named in the form of `MyPassBase` and are declared inside the | |
| `impl` namespace, where `MyPass` is the name of the pass definition in | |
| tablegen. We can update the original C++ pass definition as so: | |
| ```c++ | |
| // MyPass.cpp | |
| /// Include the generated base pass class definitions. | |
| namespace foo { | |
| #define GEN_PASS_DEF_MYPASS | |
| #include "Passes.h.inc" | |
| } | |
| /// Define the main class as deriving from the generated base class. | |
| struct MyPass : foo::impl::MyPassBase<MyPass> { | |
| using MyPassBase::MyPassBase; | |
| /// The definitions of the options and statistics are now generated within | |
| /// the base class, but are accessible in the same way. | |
| }; | |
| ``` | |
| These definitions can be enabled on a per-pass basis by defining the appropriate | |
| preprocessor `GEN_PASS_DEF_PASSNAME` macro, with `PASSNAME` equal to the | |
| uppercase version of the name of the pass definition in tablegen. | |
| The default constructors are also defined and expect the name of the actual pass | |
| class to be equal to the name defined in tablegen. | |
| Using the `gen-pass-doc` generator, markdown documentation for each of the | |
| passes can be generated. See [Passes.md](Passes.md) for example output of real | |
| MLIR passes. | |
| ### Tablegen Specification | |
| The `Pass` class is used to begin a new pass definition. This class takes as an | |
| argument the registry argument to attribute to the pass, as well as an optional | |
| string corresponding to the operation type that the pass operates on. The class | |
| contains the following fields: | |
| * `summary` | |
| - A short one-line summary of the pass, used as the description when | |
| registering the pass. | |
| * `description` | |
| - A longer, more detailed description of the pass. This is used when | |
| generating pass documentation. | |
| * `dependentDialects` | |
| - A list of strings representing the `Dialect` classes this pass may | |
| introduce entities, Attributes/Operations/Types/etc., of. | |
| * `options` | |
| - A list of pass options used by the pass. | |
| * `statistics` | |
| - A list of pass statistics used by the pass. | |
| * `constructor` | |
| - A code block used to create a default instance of the pass. | |
| Specifying it will disable the constructors auto-generation for the | |
| pass. This is a legacy option, it is not advised to use it. | |
| #### Options | |
| Options may be specified via the `Option` and `ListOption` classes. The `Option` | |
| class takes the following template parameters: | |
| * C++ variable name | |
| - A name to use for the generated option variable. | |
| * argument | |
| - The argument name of the option. | |
| * type | |
| - The C++ type of the option. | |
| * default value | |
| - The default option value. | |
| * description | |
| - A one-line description of the option. | |
| * additional option flags | |
| - A string containing any additional options necessary to construct the | |
| option. | |
| ```tablegen | |
| def MyPass : Pass<"my-pass"> { | |
| let options = [ | |
| Option<"option", "example-option", "bool", /*default=*/"true", | |
| "An example option">, | |
| ]; | |
| } | |
| ``` | |
| The `ListOption` class takes the following fields: | |
| * C++ variable name | |
| - A name to use for the generated option variable. | |
| * argument | |
| - The argument name of the option. | |
| * element type | |
| - The C++ type of the list element. | |
| * description | |
| - A one-line description of the option. | |
| * additional option flags | |
| - A string containing any additional options necessary to construct the | |
| option. | |
| ```tablegen | |
| def MyPass : Pass<"my-pass"> { | |
| let options = [ | |
| ListOption<"listOption", "example-list", "int64_t", | |
| "An example list option"> | |
| ]; | |
| } | |
| ``` | |
| #### Statistic | |
| Statistics may be specified via the `Statistic`, which takes the following | |
| template parameters: | |
| * C++ variable name | |
| - A name to use for the generated statistic variable. | |
| * display name | |
| - The name used when displaying the statistic. | |
| * description | |
| - A one-line description of the statistic. | |
| ```tablegen | |
| def MyPass : Pass<"my-pass"> { | |
| let statistics = [ | |
| Statistic<"statistic", "example-statistic", "An example statistic"> | |
| ]; | |
| } | |
| ``` | |
| ## Pass Instrumentation | |
| MLIR provides a customizable framework to instrument pass execution and analysis | |
| computation, via the `PassInstrumentation` class. This class provides hooks into | |
| the PassManager that observe various events: | |
| * `runBeforePipeline` | |
| * This callback is run just before a pass pipeline, i.e. pass manager, is | |
| executed. | |
| * `runAfterPipeline` | |
| * This callback is run right after a pass pipeline has been executed, | |
| successfully or not. | |
| * `runBeforePass` | |
| * This callback is run just before a pass is executed. | |
| * `runAfterPass` | |
| * This callback is run right after a pass has been successfully executed. | |
| If this hook is executed, `runAfterPassFailed` will *not* be. | |
| * `runAfterPassFailed` | |
| * This callback is run right after a pass execution fails. If this hook is | |
| executed, `runAfterPass` will *not* be. | |
| * `runBeforeAnalysis` | |
| * This callback is run just before an analysis is computed. | |
| * If the analysis requested another analysis as a dependency, the | |
| `runBeforeAnalysis`/`runAfterAnalysis` pair for the dependency can be | |
| called from inside of the current `runBeforeAnalysis`/`runAfterAnalysis` | |
| pair. | |
| * `runAfterAnalysis` | |
| * This callback is run right after an analysis is computed. | |
| PassInstrumentation instances may be registered directly with a | |
| [PassManager](#pass-manager) instance via the `addInstrumentation` method. | |
| Instrumentations added to the PassManager are run in a stack like fashion, i.e. | |
| the last instrumentation to execute a `runBefore*` hook will be the first to | |
| execute the respective `runAfter*` hook. The hooks of a `PassInstrumentation` | |
| class are guaranteed to be executed in a thread-safe fashion, so additional | |
| synchronization is not necessary. Below in an example instrumentation that | |
| counts the number of times the `DominanceInfo` analysis is computed: | |
| ```c++ | |
| struct DominanceCounterInstrumentation : public PassInstrumentation { | |
| /// The cumulative count of how many times dominance has been calculated. | |
| unsigned &count; | |
| DominanceCounterInstrumentation(unsigned &count) : count(count) {} | |
| void runAfterAnalysis(llvm::StringRef, TypeID id, Operation *) override { | |
| if (id == TypeID::get<DominanceInfo>()) | |
| ++count; | |
| } | |
| }; | |
| MLIRContext *ctx = ...; | |
| PassManager pm(ctx); | |
| // Add the instrumentation to the pass manager. | |
| unsigned domInfoCount; | |
| pm.addInstrumentation( | |
| std::make_unique<DominanceCounterInstrumentation>(domInfoCount)); | |
| // Run the pass manager on a module operation. | |
| ModuleOp m = ...; | |
| if (failed(pm.run(m))) | |
| ... | |
| llvm::errs() << "DominanceInfo was computed " << domInfoCount << " times!\n"; | |
| ``` | |
| ### Standard Instrumentations | |
| MLIR utilizes the pass instrumentation framework to provide a few useful | |
| developer tools and utilities. Each of these instrumentations are directly | |
| available to all users of the MLIR pass framework. | |
| #### Pass Timing | |
| The PassTiming instrumentation provides timing information about the execution | |
| of passes and computation of analyses. This provides a quick glimpse into what | |
| passes are taking the most time to execute, as well as how much of an effect a | |
| pass has on the total execution time of the pipeline. Users can enable this | |
| instrumentation directly on the PassManager via `enableTiming`. This | |
| instrumentation is also made available in mlir-opt via the `-mlir-timing` flag. | |
| The PassTiming instrumentation provides several different display modes for the | |
| timing results, each of which is described below: | |
| ##### List Display Mode | |
| In this mode, the results are displayed in a list sorted by total time with each | |
| pass/analysis instance aggregated into one unique result. This view is useful | |
| for getting an overview of what analyses/passes are taking the most time in a | |
| pipeline. This display mode is available in mlir-opt via | |
| `-mlir-timing-display=list`. | |
| ```shell | |
| $ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm)' -mlir-timing -mlir-timing-display=list | |
| ===-------------------------------------------------------------------------=== | |
| ... Execution time report ... | |
| ===-------------------------------------------------------------------------=== | |
| Total Execution Time: 0.0135 seconds | |
| ----Wall Time---- ----Name---- | |
| 0.0135 (100.0%) root | |
| 0.0041 ( 30.1%) Parser | |
| 0.0018 ( 13.3%) ConvertFuncToLLVMPass | |
| 0.0011 ( 8.2%) Output | |
| 0.0007 ( 5.2%) Pipeline Collection : ['func.func'] | |
| 0.0006 ( 4.6%) 'func.func' Pipeline | |
| 0.0005 ( 3.5%) Canonicalizer | |
| 0.0001 ( 0.9%) CSE | |
| 0.0001 ( 0.5%) (A) DataLayoutAnalysis | |
| 0.0000 ( 0.1%) (A) DominanceInfo | |
| 0.0058 ( 43.2%) Rest | |
| 0.0135 (100.0%) Total | |
| ``` | |
| The results can be displayed in JSON format via `-mlir-output-format=json`. | |
| ```shell | |
| $ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm)' -mlir-timing -mlir-timing-display=list -mlir-output-format=json | |
| [ | |
| {"wall": {"duration": 0.0135, "percentage": 100.0}, "name": "root"}, | |
| {"wall": {"duration": 0.0041, "percentage": 30.1}, "name": "Parser"}, | |
| {"wall": {"duration": 0.0018, "percentage": 13.3}, "name": "ConvertFuncToLLVMPass"}, | |
| {"wall": {"duration": 0.0011, "percentage": 8.2}, "name": "Output"}, | |
| {"wall": {"duration": 0.0007, "percentage": 5.2}, "name": "Pipeline Collection : ['func.func']"}, | |
| {"wall": {"duration": 0.0006, "percentage": 4.6}, "name": "'func.func' Pipeline"}, | |
| {"wall": {"duration": 0.0005, "percentage": 3.5}, "name": "Canonicalizer"}, | |
| {"wall": {"duration": 0.0001, "percentage": 0.9}, "name": "CSE"}, | |
| {"wall": {"duration": 0.0001, "percentage": 0.5}, "name": "(A) DataLayoutAnalysis"}, | |
| {"wall": {"duration": 0.0000, "percentage": 0.1}, "name": "(A) DominanceInfo"}, | |
| {"wall": {"duration": 0.0058, "percentage": 43.2}, "name": "Rest"}, | |
| {"wall": {"duration": 0.0135, "percentage": 100.0}, "name": "Total"} | |
| ] | |
| ``` | |
| ##### Tree Display Mode | |
| 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. This view | |
| is useful for understanding specifically which parts of the pipeline are taking | |
| the most time, and can also be used to identify when analyses are being | |
| invalidated and recomputed. This is the default display mode. | |
| ```shell | |
| $ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm)' -mlir-timing | |
| ===-------------------------------------------------------------------------=== | |
| ... Execution time report ... | |
| ===-------------------------------------------------------------------------=== | |
| Total Execution Time: 0.0127 seconds | |
| ----Wall Time---- ----Name---- | |
| 0.0038 ( 30.2%) Parser | |
| 0.0006 ( 4.8%) 'func.func' Pipeline | |
| 0.0001 ( 0.9%) CSE | |
| 0.0000 ( 0.1%) (A) DominanceInfo | |
| 0.0005 ( 3.7%) Canonicalizer | |
| 0.0017 ( 13.7%) ConvertFuncToLLVMPass | |
| 0.0001 ( 0.6%) (A) DataLayoutAnalysis | |
| 0.0010 ( 8.2%) Output | |
| 0.0054 ( 42.5%) Rest | |
| 0.0127 (100.0%) Total | |
| ``` | |
| The results can be displayed in JSON format via `-mlir-output-format=json`. | |
| ```shell | |
| $ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm)' -mlir-timing -mlir-output-format=json | |
| [ | |
| {"wall": {"duration": 0.0038, "percentage": 30.2}, "name": "Parser", "passes": [ | |
| {}]}, | |
| {"wall": {"duration": 0.0006, "percentage": 4.8}, "name": "'func.func' Pipeline", "passes": [ | |
| {"wall": {"duration": 0.0001, "percentage": 0.9}, "name": "CSE", "passes": [ | |
| {"wall": {"duration": 0.0000, "percentage": 0.1}, "name": "(A) DominanceInfo", "passes": [ | |
| {}]}, | |
| {}]}, | |
| {"wall": {"duration": 0.0005, "percentage": 3.7}, "name": "Canonicalizer", "passes": [ | |
| {}]}, | |
| {}]}, | |
| {"wall": {"duration": 0.0017, "percentage": 13.7}, "name": "ConvertFuncToLLVMPass", "passes": [ | |
| {"wall": {"duration": 0.0001, "percentage": 0.6}, "name": "(A) DataLayoutAnalysis", "passes": [ | |
| {}]}, | |
| {}]}, | |
| {"wall": {"duration": 0.0010, "percentage": 8.2}, "name": "Output", "passes": [ | |
| {}]}, | |
| {"wall": {"duration": 0.0054, "percentage": 42.5}, "name": "Rest"}, | |
| {"wall": {"duration": 0.0127, "percentage": 100.0}, "name": "Total"} | |
| ] | |
| ``` | |
| ##### Multi-threaded Pass Timing | |
| When multi-threading is enabled in the pass manager the meaning of the display | |
| slightly changes. First, a new timing column is added, `User Time`, that | |
| displays the total time spent across all threads. Secondly, the `Wall Time` | |
| column displays the longest individual time spent amongst all of the threads. | |
| This means that the `Wall Time` column will continue to give an indicator on the | |
| perceived time, or clock time, whereas the `User Time` will display the total | |
| cpu time. | |
| ```shell | |
| $ mlir-opt foo.mlir -pass-pipeline='builtin.module(func.func(cse,canonicalize),convert-func-to-llvm)' -mlir-timing | |
| ===-------------------------------------------------------------------------=== | |
| ... Pass execution timing report ... | |
| ===-------------------------------------------------------------------------=== | |
| Total Execution Time: 0.0078 seconds | |
| ---User Time--- ---Wall Time--- --- Name --- | |
| 0.0177 ( 88.5%) 0.0057 ( 71.3%) 'func.func' Pipeline | |
| 0.0044 ( 22.0%) 0.0015 ( 18.9%) CSE | |
| 0.0029 ( 14.5%) 0.0012 ( 15.2%) (A) DominanceInfo | |
| 0.0038 ( 18.9%) 0.0015 ( 18.7%) VerifierPass | |
| 0.0089 ( 44.6%) 0.0025 ( 31.1%) Canonicalizer | |
| 0.0006 ( 3.0%) 0.0002 ( 2.6%) VerifierPass | |
| 0.0004 ( 2.2%) 0.0004 ( 5.4%) VerifierPass | |
| 0.0013 ( 6.5%) 0.0013 ( 16.3%) LLVMLoweringPass | |
| 0.0006 ( 2.8%) 0.0006 ( 7.0%) VerifierPass | |
| 0.0200 (100.0%) 0.0081 (100.0%) Total | |
| ``` | |
| #### IR Printing | |
| When debugging it is often useful to dump the IR at various stages of a pass | |
| pipeline. This is where the IR printing instrumentation comes into play. This | |
| instrumentation allows for conditionally printing the IR before and after pass | |
| execution by optionally filtering on the pass being executed. This | |
| instrumentation can be added directly to the PassManager via the | |
| `enableIRPrinting` method. `mlir-opt` provides a few useful flags for utilizing | |
| this instrumentation: | |
| * `mlir-print-ir-before=(comma-separated-pass-list)` | |
| * Print the IR before each of the passes provided within the pass list. | |
| * `mlir-print-ir-before-all` | |
| * Print the IR before every pass in the pipeline. | |
| ```shell | |
| $ mlir-opt foo.mlir -pass-pipeline='func.func(cse)' -mlir-print-ir-before=cse | |
| *** IR Dump Before CSE *** | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| %c1_i32_0 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32_0 : i32, i32 | |
| } | |
| ``` | |
| * `mlir-print-ir-after=(comma-separated-pass-list)` | |
| * Print the IR after each of the passes provided within the pass list. | |
| * `mlir-print-ir-after-all` | |
| * Print the IR after every pass in the pipeline. | |
| ```shell | |
| $ mlir-opt foo.mlir -pass-pipeline='func.func(cse)' -mlir-print-ir-after=cse | |
| *** IR Dump After CSE *** | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32 : i32, i32 | |
| } | |
| ``` | |
| * `mlir-print-ir-after-change` | |
| * Only print the IR after a pass if the pass mutated the IR. This helps to | |
| reduce the number of IR dumps for "uninteresting" passes. | |
| * Note: Changes are detected by comparing a hash of the operation before | |
| and after the pass. This adds additional run-time to compute the hash of | |
| the IR, and in some rare cases may result in false-positives depending | |
| on the collision rate of the hash algorithm used. | |
| * Note: This option should be used in unison with one of the other | |
| 'mlir-print-ir-after' options above, as this option alone does not enable | |
| printing. | |
| ```shell | |
| $ mlir-opt foo.mlir -pass-pipeline='func.func(cse,cse)' -mlir-print-ir-after=cse -mlir-print-ir-after-change | |
| *** IR Dump After CSE *** | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32 : i32, i32 | |
| } | |
| ``` | |
| * `mlir-print-ir-after-failure` | |
| * Only print IR after a pass failure. | |
| * This option should *not* be used with the other `mlir-print-ir-after` flags | |
| above. | |
| ```shell | |
| $ mlir-opt foo.mlir -pass-pipeline='func.func(cse,bad-pass)' -mlir-print-ir-after-failure | |
| *** IR Dump After BadPass Failed *** | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32 : i32, i32 | |
| } | |
| ``` | |
| * `mlir-print-ir-module-scope` | |
| * Always print the top-level module operation, regardless of pass type or | |
| operation nesting level. | |
| * Note: Printing at module scope should only be used when multi-threading | |
| is disabled(`-mlir-disable-threading`) | |
| ```shell | |
| $ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='func.func(cse)' -mlir-print-ir-after=cse -mlir-print-ir-module-scope | |
| *** IR Dump After CSE *** ('func.func' operation: @bar) | |
| func.func @bar(%arg0: f32, %arg1: f32) -> f32 { | |
| ... | |
| } | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| %c1_i32_0 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32_0 : i32, i32 | |
| } | |
| *** IR Dump After CSE *** ('func.func' operation: @simple_constant) | |
| func.func @bar(%arg0: f32, %arg1: f32) -> f32 { | |
| ... | |
| } | |
| func.func @simple_constant() -> (i32, i32) { | |
| %c1_i32 = arith.constant 1 : i32 | |
| return %c1_i32, %c1_i32 : i32, i32 | |
| } | |
| ``` | |
| * `mlir-print-ir-tree-dir=(directory path)` | |
| * Without setting this option, the IR printed by the instrumentation will | |
| be printed to `stderr`. If you provide a directory using this option, | |
| the output corresponding to each pass will be printed to a file in the | |
| directory tree rooted at `(directory path)`. The path created for each | |
| pass reflects the nesting structure of the IR and the pass pipeline. | |
| * The below example illustrates the file tree created by running a pass | |
| pipeline on IR that has two `func.func` located within two nested | |
| `builtin.module` ops. | |
| * The subdirectories are given names that reflect the parent op names and | |
| the symbol names for those ops (if present). | |
| * The printer keeps a counter associated with ops that are targeted by | |
| passes and their isolated-from-above parents. Each filename is given a | |
| numeric prefix using the counter value for the op that the pass is | |
| targeting. The counter values for each parent are then prepended. This | |
| gives a naming where it is easy to distinguish which passes may have run | |
| concurrently versus which have a clear ordering. In the below example,for | |
| both `1_1_pass4.mlir` files, the first 1 refers to the counter for the | |
| parent op, and the second refers to the counter for the respective | |
| function. | |
| ``` | |
| $ pipeline="builtin.module(pass1,pass2,func.func(pass3,pass4),pass5)" | |
| $ mlir-opt foo.mlir -pass-pipeline="$pipeline" -mlir-print-ir-tree-dir=/tmp/pipeline_output | |
| $ tree /tmp/pipeline_output | |
| /tmp/pass_output | |
| ├── builtin_module_the_symbol_name | |
| │ ├── 0_pass1.mlir | |
| │ ├── 1_pass2.mlir | |
| │ ├── 2_pass5.mlir | |
| │ ├── func_func_my_func_name | |
| │ │ ├── 1_0_pass3.mlir | |
| │ │ ├── 1_1_pass4.mlir | |
| │ ├── func_func_my_other_func_name | |
| │ │ ├── 1_0_pass3.mlir | |
| │ │ ├── 1_1_pass4.mlir | |
| ``` | |
| * `mlir-use-nameloc-as-prefix` | |
| * If your source IR has named locations (`loc("named_location")"`) then passing this flag will use those | |
| names (`named_location`) to prefix the corresponding SSA identifiers: | |
| ```mlir | |
| %1 = memref.load %0[] : memref<i32> loc("alice") | |
| %2 = memref.load %0[] : memref<i32> loc("bob") | |
| %3 = memref.load %0[] : memref<i32> loc("bob") | |
| ``` | |
| will print | |
| ```mlir | |
| %alice = memref.load %0[] : memref<i32> | |
| %bob = memref.load %0[] : memref<i32> | |
| %bob_0 = memref.load %0[] : memref<i32> | |
| ``` | |
| These names will also be preserved through passes to newly created operations if using the appropriate location. | |
| ## Crash and Failure Reproduction | |
| The [pass manager](#pass-manager) in MLIR contains a builtin mechanism to | |
| generate reproducibles in the event of a crash, or a | |
| [pass failure](#pass-failure). This functionality can be enabled via | |
| `PassManager::enableCrashReproducerGeneration` or via the command line flag | |
| `mlir-pass-pipeline-crash-reproducer`. In either case, an argument is provided that | |
| corresponds to the output `.mlir` file name that the reproducible should be | |
| written to. The reproducible contains the configuration of the pass manager that | |
| was executing, as well as the initial IR before any passes were run. The reproducer | |
| is stored within the assembly format as an external resource. A potential reproducible | |
| may have the form: | |
| ```mlir | |
| module { | |
| func.func @foo() { | |
| ... | |
| } | |
| } | |
| {-# | |
| external_resources: { | |
| mlir_reproducer: { | |
| pipeline: "builtin.module(func.func(cse,canonicalize),inline)", | |
| disable_threading: true, | |
| verify_each: true | |
| } | |
| } | |
| #-} | |
| ``` | |
| The configuration dumped can be passed to `mlir-opt` by specifying | |
| `-run-reproducer` flag. This will result in parsing the configuration of the reproducer | |
| and adjusting the necessary opt state, e.g. configuring the pass manager, context, etc. | |
| Beyond specifying a filename, one can also register a `ReproducerStreamFactory` | |
| function that would be invoked in the case of a crash and the reproducer written | |
| to its stream. | |
| ### Local Reproducer Generation | |
| An additional flag may be passed to | |
| `PassManager::enableCrashReproducerGeneration`, and specified via | |
| `mlir-pass-pipeline-local-reproducer` on the command line, that signals that the pass | |
| manager should attempt to generate a "local" reproducer. This will attempt to | |
| generate a reproducer containing IR right before the pass that fails. This is | |
| useful for situations where the crash is known to be within a specific pass, or | |
| when the original input relies on components (like dialects or passes) that may | |
| not always be available. | |
| Note: Local reproducer generation requires that multi-threading is | |
| disabled(`-mlir-disable-threading`) | |
| For example, if the failure in the previous example came from the `canonicalize` pass, | |
| the following reproducer would be generated: | |
| ```mlir | |
| module { | |
| func.func @foo() { | |
| ... | |
| } | |
| } | |
| {-# | |
| external_resources: { | |
| mlir_reproducer: { | |
| pipeline: "builtin.module(func.func(canonicalize))", | |
| disable_threading: true, | |
| verify_each: true | |
| } | |
| } | |
| #-} | |
| ``` |