1# Bufferization 2 3[TOC] 4 5## Overview 6 7Bufferization in MLIR is the process of converting ops with `tensor` semantics 8to ops with `memref` semantics. MLIR provides an infrastructure that bufferizes 9an entire program in a single pass (*One-Shot Bufferize*). This infrastructure 10bufferizes all ops that implement the 11[`BufferizableOpInterface`](https://github.com/llvm/llvm-project/blob/17a68065c378da74805e4e1b9a5b78cc9f83e580/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.td) 12can be bufferized. 13 14MLIR has an older bufferization infrastructure built around 15[dialect conversion](DialectConversion.md). Most dialect conversion 16bufferization patterns have been migrated to One-Shot Bufferize, but some 17functionality such as function boundary bufferization still depends on dialect 18conversion and its type converter. New projects should use One-Shot Bufferize, 19as the dialect conversion-based bufferization will eventually be deprecated. 20Moreover, One-Shot Bufferize results in better bufferization with fewer memory 21allocations and buffer copies. This documentation is mostly about One-Shot 22Bufferize, but also describes how to gradually migrate a project from dialect 23conversion-based bufferization to One-Shot Bufferize. 24 25## What is One-Shot Bufferize? 26 27One-Shot Bufferize is a new tensor bufferization pass designed for IR in 28[destination-passing style](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/11/dps-fhpc17.pdf), 29and with aggressive in-place bufferization. 30 31One-Shot Bufferize is: 32 33* **Monolithic**: A single MLIR pass does the entire 34work, whereas the previous bufferization in MLIR was split across multiple 35passes residing in different dialects. In One-Shot Bufferize, 36`BufferizableOpInterface` implementations are spread across different dialects. 37 38* A **whole-function at a time analysis**. In-place bufferization decisions are 39made by analyzing SSA use-def chains on tensors. Op interface implementations 40not only provide the rewrite logic from tensor ops to memref ops, but also 41helper methods for One-Shot Bufferize's analysis to query information about an 42op's bufferization/memory semantics. 43 44* **Extensible** via an op interface: All 45ops that implement `BufferizableOpInterface` can be bufferized. 46 47* **2-Pass**: 48Bufferization is internally broken down into 2 steps: First, analyze the entire 49IR and make bufferization decisions. Then, bufferize (rewrite) the IR. The 50analysis has access to exact SSA use-def information. It incrementally builds 51alias and equivalence sets and does not rely on a posteriori-alias analysis from 52preallocated memory. 53 54* **Greedy**: Operations are analyzed one-by-one and it is 55decided on the spot whether a tensor OpOperand must be copied or not. Heuristics 56determine the order of analysis. 57 58* **Modular**: The current One-Shot Analysis 59can be replaced with a different analysis. The result of the analysis are 60queried by the bufferization via `BufferizationState`, in particular 61`BufferizationState::isInPlace`. Any derived class of `BufferizationState` that 62implements a small number virtual functions can serve as a custom analysis. It 63is even possible to run One-Shot Bufferize without any analysis 64(`AlwaysCopyBufferizationState`), in which case One-Shot Bufferize behaves 65exactly like the old dialect conversion-based bufferization (i.e., copy every 66buffer before writing to it). 67 68To reduce complexity, One-Shot Bufferize should be 69[run after other transformations](https://llvm.discourse.group/t/rfc-linalg-on-tensors-update-and-comprehensive-bufferization-rfc/3373), 70typically as one of the last steps right before lowering memref ops. Many 71transformations are easier in tensor land; e.g., tile/fuse/… on tensors first, 72then bufferize the remaining IR. 73 74From an architecture perspective, One-Shot Bufferize consists of 75[BufferizableOpInterface](https://github.com/llvm/llvm-project/blob/17a68065c378da74805e4e1b9a5b78cc9f83e580/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.td) 76(and its implementations) and an 77[analysis](https://github.com/llvm/llvm-project/blob/ae2764e835a26bad9774803eca0a6530df2a3e2d/mlir/include/mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h#L164) 78of tensor SSA values that decides if a buffer can be used directly or must be 79copied. The [bufferize] method of the op interface inspects analysis results and 80rewrites tensor ops into memref ops. 81 82## Goals of Bufferization 83 84The high-level goal of every bufferization technique is to: 1. Use as little 85memory as possible. 2. Copy as little memory as possible. 86 87This implies reusing already allocated buffers when possible, turning 88bufferization into an algorithmically complex problem with similarities to 89register allocation. 90 91Depending on the concrete use case, there may be additional bufferization 92requirements. If the contents of a buffer are expensive to compute, there could 93be a tradeoff between *recomputation* and *compute once and copy*. On the 94contrary, it may not even be possible to allocate new buffers at runtime on some 95architectures. 96 97## Destination-Passing Style 98 99Bufferization is an algorithmically complex problem. Given an op with a tensor 100result, bufferization has to choose a memref buffer in which the result can be 101stored. It is always safe to allocate a brand new buffer, but such a 102bufferization strategy would be unacceptable for high-performance codegen. When 103choosing an already existing buffer, we must be careful not to accidentally 104overwrite data that is still needed later in the program. 105 106To simplify this problem, One-Shot Bufferize was designed for ops that are in 107*destination-passing style*. For every tensor result, such ops have a tensor 108operand, who's buffer could be for storing the result of the op in the absence 109of other conflicts. We call such tensor operands the *destination*. 110 111As an example, consider the following op: `%0 = tensor.insert %cst into 112%t[%idx] : tensor<?xf32>` 113 114`%t` is the destination in this example. When choosing a buffer for the result 115`%0`, One-Shot Bufferize considers only two options: 116 1171. buffer(`%0`) = buffer(`%t`). 1182. buffer(`%0`) is a newly allocated buffer. 119 120There may be other buffers in the same function that could potentially be used 121for buffer(`%0`), but those are not considered by One-Shot Bufferize to keep the 122bufferization simple. One-Shot Bufferize could be extended to consider such 123buffers in the future to achieve a better quality of bufferization. 124 125Tensor ops that are not in destination-passing style always bufferize to a 126memory allocation. E.g.: 127 128```mlir 129%0 = tensor.generate %sz { 130^bb0(%i : index): 131 %cst = arith.constant 0.0 : f32 132 tensor.yield %cst : f32 133} : tensor<?xf32> 134``` 135 136The result of `tensor.generate` does not have a "destination", so bufferization 137allocates a new buffer. This could be avoided by choosing an op such as 138`linalg.generic`, which can express the same computation with a destination 139("out") tensor: 140 141```mlir 142#map = affine_map<(i) -> (i)> 143%0 = linalg.generic {indexing_maps = [#map], iterator_types = ["parallel"]} 144 outs(%t : tensor<?xf32>) { 145 ^bb0(%arg0 : f32): 146 %cst = arith.constant 0.0 : f32 147 linalg.yield %cst : f32 148} -> tensor<?xf32> 149``` 150 151At first glance, the above `linalg.generic` op may not seem very useful because 152the output tensor `%t` is entirely overwritten. Why pass the tensor `%t` as an 153operand in the first place? As an example, this can be useful for overwriting a 154slice of a tensor: 155 156```mlir 157%t = tensor.extract_slice %s [%idx] [%sz] [1] : tensor<?xf32> to tensor<?xf32> 158%0 = linalg.generic ... outs(%t) { ... } -> tensor<?xf32> 159%1 = tensor.insert_slice %0 into %s [%idx] [%sz] [1] 160 : tensor<?xf32> into tensor<?xf32> 161``` 162 163The above example bufferizes to a `memref.subview`, followed by a 164"`linalg.generic` on memrefs" that overwrites the memory of the subview. The 165`tensor.insert_slice` bufferizes to a no-op (in the absence of RaW conflicts 166such as a subsequent read of `%s`). 167 168RaW conflicts are detected with an analysis of SSA use-def chains (details 169later). One-Shot Bufferize works best if there is a single SSA use-def chain, 170where the result of a tensor op is the "destination" operand of the next tensor 171ops, e.g.: 172 173```mlir 174%0 = "my_dialect.some_op"(%t) : (tensor<?xf32>) -> (tensor<?xf32>) 175%1 = "my_dialect.another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>) 176%2 = "my_dialect.yet_another_op"(%1) : (tensor<?xf32>) -> (tensor<?xf32>) 177``` 178 179Buffer copies are likely inserted if the SSA use-def chain splits at some point, 180e.g.: 181 182```mlir 183%0 = "my_dialect.some_op"(%t) : (tensor<?xf32>) -> (tensor<?xf32>) 184%1 = "my_dialect.another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>) 185%2 = "my_dialect.yet_another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>) 186``` 187 188One-Shot Bufferize has debug flags (`test-analysis-only print-conflicts`) that 189print the results of the analysis and explain to the user why buffer copies were 190inserted. 191 192## Using One-Shot Bufferize 193 194MLIR provides a pass 195[`-one-shot-bufferize`](https://mlir.llvm.org/docs/Passes/#-one-shot-bufferize-one-shot-bufferize) 196that performs an analysis and bufferizes all ops with tensor semantics that 197implement `BufferizableOpInterface`. For modularity reasons, these op interface 198implementations are typically external models that live in a dialect's 199"Transforms" build unit. (External models are a mechanism for implementing an op 200interface in a different build unit.) It is the user's responsibility to ensure 201that all needed external models are registered before running One-Shot 202Bufferize. 203 204By default, One-Shot Bufferize fails when it encounters an op with tensor 205semantics (i.e., tensor result or tensor operand) that is not bufferizable 206(i.e., does not implement `BufferizableOpInterface`). This can be avoided with 207`allow-unknown-ops`. In that case, One-Shot Bufferize inserts 208`to_memref`/`to_tensor` ops around the bufferization boundary. These ops are 209named versions of `unrealized_conversion_cast`. Note that One-Shot Bufferize's 210analysis can currently not analyze these ops, so input IR with such ops may fail 211bufferization. Therefore, running One-Shot Bufferize multiple times in a 212sequence is also not supported at the moment. 213 214One-Shot Bufferize can be configured to bufferize only ops from a set of 215dialects with `dialect-filter`. This can be useful for gradually migrating from 216dialect conversion-based bufferization to One-Shot Bufferize. One-Shot Bufferize 217must run first in such a case, because dialect conversion-based bufferization 218generates `to_tensor`/`to_memref` ops which One-Shot Bufferize cannot analyze. 219 220One-Shot Bufferize can also be called programmatically with 221[`bufferization::runOneShotBufferize`](https://github.com/llvm/llvm-project/blob/ae2764e835a26bad9774803eca0a6530df2a3e2d/mlir/include/mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h#L167). 222Alternatively, 223[`bufferization::bufferizeOp`](https://github.com/llvm/llvm-project/blob/ae2764e835a26bad9774803eca0a6530df2a3e2d/mlir/include/mlir/Dialect/Bufferization/Transforms/Bufferize.h#L78) 224skips the analysis and inserts a copy on every buffer write, just like the 225dialect conversion-based bufferization. 226 227## Buffer Deallocation 228 229One-Shot Bufferize deallocates all buffers that it allocates. This is in 230contrast to the dialect conversion-based bufferization that delegates this job 231to the 232[`-buffer-deallocation`](https://mlir.llvm.org/docs/Passes/#-buffer-deallocation-adds-all-required-dealloc-operations-for-all-allocations-in-the-input-program) 233pass. By default, One-Shot Bufferize rejects IR where a newly allocated buffer 234is returned from a block. Such IR will fail bufferization. 235 236A new buffer allocation is returned from a block when the result of an op that 237is not in destination-passing style is returned. E.g.: 238 239```mlir 240%0 = scf.if %c -> (tensor<?xf32>) { 241 %1 = tensor.generate ... -> tensor<?xf32> 242 scf.yield %1 : tensor<?xf32> 243} else { 244 scf.yield %another_tensor : tensor<?xf32> 245} 246``` 247 248The `scf.yield` in the "else" branch is OK, but the `scf.yield` in the "then" 249branch will be rejected. 250 251Another case in which a buffer allocation may be returned is when a buffer copy 252must be inserted due to a RaW conflict. E.g.: 253 254```mlir 255%0 = scf.if %c -> (tensor<?xf32>) { 256 %1 = tensor.insert %cst into %another_tensor[%idx] : tensor<?xf32> 257 "my_dialect.reading_tensor_op"(%another_tensor) : (tensor<?xf32>) -> () 258 ... 259 scf.yield %1 : tensor<?xf32> 260} else { 261 scf.yield %yet_another_tensor : tensor<?xf32> 262} 263``` 264 265In the above example, a buffer copy of buffer(`%another_tensor`) (with `%cst` 266inserted) is yielded from the "then" branch. 267 268In both examples, a buffer is allocated inside of a block and then yielded from 269the block. Deallocation of such buffers is tricky and not currently implemented 270in an efficient way. For this reason, One-Shot Bufferize must be explicitly 271configured with `allow-return-allocs` to support such IR. 272 273When running with `allow-return-allocs`, One-Shot Bufferize resolves yields of 274newly allocated buffers with copies. E.g., the `scf.if` example above would 275bufferize to IR similar to the following: 276 277```mlir 278%0 = scf.if %c -> (memref<?xf32>) { 279 %1 = memref.alloc(...) : memref<?xf32> 280 ... 281 scf.yield %1 : memref<?xf32> 282} else { 283 %2 = memref.alloc(...) : memref<?xf32> 284 memref.copy %another_memref, %2 285 scf.yield %2 : memref<?xf32> 286} 287``` 288 289In the bufferized IR, both branches return a newly allocated buffer, so it does 290not matter which if-branch was taken. In both cases, the resulting buffer `%0` 291must be deallocated at some point after the `scf.if` (unless the `%0` is 292returned/yielded from its block). 293 294One-Shot Bufferize internally utilizes functionality from the 295[Buffer Deallocation](https://mlir.llvm.org/docs/BufferDeallocationInternals/) 296pass to deallocate yielded buffers. Therefore, ops with regions must implement 297the `RegionBranchOpInterface` when `allow-return-allocs`. 298 299Note: Buffer allocations that are returned from a function are not deallocated. 300It is the caller's responsibility to deallocate the buffer. In the future, this 301could be automated with allocation hoisting (across function boundaries) or 302reference counting. 303 304One-Shot Bufferize can be configured to leak all memory and not generate any 305buffer deallocations with `create-deallocs=0`. This can be useful for 306compatibility with legacy code that has its own method of deallocating buffers. 307 308## Memory Layouts 309 310One-Shot Bufferize bufferizes ops from top to bottom. This works well when all 311ops are bufferizable. However, when encountering a non-bufferizable tensor with 312`allow-unknown-ops`, One-Shot Bufferize must insert `to_memref` ops at the 313bufferization boundary and decide on a memref type. By default, One-Shot 314Bufferize choose the most dynamic memref type wrt. layout maps. E.g.: 315 316```mlir 317%0 = "my_dialect.unbufferizable_op(%t) : (tensor<?x?xf32>) -> (tensor<?x?xf32>) 318%1 = tensor.extract %0[%idx1, %idx2] : tensor<?xf32> 319``` 320 321When bufferizing the above IR, One-Shot Bufferize inserts a `to_memref` ops with 322dynamic offset and strides: 323 324```mlir 325#map = affine_map<(d0, d1)[s0, s1, s2] -> (d0 * s1 + s0 + d1 * s2)> 326%0 = "my_dialect.unbufferizable_op(%t) : (tensor<?x?xf32>) -> (tensor<?x?xf32>) 327%0_m = bufferization.to_memref %0 : memref<?x?xf32, #map> 328%1 = memref.load %0_m[%idx1, %idx2] : memref<?x?xf32, #map> 329``` 330 331All users of `%0` have fully dynamic layout maps. This ensures that the 332bufferized IR composes well with future bufferizations of `unbufferizable_op` 333(maybe bufferized by another pass), regardless of the exact memref type of the 334future bufferization. If the op turns out to be bufferized to an op with a 335simpler memref type (e.g., identity layout map), we expect that canonicalization 336patterns would clean up unnecessarily dynamic layout maps. (Some of these 337canonicalization patterns may not be implemented yet.) 338 339Note that One-Shot Bufferize always generates the most specific memref type when 340the entire IR is bufferizable. In that case, we do not have to rely on 341canonicalization patterns to clean up the bufferized IR. 342 343One-Shot Bufferize can be configured to always generate memref types with 344identity layout when the exact target memref type is not known via 345`fully-dynamic-layout-maps=0`. This can be useful for legacy code that cannot 346handle memref types with layout maps. Note that this leads to additional buffer 347copies when folding a `to_tensor`/`to_memref` pair with memref types that are 348not cast-compatible. 349 350## Extending One-Shot Bufferize 351 352Custom ops can be bufferized if they implement `BufferizableOpInterface`. Users 353must at least implement the following interface methods. 354 355* `bufferizesToMemoryRead`: Return `true` if the buffer of the given tensor 356 OpOperand is read. 357* `bufferizesToMemoryWrite`: Return `true` if the buffer of the given tensor 358 OpOperand is written (if bufferizing in-place). 359* `getAliasingOpResult`: Return the OpResults that may share the same buffer 360 as the given OpOperand. This interface method describes to 361 OpOperand-to-OpResult mapping wrt. destination-passing style. 362* `bufferRelation`: Return `BufferRelation::Equivalent` if the given OpResult 363 is the exact same memref as the aliasing OpOperand after bufferization (in 364 case of in-place bufferization). Otherwise, (e.g., they overlap but are not 365 necessarily the exact same memrefs), `BufferRelation::None` should be 366 returned. Additional buffer relations will be added in the future, but 367 `BufferRelation::None` is always safe. 368* `bufferize`: Rewrite the op with the given rewriter. Ops should be replaced 369 with `bufferization::replaceOpWithBufferizedValues`. 370 371To get a better intuition of the interface methods, we invite users to take a 372look at existing implementations in MLIR, e.g., the implementation of 373`tensor.insert` or `tensor.extract`. 374 375## Debugging Buffer Copies 376 377To get a better understanding of why One-Shot Bufferize introduced a buffer 378copy, users can run the pass with `test-analysis-only print-conflicts`. Every 379tensor op is then annotated with an attribute that has a boolean value for each 380tensor OpOperand. `true` means that the OpOperand bufferizes in-place. `false` 381means that the OpOperand bufferizes out-of-place and a buffer copy will be 382inserted. 383 384There are two reasons why a buffer copy may be inserted. 385 3861. Due to a RaW conflict, it is not safe to bufferize in-place. I.e., the 387 overwritten data is still needed. 3882. The buffer is not writable. E.g., `memref.global` buffers that are the 389 result of `arith.constant` ops are never modified. 390 391In the first case, `print-conflicts` illustrates the conflict in the form of a 392("read", "conflicting write", "last write") tuple. 393 394## Understanding the SSA Use-Def Chain Analysis 395 396To get a better understanding of the SSA Use-Def Chain Analysis and the RaW 397conflict detection algorithm, we invite interested users to read the 398[design document](https://discourse.llvm.org/uploads/short-url/5kckJ3DftYwQokG252teFgw3sYa.pdf) 399and watch the corresponding [ODM talk](https://youtu.be/TXEo59CYS9A) 400([slides](https://mlir.llvm.org/OpenMeetings/2022-01-13-One-Shot-Bufferization.pdf)). 401can be used to bufferize a program in a single pass, as long as each op 402 403## Migrating from Dialect Conversion-based Bufferization 404 405Both dialect conversion-based bufferization and One-Shot Bufferize generate 406`to_tensor`/`to_memref` ops at the bufferization boundary (when run with 407`allow-unknown-ops`). They can be combined and run in sequence. However, 408One-Shot Bufferize must run first because it cannot analyze those boundary ops. 409To update existing code step-by-step, it may be useful to specify a dialect 410filter for One-Shot Bufferize, so that dialects can be switched over one-by-one. 411 412## Bufferization Function Graphs 413 414One-Shot Bufferize does currently not support function graph bufferization. 415I.e., `CallOp`, `ReturnOp` and function bbArgs are not bufferizable. Users can 416run the existing `--func-bufferize` bufferization pass after One-Shot Bufferize. 417 418Alternatively, users can try 419[`ModuleBufferization`](https://github.com/llvm/llvm-project/blob/ae2764e835a26bad9774803eca0a6530df2a3e2d/mlir/include/mlir/Dialect/Linalg/ComprehensiveBufferize/ModuleBufferization.h#L31), 420which is an extension of One-Shot Bufferize. This bufferization is still under 421development and does not support arbitrary IR. In essence, returning a tensor 422from a function is not supported, unless it is equivalent to a function bbArg. 423In that case, the corresponding return value can simply be dropped during 424bufferization. 425 426## Dialect Conversion-based Bufferization 427 428Disclaimer: Most dialect conversion-based bufferization has been migrated to 429One-Shot Bufferize. New users should use One-Shot Bufferize (with or without 430analysis). The following documentation is only for existing users of dialect 431conversion-based bufferization. 432 433This system is a simple application of MLIR's dialect conversion infrastructure. 434The bulk of the code related to bufferization is a set of ordinary 435`ConversionPattern`'s that dialect authors write for converting ops that operate 436on `tensor`'s to ops that operate on `memref`'s. A set of conventions and best 437practices are followed that allow these patterns to be run across multiple 438independent passes (rather than requiring a single huge atomic conversion pass), 439which makes the compilation pipelines scalable, robust, and easy to debug. 440 441This document is targeted at people looking to utilize MLIR's bufferization 442functionality, along with people who want to extend it to cover their own ops. 443 444<a name="the-talk">**NOTE:**</a> Before reading this document, please watch the 445talk "Type Conversions the Not-So-Hard-Way: MLIR's New Bufferization 446Infrastructure" 447([slides](https://drive.google.com/file/d/1FVbzCXxZzS9LBLuvpPNLWJD-XDkt54ky/view?usp=sharing), 448[recording](https://drive.google.com/file/d/1VfVajitgf8ZPnd-HRkJvaJiFLhBsluXN/view?usp=sharing)). 449That talk gives a high-level overview of the bufferization infrastructure and 450important conceptual details related to using the MLIR dialect conversion 451infrastructure. 452 453### Bufferization's place in a compilation pipeline 454 455Bufferization itself does not free any of the buffers that have been allocated, 456nor does it do anything particularly intelligent with the placement of buffers 457w.r.t. control flow. Thus, a realistic compilation pipeline will usually consist 458of: 459 4601. Bufferization 4611. Buffer optimizations such as `buffer-hoisting`, `buffer-loop-hoisting`, and 462 `promote-buffers-to-stack`, which do optimizations that are only exposed 463 after bufferization. 4641. Finally, running the [buffer deallocation](BufferDeallocationInternals.md) 465 pass. 466 467After buffer deallocation has been completed, the program will be quite 468difficult to transform due to the presence of the deallocation ops. Thus, other 469optimizations such as linalg fusion on memrefs should be done before that stage. 470 471### General structure of the bufferization process 472 473Bufferization consists of running multiple *partial* bufferization passes, 474followed by one *finalizing* bufferization pass. 475 476There is typically one partial bufferization pass per dialect (though other 477subdivisions are possible). For example, for a dialect `X` there will typically 478be a pass `X-bufferize` that knows how to bufferize all the ops in that dialect. 479By running pass `X-bufferize` for each dialect `X` in the program, all the ops 480in the program are incrementally bufferized. 481 482Partial bufferization passes create programs where only some ops have been 483bufferized. These passes will create *materializations* (also sometimes called 484"casts") that convert between the `tensor` and `memref` type, which allows 485bridging between ops that have been bufferized and ops that have not yet been 486bufferized. 487 488Finalizing bufferizations complete the bufferization process, and guarantee that 489there are no tensors remaining in the program. This involves eliminating the 490materializations. The pass `finalizing-bufferize` provides a minimal pass that 491only eliminates materializations and issues an error if any unbufferized ops 492exist in the program. 493 494However, it is possible for a finalizing bufferization to do more than just 495eliminate materializations. By adding patterns (just as a partial bufferization 496would), it is possible for a finalizing bufferization pass to simultaneously 497bufferize ops and eliminate materializations. This has a number of disadvantages 498discussed in the talk and should generally be avoided. 499 500### Example 501 502As a concrete example, we will look at the bufferization pipeline from the 503`mlir-npcomp` reference backend 504([code](https://github.com/llvm/mlir-npcomp/blob/97d6d04d41216e73d40b89ffd79620973fc14ce3/lib/RefBackend/RefBackend.cpp#L232)). 505The code, slightly simplified and annotated, is reproduced here: 506 507```c++ 508 // Partial bufferization passes. 509 pm.addPass(createTensorConstantBufferizePass()); 510 pm.addNestedPass<FuncOp>(createTCPBufferizePass()); // Bufferizes the downstream `tcp` dialect. 511 pm.addNestedPass<FuncOp>(createSCFBufferizePass()); 512 pm.addNestedPass<FuncOp>(createLinalgBufferizePass()); 513 pm.addNestedPass<FuncOp>(createTensorBufferizePass()); 514 pm.addPass(createFuncBufferizePass()); 515 516 // Finalizing bufferization pass. 517 pm.addNestedPass<FuncOp>(createFinalizingBufferizePass()); 518``` 519 520Looking first at the partial bufferization passes, we see that there are a 521sequence of `FuncOp` passes (which run in parallel on functions). These function 522passes are bracketed by `arith-bufferize` and `func-bufferize`, which are module 523passes (and thus serialize the parallel compilation process). These two passes 524must be module passes because they make changes to the top-level module. 525 526The bulk of the bufferization work is done by the function passes. Most of these 527passes are provided as part of the upstream MLIR distribution and bufferize 528their respective dialects (e.g. `scf-bufferize` bufferizes the `scf` dialect). 529The `tcp-bufferize` pass is an exception -- it is a partial bufferization pass 530used to bufferize the downstream `tcp` dialect, and fits in perfectly with all 531the other passes provided upstream. 532 533The last pass is the finalizing bufferization pass. The `mlir-npcomp` reference 534backend has arranged that all ops are bufferized by partial bufferizations, so 535that the upstream `finalizing-bufferize` pass can be used as the finalizing 536bufferization pass. This gives excellent diagnostics when something goes wrong 537with the bufferization process, such as due to an op that wasn't handled by any 538pattern. 539 540### How to write a partial bufferization pass 541 542The contract of a partial bufferization pass is that a subset of ops (or kinds 543of ops, customizable by a ConversionTarget) get bufferized. 544 545A partial bufferization pass is just a pass that uses the 546[dialect conversion](DialectConversion.md) framework to apply 547`ConversionPattern`s with a `tensor` to `memref` type conversion. 548 549To describe how to write such a pass, we will walk through an example, the 550`tensor-bufferize` pass 551([code](https://github.com/llvm/llvm-project/blob/bc8acf2ce8ad6e8c9b1d97b2e02d3f4ad26e1d9d/mlir/lib/Dialect/Tensor/Transforms/Bufferize.cpp#L23), 552[test](https://github.com/llvm/llvm-project/blob/bc8acf2ce8ad6e8c9b1d97b2e02d3f4ad26e1d9d/mlir/test/Dialect/Tensor/bufferize.mlir#L1)) 553that bufferizes the `tensor` dialect. Note that these passes have been replaced 554with a `BufferizableOpInterface`-based implementation in the meantime, so we 555have to take a looker at an older version of the code. 556 557The bulk of the code in the pass will be a set of conversion patterns, with a 558simple example being 559[BufferizeCastOp](https://github.com/llvm/llvm-project/blob/2bf6e443e54604c7818c4d1a1837f3d091023270/mlir/lib/Dialect/Tensor/Transforms/Bufferize.cpp#L23)). 560 561``` 562class BufferizeCastOp : public OpConversionPattern<tensor::CastOp> { 563public: 564 using OpConversionPattern::OpConversionPattern; 565 LogicalResult 566 matchAndRewrite(tensor::CastOp op, OpAdaptor adaptor, 567 ConversionPatternRewriter &rewriter) const override { 568 auto resultType = getTypeConverter()->convertType(op.getType()); 569 rewriter.replaceOpWithNewOp<MemRefCastOp>(op, resultType, adaptor.source()); 570 return success(); 571 } 572}; 573``` 574 575See [the talk](#the-talk) for more details on how to write these patterns. 576 577The 578[pass itself](https://github.com/llvm/llvm-project/blob/bc8acf2ce8ad6e8c9b1d97b2e02d3f4ad26e1d9d/mlir/lib/Dialect/Tensor/Transforms/Bufferize.cpp#L57) 579is very small, and follows the basic pattern of any dialect conversion pass. 580 581``` 582void mlir::populateTensorBufferizePatterns( 583 BufferizeTypeConverter &typeConverter, RewritePatternSet &patterns) { 584 patterns.add<BufferizeCastOp, BufferizeExtractOp>(typeConverter, 585 patterns.getContext()); 586} 587 588struct TensorBufferizePass : public TensorBufferizeBase<TensorBufferizePass> { 589 void runOnOperation() override { 590 auto *context = &getContext(); 591 BufferizeTypeConverter typeConverter; 592 RewritePatternSet patterns(context); 593 ConversionTarget target(*context); 594 595 populateTensorBufferizePatterns(typeConverter, patterns); 596 target.addIllegalOp<tensor::CastOp, tensor::ExtractOp>(); 597 target.addLegalDialect<func::FuncDialect>(); 598 599 if (failed( 600 applyPartialConversion(getOperation(), target, std::move(patterns)))) 601 signalPassFailure(); 602 } 603}; 604``` 605 606The pass has all the hallmarks of a dialect conversion pass that does type 607conversions: a `TypeConverter`, a `RewritePatternSet`, and a `ConversionTarget`, 608and a call to `applyPartialConversion`. Note that a function 609`populateTensorBufferizePatterns` is separated, so that power users can use the 610patterns independently, if necessary (such as to combine multiple sets of 611conversion patterns into a single conversion call, for performance). 612 613One convenient utility provided by the MLIR bufferization infrastructure is the 614`BufferizeTypeConverter`, which comes pre-loaded with the necessary conversions 615and materializations between `tensor` and `memref`. 616 617In this case, the `BufferizationOpsDialect` is marked as legal, so the 618`bufferization.to_tensor` and `bufferization.to_memref` ops, which are inserted 619automatically by the dialect conversion framework as materializations, are 620legal. There is a helper `populateBufferizeMaterializationLegality` 621([code](https://github.com/llvm/llvm-project/blob/a0b65a7bcd6065688189b3d678c42ed6af9603db/mlir/include/mlir/Transforms/Bufferize.h#L53)) 622which helps with this in general. 623 624### Other partial bufferization examples 625 626- `scf-bufferize` 627 ([code](https://github.com/llvm/llvm-project/blob/bc8acf2ce8ad6e8c9b1d97b2e02d3f4ad26e1d9d/mlir/lib/Dialect/SCF/Transforms/Bufferize.cpp#L1), 628 [test](https://github.com/llvm/llvm-project/blob/bc8acf2ce8ad6e8c9b1d97b2e02d3f4ad26e1d9d/mlir/test/Dialect/SCF/bufferize.mlir#L1)) 629 630 - Bufferizes ops from the `scf` dialect. 631 - This is an example of how to bufferize ops that implement 632 `RegionBranchOpInterface` (that is, they use regions to represent 633 control flow). 634 - The bulk of the work is done by 635 `lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp` 636 ([code](https://github.com/llvm/llvm-project/blob/daaaed6bb89044ac58a23f1bb1ccdd12342a5a58/mlir/lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp#L1)), 637 which is well-commented and covers how to correctly convert ops that 638 contain regions. 639 640- `func-bufferize` 641 ([code](https://github.com/llvm/llvm-project/blob/2f5715dc78328215d51d5664c72c632a6dac1046/mlir/lib/Dialect/Func/Transforms/FuncBufferize.cpp#L1), 642 [test](https://github.com/llvm/llvm-project/blob/2f5715dc78328215d51d5664c72c632a6dac1046/mlir/test/Dialect/Func/func-bufferize.mlir#L1)) 643 644 - Bufferizes `func`, `call`, and `BranchOpInterface` ops. 645 - This is an example of how to bufferize ops that have multi-block 646 regions. 647 - This is an example of a pass that is not split along dialect 648 subdivisions. 649 650### How to write a finalizing bufferization pass 651 652The contract of a finalizing bufferization pass is that all tensors are gone 653from the program. 654 655The easiest way to write a finalizing bufferize pass is to not write one at all! 656MLIR provides a pass `finalizing-bufferize` which eliminates the 657`bufferization.to_tensor` / `bufferization.to_memref` materialization ops 658inserted by partial bufferization passes and emits an error if that is not 659sufficient to remove all tensors from the program. 660 661This pass is sufficient when partial bufferization passes have bufferized all 662the ops in the program, leaving behind only the materializations. When possible, 663it is recommended to structure your pass pipeline this way, as this has the 664significant advantage that if an op does not get bufferized (due to a missing 665pattern, bug in the code, etc.), `finalizing-bufferize` will emit a nice clean 666error, and the IR seen by `finalizing-bufferize` will only contain only one 667unbufferized op. 668 669However, before the current bufferization infrastructure was put in place, 670bufferization could only be done as a single finalizing bufferization mega-pass 671that used the `populate*BufferizePatterns` functions from multiple dialects to 672simultaneously bufferize everything at once. Thus, one might see code in 673downstream projects structured this way. This structure is not recommended in 674new code. A helper, `populateEliminateBufferizeMaterializationsPatterns` 675([code](https://github.com/llvm/llvm-project/blob/a0b65a7bcd6065688189b3d678c42ed6af9603db/mlir/include/mlir/Transforms/Bufferize.h#L58)) 676is available for such passes to provide patterns that eliminate 677`bufferization.to_tensor` and `bufferization.to_memref`. 678 679### Changes since [the talk](#the-talk) 680 681- `func-bufferize` was changed to be a partial conversion pass, and there is a 682 new `finalizing-bufferize` which serves as a general finalizing 683 bufferization pass. 684- Most partial bufferization passes have been reimplemented in terms of 685 `BufferizableOpInterface`. New users should use One-Shot Bufferize instead 686 of dialect conversion-based bufferization. 687