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