MLIR 24.0.0git
BufferizationOps.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
16#include "mlir/IR/Matchers.h"
17#include "llvm/ADT/SmallVectorExtras.h"
18#include <optional>
19
20using namespace mlir;
21using namespace mlir::bufferization;
22
23//===----------------------------------------------------------------------===//
24// Helper functions
25//===----------------------------------------------------------------------===//
26
28 OpBuilder &b, Value value, MemRefType destType,
30 auto srcType = llvm::cast<MemRefType>(value.getType());
31
32 // Element type and rank must match.
33 if (srcType.getElementType() != destType.getElementType())
34 return failure();
35 if (srcType.getRank() != destType.getRank())
36 return failure();
37
38 // In case the affine maps are different, we may need to use a copy if we go
39 // from dynamic to static offset or stride (the canonicalization cannot know
40 // at this point that it is really cast compatible).
41 auto isGuaranteedCastCompatible = [](MemRefType source, MemRefType target) {
42 int64_t sourceOffset, targetOffset;
43 SmallVector<int64_t, 4> sourceStrides, targetStrides;
44 if (failed(source.getStridesAndOffset(sourceStrides, sourceOffset)) ||
45 failed(target.getStridesAndOffset(targetStrides, targetOffset)))
46 return false;
47 auto dynamicToStatic = [](int64_t a, int64_t b) {
48 return ShapedType::isDynamic(a) && ShapedType::isStatic(b);
49 };
50 if (dynamicToStatic(sourceOffset, targetOffset))
51 return false;
52 for (auto it : zip(sourceStrides, targetStrides))
53 if (dynamicToStatic(std::get<0>(it), std::get<1>(it)))
54 return false;
55 return true;
56 };
57
58 // Note: If `areCastCompatible`, a cast is valid, but may fail at runtime. To
59 // ensure that we only generate casts that always succeed at runtime, we check
60 // a fix extra conditions in `isGuaranteedCastCompatible`.
61 if (memref::CastOp::areCastCompatible(srcType, destType) &&
62 isGuaranteedCastCompatible(srcType, destType)) {
63 Value casted = *options.castFn(b, value.getLoc(), destType, value);
64 return casted;
65 }
66
67 auto loc = value.getLoc();
68 SmallVector<Value, 4> dynamicOperands;
69 for (int i = 0; i < destType.getRank(); ++i) {
70 if (destType.getShape()[i] != ShapedType::kDynamic)
71 continue;
72 Value size = memref::DimOp::create(b, loc, value, i);
73 dynamicOperands.push_back(size);
74 }
75
76 FailureOr<Value> copy = options.allocationFn(
77 b, loc, destType, dynamicOperands, options.bufferAlignment);
78 if (failed(copy))
79 return failure();
80 if (failed(options.memCpyFn(b, loc, value, *copy)))
81 return failure();
82 return copy;
83}
84
85/// Try to fold to_buffer(to_tensor(x)). If x's type and the result type of the
86/// to_buffer op are different, a memref.cast is needed.
88 RewriterBase &rewriter, ToBufferOp toBuffer,
90 auto bufferToTensor = toBuffer.getTensor().getDefiningOp<ToTensorOp>();
91 if (!bufferToTensor)
92 return failure();
93
94 Type srcType = bufferToTensor.getBuffer().getType();
95 Type destType = toBuffer.getType();
96
97 // Directly rewrite if the type did not change.
98 if (srcType == destType) {
99 rewriter.replaceOp(toBuffer, bufferToTensor.getBuffer());
100 return success();
101 }
102
103 if (!llvm::isa<BaseMemRefType>(srcType) ||
104 !llvm::isa<BaseMemRefType>(destType)) {
105 // Non-builtin case: the best is to try the user-provided cast.
106 auto replacement =
107 options.castFn(rewriter, bufferToTensor.getBuffer().getLoc(), destType,
108 bufferToTensor.getBuffer());
109 if (failed(replacement))
110 return failure();
111 rewriter.replaceOp(toBuffer, *replacement);
112 return success();
113 }
114
115 auto rankedSrcType = llvm::dyn_cast<MemRefType>(srcType);
116 auto rankedDestType = llvm::dyn_cast<MemRefType>(destType);
117 auto unrankedSrcType = llvm::dyn_cast<UnrankedMemRefType>(srcType);
118
119 // Ranked memref -> Ranked memref cast.
120 if (rankedSrcType && rankedDestType) {
121 FailureOr<Value> replacement = castOrReallocMemRefValue(
122 rewriter, bufferToTensor.getBuffer(), rankedDestType, options);
123 if (failed(replacement))
124 return failure();
125
126 rewriter.replaceOp(toBuffer, *replacement);
127 return success();
128 }
129
130 // Unranked memref -> Ranked memref cast: May require a copy.
131 // TODO: Not implemented at the moment.
132 if (unrankedSrcType && rankedDestType)
133 return failure();
134
135 // Unranked/ranked memref -> unranked memref cast: No copy needed if the types
136 // are cast-compatible.
137 if (!memref::CastOp::areCastCompatible(srcType, destType))
138 return failure();
139
140 rewriter.replaceOpWithNewOp<memref::CastOp>(toBuffer, destType,
141 bufferToTensor.getBuffer());
142 return success();
143}
144
146 OpBuilder &b, Location loc, Value shapedValue,
147 SmallVector<Value> &dynamicDims) {
148 auto shapedType = llvm::cast<ShapedType>(shapedValue.getType());
149 for (int64_t i = 0; i < shapedType.getRank(); ++i) {
150 if (shapedType.isDynamicDim(i)) {
151 if (llvm::isa<MemRefType>(shapedType)) {
152 dynamicDims.push_back(memref::DimOp::create(b, loc, shapedValue, i));
153 } else {
154 assert(llvm::isa<RankedTensorType>(shapedType) && "expected tensor");
155 dynamicDims.push_back(tensor::DimOp::create(b, loc, shapedValue, i));
156 }
157 }
158 }
159}
160
161//===----------------------------------------------------------------------===//
162// AllocTensorOp
163//===----------------------------------------------------------------------===//
164
165LogicalResult AllocTensorOp::verify() {
166 if (getCopy() && !getDynamicSizes().empty())
167 return emitError("dynamic sizes not needed when copying a tensor");
168 if (!getCopy() && failed(verifyDynamicDimensionCount(
169 getOperation(), getType(), getDynamicSizes())))
170 return failure();
171 if (getCopy() && getCopy().getType() != getType())
172 return emitError("expected that `copy` and return type match");
173 return success();
174}
175
176void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
177 RankedTensorType type, ValueRange dynamicSizes) {
178 build(builder, result, type, dynamicSizes, /*copy=*/Value(),
179 /*size_hint=*/Value(),
180 /*memory_space=*/IntegerAttr());
181}
182
183void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
184 RankedTensorType type, ValueRange dynamicSizes,
185 Value copy) {
186 build(builder, result, type, dynamicSizes, copy, /*size_hint=*/Value(),
187 /*memory_space=*/IntegerAttr());
188}
189
190void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
191 TensorType type, ValueRange dynamicSizes, Value copy,
192 IntegerAttr memorySpace) {
193 build(builder, result, type, dynamicSizes, copy, /*size_hint=*/Value(),
194 memorySpace);
195}
196
197namespace {
198/// Change the type of the result of a `bufferization.alloc_tensor` by making
199/// the result type statically sized along dimension that in the original
200/// operation where defined as dynamic, but the size was defined using a
201/// `constant` op. For example:
202///
203/// %c5 = arith.constant 5: index
204/// %0 = bufferization.alloc_tensor(%arg0, %c5) : tensor<?x?xf32>
205///
206/// to
207///
208/// %0 = bufferization.alloc_tensor(%arg0) : tensor<?x5xf32>
209struct ReplaceStaticShapeDims : OpRewritePattern<AllocTensorOp> {
210 using OpRewritePattern<AllocTensorOp>::OpRewritePattern;
211
212 LogicalResult matchAndRewrite(AllocTensorOp op,
213 PatternRewriter &rewriter) const override {
214 if (op.getCopy())
215 return failure();
216 SmallVector<int64_t> newShape = llvm::to_vector(op.getType().getShape());
217 SmallVector<Value> newDynamicSizes;
218 unsigned int dynValCounter = 0;
219 for (int64_t i = 0; i < op.getType().getRank(); ++i) {
220 if (!op.isDynamicDim(i))
221 continue;
222 Value value = op.getDynamicSizes()[dynValCounter++];
223 APInt intVal;
224 if (matchPattern(value, m_ConstantInt(&intVal))) {
225 int64_t dim = intVal.getSExtValue();
226 if (dim >= 0)
227 newShape[i] = intVal.getSExtValue();
228 else
229 newDynamicSizes.push_back(value);
230 } else {
231 newDynamicSizes.push_back(value);
232 }
233 }
234 RankedTensorType newType = RankedTensorType::get(
235 newShape, op.getType().getElementType(), op.getType().getEncoding());
236 if (newType == op.getType())
237 return failure();
238 auto newOp = AllocTensorOp::create(rewriter, op.getLoc(), newType,
239 newDynamicSizes, /*copy=*/Value());
240 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
241 return success();
242 }
243};
244
245struct FoldDimOfAllocTensorOp : public OpRewritePattern<tensor::DimOp> {
246 using OpRewritePattern<tensor::DimOp>::OpRewritePattern;
247
248 LogicalResult matchAndRewrite(tensor::DimOp dimOp,
249 PatternRewriter &rewriter) const override {
250 std::optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex();
251 auto allocTensorOp = dimOp.getSource().getDefiningOp<AllocTensorOp>();
252 if (!allocTensorOp || !maybeConstantIndex)
253 return failure();
254 if (*maybeConstantIndex < 0 ||
255 *maybeConstantIndex >= allocTensorOp.getType().getRank())
256 return failure();
257 if (!allocTensorOp.getType().isDynamicDim(*maybeConstantIndex))
258 return failure();
259 rewriter.replaceOp(
260 dimOp, allocTensorOp.getDynamicSize(rewriter, *maybeConstantIndex));
261 return success();
262 }
263};
264} // namespace
265
266void AllocTensorOp::getCanonicalizationPatterns(RewritePatternSet &results,
267 MLIRContext *ctx) {
268 results.add<FoldDimOfAllocTensorOp, ReplaceStaticShapeDims>(ctx);
269}
270
271LogicalResult AllocTensorOp::reifyResultShapes(
272 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
273 auto shapes =
274 llvm::map_to_vector<4>(llvm::seq<int64_t>(0, getType().getRank()),
275 [&](int64_t dim) -> OpFoldResult {
276 if (isDynamicDim(dim))
277 return getDynamicSize(builder, dim);
278 return builder.getIndexAttr(getStaticSize(dim));
279 });
280 reifiedReturnShapes.emplace_back(std::move(shapes));
281 return success();
282}
283
284ParseResult AllocTensorOp::parse(OpAsmParser &parser, OperationState &result) {
286 if (parser.parseLParen() || parser.parseOperandList(dynamicSizesOperands) ||
287 parser.parseRParen())
288 return failure();
289 ParseResult copyKeyword = parser.parseOptionalKeyword("copy");
291 if (copyKeyword.succeeded())
292 if (parser.parseLParen() || parser.parseOperand(copyOperand) ||
293 parser.parseRParen())
294 return failure();
295 ParseResult sizeHintKeyword = parser.parseOptionalKeyword("size_hint");
296 OpAsmParser::UnresolvedOperand sizeHintOperand;
297 if (sizeHintKeyword.succeeded())
298 if (parser.parseEqual() || parser.parseOperand(sizeHintOperand))
299 return failure();
300 if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon())
301 return failure();
302
303 TensorType type;
304 if (parser.parseCustomTypeWithFallback(type))
305 return failure();
306 result.addTypes(type);
307
308 Type indexType = parser.getBuilder().getIndexType();
309 if (parser.resolveOperands(dynamicSizesOperands, indexType, result.operands))
310 return failure();
311 if (copyKeyword.succeeded())
312 if (parser.resolveOperand(copyOperand, type, result.operands))
313 return failure();
314 if (sizeHintKeyword.succeeded())
315 if (parser.resolveOperand(sizeHintOperand, indexType, result.operands))
316 return failure();
317 result.addAttribute(AllocTensorOp::getOperandSegmentSizeAttr(),
319 {static_cast<int32_t>(dynamicSizesOperands.size()),
320 static_cast<int32_t>(copyKeyword.succeeded()),
321 static_cast<int32_t>(sizeHintKeyword.succeeded())}));
322 return success();
323}
324
325void AllocTensorOp::print(OpAsmPrinter &p) {
326 p << "(" << getDynamicSizes() << ")";
327 if (getCopy())
328 p << " copy(" << getCopy() << ")";
329 if (getSizeHint())
330 p << " size_hint=" << getSizeHint();
331 p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{
332 AllocTensorOp::getOperandSegmentSizeAttr()});
333 p << " : ";
334 auto type = getResult().getType();
335 if (auto validType = llvm::dyn_cast<::mlir::TensorType>(type))
336 p.printStrippedAttrOrType(validType);
337 else
338 p << type;
339}
340
341Value AllocTensorOp::getDynamicSize(OpBuilder &b, unsigned idx) {
342 assert(isDynamicDim(idx) && "expected dynamic dim");
343 if (getCopy())
344 return tensor::DimOp::create(b, getLoc(), getCopy(), idx);
345 return getOperand(getIndexOfDynamicSize(idx));
346}
347
348//===----------------------------------------------------------------------===//
349// CloneOp
350//===----------------------------------------------------------------------===//
351
352OpFoldResult CloneOp::fold(FoldAdaptor adaptor) {
353 return succeeded(memref::foldMemRefCast(*this)) ? getResult() : Value();
354}
355
356namespace {
357
358/// Merge the clone and its source (by converting the clone to a cast) when
359/// possible.
360struct SimplifyClones : public OpRewritePattern<CloneOp> {
361 using OpRewritePattern<CloneOp>::OpRewritePattern;
362
363 LogicalResult matchAndRewrite(CloneOp cloneOp,
364 PatternRewriter &rewriter) const override {
365 if (cloneOp.use_empty()) {
366 rewriter.eraseOp(cloneOp);
367 return success();
368 }
369
370 Value source = cloneOp.getInput();
371 if (source.getType() != cloneOp.getType() &&
372 !memref::CastOp::areCastCompatible({source.getType()},
373 {cloneOp.getType()}))
374 return failure();
375
376 // Aims to find the dealloc op for the canonical source
377 // which otherwise could prevent removal of unnecessary allocs.
378 Value canonicalSource = source;
379 while (auto iface = dyn_cast_or_null<ViewLikeOpInterface>(
380 canonicalSource.getDefiningOp())) {
381 if (canonicalSource != iface.getViewDest()) {
382 break;
383 }
384 canonicalSource = iface.getViewSource();
385 }
386
387 std::optional<Operation *> maybeCloneDeallocOp =
388 memref::findDealloc(cloneOp.getOutput());
389 // Skip if either of them has > 1 deallocate operations.
390 if (!maybeCloneDeallocOp.has_value())
391 return failure();
392 std::optional<Operation *> maybeSourceDeallocOp =
393 memref::findDealloc(canonicalSource);
394 if (!maybeSourceDeallocOp.has_value())
395 return failure();
396 Operation *cloneDeallocOp = *maybeCloneDeallocOp;
397 Operation *sourceDeallocOp = *maybeSourceDeallocOp;
398
399 // If both are deallocated in the same block, their in-block lifetimes
400 // might not fully overlap, so we cannot decide which one to drop.
401 if (cloneDeallocOp && sourceDeallocOp &&
402 cloneDeallocOp->getBlock() == sourceDeallocOp->getBlock())
403 return failure();
404
405 Block *currentBlock = cloneOp->getBlock();
406 Operation *redundantDealloc = nullptr;
407 if (cloneDeallocOp && cloneDeallocOp->getBlock() == currentBlock) {
408 redundantDealloc = cloneDeallocOp;
409 } else if (sourceDeallocOp && sourceDeallocOp->getBlock() == currentBlock) {
410 redundantDealloc = sourceDeallocOp;
411 }
412
413 if (!redundantDealloc)
414 return failure();
415
416 // Safety check that there are no other deallocations inbetween
417 // cloneOp and redundantDealloc, as otherwise we might deallocate an alias
418 // of source before the uses of the clone. With alias information, we could
419 // restrict this to only fail of the dealloc's operand is an alias
420 // of the source.
421 for (Operation *pos = cloneOp->getNextNode(); pos != redundantDealloc;
422 pos = pos->getNextNode()) {
423 // Bail if we run out of operations while looking for a deallocation op.
424 if (!pos)
425 return failure();
426 auto effectInterface = dyn_cast<MemoryEffectOpInterface>(pos);
427 if (!effectInterface)
428 continue;
429 if (effectInterface.hasEffect<MemoryEffects::Free>())
430 return failure();
431 }
432
433 if (source.getType() != cloneOp.getType())
434 source = memref::CastOp::create(rewriter, cloneOp.getLoc(),
435 cloneOp.getType(), source);
436 rewriter.replaceOp(cloneOp, source);
437 rewriter.eraseOp(redundantDealloc);
438 return success();
439 }
440};
441
442} // namespace
443
444void CloneOp::getCanonicalizationPatterns(RewritePatternSet &results,
445 MLIRContext *context) {
446 results.add<SimplifyClones>(context);
447}
448
449//===----------------------------------------------------------------------===//
450// MaterializeInDestinationOp
451//===----------------------------------------------------------------------===//
452
453LogicalResult MaterializeInDestinationOp::reifyResultShapes(
454 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
455 if (getOperation()->getNumResults() == 1) {
456 assert(isa<TensorType>(getDest().getType()) && "expected tensor type");
457 reifiedReturnShapes.resize(1,
459 reifiedReturnShapes[0] =
460 tensor::getMixedSizes(builder, getLoc(), getDest());
461 }
462 return success();
463}
464
465Value MaterializeInDestinationOp::buildSubsetExtraction(OpBuilder &builder,
466 Location loc) {
467 if (isa<TensorType>(getDest().getType())) {
468 // The subset is the entire destination tensor.
469 return getDest();
470 }
471
472 // The "restrict" attribute is transferred from this op to the newly created
473 // to_tensor op. If this op does not the "restrict" attribute, the subset
474 // extraction cannot be built because there is no guarantee that there is no
475 // pre-existing "restrict" to_tensor op with the same/an aliasing destination.
476 if (!getRestrict())
477 return {};
478
479 // Build a bufferization.to_tensor op.
480 assert(isa<BaseMemRefType>(getDest().getType()) && "expected memref type");
481 assert(getRestrict() &&
482 "expected that ops with memrefs dest have 'restrict'");
483 setRestrict(false);
484 return ToTensorOp::create(
485 builder, loc, memref::getTensorTypeFromMemRefType(getDest().getType()),
486 getDest(),
487 /*restrict=*/true, getWritable());
488}
489
490bool MaterializeInDestinationOp::isEquivalentSubset(
491 Value candidate, function_ref<bool(Value, Value)> equivalenceFn) {
492 return equivalenceFn(getDest(), candidate);
493}
494
496MaterializeInDestinationOp::getValuesNeededToBuildSubsetExtraction() {
497 return {getDest()};
498}
499
500OpOperand &MaterializeInDestinationOp::getSourceOperand() {
501 return getOperation()->getOpOperand(0) /*source*/;
502}
503
504bool MaterializeInDestinationOp::operatesOnEquivalentSubset(
505 SubsetOpInterface subsetOp,
506 function_ref<bool(Value, Value)> equivalenceFn) {
507 return false;
508}
509
510bool MaterializeInDestinationOp::operatesOnDisjointSubset(
511 SubsetOpInterface subsetOp,
512 function_ref<bool(Value, Value)> equivalenceFn) {
513 return false;
514}
515
516LogicalResult MaterializeInDestinationOp::verify() {
517 if (!isa<TensorType, BaseMemRefType>(getDest().getType()))
518 return emitOpError("'dest' must be a tensor or a memref");
519 if (auto destType = dyn_cast<TensorType>(getDest().getType())) {
520 if (getOperation()->getNumResults() != 1)
521 return emitOpError("tensor 'dest' implies exactly one tensor result");
522 if (destType != getResult().getType())
523 return emitOpError("result and 'dest' types must match");
524 }
525 if (isa<BaseMemRefType>(getDest().getType()) &&
526 getOperation()->getNumResults() != 0)
527 return emitOpError("memref 'dest' implies zero results");
528 if (getRestrict() && !isa<BaseMemRefType>(getDest().getType()))
529 return emitOpError("'restrict' is valid only for memref destinations");
530 if (getWritable() != isa<BaseMemRefType>(getDest().getType()))
531 return emitOpError("'writable' must be specified if and only if the "
532 "destination is of memref type");
533 TensorType srcType = getSource().getType();
534 ShapedType destType = cast<ShapedType>(getDest().getType());
535 if (srcType.hasRank() != destType.hasRank())
536 return emitOpError("source/destination shapes are incompatible");
537 if (srcType.hasRank()) {
538 if (failed(verifyRanksMatch(getOperation(), srcType, destType, "source",
539 "destination")))
540 return failure();
541 for (auto [src, dest] :
542 llvm::zip(srcType.getShape(), destType.getShape())) {
543 if (src == ShapedType::kDynamic || dest == ShapedType::kDynamic) {
544 // Cannot verify dynamic dimension size. Assume that that they match at
545 // runtime.
546 continue;
547 }
548 if (src != dest)
549 return emitOpError("source/destination shapes are incompatible");
550 }
551 }
552 return success();
553}
554
555void MaterializeInDestinationOp::build(OpBuilder &builder,
556 OperationState &state, Value source,
557 Value dest) {
558 auto destTensorType = dyn_cast<TensorType>(dest.getType());
559 build(builder, state, /*result=*/destTensorType ? destTensorType : Type(),
560 source, dest);
561}
562
563MutableOperandRange MaterializeInDestinationOp::getDpsInitsMutable() {
564 return getDestMutable();
565}
566
567void MaterializeInDestinationOp::getEffects(
569 &effects) {
570 if (isa<BaseMemRefType>(getDest().getType()))
571 effects.emplace_back(MemoryEffects::Write::get(), &getDestMutable(),
573}
574
575//===----------------------------------------------------------------------===//
576// ToTensorOp
577//===----------------------------------------------------------------------===//
578
579OpFoldResult ToTensorOp::fold(FoldAdaptor) {
580 if (auto toBuffer = getBuffer().getDefiningOp<ToBufferOp>())
581 // Approximate alias analysis by conservatively folding only when no there
582 // is no interleaved operation.
583 if (toBuffer->getBlock() == this->getOperation()->getBlock() &&
584 toBuffer->getNextNode() == this->getOperation())
585 return toBuffer.getTensor();
586 return {};
587}
588
589namespace {
590struct DimOfToTensorFolder : public OpRewritePattern<tensor::DimOp> {
591 using OpRewritePattern<tensor::DimOp>::OpRewritePattern;
592
593 LogicalResult matchAndRewrite(tensor::DimOp dimOp,
594 PatternRewriter &rewriter) const override {
595 auto memrefToTensorOp = dimOp.getSource().getDefiningOp<ToTensorOp>();
596 if (!memrefToTensorOp)
597 return failure();
598
599 rewriter.replaceOpWithNewOp<memref::DimOp>(
600 dimOp, memrefToTensorOp.getBuffer(), dimOp.getIndex());
601 return success();
602 }
603};
604} // namespace
605
606void ToTensorOp::getCanonicalizationPatterns(RewritePatternSet &results,
607 MLIRContext *context) {
608 results.add<DimOfToTensorFolder>(context);
609}
610
611//===----------------------------------------------------------------------===//
612// ToBufferOp
613//===----------------------------------------------------------------------===//
614
615OpFoldResult ToBufferOp::fold(FoldAdaptor) {
616 if (auto memrefToTensor = getTensor().getDefiningOp<ToTensorOp>())
617 if (memrefToTensor.getBuffer().getType() == getType())
618 return memrefToTensor.getBuffer();
619 return {};
620}
621
622namespace {
623
624/// Replace tensor.cast + to_buffer by to_buffer + memref.cast.
625struct ToBufferOfCast : public OpRewritePattern<ToBufferOp> {
626 using OpRewritePattern<ToBufferOp>::OpRewritePattern;
627
628 LogicalResult matchAndRewrite(ToBufferOp toBuffer,
629 PatternRewriter &rewriter) const final {
630 auto tensorCastOperand =
631 toBuffer.getOperand().getDefiningOp<tensor::CastOp>();
632 if (!tensorCastOperand)
633 return failure();
634 auto srcTensorType = llvm::dyn_cast<RankedTensorType>(
635 tensorCastOperand.getOperand().getType());
636 if (!srcTensorType)
637 return failure();
638 auto currentOutputMemRefType =
639 dyn_cast<BaseMemRefType>(toBuffer.getResult().getType());
640 if (!currentOutputMemRefType)
641 return failure();
642
643 auto memrefType = currentOutputMemRefType.cloneWith(
644 srcTensorType.getShape(), srcTensorType.getElementType());
645 Value memref = ToBufferOp::create(rewriter, toBuffer.getLoc(), memrefType,
646 tensorCastOperand.getOperand(),
647 toBuffer.getReadOnly());
648 rewriter.replaceOpWithNewOp<memref::CastOp>(toBuffer, toBuffer.getType(),
649 memref);
650 return success();
651 }
652};
653
654/// Canonicalize bufferization.to_tensor + bufferization.to_buffer. Insert a
655/// cast if necessary.
656struct ToBufferToTensorFolding : public OpRewritePattern<ToBufferOp> {
657 using OpRewritePattern<ToBufferOp>::OpRewritePattern;
658
659 LogicalResult matchAndRewrite(ToBufferOp toBuffer,
660 PatternRewriter &rewriter) const final {
661 BufferizationOptions options;
662 options.bufferAlignment = 0;
663 return foldToBufferToTensorPair(rewriter, toBuffer, options);
664 }
665};
666
667/// Fold a load on a to_buffer operation into an tensor.extract on the
668/// corresponding tensor.
669struct LoadOfToBuffer : public OpRewritePattern<memref::LoadOp> {
670 using OpRewritePattern<memref::LoadOp>::OpRewritePattern;
671
672 LogicalResult matchAndRewrite(memref::LoadOp load,
673 PatternRewriter &rewriter) const override {
674 auto toBuffer = load.getMemref().getDefiningOp<ToBufferOp>();
675 if (!toBuffer || !toBuffer.getReadOnly())
676 return failure();
677
678 rewriter.replaceOpWithNewOp<tensor::ExtractOp>(load, toBuffer.getTensor(),
679 load.getIndices());
680 return success();
681 }
682};
683
684/// Fold dim of a to_buffer into the dim of the tensor.
685struct DimOfCastOp : public OpRewritePattern<memref::DimOp> {
686 using OpRewritePattern<memref::DimOp>::OpRewritePattern;
687
688 LogicalResult matchAndRewrite(memref::DimOp dimOp,
689 PatternRewriter &rewriter) const override {
690 auto castOp = dimOp.getSource().getDefiningOp<ToBufferOp>();
691 if (!castOp)
692 return failure();
693 Value newSource = castOp.getOperand();
694 rewriter.replaceOpWithNewOp<tensor::DimOp>(dimOp, newSource,
695 dimOp.getIndex());
696 return success();
697 }
698};
699
700} // namespace
701
702void ToBufferOp::getCanonicalizationPatterns(RewritePatternSet &results,
703 MLIRContext *context) {
704 results.add<DimOfCastOp, LoadOfToBuffer, ToBufferOfCast,
705 ToBufferToTensorFolding>(context);
706}
707
708std::optional<Operation *> CloneOp::buildDealloc(OpBuilder &builder,
709 Value alloc) {
710 return memref::DeallocOp::create(builder, alloc.getLoc(), alloc)
711 .getOperation();
712}
713
714std::optional<Value> CloneOp::buildClone(OpBuilder &builder, Value alloc) {
715 return CloneOp::create(builder, alloc.getLoc(), alloc).getResult();
716}
717
718//===----------------------------------------------------------------------===//
719// DeallocOp
720//===----------------------------------------------------------------------===//
721
722LogicalResult DeallocOp::inferReturnTypes(
723 MLIRContext *context, std::optional<::mlir::Location> location,
724 ValueRange operands, DictionaryAttr attributes, PropertyRef properties,
725 RegionRange regions, SmallVectorImpl<Type> &inferredReturnTypes) {
726 DeallocOpAdaptor adaptor(operands, attributes, properties, regions);
727 inferredReturnTypes = SmallVector<Type>(adaptor.getRetained().size(),
728 IntegerType::get(context, 1));
729 return success();
730}
731
732LogicalResult DeallocOp::verify() {
733 if (getMemrefs().size() != getConditions().size())
734 return emitOpError(
735 "must have the same number of conditions as memrefs to deallocate");
736 if (getRetained().size() != getUpdatedConditions().size())
737 return emitOpError("must have the same number of updated conditions "
738 "(results) as retained operands");
739 return success();
740}
741
742static LogicalResult updateDeallocIfChanged(DeallocOp deallocOp,
743 ValueRange memrefs,
744 ValueRange conditions,
745 PatternRewriter &rewriter) {
746 if (deallocOp.getMemrefs() == memrefs &&
747 deallocOp.getConditions() == conditions)
748 return failure();
749
750 rewriter.modifyOpInPlace(deallocOp, [&]() {
751 deallocOp.getMemrefsMutable().assign(memrefs);
752 deallocOp.getConditionsMutable().assign(conditions);
753 });
754 return success();
755}
756
757namespace {
758
759/// Remove duplicate values in the list of memrefs to be deallocated. We need to
760/// make sure the corresponding condition value is updated accordingly since
761/// their two conditions might not cover the same set of cases. In that case, we
762/// have to combine them (by computing the disjunction of them).
763/// Example:
764/// ```mlir
765/// bufferization.dealloc (%arg0, %arg0 : ...) if (%arg1, %arg2)
766/// ```
767/// is canonicalized to
768/// ```mlir
769/// %0 = arith.ori %arg1, %arg2 : i1
770/// bufferization.dealloc (%arg0 : memref<2xi32>) if (%0)
771/// ```
772struct DeallocRemoveDuplicateDeallocMemrefs
773 : public OpRewritePattern<DeallocOp> {
774 using OpRewritePattern<DeallocOp>::OpRewritePattern;
775
776 LogicalResult matchAndRewrite(DeallocOp deallocOp,
777 PatternRewriter &rewriter) const override {
778 // Unique memrefs to be deallocated.
779 DenseMap<Value, unsigned> memrefToCondition;
780 SmallVector<Value> newMemrefs, newConditions;
781 for (auto [i, memref, cond] :
782 llvm::enumerate(deallocOp.getMemrefs(), deallocOp.getConditions())) {
783 if (memrefToCondition.count(memref)) {
784 // If the dealloc conditions don't match, we need to make sure that the
785 // dealloc happens on the union of cases.
786 Value &newCond = newConditions[memrefToCondition[memref]];
787 if (newCond != cond)
788 newCond =
789 arith::OrIOp::create(rewriter, deallocOp.getLoc(), newCond, cond);
790 } else {
791 memrefToCondition.insert({memref, newConditions.size()});
792 newMemrefs.push_back(memref);
793 newConditions.push_back(cond);
794 }
795 }
796
797 // Return failure if we don't change anything such that we don't run into an
798 // infinite loop of pattern applications.
799 return updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
800 rewriter);
801 }
802};
803
804/// Remove duplicate values in the list of retained memrefs. We need to make
805/// sure the corresponding result condition value is replaced properly.
806/// Example:
807/// ```mlir
808/// %0:2 = bufferization.dealloc retain (%arg3, %arg3 : ...)
809/// ```
810/// is canonicalized to
811/// ```mlir
812/// %0 = bufferization.dealloc retain (%arg3 : memref<2xi32>)
813/// ```
814struct DeallocRemoveDuplicateRetainedMemrefs
815 : public OpRewritePattern<DeallocOp> {
816 using OpRewritePattern<DeallocOp>::OpRewritePattern;
817
818 LogicalResult matchAndRewrite(DeallocOp deallocOp,
819 PatternRewriter &rewriter) const override {
820 // Unique retained values
822 SmallVector<Value> newRetained;
823 SmallVector<unsigned> resultReplacementIdx;
824 unsigned i = 0;
825 for (auto retained : deallocOp.getRetained()) {
826 if (seen.count(retained)) {
827 resultReplacementIdx.push_back(seen[retained]);
828 continue;
829 }
830
831 seen[retained] = i;
832 newRetained.push_back(retained);
833 resultReplacementIdx.push_back(i++);
834 }
835
836 // Return failure if we don't change anything such that we don't run into an
837 // infinite loop of pattern applications.
838 if (newRetained.size() == deallocOp.getRetained().size())
839 return failure();
840
841 // We need to create a new op because the number of results is always the
842 // same as the number of condition operands.
843 auto newDeallocOp =
844 DeallocOp::create(rewriter, deallocOp.getLoc(), deallocOp.getMemrefs(),
845 deallocOp.getConditions(), newRetained);
846 SmallVector<Value> replacements(
847 llvm::map_range(resultReplacementIdx, [&](unsigned idx) {
848 return newDeallocOp.getUpdatedConditions()[idx];
849 }));
850 rewriter.replaceOp(deallocOp, replacements);
851 return success();
852 }
853};
854
855/// Erase deallocation operations where the variadic list of memrefs to
856/// deallocate is empty. Example:
857/// ```mlir
858/// %0 = bufferization.dealloc retain (%arg0: memref<2xi32>)
859/// ```
860struct EraseEmptyDealloc : public OpRewritePattern<DeallocOp> {
861 using OpRewritePattern<DeallocOp>::OpRewritePattern;
862
863 LogicalResult matchAndRewrite(DeallocOp deallocOp,
864 PatternRewriter &rewriter) const override {
865 if (deallocOp.getMemrefs().empty()) {
866 Value constFalse = arith::ConstantOp::create(rewriter, deallocOp.getLoc(),
867 rewriter.getBoolAttr(false));
868 rewriter.replaceOp(
869 deallocOp, SmallVector<Value>(deallocOp.getUpdatedConditions().size(),
870 constFalse));
871 return success();
872 }
873 return failure();
874 }
875};
876
877/// Removes memrefs from the deallocation list if their associated condition is
878/// always 'false'.
879///
880/// Example:
881/// ```
882/// bufferization.dealloc (%arg0, %arg1 : memref<2xi32>, memref<2xi32>)
883/// if (%arg2, %false)
884/// ```
885/// becomes
886/// ```
887/// bufferization.dealloc (%arg0 : memref<2xi32>) if (%arg2)
888/// ```
889struct EraseAlwaysFalseDealloc : public OpRewritePattern<DeallocOp> {
890 using OpRewritePattern<DeallocOp>::OpRewritePattern;
891
892 LogicalResult matchAndRewrite(DeallocOp deallocOp,
893 PatternRewriter &rewriter) const override {
894 SmallVector<Value> newMemrefs, newConditions;
895 for (auto [memref, cond] :
896 llvm::zip(deallocOp.getMemrefs(), deallocOp.getConditions())) {
897 if (!matchPattern(cond, m_Zero())) {
898 newMemrefs.push_back(memref);
899 newConditions.push_back(cond);
900 }
901 }
902
903 return updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
904 rewriter);
905 }
906};
907
908/// The `memref.extract_strided_metadata` is often inserted to get the base
909/// memref if the operand is not already guaranteed to be the result of a memref
910/// allocation operation. This canonicalization pattern removes this extraction
911/// operation if the operand is now produced by an allocation operation (e.g.,
912/// due to other canonicalizations simplifying the IR).
913///
914/// Example:
915/// ```mlir
916/// %alloc = memref.alloc() : memref<2xi32>
917/// %base_memref, %offset, %size, %stride = memref.extract_strided_metadata
918/// %alloc : memref<2xi32> -> memref<i32>, index, index, index
919/// bufferization.dealloc (%base_memref : memref<i32>) if (%cond)
920/// ```
921/// is canonicalized to
922/// ```mlir
923/// %alloc = memref.alloc() : memref<2xi32>
924/// bufferization.dealloc (%alloc : memref<2xi32>) if (%cond)
925/// ```
926struct SkipExtractMetadataOfAlloc : public OpRewritePattern<DeallocOp> {
927 using OpRewritePattern<DeallocOp>::OpRewritePattern;
928
929 LogicalResult matchAndRewrite(DeallocOp deallocOp,
930 PatternRewriter &rewriter) const override {
931 SmallVector<Value> newMemrefs(
932 llvm::map_range(deallocOp.getMemrefs(), [&](Value memref) {
933 auto extractStridedOp =
934 memref.getDefiningOp<memref::ExtractStridedMetadataOp>();
935 if (!extractStridedOp)
936 return memref;
937 Value allocMemref = extractStridedOp.getOperand();
938 auto allocOp = allocMemref.getDefiningOp<MemoryEffectOpInterface>();
939 if (!allocOp)
940 return memref;
941 if (allocOp.getEffectOnValue<MemoryEffects::Allocate>(allocMemref))
942 return allocMemref;
943 return memref;
944 }));
945
946 return updateDeallocIfChanged(deallocOp, newMemrefs,
947 deallocOp.getConditions(), rewriter);
948 }
949};
950
951/// Removes pairs of `bufferization.dealloc` and alloc operations if there is no
952/// other user of the allocated value and the allocating operation can be safely
953/// removed. If the same value is present multiple times, this pattern relies on
954/// other canonicalization patterns to remove the duplicate first.
955///
956/// Example:
957/// ```mlir
958/// %alloc = memref.alloc() : memref<2xi32>
959/// bufferization.dealloc (%alloc, %arg0, : ...) if (%true, %true)
960/// ```
961/// is canonicalized to
962/// ```mlir
963/// bufferization.dealloc (%arg0 : ...) if (%true)
964/// ```
965struct RemoveAllocDeallocPairWhenNoOtherUsers
966 : public OpRewritePattern<DeallocOp> {
967 using OpRewritePattern<DeallocOp>::OpRewritePattern;
968
969 LogicalResult matchAndRewrite(DeallocOp deallocOp,
970 PatternRewriter &rewriter) const override {
971 SmallVector<Value> newMemrefs, newConditions;
972 SmallVector<Operation *> toDelete;
973 for (auto [memref, cond] :
974 llvm::zip(deallocOp.getMemrefs(), deallocOp.getConditions())) {
975 if (auto allocOp = memref.getDefiningOp<MemoryEffectOpInterface>()) {
976 // Check that it is indeed an allocate effect, that the op has no other
977 // side effects (which would not allow us to remove the op), and that
978 // there are no other users.
979 if (allocOp.getEffectOnValue<MemoryEffects::Allocate>(memref) &&
981 memref.hasOneUse()) {
982 toDelete.push_back(allocOp);
983 continue;
984 }
985 }
986
987 newMemrefs.push_back(memref);
988 newConditions.push_back(cond);
989 }
990
991 if (failed(updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
992 rewriter)))
993 return failure();
994
995 for (Operation *op : toDelete)
996 rewriter.eraseOp(op);
997
998 return success();
999 }
1000};
1001
1002} // anonymous namespace
1003
1004void DeallocOp::getCanonicalizationPatterns(RewritePatternSet &results,
1005 MLIRContext *context) {
1007}
1008
1010 RewritePatternSet &patterns, MLIRContext *context) {
1011 patterns.add<DeallocRemoveDuplicateDeallocMemrefs,
1012 DeallocRemoveDuplicateRetainedMemrefs, EraseEmptyDealloc,
1013 EraseAlwaysFalseDealloc, SkipExtractMetadataOfAlloc,
1014 RemoveAllocDeallocPairWhenNoOtherUsers>(context);
1015}
1016
1017//===----------------------------------------------------------------------===//
1018// TableGen'd op method definitions
1019//===----------------------------------------------------------------------===//
1020
1021#define GET_OP_CLASSES
1022#include "mlir/Dialect/Bufferization/IR/BufferizationOps.cpp.inc"
return success()
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static SmallVector< Value > getDynamicSize(Value memref, func::FuncOp funcOp)
Return the dynamic shapes of the memref based on the defining op.
static LogicalResult updateDeallocIfChanged(DeallocOp deallocOp, ValueRange memrefs, ValueRange conditions, PatternRewriter &rewriter)
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...
auto load
*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 llvm::ManagedStatic< PassManagerOptions > options
template bool mlir::hasSingleEffect< MemoryEffects::Allocate >(Operation *)
static void getDynamicSizes(RankedTensorType tp, ValueRange sizes, SmallVectorImpl< Value > &dynSizes)
Collects the dynamic dimension sizes for tp with the assumption that sizes are the dimension sizes fo...
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
virtual ParseResult parseRParen()=0
Parse a ) token.
virtual ParseResult parseEqual()=0
Parse a = token.
virtual ParseResult parseCustomTypeWithFallback(Type &result, function_ref< ParseResult(Type &result)> parseType)=0
Parse a custom type with the provided callback, unless the next token is #, in which case the generic...
virtual ParseResult parseColon()=0
Parse a : token.
virtual ParseResult parseLParen()=0
Parse a ( token.
void printStrippedAttrOrType(AttrOrType attrOrType)
Print the provided attribute in the context of an operation custom printer/parser: this will invoke d...
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:171
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
IndexType getIndexType()
Definition Builders.cpp:59
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:119
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
This class helps build Operations.
Definition Builders.h:210
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Type-safe wrapper around a void* for passing properties, including the properties structs of operatio...
This class provides an abstraction over the different types of ranges over Regions.
Definition Region.h:378
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
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...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class represents a specific instance of an effect.
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
ArrayRef< int64_t > getShape() const
Returns the shape of this tensor type.
bool hasRank() const
Returns if this type is ranked, i.e. it has a known number of dimensions.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
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
void populateDeallocOpCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context)
Add the canonicalization patterns for bufferization.dealloc to the given pattern set to make them ava...
FailureOr< Value > castOrReallocMemRefValue(OpBuilder &b, Value value, MemRefType type, const BufferizationOptions &options)
Try to cast the given ranked MemRef-typed value to the given ranked MemRef type.
LogicalResult foldToBufferToTensorPair(RewriterBase &rewriter, ToBufferOp toBuffer, const BufferizationOptions &options)
Try to fold to_buffer(to_tensor(x)).
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...
Type getTensorTypeFromMemRefType(Type type)
Return an unranked/ranked tensor type for the given unranked/ranked memref type.
Definition MemRefOps.cpp:62
std::optional< Operation * > findDealloc(Value allocValue)
Finds a single dealloc operation for the given allocated value.
LogicalResult foldMemRefCast(Operation *op, Value inner=nullptr)
This is a common utility used for patterns of the form "someop(memref.cast) -> someop".
Definition MemRefOps.cpp:47
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:90
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
LogicalResult verifyDynamicDimensionCount(Operation *op, ShapedType type, ValueRange dynamicSizes)
Verify that the number of dynamic size operands matches the number of dynamic dimensions in the shape...
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
LogicalResult verifyRanksMatch(Operation *op, ShapedType lhs, ShapedType rhs, StringRef lhsName, StringRef rhsName)
Verify that two shaped types have matching ranks.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
This is the representation of an operand reference.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
This represents an operation in an abstracted form, suitable for use with the builder APIs.