MLIR 23.0.0git
BufferizableOpInterface.cpp
Go to the documentation of this file.
1//===- BufferizableOpInterface.cpp - Bufferizable Ops ---=----------------===//
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
15#include "mlir/IR/AsmState.h"
16#include "mlir/IR/Operation.h"
18#include "mlir/IR/Value.h"
20#include "llvm/ADT/ScopeExit.h"
21#include "llvm/ADT/SmallVectorExtras.h"
22
23//===----------------------------------------------------------------------===//
24// BufferizableOpInterface
25//===----------------------------------------------------------------------===//
26
27namespace mlir {
28namespace bufferization {
29
30#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.cpp.inc"
31
32} // namespace bufferization
33} // namespace mlir
34
35MLIR_DEFINE_EXPLICIT_TYPE_ID(mlir::bufferization::AnalysisState)
36
37#define DEBUG_TYPE "bufferizable-op-interface"
38
39using namespace mlir;
40using namespace bufferization;
41
42static bool isRepetitiveRegion(Region *region,
44 Operation *op = region->getParentOp();
45 if (auto bufferizableOp = options.dynCastBufferizableOp(op))
46 if (bufferizableOp.isRepetitiveRegion(region->getRegionNumber()))
47 return true;
48 return false;
49}
50
51Region *AnalysisState::getEnclosingRepetitiveRegion(
53 if (!op->getBlock())
54 return nullptr;
55 if (auto iter = enclosingRepetitiveRegionCache.find_as(op);
56 iter != enclosingRepetitiveRegionCache.end())
57 return iter->second;
58 return enclosingRepetitiveRegionCache[op] =
60}
61
62Region *AnalysisState::getEnclosingRepetitiveRegion(
63 Value value, const BufferizationOptions &options) {
64 if (auto iter = enclosingRepetitiveRegionCache.find_as(value);
65 iter != enclosingRepetitiveRegionCache.end())
66 return iter->second;
67
68 Region *region = value.getParentRegion();
69 // Collect all visited regions since we only know the repetitive region we
70 // want to map it to later on
71 SmallVector<Region *> visitedRegions;
72 while (region) {
73 visitedRegions.push_back(region);
74 if (isRepetitiveRegion(region, options))
75 break;
76 region = region->getParentRegion();
77 }
78 enclosingRepetitiveRegionCache[value] = region;
79 for (Region *r : visitedRegions)
80 enclosingRepetitiveRegionCache[r] = region;
81 return region;
82}
83
84Region *AnalysisState::getEnclosingRepetitiveRegion(
85 Block *block, const BufferizationOptions &options) {
86 if (auto iter = enclosingRepetitiveRegionCache.find_as(block);
87 iter != enclosingRepetitiveRegionCache.end())
88 return iter->second;
89
90 Region *region = block->getParent();
91 Operation *op = nullptr;
92 // Collect all visited regions since we only know the repetitive region we
93 // want to map it to later on
94 SmallVector<Region *> visitedRegions;
95 do {
96 op = region->getParentOp();
97 if (isRepetitiveRegion(region, options))
98 break;
99 } while ((region = op->getParentRegion()));
100
101 enclosingRepetitiveRegionCache[block] = region;
102 for (Region *r : visitedRegions)
103 enclosingRepetitiveRegionCache[r] = region;
104 return region;
105}
106
107bool AnalysisState::insideMutuallyExclusiveRegions(Operation *op0,
108 Operation *op1) {
109 auto key = std::make_pair(op0, op1);
110 if (auto iter = insideMutuallyExclusiveRegionsCache.find(key);
111 iter != insideMutuallyExclusiveRegionsCache.end())
112 return iter->second;
114 // Populate results for both orderings of the ops.
115 insideMutuallyExclusiveRegionsCache[key] = result;
116 insideMutuallyExclusiveRegionsCache[std::make_pair(op1, op0)] = result;
117 return result;
118}
119
120void AnalysisState::resetCache() {
121 enclosingRepetitiveRegionCache.clear();
122 insideMutuallyExclusiveRegionsCache.clear();
123}
124
125SymbolTableCollection &BufferizationState::getSymbolTables() {
126 return symbolTables;
127}
128
129SymbolTableCollection &BufferizationState::getSymbolTables() const {
130 return symbolTables;
131}
132
133Region *bufferization::getNextEnclosingRepetitiveRegion(
134 Region *region, const BufferizationOptions &options) {
135 assert(isRepetitiveRegion(region, options) && "expected repetitive region");
136 while ((region = region->getParentRegion())) {
137 if (isRepetitiveRegion(region, options))
138 break;
139 }
140 return region;
141}
142
143Region *bufferization::getParallelRegion(Region *region,
144 const BufferizationOptions &options) {
145 while (region) {
146 auto bufferizableOp = options.dynCastBufferizableOp(region->getParentOp());
147 if (bufferizableOp &&
148 bufferizableOp.isParallelRegion(region->getRegionNumber())) {
149 assert(isRepetitiveRegion(region, options) &&
150 "expected that all parallel regions are also repetitive regions");
151 return region;
152 }
153 region = region->getParentRegion();
154 }
155 return nullptr;
156}
157
158Operation *bufferization::getOwnerOfValue(Value value) {
159 if (auto opResult = llvm::dyn_cast<OpResult>(value))
160 return opResult.getDefiningOp();
161 return llvm::cast<BlockArgument>(value).getOwner()->getParentOp();
162}
163
164// TODO: Properly support with options, for now it is hardcoded to builtin
165// Tensor/MemRef types based approach
166/// Create an AllocTensorOp for the given shaped value. If `copy` is set, the
167/// shaped value is copied. Otherwise, a tensor with undefined contents is
168/// allocated.
169FailureOr<Value> bufferization::allocateTensorForShapedValue(
170 OpBuilder &b, Location loc, Value shapedValue,
171 const BufferizationOptions &options, const BufferizationState &state,
172 bool copy) {
174 if (llvm::isa<RankedTensorType>(shapedValue.getType())) {
175 tensor = shapedValue;
176 } else if (llvm::isa<MemRefType>(shapedValue.getType())) {
177 tensor = ToTensorOp::create(
178 b, loc, memref::getTensorTypeFromMemRefType(shapedValue.getType()),
179 shapedValue);
180 } else if (llvm::isa<UnrankedTensorType>(shapedValue.getType()) ||
181 llvm::isa<UnrankedMemRefType>(shapedValue.getType())) {
182 return getOwnerOfValue(shapedValue)
183 ->emitError("copying of unranked tensors is not implemented");
184 } else {
185 llvm_unreachable("expected RankedTensorType or MemRefType");
186 }
187 RankedTensorType tensorType = llvm::cast<RankedTensorType>(tensor.getType());
188 SmallVector<Value> dynamicSizes;
189 if (!copy) {
190 // Compute the dynamic part of the shape.
191 // First try to query the shape via ReifyRankedShapedTypeOpInterface.
192 bool reifiedShapes = false;
193 if (llvm::isa<RankedTensorType>(shapedValue.getType()) &&
194 llvm::isa<OpResult>(shapedValue)) {
196 if (succeeded(
197 reifyResultShapes(b, shapedValue.getDefiningOp(), resultDims))) {
198 reifiedShapes = true;
199 auto &shape =
200 resultDims[llvm::cast<OpResult>(shapedValue).getResultNumber()];
201 for (const auto &dim : enumerate(tensorType.getShape())) {
202 if (ShapedType::isDynamic(dim.value())) {
203 dynamicSizes.push_back(
204 getValueOrCreateConstantIndexOp(b, loc, shape[dim.index()]));
205 }
206 }
207 }
208 }
209
210 // If the shape could not be reified, create DimOps.
211 if (!reifiedShapes)
212 populateDynamicDimSizes(b, loc, tensor, dynamicSizes);
213 }
214
215 // Create AllocTensorOp.
216 auto allocTensorOp = AllocTensorOp::create(b, loc, tensorType, dynamicSizes,
217 copy ? tensor : Value());
218
219 // Add 'memory_space' attribute. Not needed if 'copy' operand is specified.
220 if (copy)
221 return allocTensorOp.getResult();
222 auto copyBufferType =
223 detail::asMemRefType(getBufferType(tensor, options, state));
224 if (failed(copyBufferType))
225 return failure();
226 std::optional<Attribute> memorySpace = copyBufferType->getMemorySpace();
227 if (!memorySpace)
228 memorySpace =
229 options.defaultMemorySpaceFn(cast<TensorLikeType>(tensorType));
230 if (memorySpace.has_value())
231 allocTensorOp.setMemorySpaceAttr(memorySpace.value());
232 return allocTensorOp.getResult();
233}
234
235// TODO: Properly support with options, for now it is hardcoded to builtin
236// Tensor/MemRef types based approach
237LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
238 RewriterBase &rewriter, const AnalysisState &analysisState,
239 const BufferizationState &bufferizationState) {
240 OpBuilder::InsertionGuard g(rewriter);
241 Operation *op = getOperation();
242 SmallVector<OpOperand *> outOfPlaceOpOperands;
243 DenseSet<OpOperand *> copiedOpOperands;
244 SmallVector<Value> outOfPlaceValues;
245 DenseSet<Value> copiedOpValues;
246
247 // Find all out-of-place OpOperands.
248 for (OpOperand &opOperand : op->getOpOperands()) {
249 Type operandType = opOperand.get().getType();
250 if (!llvm::isa<TensorType>(operandType))
251 continue;
252 if (analysisState.isInPlace(opOperand))
253 continue;
254 if (llvm::isa<UnrankedTensorType>(operandType))
255 return op->emitError("copying of unranked tensors is not implemented");
256
257 AliasingValueList aliasingValues =
258 analysisState.getAliasingValues(opOperand);
259 if (aliasingValues.getNumAliases() == 1 &&
260 isa<OpResult>(aliasingValues.getAliases()[0].value) &&
261 !analysisState.bufferizesToMemoryWrite(opOperand) &&
262 analysisState
263 .getAliasingOpOperands(aliasingValues.getAliases()[0].value)
264 .getNumAliases() == 1 &&
265 !isa<UnrankedTensorType>(
266 aliasingValues.getAliases()[0].value.getType())) {
267 // The op itself does not write but may create exactly one alias. Instead
268 // of copying the OpOperand, copy the OpResult. The OpResult can sometimes
269 // be smaller than the OpOperand (e.g., in the case of an extract_slice,
270 // where the result is usually a smaller part of the source). Do not apply
271 // this optimization if the OpResult is an unranked tensor (because those
272 // cannot be copied at the moment).
273 Value value = aliasingValues.getAliases()[0].value;
274 outOfPlaceValues.push_back(value);
275 if (!analysisState.canOmitTensorCopy(opOperand))
276 copiedOpValues.insert(value);
277 } else {
278 // In all other cases, make a copy of the OpOperand.
279 outOfPlaceOpOperands.push_back(&opOperand);
280 if (!analysisState.canOmitTensorCopy(opOperand))
281 copiedOpOperands.insert(&opOperand);
282 }
283 }
284
285 // Insert copies of OpOperands.
286 rewriter.setInsertionPoint(op);
287 for (OpOperand *opOperand : outOfPlaceOpOperands) {
288 FailureOr<Value> copy = allocateTensorForShapedValue(
289 rewriter, op->getLoc(), opOperand->get(), analysisState.getOptions(),
290 bufferizationState, copiedOpOperands.contains(opOperand));
291 if (failed(copy))
292 return failure();
293 rewriter.modifyOpInPlace(op, [&]() { opOperand->set(*copy); });
294 }
295
296 // Insert copies of Values.
297 rewriter.setInsertionPointAfter(op);
298 for (Value value : outOfPlaceValues) {
299 FailureOr<Value> copy = allocateTensorForShapedValue(
300 rewriter, op->getLoc(), value, analysisState.getOptions(),
301 bufferizationState, copiedOpValues.count(value));
302 if (failed(copy))
303 return failure();
304 SmallVector<OpOperand *> uses = llvm::map_to_vector(
305 value.getUses(), [](OpOperand &use) { return &use; });
306 for (OpOperand *use : uses) {
307 // Do not update the alloc_tensor op that we just created.
308 if (use->getOwner() == copy->getDefiningOp())
309 continue;
310 // tensor.dim ops may have been created to be used as alloc_tensor op
311 // dynamic extents. Do not update these either.
312 if (isa<tensor::DimOp>(use->getOwner()))
313 continue;
314 rewriter.modifyOpInPlace(use->getOwner(), [&]() { use->set(*copy); });
315 }
316 }
317
318 return success();
319}
320
321//===----------------------------------------------------------------------===//
322// OpFilter
323//===----------------------------------------------------------------------===//
324
325bool OpFilter::isOpAllowed(Operation *op) const {
326 // All other ops: Allow/disallow according to filter.
327 bool isAllowed = !hasAllowRule();
328 for (const Entry &entry : entries) {
329 bool filterResult = entry.fn(op);
330 switch (entry.type) {
331 case Entry::ALLOW:
332 isAllowed |= filterResult;
333 break;
334 case Entry::DENY:
335 if (filterResult)
336 // DENY filter matches. This op is no allowed. (Even if other ALLOW
337 // filters may match.)
338 return false;
339 };
340 }
341 return isAllowed;
342}
343
344//===----------------------------------------------------------------------===//
345// BufferizationOptions
346//===----------------------------------------------------------------------===//
347
348namespace {
349
350/// Default function arg type converter: Use a fully dynamic layout map.
351BufferLikeType
352defaultFunctionArgTypeConverter(TensorLikeType type, Attribute memorySpace,
353 func::FuncOp funcOp,
354 const BufferizationOptions &options) {
355 if (auto tensorType = mlir::dyn_cast<TensorType>(type)) {
356 return cast<BufferLikeType>(
357 getMemRefTypeWithFullyDynamicLayout(tensorType, memorySpace));
358 }
359
360 // If not builtin, fallback to unknown type conversion.
361 return options.unknownTypeConverterFn(type, memorySpace, options);
362}
363/// Default unknown type converter: Use a fully dynamic layout map.
364BufferLikeType
365defaultUnknownTypeConverter(TensorLikeType tensorType, Attribute memorySpace,
366 const BufferizationOptions &options) {
367 return cast<BufferLikeType>(getMemRefTypeWithFullyDynamicLayout(
368 cast<TensorType>(tensorType), memorySpace));
369}
370
371} // namespace
372
373// Default constructor for BufferizationOptions.
374BufferizationOptions::BufferizationOptions()
375 : functionArgTypeConverterFn(defaultFunctionArgTypeConverter),
376 unknownTypeConverterFn(defaultUnknownTypeConverter) {}
377
378bool BufferizationOptions::isOpAllowed(Operation *op) const {
379 // Special case: If function boundary bufferization is deactivated, do not
380 // allow ops that belong to the `func` dialect.
381 bool isFuncBoundaryOp = isa_and_nonnull<func::FuncDialect>(op->getDialect());
382 if (!bufferizeFunctionBoundaries && isFuncBoundaryOp)
383 return false;
384
385 return opFilter.isOpAllowed(op);
386}
387
388BufferizableOpInterface
389BufferizationOptions::dynCastBufferizableOp(Operation *op) const {
390 if (!isOpAllowed(op))
391 return nullptr;
392 auto bufferizableOp = dyn_cast<BufferizableOpInterface>(op);
393 if (!bufferizableOp)
394 return nullptr;
395 return bufferizableOp;
396}
397
398BufferizableOpInterface
399BufferizationOptions::dynCastBufferizableOp(Value value) const {
400 return dynCastBufferizableOp(getOwnerOfValue(value));
401}
402
403void BufferizationOptions::setFunctionBoundaryTypeConversion(
404 LayoutMapOption layoutMapOption) {
405 functionArgTypeConverterFn = [=](TensorLikeType type, Attribute memorySpace,
406 func::FuncOp funcOp,
407 const BufferizationOptions &options) {
408 if (auto tensorType = mlir::dyn_cast<TensorType>(type)) {
409 if (layoutMapOption == LayoutMapOption::IdentityLayoutMap)
410 return cast<BufferLikeType>(
411 bufferization::getMemRefTypeWithStaticIdentityLayout(tensorType,
412 memorySpace));
413 return cast<BufferLikeType>(
414 bufferization::getMemRefTypeWithFullyDynamicLayout(tensorType,
415 memorySpace));
416 }
417
418 // If not builtin, fallback to unknown type conversion.
419 return options.unknownTypeConverterFn(type, memorySpace, options);
420 };
421 inferFunctionResultLayout =
422 layoutMapOption == LayoutMapOption::InferLayoutMap;
423}
424
425//===----------------------------------------------------------------------===//
426// Helper functions for BufferizableOpInterface
427//===----------------------------------------------------------------------===//
428
430 if (auto bbArg = llvm::dyn_cast<BlockArgument>(value)) {
431 b.setInsertionPointToStart(bbArg.getOwner());
432 } else {
433 b.setInsertionPointAfter(value.getDefiningOp());
434 }
435}
436
437/// Determine which OpOperand* will alias with `value` if the op is bufferized
438/// in place. Return all tensor OpOperand* if the op is not bufferizable.
439AliasingOpOperandList AnalysisState::getAliasingOpOperands(Value value) const {
440 if (Operation *op = getOwnerOfValue(value))
441 if (auto bufferizableOp = getOptions().dynCastBufferizableOp(op))
442 return bufferizableOp.getAliasingOpOperands(value, *this);
443
444 // The op is not bufferizable.
445 return detail::unknownGetAliasingOpOperands(value);
446}
447
448/// Determine which Values will alias with `opOperand` if the op is bufferized
449/// in place. Return all tensor Values if the op is not bufferizable.
450AliasingValueList AnalysisState::getAliasingValues(OpOperand &opOperand) const {
451 if (auto bufferizableOp =
452 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
453 return bufferizableOp.getAliasingValues(opOperand, *this);
454
455 // The op is not bufferizable.
456 return detail::unknownGetAliasingValues(opOperand);
457}
458
459/// Return true if `opOperand` bufferizes to a memory read. Return `true` if the
460/// op is not bufferizable.
461bool AnalysisState::bufferizesToMemoryRead(OpOperand &opOperand) const {
462 if (auto bufferizableOp =
463 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
464 return bufferizableOp.bufferizesToMemoryRead(opOperand, *this);
465
466 // Unknown op that returns a tensor. The inplace analysis does not support it.
467 // Conservatively return true.
468 return true;
469}
470
471/// Return true if `opOperand` bufferizes to a memory write. Return
472/// `true` if the op is not bufferizable.
473bool AnalysisState::bufferizesToMemoryWrite(OpOperand &opOperand) const {
474 if (auto bufferizableOp =
475 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
476 return bufferizableOp.bufferizesToMemoryWrite(opOperand, *this);
477
478 // Unknown op that returns a tensor. The inplace analysis does not support it.
479 // Conservatively return true.
480 return true;
481}
482
483/// Return true if `opOperand` does neither read nor write but bufferizes to an
484/// alias. Return false if the op is not bufferizable.
485bool AnalysisState::bufferizesToAliasOnly(OpOperand &opOperand) const {
486 if (auto bufferizableOp =
487 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
488 return bufferizableOp.bufferizesToAliasOnly(opOperand, *this);
489
490 // Unknown op that returns a tensor. The inplace analysis does not support it.
491 // Conservatively return false.
492 return false;
493}
494
495bool AnalysisState::bufferizesToMemoryWrite(Value value) const {
496 auto opResult = llvm::dyn_cast<OpResult>(value);
497 if (!opResult)
498 return true;
499 auto bufferizableOp = getOptions().dynCastBufferizableOp(value);
500 if (!bufferizableOp)
501 return true;
502 return bufferizableOp.resultBufferizesToMemoryWrite(opResult, *this);
503}
504
505/// Return true if the given value is read by an op that bufferizes to a memory
506/// read. Also takes into account ops that create an alias but do not read by
507/// themselves (e.g., ExtractSliceOp).
508bool AnalysisState::isValueRead(Value value) const {
509 assert(llvm::isa<TensorLikeType>(value.getType()) &&
510 "expected TensorLikeType");
511 SmallVector<OpOperand *> workingSet;
512 DenseSet<OpOperand *> visited;
513 for (OpOperand &use : value.getUses())
514 workingSet.push_back(&use);
515
516 while (!workingSet.empty()) {
517 OpOperand *uMaybeReading = workingSet.pop_back_val();
518 if (!visited.insert(uMaybeReading).second)
519 continue;
520
521 // Skip over all ops that neither read nor write (but create an alias).
522 if (bufferizesToAliasOnly(*uMaybeReading))
523 for (AliasingValue alias : getAliasingValues(*uMaybeReading))
524 for (OpOperand &use : alias.value.getUses())
525 workingSet.push_back(&use);
526 if (bufferizesToMemoryRead(*uMaybeReading))
527 return true;
528 }
529
530 return false;
531}
532
533// Starting from `opOperand`, follow the use-def chain in reverse, always
534// selecting the aliasing OpOperands. Find and return Values for which
535// `condition` evaluates to true. Uses of such matching Values are not
536// traversed any further, the visited aliasing opOperands will be preserved
537// through `visitedOpOperands`.
538llvm::SetVector<Value> AnalysisState::findValueInReverseUseDefChain(
539 OpOperand *opOperand, llvm::function_ref<bool(Value)> condition,
540 TraversalConfig config,
541 llvm::DenseSet<OpOperand *> *visitedOpOperands) const {
542 llvm::DenseSet<Value> visited;
543 llvm::SetVector<Value> result, workingSet;
544 workingSet.insert(opOperand->get());
545
546 if (visitedOpOperands)
547 visitedOpOperands->insert(opOperand);
548
549 while (!workingSet.empty()) {
550 Value value = workingSet.pop_back_val();
551
552 if (!config.revisitAlreadyVisitedValues && visited.contains(value)) {
553 // Stop traversal if value was already visited.
554 if (config.alwaysIncludeLeaves)
555 result.insert(value);
556 continue;
557 }
558 visited.insert(value);
559
560 if (condition(value)) {
561 result.insert(value);
562 continue;
563 }
564
565 if (!config.followUnknownOps && !options.dynCastBufferizableOp(value)) {
566 // Stop iterating if `followUnknownOps` is unset and the op is either
567 // not bufferizable or excluded in the OpFilter.
568 if (config.alwaysIncludeLeaves)
569 result.insert(value);
570 continue;
571 }
572
573 AliasingOpOperandList aliases = getAliasingOpOperands(value);
574 if (aliases.getNumAliases() == 0) {
575 // The traversal ends naturally if there are no more OpOperands that
576 // could be followed.
577 if (config.alwaysIncludeLeaves)
578 result.insert(value);
579 continue;
580 }
581
582 for (AliasingOpOperand a : aliases) {
583 if (config.followEquivalentOnly &&
584 a.relation != BufferRelation::Equivalent) {
585 // Stop iterating if `followEquivalentOnly` is set but the alias is not
586 // equivalent.
587 if (config.alwaysIncludeLeaves)
588 result.insert(value);
589 continue;
590 }
591
592 if (config.followInPlaceOnly && !isInPlace(*a.opOperand)) {
593 // Stop iterating if `followInPlaceOnly` is set but the alias is
594 // out-of-place.
595 if (config.alwaysIncludeLeaves)
596 result.insert(value);
597 continue;
598 }
599
600 if (config.followSameTypeOrCastsOnly &&
601 a.opOperand->get().getType() != value.getType() &&
602 !value.getDefiningOp<CastOpInterface>()) {
603 // Stop iterating if `followSameTypeOrCastsOnly` is set but the alias is
604 // has a different type and the op is not a cast.
605 if (config.alwaysIncludeLeaves)
606 result.insert(value);
607 continue;
608 }
609
610 workingSet.insert(a.opOperand->get());
611 if (visitedOpOperands)
612 visitedOpOperands->insert(a.opOperand);
613 }
614 }
615
616 return result;
617}
618
619// Find the values that define the contents of the given operand's value.
620llvm::SetVector<Value>
621AnalysisState::findDefinitions(OpOperand *opOperand) const {
622 TraversalConfig config;
623 config.alwaysIncludeLeaves = false;
624 return findValueInReverseUseDefChain(
625 opOperand, [&](Value v) { return this->bufferizesToMemoryWrite(v); },
626 config);
627}
628
629AnalysisState::AnalysisState(const BufferizationOptions &options)
631
633 : options(options), type(type) {
634 for (const BufferizationOptions::AnalysisStateInitFn &fn :
635 options.stateInitializers)
636 fn(*this);
637}
638
639bool AnalysisState::canOmitTensorCopy(OpOperand &opOperand) const {
640 // Do not copy if the tensor has undefined contents.
641 if (hasUndefinedContents(&opOperand))
642 return true;
643
644 // Do not copy if the buffer of the tensor is entirely overwritten (with
645 // values that do not depend on the old tensor).
646 if (bufferizesToMemoryWrite(opOperand) && !bufferizesToMemoryRead(opOperand))
647 return true;
648
649 // Do not copy if the tensor is never read.
650 AliasingValueList aliases = getAliasingValues(opOperand);
651 if (!bufferizesToMemoryRead(opOperand) &&
652 llvm::none_of(aliases,
653 [&](AliasingValue a) { return isValueRead(a.value); }))
654 return true;
655
656 // Default: Cannot omit the copy.
657 return false;
658}
659
660bool AnalysisState::isInPlace(OpOperand &opOperand) const {
661 // ToBufferOps are always in-place.
662 if (isa<ToBufferOp>(opOperand.getOwner()))
663 return true;
664
665 // In the absence of analysis information, OpOperands that bufferize to a
666 // memory write are out-of-place, i.e., an alloc and copy is inserted.
667 return !bufferizesToMemoryWrite(opOperand);
668}
669
670bool AnalysisState::areEquivalentBufferizedValues(Value v1, Value v2) const {
671 // In the absence of analysis information, we do not know if the values are
672 // equivalent. The conservative answer is "false".
673 return false;
674}
675
676bool AnalysisState::areAliasingBufferizedValues(Value v1, Value v2) const {
677 // In the absence of analysis information, we do not know if the values may be
678 // aliasing. The conservative answer is "true".
679 return true;
680}
681
682bool AnalysisState::hasUndefinedContents(OpOperand *opOperand) const {
683 // In the absence of analysis information, the conservative answer is "false".
684 return false;
685}
686
687FailureOr<Value> bufferization::getBuffer(RewriterBase &rewriter, Value value,
688 const BufferizationOptions &options,
689 const BufferizationState &state) {
690#ifndef NDEBUG
691 auto tensorType = llvm::dyn_cast<TensorLikeType>(value.getType());
692 assert(tensorType && "unexpected non-tensor type");
693#endif // NDEBUG
694
695 // Replace "%t = to_tensor %m" with %m.
696 if (auto toTensorOp = value.getDefiningOp<bufferization::ToTensorOp>())
697 return toTensorOp.getBuffer();
698
699 // Insert to_buffer op.
700 OpBuilder::InsertionGuard g(rewriter);
701 setInsertionPointAfter(rewriter, value);
702 FailureOr<BufferLikeType> bufferType = getBufferType(value, options, state);
703 if (failed(bufferType))
704 return failure();
705
706 return bufferization::ToBufferOp::create(rewriter, value.getLoc(),
707 *bufferType, value)
708 .getResult();
709}
710
711/// Return the buffer type for a given Value (tensor) after bufferization.
712FailureOr<BufferLikeType>
713bufferization::getBufferType(Value value, const BufferizationOptions &options,
714 const BufferizationState &state) {
715 SmallVector<Value> invocationStack;
716 return getBufferType(value, options, state, invocationStack);
717}
718
719/// Return the buffer type for a given Value (tensor) after bufferization.
720FailureOr<BufferLikeType>
721bufferization::getBufferType(Value value, const BufferizationOptions &options,
722 const BufferizationState &state,
723 SmallVector<Value> &invocationStack) {
724 assert(llvm::isa<TensorLikeType>(value.getType()) &&
725 "unexpected non-tensor type");
726 invocationStack.push_back(value);
727 llvm::scope_exit popFromStack([&]() { invocationStack.pop_back(); });
728
729 // Try querying BufferizableOpInterface.
730 Operation *op = getOwnerOfValue(value);
731 auto bufferizableOp = options.dynCastBufferizableOp(op);
732 if (bufferizableOp)
733 return bufferizableOp.getBufferType(value, options, state, invocationStack);
734
735 // Op is not bufferizable.
736 auto memSpace =
737 options.defaultMemorySpaceFn(cast<TensorLikeType>(value.getType()));
738 if (!memSpace.has_value())
739 return op->emitError("could not infer memory space");
740
741 return options.unknownTypeConverterFn(cast<TensorLikeType>(value.getType()),
742 *memSpace, options);
743}
744
745bool bufferization::hasTensorSemantics(Operation *op) {
746 if (auto bufferizableOp = dyn_cast<BufferizableOpInterface>(op))
747 return bufferizableOp.hasTensorSemantics();
748 return detail::defaultHasTensorSemantics(op);
749}
750
751void bufferization::replaceOpWithBufferizedValues(RewriterBase &rewriter,
752 Operation *op,
753 ValueRange values) {
754 assert(values.size() == op->getNumResults() &&
755 "expected one value per OpResult");
756 OpBuilder::InsertionGuard g(rewriter);
757
758 // Replace all OpResults with the given values.
759 SmallVector<Value> replacements;
760 for (OpResult opResult : op->getOpResults()) {
761 Value replacement = values[opResult.getResultNumber()];
762 if (llvm::isa<TensorLikeType>(opResult.getType())) {
763 // The OpResult is a tensor. Such values are replaced with memrefs during
764 // bufferization.
765 assert(llvm::isa<BufferLikeType>(replacement.getType()) &&
766 "tensor op result should be replaced with a buffer value");
767 // The existing uses of the OpResult still expect a tensor. Insert a
768 // ToTensorOp. Throughout bufferization, this ToTensorOp will gradually
769 // loose all of its users and eventually DCE away.
770 rewriter.setInsertionPointAfter(op);
771 replacement = bufferization::ToTensorOp::create(
772 rewriter, replacement.getLoc(), opResult.getType(), replacement);
773 }
774 replacements.push_back(replacement);
775 }
776
777 rewriter.replaceOp(op, replacements);
778}
779
780//===----------------------------------------------------------------------===//
781// Bufferization-specific scoped alloc insertion support.
782//===----------------------------------------------------------------------===//
783
784/// Create a memref allocation with the given type and dynamic extents.
785FailureOr<Value> BufferizationOptions::createAlloc(OpBuilder &b, Location loc,
786 MemRefType type,
787 ValueRange dynShape) const {
788 if (allocationFn)
789 return (*allocationFn)(b, loc, type, dynShape, bufferAlignment);
790
791 // Default bufferallocation via AllocOp.
792 if (bufferAlignment != 0)
793 return memref::AllocOp::create(b, loc, type, dynShape,
794 b.getI64IntegerAttr(bufferAlignment))
795 .getResult();
796 return memref::AllocOp::create(b, loc, type, dynShape).getResult();
797}
798
799/// Create a memory copy between two memref buffers.
800LogicalResult BufferizationOptions::createMemCpy(OpBuilder &b, Location loc,
801 Value from, Value to) const {
802 if (memCpyFn)
803 return (*memCpyFn)(b, loc, from, to);
804
805 memref::CopyOp::create(b, loc, from, to);
806 return success();
807}
808
809//===----------------------------------------------------------------------===//
810// Bufferization-specific IRMapping support with debugging.
811//===----------------------------------------------------------------------===//
812
814bufferization::getMemRefTypeWithFullyDynamicLayout(TensorType tensorType,
815 Attribute memorySpace) {
816 // Case 1: Unranked memref type.
817 if (auto unrankedTensorType =
818 llvm::dyn_cast<UnrankedTensorType>(tensorType)) {
819 return UnrankedMemRefType::get(unrankedTensorType.getElementType(),
820 memorySpace);
821 }
822
823 // Case 2: Ranked memref type.
824 auto rankedTensorType = llvm::cast<RankedTensorType>(tensorType);
825 int64_t dynamicOffset = ShapedType::kDynamic;
826 SmallVector<int64_t> dynamicStrides(rankedTensorType.getRank(),
827 ShapedType::kDynamic);
828 auto stridedLayout = StridedLayoutAttr::get(tensorType.getContext(),
829 dynamicOffset, dynamicStrides);
830 return MemRefType::get(rankedTensorType.getShape(),
831 rankedTensorType.getElementType(), stridedLayout,
832 memorySpace);
833}
834
835/// Return a MemRef type with a static identity layout (i.e., no layout map). If
836/// the given tensor type is unranked, return an unranked MemRef type.
838bufferization::getMemRefTypeWithStaticIdentityLayout(TensorType tensorType,
839 Attribute memorySpace) {
840 // Case 1: Unranked memref type.
841 if (auto unrankedTensorType =
842 llvm::dyn_cast<UnrankedTensorType>(tensorType)) {
843 return UnrankedMemRefType::get(unrankedTensorType.getElementType(),
844 memorySpace);
845 }
846
847 // Case 2: Ranked memref type.
848 auto rankedTensorType = llvm::cast<RankedTensorType>(tensorType);
849 MemRefLayoutAttrInterface layout = {};
850 return MemRefType::get(rankedTensorType.getShape(),
851 rankedTensorType.getElementType(), layout,
852 memorySpace);
853}
854
855//===----------------------------------------------------------------------===//
856// Default implementations of interface methods
857//===----------------------------------------------------------------------===//
858
859bool bufferization::detail::defaultResultBufferizesToMemoryWrite(
860 OpResult opResult, const AnalysisState &state) {
861 auto bufferizableOp = cast<BufferizableOpInterface>(opResult.getDefiningOp());
862 AliasingOpOperandList opOperands =
863 bufferizableOp.getAliasingOpOperands(opResult, state);
864
865 // Case 1: OpResults that have no aliasing OpOperand usually bufferize to
866 // memory writes.
867 if (opOperands.getAliases().empty())
868 return true;
869
870 // Case 2: If an aliasing OpOperand bufferizes to a memory write, the OpResult
871 // may bufferize to a memory write.
872 if (llvm::any_of(opOperands, [&](AliasingOpOperand alias) {
873 return state.bufferizesToMemoryWrite(*alias.opOperand);
874 }))
875 return true;
876
877 // Case 3: Check if a nested aliasing OpOperand value bufferizes to a memory
878 // write. (Or: The reverse SSA use-def chain ends inside the reigon.) In that
879 // case, the OpResult bufferizes to a memory write. E.g.:
880 //
881 // %0 = "some_writing_op" : tensor<?xf32>
882 // %r = scf.if ... -> tensor<?xf32> {
883 // scf.yield %0 : tensor<?xf32>
884 // } else {
885 // %1 = "another_writing_op"(%0) : tensor<?xf32>
886 // scf.yield %1 : tensor<?xf32>
887 // }
888 // "some_reading_op"(%r)
889 //
890 // %r bufferizes to a memory write because an aliasing OpOperand value (%1)
891 // bufferizes to a memory write and the defining op is inside the scf.if.
892 //
893 // Note: This treatment of surrouding ops is useful for ops that have a
894 // region but no OpOperand such as scf.if or scf.execute_region. It simplifies
895 // the analysis considerably.
896 //
897 // "another_writing_op" in the above example should be able to bufferize
898 // inplace in the absence of another read of %0. However, if the scf.if op
899 // would not be considered a "write", the analysis would detect the
900 // following conflict:
901 //
902 // * read = some_reading_op
903 // * lastWrite = %0 (Note: The last write of %r would be a set: {%0, %1}.)
904 // * conflictingWrite = %1
905 //
906 auto isMemoryWriteInsideOp = [&](Value v) {
907 Operation *op = getOwnerOfValue(v);
908 if (!opResult.getDefiningOp()->isAncestor(op))
909 return false;
910 return state.bufferizesToMemoryWrite(v);
911 };
912 TraversalConfig config;
913 config.alwaysIncludeLeaves = false;
914 for (AliasingOpOperand alias : opOperands) {
915 if (!state
916 .findValueInReverseUseDefChain(alias.opOperand,
917 isMemoryWriteInsideOp, config)
918 .empty())
919 return true;
920 }
921 return false;
922}
923
924// Compute the AliasingOpOperandList for a given Value based on
925// getAliasingValues.
926AliasingOpOperandList bufferization::detail::defaultGetAliasingOpOperands(
927 Value value, const AnalysisState &state) {
928 Operation *op = getOwnerOfValue(value);
930 for (OpOperand &opOperand : op->getOpOperands()) {
931 if (!llvm::isa<TensorLikeType>(opOperand.get().getType()))
932 continue;
933 AliasingValueList aliasingValues = state.getAliasingValues(opOperand);
934 for (const auto &it : aliasingValues)
935 if (it.value == value)
936 result.emplace_back(&opOperand, it.relation, it.isDefinite);
937 }
938 return AliasingOpOperandList(std::move(result));
939}
940
941FailureOr<BufferLikeType> bufferization::detail::defaultGetBufferType(
942 Value value, const BufferizationOptions &options,
943 const BufferizationState &bufferizationState,
944 SmallVector<Value> &invocationStack) {
945 assert(llvm::isa<TensorType>(value.getType()) && "expected tensor type");
946 auto tensorType = cast<TensorType>(value.getType());
947
948 auto elementType = tensorType.getElementType();
949
950 if (!BaseMemRefType::isValidElementType(elementType))
951 return getOwnerOfValue(value)->emitError()
952 << "cannot bufferize value of type " << tensorType
953 << ": element type " << elementType
954 << " is not a valid memref element type";
955
956 // No further analysis is possible for a block argument.
957 if (llvm::isa<BlockArgument>(value)) {
958 return options.unknownTypeConverterFn(cast<TensorLikeType>(tensorType),
959 /*memorySpace=*/nullptr, options);
960 }
961
962 // Value is an OpResult.
963 Operation *op = getOwnerOfValue(value);
964 auto opResult = llvm::cast<OpResult>(value);
965 AnalysisState analysisState(options);
966 AliasingOpOperandList aliases = analysisState.getAliasingOpOperands(opResult);
967 if (aliases.getNumAliases() > 0 &&
968 aliases.getAliases()[0].relation == BufferRelation::Equivalent) {
969 // If the OpResult has an equivalent OpOperand, both OpResult and
970 // OpOperand bufferize to the exact same buffer type.
971 Value equivalentOperand = aliases.getAliases().front().opOperand->get();
972 return getBufferType(equivalentOperand, options, bufferizationState,
973 invocationStack);
974 }
975
976 // If we do not know the memory space and there is no default memory space,
977 // report a failure.
978 auto memSpace =
979 options.defaultMemorySpaceFn(cast<TensorLikeType>(tensorType));
980 if (!memSpace.has_value())
981 return op->emitError("could not infer memory space");
982
983 return options.unknownTypeConverterFn(cast<TensorLikeType>(tensorType),
984 *memSpace, options);
985}
986
987bool bufferization::detail::defaultIsRepetitiveRegion(
988 BufferizableOpInterface bufferizableOp, unsigned index) {
989 assert(index < bufferizableOp->getNumRegions() && "invalid region index");
990 auto regionInterface =
991 dyn_cast<RegionBranchOpInterface>(bufferizableOp.getOperation());
992 if (!regionInterface)
993 return false;
994 return regionInterface.isRepetitiveRegion(index);
995}
996
997AliasingOpOperandList
998bufferization::detail::unknownGetAliasingOpOperands(Value value) {
999 // TODO: Take into account successor blocks.
1000 // No aliasing in case of non-entry blocks.
1001 if (auto bbArg = dyn_cast<BlockArgument>(value))
1002 if (bbArg.getOwner() != &bbArg.getOwner()->getParent()->getBlocks().front())
1003 return {};
1004
1005 // Unknown op: Conservatively assume that each OpResult may alias with every
1006 // OpOperand. In addition, each block argument of an entry block may alias
1007 // with every OpOperand.
1008 AliasingOpOperandList r;
1009 for (OpOperand &operand : value.getDefiningOp()->getOpOperands())
1010 if (isa<TensorLikeType>(operand.get().getType()))
1011 r.addAlias({&operand, BufferRelation::Unknown, /*isDefinite=*/false});
1012 return r;
1013}
1014
1015AliasingValueList
1016bufferization::detail::unknownGetAliasingValues(OpOperand &opOperand) {
1017 // TODO: Take into account successor blocks.
1018 // Unknown op: Conservatively assume that each OpResult may alias with every
1019 // OpOperand. In addition, each block argument of an entry block may alias
1020 // with every OpOperand.
1021 AliasingValueList r;
1022 for (OpResult result : opOperand.getOwner()->getOpResults())
1023 if (llvm::isa<TensorLikeType>(result.getType()))
1024 r.addAlias({result, BufferRelation::Unknown, /*isDefinite=*/false});
1025 for (Region &region : opOperand.getOwner()->getRegions())
1026 if (!region.getBlocks().empty())
1027 for (BlockArgument bbArg : region.getBlocks().front().getArguments())
1028 if (isa<TensorLikeType>(bbArg.getType()))
1029 r.addAlias({bbArg, BufferRelation::Unknown, /*isDefinite=*/false});
1030 return r;
1031}
1032
1033bool bufferization::detail::defaultHasTensorSemantics(Operation *op) {
1034 auto isaTensor = [](Type t) { return isa<TensorLikeType>(t); };
1035 bool hasTensorBlockArgument = any_of(op->getRegions(), [&](Region &r) {
1036 return any_of(r.getBlocks(), [&](Block &b) {
1037 return any_of(b.getArguments(), [&](BlockArgument bbArg) {
1038 return isaTensor(bbArg.getType());
1039 });
1040 });
1041 });
1042 if (hasTensorBlockArgument)
1043 return true;
1044
1045 if (any_of(op->getResultTypes(), isaTensor))
1046 return true;
1047 return any_of(op->getOperandTypes(), isaTensor);
1048}
1049
1050FailureOr<BaseMemRefType>
1051bufferization::detail::asMemRefType(FailureOr<BufferLikeType> bufferType) {
1052 if (failed(bufferType))
1053 return failure();
1054 return cast<BaseMemRefType>(*bufferType);
1055}
1056
1057bool bufferization::detail::typesMatchAfterBufferization(Operation &op,
1058 Value tensor,
1059 Value buffer) {
1060 return mlir::succeeded(
1061 cast<TensorLikeType>(tensor.getType())
1062 .verifyCompatibleBufferType(cast<BufferLikeType>(buffer.getType()),
1063 [&]() { return op.emitError(); }));
1064}
return success()
static void setInsertionPointAfter(OpBuilder &b, Value value)
static bool isRepetitiveRegion(Region *region, const BufferizationOptions &options)
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
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 the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static bool isaTensor(Type t)
static llvm::ManagedStatic< PassManagerOptions > options
static RankedTensorType getBufferType(const SparseTensorType &stt, bool needTmpCOO)
#define MLIR_DEFINE_EXPLICIT_TYPE_ID(CLASS_NAME)
Definition TypeID.h:323
static Operation * getOwnerOfValue(Value value)
Base class for generic analysis states.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class provides a shared interface for ranked and unranked memref types.
static bool isValidElementType(Type type)
Return true if the specified element type is ok in a memref.
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
IRValueT get() const
Return the current value being used by this operand.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:350
This class helps build Operations.
Definition Builders.h:209
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:414
This class represents an operand of an operation.
Definition Value.h:254
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:237
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
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
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
operand_type_range getOperandTypes()
Definition Operation.h:422
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:702
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
result_range getOpResults()
Definition Operation.h:445
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
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
unsigned getRegionNumber()
Return the number of this region in the parent operation.
Definition Region.cpp:62
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:200
BlockListType & getBlocks()
Definition Region.h:45
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
This class represents a collection of SymbolTables.
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
Type getElementType() const
Returns the element type of this tensor type.
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
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
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
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
void populateDynamicDimSizes(OpBuilder &b, Location loc, Value shapedValue, SmallVector< Value > &dynamicDims)
Populate dynamicDims with tensor::DimOp / memref::DimOp results for all dynamic dimensions of the giv...
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
Type getTensorTypeFromMemRefType(Type type)
Return an unranked/ranked tensor type for the given unranked/ranked memref type.
Definition MemRefOps.cpp:62
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
bool insideMutuallyExclusiveRegions(Operation *a, Operation *b)
Return true if a and b are in mutually exclusive regions as per RegionBranchOpInterface.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
Region * getEnclosingRepetitiveRegion(Operation *op)
Return the first enclosing region of the given op that may be executed repetitively as per RegionBran...
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...