xref: /llvm-project/mlir/docs/Tutorials/UnderstandingTheIRStructure.md (revision 6b8edc9f7933d59ababd34ff040fab3d16cb3e3b)
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 &region : 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 &region) {
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![Index Map Example](/includes/img/DefUseChains.svg)
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![Index Map Example](/includes/img/Use-list.svg)
287