163d1dc66SMehdi Amini# Understanding the IR Structure 263d1dc66SMehdi Amini 363d1dc66SMehdi AminiThe MLIR Language Reference describes the 431d1ae79SMarkus Böck[High Level Structure](../LangRef.md/#high-level-structure), this document 563d1dc66SMehdi Aminiillustrates this structure through examples, and introduces at the same time the 663d1dc66SMehdi AminiC++ APIs involved in manipulating it. 763d1dc66SMehdi Amini 831d1ae79SMarkus BöckWe will implement a [pass](../PassManagement.md/#operation-pass) that traverses any 963d1dc66SMehdi AminiMLIR input and prints the entity inside the IR. A pass (or in general almost any 1063d1dc66SMehdi Aminipiece of IR) is always rooted with an operation. Most of the time the top-level 1163d1dc66SMehdi Aminioperation is a `ModuleOp`, the MLIR `PassManager` is actually limited to 1263d1dc66SMehdi Aminioperation on a top-level `ModuleOp`. As such a pass starts with an operation, 1363d1dc66SMehdi Aminiand so will our traversal: 1463d1dc66SMehdi Amini 1563d1dc66SMehdi Amini``` 1663d1dc66SMehdi Amini void runOnOperation() override { 1763d1dc66SMehdi Amini Operation *op = getOperation(); 1863d1dc66SMehdi Amini resetIndent(); 1963d1dc66SMehdi Amini printOperation(op); 2063d1dc66SMehdi Amini } 2163d1dc66SMehdi Amini``` 2263d1dc66SMehdi Amini 2363d1dc66SMehdi Amini## Traversing the IR Nesting 2463d1dc66SMehdi Amini 2563d1dc66SMehdi AminiThe IR is recursively nested, an `Operation` can have one or multiple nested 2663d1dc66SMehdi Amini`Region`s, each of which is actually a list of `Blocks`, each of which itself 2763d1dc66SMehdi Aminiwraps a list of `Operation`s. Our traversal will follow this structure with 2863d1dc66SMehdi Aminithree methods: `printOperation()`, `printRegion()`, and `printBlock()`. 2963d1dc66SMehdi Amini 3063d1dc66SMehdi AminiThe first method inspects the properties of an operation, before iterating on 3163d1dc66SMehdi Aminithe nested regions and print them individually: 3263d1dc66SMehdi Amini 3363d1dc66SMehdi Amini```c++ 3463d1dc66SMehdi Amini void printOperation(Operation *op) { 3563d1dc66SMehdi Amini // Print the operation itself and some of its properties 3663d1dc66SMehdi Amini printIndent() << "visiting op: '" << op->getName() << "' with " 3763d1dc66SMehdi Amini << op->getNumOperands() << " operands and " 3863d1dc66SMehdi Amini << op->getNumResults() << " results\n"; 3963d1dc66SMehdi Amini // Print the operation attributes 4063d1dc66SMehdi Amini if (!op->getAttrs().empty()) { 4163d1dc66SMehdi Amini printIndent() << op->getAttrs().size() << " attributes:\n"; 4263d1dc66SMehdi Amini for (NamedAttribute attr : op->getAttrs()) 43fd927359SMehdi Amini printIndent() << " - '" << attr.getName() << "' : '" 44fd927359SMehdi Amini << attr.getValue() << "'\n"; 4563d1dc66SMehdi Amini } 4663d1dc66SMehdi Amini 4763d1dc66SMehdi Amini // Recurse into each of the regions attached to the operation. 4863d1dc66SMehdi Amini printIndent() << " " << op->getNumRegions() << " nested regions:\n"; 4963d1dc66SMehdi Amini auto indent = pushIndent(); 5063d1dc66SMehdi Amini for (Region ®ion : op->getRegions()) 5163d1dc66SMehdi Amini printRegion(region); 5263d1dc66SMehdi Amini } 5363d1dc66SMehdi Amini``` 5463d1dc66SMehdi Amini 5563d1dc66SMehdi AminiA `Region` does not hold anything other than a list of `Block`s: 5663d1dc66SMehdi Amini 5763d1dc66SMehdi Amini```c++ 5863d1dc66SMehdi Amini void printRegion(Region ®ion) { 5963d1dc66SMehdi Amini // A region does not hold anything by itself other than a list of blocks. 6063d1dc66SMehdi Amini printIndent() << "Region with " << region.getBlocks().size() 6163d1dc66SMehdi Amini << " blocks:\n"; 6263d1dc66SMehdi Amini auto indent = pushIndent(); 6363d1dc66SMehdi Amini for (Block &block : region.getBlocks()) 6463d1dc66SMehdi Amini printBlock(block); 6563d1dc66SMehdi Amini } 6663d1dc66SMehdi Amini``` 6763d1dc66SMehdi Amini 6863d1dc66SMehdi AminiFinally, a `Block` has a list of arguments, and holds a list of `Operation`s: 6963d1dc66SMehdi Amini 7063d1dc66SMehdi Amini```c++ 7163d1dc66SMehdi Amini void printBlock(Block &block) { 7263d1dc66SMehdi Amini // Print the block intrinsics properties (basically: argument list) 7363d1dc66SMehdi Amini printIndent() 7463d1dc66SMehdi Amini << "Block with " << block.getNumArguments() << " arguments, " 7563d1dc66SMehdi Amini << block.getNumSuccessors() 7663d1dc66SMehdi Amini << " successors, and " 7763d1dc66SMehdi Amini // Note, this `.size()` is traversing a linked-list and is O(n). 7863d1dc66SMehdi Amini << block.getOperations().size() << " operations\n"; 7963d1dc66SMehdi Amini 8063d1dc66SMehdi Amini // A block main role is to hold a list of Operations: let's recurse into 8163d1dc66SMehdi Amini // printing each operation. 8263d1dc66SMehdi Amini auto indent = pushIndent(); 8363d1dc66SMehdi Amini for (Operation &op : block.getOperations()) 8463d1dc66SMehdi Amini printOperation(&op); 8563d1dc66SMehdi Amini } 8663d1dc66SMehdi Amini``` 8763d1dc66SMehdi Amini 8863d1dc66SMehdi AminiThe code for the pass is available 8994fac81fSxgupta[here in the repo](https://github.com/llvm/llvm-project/blob/main/mlir/test/lib/IR/TestPrintNesting.cpp) 9063d1dc66SMehdi Aminiand can be exercised with `mlir-opt -test-print-nesting`. 9163d1dc66SMehdi Amini 9263d1dc66SMehdi Amini### Example 9363d1dc66SMehdi Amini 9463d1dc66SMehdi AminiThe Pass introduced in the previous section can be applied on the following IR 9563d1dc66SMehdi Aminiwith `mlir-opt -test-print-nesting -allow-unregistered-dialect 9663d1dc66SMehdi Aminillvm-project/mlir/test/IR/print-ir-nesting.mlir`: 9763d1dc66SMehdi Amini 9863d1dc66SMehdi Amini```mlir 99f8479d9dSRiver Riddle"builtin.module"() ( { 100*6b8edc9fScsstormq %results:4 = "dialect.op1"() {"attribute name" = 42 : i32} : () -> (i1, i16, i32, i64) 10163d1dc66SMehdi Amini "dialect.op2"() ( { 102*6b8edc9fScsstormq "dialect.innerop1"(%results#0, %results#1) : (i1, i16) -> () 10363d1dc66SMehdi Amini }, { 10463d1dc66SMehdi Amini "dialect.innerop2"() : () -> () 105*6b8edc9fScsstormq "dialect.innerop3"(%results#0, %results#2, %results#3)[^bb1, ^bb2] : (i1, i32, i64) -> () 10663d1dc66SMehdi Amini ^bb1(%1: i32): // pred: ^bb0 10763d1dc66SMehdi Amini "dialect.innerop4"() : () -> () 10863d1dc66SMehdi Amini "dialect.innerop5"() : () -> () 10963d1dc66SMehdi Amini ^bb2(%2: i64): // pred: ^bb0 11063d1dc66SMehdi Amini "dialect.innerop6"() : () -> () 11163d1dc66SMehdi Amini "dialect.innerop7"() : () -> () 11263d1dc66SMehdi Amini }) {"other attribute" = 42 : i64} : () -> () 11363d1dc66SMehdi Amini}) : () -> () 11463d1dc66SMehdi Amini``` 11563d1dc66SMehdi Amini 11663d1dc66SMehdi AminiAnd will yield the following output: 11763d1dc66SMehdi Amini 11863d1dc66SMehdi Amini``` 119f8479d9dSRiver Riddlevisiting op: 'builtin.module' with 0 operands and 0 results 12063d1dc66SMehdi Amini 1 nested regions: 12163d1dc66SMehdi Amini Region with 1 blocks: 12255774937SJie Fu Block with 0 arguments, 0 successors, and 2 operations 12363d1dc66SMehdi Amini visiting op: 'dialect.op1' with 0 operands and 4 results 12463d1dc66SMehdi Amini 1 attributes: 12563d1dc66SMehdi Amini - 'attribute name' : '42 : i32' 12663d1dc66SMehdi Amini 0 nested regions: 12763d1dc66SMehdi Amini visiting op: 'dialect.op2' with 0 operands and 0 results 128*6b8edc9fScsstormq 1 attributes: 129*6b8edc9fScsstormq - 'other attribute' : '42 : i64' 13063d1dc66SMehdi Amini 2 nested regions: 13163d1dc66SMehdi Amini Region with 1 blocks: 13263d1dc66SMehdi Amini Block with 0 arguments, 0 successors, and 1 operations 13363d1dc66SMehdi Amini visiting op: 'dialect.innerop1' with 2 operands and 0 results 13463d1dc66SMehdi Amini 0 nested regions: 13563d1dc66SMehdi Amini Region with 3 blocks: 13663d1dc66SMehdi Amini Block with 0 arguments, 2 successors, and 2 operations 13763d1dc66SMehdi Amini visiting op: 'dialect.innerop2' with 0 operands and 0 results 13863d1dc66SMehdi Amini 0 nested regions: 13963d1dc66SMehdi Amini visiting op: 'dialect.innerop3' with 3 operands and 0 results 14063d1dc66SMehdi Amini 0 nested regions: 14163d1dc66SMehdi Amini Block with 1 arguments, 0 successors, and 2 operations 14263d1dc66SMehdi Amini visiting op: 'dialect.innerop4' with 0 operands and 0 results 14363d1dc66SMehdi Amini 0 nested regions: 14463d1dc66SMehdi Amini visiting op: 'dialect.innerop5' with 0 operands and 0 results 14563d1dc66SMehdi Amini 0 nested regions: 14663d1dc66SMehdi Amini Block with 1 arguments, 0 successors, and 2 operations 14763d1dc66SMehdi Amini visiting op: 'dialect.innerop6' with 0 operands and 0 results 14863d1dc66SMehdi Amini 0 nested regions: 14963d1dc66SMehdi Amini visiting op: 'dialect.innerop7' with 0 operands and 0 results 15063d1dc66SMehdi Amini 0 nested regions: 15163d1dc66SMehdi Amini``` 15263d1dc66SMehdi Amini 15355774937SJie Fu## Other IR Traversal Methods 15463d1dc66SMehdi Amini 15563d1dc66SMehdi AminiIn many cases, unwrapping the recursive structure of the IR is cumbersome and 15663d1dc66SMehdi Aminiyou may be interested in using other helpers. 15763d1dc66SMehdi Amini 15863d1dc66SMehdi Amini### Filtered iterator: `getOps<OpTy>()` 15963d1dc66SMehdi Amini 16063d1dc66SMehdi AminiFor example the `Block` class exposes a convenient templated method 16163d1dc66SMehdi Amini`getOps<OpTy>()` that provided a filtered iterator. Here is an example: 16263d1dc66SMehdi Amini 16363d1dc66SMehdi Amini```c++ 16463d1dc66SMehdi Amini auto varOps = entryBlock.getOps<spirv::GlobalVariableOp>(); 16563d1dc66SMehdi Amini for (spirv::GlobalVariableOp gvOp : varOps) { 16663d1dc66SMehdi Amini // process each GlobalVariable Operation in the block. 16763d1dc66SMehdi Amini ... 16863d1dc66SMehdi Amini } 16963d1dc66SMehdi Amini``` 17063d1dc66SMehdi Amini 17163d1dc66SMehdi AminiSimilarly, the `Region` class exposes the same `getOps` method that will iterate 17263d1dc66SMehdi Aminion all the blocks in the region. 17363d1dc66SMehdi Amini 17463d1dc66SMehdi Amini### Walkers 17563d1dc66SMehdi Amini 17663d1dc66SMehdi AminiThe `getOps<OpTy>()` is useful to iterate on some Operations immediately listed 17763d1dc66SMehdi Aminiinside a single block (or a single region), however it is frequently interesting 17863d1dc66SMehdi Aminito traverse the IR in a nested fashion. To this end MLIR exposes the `walk()` 17963d1dc66SMehdi Aminihelper on `Operation`, `Block`, and `Region`. This helper takes a single 18063d1dc66SMehdi Aminiargument: a callback method that will be invoked for every operation recursively 18163d1dc66SMehdi Amininested under the provided entity. 18263d1dc66SMehdi Amini 18363d1dc66SMehdi Amini```c++ 18463d1dc66SMehdi Amini // Recursively traverse all the regions and blocks nested inside the function 18563d1dc66SMehdi Amini // and apply the callback on every single operation in post-order. 18663d1dc66SMehdi Amini getFunction().walk([&](mlir::Operation *op) { 18763d1dc66SMehdi Amini // process Operation `op`. 18863d1dc66SMehdi Amini }); 18963d1dc66SMehdi Amini``` 19063d1dc66SMehdi Amini 19163d1dc66SMehdi AminiThe provided callback can be specialized to filter on a particular type of 19263d1dc66SMehdi AminiOperation, for example the following will apply the callback only on `LinalgOp` 19363d1dc66SMehdi Aminioperations nested inside the function: 19463d1dc66SMehdi Amini 19563d1dc66SMehdi Amini```c++ 1961fe1f913SIngo Müller getFunction().walk([](LinalgOp linalgOp) { 19763d1dc66SMehdi Amini // process LinalgOp `linalgOp`. 19863d1dc66SMehdi Amini }); 19963d1dc66SMehdi Amini``` 20063d1dc66SMehdi Amini 20163d1dc66SMehdi AminiFinally, the callback can optionally stop the walk by returning a 20263d1dc66SMehdi Amini`WalkResult::interrupt()` value. For example the following walk will find all 20363d1dc66SMehdi Amini`AllocOp` nested inside the function and interrupt the traversal if one of them 20463d1dc66SMehdi Aminidoes not satisfy a criteria: 20563d1dc66SMehdi Amini 20663d1dc66SMehdi Amini```c++ 20763d1dc66SMehdi Amini WalkResult result = getFunction().walk([&](AllocOp allocOp) { 20863d1dc66SMehdi Amini if (!isValid(allocOp)) 20963d1dc66SMehdi Amini return WalkResult::interrupt(); 21063d1dc66SMehdi Amini return WalkResult::advance(); 21163d1dc66SMehdi Amini }); 21263d1dc66SMehdi Amini if (result.wasInterrupted()) 21363d1dc66SMehdi Amini // One alloc wasn't matching. 21463d1dc66SMehdi Amini ... 21563d1dc66SMehdi Amini``` 21663d1dc66SMehdi Amini 21763d1dc66SMehdi Amini## Traversing the def-use chains 21863d1dc66SMehdi Amini 21963d1dc66SMehdi AminiAnother relationship in the IR is the one that links a `Value` with its users. 22063d1dc66SMehdi AminiAs defined in the 22131d1ae79SMarkus Böck[language reference](../LangRef.md/#high-level-structure), 22263d1dc66SMehdi Aminieach Value is either a `BlockArgument` or the result of exactly one `Operation` 22363d1dc66SMehdi Amini(an `Operation` can have multiple results, each of them is a separate `Value`). 22463d1dc66SMehdi AminiThe users of a `Value` are `Operation`s, through their arguments: each 22563d1dc66SMehdi Amini`Operation` argument references a single `Value`. 22663d1dc66SMehdi Amini 22763d1dc66SMehdi AminiHere is a code sample that inspects the operands of an `Operation` and prints 22863d1dc66SMehdi Aminisome information about them: 22963d1dc66SMehdi Amini 23063d1dc66SMehdi Amini```c++ 23163d1dc66SMehdi Amini // Print information about the producer of each of the operands. 23263d1dc66SMehdi Amini for (Value operand : op->getOperands()) { 23363d1dc66SMehdi Amini if (Operation *producer = operand.getDefiningOp()) { 23463d1dc66SMehdi Amini llvm::outs() << " - Operand produced by operation '" 23563d1dc66SMehdi Amini << producer->getName() << "'\n"; 23663d1dc66SMehdi Amini } else { 23763d1dc66SMehdi Amini // If there is no defining op, the Value is necessarily a Block 23863d1dc66SMehdi Amini // argument. 23963d1dc66SMehdi Amini auto blockArg = operand.cast<BlockArgument>(); 24063d1dc66SMehdi Amini llvm::outs() << " - Operand produced by Block argument, number " 24163d1dc66SMehdi Amini << blockArg.getArgNumber() << "\n"; 24263d1dc66SMehdi Amini } 24363d1dc66SMehdi Amini } 24463d1dc66SMehdi Amini``` 24563d1dc66SMehdi Amini 24663d1dc66SMehdi AminiSimilarly, the following code sample iterates through the result `Value`s 24763d1dc66SMehdi Aminiproduced by an `Operation` and for each result will iterate the users of these 24863d1dc66SMehdi Aminiresults and print informations about them: 24963d1dc66SMehdi Amini 25063d1dc66SMehdi Amini```c++ 25163d1dc66SMehdi Amini // Print information about the user of each of the result. 25263d1dc66SMehdi Amini llvm::outs() << "Has " << op->getNumResults() << " results:\n"; 25363d1dc66SMehdi Amini for (auto indexedResult : llvm::enumerate(op->getResults())) { 25463d1dc66SMehdi Amini Value result = indexedResult.value(); 25563d1dc66SMehdi Amini llvm::outs() << " - Result " << indexedResult.index(); 25663d1dc66SMehdi Amini if (result.use_empty()) { 25763d1dc66SMehdi Amini llvm::outs() << " has no uses\n"; 25863d1dc66SMehdi Amini continue; 25963d1dc66SMehdi Amini } 26063d1dc66SMehdi Amini if (result.hasOneUse()) { 26163d1dc66SMehdi Amini llvm::outs() << " has a single use: "; 26263d1dc66SMehdi Amini } else { 26363d1dc66SMehdi Amini llvm::outs() << " has " 26463d1dc66SMehdi Amini << std::distance(result.getUses().begin(), 26563d1dc66SMehdi Amini result.getUses().end()) 26663d1dc66SMehdi Amini << " uses:\n"; 26763d1dc66SMehdi Amini } 26863d1dc66SMehdi Amini for (Operation *userOp : result.getUsers()) { 26963d1dc66SMehdi Amini llvm::outs() << " - " << userOp->getName() << "\n"; 27063d1dc66SMehdi Amini } 27163d1dc66SMehdi Amini } 27263d1dc66SMehdi Amini``` 27363d1dc66SMehdi Amini 27463d1dc66SMehdi AminiThe illustrating code for this pass is available 27594fac81fSxgupta[here in the repo](https://github.com/llvm/llvm-project/blob/main/mlir/test/lib/IR/TestPrintDefUse.cpp) 27663d1dc66SMehdi Aminiand can be exercised with `mlir-opt -test-print-defuse`. 27763d1dc66SMehdi Amini 27863d1dc66SMehdi AminiThe chaining of `Value`s and their uses can be viewed as following: 27963d1dc66SMehdi Amini 28063d1dc66SMehdi Amini 28163d1dc66SMehdi Amini 28263d1dc66SMehdi AminiThe uses of a `Value` (`OpOperand` or `BlockOperand`) are also chained in a 28363d1dc66SMehdi Aminidoubly linked-list, which is particularly useful when replacing all uses of a 28463d1dc66SMehdi Amini`Value` with a new one ("RAUW"): 28563d1dc66SMehdi Amini 28663d1dc66SMehdi Amini 287