xref: /llvm-project/mlir/include/mlir/Transforms/Passes.td (revision 35df525fd00c2037ef144189ee818b7d612241ff)
1//===-- Passes.td - Transforms pass definition file --------*- tablegen -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file contains definitions for passes within the Transforms/ directory.
10//
11//===----------------------------------------------------------------------===//
12
13#ifndef MLIR_TRANSFORMS_PASSES
14#define MLIR_TRANSFORMS_PASSES
15
16include "mlir/Pass/PassBase.td"
17include "mlir/Rewrite/PassUtil.td"
18
19def Canonicalizer : Pass<"canonicalize"> {
20  let summary = "Canonicalize operations";
21  let description = [{
22    This pass performs various types of canonicalizations over a set of
23    operations by iteratively applying the canonicalization patterns of all
24    loaded dialects until either a fixpoint is reached or the maximum number of
25    iterations/rewrites is exhausted. Canonicalization is best-effort and does
26    not guarantee that the entire IR is in a canonical form after running this
27    pass. See [Operation Canonicalization](Canonicalization.md) for more
28    details.
29  }];
30  let constructor = "mlir::createCanonicalizerPass()";
31  let dependentDialects = ["ub::UBDialect"];
32  let options = [
33    Option<"topDownProcessingEnabled", "top-down", "bool",
34           /*default=*/"true",
35           "Seed the worklist in general top-down order">,
36    Option<"enableRegionSimplification", "region-simplify", "mlir::GreedySimplifyRegionLevel",
37           /*default=*/"mlir::GreedySimplifyRegionLevel::Normal",
38           "Perform control flow optimizations to the region tree",
39             [{::llvm::cl::values(
40               clEnumValN(mlir::GreedySimplifyRegionLevel::Disabled, "disabled",
41                "Don't run any control-flow simplification."),
42               clEnumValN(mlir::GreedySimplifyRegionLevel::Normal, "normal",
43                "Perform simple control-flow simplifications (e.g. dead args elimination)."),
44               clEnumValN(mlir::GreedySimplifyRegionLevel::Aggressive, "aggressive",
45                "Perform aggressive control-flow simplification (e.g. block merging).")
46              )}]>,
47    Option<"maxIterations", "max-iterations", "int64_t",
48           /*default=*/"10",
49           "Max. iterations between applying patterns / simplifying regions">,
50    Option<"maxNumRewrites", "max-num-rewrites", "int64_t", /*default=*/"-1",
51           "Max. number of pattern rewrites within an iteration">,
52    Option<"testConvergence", "test-convergence", "bool", /*default=*/"false",
53           "Test only: Fail pass on non-convergence to detect cyclic pattern">
54  ] # RewritePassUtils.options;
55}
56
57def ControlFlowSink : Pass<"control-flow-sink"> {
58  let summary = "Sink operations into conditional blocks";
59  let description = [{
60    This pass implements control-flow sink on operations that implement
61    `RegionBranchOpInterface` by moving dominating operations whose only uses
62    are in a conditionally-executed regions into those regions so that
63    executions paths where their results are not needed do not perform
64    unnecessary computations.
65
66    This is similar (but opposite) to loop-invariant code motion, which hoists
67    operations out of regions executed more than once. The implementation of
68    control-flow sink uses a simple and conversative cost model: operations are
69    never duplicated and are only moved into singly-executed regions.
70
71    It is recommended to run canonicalization first to remove unreachable
72    blocks: ops in unreachable blocks may prevent other operations from being
73    sunk as they may contain uses of their results
74  }];
75  let constructor = "::mlir::createControlFlowSinkPass()";
76  let statistics = [
77    Statistic<"numSunk", "num-sunk", "Number of operations sunk">,
78  ];
79}
80
81def CSE : Pass<"cse"> {
82  let summary = "Eliminate common sub-expressions";
83  let description = [{
84    This pass implements a generalized algorithm for common sub-expression
85    elimination. This pass relies on information provided by the
86    `Memory SideEffect` interface to identify when it is safe to eliminate
87    operations. See [Common subexpression elimination](https://en.wikipedia.org/wiki/Common_subexpression_elimination)
88    for more general details on this optimization.
89  }];
90  let constructor = "mlir::createCSEPass()";
91  let statistics = [
92    Statistic<"numCSE", "num-cse'd", "Number of operations CSE'd">,
93    Statistic<"numDCE", "num-dce'd", "Number of operations DCE'd">
94  ];
95}
96
97def RemoveDeadValues : Pass<"remove-dead-values"> {
98  let summary = "Remove dead values";
99  let description = [{
100    The goal of this pass is optimization (reducing runtime) by removing
101    unnecessary instructions. Unlike other passes that rely on local information
102    gathered from patterns to accomplish optimization, this pass uses a full
103    analysis of the IR, specifically, liveness analysis, and is thus more
104    powerful.
105
106    Currently, this pass performs the following optimizations:
107    (A) Removes function arguments that are not live,
108    (B) Removes function return values that are not live across all callers of
109    the function,
110    (C) Removes unneccesary operands, results, region arguments, and region
111    terminator operands of region branch ops, and,
112    (D) Removes simple and region branch ops that have all non-live results and
113    don't affect memory in any way,
114
115    iff
116
117    the IR doesn't have any non-function symbol ops, non-call symbol user ops
118    and branch ops.
119
120    Here, a "simple op" refers to an op that isn't a symbol op, symbol-user op,
121    region branch op, branch op, region branch terminator op, or return-like.
122
123    It is noteworthy that we do not refer to non-live values as "dead" in this
124    file to avoid confusing it with dead code analysis's "dead", which refers to
125    unreachable code (code that never executes on hardware) while "non-live"
126    refers to code that executes on hardware but is unnecessary. Thus, while the
127    removal of dead code helps little in reducing runtime, removing non-live
128    values should theoretically have significant impact (depending on the amount
129    removed).
130
131    It is also important to note that unlike other passes (like `canonicalize`)
132    that apply op-specific optimizations through patterns, this pass uses
133    different interfaces to handle various types of ops and tries to cover all
134    existing ops through these interfaces.
135
136    It is because of its reliance on (a) liveness analysis and (b) interfaces
137    that makes it so powerful that it can optimize ops that don't have a
138    canonicalizer and even when an op does have a canonicalizer, it can perform
139    more aggressive optimizations, as observed in the test files associated with
140    this pass.
141
142    Example of optimization (A):-
143
144    ```
145    int add_2_to_y(int x, int y) {
146      return 2 + y
147    }
148
149    print(add_2_to_y(3, 4))
150    print(add_2_to_y(5, 6))
151    ```
152
153    becomes
154
155    ```
156    int add_2_to_y(int y) {
157      return 2 + y
158    }
159
160    print(add_2_to_y(4))
161    print(add_2_to_y(6))
162    ```
163
164    Example of optimization (B):-
165
166    ```
167    int, int get_incremented_values(int y) {
168      store y somewhere in memory
169      return y + 1, y + 2
170    }
171
172    y1, y2 = get_incremented_values(4)
173    y3, y4 = get_incremented_values(6)
174    print(y2)
175    ```
176
177    becomes
178
179    ```
180    int get_incremented_values(int y) {
181      store y somewhere in memory
182      return y + 2
183    }
184
185    y2 = get_incremented_values(4)
186    y4 = get_incremented_values(6)
187    print(y2)
188    ```
189
190    Example of optimization (C):-
191
192    Assume only `%result1` is live here. Then,
193
194    ```
195    %result1, %result2, %result3 = scf.while (%arg1 = %operand1, %arg2 = %operand2) {
196      %terminator_operand2 = add %arg2, %arg2
197      %terminator_operand3 = mul %arg2, %arg2
198      %terminator_operand4 = add %arg1, %arg1
199      scf.condition(%terminator_operand1) %terminator_operand2, %terminator_operand3, %terminator_operand4
200    } do {
201    ^bb0(%arg3, %arg4, %arg5):
202      %terminator_operand6 = add %arg4, %arg4
203      %terminator_operand5 = add %arg5, %arg5
204      scf.yield %terminator_operand5, %terminator_operand6
205    }
206    ```
207
208    becomes
209
210    ```
211    %result1, %result2 = scf.while (%arg2 = %operand2) {
212      %terminator_operand2 = add %arg2, %arg2
213      %terminator_operand3 = mul %arg2, %arg2
214      scf.condition(%terminator_operand1) %terminator_operand2, %terminator_operand3
215    } do {
216    ^bb0(%arg3, %arg4):
217      %terminator_operand6 = add %arg4, %arg4
218      scf.yield %terminator_operand6
219    }
220    ```
221
222    It is interesting to see that `%result2` won't be removed even though it is
223    not live because `%terminator_operand3` forwards to it and cannot be
224    removed. And, that is because it also forwards to `%arg4`, which is live.
225
226    Example of optimization (D):-
227
228    ```
229    int square_and_double_of_y(int y) {
230      square = y ^ 2
231      double = y * 2
232      return square, double
233    }
234
235    sq, do = square_and_double_of_y(5)
236    print(do)
237    ```
238
239    becomes
240
241    ```
242    int square_and_double_of_y(int y) {
243      double = y * 2
244      return double
245    }
246
247    do = square_and_double_of_y(5)
248    print(do)
249    ```
250  }];
251  let constructor = "mlir::createRemoveDeadValuesPass()";
252}
253
254def PrintIRPass : Pass<"print-ir"> {
255  let summary = "Print IR on the debug stream";
256  let description = [{
257    Print the entire IR on the debug stream. This is meant for debugging
258    purposes to inspect the IR at a specific point in the pipeline.
259  }];
260  let constructor = "mlir::createPrintIRPass()";
261  let options = [
262    Option<"label", "label", "std::string", /*default=*/"", "Label">,
263  ];
264}
265
266def GenerateRuntimeVerification : Pass<"generate-runtime-verification"> {
267  let summary = "Generate additional runtime op verification checks";
268  let description = [{
269    This pass generates op-specific runtime checks using the
270    `RuntimeVerifiableOpInterface`. It can be run for debugging purposes after
271    passes that are suspected to introduce faulty IR.
272  }];
273  let constructor = "mlir::createGenerateRuntimeVerificationPass()";
274}
275
276def Inliner : Pass<"inline"> {
277  let summary = "Inline function calls";
278  let constructor = "mlir::createInlinerPass()";
279  let options = [
280    Option<"defaultPipelineStr", "default-pipeline", "std::string",
281           /*default=*/"\"canonicalize\"",
282           "The optimizer pipeline used for callables that do not have "
283           "a dedicated optimizer pipeline in opPipelineList">,
284    ListOption<"opPipelineList", "op-pipelines", "OpPassManager",
285               "Callable operation specific optimizer pipelines (in the form "
286               "of `dialect.op(pipeline)`)">,
287    Option<"maxInliningIterations", "max-iterations", "unsigned",
288           /*default=*/"4",
289           "Maximum number of iterations when inlining within an SCC">,
290    Option<"inliningThreshold", "inlining-threshold", "unsigned",
291           /*default=*/"-1U",
292           "If the ratio between the number of the operations "
293           "in the callee and the number of the operations "
294           "in the caller exceeds this value (in percentage), "
295           "then the callee is not inlined even if it is legal "
296           "to inline it">,
297  ];
298}
299
300def LocationSnapshot : Pass<"snapshot-op-locations"> {
301  let summary = "Generate new locations from the current IR";
302  let description = [{
303    This pass allows for generating new locations from the IR during any stage
304    of compilation, by snapshotting the IR to a file and using that file to
305    generate new locations for the operations.
306
307    Depending on the value of the `tag` option, different resulting locations
308    may be generated:
309
310    * If unset, the original location of the operation is replaced.
311
312    Example:
313
314    ```mlir
315    // old:
316    ... loc("original_source.cpp":1:1)
317
318    // new:
319    ... loc("snapshot_source.mlir":10:10)
320    ```
321
322    * If set, the new location is fused with the original location in the form
323    of a [`Name Location`](Dialects/Builtin.md/#nameloc) with the specified tag.
324
325    Example:
326
327    ```mlir
328    // old:
329    ... loc("original_source.cpp":1:1)
330
331    // new:
332    ... loc(fused["original_source.cpp":1:1, "snapshot"("snapshot_source.mlir":10:10)])
333    ```
334  }];
335  let options = [
336    Option<"fileName", "filename", "std::string", /*default=*/"",
337           "The filename to print the generated IR">,
338    Option<"tag", "tag", "std::string", /*default=*/"",
339           "A tag to use when fusing the new locations with the "
340           "original. If unset, the locations are replaced.">,
341    Option<"enableDebugInfo", "print-debuginfo", "bool", /*default=*/"false",
342           "Print debug info in MLIR output">,
343    Option<"printGenericOpForm", "print-op-generic", "bool", /*default=*/"false",
344           "Print the generic op form">,
345    Option<"useLocalScope", "print-local-scope", "bool", /*default=*/"false",
346           "Print with local scope and inline information (eliding "
347           "aliases for attributes, types, and locations">,
348    Option<"printPrettyDebugInfo", "pretty-debuginfo", "bool", /*default=*/"false",
349           "Print pretty debug info in MLIR output">,
350  ];
351}
352
353def LoopInvariantCodeMotion : Pass<"loop-invariant-code-motion"> {
354  let summary = "Hoist loop invariant instructions outside of the loop";
355  let constructor = "mlir::createLoopInvariantCodeMotionPass()";
356}
357
358def LoopInvariantSubsetHoisting : Pass<"loop-invariant-subset-hoisting"> {
359  let summary = "Hoist loop invariant subset ops outside of the loop";
360  let constructor = "mlir::createLoopInvariantSubsetHoistingPass()";
361}
362
363def Mem2Reg : Pass<"mem2reg"> {
364  let summary = "Promotes memory slots into values.";
365  let description = [{
366    This pass removes loads out of and stores into a memory slot, and turns
367    them into direct uses of SSA values. This is done generically using the
368    `PromoteAllocationOpInterface`, `PromoteOpInterface` and
369    `PromoteMemOpInterface` interfaces.
370
371    This pass will attempt to compute which definitions of the content of
372    the memory slot reach operations that use the memory slot pointer. It
373    will rewire or remove operations that use the slot pointer so they no
374    longer use it. If any of this is not possible, the IR will be left
375    without mutation.
376
377    This pass only supports unstructured control-flow. Promotion of operations
378    within subregions will not happen.
379  }];
380
381  let options = [
382    Option<"enableRegionSimplification", "region-simplify", "bool",
383       /*default=*/"true",
384       "Perform control flow optimizations to the region tree">,
385  ];
386
387  let statistics = [
388    Statistic<"promotedAmount",
389              "promoted slots",
390              "Total amount of memory slot promoted">,
391    Statistic<"newBlockArgumentAmount",
392              "new block args",
393              "Total amount of new block argument inserted in blocks">,
394  ];
395}
396
397def PrintOpStats : Pass<"print-op-stats"> {
398  let summary = "Print statistics of operations";
399  let constructor = "mlir::createPrintOpStatsPass()";
400  let options = [
401    Option<"printAsJSON", "json", "bool", /*default=*/"false",
402           "print the stats as JSON">
403  ];
404}
405
406def SCCP : Pass<"sccp"> {
407  let summary = "Sparse Conditional Constant Propagation";
408  let description = [{
409    This pass implements a general algorithm for sparse conditional constant
410    propagation. This algorithm detects values that are known to be constant and
411    optimistically propagates this throughout the IR. Any values proven to be
412    constant are replaced, and removed if possible.
413
414    This implementation is based on the algorithm described by Wegman and Zadeck
415    in [“Constant Propagation with Conditional Branches”](https://dl.acm.org/doi/10.1145/103135.103136) (1991).
416  }];
417  let constructor = "mlir::createSCCPPass()";
418}
419
420def SROA : Pass<"sroa"> {
421  let summary = "Scalar Replacement of Aggregates";
422  let description = [{
423    Scalar Replacement of Aggregates. Replaces allocations of aggregates into
424    independant allocations of its elements.
425
426    Allocators must implement `DestructurableAllocationOpInterface` to provide
427    the list of memory slots for which destructuring should be attempted.
428
429    This pass will only be applied if all accessors of the aggregate implement
430    the `DestructurableAccessorOpInterface`. If the accessors provide a view
431    into the struct, users of the view must ensure it is used in a type-safe
432    manner and within bounds by implementing `TypeSafeOpInterface`.
433  }];
434
435  let statistics = [
436    Statistic<
437      "destructuredAmount",
438      "destructured slots",
439      "Total amount of memory slots destructured"
440    >,
441    Statistic<
442      "slotsWithMemoryBenefit",
443      "slots with memory benefit",
444      "Total amount of memory slots in which the destructured size was smaller "
445      "than the total size after eliminating unused fields"
446    >,
447    Statistic<
448      "maxSubelementAmount",
449      "max subelement number",
450      "Maximal number of sub-elements a successfully destructured slot "
451      "initially had"
452    >,
453  ];
454}
455
456def StripDebugInfo : Pass<"strip-debuginfo"> {
457  let summary = "Strip debug info from all operations";
458  let description = [{
459    This pass strips the IR of any location information, by replacing all
460    operation locations with [`unknown`](Dialects/Builtin.md/#unknownloc).
461  }];
462  let constructor = "mlir::createStripDebugInfoPass()";
463}
464
465def SymbolDCE : Pass<"symbol-dce"> {
466  let summary = "Eliminate dead symbols";
467  let description = [{
468    This pass deletes all symbols that are found to be unreachable. This is done
469    by computing the set of operations that are known to be live, propagating
470    that liveness to other symbols, and then deleting all symbols that are not
471    within this live set. Live symbols are those that have a
472    [visibility](SymbolsAndSymbolTables.md/#symbol-visibility) that extends
473    beyond the IR, e.g. `public`, or those that are referenced by live symbols
474    or other non-Symbol operations.
475
476    For example, consider the following input:
477
478    ```mlir
479    func.func private @dead_private_function()
480    func.func private @live_private_function()
481
482    // Note: The `public` isn't necessary here, as this is the default.
483    func.func public @public_function() {
484      "foo.return"() {uses = [@live_private_function]} : () -> ()
485    }
486    ```
487
488    A known live function, `public_function`, contains a reference to an
489    otherwise non-live function `live_private_function`. After running
490    `symbol-dce`, only these two symbols should remain, as the final symbol
491    `dead_private_function` is not visible outside of the current IR and there
492    are no links to known-live operations. After running, we get the expected:
493
494    ```mlir
495    func.func private @live_private_function()
496
497    func.func public @public_function() {
498      "foo.return"() {uses = [@live_private_function]} : () -> ()
499    }
500    ```
501
502    See [Symbols and SymbolTables](SymbolsAndSymbolTables.md) for more
503    information on `Symbols`.
504  }];
505  let constructor = "mlir::createSymbolDCEPass()";
506
507  let statistics = [
508    Statistic<"numDCE", "num-dce'd", "Number of symbols DCE'd">,
509  ];
510}
511
512def SymbolPrivatize : Pass<"symbol-privatize"> {
513  let summary = "Mark symbols private";
514  let description = [{
515    This pass marks all top-level symbols of the operation run as `private`
516    except if listed in `exclude` pass option.
517  }];
518  let options = [
519    ListOption<"exclude", "exclude", "std::string",
520       "Comma separated list of symbols that should not be marked private">
521  ];
522  let constructor = "mlir::createSymbolPrivatizePass()";
523}
524
525def ViewOpGraph : Pass<"view-op-graph"> {
526  let summary = "Print Graphviz visualization of an operation";
527  let description = [{
528    This pass prints a Graphviz graph of a module.
529
530    - Operations are represented as nodes;
531    - Uses (data flow) as edges;
532    - Control flow as dashed edges;
533    - Regions/blocks as subgraphs.
534
535    By default, only data flow edges are printed.
536
537    Note: See https://www.graphviz.org/doc/info/lang.html for more information
538    about the Graphviz DOT language.
539  }];
540  let options = [
541    Option<"maxLabelLen", "max-label-len", "unsigned",
542            /*default=*/"20", "Limit attribute/type length to number of chars">,
543    Option<"printAttrs", "print-attrs", "bool",
544           /*default=*/"true", "Print attributes of operations">,
545    Option<"printControlFlowEdges", "print-control-flow-edges", "bool",
546           /*default=*/"false", "Print control flow edges">,
547    Option<"printDataFlowEdges", "print-data-flow-edges", "bool",
548           /*default=*/"true", "Print data flow edges">,
549    Option<"printResultTypes", "print-result-types", "bool",
550            /*default=*/"true", "Print result types of operations">
551  ];
552  let constructor = "mlir::createPrintOpGraphPass()";
553}
554
555def TopologicalSort : Pass<"topological-sort"> {
556  let summary = "Sort regions without SSA dominance in topological order";
557  let description = [{
558    Recursively sorts all nested regions without SSA dominance in topological
559    order. The main purpose is readability, as well as potentially processing of
560    certain transformations and analyses. The function sorts the operations in
561    all nested regions such that, as much as possible, all users appear after
562    their producers.
563
564    This sort is stable. If the block is already topologically sorted, the IR
565    is not changed. Operations that form a cycle are moved to the end of the
566    regions in a stable order.
567  }];
568
569  let constructor = "mlir::createTopologicalSortPass()";
570}
571
572def CompositeFixedPointPass : Pass<"composite-fixed-point-pass"> {
573  let summary = "Composite fixed point pass";
574  let description = [{
575    Composite pass runs provided set of passes until fixed point or maximum
576    number of iterations reached.
577  }];
578
579  let options = [
580    Option<"name", "name", "std::string", /*default=*/"\"CompositeFixedPointPass\"",
581      "Composite pass display name">,
582    Option<"pipelineStr", "pipeline", "std::string", /*default=*/"",
583      "Composite pass inner pipeline">,
584    Option<"maxIter", "max-iterations", "int", /*default=*/"10",
585      "Maximum number of iterations if inner pipeline">,
586  ];
587}
588
589#endif // MLIR_TRANSFORMS_PASSES
590