1 //===- ComprehensiveBufferize.h - Linalg bufferization pass -----*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef MLIR_DIALECT_LINALG_TRANSFORMS_COMPREHENSIVE_BUFFERIZE_H
10 #define MLIR_DIALECT_LINALG_TRANSFORMS_COMPREHENSIVE_BUFFERIZE_H
11 
12 #include "mlir/Dialect/Tensor/IR/Tensor.h"
13 #include "mlir/IR/Value.h"
14 #include "llvm/ADT/EquivalenceClasses.h"
15 #include "llvm/ADT/SetOperations.h"
16 
17 namespace mlir {
18 
19 class DominanceInfo;
20 class FuncOp;
21 class GlobalCreator;
22 
23 namespace linalg {
24 
25 /// The BufferizationAliasInfo class maintains a list of buffer aliases and
26 /// equivalence classes to support bufferization.
27 /// ExtractSliceOps have special behavior, they act as a level of indirection
28 /// for bufferization. They don't create reads or writes themselves and analysis
29 /// needs to look through their uses.
30 /// ExtractSliceOp + InsertSliceOp have special joint behavior: they may
31 /// bufferize to the same buffer (i.e. subview), which is what introduces the
32 /// need for bufferization classes.
33 /// Some of these functionalities could be refactored in a Bufferizer class that
34 /// uses BufferizationAliasInfo.
35 class BufferizationAliasInfo {
36 public:
37   /// Specify fine-grain relationship between buffers to enable more analysis.
38   enum class BufferRelation {
39     None,
40     // TODO: ResultContainsOperand,
41     // TODO: OperandContainsResult,
42     Equivalent
43   };
44 
45   explicit BufferizationAliasInfo(Operation *rootOp);
46 
47   /// Add a new entry for `v` in the `aliasInfo` and `equivalentInfo`. In the
48   /// beginning the alias and equivalence sets only contain `v` itself.
49   void createAliasInfoEntry(Value v);
50 
51   /// Insert an info entry for `newValue` and merge its alias set with that of
52   /// `alias`.
53   void insertNewBufferAlias(Value newValue, Value alias);
54 
55   /// Insert an info entry for `newValue` and merge its alias set with that of
56   /// `alias`. Additionally, merge their equivalence classes.
57   void insertNewBufferEquivalence(Value newValue, Value alias);
58 
59   /// Return true if, under current bufferization decisions, the buffer of
60   /// `value` is not writable.
61   bool aliasesNonWritableBuffer(Value value) const;
62 
63   /// Return true if the buffer to which `operand` would bufferize is equivalent
64   /// to some buffer write.
65   bool aliasesInPlaceWrite(Value v) const;
66 
67   /// Set the inPlace bufferization spec to true.
68   /// Merge result's and operand's aliasing sets and iterate to a fixed point.
69   void bufferizeInPlace(OpResult result, OpOperand &operand);
70 
71   /// Set the inPlace bufferization spec to false.
72   void bufferizeOutOfPlace(OpResult result);
73 
74   /// Return true if it is possible to find an inplace write W among `usesWrite`
75   /// and a read R among `usesRead`, such that W and R interfere.
76   /// Such a (W, R) pair is an interference to the inplace bufferization of
77   /// opResult when:
78   ///   1. R is not known properly dominate W (i.e. the effects of the write may
79   ///      be visible from R).
80   ///   2. one cannot find an intermediate clobbering write `C` to W, such that
81   ///      C interleaved between W and R (i.e. W -> C -> R where -> denotes
82   ///      dominance).
83   bool wouldCreateReadAfterWriteInterference(
84       Operation *opToBufferize, DenseSet<OpOperand *> &usesRead,
85       DenseSet<OpOperand *> &usesWrite, const DominanceInfo &domInfo) const;
86 
87   /// Return true if bufferizing `opResult` inplace would create a write to a
88   /// non-writable buffer.
89   bool wouldCreateWriteToNonWritableBuffer(OpResult opResult) const;
90 
91   /// Assume that result bufferizes in-place with one of the operation's
92   /// operands. Return true if it is possible to find an inplace write W (resp.
93   /// a read R) among the uses of `aliasInfo[result]`, and a read R (resp. an
94   /// inplace write W) among the uses of
95   /// `aliasInfo[getAliasingOpOperand(result)]`, such that W and R interfere.
96   /// Interference detection is needed to determine which cases may bufferize
97   /// inplace without interferences. Such cases comprise:
98   ///
99   /// ```
100   ///  %0 = op_to_bufferize(%1)
101   ///  read(%1)
102   ///
103   ///  %0 = op_to_bufferize(%1)
104   ///  write(%0)
105   ///  read(%1)
106   ///
107   ///  %0 = op_to_bufferize(%1)
108   ///  write(%1)
109   ///  read(%0)
110   /// ```
111   bool
112   wouldCreateReadAfterWriteInterference(OpResult result,
113                                         const DominanceInfo &domInfo) const;
114 
115   /// Return true if `v1` and `v2` bufferize to equivalent buffers.
areEquivalentBufferizedValues(Value v1,Value v2)116   bool areEquivalentBufferizedValues(Value v1, Value v2) const {
117     return equivalentInfo.getLeaderValue(v1) ==
118            equivalentInfo.getLeaderValue(v2);
119   }
120 
121   /// Return true if the source of an `insertSliceOp` bufferizes to an
122   /// equivalent ExtractSliceOp.
123   bool isSourceEquivalentToAMatchingInplaceExtractSliceOp(
124       tensor::InsertSliceOp insertSliceOp) const;
125 
126   /// Apply `fun` to all the members of the equivalence class of `v`.
127   void applyOnEquivalenceClass(Value v, function_ref<void(Value)> fun) const;
128 
129   /// Return true if the value is known to bufferize to writable memory.
130   bool bufferizesToWritableMemory(Value v) const;
131 
132   /// Specify that the value is known to bufferize to writable memory.
133   void setBufferizesToWritableMemory(Value v);
134 
135   /// Print to `os`.
136   void printAliases(raw_ostream &os) const;
137   void printEquivalences(raw_ostream &os) const;
138 
139   /// Print to `errs()`.
140   void dumpAliases() const;
141   void dumpEquivalences() const;
142 
143 private:
144   /// llvm::EquivalenceClasses wants comparable elements because it uses
145   /// std::set as the underlying impl.
146   /// ValueWrapper wraps Value and uses pointer comparison on the defining op.
147   /// This is a poor man's comparison but it's not like UnionFind needs ordering
148   /// anyway ..
149   struct ValueWrapper {
ValueWrapperValueWrapper150     ValueWrapper(Value val) : v(val) {}
ValueValueWrapper151     operator Value() const { return v; }
152     bool operator<(const ValueWrapper &wrap) const {
153       return v.getImpl() < wrap.v.getImpl();
154     }
155     bool operator==(const ValueWrapper &wrap) const { return v == wrap.v; }
156     Value v;
157   };
158 
159   using EquivalenceClassRangeType = llvm::iterator_range<
160       llvm::EquivalenceClasses<ValueWrapper>::member_iterator>;
161   /// Check that aliasInfo for `v` exists and return a reference to it.
162   EquivalenceClassRangeType getAliases(Value v) const;
163 
164   /// Return true if the (ExtractSliceOp, InsertSliceOp) pair match (i.e.
165   /// equivalent operand / result and same offset/sizes/strides specification).
166   ///
167   /// This is one particular type of relationship between ops on tensors that
168   /// reduce to an equivalence on buffers. This should be generalized and
169   /// exposed as interfaces on the proper types.
170   bool areEquivalentExtractSliceOps(tensor::ExtractSliceOp st,
171                                     tensor::InsertSliceOp sti) const;
172 
173   /// Return true if there is a `candidateOp` that would write to memory after
174   /// bufferization and such that:
175   ///   1. The written buffer is equivalent to either `aliasingRead` or
176   ///      `aliasingWrite` under the inPlace bufferization decisions taken
177   ///      so far.
178   ///   2. `aliasingWrite` properly dominates `candidateOp`.
179   ///   3. `candidateOp` properly dominates `aliasingReadOp`.
180   // TODO: richer clobbering analysis with container-containee relationship
181   // instead of equivalence.
182   bool existsInterleavedValueClobber(OpOperand &aliasingRead,
183                                      OpOperand &aliasingWrite,
184                                      const DominanceInfo &domInfo) const;
185 
186   /// Return true if there is a write that:
187   ///   1. Properly dominates aliasingReadOp.
188   ///   2. Is properly dominated by aliasingWriteOp.
189   ///   3. Clobbers the write that would be interfering with the read.
190   ///
191   /// Case discussion:
192   /// ================
193   /// Case 1: opOperand is produced by opToBufferize,
194   /// Case 2: opResult is produced by opToBufferize,
195   /// Common case:
196   ///   - aliasingReadOp is a read to an alias of opOperand.
197   ///   - aliasingWriteOp is an inplace write to an alias of opResult.
198   ///   - aliasingWriteOp dominates aliasingReadOp.
199   ///
200   /// ```
201   ///    // Either case 1:
202   ///    %opOperand = opToBufferize(%opResult)
203   ///    aliasingWriteOp(%aliasingWrite = alias(%opResult)) // inplace
204   ///     aliasingReadOp( %aliasingRead = alias(%opOperand))
205   /// ```
206   ///
207   /// ```
208   ///    // Or case 2:
209   ///    %opResult = opToBufferize(%opOperand)
210   ///    aliasingWriteOp(%aliasingWrite = alias(%opResult)) // inplace
211   ///     aliasingReadOp( %aliasingRead = alias(%opOperand))
212   /// ```
213   ///
214   /// Capture possible cases where `aliasingWriteOp(alias(%opResult))` has no
215   /// visible effect on `aliasingReadOp(alias(%opOperand))`.
216   bool isClobberedWriteBeforeRead(Operation *opToBufferize,
217                                   OpOperand &aliasingRead,
218                                   OpOperand &aliasingWrite,
219                                   const DominanceInfo &domInfo) const;
220 
221   /// Set of tensors that are known to bufferize to writable memory.
222   llvm::DenseSet<Value> bufferizeToWritableMemory;
223 
224   /// Auxiliary structure to store all the values a given value aliases with.
225   /// These are the conservative cases that can further decompose into
226   /// "equivalent" buffer relationships.
227   llvm::EquivalenceClasses<ValueWrapper> aliasInfo;
228 
229   /// Auxiliary structure to store all the equivalent buffer classes.
230   llvm::EquivalenceClasses<ValueWrapper> equivalentInfo;
231 };
232 
233 /// Analyze the `ops` to determine which OpResults are inplaceable:
234 LogicalResult inPlaceAnalysis(SmallVector<Operation *> &ops,
235                               BufferizationAliasInfo &aliasInfo,
236                               const DominanceInfo &domInfo);
237 
238 /// Bufferize one particular op.
239 /// `bufferizedFunctionTypes` (resp. `globalCreator`) are expected to be
240 /// non-null if `op` is a CallOpInterface (resp. GlobalCreator).
241 LogicalResult
242 bufferizeOp(Operation *op, BlockAndValueMapping &bvm,
243             BufferizationAliasInfo &aliasInfo,
244             DenseMap<FuncOp, FunctionType> *bufferizedFunctionTypes = nullptr,
245             GlobalCreator *globalCreator = nullptr);
246 
247 } // namespace linalg
248 } // namespace mlir
249 
250 #endif // define MLIR_DIALECT_LINALG_TRANSFORMS_COMPREHENSIVE_BUFFERIZE_H
251