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