| # Understanding the IR Structure |
| |
| The MLIR Language Reference describes the |
| [High Level Structure](../LangRef.md/#high-level-structure), this document |
| illustrates this structure through examples, and introduces at the same time the |
| C++ APIs involved in manipulating it. |
| |
| We will implement a [pass](../PassManagement.md/#operation-pass) that traverses any |
| MLIR input and prints the entity inside the IR. A pass (or in general almost any |
| piece of IR) is always rooted with an operation. Most of the time the top-level |
| operation is a `ModuleOp`, the MLIR `PassManager` is actually limited to |
| operation on a top-level `ModuleOp`. As such a pass starts with an operation, |
| and so will our traversal: |
| |
| ``` |
| void runOnOperation() override { |
| Operation *op = getOperation(); |
| resetIndent(); |
| printOperation(op); |
| } |
| ``` |
| |
| ## Traversing the IR Nesting |
| |
| The IR is recursively nested, an `Operation` can have one or multiple nested |
| `Region`s, each of which is actually a list of `Blocks`, each of which itself |
| wraps a list of `Operation`s. Our traversal will follow this structure with |
| three methods: `printOperation()`, `printRegion()`, and `printBlock()`. |
| |
| The first method inspects the properties of an operation, before iterating on |
| the nested regions and print them individually: |
| |
| ```c++ |
| void printOperation(Operation *op) { |
| // Print the operation itself and some of its properties |
| printIndent() << "visiting op: '" << op->getName() << "' with " |
| << op->getNumOperands() << " operands and " |
| << op->getNumResults() << " results\n"; |
| // Print the operation attributes |
| if (!op->getAttrs().empty()) { |
| printIndent() << op->getAttrs().size() << " attributes:\n"; |
| for (NamedAttribute attr : op->getAttrs()) |
| printIndent() << " - '" << attr.first << "' : '" << attr.second |
| << "'\n"; |
| } |
| |
| // Recurse into each of the regions attached to the operation. |
| printIndent() << " " << op->getNumRegions() << " nested regions:\n"; |
| auto indent = pushIndent(); |
| for (Region ®ion : op->getRegions()) |
| printRegion(region); |
| } |
| ``` |
| |
| A `Region` does not hold anything other than a list of `Block`s: |
| |
| ```c++ |
| void printRegion(Region ®ion) { |
| // A region does not hold anything by itself other than a list of blocks. |
| printIndent() << "Region with " << region.getBlocks().size() |
| << " blocks:\n"; |
| auto indent = pushIndent(); |
| for (Block &block : region.getBlocks()) |
| printBlock(block); |
| } |
| ``` |
| |
| Finally, a `Block` has a list of arguments, and holds a list of `Operation`s: |
| |
| ```c++ |
| void printBlock(Block &block) { |
| // Print the block intrinsics properties (basically: argument list) |
| printIndent() |
| << "Block with " << block.getNumArguments() << " arguments, " |
| << block.getNumSuccessors() |
| << " successors, and " |
| // Note, this `.size()` is traversing a linked-list and is O(n). |
| << block.getOperations().size() << " operations\n"; |
| |
| // A block main role is to hold a list of Operations: let's recurse into |
| // printing each operation. |
| auto indent = pushIndent(); |
| for (Operation &op : block.getOperations()) |
| printOperation(&op); |
| } |
| ``` |
| |
| The code for the pass is available |
| [here in the repo](https://github.com/llvm/llvm-project/blob/main/mlir/test/lib/IR/TestPrintNesting.cpp) |
| and can be exercised with `mlir-opt -test-print-nesting`. |
| |
| ### Example |
| |
| The Pass introduced in the previous section can be applied on the following IR |
| with `mlir-opt -test-print-nesting -allow-unregistered-dialect |
| llvm-project/mlir/test/IR/print-ir-nesting.mlir`: |
| |
| ```mlir |
| "builtin.module"() ( { |
| %0:4 = "dialect.op1"() {"attribute name" = 42 : i32} : () -> (i1, i16, i32, i64) |
| "dialect.op2"() ( { |
| "dialect.innerop1"(%0#0, %0#1) : (i1, i16) -> () |
| }, { |
| "dialect.innerop2"() : () -> () |
| "dialect.innerop3"(%0#0, %0#2, %0#3)[^bb1, ^bb2] : (i1, i32, i64) -> () |
| ^bb1(%1: i32): // pred: ^bb0 |
| "dialect.innerop4"() : () -> () |
| "dialect.innerop5"() : () -> () |
| ^bb2(%2: i64): // pred: ^bb0 |
| "dialect.innerop6"() : () -> () |
| "dialect.innerop7"() : () -> () |
| }) {"other attribute" = 42 : i64} : () -> () |
| }) : () -> () |
| ``` |
| |
| And will yield the following output: |
| |
| ``` |
| visiting op: 'builtin.module' with 0 operands and 0 results |
| 1 nested regions: |
| Region with 1 blocks: |
| Block with 0 arguments, 0 successors, and 3 operations |
| visiting op: 'dialect.op1' with 0 operands and 4 results |
| 1 attributes: |
| - 'attribute name' : '42 : i32' |
| 0 nested regions: |
| visiting op: 'dialect.op2' with 0 operands and 0 results |
| 2 nested regions: |
| Region with 1 blocks: |
| Block with 0 arguments, 0 successors, and 1 operations |
| visiting op: 'dialect.innerop1' with 2 operands and 0 results |
| 0 nested regions: |
| Region with 3 blocks: |
| Block with 0 arguments, 2 successors, and 2 operations |
| visiting op: 'dialect.innerop2' with 0 operands and 0 results |
| 0 nested regions: |
| visiting op: 'dialect.innerop3' with 3 operands and 0 results |
| 0 nested regions: |
| Block with 1 arguments, 0 successors, and 2 operations |
| visiting op: 'dialect.innerop4' with 0 operands and 0 results |
| 0 nested regions: |
| visiting op: 'dialect.innerop5' with 0 operands and 0 results |
| 0 nested regions: |
| Block with 1 arguments, 0 successors, and 2 operations |
| visiting op: 'dialect.innerop6' with 0 operands and 0 results |
| 0 nested regions: |
| visiting op: 'dialect.innerop7' with 0 operands and 0 results |
| 0 nested regions: |
| 0 nested regions: |
| ``` |
| |
| ## Other IR Traversal Methods. |
| |
| In many cases, unwrapping the recursive structure of the IR is cumbersome and |
| you may be interested in using other helpers. |
| |
| ### Filtered iterator: `getOps<OpTy>()` |
| |
| For example the `Block` class exposes a convenient templated method |
| `getOps<OpTy>()` that provided a filtered iterator. Here is an example: |
| |
| ```c++ |
| auto varOps = entryBlock.getOps<spirv::GlobalVariableOp>(); |
| for (spirv::GlobalVariableOp gvOp : varOps) { |
| // process each GlobalVariable Operation in the block. |
| ... |
| } |
| ``` |
| |
| Similarly, the `Region` class exposes the same `getOps` method that will iterate |
| on all the blocks in the region. |
| |
| ### Walkers |
| |
| The `getOps<OpTy>()` is useful to iterate on some Operations immediately listed |
| inside a single block (or a single region), however it is frequently interesting |
| to traverse the IR in a nested fashion. To this end MLIR exposes the `walk()` |
| helper on `Operation`, `Block`, and `Region`. This helper takes a single |
| argument: a callback method that will be invoked for every operation recursively |
| nested under the provided entity. |
| |
| ```c++ |
| // Recursively traverse all the regions and blocks nested inside the function |
| // and apply the callback on every single operation in post-order. |
| getFunction().walk([&](mlir::Operation *op) { |
| // process Operation `op`. |
| }); |
| ``` |
| |
| The provided callback can be specialized to filter on a particular type of |
| Operation, for example the following will apply the callback only on `LinalgOp` |
| operations nested inside the function: |
| |
| ```c++ |
| getFunction.walk([](LinalgOp linalgOp) { |
| // process LinalgOp `linalgOp`. |
| }); |
| ``` |
| |
| Finally, the callback can optionally stop the walk by returning a |
| `WalkResult::interrupt()` value. For example the following walk will find all |
| `AllocOp` nested inside the function and interrupt the traversal if one of them |
| does not satisfy a criteria: |
| |
| ```c++ |
| WalkResult result = getFunction().walk([&](AllocOp allocOp) { |
| if (!isValid(allocOp)) |
| return WalkResult::interrupt(); |
| return WalkResult::advance(); |
| }); |
| if (result.wasInterrupted()) |
| // One alloc wasn't matching. |
| ... |
| ``` |
| |
| ## Traversing the def-use chains |
| |
| Another relationship in the IR is the one that links a `Value` with its users. |
| As defined in the |
| [language reference](../LangRef.md/#high-level-structure), |
| each Value is either a `BlockArgument` or the result of exactly one `Operation` |
| (an `Operation` can have multiple results, each of them is a separate `Value`). |
| The users of a `Value` are `Operation`s, through their arguments: each |
| `Operation` argument references a single `Value`. |
| |
| Here is a code sample that inspects the operands of an `Operation` and prints |
| some information about them: |
| |
| ```c++ |
| // Print information about the producer of each of the operands. |
| for (Value operand : op->getOperands()) { |
| if (Operation *producer = operand.getDefiningOp()) { |
| llvm::outs() << " - Operand produced by operation '" |
| << producer->getName() << "'\n"; |
| } else { |
| // If there is no defining op, the Value is necessarily a Block |
| // argument. |
| auto blockArg = operand.cast<BlockArgument>(); |
| llvm::outs() << " - Operand produced by Block argument, number " |
| << blockArg.getArgNumber() << "\n"; |
| } |
| } |
| ``` |
| |
| Similarly, the following code sample iterates through the result `Value`s |
| produced by an `Operation` and for each result will iterate the users of these |
| results and print informations about them: |
| |
| ```c++ |
| // Print information about the user of each of the result. |
| llvm::outs() << "Has " << op->getNumResults() << " results:\n"; |
| for (auto indexedResult : llvm::enumerate(op->getResults())) { |
| Value result = indexedResult.value(); |
| llvm::outs() << " - Result " << indexedResult.index(); |
| if (result.use_empty()) { |
| llvm::outs() << " has no uses\n"; |
| continue; |
| } |
| if (result.hasOneUse()) { |
| llvm::outs() << " has a single use: "; |
| } else { |
| llvm::outs() << " has " |
| << std::distance(result.getUses().begin(), |
| result.getUses().end()) |
| << " uses:\n"; |
| } |
| for (Operation *userOp : result.getUsers()) { |
| llvm::outs() << " - " << userOp->getName() << "\n"; |
| } |
| } |
| ``` |
| |
| The illustrating code for this pass is available |
| [here in the repo](https://github.com/llvm/llvm-project/blob/main/mlir/test/lib/IR/TestPrintDefUse.cpp) |
| and can be exercised with `mlir-opt -test-print-defuse`. |
| |
| The chaining of `Value`s and their uses can be viewed as following: |
| |
|  |
| |
| The uses of a `Value` (`OpOperand` or `BlockOperand`) are also chained in a |
| doubly linked-list, which is particularly useful when replacing all uses of a |
| `Value` with a new one ("RAUW"): |
| |
|  |