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 = options.defaultMemorySpaceFn(tensorType);
229 if (memorySpace.has_value())
230 allocTensorOp.setMemorySpaceAttr(memorySpace.value());
231 return allocTensorOp.getResult();
232}
233
234// TODO: Properly support with options, for now it is hardcoded to builtin
235// Tensor/MemRef types based approach
236LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
237 RewriterBase &rewriter, const AnalysisState &analysisState,
238 const BufferizationState &bufferizationState) {
239 OpBuilder::InsertionGuard g(rewriter);
240 Operation *op = getOperation();
241 SmallVector<OpOperand *> outOfPlaceOpOperands;
242 DenseSet<OpOperand *> copiedOpOperands;
243 SmallVector<Value> outOfPlaceValues;
244 DenseSet<Value> copiedOpValues;
245
246 // Find all out-of-place OpOperands.
247 for (OpOperand &opOperand : op->getOpOperands()) {
248 Type operandType = opOperand.get().getType();
249 if (!llvm::isa<TensorType>(operandType))
250 continue;
251 if (analysisState.isInPlace(opOperand))
252 continue;
253 if (llvm::isa<UnrankedTensorType>(operandType))
254 return op->emitError("copying of unranked tensors is not implemented");
255
256 AliasingValueList aliasingValues =
257 analysisState.getAliasingValues(opOperand);
258 if (aliasingValues.getNumAliases() == 1 &&
259 isa<OpResult>(aliasingValues.getAliases()[0].value) &&
260 !analysisState.bufferizesToMemoryWrite(opOperand) &&
261 analysisState
262 .getAliasingOpOperands(aliasingValues.getAliases()[0].value)
263 .getNumAliases() == 1 &&
264 !isa<UnrankedTensorType>(
265 aliasingValues.getAliases()[0].value.getType())) {
266 // The op itself does not write but may create exactly one alias. Instead
267 // of copying the OpOperand, copy the OpResult. The OpResult can sometimes
268 // be smaller than the OpOperand (e.g., in the case of an extract_slice,
269 // where the result is usually a smaller part of the source). Do not apply
270 // this optimization if the OpResult is an unranked tensor (because those
271 // cannot be copied at the moment).
272 Value value = aliasingValues.getAliases()[0].value;
273 outOfPlaceValues.push_back(value);
274 if (!analysisState.canOmitTensorCopy(opOperand))
275 copiedOpValues.insert(value);
276 } else {
277 // In all other cases, make a copy of the OpOperand.
278 outOfPlaceOpOperands.push_back(&opOperand);
279 if (!analysisState.canOmitTensorCopy(opOperand))
280 copiedOpOperands.insert(&opOperand);
281 }
282 }
283
284 // Insert copies of OpOperands.
285 rewriter.setInsertionPoint(op);
286 for (OpOperand *opOperand : outOfPlaceOpOperands) {
287 FailureOr<Value> copy = allocateTensorForShapedValue(
288 rewriter, op->getLoc(), opOperand->get(), analysisState.getOptions(),
289 bufferizationState, copiedOpOperands.contains(opOperand));
290 if (failed(copy))
291 return failure();
292 rewriter.modifyOpInPlace(op, [&]() { opOperand->set(*copy); });
293 }
294
295 // Insert copies of Values.
296 rewriter.setInsertionPointAfter(op);
297 for (Value value : outOfPlaceValues) {
298 FailureOr<Value> copy = allocateTensorForShapedValue(
299 rewriter, op->getLoc(), value, analysisState.getOptions(),
300 bufferizationState, copiedOpValues.count(value));
301 if (failed(copy))
302 return failure();
303 SmallVector<OpOperand *> uses = llvm::map_to_vector(
304 value.getUses(), [](OpOperand &use) { return &use; });
305 for (OpOperand *use : uses) {
306 // Do not update the alloc_tensor op that we just created.
307 if (use->getOwner() == copy->getDefiningOp())
308 continue;
309 // tensor.dim ops may have been created to be used as alloc_tensor op
310 // dynamic extents. Do not update these either.
311 if (isa<tensor::DimOp>(use->getOwner()))
312 continue;
313 rewriter.modifyOpInPlace(use->getOwner(), [&]() { use->set(*copy); });
314 }
315 }
316
317 return success();
318}
319
320//===----------------------------------------------------------------------===//
321// OpFilter
322//===----------------------------------------------------------------------===//
323
324bool OpFilter::isOpAllowed(Operation *op) const {
325 // All other ops: Allow/disallow according to filter.
326 bool isAllowed = !hasAllowRule();
327 for (const Entry &entry : entries) {
328 bool filterResult = entry.fn(op);
329 switch (entry.type) {
330 case Entry::ALLOW:
331 isAllowed |= filterResult;
332 break;
333 case Entry::DENY:
334 if (filterResult)
335 // DENY filter matches. This op is no allowed. (Even if other ALLOW
336 // filters may match.)
337 return false;
338 };
339 }
340 return isAllowed;
341}
342
343//===----------------------------------------------------------------------===//
344// BufferizationOptions
345//===----------------------------------------------------------------------===//
346
347namespace {
348
349/// Default function arg type converter: Use a fully dynamic layout map.
350BufferLikeType
351defaultFunctionArgTypeConverter(TensorLikeType type, Attribute memorySpace,
352 func::FuncOp funcOp,
353 const BufferizationOptions &options) {
354 if (auto tensorType = mlir::dyn_cast<TensorType>(type)) {
355 return cast<BufferLikeType>(
356 getMemRefTypeWithFullyDynamicLayout(tensorType, memorySpace));
357 }
358
359 // If not builtin, fallback to TensorLikeType::getBufferType()
360 auto bufferType =
361 type.getBufferType(options, [&]() { return funcOp->emitError(); });
362 assert(succeeded(bufferType) &&
363 "a valid buffer is always expected at function boundary");
364 return *bufferType;
365}
366/// Default unknown type converter: Use a fully dynamic layout map.
368defaultUnknownTypeConverter(TensorType tensorType, Attribute memorySpace,
369 const BufferizationOptions &options) {
370 return getMemRefTypeWithFullyDynamicLayout(tensorType, memorySpace);
371}
372
373} // namespace
374
375// Default constructor for BufferizationOptions.
376BufferizationOptions::BufferizationOptions()
377 : functionArgTypeConverterFn(defaultFunctionArgTypeConverter),
378 unknownTypeConverterFn(defaultUnknownTypeConverter) {}
379
380bool BufferizationOptions::isOpAllowed(Operation *op) const {
381 // Special case: If function boundary bufferization is deactivated, do not
382 // allow ops that belong to the `func` dialect.
383 bool isFuncBoundaryOp = isa_and_nonnull<func::FuncDialect>(op->getDialect());
384 if (!bufferizeFunctionBoundaries && isFuncBoundaryOp)
385 return false;
386
387 return opFilter.isOpAllowed(op);
388}
389
390BufferizableOpInterface
391BufferizationOptions::dynCastBufferizableOp(Operation *op) const {
392 if (!isOpAllowed(op))
393 return nullptr;
394 auto bufferizableOp = dyn_cast<BufferizableOpInterface>(op);
395 if (!bufferizableOp)
396 return nullptr;
397 return bufferizableOp;
398}
399
400BufferizableOpInterface
401BufferizationOptions::dynCastBufferizableOp(Value value) const {
402 return dynCastBufferizableOp(getOwnerOfValue(value));
403}
404
405void BufferizationOptions::setFunctionBoundaryTypeConversion(
406 LayoutMapOption layoutMapOption) {
407 functionArgTypeConverterFn = [=](TensorLikeType type, Attribute memorySpace,
408 func::FuncOp funcOp,
409 const BufferizationOptions &options) {
410 if (auto tensorType = mlir::dyn_cast<TensorType>(type)) {
411 if (layoutMapOption == LayoutMapOption::IdentityLayoutMap)
412 return cast<BufferLikeType>(
413 bufferization::getMemRefTypeWithStaticIdentityLayout(tensorType,
414 memorySpace));
415 return cast<BufferLikeType>(
416 bufferization::getMemRefTypeWithFullyDynamicLayout(tensorType,
417 memorySpace));
418 }
419
420 // If not builtin, fallback to TensorLikeType::getBufferType()
421 auto bufferType =
422 type.getBufferType(options, [&]() { return funcOp->emitError(); });
423 assert(succeeded(bufferType) &&
424 "a valid buffer is always expected at function boundary");
425 return *bufferType;
426 };
427 inferFunctionResultLayout =
428 layoutMapOption == LayoutMapOption::InferLayoutMap;
429}
430
431//===----------------------------------------------------------------------===//
432// Helper functions for BufferizableOpInterface
433//===----------------------------------------------------------------------===//
434
436 if (auto bbArg = llvm::dyn_cast<BlockArgument>(value)) {
437 b.setInsertionPointToStart(bbArg.getOwner());
438 } else {
439 b.setInsertionPointAfter(value.getDefiningOp());
440 }
441}
442
443/// Determine which OpOperand* will alias with `value` if the op is bufferized
444/// in place. Return all tensor OpOperand* if the op is not bufferizable.
445AliasingOpOperandList AnalysisState::getAliasingOpOperands(Value value) const {
446 if (Operation *op = getOwnerOfValue(value))
447 if (auto bufferizableOp = getOptions().dynCastBufferizableOp(op))
448 return bufferizableOp.getAliasingOpOperands(value, *this);
449
450 // The op is not bufferizable.
451 return detail::unknownGetAliasingOpOperands(value);
452}
453
454/// Determine which Values will alias with `opOperand` if the op is bufferized
455/// in place. Return all tensor Values if the op is not bufferizable.
456AliasingValueList AnalysisState::getAliasingValues(OpOperand &opOperand) const {
457 if (auto bufferizableOp =
458 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
459 return bufferizableOp.getAliasingValues(opOperand, *this);
460
461 // The op is not bufferizable.
462 return detail::unknownGetAliasingValues(opOperand);
463}
464
465/// Return true if `opOperand` bufferizes to a memory read. Return `true` if the
466/// op is not bufferizable.
467bool AnalysisState::bufferizesToMemoryRead(OpOperand &opOperand) const {
468 if (auto bufferizableOp =
469 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
470 return bufferizableOp.bufferizesToMemoryRead(opOperand, *this);
471
472 // Unknown op that returns a tensor. The inplace analysis does not support it.
473 // Conservatively return true.
474 return true;
475}
476
477/// Return true if `opOperand` bufferizes to a memory write. Return
478/// `true` if the op is not bufferizable.
479bool AnalysisState::bufferizesToMemoryWrite(OpOperand &opOperand) const {
480 if (auto bufferizableOp =
481 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
482 return bufferizableOp.bufferizesToMemoryWrite(opOperand, *this);
483
484 // Unknown op that returns a tensor. The inplace analysis does not support it.
485 // Conservatively return true.
486 return true;
487}
488
489/// Return true if `opOperand` does neither read nor write but bufferizes to an
490/// alias. Return false if the op is not bufferizable.
491bool AnalysisState::bufferizesToAliasOnly(OpOperand &opOperand) const {
492 if (auto bufferizableOp =
493 getOptions().dynCastBufferizableOp(opOperand.getOwner()))
494 return bufferizableOp.bufferizesToAliasOnly(opOperand, *this);
495
496 // Unknown op that returns a tensor. The inplace analysis does not support it.
497 // Conservatively return false.
498 return false;
499}
500
501bool AnalysisState::bufferizesToMemoryWrite(Value value) const {
502 auto opResult = llvm::dyn_cast<OpResult>(value);
503 if (!opResult)
504 return true;
505 auto bufferizableOp = getOptions().dynCastBufferizableOp(value);
506 if (!bufferizableOp)
507 return true;
508 return bufferizableOp.resultBufferizesToMemoryWrite(opResult, *this);
509}
510
511/// Return true if the given value is read by an op that bufferizes to a memory
512/// read. Also takes into account ops that create an alias but do not read by
513/// themselves (e.g., ExtractSliceOp).
514bool AnalysisState::isValueRead(Value value) const {
515 assert(llvm::isa<TensorLikeType>(value.getType()) &&
516 "expected TensorLikeType");
517 SmallVector<OpOperand *> workingSet;
518 DenseSet<OpOperand *> visited;
519 for (OpOperand &use : value.getUses())
520 workingSet.push_back(&use);
521
522 while (!workingSet.empty()) {
523 OpOperand *uMaybeReading = workingSet.pop_back_val();
524 if (!visited.insert(uMaybeReading).second)
525 continue;
526
527 // Skip over all ops that neither read nor write (but create an alias).
528 if (bufferizesToAliasOnly(*uMaybeReading))
529 for (AliasingValue alias : getAliasingValues(*uMaybeReading))
530 for (OpOperand &use : alias.value.getUses())
531 workingSet.push_back(&use);
532 if (bufferizesToMemoryRead(*uMaybeReading))
533 return true;
534 }
535
536 return false;
537}
538
539// Starting from `opOperand`, follow the use-def chain in reverse, always
540// selecting the aliasing OpOperands. Find and return Values for which
541// `condition` evaluates to true. Uses of such matching Values are not
542// traversed any further, the visited aliasing opOperands will be preserved
543// through `visitedOpOperands`.
544llvm::SetVector<Value> AnalysisState::findValueInReverseUseDefChain(
545 OpOperand *opOperand, llvm::function_ref<bool(Value)> condition,
546 TraversalConfig config,
547 llvm::DenseSet<OpOperand *> *visitedOpOperands) const {
548 llvm::DenseSet<Value> visited;
549 llvm::SetVector<Value> result, workingSet;
550 workingSet.insert(opOperand->get());
551
552 if (visitedOpOperands)
553 visitedOpOperands->insert(opOperand);
554
555 while (!workingSet.empty()) {
556 Value value = workingSet.pop_back_val();
557
558 if (!config.revisitAlreadyVisitedValues && visited.contains(value)) {
559 // Stop traversal if value was already visited.
560 if (config.alwaysIncludeLeaves)
561 result.insert(value);
562 continue;
563 }
564 visited.insert(value);
565
566 if (condition(value)) {
567 result.insert(value);
568 continue;
569 }
570
571 if (!config.followUnknownOps && !options.dynCastBufferizableOp(value)) {
572 // Stop iterating if `followUnknownOps` is unset and the op is either
573 // not bufferizable or excluded in the OpFilter.
574 if (config.alwaysIncludeLeaves)
575 result.insert(value);
576 continue;
577 }
578
579 AliasingOpOperandList aliases = getAliasingOpOperands(value);
580 if (aliases.getNumAliases() == 0) {
581 // The traversal ends naturally if there are no more OpOperands that
582 // could be followed.
583 if (config.alwaysIncludeLeaves)
584 result.insert(value);
585 continue;
586 }
587
588 for (AliasingOpOperand a : aliases) {
589 if (config.followEquivalentOnly &&
590 a.relation != BufferRelation::Equivalent) {
591 // Stop iterating if `followEquivalentOnly` is set but the alias is not
592 // equivalent.
593 if (config.alwaysIncludeLeaves)
594 result.insert(value);
595 continue;
596 }
597
598 if (config.followInPlaceOnly && !isInPlace(*a.opOperand)) {
599 // Stop iterating if `followInPlaceOnly` is set but the alias is
600 // out-of-place.
601 if (config.alwaysIncludeLeaves)
602 result.insert(value);
603 continue;
604 }
605
606 if (config.followSameTypeOrCastsOnly &&
607 a.opOperand->get().getType() != value.getType() &&
608 !value.getDefiningOp<CastOpInterface>()) {
609 // Stop iterating if `followSameTypeOrCastsOnly` is set but the alias is
610 // has a different type and the op is not a cast.
611 if (config.alwaysIncludeLeaves)
612 result.insert(value);
613 continue;
614 }
615
616 workingSet.insert(a.opOperand->get());
617 if (visitedOpOperands)
618 visitedOpOperands->insert(a.opOperand);
619 }
620 }
621
622 return result;
623}
624
625// Find the values that define the contents of the given operand's value.
626llvm::SetVector<Value>
627AnalysisState::findDefinitions(OpOperand *opOperand) const {
628 TraversalConfig config;
629 config.alwaysIncludeLeaves = false;
630 return findValueInReverseUseDefChain(
631 opOperand, [&](Value v) { return this->bufferizesToMemoryWrite(v); },
632 config);
633}
634
635AnalysisState::AnalysisState(const BufferizationOptions &options)
637
639 : options(options), type(type) {
640 for (const BufferizationOptions::AnalysisStateInitFn &fn :
641 options.stateInitializers)
642 fn(*this);
643}
644
645bool AnalysisState::canOmitTensorCopy(OpOperand &opOperand) const {
646 // Do not copy if the tensor has undefined contents.
647 if (hasUndefinedContents(&opOperand))
648 return true;
649
650 // Do not copy if the buffer of the tensor is entirely overwritten (with
651 // values that do not depend on the old tensor).
652 if (bufferizesToMemoryWrite(opOperand) && !bufferizesToMemoryRead(opOperand))
653 return true;
654
655 // Do not copy if the tensor is never read.
656 AliasingValueList aliases = getAliasingValues(opOperand);
657 if (!bufferizesToMemoryRead(opOperand) &&
658 llvm::none_of(aliases,
659 [&](AliasingValue a) { return isValueRead(a.value); }))
660 return true;
661
662 // Default: Cannot omit the copy.
663 return false;
664}
665
666bool AnalysisState::isInPlace(OpOperand &opOperand) const {
667 // ToBufferOps are always in-place.
668 if (isa<ToBufferOp>(opOperand.getOwner()))
669 return true;
670
671 // In the absence of analysis information, OpOperands that bufferize to a
672 // memory write are out-of-place, i.e., an alloc and copy is inserted.
673 return !bufferizesToMemoryWrite(opOperand);
674}
675
676bool AnalysisState::areEquivalentBufferizedValues(Value v1, Value v2) const {
677 // In the absence of analysis information, we do not know if the values are
678 // equivalent. The conservative answer is "false".
679 return false;
680}
681
682bool AnalysisState::areAliasingBufferizedValues(Value v1, Value v2) const {
683 // In the absence of analysis information, we do not know if the values may be
684 // aliasing. The conservative answer is "true".
685 return true;
686}
687
688bool AnalysisState::hasUndefinedContents(OpOperand *opOperand) const {
689 // In the absence of analysis information, the conservative answer is "false".
690 return false;
691}
692
693FailureOr<Value> bufferization::getBuffer(RewriterBase &rewriter, Value value,
694 const BufferizationOptions &options,
695 const BufferizationState &state) {
696#ifndef NDEBUG
697 auto tensorType = llvm::dyn_cast<TensorLikeType>(value.getType());
698 assert(tensorType && "unexpected non-tensor type");
699#endif // NDEBUG
700
701 // Replace "%t = to_tensor %m" with %m.
702 if (auto toTensorOp = value.getDefiningOp<bufferization::ToTensorOp>())
703 return toTensorOp.getBuffer();
704
705 // Insert to_buffer op.
706 OpBuilder::InsertionGuard g(rewriter);
707 setInsertionPointAfter(rewriter, value);
708 FailureOr<BufferLikeType> bufferType = getBufferType(value, options, state);
709 if (failed(bufferType))
710 return failure();
711
712 return bufferization::ToBufferOp::create(rewriter, value.getLoc(),
713 *bufferType, value)
714 .getResult();
715}
716
717/// Return the buffer type for a given Value (tensor) after bufferization.
718FailureOr<BufferLikeType>
719bufferization::getBufferType(Value value, const BufferizationOptions &options,
720 const BufferizationState &state) {
721 SmallVector<Value> invocationStack;
722 return getBufferType(value, options, state, invocationStack);
723}
724
725/// Return the buffer type for a given Value (tensor) after bufferization.
726FailureOr<BufferLikeType>
727bufferization::getBufferType(Value value, const BufferizationOptions &options,
728 const BufferizationState &state,
729 SmallVector<Value> &invocationStack) {
730 assert(llvm::isa<TensorLikeType>(value.getType()) &&
731 "unexpected non-tensor type");
732 invocationStack.push_back(value);
733 llvm::scope_exit popFromStack([&]() { invocationStack.pop_back(); });
734
735 // Try querying BufferizableOpInterface.
736 Operation *op = getOwnerOfValue(value);
737 auto bufferizableOp = options.dynCastBufferizableOp(op);
738 if (bufferizableOp)
739 return bufferizableOp.getBufferType(value, options, state, invocationStack);
740
741 // Op is not bufferizable.
742 return cast<TensorLikeType>(value.getType()).getBufferType(options, [&]() {
743 return op->emitError();
744 });
745}
746
747bool bufferization::hasTensorSemantics(Operation *op) {
748 if (auto bufferizableOp = dyn_cast<BufferizableOpInterface>(op))
749 return bufferizableOp.hasTensorSemantics();
750 return detail::defaultHasTensorSemantics(op);
751}
752
753void bufferization::replaceOpWithBufferizedValues(RewriterBase &rewriter,
754 Operation *op,
755 ValueRange values) {
756 assert(values.size() == op->getNumResults() &&
757 "expected one value per OpResult");
758 OpBuilder::InsertionGuard g(rewriter);
759
760 // Replace all OpResults with the given values.
761 SmallVector<Value> replacements;
762 for (OpResult opResult : op->getOpResults()) {
763 Value replacement = values[opResult.getResultNumber()];
764 if (llvm::isa<TensorLikeType>(opResult.getType())) {
765 // The OpResult is a tensor. Such values are replaced with memrefs during
766 // bufferization.
767 assert(llvm::isa<BufferLikeType>(replacement.getType()) &&
768 "tensor op result should be replaced with a buffer value");
769 // The existing uses of the OpResult still expect a tensor. Insert a
770 // ToTensorOp. Throughout bufferization, this ToTensorOp will gradually
771 // loose all of its users and eventually DCE away.
772 rewriter.setInsertionPointAfter(op);
773 replacement = bufferization::ToTensorOp::create(
774 rewriter, replacement.getLoc(), opResult.getType(), replacement);
775 }
776 replacements.push_back(replacement);
777 }
778
779 rewriter.replaceOp(op, replacements);
780}
781
782//===----------------------------------------------------------------------===//
783// Bufferization-specific scoped alloc insertion support.
784//===----------------------------------------------------------------------===//
785
786/// Create a memref allocation with the given type and dynamic extents.
787FailureOr<Value> BufferizationOptions::createAlloc(OpBuilder &b, Location loc,
788 MemRefType type,
789 ValueRange dynShape) const {
790 if (allocationFn)
791 return (*allocationFn)(b, loc, type, dynShape, bufferAlignment);
792
793 // Default bufferallocation via AllocOp.
794 if (bufferAlignment != 0)
795 return memref::AllocOp::create(b, loc, type, dynShape,
796 b.getI64IntegerAttr(bufferAlignment))
797 .getResult();
798 return memref::AllocOp::create(b, loc, type, dynShape).getResult();
799}
800
801/// Create a memory copy between two memref buffers.
802LogicalResult BufferizationOptions::createMemCpy(OpBuilder &b, Location loc,
803 Value from, Value to) const {
804 if (memCpyFn)
805 return (*memCpyFn)(b, loc, from, to);
806
807 memref::CopyOp::create(b, loc, from, to);
808 return success();
809}
810
811//===----------------------------------------------------------------------===//
812// Bufferization-specific IRMapping support with debugging.
813//===----------------------------------------------------------------------===//
814
815BaseMemRefType bufferization::getMemRefType(TensorType tensorType,
816 const BufferizationOptions &options,
817 MemRefLayoutAttrInterface layout,
818 Attribute memorySpace) {
819 // Case 1: Unranked memref type.
820 if (auto unrankedTensorType =
821 llvm::dyn_cast<UnrankedTensorType>(tensorType)) {
822 assert(!layout && "UnrankedTensorType cannot have a layout map");
823 return UnrankedMemRefType::get(unrankedTensorType.getElementType(),
824 memorySpace);
825 }
826
827 // Case 2: Ranked memref type with specified layout.
828 auto rankedTensorType = llvm::cast<RankedTensorType>(tensorType);
829 if (layout) {
830 return MemRefType::get(rankedTensorType.getShape(),
831 rankedTensorType.getElementType(), layout,
832 memorySpace);
833 }
834
835 return options.unknownTypeConverterFn(tensorType, memorySpace, options);
836}
837
839bufferization::getMemRefTypeWithFullyDynamicLayout(TensorType tensorType,
840 Attribute memorySpace) {
841 // Case 1: Unranked memref type.
842 if (auto unrankedTensorType =
843 llvm::dyn_cast<UnrankedTensorType>(tensorType)) {
844 return UnrankedMemRefType::get(unrankedTensorType.getElementType(),
845 memorySpace);
846 }
847
848 // Case 2: Ranked memref type.
849 auto rankedTensorType = llvm::cast<RankedTensorType>(tensorType);
850 int64_t dynamicOffset = ShapedType::kDynamic;
851 SmallVector<int64_t> dynamicStrides(rankedTensorType.getRank(),
852 ShapedType::kDynamic);
853 auto stridedLayout = StridedLayoutAttr::get(tensorType.getContext(),
854 dynamicOffset, dynamicStrides);
855 return MemRefType::get(rankedTensorType.getShape(),
856 rankedTensorType.getElementType(), stridedLayout,
857 memorySpace);
858}
859
860/// Return a MemRef type with a static identity layout (i.e., no layout map). If
861/// the given tensor type is unranked, return an unranked MemRef type.
863bufferization::getMemRefTypeWithStaticIdentityLayout(TensorType tensorType,
864 Attribute memorySpace) {
865 // Case 1: Unranked memref type.
866 if (auto unrankedTensorType =
867 llvm::dyn_cast<UnrankedTensorType>(tensorType)) {
868 return UnrankedMemRefType::get(unrankedTensorType.getElementType(),
869 memorySpace);
870 }
871
872 // Case 2: Ranked memref type.
873 auto rankedTensorType = llvm::cast<RankedTensorType>(tensorType);
874 MemRefLayoutAttrInterface layout = {};
875 return MemRefType::get(rankedTensorType.getShape(),
876 rankedTensorType.getElementType(), layout,
877 memorySpace);
878}
879
880//===----------------------------------------------------------------------===//
881// Default implementations of interface methods
882//===----------------------------------------------------------------------===//
883
884bool bufferization::detail::defaultResultBufferizesToMemoryWrite(
885 OpResult opResult, const AnalysisState &state) {
886 auto bufferizableOp = cast<BufferizableOpInterface>(opResult.getDefiningOp());
887 AliasingOpOperandList opOperands =
888 bufferizableOp.getAliasingOpOperands(opResult, state);
889
890 // Case 1: OpResults that have no aliasing OpOperand usually bufferize to
891 // memory writes.
892 if (opOperands.getAliases().empty())
893 return true;
894
895 // Case 2: If an aliasing OpOperand bufferizes to a memory write, the OpResult
896 // may bufferize to a memory write.
897 if (llvm::any_of(opOperands, [&](AliasingOpOperand alias) {
898 return state.bufferizesToMemoryWrite(*alias.opOperand);
899 }))
900 return true;
901
902 // Case 3: Check if a nested aliasing OpOperand value bufferizes to a memory
903 // write. (Or: The reverse SSA use-def chain ends inside the reigon.) In that
904 // case, the OpResult bufferizes to a memory write. E.g.:
905 //
906 // %0 = "some_writing_op" : tensor<?xf32>
907 // %r = scf.if ... -> tensor<?xf32> {
908 // scf.yield %0 : tensor<?xf32>
909 // } else {
910 // %1 = "another_writing_op"(%0) : tensor<?xf32>
911 // scf.yield %1 : tensor<?xf32>
912 // }
913 // "some_reading_op"(%r)
914 //
915 // %r bufferizes to a memory write because an aliasing OpOperand value (%1)
916 // bufferizes to a memory write and the defining op is inside the scf.if.
917 //
918 // Note: This treatment of surrouding ops is useful for ops that have a
919 // region but no OpOperand such as scf.if or scf.execute_region. It simplifies
920 // the analysis considerably.
921 //
922 // "another_writing_op" in the above example should be able to bufferize
923 // inplace in the absence of another read of %0. However, if the scf.if op
924 // would not be considered a "write", the analysis would detect the
925 // following conflict:
926 //
927 // * read = some_reading_op
928 // * lastWrite = %0 (Note: The last write of %r would be a set: {%0, %1}.)
929 // * conflictingWrite = %1
930 //
931 auto isMemoryWriteInsideOp = [&](Value v) {
932 Operation *op = getOwnerOfValue(v);
933 if (!opResult.getDefiningOp()->isAncestor(op))
934 return false;
935 return state.bufferizesToMemoryWrite(v);
936 };
937 TraversalConfig config;
938 config.alwaysIncludeLeaves = false;
939 for (AliasingOpOperand alias : opOperands) {
940 if (!state
941 .findValueInReverseUseDefChain(alias.opOperand,
942 isMemoryWriteInsideOp, config)
943 .empty())
944 return true;
945 }
946 return false;
947}
948
949// Compute the AliasingOpOperandList for a given Value based on
950// getAliasingValues.
951AliasingOpOperandList bufferization::detail::defaultGetAliasingOpOperands(
952 Value value, const AnalysisState &state) {
953 Operation *op = getOwnerOfValue(value);
955 for (OpOperand &opOperand : op->getOpOperands()) {
956 if (!llvm::isa<TensorLikeType>(opOperand.get().getType()))
957 continue;
958 AliasingValueList aliasingValues = state.getAliasingValues(opOperand);
959 for (const auto &it : aliasingValues)
960 if (it.value == value)
961 result.emplace_back(&opOperand, it.relation, it.isDefinite);
962 }
963 return AliasingOpOperandList(std::move(result));
964}
965
966FailureOr<BufferLikeType> bufferization::detail::defaultGetBufferType(
967 Value value, const BufferizationOptions &options,
968 const BufferizationState &bufferizationState,
969 SmallVector<Value> &invocationStack) {
970 assert(llvm::isa<TensorType>(value.getType()) && "expected tensor type");
971 auto tensorType = cast<TensorType>(value.getType());
972
973 auto elementType = tensorType.getElementType();
974
975 if (!BaseMemRefType::isValidElementType(elementType))
976 return getOwnerOfValue(value)->emitError()
977 << "cannot bufferize value of type " << tensorType
978 << ": element type " << elementType
979 << " is not a valid memref element type";
980
981 // No further analysis is possible for a block argument.
982 if (llvm::isa<BlockArgument>(value)) {
983 return cast<BufferLikeType>(
984 bufferization::getMemRefType(tensorType, options));
985 }
986
987 // Value is an OpResult.
988 Operation *op = getOwnerOfValue(value);
989 auto opResult = llvm::cast<OpResult>(value);
990 AnalysisState analysisState(options);
991 AliasingOpOperandList aliases = analysisState.getAliasingOpOperands(opResult);
992 if (aliases.getNumAliases() > 0 &&
993 aliases.getAliases()[0].relation == BufferRelation::Equivalent) {
994 // If the OpResult has an equivalent OpOperand, both OpResult and
995 // OpOperand bufferize to the exact same buffer type.
996 Value equivalentOperand = aliases.getAliases().front().opOperand->get();
997 return getBufferType(equivalentOperand, options, bufferizationState,
998 invocationStack);
999 }
1000
1001 // If we do not know the memory space and there is no default memory space,
1002 // report a failure.
1003 auto memSpace =
1004 options.defaultMemorySpaceFn(cast<TensorType>(value.getType()));
1005 if (!memSpace.has_value())
1006 return op->emitError("could not infer memory space");
1007
1008 return cast<BufferLikeType>(
1009 getMemRefType(tensorType, options, /*layout=*/{}, *memSpace));
1010}
1011
1012bool bufferization::detail::defaultIsRepetitiveRegion(
1013 BufferizableOpInterface bufferizableOp, unsigned index) {
1014 assert(index < bufferizableOp->getNumRegions() && "invalid region index");
1015 auto regionInterface =
1016 dyn_cast<RegionBranchOpInterface>(bufferizableOp.getOperation());
1017 if (!regionInterface)
1018 return false;
1019 return regionInterface.isRepetitiveRegion(index);
1020}
1021
1022AliasingOpOperandList
1023bufferization::detail::unknownGetAliasingOpOperands(Value value) {
1024 // TODO: Take into account successor blocks.
1025 // No aliasing in case of non-entry blocks.
1026 if (auto bbArg = dyn_cast<BlockArgument>(value))
1027 if (bbArg.getOwner() != &bbArg.getOwner()->getParent()->getBlocks().front())
1028 return {};
1029
1030 // Unknown op: Conservatively assume that each OpResult may alias with every
1031 // OpOperand. In addition, each block argument of an entry block may alias
1032 // with every OpOperand.
1033 AliasingOpOperandList r;
1034 for (OpOperand &operand : value.getDefiningOp()->getOpOperands())
1035 if (isa<TensorLikeType>(operand.get().getType()))
1036 r.addAlias({&operand, BufferRelation::Unknown, /*isDefinite=*/false});
1037 return r;
1038}
1039
1040AliasingValueList
1041bufferization::detail::unknownGetAliasingValues(OpOperand &opOperand) {
1042 // TODO: Take into account successor blocks.
1043 // Unknown op: Conservatively assume that each OpResult may alias with every
1044 // OpOperand. In addition, each block argument of an entry block may alias
1045 // with every OpOperand.
1046 AliasingValueList r;
1047 for (OpResult result : opOperand.getOwner()->getOpResults())
1048 if (llvm::isa<TensorLikeType>(result.getType()))
1049 r.addAlias({result, BufferRelation::Unknown, /*isDefinite=*/false});
1050 for (Region &region : opOperand.getOwner()->getRegions())
1051 if (!region.getBlocks().empty())
1052 for (BlockArgument bbArg : region.getBlocks().front().getArguments())
1053 if (isa<TensorLikeType>(bbArg.getType()))
1054 r.addAlias({bbArg, BufferRelation::Unknown, /*isDefinite=*/false});
1055 return r;
1056}
1057
1058bool bufferization::detail::defaultHasTensorSemantics(Operation *op) {
1059 auto isaTensor = [](Type t) { return isa<TensorLikeType>(t); };
1060 bool hasTensorBlockArgument = any_of(op->getRegions(), [&](Region &r) {
1061 return any_of(r.getBlocks(), [&](Block &b) {
1062 return any_of(b.getArguments(), [&](BlockArgument bbArg) {
1063 return isaTensor(bbArg.getType());
1064 });
1065 });
1066 });
1067 if (hasTensorBlockArgument)
1068 return true;
1069
1070 if (any_of(op->getResultTypes(), isaTensor))
1071 return true;
1072 return any_of(op->getOperandTypes(), isaTensor);
1073}
1074
1075FailureOr<BaseMemRefType>
1076bufferization::detail::asMemRefType(FailureOr<BufferLikeType> bufferType) {
1077 if (failed(bufferType))
1078 return failure();
1079 return cast<BaseMemRefType>(*bufferType);
1080}
1081
1082bool bufferization::detail::typesMatchAfterBufferization(Operation &op,
1083 Value tensor,
1084 Value buffer) {
1085 return mlir::succeeded(
1086 cast<TensorLikeType>(tensor.getType())
1087 .verifyCompatibleBufferType(cast<BufferLikeType>(buffer.getType()),
1088 [&]() { return op.emitError(); }));
1089}
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:88
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:238
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:231
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:241
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:252
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:409
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:423
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:703
result_type_range getResultTypes()
Definition Operation.h:454
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition Operation.h:289
result_range getOpResults()
Definition Operation.h:446
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:248
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:430
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
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
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...