MLIR 24.0.0git
OneShotAnalysis.cpp
Go to the documentation of this file.
1//===- OneShotAnalysis.cpp - One-Shot (Single Pass) Analysis --------------===//
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// One-Shot Analysis analyzes function bodies. By default, function boundaries
10// (FuncOp bbArgs, CallOps, ReturnOps) are treated as "unknown" ops.
11// OneShotModuleBufferization.cpp is an extension of One-Shot Analysis for
12// simple call graphs without loops.
13//
14// One-Shot Bufferize consists of three phases.
15//
16// 1. Analyze ops to decide which OpOperands can bufferize inplace, i.e.,
17// without inserting buffer copies. The analysis queries op bufferization
18// semantics via `BufferizableOpInterface`.
19// 2. Insert copies for OpOperands that were decided to bufferize out-of-place
20// in tensor land during `TensorCopyInsertion`.
21// 3. Bufferize ops by calling `BufferizableOpInterface::bufferize`.
22//
23// This file contains only the analysis. For convenience, this file also
24// contains a helper function `runOneShotBufferize` that analyzes an op (and its
25// nested ops) and then bufferizes it.
26//
27// Inplace bufferization decisions are passed from the analysis to the
28// `TensorCopyInsertion` phase via `AnalysisState`. They can be printed for
29// debugging purposes with `testAnalysisOnly`.
30//
31// Ops that do not implement `BufferizableOpInterface` can be analyzed but are
32// treated conservatively. E.g., the analysis has to assume that their tensor
33// OpOperands bufferize to memory writes. While such ops can be analyzed, they
34// are not bufferized and remain in the IR. to_tensor and to_buffer ops are
35// inserted at the bufferization boundary.
36//
37// This analysis caters to high-performance codegen where buffer reuse is deemed
38// critical: the analysis should fail if the bufferized form of the function
39// needs to return a buffer, unless `allowReturnAllocs` is enabled.
40
42
43#include <random>
44#include <tuple>
45
51#include "mlir/IR/AsmState.h"
52#include "mlir/IR/Dominance.h"
53#include "mlir/IR/Iterators.h"
54#include "mlir/IR/Operation.h"
58#include "llvm/ADT/DenseMap.h"
59#include "llvm/ADT/DenseSet.h"
60#include "llvm/ADT/STLExtras.h"
61#include "llvm/ADT/SetVector.h"
62#include "llvm/ADT/SmallPtrSet.h"
63#include "llvm/ADT/SmallVector.h"
64#include "llvm/Support/DebugLog.h"
65
67
68// Run mlir-opt with `-debug-only="one-shot-analysis"` for detailed debug
69// output.
70#define DEBUG_TYPE "one-shot-analysis"
71
72using namespace mlir;
73using namespace mlir::bufferization;
74
75static bool isaTensor(Type t) { return isa<TensorLikeType>(t); }
76
77//===----------------------------------------------------------------------===//
78// Bufferization-specific attribute manipulation.
79// These are for testing and debugging only. Bufferization information is stored
80// in OneShotBufferizationState. When run with `testAnalysisOnly`, the IR is
81// annotated with the results of the analysis, so that they can be checked in
82// tests.
83//===----------------------------------------------------------------------===//
84
85/// Attribute marker to specify op operands that bufferize in-place.
86constexpr StringLiteral kInPlaceOperandsAttrName = "__inplace_operands_attr__";
87
88constexpr StringLiteral kOpResultAliasSetAttrName =
89 "__opresult_alias_set_attr__";
90
91constexpr StringLiteral kBbArgAliasSetAttrName = "__bbarg_alias_set_attr__";
92
93/// Mark whether OpOperand will be bufferized inplace.
94static void setInPlaceOpOperand(OpOperand &opOperand, bool inPlace) {
95 Operation *op = opOperand.getOwner();
96 SmallVector<StringRef> inPlaceVector;
97 if (auto attr = op->getDiscardableAttr(kInPlaceOperandsAttrName)) {
98 inPlaceVector = SmallVector<StringRef>(llvm::to_vector<4>(
99 cast<ArrayAttr>(attr).getAsValueRange<StringAttr>()));
100 // The existing attribute may have fewer entries than the current operand
101 // count (e.g., when user-provided annotations are inconsistent with the
102 // op's actual operand count). Resize to avoid an out-of-bounds access.
103 if (inPlaceVector.size() < op->getNumOperands())
104 inPlaceVector.resize(op->getNumOperands(), "none");
105 } else {
106 inPlaceVector = SmallVector<StringRef>(op->getNumOperands(), "none");
107 for (OpOperand &opOperand : op->getOpOperands())
108 if (isa<TensorLikeType>(opOperand.get().getType()))
109 inPlaceVector[opOperand.getOperandNumber()] = "false";
110 }
111 inPlaceVector[opOperand.getOperandNumber()] = inPlace ? "true" : "false";
113 OpBuilder(op).getStrArrayAttr(inPlaceVector));
114}
115
116//===----------------------------------------------------------------------===//
117// OneShotAnalysisState
118//===----------------------------------------------------------------------===//
119
120/// A region with more than one block is unstructured control flow. Single-block
121/// regions, including structured loops, are not.
123 WalkResult walkRes = op->walk([&](Operation *nested) {
124 for (Region &region : nested->getRegions()) {
125 if (region.getBlocks().size() > 1) {
126 return WalkResult::interrupt();
127 }
128 }
129 return WalkResult::advance();
130 });
131 return walkRes.wasInterrupted();
132}
133
134/// Return "true" if any allowed op has a parallel region.
137 WalkResult walkRes =
138 op->walk([&](BufferizableOpInterface bufferizableOp) -> WalkResult {
139 if (!options.isOpAllowed(bufferizableOp))
140 return WalkResult::skip();
141 for (Region &region : bufferizableOp->getRegions()) {
142 if (bufferizableOp.isParallelRegion(region.getRegionNumber()))
143 return WalkResult::interrupt();
144 }
145 return WalkResult::advance();
146 });
147 return walkRes.wasInterrupted();
148}
149
153 mayHaveUnstructuredCF = options.mayHaveUnstructuredControlFlow.value_or(
155 if (!options.mayHaveParallelRegions.has_value()) {
156 mayHaveParallelRegionsFlag = detectParallelRegions(op, options);
157 } else {
158 mayHaveParallelRegionsFlag = *options.mayHaveParallelRegions;
159 }
160
161 // Set up alias sets.
162 op->walk([&](Operation *op) {
163 for (Value v : op->getResults())
164 if (isa<TensorLikeType>(v.getType()))
165 createAliasInfoEntry(v);
166 for (Region &r : op->getRegions())
167 for (Block &b : r.getBlocks())
168 for (auto bbArg : b.getArguments())
169 if (isa<TensorLikeType>(bbArg.getType()))
170 createAliasInfoEntry(bbArg);
171 });
172
173 // Mark OpOperands in-place that must bufferize in-place.
174 op->walk([&](BufferizableOpInterface bufferizableOp) {
175 if (!options.isOpAllowed(bufferizableOp))
176 return WalkResult::skip();
177
178 for (OpOperand &opOperand : bufferizableOp->getOpOperands())
179 if (isa<TensorLikeType>(opOperand.get().getType()))
180 if (bufferizableOp.mustBufferizeInPlace(opOperand, *this))
181 bufferizeInPlace(opOperand);
182 return WalkResult::advance();
183 });
184}
185
187 Value v, function_ref<void(Value)> fun) const {
188 auto leaderIt = equivalentInfo.findLeader(v);
189 for (auto mit = leaderIt, meit = equivalentInfo.member_end(); mit != meit;
190 ++mit) {
191 fun(*mit);
192 }
193}
194
196 function_ref<void(Value)> fun) const {
197 auto leaderIt = aliasInfo.findLeader(v);
198 for (auto mit = leaderIt, meit = aliasInfo.member_end(); mit != meit; ++mit) {
199 fun(*mit);
200 }
201}
202
204 Value v2) const {
205 return equivalentInfo.isEquivalent(v1, v2);
206}
207
209 Value v2) const {
210 return aliasInfo.isEquivalent(v1, v2);
211}
212
214 if (inplaceBufferized.contains(&operand))
215 return;
216 inplaceBufferized.insert(&operand);
217 for (AliasingValue alias : getAliasingValues(operand))
218 aliasInfo.unionSets(alias.value, operand.get());
219 ++statNumTensorInPlace;
220}
221
223 assert(!inplaceBufferized.contains(&operand) &&
224 "OpOperand was already decided to bufferize inplace");
225 ++statNumTensorOutOfPlace;
226}
227
229 aliasInfo.insert(v);
230 equivalentInfo.insert(v);
231}
232
234 op->walk([&](Operation *op) {
235 // Skip unknown ops.
236 auto bufferizableOp = getOptions().dynCastBufferizableOp(op);
237 if (!bufferizableOp)
238 return WalkResult::skip();
239
240 // Check all tensor OpResults.
241 for (OpResult opResult : op->getOpResults()) {
242 if (!isa<TensorLikeType>(opResult.getType()))
243 continue;
244
245 // If there is no preceding definition, the tensor contents are
246 // undefined.
247 if (opResult.getUses().empty())
248 continue;
249 // It does not really matter which use to take to search about
250 // the value's definitions.
251 OpOperand *opOperand = &(*opResult.getUses().begin());
252 if (findDefinitionsCached(opOperand).empty())
253 for (OpOperand &use : opResult.getUses())
254 undefinedTensorUses.insert(&use);
255 }
256
257 return WalkResult::advance();
258 });
259}
260
262 return undefinedTensorUses.contains(opOperand);
263}
264
266 return inplaceBufferized.contains(&opOperand);
267}
268
270 bool isWritten = false;
271 applyOnAliases(value, [&](Value val) {
272 for (OpOperand &use : val.getUses())
273 if (isInPlace(use) && bufferizesToMemoryWrite(use))
274 isWritten = true;
275 });
276 return isWritten;
277}
278
280 // TODO: Out-of-place bufferized value could be considered writable.
281 // Query BufferizableOpInterface to see if the BlockArgument is writable.
282 if (auto bufferizableOp =
283 getOptions().dynCastBufferizableOp(getOwnerOfValue(value)))
284 return bufferizableOp.isWritable(value, *this);
285
286 // Not a bufferizable op: The conservative answer is "not writable".
287 return false;
288}
289
291 aliasInfo.unionSets(v1, v2);
292}
293
295 equivalentInfo.unionSets(v1, v2);
296}
297
299
300//===----------------------------------------------------------------------===//
301// Bufferization-specific alias analysis.
302//===----------------------------------------------------------------------===//
303
304/// Return true if opOperand has been decided to bufferize in-place.
305static bool isInplaceMemoryWrite(OpOperand &opOperand,
306 const OneShotAnalysisState &state) {
307 // OpOperands that do not bufferize to a memory write do not write in-place.
308 if (!state.bufferizesToMemoryWrite(opOperand))
309 return false;
310 // Check current bufferization decisions.
311 return state.isInPlace(opOperand);
312}
313
314/// Cached CFG reachability to avoid repeated linear BFS traversals from
315/// `Block->isReachable`.
317public:
318 bool isReachable(Block *from, Block *to,
319 const SmallPtrSetImpl<Block *> *barriers = nullptr) {
320 assert(from->getParent() == to->getParent() && "expected same region");
321
323 BarrierKey sortedBarriers;
324 if (barriers && !barriers->empty()) {
325 except.insert(barriers->begin(), barriers->end());
326 sortedBarriers.append(barriers->begin(), barriers->end());
327 llvm::sort(sortedBarriers);
328 }
329
330 ReachableByBarriers &inner = cached[from];
331 if (auto it = inner.find(sortedBarriers); it != inner.end())
332 return it->second.contains(to);
333
334 // Same traversal as `Block::isReachable`: `except` is both the barrier set
335 // and the visited set.
336 ReachableSet reachable;
337 SmallVector<Block *> worklist(from->succ_begin(), from->succ_end());
338 while (!worklist.empty()) {
339 Block *next = worklist.pop_back_val();
340 if (!except.insert(next).second)
341 continue;
342 reachable.insert(next);
343 worklist.append(next->succ_begin(), next->succ_end());
344 }
345 bool result = reachable.contains(to);
346 inner.try_emplace(std::move(sortedBarriers), std::move(reachable));
347 return result;
348 }
349
350 void clear() { cached.clear(); }
351
352private:
353 using ReachableSet = SmallPtrSet<Block *, 16>;
354 /// Sorted extra-barrier blocks for one query. Empty means no extra barriers.
355 using BarrierKey = SmallVector<Block *, 8>;
356 /// Sorted barrier list -> blocks reachable from `from` without crossing those
357 /// barriers.
358 using ReachableByBarriers = DenseMap<BarrierKey, ReachableSet>;
359 /// `from` block -> reachable-set keyed by sorted extra-barrier blocks.
361};
362
363/// `canUseOpDominanceDueToBlocks` walks enclosing regions and queries CFG
364/// reachability, but the answer is a property of blocks: every op in a block
365/// has the same parent chain, and a definition contributes only its block as
366/// a reachability barrier. One entry per (read block, write block, def blocks)
367/// therefore covers every operand pair in those blocks.
369public:
370 bool getOrCompute(Block *readBlock, Block *writeBlock,
371 ArrayRef<Block *> sortedDefBlocks,
372 function_ref<bool()> compute) {
373 if (sortedDefBlocks.size() == 1) {
374 auto key =
375 std::make_tuple(readBlock, writeBlock, sortedDefBlocks.front());
376 if (auto it = singleDef.find(key); it != singleDef.end())
377 return it->second;
378 bool result = compute();
379 singleDef.try_emplace(key, result);
380 return result;
381 }
382
383 DefKey key(sortedDefBlocks.begin(), sortedDefBlocks.end());
384 auto &byDefs = manyDefs[std::make_pair(readBlock, writeBlock)];
385 if (auto it = byDefs.find(key); it != byDefs.end())
386 return it->second;
387 bool result = compute();
388 byDefs.try_emplace(std::move(key), result);
389 return result;
390 }
391
392 void clear() {
393 singleDef.clear();
394 manyDefs.clear();
395 }
396
397private:
398 using DefKey = SmallVector<Block *, 4>;
399 /// Common case: a single definition block.
401 /// Sorted unique definition blocks, for reads with several definitions.
403};
404
406
407/// Return true if `a` cannot happen after `b`.
408/// True if `a` properly dominates `b` and `b` is not inside `a`, or if they
409/// are in the same region with no CFG path from `b` to `a`.
410/// Dominance is sufficient but not necessary. E.g.:
411///
412/// ^bb0:
413/// cf.cond_br %c, ^bb1, ^bb2
414/// ^bb1:
415/// "op_a"(%t)
416/// cf.br ^bb3
417/// ^bb2:
418/// "op_b"(%t)
419/// cf.br ^bb3
420/// ^bb3:
421///
422/// `op_a` does not dominate `op_b`, but there is no CFG path from `op_b` to
423/// `op_a`, so `op_a` cannot happen after `op_b`.
424///
425/// `extraBarriers` are extra blocks a CFG path from `b` to `a` must not cross.
427 const DominanceInfo &domInfo,
429 SmallPtrSet<Block *, 16> extraBarriers = {}) {
430 do {
431 // TODO: Instead of isProperAncestor + properlyDominates, we should use
432 // properlyDominatesImpl(a, b, /*enclosingOpOk=*/false)
433 if (a->isProperAncestor(b))
434 return false;
435 // Dominance is a stronger condition than reachability. Prefer using it
436 // since it is cached and works within a single block.
437 if (domInfo.properlyDominates(a, b))
438 return true;
439 // Distinct blocks in the same region only exist with unstructured control
440 // flow. Dominance is a complete ordering otherwise.
441 if (state.mayHaveUnstructuredControlFlow()) {
442 Block *aBlock = a->getBlock();
443 Block *bBlock = b->getBlock();
444 if (aBlock != bBlock && aBlock->getParent() == bBlock->getParent() &&
445 !state.isReachableCached(bBlock, aBlock, &extraBarriers))
446 return true;
447 }
448 } while ((a = a->getParentOp()));
449 return false;
450}
451
452/// Return `true` if op dominance can be used to rule out a read-after-write
453/// conflicts based on the ordering of ops. Returns `false` if op dominance
454/// cannot be used to due region-based loops.
455///
456/// Generalized op dominance can often be used to rule out potential conflicts
457/// due to "read happens before write". E.g., the following IR is not a RaW
458/// conflict because the read happens *before* the write.
459///
460/// Example 1:
461/// %0 = ... : tensor<?xf32> // DEF
462/// "reading_op"(%0) : tensor<?xf32> // READ
463/// %1 = "writing_op"(%0) : tensor<?xf32> -> tensor<?xf32> // WRITE
464///
465/// This is no longer true inside loops (or repetitive regions). In such cases,
466/// there may not be a meaningful `cannotHappenAfter` relationship because ops
467/// could be executed multiple times. E.g.:
468///
469/// Example 2:
470/// %0 = ... : tensor<?xf32> // DEF
471/// scf.for ... {
472/// "reading_op"(%0) : tensor<?xf32> // READ
473/// %1 = "writing_op"(%0) : tensor<?xf32> -> tensor<?xf32> // WRITE
474/// ...
475/// }
476///
477/// In the above example, reading_op happens before writing_op according to
478/// op dominance. However, both ops may happen multiple times; in
479/// particular, the second execution of reading_op happens after the first
480/// execution of writing_op. This is problematic because the tensor %0 they
481/// operate on (i.e., the "definition") is defined outside of the loop.
482///
483/// On a high-level, there is a potential RaW in a program if there exists a
484/// possible program execution such that there is a sequence of DEF, followed
485/// by WRITE, followed by READ. Each additional DEF resets the sequence.
486///
487/// E.g.:
488/// No conflict: DEF, WRITE, DEF, READ
489/// Potential conflict: DEF, READ, WRITE, READ, WRITE
490///
491/// Example 1 has no conflict: DEF, READ, WRITE
492/// Example 2 has a potential conflict: DEF, (READ, WRITE)*
493//
494/// Example 3:
495/// scf.for ... {
496/// %0 = ... : tensor<?xf32>
497/// "reading_op"(%0) : tensor<?xf32>
498/// %1 = "writing_op"(%0) : tensor<?xf32> -> tensor<?xf32>
499/// ...
500/// }
501/// This has no conflict: (DEF, READ, WRITE)*
502///
503/// Example 4:
504/// %0 = ... : tensor<?xf32>
505/// scf.for ... {
506/// scf.for ... { "reading_op"(%0) }
507/// %1 = "writing_op"(%0)
508/// }
509/// This has a potential conflict: DEF, ((READ)*, WRITE)*
510///
511/// Example 5:
512/// %0 = ... : tensor<?xf32>
513/// scf.for ... { %1 = "writing_op"(%0) }
514/// scf.for ... { "reading_op"(%0) }
515/// This has a potential conflict: DEF, WRITE*, READ*
516///
517/// The following rules are used to rule out RaW conflicts via ordering of ops:
518///
519/// 1. If the closest enclosing repetitive region of DEF is a proper ancestor of
520/// a repetitive region that enclosing both READ and WRITE, we cannot rule
521/// out RaW conflict due to the ordering of ops.
522/// 2. Otherwise: There are no loops that interfere with our analysis; for
523/// analysis purposes, we can assume that there are no loops/repetitive
524/// regions. I.e., we can rule out a RaW conflict if READ cannot happen after
525/// WRITE or WRITE cannot happen after DEF. (Checked in
526/// `hasReadAfterWriteInterference`.)
527///
529 const SetVector<Value> &definitions,
530 AnalysisState &state) {
531 const BufferizationOptions &options = state.getOptions();
532 for (Value def : definitions) {
533 Region *rRead =
534 state.getEnclosingRepetitiveRegion(uRead->getOwner(), options);
535 Region *rDef = state.getEnclosingRepetitiveRegion(def, options);
536
537 // READ and DEF are in the same repetitive region. `cannotHappenAfter` can
538 // be used to rule out RaW conflicts due to op ordering.
539 if (rRead == rDef)
540 continue;
541
542 // Find the enclosing repetitive region of READ that is closest to DEF but
543 // not the repetitive region of DEF itself.
544 while (true) {
545 Region *nextRegion = getNextEnclosingRepetitiveRegion(rRead, options);
546 if (nextRegion == rDef)
547 break;
548 assert(nextRegion && "expected to find another repetitive region");
549 rRead = nextRegion;
550 }
551
552 // We cannot use op dominance if WRITE is inside the same repetitive region.
553 if (rRead->getParentOp()->isAncestor(uWrite->getOwner()))
554 return false;
555 }
556
557 return true;
558}
559
560/// Return `true` if op dominance can be used to rule out a read-after-write
561/// conflicts based on the ordering of ops. Returns `false` if op dominance
562/// cannot be used to due block-based loops within a region.
563///
564/// Refer to the `canUseOpDominanceDueToRegions` documentation for details on
565/// how op domiance is used during RaW conflict detection.
566///
567/// On a high-level, there is a potential RaW in a program if there exists a
568/// possible program execution such that there is a sequence of DEF, followed
569/// by WRITE, followed by READ. Each additional DEF resets the sequence.
570///
571/// Op dominance cannot be used if there is a path from block(READ) to
572/// block(WRITE) and a path from block(WRITE) to block(READ). block(DEF) should
573/// not appear on that path. Walk from the uses up through enclosing regions
574/// and stop at the definition region(s): SSA dominance means a cycle that
575/// skips DEF cannot live above DEF.
576static bool
578 const SetVector<Value> &definitions,
579 OneShotAnalysisState &state) {
580 // Outermost region that contains a definition. SSA dominance means a cycle
581 // that skips DEF cannot live above this.
582 assert(!definitions.empty() && "expected at least one definition");
583 Region *outermostDefRegion = nullptr;
584 for (Value def : definitions) {
585 Region *defRegion = def.getParentRegion();
586 if (!outermostDefRegion || defRegion->isAncestor(outermostDefRegion))
587 outermostDefRegion = defRegion;
588 }
589 assert(outermostDefRegion && "expected a definition region");
590
591 for (Operation *readOp = uRead->getOwner(); readOp != nullptr;
592 readOp = readOp->getParentOp()) {
593 Region *region = readOp->getParentRegion();
594 if (!region)
595 continue;
596
597 Operation *writeOp = region->findAncestorOpInRegion(*uWrite->getOwner());
598 if (!writeOp)
599 continue;
600
601 Block *readBlock = readOp->getBlock();
602 Block *writeBlock = writeOp->getBlock();
603 for (Value def : definitions) {
604 SmallPtrSet<Block *, 16> except = {def.getParentBlock()};
605 if (state.isReachableCached(readBlock, writeBlock, &except) &&
606 state.isReachableCached(writeBlock, readBlock, &except))
607 return false;
608 }
609 if (region == outermostDefRegion)
610 break;
611 }
612 return true;
613}
614
616 const SetVector<Value> &definitions,
617 OneShotAnalysisState &state) {
618 // No multi-block region means no block-based cycle for dominance to miss.
620 return true;
621
622 assert(!definitions.empty() && "expected at least one definition");
623 Block *readBlock = uRead->getOwner()->getBlock();
624 Block *writeBlock = uWrite->getOwner()->getBlock();
625
626 SmallVector<Block *> defBlocks;
627 defBlocks.reserve(definitions.size());
628 for (Value def : definitions)
629 defBlocks.push_back(def.getParentBlock());
630 llvm::sort(defBlocks);
631 defBlocks.erase(llvm::unique(defBlocks), defBlocks.end());
632
634 readBlock, writeBlock, defBlocks, [&] {
635 return computeCanUseOpDominanceDueToBlocks(uRead, uWrite, definitions,
636 state);
637 });
638}
639
640static bool canUseOpDominance(OpOperand *uRead, OpOperand *uWrite,
641 const SetVector<Value> &definitions,
642 OneShotAnalysisState &state) {
643 return canUseOpDominanceDueToRegions(uRead, uWrite, definitions, state) &&
644 canUseOpDominanceDueToBlocks(uRead, uWrite, definitions, state);
645}
646
647/// Annotate IR with details about the detected RaW conflict.
648static void annotateConflict(OpOperand *uRead, OpOperand *uConflictingWrite,
649 Value definition) {
650 static uint64_t counter = 0;
651 Operation *readingOp = uRead->getOwner();
652 Operation *conflictingWritingOp = uConflictingWrite->getOwner();
653
654 OpBuilder b(conflictingWritingOp->getContext());
655 std::string id = "C_" + std::to_string(counter++);
656
657 std::string conflictingWriteAttr =
658 id +
659 "[CONFL-WRITE: " + std::to_string(uConflictingWrite->getOperandNumber()) +
660 "]";
661 conflictingWritingOp->setDiscardableAttr(conflictingWriteAttr,
662 b.getUnitAttr());
663
664 std::string readAttr =
665 id + "[READ: " + std::to_string(uRead->getOperandNumber()) + "]";
666 readingOp->setDiscardableAttr(readAttr, b.getUnitAttr());
667
668 if (auto opResult = dyn_cast<OpResult>(definition)) {
669 std::string defAttr =
670 id + "[DEF: result " + std::to_string(opResult.getResultNumber()) + "]";
671 opResult.getDefiningOp()->setDiscardableAttr(defAttr, b.getUnitAttr());
672 } else {
673 auto bbArg = cast<BlockArgument>(definition);
674 std::string defAttr =
675 id + "[DEF: bbArg " + std::to_string(bbArg.getArgNumber()) + "]";
676 bbArg.getOwner()->getParentOp()->setDiscardableAttr(defAttr,
677 b.getUnitAttr());
678 }
679}
680
681/// Return 'true' if a tensor that is equivalent to `other` can be found in the
682/// reverse use-def chain of `start`. Note: If an OpOperand bufferizes out of
683/// place along that use-def chain, the two tensors may not materialize as
684/// equivalent buffers (but separate allocations).
685///
686/// Note: This function also requires that the two tensors have equivalent
687/// indexing. I.e., the tensor types do not change along the use-def chain,
688/// apart from static <-> dynamic dim casts.
690 OpOperand *start,
691 Value other) {
692 TraversalConfig config;
693 config.followEquivalentOnly = true;
694 config.alwaysIncludeLeaves = false;
695 config.followSameTypeOrCastsOnly = true;
696 return !state
697 .findValueInReverseUseDefChain(
698 start, [&](Value v) { return v == other; }, config)
699 .empty();
700}
701
702/// Return "true" if the given operand's value is originating from a subset
703/// that is equivalent to the subset that `subsetOp` inserts into.
705 OpOperand *opOperand,
706 SubsetInsertionOpInterface subsetOp) {
707 auto matchingSubset = [&](Value val) {
708 if (auto opResult = dyn_cast<OpResult>(val))
709 if (subsetOp.isEquivalentSubset(opResult, [&](Value v1, Value v2) {
710 return state.areEquivalentBufferizedValues(v1, v2);
711 }))
712 return true;
713 return false;
714 };
715 // There may be multiple leaves at which the reverse SSA use-def chain lookup
716 // terminates. All of them must be equivalent subsets.
717 SetVector<Value> backwardSlice =
718 state.findValueInReverseUseDefChain(opOperand, matchingSubset);
719 return llvm::all_of(backwardSlice, matchingSubset);
720}
721
722/// Return "true" if the given "read" and potentially conflicting "write" are
723/// not conflicting due to their subset relationship. The comments in this
724/// function are expressed in terms of tensor.extract_slice/tensor.insert_slice
725/// pairs, but apply to any subset ops that implement the
726/// `SubsetInsertionOpInterface`.
728 OpOperand *uConflictingWrite,
729 const AnalysisState &state) {
730 Operation *readingOp = uRead->getOwner();
731 Operation *conflictingWritingOp = uConflictingWrite->getOwner();
732
733 // Special rules for matching ExtractSliceOp/InsertSliceOp pairs. If
734 // uRead is an InsertSliceOp...
735 if (auto subsetOp = dyn_cast<SubsetInsertionOpInterface>(readingOp)) {
736 // As an example, consider the following IR.
737 //
738 // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] }
739 // %1 = linalg.fill %cst, %0 {inplace= [true] }
740 // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1]
741 // {inplace= [true] }
742
743 if (uRead == &subsetOp.getDestinationOperand() &&
744 matchesInsertDestination(state, uConflictingWrite, subsetOp))
745 // Case 1: The main insight is that InsertSliceOp reads only part of
746 // the destination tensor. The overwritten area is not read. If
747 // uConflictingWrite writes into exactly the memory location that is
748 // being read by uRead, this is not a conflict.
749 //
750 // In the above example:
751 // uRead = OpOperand 1 (%t) of tensor.insert_slice
752 // uConflictingWrite = OpOperand 1 (%0) of linalg.fill
753 //
754 // The read of %t does not conflict with the write of the FillOp
755 // (same aliases!) because the area that the FillOp operates on is
756 // exactly the one that is *not* read via %t.
757 return true;
758
759 if (uRead == &subsetOp.getSourceOperand() &&
760 uConflictingWrite == &subsetOp.getDestinationOperand() &&
761 matchesInsertDestination(state, uRead, subsetOp))
762 // Case 2: The read of the source tensor and the write to the dest
763 // tensor via an InsertSliceOp is not a conflict if the read is
764 // reading exactly that part of an equivalent tensor that the
765 // InsertSliceOp is writing.
766 //
767 // In the above example:
768 // uRead = OpOperand 0 (%1) of tensor.insert_slice
769 // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice
770 return true;
771 }
772
773 // If uConflictingWrite is an InsertSliceOp...
774 if (auto subsetOp =
775 dyn_cast<SubsetInsertionOpInterface>(conflictingWritingOp)) {
776 if (uConflictingWrite == &subsetOp.getDestinationOperand()) {
777 auto writtenSubset = cast<SubsetOpInterface>(conflictingWritingOp);
778 auto isDisjointSubset = [&](SubsetOpInterface readSubset) {
779 return readSubset.operatesOnDisjointSubset(
780 writtenSubset, [&](Value v1, Value v2) {
781 return state.areEquivalentBufferizedValues(v1, v2);
782 });
783 };
784 auto isDisjointExtraction = [&](Value value) {
785 auto extraction = value.getDefiningOp<SubsetExtractionOpInterface>();
786 return extraction && isDisjointSubset(cast<SubsetOpInterface>(
787 extraction.getOperation()));
788 };
789
790 // Example:
791 //
792 // %0 = tensor.insert_slice %s into %t[0][4][1]
793 // %1 = vector.transfer_read %t[%c4], %cst
794 //
795 // A read from a subset does not conflict with a write to a disjoint
796 // subset of an equivalent tensor. Check the operand roles explicitly
797 // because not every subset extraction bufferizes to a memory read.
798 if (auto extraction = dyn_cast<SubsetExtractionOpInterface>(readingOp)) {
799 if (uRead == &extraction.getSourceOperand() &&
800 isDisjointSubset(cast<SubsetOpInterface>(readingOp)))
801 return true;
802 }
803
804 // Example:
805 //
806 // %0 = tensor.insert_slice %s into %t[0][4][1]
807 // %1 = tensor.extract_slice %t[4][4][1]
808 // return %0, %1
809 //
810 // The actual read may be further down the aliasing use-def chain. E.g.,
811 // tensor.extract_slice is an alias-only op and the read is attributed to
812 // a return or another consumer of its result. Trace such reads back to
813 // their subset extractions. Every origin must be a disjoint subset; a
814 // non-subset leaf or an extraction that may overlap keeps the analysis
815 // conservative.
816 SetVector<Value> readOrigins =
817 state.findValueInReverseUseDefChain(uRead, isDisjointExtraction);
818 if (!readOrigins.empty() &&
819 llvm::all_of(readOrigins, isDisjointExtraction))
820 return true;
821 }
822
823 // As an example, consider the following IR.
824 //
825 // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] }
826 // %1 = linalg.fill %cst, %0 {inplace= [true] }
827 // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1]
828 // {inplace= [true] }
829 // %3 = vector.transfer_read %1, %cst
830 //
831 // In the above example:
832 // uRead = OpOperand 0 (%1) of vector.transfer_read
833 // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice
834 // definition = %1
835 //
836 // This is not a conflict because the InsertSliceOp overwrites the
837 // memory segment of %1 with the exact same data. (Effectively, there
838 // is no memory write here.)
839 if (uConflictingWrite == &subsetOp.getDestinationOperand() &&
840 state.areEquivalentBufferizedValues(
841 uRead->get(), subsetOp.getSourceOperand().get()) &&
842 matchesInsertDestination(state, &subsetOp.getSourceOperand(), subsetOp))
843 return true;
844 }
845
846 return false;
847}
848
849/// Given sets of uses and writes, return true if there is a RaW conflict under
850/// the assumption that all given reads/writes alias the same buffer and that
851/// all given writes bufferize inplace.
852///
853/// A conflict is: According to SSA use-def chains, a read R is supposed to read
854/// the result of a definition W1. But because of bufferization decisions, R
855/// actually reads another definition W2.
856static bool
858 const DenseSet<OpOperand *> &usesWrite,
859 const DominanceInfo &domInfo,
860 OneShotAnalysisState &state) {
861 const BufferizationOptions &options = state.getOptions();
862
863 // Before going through the main RaW analysis, find cases where a buffer must
864 // be privatized due to parallelism. If the result of a write is never read,
865 // privatization is not necessary (and large parts of the IR are likely dead).
866 if (state.mayHaveParallelRegions() && !usesRead.empty()) {
867 for (OpOperand *uConflictingWrite : usesWrite) {
868 // Find the allocation point or last write (definition) of the buffer.
869 // Note: In contrast to `findDefinitions`, this also returns results of
870 // ops that do not bufferize to memory write when no other definition
871 // could be found. E.g., "bufferization.alloc_tensor" would be included,
872 // even though that op just bufferizes to an allocation but does define
873 // the contents of the buffer.
874 SetVector<Value> definitionsOrLeaves =
875 state.findValueInReverseUseDefChain(uConflictingWrite, [&](Value v) {
876 return state.bufferizesToMemoryWrite(v);
877 });
878 assert(!definitionsOrLeaves.empty() &&
879 "expected at least one definition or leaf");
880
881 // The writing op must bufferize out-of-place if the definition is in a
882 // different parallel region than this write.
883 Region *writeParallelRegion = getParallelRegion(
884 uConflictingWrite->getOwner()->getParentRegion(), options);
885 for (Value def : definitionsOrLeaves) {
886 if (getParallelRegion(def.getParentRegion(), options) !=
887 writeParallelRegion) {
888 LDBG() << "\n- bufferizes out-of-place due to parallel region:\n"
889 << " unConflictingWrite = operand "
890 << uConflictingWrite->getOperandNumber() << " of "
891 << OpWithFlags(uConflictingWrite->getOwner(),
892 OpPrintingFlags().skipRegions());
893 return true;
894 }
895 }
896 }
897 }
898
899 for (OpOperand *uRead : usesRead) {
900 Operation *readingOp = uRead->getOwner();
901 LDBG() << "\n- check conflict:\n"
902 << " uRead = operand " << uRead->getOperandNumber() << " of "
903 << OpWithFlags(readingOp, OpPrintingFlags().skipRegions());
904
905 // Find the definition of uRead by following the SSA use-def chain.
906 // E.g.:
907 //
908 // %0 = "writing_op"(%t) : tensor<?x32> -> tensor<?xf32>
909 // %1 = "aliasing_op"(%0) : tensor<?x32> -> tensor<?xf32>
910 // %2 = "reading_op"(%1) : : tensor<?x32> -> not_a_tensor_type
911 //
912 // In the above example, if uRead is the OpOperand of reading_op, the
913 // definition is %0. Note that operations that create an alias but do not
914 // bufferize to a memory write (such as ExtractSliceOp) are skipped.
915 const SetVector<Value> &definitions = state.findDefinitionsCached(uRead);
916 if (definitions.empty()) {
917 // Fast path: No conflict if there are no definitions.
918 LDBG() << " no conflict: read value has no definitions";
919 continue;
920 }
921
922 // If `useDominance` is true below, then for CFG regions we know that we can
923 // rule out blocks containing DEFs of block args for the reachability check
924 // in `cannotHappenAfter`. See the conditions in
925 // `canUseOpDominanceDueToBlocks`. E.g.:
926 //
927 // ^h(%t: tensor<?xf32>): // DEF
928 // cf.cond_br %c, ^exit, ^body
929 // ^body:
930 // %w = "writing_op"(%t) // WRITE
931 // cf.br ^h(%w)
932 // ^exit:
933 // "reading_op"(%t) // READ
934 //
935 // The only CFG path from WRITE to READ is ^body -> ^h -> ^exit, so READ
936 // cannot happen after WRITE without going through the DEF in ^h, and
937 // therefore there is no conflict.
938 SmallPtrSet<Block *, 16> bbArgDefBlocks;
939 for (Value def : definitions)
940 if (isa<BlockArgument>(def))
941 bbArgDefBlocks.insert(def.getParentBlock());
942
943 // Look for conflicting memory writes. Potential conflicts are writes to an
944 // alias that have been decided to bufferize inplace.
945 for (OpOperand *uConflictingWrite : usesWrite) {
946 LDBG() << " unConflictingWrite = operand "
947 << uConflictingWrite->getOperandNumber() << " of "
948 << OpWithFlags(uConflictingWrite->getOwner(),
949 OpPrintingFlags().skipRegions());
950
951 // Check if op dominance can be used to rule out read-after-write
952 // conflicts.
953 bool useDominance =
954 canUseOpDominance(uRead, uConflictingWrite, definitions, state);
955 LDBG() << "\n- useDominance = " << useDominance;
956
957 // Throughout this loop, check for multiple requirements that have to be
958 // met for uConflictingWrite to be an actual conflict.
959 Operation *conflictingWritingOp = uConflictingWrite->getOwner();
960
961 // Inside of repetitive regions, ops may be executed multiple times and op
962 // dominance cannot be used to rule out conflicts.
963 if (useDominance) {
964 // No conflict if the readingOp cannot happen after
965 // conflictingWritingOp, i.e., the write is not visible when reading.
966 //
967 // Note: If ops are executed multiple times (e.g., because they are
968 // inside a loop), there may be no meaningful `cannotHappenAfter`
969 // relationship.
970 if (cannotHappenAfter(readingOp, conflictingWritingOp, domInfo, state,
971 bbArgDefBlocks)) {
972 LDBG() << " no conflict: read cannot happen after write";
973 continue;
974 }
975
976 // No conflict if the reading use equals the use of the conflicting
977 // write. A use cannot conflict with itself.
978 //
979 // Note: Just being the same op is not enough. It has to be the same
980 // use.
981 // Note: If the op is executed multiple times (e.g., because it is
982 // inside a loop), it may be conflicting with itself.
983 if (uConflictingWrite == uRead) {
984 LDBG() << " no conflict: read and write are same use";
985 continue;
986 }
987
988 // Ops are not conflicting if they are in mutually exclusive regions.
989 //
990 // Note: If ops are executed multiple times (e.g., because they are
991 // inside a loop), mutually exclusive regions may be executed
992 // multiple times.
993 if (state.insideMutuallyExclusiveRegions(readingOp,
994 conflictingWritingOp)) {
995 LDBG() << " no conflict: read and write are in "
996 "mutually exclusive regions";
997 continue;
998 }
999
1000 // Two equivalent operands of the same op are not conflicting if the op
1001 // bufferizes to element-wise access. I.e., all loads at a position
1002 // happen before all stores to the same position.
1003 if (conflictingWritingOp == readingOp) {
1004 if (auto bufferizableOp = options.dynCastBufferizableOp(readingOp)) {
1005 if (bufferizableOp.bufferizesToElementwiseAccess(
1006 state, {uRead, uConflictingWrite})) {
1008 state, uRead, uConflictingWrite->get()) ||
1010 state, uConflictingWrite, uRead->get())) {
1011 LDBG() << " no conflict: op bufferizes to element-wise access";
1012 continue;
1013 }
1014 }
1015 }
1016 }
1017 }
1018
1019 // No conflict if the operands are non-conflicting subsets.
1020 if (state.areNonConflictingSubsetsCached(uRead, uConflictingWrite)) {
1021 LDBG() << " no conflict: non-conflicting subsets";
1022 continue;
1023 }
1024
1025 // No conflict if the op interface says so.
1026 if (auto bufferizableOp = options.dynCastBufferizableOp(readingOp)) {
1027 if (bufferizableOp.isNotConflicting(uRead, uConflictingWrite, state)) {
1028 LDBG() << " no conflict: op interace of reading op says 'no'";
1029 continue;
1030 }
1031 }
1032
1033 if (conflictingWritingOp != readingOp) {
1034 if (auto bufferizableOp =
1035 options.dynCastBufferizableOp(conflictingWritingOp)) {
1036 if (bufferizableOp.isNotConflicting(uRead, uConflictingWrite,
1037 state)) {
1038 LDBG() << " no conflict: op interace of writing op says 'no'";
1039 continue;
1040 }
1041 }
1042 }
1043
1044 // Check all possible definitions.
1045 for (Value definition : definitions) {
1046 LDBG() << " * definition = " << definition;
1047
1048 // No conflict if the conflicting write cannot happen after the
1049 // definition.
1050 if (Operation *defOp = definition.getDefiningOp()) {
1051 if (cannotHappenAfter(conflictingWritingOp, defOp, domInfo, state)) {
1052 LDBG() << " no conflict: write cannot happen after definition";
1053 continue;
1054 }
1055 // No conflict if conflictingWritingOp is contained in defOp.
1056 if (defOp->isProperAncestor(conflictingWritingOp)) {
1057 LDBG() << " no conflict: write is contained in definition";
1058 continue;
1059 }
1060 } else {
1061 // A bbArg is defined at block entry. The write happens after that
1062 // only if it is in the same block or in a reachable block (e.g. a
1063 // successor).
1064 Block *defBlock = cast<BlockArgument>(definition).getOwner();
1065 Operation *writeOp = defBlock->getParent()->findAncestorOpInRegion(
1066 *conflictingWritingOp);
1067 if (!writeOp ||
1068 (writeOp->getBlock() != defBlock &&
1069 !state.isReachableCached(defBlock, writeOp->getBlock()))) {
1070 LDBG() << " no conflict: definition is bbArg and write cannot "
1071 "happen after def";
1072 continue;
1073 }
1074 }
1075
1076 // No conflict if the conflicting write and the definition are the same
1077 // use.
1078 AliasingValueList aliases = state.getAliasingValues(*uConflictingWrite);
1079 if (aliases.getNumAliases() == 1 &&
1080 aliases.getAliases()[0].value == definition) {
1081 LDBG() << " no conflict: definition and write are same";
1082 continue;
1083 }
1084
1085 // All requirements are met. Conflict found!
1086
1087 if (options.printConflicts)
1088 annotateConflict(uRead, uConflictingWrite, definition);
1089 LDBG() << " => RaW CONFLICT FOUND";
1090 return true;
1091 }
1092 }
1093 }
1094
1095 return false;
1096}
1097
1098// Helper function to iterate on aliases of `root` and capture the writes.
1100 const OneShotAnalysisState &state) {
1101 state.applyOnAliases(root, [&](Value alias) {
1102 for (auto &use : alias.getUses())
1103 // Inplace write to a value that aliases root.
1104 if (isInplaceMemoryWrite(use, state))
1105 res.insert(&use);
1106 });
1107}
1108
1109// Helper function to iterate on aliases of `root` and capture the reads.
1111 const OneShotAnalysisState &state) {
1112 state.applyOnAliases(root, [&](Value alias) {
1113 for (auto &use : alias.getUses()) {
1114 // Read of a value that aliases root.
1115 if (state.bufferizesToMemoryRead(use)) {
1116 res.insert(&use);
1117 continue;
1118 }
1119
1120 // Read of a dependent value in the SSA use-def chain. E.g.:
1121 //
1122 // %0 = ...
1123 // %1 = tensor.extract_slice %0 {not_analyzed_yet}
1124 // "read"(%1)
1125 //
1126 // In the above example, getAliasingReads(%0) includes the first OpOperand
1127 // of the tensor.extract_slice op. The extract_slice itself does not read
1128 // but its aliasing result is eventually fed into an op that does.
1129 //
1130 // Note: This is considered a "read" only if the use does not bufferize to
1131 // a memory write. (We already ruled out memory reads. In case of a memory
1132 // write, the buffer would be entirely overwritten; in the above example
1133 // there would then be no flow of data from the extract_slice operand to
1134 // its result's uses.)
1135 if (!state.bufferizesToMemoryWrite(use)) {
1136 AliasingValueList aliases = state.getAliasingValues(use);
1137 if (llvm::any_of(aliases, [&](AliasingValue a) {
1138 return state.isValueRead(a.value);
1139 }))
1140 res.insert(&use);
1141 }
1142 }
1143 });
1144}
1145
1146/// Return true if bufferizing `operand` inplace would create a conflict. A read
1147/// R and a write W of the same alias set is a conflict if inplace bufferization
1148/// of W changes the value read by R to a value different from the one that
1149/// would be expected by tracing back R's origin through SSA use-def chains.
1150/// A conflict can only be introduced by a new alias and/or an inplace
1151/// bufferization decision.
1152///
1153/// Example:
1154/// %0 = tensor.extract_slice %t[...][...][1, 1] {inplace?}
1155/// %1 = vector.transfer_write %v1, %t {inplace} : vector<5xf32>, tensor<?xf32>
1156/// %e = tensor.extract_slice %1
1157/// %2 = vector.transfer_write %v2, %0 {inplace} : vector<6xf32>, tensor<?xf32>
1158/// %3 = vector.transfer_read %e, %cst : tensor<?xf32>, vector<7xf32>
1159///
1160/// In the above example, the two TransferWriteOps have already been decided to
1161/// bufferize inplace. Bufferizing the ExtractSliceOp inplace would create a
1162/// conflict because:
1163/// * According to SSA use-def chains, we expect to read the result of %1.
1164/// * However, adding an alias {%0, %t} would mean that the second
1165/// TransferWriteOp overwrites the result of the first one. Therefore, the
1166/// TransferReadOp would no longer be reading the result of %1.
1167///
1168/// If `checkConsistencyOnly` is true, this function checks if there is a
1169/// read-after-write conflict without bufferizing `operand` inplace. This would
1170/// indicate a problem with the current inplace bufferization decisions.
1171///
1172/// Note: If `checkConsistencyOnly`, this function may be called with a null
1173/// OpResult. In that case, only the consistency of bufferization decisions
1174/// involving aliases of the given OpOperand are checked.
1176 OpOperand &operand, const DominanceInfo &domInfo,
1177 OneShotAnalysisState &state, bool checkConsistencyOnly = false) {
1178 // Collect reads and writes of all aliases of OpOperand and OpResult.
1179 DenseSet<OpOperand *> usesRead, usesWrite;
1180 getAliasingReads(usesRead, operand.get(), state);
1181 getAliasingInplaceWrites(usesWrite, operand.get(), state);
1182 for (AliasingValue alias : state.getAliasingValues(operand)) {
1183 getAliasingReads(usesRead, alias.value, state);
1184 getAliasingInplaceWrites(usesWrite, alias.value, state);
1185 }
1186 if (!checkConsistencyOnly && state.bufferizesToMemoryWrite(operand))
1187 usesWrite.insert(&operand);
1188
1189 return hasReadAfterWriteInterference(usesRead, usesWrite, domInfo, state);
1190}
1191
1192/// Annotate IR with details about the detected non-writability conflict.
1194 static int64_t counter = 0;
1195 OpBuilder b(value.getContext());
1196 std::string id = "W_" + std::to_string(counter++);
1197 if (auto opResult = dyn_cast<OpResult>(value)) {
1198 std::string attr = id + "[NOT-WRITABLE: result " +
1199 std::to_string(opResult.getResultNumber()) + "]";
1200 opResult.getDefiningOp()->setDiscardableAttr(attr, b.getUnitAttr());
1201 } else {
1202 auto bbArg = cast<BlockArgument>(value);
1203 std::string attr = id + "[NOT-WRITABLE: bbArg " +
1204 std::to_string(bbArg.getArgNumber()) + "]";
1205 bbArg.getOwner()->getParentOp()->setDiscardableAttr(attr, b.getUnitAttr());
1206 }
1207}
1208
1209/// Return true if bufferizing `operand` inplace would create a write to a
1210/// non-writable buffer.
1211static bool
1213 OneShotAnalysisState &state,
1214 bool checkConsistencyOnly = false) {
1215 bool foundWrite =
1216 !checkConsistencyOnly && state.bufferizesToMemoryWrite(operand);
1217
1218 if (!foundWrite) {
1219 // Collect writes of all aliases of OpOperand and OpResult.
1220 DenseSet<OpOperand *> usesWrite;
1221 getAliasingInplaceWrites(usesWrite, operand.get(), state);
1222 for (AliasingValue alias : state.getAliasingValues(operand))
1223 getAliasingInplaceWrites(usesWrite, alias.value, state);
1224 foundWrite = !usesWrite.empty();
1225 }
1226
1227 if (!foundWrite)
1228 return false;
1229
1230 // Look for a read-only tensor among all aliases.
1231 bool foundReadOnly = false;
1232 auto checkReadOnly = [&](Value v) {
1233 if (!state.isWritable(v)) {
1234 foundReadOnly = true;
1235 if (state.getOptions().printConflicts)
1237 }
1238 };
1239 state.applyOnAliases(operand.get(), checkReadOnly);
1240 for (AliasingValue alias : state.getAliasingValues(operand))
1241 state.applyOnAliases(alias.value, checkReadOnly);
1242 if (foundReadOnly) {
1243 LDBG() << "=> NOT WRITABLE";
1244 return true;
1245 }
1246
1247 return false;
1248}
1249
1250//===----------------------------------------------------------------------===//
1251// Bufferization analyses.
1252//===----------------------------------------------------------------------===//
1253
1254// Find the values that define the contents of the given operand's value.
1255const llvm::SetVector<Value> &
1257 Value value = opOperand->get();
1258 if (!cachedDefinitions.count(value))
1259 cachedDefinitions[value] = findDefinitions(opOperand);
1260 return cachedDefinitions[value];
1261}
1262
1264 Block *from, Block *to, const SmallPtrSetImpl<Block *> *barriers) {
1265 if (!cfgReachabilityCache)
1266 cfgReachabilityCache = std::make_unique<CFGReachabilityCache>();
1267 return cfgReachabilityCache->isReachable(from, to, barriers);
1268}
1269
1271 Block *readBlock, Block *writeBlock, ArrayRef<Block *> defBlocks,
1272 function_ref<bool()> compute) {
1273 if (!opDominanceBlockCache)
1274 opDominanceBlockCache = std::make_unique<OpDominanceBlockCache>();
1275 return opDominanceBlockCache->getOrCompute(readBlock, writeBlock, defBlocks,
1276 compute);
1277}
1278
1280 OpOperand *uRead, OpOperand *uConflictingWrite) {
1281 auto key = std::make_pair(uRead, uConflictingWrite);
1282 auto [it, inserted] = nonConflictingSubsetCache.try_emplace(key, false);
1283 if (inserted)
1284 it->second = areNonConflictingSubsets(uRead, uConflictingWrite, *this);
1285 return it->second;
1286}
1287
1289 AnalysisState::resetCache();
1290 cachedDefinitions.clear();
1291 nonConflictingSubsetCache.clear();
1292 if (cfgReachabilityCache)
1293 cfgReachabilityCache->clear();
1294 if (opDominanceBlockCache)
1295 opDominanceBlockCache->clear();
1296}
1297
1298/// Determine if `operand` can be bufferized in-place.
1299static LogicalResult
1301 const DominanceInfo &domInfo) {
1302 LDBG() << "//===-------------------------------------------===//\n"
1303 << "Analyzing operand #" << operand.getOperandNumber() << " of "
1304 << OpWithFlags(operand.getOwner(), OpPrintingFlags().skipRegions());
1305
1306 bool foundInterference =
1307 wouldCreateWriteToNonWritableBuffer(operand, state) ||
1308 wouldCreateReadAfterWriteInterference(operand, domInfo, state);
1309
1310 if (foundInterference)
1311 state.bufferizeOutOfPlace(operand);
1312 else
1313 state.bufferizeInPlace(operand);
1314
1315 LDBG() << "//===-------------------------------------------===//";
1316 return success();
1317}
1318
1319LogicalResult
1321 const DominanceInfo &domInfo) {
1322 for (OpOperand &opOperand : op->getOpOperands())
1323 if (isa<TensorLikeType>(opOperand.get().getType()))
1324 if (failed(bufferizableInPlaceAnalysisImpl(opOperand, *this, domInfo)))
1325 return failure();
1326 return success();
1327}
1328
1329/// Analyze equivalence of tied OpResult/OpOperand pairs of the given ops.
1331 OneShotAnalysisState &state) {
1332 for (Operation *op : ops) {
1333 if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp(op)) {
1334 for (OpResult opResult : op->getOpResults()) {
1335 if (!isa<TensorLikeType>(opResult.getType()))
1336 continue;
1337 AliasingOpOperandList aliases = state.getAliasingOpOperands(opResult);
1338 if (aliases.getNumAliases() == 0)
1339 // Nothing to do if there are no aliasing OpOperands.
1340 continue;
1341
1342 Value firstOperand = aliases.begin()->opOperand->get();
1343 bool allEquivalent = true;
1344 for (AliasingOpOperand alias : aliases) {
1345 bool isEquiv = alias.relation == BufferRelation::Equivalent;
1346 bool isInPlace = state.isInPlace(*alias.opOperand);
1347 Value operand = alias.opOperand->get();
1348 if (isEquiv && isInPlace && alias.isDefinite) {
1349 // Found a definite, equivalent alias. Merge equivalence sets.
1350 // There can only be one definite alias, so we can stop here.
1351 state.unionEquivalenceClasses(opResult, operand);
1352 allEquivalent = false;
1353 break;
1354 }
1355 if (!isEquiv || !isInPlace)
1356 allEquivalent = false;
1357 if (!state.areEquivalentBufferizedValues(operand, firstOperand))
1358 allEquivalent = false;
1359 }
1360
1361 // If all "maybe" aliases are equivalent and the OpResult is not a new
1362 // allocation, it is a definite, equivalent alias. E.g.:
1363 //
1364 // aliasingOpOperands(%r) = {(%t0, EQUIV, MAYBE), (%t1, EQUIV, MAYBE)}
1365 // aliasingValues(%t0) = {(%r, EQUIV, MAYBE)}
1366 // aliasingValues(%t1) = {(%r, EQUIV, MAYBE)}
1367 // %r = arith.select %c, %t0, %t1 : tensor<?xf32>
1368 //
1369 // If %t0 and %t1 are equivalent, it is safe to union the equivalence
1370 // classes of %r, %t0 and %t1.
1371 if (allEquivalent && !bufferizableOp.bufferizesToAllocation(opResult))
1372 state.unionEquivalenceClasses(opResult, firstOperand);
1373 }
1374 }
1375 }
1376}
1377
1378/// Analyze equivalence of tied OpResult/OpOperand pairs of all ops contained
1379/// in `op`.
1381 // Traverse ops in PostOrder: Nested ops first, then enclosing ops.
1383 op->walk<WalkOrder::PostOrder>([&](Operation *op) {
1384 // No tensors => no buffers.
1385 if (none_of(op->getResultTypes(), isaTensor))
1386 return;
1387 ops.push_back(op);
1388 });
1389
1390 equivalenceAnalysis(ops, state);
1391}
1392
1393/// "Bottom-up from terminators" heuristic.
1394static SmallVector<Operation *>
1396 const OneShotAnalysisState &state) {
1397 SetVector<Operation *> traversedOps;
1398
1399 // Find region terminators.
1400 op->walk<WalkOrder::PostOrder>([&](RegionBranchTerminatorOpInterface term) {
1401 if (!traversedOps.insert(term))
1402 return;
1403 // Follow the reverse SSA use-def chain from each yielded value as long as
1404 // we stay within the same region.
1405 SmallVector<OpResult> worklist;
1406 for (Value v : term->getOperands()) {
1407 if (!isa<TensorLikeType>(v.getType()))
1408 continue;
1409 auto opResult = dyn_cast<OpResult>(v);
1410 if (!opResult)
1411 continue;
1412 worklist.push_back(opResult);
1413 }
1414 while (!worklist.empty()) {
1415 OpResult opResult = worklist.pop_back_val();
1416 Operation *defOp = opResult.getDefiningOp();
1417 if (!traversedOps.insert(defOp))
1418 continue;
1419 if (!term->getParentRegion()->findAncestorOpInRegion(*defOp))
1420 continue;
1421 AliasingOpOperandList aliases = state.getAliasingOpOperands(opResult);
1422 for (auto alias : aliases) {
1423 Value v = alias.opOperand->get();
1424 if (!isa<TensorLikeType>(v.getType()))
1425 continue;
1426 auto opResult = dyn_cast<OpResult>(v);
1427 if (!opResult)
1428 continue;
1429 worklist.push_back(opResult);
1430 }
1431 }
1432 });
1433
1434 // Analyze traversed ops, then all remaining ops.
1435 SmallVector<Operation *> result(traversedOps.begin(), traversedOps.end());
1437 if (!traversedOps.contains(op) && hasTensorSemantics(op))
1438 result.push_back(op);
1439 });
1440 return result;
1441}
1442
1444 const DominanceInfo &domInfo) {
1447
1448 SmallVector<Operation *> orderedOps;
1449 if (heuristic ==
1451 orderedOps = bottomUpFromTerminatorsHeuristic(op, *this);
1452 } else {
1453 op->walk([&](Operation *op) {
1454 // No tensors => no buffers.
1455 if (!hasTensorSemantics(op))
1456 return;
1457 orderedOps.push_back(op);
1458 });
1459 switch (heuristic) {
1461 // Default: Walk ops in reverse for better interference analysis.
1462 std::reverse(orderedOps.begin(), orderedOps.end());
1463 break;
1464 }
1466 // Ops are already sorted top-down in `orderedOps`.
1467 break;
1468 }
1470 assert(getOptions().analysisFuzzerSeed &&
1471 "expected that fuzzer seed it set");
1472 // This is a fuzzer. For testing purposes only. Randomize the order in
1473 // which operations are analyzed. The bufferization quality is likely
1474 // worse, but we want to make sure that no assertions are triggered
1475 // anywhere.
1476 std::mt19937 g(getOptions().analysisFuzzerSeed);
1477 llvm::shuffle(orderedOps.begin(), orderedOps.end(), g);
1478 break;
1479 }
1480 default: {
1481 llvm_unreachable("unsupported heuristic");
1482 }
1483 }
1484 }
1485
1486 // Analyze ops in the computed order.
1487 for (Operation *op : orderedOps)
1488 if (failed(analyzeSingleOp(op, domInfo)))
1489 return failure();
1490
1491 equivalenceAnalysis(op, *this);
1492 return success();
1493}
1494
1496 Operation *op, const DominanceInfo &domInfo, OneShotAnalysisState &state) {
1497 const BufferizationOptions &options = state.getOptions();
1498
1499 // Note: This walk cannot be combined with the one below because interface
1500 // methods of invalid/unsupported ops may be called during the second walk.
1501 // (On ops different from `op`.)
1502 WalkResult walkResult = op->walk([&](BufferizableOpInterface op) {
1503 // Skip ops that are not in the filter.
1504 if (!options.isOpAllowed(op.getOperation()))
1505 return WalkResult::advance();
1506
1507 // Check for unsupported unstructured control flow.
1508 if (!op.supportsUnstructuredControlFlow()) {
1509 for (Region &r : op->getRegions()) {
1510 if (r.getBlocks().size() > 1) {
1511 op->emitOpError("op or BufferizableOpInterface implementation does "
1512 "not support unstructured control flow, but at least "
1513 "one region has multiple blocks");
1514 return WalkResult::interrupt();
1515 }
1516 }
1517 }
1518
1519 return WalkResult::advance();
1520 });
1521 if (walkResult.wasInterrupted())
1522 return failure();
1523
1524 walkResult = op->walk([&](BufferizableOpInterface op) {
1525 // Skip ops that are not in the filter.
1526 if (!options.isOpAllowed(op.getOperation()))
1527 return WalkResult::advance();
1528
1529 // Input IR may not contain any ToTensorOps without the "restrict"
1530 // attribute. Such tensors may alias any other tensor, which is currently
1531 // not handled in the analysis.
1532 if (auto toTensorOp = dyn_cast<ToTensorOp>(op.getOperation())) {
1533 if (!toTensorOp.getRestrict() && !toTensorOp->getUses().empty()) {
1534 op->emitOpError("to_tensor ops without `restrict` are not supported by "
1535 "One-Shot Analysis");
1536 return WalkResult::interrupt();
1537 }
1538 }
1539
1540 for (OpOperand &opOperand : op->getOpOperands()) {
1541 if (isa<TensorLikeType>(opOperand.get().getType())) {
1543 opOperand, domInfo, state,
1544 /*checkConsistencyOnly=*/true)) {
1545 // This error can happen if certain "mustBufferizeInPlace" interface
1546 // methods are implemented incorrectly, such that the IR already has
1547 // a RaW conflict before making any bufferization decisions. It can
1548 // also happen if the bufferization.materialize_in_destination is used
1549 // in such a way that a RaW conflict is not avoidable.
1550 op->emitOpError("not bufferizable under the given constraints: "
1551 "cannot avoid RaW conflict");
1552 return WalkResult::interrupt();
1553 }
1554
1555 if (state.isInPlace(opOperand) &&
1557 opOperand, state, /*checkConsistencyOnly=*/true)) {
1558 op->emitOpError("not bufferizable under the given constraints: would "
1559 "write to read-only buffer");
1560 return WalkResult::interrupt();
1561 }
1562 }
1563 }
1564
1565 return WalkResult::advance();
1566 });
1567
1568 return success(!walkResult.wasInterrupted());
1569}
1570
1571/// Annotate the IR with the result of the analysis. For testing/debugging only.
1572static void
1574 const OneShotAnalysisState &state) {
1575 // Add __inplace_operands_attr__.
1576 op->walk([&](Operation *op) {
1577 for (OpOperand &opOperand : op->getOpOperands())
1578 if (isa<TensorLikeType>(opOperand.get().getType()))
1579 setInPlaceOpOperand(opOperand, state.isInPlace(opOperand));
1580 });
1581}
1582
1584 const OneShotAnalysisState &state) {
1585 AsmState asmState(op);
1586 Builder b(op->getContext());
1587 // Helper function to build an array attribute of aliasing SSA value strings.
1588 auto buildAliasesArray = [&](Value v) {
1589 SmallVector<Attribute> aliases;
1590 state.applyOnAliases(v, [&](Value alias) {
1591 std::string buffer;
1592 llvm::raw_string_ostream stream(buffer);
1593 alias.printAsOperand(stream, asmState);
1594 aliases.push_back(b.getStringAttr(buffer));
1595 });
1596 return b.getArrayAttr(aliases);
1597 };
1598
1599 op->walk([&](Operation *op) {
1600 // Build alias set array for every OpResult.
1601 SmallVector<Attribute> opResultAliasSets;
1602 for (OpResult opResult : op->getOpResults()) {
1603 if (llvm::isa<TensorLikeType>(opResult.getType())) {
1604 opResultAliasSets.push_back(buildAliasesArray(opResult));
1605 }
1606 }
1607 if (!opResultAliasSets.empty())
1609 b.getArrayAttr(opResultAliasSets));
1610
1611 // Build alias set array for every BlockArgument.
1612 SmallVector<Attribute> regionAliasSets;
1613 bool hasTensorBbArg = false;
1614 for (Region &r : op->getRegions()) {
1615 SmallVector<Attribute> blockAliasSets;
1616 for (Block &block : r.getBlocks()) {
1617 SmallVector<Attribute> bbArgAliasSets;
1618 for (BlockArgument bbArg : block.getArguments()) {
1619 if (llvm::isa<TensorLikeType>(bbArg.getType())) {
1620 bbArgAliasSets.push_back(buildAliasesArray(bbArg));
1621 hasTensorBbArg = true;
1622 }
1623 }
1624 blockAliasSets.push_back(b.getArrayAttr(bbArgAliasSets));
1625 }
1626 regionAliasSets.push_back(b.getArrayAttr(blockAliasSets));
1627 }
1628 if (hasTensorBbArg)
1630 b.getArrayAttr(regionAliasSets));
1631 });
1632}
1633
1635 OneShotAnalysisState &state,
1636 BufferizationStatistics *statistics) {
1637 DominanceInfo domInfo(op);
1639
1640 if (failed(checkPreBufferizationAssumptions(op, domInfo, state)))
1641 return failure();
1642
1643 // If the analysis fails, just return.
1644 if (failed(state.analyzeOp(op, domInfo)))
1645 return failure();
1646
1647 if (statistics) {
1648 statistics->numTensorInPlace = state.getStatNumTensorInPlace();
1649 statistics->numTensorOutOfPlace = state.getStatNumTensorOutOfPlace();
1650 }
1651
1652 bool failedAnalysis = false;
1653
1654 // Gather some extra analysis data.
1655 state.gatherUndefinedTensorUses(op);
1656
1657 // Analysis verification: After setting up alias/equivalence sets, each op
1658 // can check for expected invariants/limitations and fail the analysis if
1659 // necessary.
1660 op->walk([&](Operation *op) {
1661 if (BufferizableOpInterface bufferizableOp =
1662 options.dynCastBufferizableOp(op))
1663 failedAnalysis |= failed(bufferizableOp.verifyAnalysis(state));
1664 });
1665
1666 // Annotate operations if we only want to report the analysis.
1667 if (options.testAnalysisOnly)
1669 if (options.dumpAliasSets)
1670 annotateOpsWithAliasSets(op, state);
1671
1672 return success(!failedAnalysis);
1673}
1674
1677 BufferizationState &state, BufferizationStatistics *statistics) {
1678 // copy-before-write deactivates the analysis. It cannot be used together with
1679 // test-analysis-only.
1680 assert(!(options.copyBeforeWrite && options.testAnalysisOnly) &&
1681 "invalid combination of bufferization flags");
1682
1683 if (options.copyBeforeWrite) {
1684 // Copy buffer before each write. No analysis is needed.
1685 } else {
1686 // Run One-Shot Analysis and insert buffer copies (on the tensor level)
1687 // only where needed. This is the default and much more efficient than
1688 // copy-before-write.
1689 if (failed(insertTensorCopies(op, options, state, statistics)))
1690 return failure();
1691
1692 // If test-analysis-only is set, the IR was annotated with RaW conflict
1693 // markers (attributes) during One-Shot Analysis.
1694 if (options.testAnalysisOnly)
1695 return success();
1696 }
1697
1698 // Bufferize the op and its nested ops. If options.copyBeforeWrite is set,
1699 // a new buffer copy is allocated every time a buffer is written to.
1700 return bufferizeOp(op, options, state, statistics);
1701}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
static bool hasReadAfterWriteInterference(const DenseSet< OpOperand * > &usesRead, const DenseSet< OpOperand * > &usesWrite, const DominanceInfo &domInfo, OneShotAnalysisState &state)
Given sets of uses and writes, return true if there is a RaW conflict under the assumption that all g...
static void getAliasingReads(DenseSet< OpOperand * > &res, Value root, const OneShotAnalysisState &state)
static void equivalenceAnalysis(SmallVector< Operation * > &ops, OneShotAnalysisState &state)
Analyze equivalence of tied OpResult/OpOperand pairs of the given ops.
static void setInPlaceOpOperand(OpOperand &opOperand, bool inPlace)
Mark whether OpOperand will be bufferized inplace.
static bool computeCanUseOpDominanceDueToBlocks(OpOperand *uRead, OpOperand *uWrite, const SetVector< Value > &definitions, OneShotAnalysisState &state)
Return true if op dominance can be used to rule out a read-after-write conflicts based on the orderin...
constexpr StringLiteral kInPlaceOperandsAttrName
Attribute marker to specify op operands that bufferize in-place.
static bool detectUnstructuredControlFlow(Operation *op)
A region with more than one block is unstructured control flow.
static bool isaTensor(Type t)
static void annotateNonWritableTensor(Value value)
Annotate IR with details about the detected non-writability conflict.
static SmallVector< Operation * > bottomUpFromTerminatorsHeuristic(Operation *op, const OneShotAnalysisState &state)
"Bottom-up from terminators" heuristic.
static bool canUseOpDominanceDueToRegions(OpOperand *uRead, OpOperand *uWrite, const SetVector< Value > &definitions, AnalysisState &state)
Return true if op dominance can be used to rule out a read-after-write conflicts based on the orderin...
static LogicalResult bufferizableInPlaceAnalysisImpl(OpOperand &operand, OneShotAnalysisState &state, const DominanceInfo &domInfo)
Determine if operand can be bufferized in-place.
constexpr StringLiteral kOpResultAliasSetAttrName
static bool cannotHappenAfter(Operation *a, Operation *b, const DominanceInfo &domInfo, OneShotAnalysisState &state, SmallPtrSet< Block *, 16 > extraBarriers={})
Return true if a cannot happen after b.
static bool matchesInsertDestination(const AnalysisState &state, OpOperand *opOperand, SubsetInsertionOpInterface subsetOp)
Return "true" if the given operand's value is originating from a subset that is equivalent to the sub...
static bool detectParallelRegions(Operation *op, const BufferizationOptions &options)
Return "true" if any allowed op has a parallel region.
static bool wouldCreateWriteToNonWritableBuffer(OpOperand &operand, OneShotAnalysisState &state, bool checkConsistencyOnly=false)
Return true if bufferizing operand inplace would create a write to a non-writable buffer.
static void annotateOpsWithAliasSets(Operation *op, const OneShotAnalysisState &state)
static void annotateOpsWithBufferizationMarkers(Operation *op, const OneShotAnalysisState &state)
Annotate the IR with the result of the analysis. For testing/debugging only.
static bool canUseOpDominanceDueToBlocks(OpOperand *uRead, OpOperand *uWrite, const SetVector< Value > &definitions, OneShotAnalysisState &state)
static bool wouldCreateReadAfterWriteInterference(OpOperand &operand, const DominanceInfo &domInfo, OneShotAnalysisState &state, bool checkConsistencyOnly=false)
Return true if bufferizing operand inplace would create a conflict.
constexpr StringLiteral kBbArgAliasSetAttrName
static void getAliasingInplaceWrites(DenseSet< OpOperand * > &res, Value root, const OneShotAnalysisState &state)
static bool areNonConflictingSubsets(OpOperand *uRead, OpOperand *uConflictingWrite, const AnalysisState &state)
Return "true" if the given "read" and potentially conflicting "write" are not conflicting due to thei...
static bool canUseOpDominance(OpOperand *uRead, OpOperand *uWrite, const SetVector< Value > &definitions, OneShotAnalysisState &state)
static void annotateConflict(OpOperand *uRead, OpOperand *uConflictingWrite, Value definition)
Annotate IR with details about the detected RaW conflict.
static bool hasEquivalentValueInReverseUseDefChain(AnalysisState &state, OpOperand *start, Value other)
Return 'true' if a tensor that is equivalent to other can be found in the reverse use-def chain of st...
static bool isInplaceMemoryWrite(OpOperand &opOperand, const OneShotAnalysisState &state)
Return true if opOperand has been decided to bufferize in-place.
static llvm::ManagedStatic< PassManagerOptions > options
#define MLIR_DEFINE_EXPLICIT_TYPE_ID(CLASS_NAME)
Definition TypeID.h:323
static Operation * getOwnerOfValue(Value value)
Cached CFG reachability to avoid repeated linear BFS traversals from Block->isReachable.
bool isReachable(Block *from, Block *to, const SmallPtrSetImpl< Block * > *barriers=nullptr)
canUseOpDominanceDueToBlocks walks enclosing regions and queries CFG reachability,...
bool getOrCompute(Block *readBlock, Block *writeBlock, ArrayRef< Block * > sortedDefBlocks, function_ref< bool()> compute)
Base class for generic analysis states.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
This class provides management for the lifetime of the state used when printing the IR.
Definition AsmState.h:542
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
succ_iterator succ_end()
Definition Block.h:279
succ_iterator succ_begin()
Definition Block.h:278
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
A class for computing basic dominance information.
Definition Dominance.h:143
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
IRValueT get() const
Return the current value being used by this operand.
This class helps build Operations.
Definition Builders.h:210
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
Set of flags used to control the behavior of the various IR print methods (e.g.
This is a value defined by a result of an operation.
Definition Value.h:454
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1169
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Attribute getDiscardableAttr(StringRef name)
Access a discardable attribute by name, returns a null Attribute if the discardable attribute does no...
Definition Operation.h:485
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Definition Operation.h:512
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
unsigned getNumOperands()
Definition Operation.h:371
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_type_range getResultTypes()
Definition Operation.h:453
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition Operation.h:288
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
result_range getOpResults()
Definition Operation.h:445
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
Operation * findAncestorOpInRegion(Operation &op)
Returns 'op' if 'op' lies in this region, or otherwise finds the ancestor of 'op' that lies in this r...
Definition Region.cpp:168
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:234
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
This class provides an efficient unique identifier for a specific C++ type.
Definition TypeID.h:107
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Definition Value.h:108
Type getType() const
Return the type of this value.
Definition Value.h:105
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
void printAsOperand(raw_ostream &os, AsmState &state) const
Print this value as if it were an operand.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A utility result that is used to signal how to proceed with an ongoing walk:
Definition WalkResult.h:29
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
bool wasInterrupted() const
Returns true if the walk was interrupted.
Definition WalkResult.h:51
static WalkResult interrupt()
Definition WalkResult.h:46
virtual ~Extension()
Base virtual destructor.
State for analysis-enabled bufferization.
void bufferizeOutOfPlace(OpOperand &operand)
Mark the given OpOperand as out-of-place.
bool isWritable(Value value) const
Return true if the buffer of the given tensor value is writable.
const SetVector< Value > & findDefinitionsCached(OpOperand *opOperand)
Find the definitions of the given operand's value or retrieve them from the cache.
bool isReachableCached(Block *from, Block *to, const llvm::SmallPtrSetImpl< Block * > *barriers=nullptr)
Return true if to is reachable from from without crossing barriers.
bool isInPlace(OpOperand &opOperand) const override
Return true if the given OpResult has been decided to bufferize inplace.
bool canUseOpDominanceDueToBlocksCached(Block *readBlock, Block *writeBlock, llvm::ArrayRef< Block * > defBlocks, function_ref< bool()> compute)
Cached canUseOpDominanceDueToBlocks.
LogicalResult analyzeOp(Operation *op, const DominanceInfo &domInfo)
Analyze the given op and its nested ops.
bool isValueWritten(Value value) const
Return true if the buffer of the given tensor value is written to.
void unionEquivalenceClasses(Value v1, Value v2)
Union the equivalence classes of v1 and v2.
void gatherUndefinedTensorUses(Operation *op)
Find all tensor values in the given operation that have undefined contents and store them in undefine...
void resetCache() override
Reset cached data structures.
const OneShotBufferizationOptions & getOptions() const
Return a reference to the BufferizationOptions.
LogicalResult analyzeSingleOp(Operation *op, const DominanceInfo &domInfo)
Analyze a single op (without nested ops).
void applyOnEquivalenceClass(Value v, function_ref< void(Value)> fun) const
Apply fun to all the members of the equivalence class of v.
bool mayHaveUnstructuredControlFlow() const
True if any region in the analyzed IR has more than one block.
bool hasUndefinedContents(OpOperand *opOperand) const override
Return true if the given tensor has undefined contents.
bool areNonConflictingSubsetsCached(OpOperand *uRead, OpOperand *uConflictingWrite)
Return whether uRead and uConflictingWrite are non-conflicting subsets, with caching.
bool mayHaveParallelRegions() const
True if the analyzed IR may contain a parallel region.
void bufferizeInPlace(OpOperand &operand)
Mark the given OpOperand as in-place and merge the results' and operand's aliasing sets.
void applyOnAliases(Value v, function_ref< void(Value)> fun) const
Apply fun to all aliases of v.
bool areEquivalentBufferizedValues(Value v1, Value v2) const override
Return true if v1 and v2 bufferize to equivalent buffers.
OneShotAnalysisState(Operation *op, const OneShotBufferizationOptions &options)
bool areAliasingBufferizedValues(Value v1, Value v2) const override
Return true if v1 and v2 may bufferize to aliasing buffers.
void unionAliasSets(Value v1, Value v2)
Union the alias sets of v1 and v2.
void createAliasInfoEntry(Value v)
Add a new entry for v in the aliasInfo and equivalentInfo.
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
LogicalResult bufferizeOp(Operation *op, const BufferizationOptions &options, BufferizationState &bufferizationState, BufferizationStatistics *statistics=nullptr)
Bufferize op and its nested ops that implement BufferizableOpInterface.
LogicalResult analyzeOp(Operation *op, OneShotAnalysisState &state, BufferizationStatistics *statistics=nullptr)
Analyze op and its nested ops.
LogicalResult insertTensorCopies(Operation *op, const OneShotBufferizationOptions &options, const BufferizationState &bufferizationState, BufferizationStatistics *statistics=nullptr)
Resolve RaW and other conflicts by inserting bufferization.alloc_tensor ops.
LogicalResult runOneShotBufferize(Operation *op, const OneShotBufferizationOptions &options, BufferizationState &state, BufferizationStatistics *statistics=nullptr)
Run One-Shot Bufferize on the given op: Analysis + Bufferization.
LogicalResult checkPreBufferizationAssumptions(Operation *op, const DominanceInfo &domInfo, OneShotAnalysisState &state)
Perform various checks on the input IR to see if it contains IR constructs that are unsupported by On...
Include the generated interface declarations.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
This iterator enumerates elements in "reverse" order.
Definition Iterators.h:29
Bufferization statistics for debugging.
Definition Bufferize.h:35
Options for analysis-enabled bufferization.
AnalysisHeuristic analysisHeuristic
The heuristic controls the order in which ops are traversed during the analysis.