MLIR 24.0.0git
MemRefOps.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
14#include "mlir/IR/AffineMap.h"
15#include "mlir/IR/Builders.h"
17#include "mlir/IR/Matchers.h"
25#include "llvm/ADT/STLExtras.h"
26#include "llvm/ADT/SmallBitVector.h"
27#include "llvm/ADT/SmallVectorExtras.h"
28
29using namespace mlir;
30using namespace mlir::memref;
31
32/// Materialize a single constant operation from a given attribute value with
33/// the desired resultant type.
34Operation *MemRefDialect::materializeConstant(OpBuilder &builder,
35 Attribute value, Type type,
36 Location loc) {
37 return arith::ConstantOp::materialize(builder, value, type, loc);
38}
39
40//===----------------------------------------------------------------------===//
41// Common canonicalization pattern support logic
42//===----------------------------------------------------------------------===//
43
44/// This is a common class used for patterns of the form
45/// "someop(memrefcast) -> someop". It folds the source of any memref.cast
46/// into the root operation directly.
47LogicalResult mlir::memref::foldMemRefCast(Operation *op, Value inner) {
48 bool folded = false;
49 for (OpOperand &operand : op->getOpOperands()) {
50 auto cast = operand.get().getDefiningOp<CastOp>();
51 if (cast && operand.get() != inner &&
52 !llvm::isa<UnrankedMemRefType>(cast.getOperand().getType())) {
53 operand.set(cast.getOperand());
54 folded = true;
55 }
56 }
57 return success(folded);
58}
59
60/// Return an unranked/ranked tensor type for the given unranked/ranked memref
61/// type.
63 if (auto memref = llvm::dyn_cast<MemRefType>(type))
64 return RankedTensorType::get(memref.getShape(), memref.getElementType());
65 if (auto memref = llvm::dyn_cast<UnrankedMemRefType>(type))
66 return UnrankedTensorType::get(memref.getElementType());
67 return NoneType::get(type.getContext());
68}
69
71 int64_t dim) {
72 auto memrefType = llvm::cast<MemRefType>(value.getType());
73 if (memrefType.isDynamicDim(dim))
74 return builder.createOrFold<memref::DimOp>(loc, value, dim);
75
76 return builder.getIndexAttr(memrefType.getDimSize(dim));
77}
78
80 Location loc, Value value) {
81 auto memrefType = llvm::cast<MemRefType>(value.getType());
83 for (int64_t i = 0; i < memrefType.getRank(); ++i)
84 result.push_back(getMixedSize(builder, loc, value, i));
85 return result;
86}
87
88//===----------------------------------------------------------------------===//
89// Utility functions for propagating static information
90//===----------------------------------------------------------------------===//
91
92/// Helper function that sets values[i] to constValues[i] if the latter is a
93/// static value, as indicated by ShapedType::kDynamic.
94///
95/// If constValues[i] is dynamic, tries to extract a constant value from
96/// value[i] to allow for additional folding opportunities. Also convertes all
97/// existing attributes to index attributes. (They may be i64 attributes.)
99 ArrayRef<int64_t> constValues) {
100 assert(constValues.size() == values.size() &&
101 "incorrect number of const values");
102 for (auto [i, cstVal] : llvm::enumerate(constValues)) {
103 Builder builder(values[i].getContext());
104 if (ShapedType::isStatic(cstVal)) {
105 // Constant value is known, use it directly.
106 values[i] = builder.getIndexAttr(cstVal);
107 continue;
108 }
109 if (std::optional<int64_t> cst = getConstantIntValue(values[i])) {
110 // Try to extract a constant or convert an existing to index.
111 values[i] = builder.getIndexAttr(*cst);
112 }
113 }
114}
115
116/// Helper function to retrieve a lossless memory-space cast, and the
117/// corresponding new result memref type.
118static std::tuple<MemorySpaceCastOpInterface, PtrLikeTypeInterface, Type>
120 MemorySpaceCastOpInterface castOp =
121 MemorySpaceCastOpInterface::getIfPromotableCast(src);
122
123 // Bail if the cast is not lossless.
124 if (!castOp)
125 return {};
126
127 // Transform the source and target type of `castOp` to have the same metadata
128 // as `resultTy`. Bail if not possible.
129 FailureOr<PtrLikeTypeInterface> srcTy = resultTy.clonePtrWith(
130 castOp.getSourcePtr().getType().getMemorySpace(), std::nullopt);
131 if (failed(srcTy))
132 return {};
133
134 FailureOr<PtrLikeTypeInterface> tgtTy = resultTy.clonePtrWith(
135 castOp.getTargetPtr().getType().getMemorySpace(), std::nullopt);
136 if (failed(tgtTy))
137 return {};
138
139 // Check if this is a valid memory-space cast.
140 if (!castOp.isValidMemorySpaceCast(*tgtTy, *srcTy))
141 return {};
142
143 return std::make_tuple(castOp, *tgtTy, *srcTy);
144}
145
146/// Implementation of `bubbleDownCasts` method for memref operations that
147/// return a single memref result.
148template <typename ConcreteOpTy>
149static FailureOr<std::optional<SmallVector<Value>>>
151 OpOperand &src) {
152 auto [castOp, tgtTy, resTy] = getMemorySpaceCastInfo(op.getType(), src.get());
153 // Bail if we cannot cast.
154 if (!castOp)
155 return failure();
156
157 // Create the new operands.
158 SmallVector<Value> operands;
159 llvm::append_range(operands, op->getOperands());
160 operands[src.getOperandNumber()] = castOp.getSourcePtr();
161
162 // Create the new op and results.
163 auto newOp = ConcreteOpTy::create(
164 builder, op.getLoc(), TypeRange(resTy), operands, op.getProperties(),
165 op->getDiscardableAttrDictionary().getValue());
166
167 // Insert a memory-space cast to the original memory space of the op.
168 MemorySpaceCastOpInterface result = castOp.cloneMemorySpaceCastOp(
169 builder, tgtTy,
170 cast<TypedValue<PtrLikeTypeInterface>>(newOp.getResult()));
171 return std::optional<SmallVector<Value>>(
172 SmallVector<Value>({result.getTargetPtr()}));
173}
174
175//===----------------------------------------------------------------------===//
176// AllocOp / AllocaOp
177//===----------------------------------------------------------------------===//
178
179void AllocOp::getAsmResultNames(
180 function_ref<void(Value, StringRef)> setNameFn) {
181 setNameFn(getResult(), "alloc");
182}
183
184void AllocaOp::getAsmResultNames(
185 function_ref<void(Value, StringRef)> setNameFn) {
186 setNameFn(getResult(), "alloca");
187}
188
189template <typename AllocLikeOp>
190static LogicalResult verifyAllocLikeOp(AllocLikeOp op) {
191 static_assert(llvm::is_one_of<AllocLikeOp, AllocOp, AllocaOp>::value,
192 "applies to only alloc or alloca");
193 auto memRefType = llvm::dyn_cast<MemRefType>(op.getResult().getType());
194 if (!memRefType)
195 return op.emitOpError("result must be a memref");
196
197 if (failed(verifyDynamicDimensionCount(op, memRefType, op.getDynamicSizes())))
198 return failure();
199
200 unsigned numSymbols = 0;
201 if (!memRefType.getLayout().isIdentity())
202 numSymbols = memRefType.getLayout().getAffineMap().getNumSymbols();
203 if (op.getSymbolOperands().size() != numSymbols)
204 return op.emitOpError("symbol operand count does not equal memref symbol "
205 "count: expected ")
206 << numSymbols << ", got " << op.getSymbolOperands().size();
207
208 return success();
209}
210
211LogicalResult AllocOp::verify() { return verifyAllocLikeOp(*this); }
212
213LogicalResult AllocaOp::verify() {
214 // An alloca op needs to have an ancestor with an allocation scope trait.
215 if (!(*this)->getParentWithTrait<OpTrait::AutomaticAllocationScope>())
216 return emitOpError(
217 "requires an ancestor op with AutomaticAllocationScope trait");
218
219 return verifyAllocLikeOp(*this);
220}
221
222namespace {
223/// Fold constant dimensions into an alloc like operation.
224template <typename AllocLikeOp>
225struct SimplifyAllocConst : public OpRewritePattern<AllocLikeOp> {
226 using OpRewritePattern<AllocLikeOp>::OpRewritePattern;
227
228 LogicalResult matchAndRewrite(AllocLikeOp alloc,
229 PatternRewriter &rewriter) const override {
230 // Check to see if any dimensions operands are constants. If so, we can
231 // substitute and drop them.
232 if (llvm::none_of(alloc.getDynamicSizes(), [](Value operand) {
233 APInt constSizeArg;
234 if (!matchPattern(operand, m_ConstantInt(&constSizeArg)))
235 return false;
236 return constSizeArg.isNonNegative();
237 }))
238 return failure();
239
240 auto memrefType = alloc.getType();
241
242 // Ok, we have one or more constant operands. Collect the non-constant ones
243 // and keep track of the resultant memref type to build.
244 SmallVector<int64_t, 4> newShapeConstants;
245 newShapeConstants.reserve(memrefType.getRank());
246 SmallVector<Value, 4> dynamicSizes;
247
248 unsigned dynamicDimPos = 0;
249 for (unsigned dim = 0, e = memrefType.getRank(); dim < e; ++dim) {
250 int64_t dimSize = memrefType.getDimSize(dim);
251 // If this is already static dimension, keep it.
252 if (ShapedType::isStatic(dimSize)) {
253 newShapeConstants.push_back(dimSize);
254 continue;
255 }
256 auto dynamicSize = alloc.getDynamicSizes()[dynamicDimPos];
257 APInt constSizeArg;
258 if (matchPattern(dynamicSize, m_ConstantInt(&constSizeArg)) &&
259 constSizeArg.isNonNegative()) {
260 // Dynamic shape dimension will be folded.
261 newShapeConstants.push_back(constSizeArg.getZExtValue());
262 } else {
263 // Dynamic shape dimension not folded; copy dynamicSize from old memref.
264 newShapeConstants.push_back(ShapedType::kDynamic);
265 dynamicSizes.push_back(dynamicSize);
266 }
267 dynamicDimPos++;
268 }
269
270 // Create new memref type (which will have fewer dynamic dimensions).
271 MemRefType newMemRefType =
272 MemRefType::Builder(memrefType).setShape(newShapeConstants);
273 assert(dynamicSizes.size() == newMemRefType.getNumDynamicDims());
274
275 // Create and insert the alloc op for the new memref.
276 auto newAlloc = AllocLikeOp::create(rewriter, alloc.getLoc(), newMemRefType,
277 dynamicSizes, alloc.getSymbolOperands(),
278 alloc.getAlignmentAttr());
279 // Insert a cast so we have the same type as the old alloc.
280 rewriter.replaceOpWithNewOp<CastOp>(alloc, alloc.getType(), newAlloc);
281 return success();
282 }
283};
284
285/// Fold alloc operations with no users or only store and dealloc uses.
286template <typename T>
287struct SimplifyDeadAlloc : public OpRewritePattern<T> {
288 using OpRewritePattern<T>::OpRewritePattern;
289
290 LogicalResult matchAndRewrite(T alloc,
291 PatternRewriter &rewriter) const override {
292 if (llvm::any_of(alloc->getUsers(), [&](Operation *op) {
293 if (auto storeOp = dyn_cast<StoreOp>(op))
294 return storeOp.getValue() == alloc;
295 return !isa<DeallocOp>(op);
296 }))
297 return failure();
298
299 for (Operation *user : llvm::make_early_inc_range(alloc->getUsers()))
300 rewriter.eraseOp(user);
301
302 rewriter.eraseOp(alloc);
303 return success();
304 }
305};
306} // namespace
307
308void AllocOp::getCanonicalizationPatterns(RewritePatternSet &results,
309 MLIRContext *context) {
310 results.add<SimplifyAllocConst<AllocOp>, SimplifyDeadAlloc<AllocOp>>(context);
311}
312
313void AllocaOp::getCanonicalizationPatterns(RewritePatternSet &results,
314 MLIRContext *context) {
315 results.add<SimplifyAllocConst<AllocaOp>, SimplifyDeadAlloc<AllocaOp>>(
316 context);
317}
318
319//===----------------------------------------------------------------------===//
320// ReallocOp
321//===----------------------------------------------------------------------===//
322
323LogicalResult ReallocOp::verify() {
324 auto sourceType = llvm::cast<MemRefType>(getOperand(0).getType());
325 MemRefType resultType = getType();
326
327 // The source memref should have identity layout (or none).
328 if (!sourceType.getLayout().isIdentity())
329 return emitError("unsupported layout for source memref type ")
330 << sourceType;
331
332 // The result memref should have identity layout (or none).
333 if (!resultType.getLayout().isIdentity())
334 return emitError("unsupported layout for result memref type ")
335 << resultType;
336
337 // The source memref and the result memref should be in the same memory space.
338 if (sourceType.getMemorySpace() != resultType.getMemorySpace())
339 return emitError("different memory spaces specified for source memref "
340 "type ")
341 << sourceType << " and result memref type " << resultType;
342
343 // The source memref and the result memref should have the same element type.
344 if (failed(verifyElementTypesMatch(*this, sourceType, resultType, "source",
345 "result")))
346 return failure();
347
348 // Verify that we have the dynamic dimension operand when it is needed.
349 if (resultType.getNumDynamicDims() && !getDynamicResultSize())
350 return emitError("missing dimension operand for result type ")
351 << resultType;
352 if (!resultType.getNumDynamicDims() && getDynamicResultSize())
353 return emitError("unnecessary dimension operand for result type ")
354 << resultType;
355
356 return success();
357}
358
359void ReallocOp::getCanonicalizationPatterns(RewritePatternSet &results,
360 MLIRContext *context) {
361 results.add<SimplifyDeadAlloc<ReallocOp>>(context);
362}
363
364//===----------------------------------------------------------------------===//
365// AllocaScopeOp
366//===----------------------------------------------------------------------===//
367
368void AllocaScopeOp::print(OpAsmPrinter &p) {
369 bool printBlockTerminators = false;
370
371 p << ' ';
372 if (!getResults().empty()) {
373 p << " -> (" << getResultTypes() << ")";
374 printBlockTerminators = true;
375 }
376 p << ' ';
377 p.printRegion(getBodyRegion(),
378 /*printEntryBlockArgs=*/false,
379 /*printBlockTerminators=*/printBlockTerminators);
380 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary());
381}
382
383ParseResult AllocaScopeOp::parse(OpAsmParser &parser, OperationState &result) {
384 // Create a region for the body.
385 result.regions.reserve(1);
386 Region *bodyRegion = result.addRegion();
387
388 // Parse optional results type list.
389 if (parser.parseOptionalArrowTypeList(result.types))
390 return failure();
391
392 // Parse the body region.
393 if (parser.parseRegion(*bodyRegion, /*arguments=*/{}))
394 return failure();
395 AllocaScopeOp::ensureTerminator(*bodyRegion, parser.getBuilder(),
396 result.location);
397
398 // Parse the optional attribute list.
399 if (parser.parseOptionalAttrDict(result.attributes))
400 return failure();
401
402 return success();
403}
404
405void AllocaScopeOp::getSuccessorRegions(
407 if (!point.isParent()) {
408 regions.push_back(RegionSuccessor(getOperation()));
409 return;
410 }
411
412 regions.push_back(RegionSuccessor(&getBodyRegion()));
413}
414
415ValueRange AllocaScopeOp::getSuccessorInputs(RegionSuccessor successor) {
416 return successor.isOperation() ? ValueRange(getResults()) : ValueRange();
417}
418
419/// Given an operation, return whether this op is guaranteed to
420/// allocate an AutomaticAllocationScopeResource
422 MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
423 if (!interface)
424 return false;
425 for (auto res : op->getResults()) {
426 if (auto effect =
427 interface.getEffectOnValue<MemoryEffects::Allocate>(res)) {
428 if (isa<SideEffects::AutomaticAllocationScopeResource>(
429 effect->getResource()))
430 return true;
431 }
432 }
433 return false;
434}
435
436/// Given an operation, return whether this op itself could
437/// allocate an AutomaticAllocationScopeResource. Note that
438/// this will not check whether an operation contained within
439/// the op can allocate.
441 // This op itself doesn't create a stack allocation,
442 // the inner allocation should be handled separately.
444 return false;
445 MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
446 if (!interface)
447 return true;
448 for (auto res : op->getResults()) {
449 if (auto effect =
450 interface.getEffectOnValue<MemoryEffects::Allocate>(res)) {
451 if (isa<SideEffects::AutomaticAllocationScopeResource>(
452 effect->getResource()))
453 return true;
454 }
455 }
456 return false;
457}
458
459/// Return whether this op is the last non terminating op
460/// in a region. That is to say, it is in a one-block region
461/// and is only followed by a terminator. This prevents
462/// extending the lifetime of allocations.
464 return op->getBlock()->mightHaveTerminator() &&
465 op->getNextNode() == op->getBlock()->getTerminator() &&
467}
468
469/// Inline an AllocaScopeOp if either the direct parent is an allocation scope
470/// or it contains no allocation.
471struct AllocaScopeInliner : public OpRewritePattern<AllocaScopeOp> {
472 using OpRewritePattern<AllocaScopeOp>::OpRewritePattern;
473
474 LogicalResult matchAndRewrite(AllocaScopeOp op,
475 PatternRewriter &rewriter) const override {
476 bool hasPotentialAlloca =
477 op->walk<WalkOrder::PreOrder>([&](Operation *alloc) {
478 if (alloc == op)
479 return WalkResult::advance();
481 return WalkResult::interrupt();
482 if (alloc->hasTrait<OpTrait::AutomaticAllocationScope>())
483 return WalkResult::skip();
484 return WalkResult::advance();
485 }).wasInterrupted();
486
487 // If this contains no potential allocation, it is always legal to
488 // inline. Otherwise, consider two conditions:
489 if (hasPotentialAlloca) {
490 // If the parent isn't an allocation scope, or we are not the last
491 // non-terminator op in the parent, we will extend the lifetime.
492 if (!op->getParentOp()->hasTrait<OpTrait::AutomaticAllocationScope>())
493 return failure();
495 return failure();
496 }
497
498 Block *block = &op.getRegion().front();
499 Operation *terminator = block->getTerminator();
500 ValueRange results = terminator->getOperands();
501 rewriter.inlineBlockBefore(block, op);
502 rewriter.replaceOp(op, results);
503 rewriter.eraseOp(terminator);
504 return success();
505 }
506};
507
508/// Move allocations into an allocation scope, if it is legal to
509/// move them (e.g. their operands are available at the location
510/// the op would be moved to).
511struct AllocaScopeHoister : public OpRewritePattern<AllocaScopeOp> {
512 using OpRewritePattern<AllocaScopeOp>::OpRewritePattern;
513
514 LogicalResult matchAndRewrite(AllocaScopeOp op,
515 PatternRewriter &rewriter) const override {
516
517 if (!op->getParentWithTrait<OpTrait::AutomaticAllocationScope>())
518 return failure();
519
520 Operation *lastParentWithoutScope = op->getParentOp();
521
522 if (!lastParentWithoutScope ||
523 lastParentWithoutScope->hasTrait<OpTrait::AutomaticAllocationScope>())
524 return failure();
525
526 // Only apply to if this is this last non-terminator
527 // op in the block (lest lifetime be extended) of a one
528 // block region
529 if (!lastNonTerminatorInRegion(op) ||
530 !lastNonTerminatorInRegion(lastParentWithoutScope))
531 return failure();
532
533 while (!lastParentWithoutScope->getParentOp()
535 lastParentWithoutScope = lastParentWithoutScope->getParentOp();
536 if (!lastParentWithoutScope ||
537 !lastNonTerminatorInRegion(lastParentWithoutScope))
538 return failure();
539 }
540 assert(lastParentWithoutScope->getParentOp()
542
543 Region *containingRegion = nullptr;
544 for (auto &r : lastParentWithoutScope->getRegions()) {
545 if (r.isAncestor(op->getParentRegion())) {
546 assert(containingRegion == nullptr &&
547 "only one region can contain the op");
548 containingRegion = &r;
549 }
550 }
551 assert(containingRegion && "op must be contained in a region");
552
554 op->walk([&](Operation *alloc) {
556 return WalkResult::skip();
557
558 // If any operand is not defined before the location of
559 // lastParentWithoutScope (i.e. where we would hoist to), skip.
560 if (llvm::any_of(alloc->getOperands(), [&](Value v) {
561 return containingRegion->isAncestor(v.getParentRegion());
562 }))
563 return WalkResult::skip();
564 toHoist.push_back(alloc);
565 return WalkResult::advance();
566 });
567
568 if (toHoist.empty())
569 return failure();
570 rewriter.setInsertionPoint(lastParentWithoutScope);
571 for (auto *op : toHoist) {
572 auto *cloned = rewriter.clone(*op);
573 rewriter.replaceOp(op, cloned->getResults());
574 }
575 return success();
576 }
577};
578
579void AllocaScopeOp::getCanonicalizationPatterns(RewritePatternSet &results,
580 MLIRContext *context) {
581 results.add<AllocaScopeInliner, AllocaScopeHoister>(context);
582}
583
584//===----------------------------------------------------------------------===//
585// AssumeAlignmentOp
586//===----------------------------------------------------------------------===//
587
588LogicalResult AssumeAlignmentOp::verify() {
589 if (!llvm::isPowerOf2_32(getAlignment()))
590 return emitOpError("alignment must be power of 2");
591 return success();
592}
593
594void AssumeAlignmentOp::getAsmResultNames(
595 function_ref<void(Value, StringRef)> setNameFn) {
596 setNameFn(getResult(), "assume_align");
597}
598
599OpFoldResult AssumeAlignmentOp::fold(FoldAdaptor adaptor) {
600 auto source = getMemref().getDefiningOp<AssumeAlignmentOp>();
601 if (!source)
602 return {};
603 if (source.getAlignment() != getAlignment())
604 return {};
605 return getMemref();
606}
607
608FailureOr<std::optional<SmallVector<Value>>>
609AssumeAlignmentOp::bubbleDownCasts(OpBuilder &builder) {
610 return bubbleDownCastsPassthroughOpImpl(*this, builder, getMemrefMutable());
611}
612
613FailureOr<OpFoldResult> AssumeAlignmentOp::reifyDimOfResult(OpBuilder &builder,
614 int resultIndex,
615 int dim) {
616 assert(resultIndex == 0 && "AssumeAlignmentOp has a single result");
617 return getMixedSize(builder, getLoc(), getMemref(), dim);
618}
619
620//===----------------------------------------------------------------------===//
621// DistinctObjectsOp
622//===----------------------------------------------------------------------===//
623
624LogicalResult DistinctObjectsOp::verify() {
625 if (getOperandTypes() != getResultTypes())
626 return emitOpError("operand types and result types must match");
627
628 if (getOperandTypes().empty())
629 return emitOpError("expected at least one operand");
630
631 return success();
632}
633
634LogicalResult DistinctObjectsOp::inferReturnTypes(
635 MLIRContext * /*context*/, std::optional<Location> /*location*/,
636 ValueRange operands, DictionaryAttr /*attributes*/,
637 PropertyRef /*properties*/, RegionRange /*regions*/,
638 SmallVectorImpl<Type> &inferredReturnTypes) {
639 llvm::copy(operands.getTypes(), std::back_inserter(inferredReturnTypes));
640 return success();
641}
642
643//===----------------------------------------------------------------------===//
644// CastOp
645//===----------------------------------------------------------------------===//
646
647void CastOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
648 setNameFn(getResult(), "cast");
649}
650
651/// Determines whether MemRef_CastOp casts to a more dynamic version of the
652/// source memref. This is useful to fold a memref.cast into a consuming op
653/// and implement canonicalization patterns for ops in different dialects that
654/// may consume the results of memref.cast operations. Such foldable memref.cast
655/// operations are typically inserted as `view` and `subview` ops are
656/// canonicalized, to preserve the type compatibility of their uses.
657///
658/// Returns true when all conditions are met:
659/// 1. source and result are ranked memrefs with strided semantics and same
660/// element type and rank.
661/// 2. each of the source's size, offset or stride has more static information
662/// than the corresponding result's size, offset or stride.
663///
664/// Example 1:
665/// ```mlir
666/// %1 = memref.cast %0 : memref<8x16xf32> to memref<?x?xf32>
667/// %2 = consumer %1 ... : memref<?x?xf32> ...
668/// ```
669///
670/// may fold into:
671///
672/// ```mlir
673/// %2 = consumer %0 ... : memref<8x16xf32> ...
674/// ```
675///
676/// Example 2:
677/// ```
678/// %1 = memref.cast %0 : memref<?x16xf32, affine_map<(i, j)->(16 * i + j)>>
679/// to memref<?x?xf32>
680/// consumer %1 : memref<?x?xf32> ...
681/// ```
682///
683/// may fold into:
684///
685/// ```
686/// consumer %0 ... : memref<?x16xf32, affine_map<(i, j)->(16 * i + j)>>
687/// ```
688bool CastOp::canFoldIntoConsumerOp(CastOp castOp) {
689 MemRefType sourceType =
690 llvm::dyn_cast<MemRefType>(castOp.getSource().getType());
691 MemRefType resultType = llvm::dyn_cast<MemRefType>(castOp.getType());
692
693 // Requires ranked MemRefType.
694 if (!sourceType || !resultType)
695 return false;
696
697 // Requires same elemental type.
698 if (sourceType.getElementType() != resultType.getElementType())
699 return false;
700
701 // Requires same rank.
702 if (sourceType.getRank() != resultType.getRank())
703 return false;
704
705 // Only fold casts between strided memref forms.
706 int64_t sourceOffset, resultOffset;
707 SmallVector<int64_t, 4> sourceStrides, resultStrides;
708 if (failed(sourceType.getStridesAndOffset(sourceStrides, sourceOffset)) ||
709 failed(resultType.getStridesAndOffset(resultStrides, resultOffset)))
710 return false;
711
712 // If cast is towards more static sizes along any dimension, don't fold.
713 for (auto it : llvm::zip(sourceType.getShape(), resultType.getShape())) {
714 auto ss = std::get<0>(it), st = std::get<1>(it);
715 if (ss != st)
716 if (ShapedType::isDynamic(ss) && ShapedType::isStatic(st))
717 return false;
718 }
719
720 // If cast is towards more static offset along any dimension, don't fold.
721 if (sourceOffset != resultOffset)
722 if (ShapedType::isDynamic(sourceOffset) &&
723 ShapedType::isStatic(resultOffset))
724 return false;
725
726 // If cast is towards more static strides along any dimension, don't fold.
727 for (auto it : llvm::zip(sourceStrides, resultStrides)) {
728 auto ss = std::get<0>(it), st = std::get<1>(it);
729 if (ss != st)
730 if (ShapedType::isDynamic(ss) && ShapedType::isStatic(st))
731 return false;
732 }
733
734 return true;
735}
736
737bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
738 if (inputs.size() != 1 || outputs.size() != 1)
739 return false;
740 if (inputs == outputs)
741 return true;
742 Type a = inputs.front(), b = outputs.front();
743 auto aT = llvm::dyn_cast<MemRefType>(a);
744 auto bT = llvm::dyn_cast<MemRefType>(b);
745
746 auto uaT = llvm::dyn_cast<UnrankedMemRefType>(a);
747 auto ubT = llvm::dyn_cast<UnrankedMemRefType>(b);
748
749 if (aT && bT) {
750 if (aT.getElementType() != bT.getElementType())
751 return false;
752 if (aT.getLayout() != bT.getLayout()) {
753 int64_t aOffset, bOffset;
754 SmallVector<int64_t, 4> aStrides, bStrides;
755 if (failed(aT.getStridesAndOffset(aStrides, aOffset)) ||
756 failed(bT.getStridesAndOffset(bStrides, bOffset)) ||
757 aStrides.size() != bStrides.size())
758 return false;
759
760 // Strides along a dimension/offset are compatible if the value in the
761 // source memref is static and the value in the target memref is the
762 // same. They are also compatible if either one is dynamic (see
763 // description of MemRefCastOp for details).
764 // Note that for dimensions of size 1, the stride can differ.
765 auto checkCompatible = [](int64_t a, int64_t b) {
766 return (ShapedType::isDynamic(a) || ShapedType::isDynamic(b) || a == b);
767 };
768 if (!checkCompatible(aOffset, bOffset))
769 return false;
770 for (const auto &[index, aStride] : enumerate(aStrides)) {
771 if (aT.getDimSize(index) == 1 || bT.getDimSize(index) == 1)
772 continue;
773 if (!checkCompatible(aStride, bStrides[index]))
774 return false;
775 }
776 }
777 if (aT.getMemorySpace() != bT.getMemorySpace())
778 return false;
779
780 // They must have the same rank, and any specified dimensions must match.
781 if (aT.getRank() != bT.getRank())
782 return false;
783
784 for (unsigned i = 0, e = aT.getRank(); i != e; ++i) {
785 int64_t aDim = aT.getDimSize(i), bDim = bT.getDimSize(i);
786 if (ShapedType::isStatic(aDim) && ShapedType::isStatic(bDim) &&
787 aDim != bDim)
788 return false;
789 }
790 return true;
791 } else {
792 if (!aT && !uaT)
793 return false;
794 if (!bT && !ubT)
795 return false;
796 // Unranked to unranked casting is unsupported
797 if (uaT && ubT)
798 return false;
799
800 auto aEltType = (aT) ? aT.getElementType() : uaT.getElementType();
801 auto bEltType = (bT) ? bT.getElementType() : ubT.getElementType();
802 if (aEltType != bEltType)
803 return false;
804
805 auto aMemSpace = (aT) ? aT.getMemorySpace() : uaT.getMemorySpace();
806 auto bMemSpace = (bT) ? bT.getMemorySpace() : ubT.getMemorySpace();
807 return aMemSpace == bMemSpace;
808 }
809
810 return false;
811}
812
813OpFoldResult CastOp::fold(FoldAdaptor adaptor) {
814 return succeeded(foldMemRefCast(*this)) ? getResult() : Value();
815}
816
817FailureOr<std::optional<SmallVector<Value>>>
818CastOp::bubbleDownCasts(OpBuilder &builder) {
819 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSourceMutable());
820}
821
822//===----------------------------------------------------------------------===//
823// CopyOp
824//===----------------------------------------------------------------------===//
825
826namespace {
827
828/// Fold memref.copy(%x, %x).
829struct FoldSelfCopy : public OpRewritePattern<CopyOp> {
830 using OpRewritePattern<CopyOp>::OpRewritePattern;
831
832 LogicalResult matchAndRewrite(CopyOp copyOp,
833 PatternRewriter &rewriter) const override {
834 if (copyOp.getSource() != copyOp.getTarget())
835 return failure();
836
837 rewriter.eraseOp(copyOp);
838 return success();
839 }
840};
841
842struct FoldEmptyCopy final : public OpRewritePattern<CopyOp> {
843 using OpRewritePattern<CopyOp>::OpRewritePattern;
844
845 static bool isEmptyMemRef(BaseMemRefType type) {
846 return type.hasRank() && llvm::is_contained(type.getShape(), 0);
847 }
848
849 LogicalResult matchAndRewrite(CopyOp copyOp,
850 PatternRewriter &rewriter) const override {
851 if (isEmptyMemRef(copyOp.getSource().getType()) ||
852 isEmptyMemRef(copyOp.getTarget().getType())) {
853 rewriter.eraseOp(copyOp);
854 return success();
855 }
856
857 return failure();
858 }
859};
860} // namespace
861
862void CopyOp::getCanonicalizationPatterns(RewritePatternSet &results,
863 MLIRContext *context) {
864 results.add<FoldEmptyCopy, FoldSelfCopy>(context);
865}
866
867/// If the source/target of a CopyOp is a CastOp that does not modify the shape
868/// and element type, the cast can be skipped. Such CastOps only cast the layout
869/// of the type.
870static LogicalResult foldCopyOfCast(CopyOp op) {
871 for (OpOperand &operand : op->getOpOperands()) {
872 auto castOp = operand.get().getDefiningOp<memref::CastOp>();
873 if (castOp && memref::CastOp::canFoldIntoConsumerOp(castOp)) {
874 operand.set(castOp.getOperand());
875 return success();
876 }
877 }
878 return failure();
879}
880
881LogicalResult CopyOp::fold(FoldAdaptor adaptor,
882 SmallVectorImpl<OpFoldResult> &results) {
883
884 /// copy(memrefcast) -> copy
885 return foldCopyOfCast(*this);
886}
887
888//===----------------------------------------------------------------------===//
889// DeallocOp
890//===----------------------------------------------------------------------===//
891
892LogicalResult DeallocOp::fold(FoldAdaptor adaptor,
893 SmallVectorImpl<OpFoldResult> &results) {
894 /// dealloc(memrefcast) -> dealloc
895 return foldMemRefCast(*this);
896}
897
898//===----------------------------------------------------------------------===//
899// DimOp
900//===----------------------------------------------------------------------===//
901
902void DimOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
903 setNameFn(getResult(), "dim");
904}
905
906void DimOp::build(OpBuilder &builder, OperationState &result, Value source,
907 int64_t index) {
908 auto loc = result.location;
909 Value indexValue = arith::ConstantIndexOp::create(builder, loc, index);
910 build(builder, result, source, indexValue);
911}
912
913std::optional<int64_t> DimOp::getConstantIndex() {
915}
916
917Speculation::Speculatability DimOp::getSpeculatability() {
918 auto constantIndex = getConstantIndex();
919 if (!constantIndex)
921
922 auto rankedSourceType = dyn_cast<MemRefType>(getSource().getType());
923 if (!rankedSourceType)
925
926 if (rankedSourceType.getRank() <= constantIndex)
928
930}
931
932void DimOp::inferResultRangesFromOptional(ArrayRef<IntegerValueRange> argRanges,
933 SetIntLatticeFn setResultRange) {
934 setResultRange(getResult(),
935 intrange::inferShapedDimOpInterface(*this, argRanges[1]));
936}
937
938/// Return a map with key being elements in `vals` and data being number of
939/// occurences of it. Use std::map, since the `vals` here are strides and the
940/// dynamic stride value is the same as the tombstone value for
941/// `DenseMap<int64_t>`.
942static std::map<int64_t, unsigned> getNumOccurences(ArrayRef<int64_t> vals) {
943 std::map<int64_t, unsigned> numOccurences;
944 for (auto val : vals)
945 numOccurences[val]++;
946 return numOccurences;
947}
948
949/// Returns the set of source dimensions that are dropped in a rank reduction.
950/// For each result dimension in order, matches the leftmost unmatched source
951/// dimension with the same size. Source dimensions not matched are dropped.
952///
953/// Example: memref<1x8x1x3> to memref<1x8x3>. Source sizes [1, 8, 1, 3], result
954/// [1, 8, 3]. Match result[0]=1 -> source dim 0, result[1]=8 -> source dim 1,
955/// result[2]=3 -> source dim 3. Source dim 2 is unmatched and dropped.
956static FailureOr<llvm::SmallBitVector>
958 MemRefType reducedType,
960 int64_t rankReduction = originalType.getRank() - reducedType.getRank();
961 if (rankReduction <= 0)
962 return llvm::SmallBitVector(originalType.getRank());
963
964 // Build source sizes from subview sizes (one per source dim).
965 SmallVector<int64_t> sourceSizes(originalType.getRank());
966 for (const auto &it : llvm::enumerate(sizes)) {
967 if (std::optional<int64_t> cst = getConstantIntValue(it.value()))
968 sourceSizes[it.index()] = *cst;
969 else
970 sourceSizes[it.index()] = ShapedType::kDynamic;
971 }
972
973 ArrayRef<int64_t> resultSizes = reducedType.getShape();
974 llvm::SmallBitVector usedSourceDims(originalType.getRank());
975 int64_t startJ = 0;
976 for (int64_t resultSize : resultSizes) {
977 bool matched = false;
978 for (int64_t j = startJ; j < originalType.getRank(); ++j) {
979 if (sourceSizes[j] == resultSize) {
980 usedSourceDims.set(j);
981 matched = true;
982 startJ = j + 1;
983 break;
984 }
985 }
986 if (!matched)
987 return failure();
988 }
989
990 llvm::SmallBitVector unusedDims(originalType.getRank());
991 for (int64_t i = 0; i < originalType.getRank(); ++i)
992 if (!usedSourceDims.test(i))
993 unusedDims.set(i);
994 return unusedDims;
995}
996
997/// Returns the set of source dimensions that are dropped in a rank reduction.
998/// A dimension is dropped if its stride is dropped; uses stride occurrence
999/// counting to disambiguate when multiple unit dims exist.
1000///
1001/// Example: memref<1x1x?xf32, strided<[?, 4, 1]>> to memref<1x4xf32,
1002/// strided<[4, 1]>>. Source strides [?, 4, 1], candidate [4, 1]. Dim 0 (stride
1003/// ?) can be dropped; dim 1 (stride 4) must be kept. Source dim 0 is dropped.
1004static FailureOr<llvm::SmallBitVector> computeMemRefRankReductionMaskByStrides(
1005 MemRefType originalType, MemRefType reducedType,
1006 ArrayRef<int64_t> originalStrides, ArrayRef<int64_t> candidateStrides,
1007 llvm::SmallBitVector unusedDims) {
1008 // Track the number of occurences of the strides in the original type
1009 // and the candidate type. For each unused dim that stride should not be
1010 // present in the candidate type. Note that there could be multiple dimensions
1011 // that have the same size. We dont need to exactly figure out which dim
1012 // corresponds to which stride, we just need to verify that the number of
1013 // reptitions of a stride in the original + number of unused dims with that
1014 // stride == number of repititions of a stride in the candidate.
1015 std::map<int64_t, unsigned> currUnaccountedStrides =
1016 getNumOccurences(originalStrides);
1017 std::map<int64_t, unsigned> candidateStridesNumOccurences =
1018 getNumOccurences(candidateStrides);
1019 for (size_t dim = 0, e = unusedDims.size(); dim != e; ++dim) {
1020 if (!unusedDims.test(dim))
1021 continue;
1022 int64_t originalStride = originalStrides[dim];
1023 if (currUnaccountedStrides[originalStride] >
1024 candidateStridesNumOccurences[originalStride]) {
1025 // This dim can be treated as dropped.
1026 currUnaccountedStrides[originalStride]--;
1027 continue;
1028 }
1029 if (currUnaccountedStrides[originalStride] ==
1030 candidateStridesNumOccurences[originalStride]) {
1031 // The stride for this is not dropped. Keep as is.
1032 unusedDims.reset(dim);
1033 continue;
1034 }
1035 if (currUnaccountedStrides[originalStride] <
1036 candidateStridesNumOccurences[originalStride]) {
1037 // This should never happen. Cant have a stride in the reduced rank type
1038 // that wasnt in the original one.
1039 return failure();
1040 }
1041 }
1042 if (static_cast<int64_t>(unusedDims.count()) + reducedType.getRank() !=
1043 originalType.getRank())
1044 return failure();
1045 return unusedDims;
1046}
1047
1048/// Given the `originalType` and a `candidateReducedType` whose shape is assumed
1049/// to be a subset of `originalType` with some `1` entries erased, return the
1050/// set of indices that specifies which of the entries of `originalShape` are
1051/// dropped to obtain `reducedShape`.
1052/// This accounts for cases where there are multiple unit-dims, but only a
1053/// subset of those are dropped. For MemRefTypes these can be disambiguated
1054/// using the strides. If a dimension is dropped the stride must be dropped too.
1055static FailureOr<llvm::SmallBitVector>
1056computeMemRefRankReductionMask(MemRefType originalType, MemRefType reducedType,
1057 ArrayRef<OpFoldResult> sizes) {
1058 llvm::SmallBitVector unusedDims(originalType.getRank());
1059 if (originalType.getRank() == reducedType.getRank())
1060 return unusedDims;
1061
1062 for (const auto &dim : llvm::enumerate(sizes))
1063 if (auto attr = llvm::dyn_cast_if_present<Attribute>(dim.value()))
1064 if (llvm::cast<IntegerAttr>(attr).getInt() == 1)
1065 unusedDims.set(dim.index());
1066
1067 // Early exit for the case where the number of unused dims matches the number
1068 // of ranks reduced.
1069 if (static_cast<int64_t>(unusedDims.count()) + reducedType.getRank() ==
1070 originalType.getRank())
1071 return unusedDims;
1072
1073 SmallVector<int64_t> originalStrides, candidateStrides;
1074 int64_t originalOffset, candidateOffset;
1075 if (failed(
1076 originalType.getStridesAndOffset(originalStrides, originalOffset)) ||
1077 failed(
1078 reducedType.getStridesAndOffset(candidateStrides, candidateOffset)))
1079 return failure();
1080
1081 // Try stride-based first when we have meaningful static stride info
1082 // (preserves static strides). Fall back to position-based otherwise.
1083 auto hasNonTrivialStaticStride = [](ArrayRef<int64_t> strides) {
1084 // The innermost stride 1 is trivial for row-major and does not help
1085 // disambiguate.
1086 if (strides.size() <= 1)
1087 return false;
1088 return llvm::any_of(strides.drop_back(),
1089 [](int64_t s) { return !ShapedType::isDynamic(s); });
1090 };
1091 if (hasNonTrivialStaticStride(originalStrides) ||
1092 hasNonTrivialStaticStride(candidateStrides)) {
1093 FailureOr<llvm::SmallBitVector> strideBased =
1094 computeMemRefRankReductionMaskByStrides(originalType, reducedType,
1095 originalStrides,
1096 candidateStrides, unusedDims);
1097 if (succeeded(strideBased))
1098 return *strideBased;
1099 }
1100 return computeMemRefRankReductionMaskByPosition(originalType, reducedType,
1101 sizes);
1102}
1103
1104llvm::SmallBitVector SubViewOp::getDroppedDims() {
1105 MemRefType sourceType = getSourceType();
1106 MemRefType resultType = getType();
1107 FailureOr<llvm::SmallBitVector> unusedDims =
1108 computeMemRefRankReductionMask(sourceType, resultType, getMixedSizes());
1109 assert(succeeded(unusedDims) && "unable to find unused dims of subview");
1110 return *unusedDims;
1111}
1112
1113OpFoldResult DimOp::fold(FoldAdaptor adaptor) {
1114 // All forms of folding require a known index.
1115 std::optional<int64_t> index = getConstantIndex();
1116 if (!index)
1117 return {};
1118
1119 // Folding for unranked types (UnrankedMemRefType) is not supported.
1120 auto memrefType = llvm::dyn_cast<MemRefType>(getSource().getType());
1121 if (!memrefType)
1122 return {};
1123
1124 // Out of bound indices produce undefined behavior but are still valid IR.
1125 // Don't choke on them.
1126 int64_t indexVal = index.value();
1127 if (indexVal < 0 || indexVal >= memrefType.getRank())
1128 return {};
1129
1130 // Fold if the shape extent along the given index is known.
1131 if (!memrefType.isDynamicDim(indexVal)) {
1132 Builder builder(getContext());
1133 return builder.getIndexAttr(memrefType.getShape()[indexVal]);
1134 }
1135
1136 // The size at the given index is now known to be a dynamic size.
1137 // Fold dim to the size argument for an `AllocOp`, `ViewOp`, or `SubViewOp`.
1138 Operation *definingOp = getSource().getDefiningOp();
1139
1140 if (auto alloc = dyn_cast_or_null<AllocOp>(definingOp))
1141 return *(alloc.getDynamicSizes().begin() +
1142 memrefType.getDynamicDimIndex(indexVal));
1143
1144 if (auto alloca = dyn_cast_or_null<AllocaOp>(definingOp))
1145 return *(alloca.getDynamicSizes().begin() +
1146 memrefType.getDynamicDimIndex(indexVal));
1147
1148 if (auto view = dyn_cast_or_null<ViewOp>(definingOp))
1149 return *(view.getDynamicSizes().begin() +
1150 memrefType.getDynamicDimIndex(indexVal));
1151
1152 if (auto subview = dyn_cast_or_null<SubViewOp>(definingOp)) {
1153 // The result dim is dynamic (the static case was handled above). Dropped
1154 // dims always have static size 1, so dynamic source sizes are never
1155 // dropped and map in order to the dynamic result dims. Find the k-th
1156 // dynamic source size, where k is the dynamic dim index of the result dim.
1157 unsigned dynamicResultDimIdx = memrefType.getDynamicDimIndex(indexVal);
1158 unsigned dynamicIdx = 0;
1159 for (OpFoldResult size : subview.getMixedSizes()) {
1160 if (llvm::isa<Attribute>(size))
1161 continue;
1162 if (dynamicIdx == dynamicResultDimIdx)
1163 return size;
1164 dynamicIdx++;
1165 }
1166 return {};
1167 }
1168
1169 // dim(memrefcast) -> dim
1170 if (succeeded(foldMemRefCast(*this)))
1171 return getResult();
1172
1173 return {};
1174}
1175
1176namespace {
1177/// Fold dim of a memref reshape operation to a load into the reshape's shape
1178/// operand.
1179struct DimOfMemRefReshape : public OpRewritePattern<DimOp> {
1180 using OpRewritePattern<DimOp>::OpRewritePattern;
1181
1182 LogicalResult matchAndRewrite(DimOp dim,
1183 PatternRewriter &rewriter) const override {
1184 auto reshape = dim.getSource().getDefiningOp<ReshapeOp>();
1185
1186 if (!reshape)
1187 return rewriter.notifyMatchFailure(
1188 dim, "Dim op is not defined by a reshape op.");
1189
1190 // dim of a memref reshape can be folded if dim.getIndex() dominates the
1191 // reshape. Instead of using `DominanceInfo` (which is usually costly) we
1192 // cheaply check that either of the following conditions hold:
1193 // 1. dim.getIndex() is defined in the same block as reshape but before
1194 // reshape.
1195 // 2. dim.getIndex() is defined in a parent block of
1196 // reshape.
1197
1198 // Check condition 1
1199 if (dim.getIndex().getParentBlock() == reshape->getBlock()) {
1200 if (auto *definingOp = dim.getIndex().getDefiningOp()) {
1201 if (reshape->isBeforeInBlock(definingOp)) {
1202 return rewriter.notifyMatchFailure(
1203 dim,
1204 "dim.getIndex is not defined before reshape in the same block.");
1205 }
1206 } // else dim.getIndex is a block argument to reshape->getBlock and
1207 // dominates reshape
1208 } // Check condition 2
1209 else if (dim->getBlock() != reshape->getBlock() &&
1210 !dim.getIndex().getParentRegion()->isProperAncestor(
1211 reshape->getParentRegion())) {
1212 // If dim and reshape are in the same block but dim.getIndex() isn't, we
1213 // already know dim.getIndex() dominates reshape without calling
1214 // `isProperAncestor`
1215 return rewriter.notifyMatchFailure(
1216 dim, "dim.getIndex does not dominate reshape.");
1217 }
1218
1219 // Place the load directly after the reshape to ensure that the shape memref
1220 // was not mutated.
1221 rewriter.setInsertionPointAfter(reshape);
1222 Location loc = dim.getLoc();
1223 Value load =
1224 LoadOp::create(rewriter, loc, reshape.getShape(), dim.getIndex());
1225 if (load.getType() != dim.getType())
1226 load = arith::IndexCastOp::create(rewriter, loc, dim.getType(), load);
1227 rewriter.replaceOp(dim, load);
1228 return success();
1229 }
1230};
1231
1232} // namespace
1233
1234void DimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1235 MLIRContext *context) {
1236 results.add<DimOfMemRefReshape>(context);
1237}
1238
1239// ---------------------------------------------------------------------------
1240// DmaStartOp
1241// ---------------------------------------------------------------------------
1242
1243void DmaStartOp::build(OpBuilder &builder, OperationState &result,
1244 Value srcMemRef, ValueRange srcIndices, Value destMemRef,
1245 ValueRange destIndices, Value numElements,
1246 Value tagMemRef, ValueRange tagIndices, Value stride,
1247 Value elementsPerStride) {
1248 result.addOperands(srcMemRef);
1249 result.addOperands(srcIndices);
1250 result.addOperands(destMemRef);
1251 result.addOperands(destIndices);
1252 result.addOperands({numElements, tagMemRef});
1253 result.addOperands(tagIndices);
1254 if (stride)
1255 result.addOperands({stride, elementsPerStride});
1256}
1257
1258void DmaStartOp::print(OpAsmPrinter &p) {
1259 p << " " << getSrcMemRef() << '[' << getSrcIndices() << "], "
1260 << getDstMemRef() << '[' << getDstIndices() << "], " << getNumElements()
1261 << ", " << getTagMemRef() << '[' << getTagIndices() << ']';
1262 if (isStrided())
1263 p << ", " << getStride() << ", " << getNumElementsPerStride();
1264
1265 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary());
1266 p << " : " << getSrcMemRef().getType() << ", " << getDstMemRef().getType()
1267 << ", " << getTagMemRef().getType();
1268}
1269
1270// Parse DmaStartOp.
1271// Ex:
1272// %dma_id = dma_start %src[%i, %j], %dst[%k, %l], %size,
1273// %tag[%index], %stride, %num_elt_per_stride :
1274// : memref<3076 x f32, 0>,
1275// memref<1024 x f32, 2>,
1276// memref<1 x i32>
1277//
1278ParseResult DmaStartOp::parse(OpAsmParser &parser, OperationState &result) {
1279 OpAsmParser::UnresolvedOperand srcMemRefInfo;
1280 SmallVector<OpAsmParser::UnresolvedOperand, 4> srcIndexInfos;
1281 OpAsmParser::UnresolvedOperand dstMemRefInfo;
1282 SmallVector<OpAsmParser::UnresolvedOperand, 4> dstIndexInfos;
1283 OpAsmParser::UnresolvedOperand numElementsInfo;
1284 OpAsmParser::UnresolvedOperand tagMemrefInfo;
1285 SmallVector<OpAsmParser::UnresolvedOperand, 4> tagIndexInfos;
1286 SmallVector<OpAsmParser::UnresolvedOperand, 2> strideInfo;
1287
1288 SmallVector<Type, 3> types;
1289 auto indexType = parser.getBuilder().getIndexType();
1290
1291 // Parse and resolve the following list of operands:
1292 // *) source memref followed by its indices (in square brackets).
1293 // *) destination memref followed by its indices (in square brackets).
1294 // *) dma size in KiB.
1295 if (parser.parseOperand(srcMemRefInfo) ||
1296 parser.parseOperandList(srcIndexInfos, OpAsmParser::Delimiter::Square) ||
1297 parser.parseComma() || parser.parseOperand(dstMemRefInfo) ||
1298 parser.parseOperandList(dstIndexInfos, OpAsmParser::Delimiter::Square) ||
1299 parser.parseComma() || parser.parseOperand(numElementsInfo) ||
1300 parser.parseComma() || parser.parseOperand(tagMemrefInfo) ||
1301 parser.parseOperandList(tagIndexInfos, OpAsmParser::Delimiter::Square))
1302 return failure();
1303
1304 // Parse optional stride and elements per stride.
1305 if (parser.parseTrailingOperandList(strideInfo))
1306 return failure();
1307
1308 bool isStrided = strideInfo.size() == 2;
1309 if (!strideInfo.empty() && !isStrided) {
1310 return parser.emitError(parser.getNameLoc(),
1311 "expected two stride related operands");
1312 }
1313
1314 if (parser.parseColonTypeList(types))
1315 return failure();
1316 if (types.size() != 3)
1317 return parser.emitError(parser.getNameLoc(), "fewer/more types expected");
1318
1319 if (parser.resolveOperand(srcMemRefInfo, types[0], result.operands) ||
1320 parser.resolveOperands(srcIndexInfos, indexType, result.operands) ||
1321 parser.resolveOperand(dstMemRefInfo, types[1], result.operands) ||
1322 parser.resolveOperands(dstIndexInfos, indexType, result.operands) ||
1323 // size should be an index.
1324 parser.resolveOperand(numElementsInfo, indexType, result.operands) ||
1325 parser.resolveOperand(tagMemrefInfo, types[2], result.operands) ||
1326 // tag indices should be index.
1327 parser.resolveOperands(tagIndexInfos, indexType, result.operands))
1328 return failure();
1329
1330 if (isStrided) {
1331 if (parser.resolveOperands(strideInfo, indexType, result.operands))
1332 return failure();
1333 }
1334
1335 return success();
1336}
1337
1338LogicalResult DmaStartOp::verify() {
1339 unsigned numOperands = getNumOperands();
1340
1341 // Mandatory non-variadic operands are: src memref, dst memref, tag memref and
1342 // the number of elements.
1343 if (numOperands < 4)
1344 return emitOpError("expected at least 4 operands");
1345
1346 // Check types of operands. The order of these calls is important: the later
1347 // calls rely on some type properties to compute the operand position.
1348 // 1. Source memref.
1349 if (!llvm::isa<MemRefType>(getSrcMemRef().getType()))
1350 return emitOpError("expected source to be of memref type");
1351 if (numOperands < getSrcMemRefRank() + 4)
1352 return emitOpError() << "expected at least " << getSrcMemRefRank() + 4
1353 << " operands";
1354 if (!getSrcIndices().empty() &&
1355 !llvm::all_of(getSrcIndices().getTypes(),
1356 [](Type t) { return t.isIndex(); }))
1357 return emitOpError("expected source indices to be of index type");
1358
1359 // 2. Destination memref.
1360 if (!llvm::isa<MemRefType>(getDstMemRef().getType()))
1361 return emitOpError("expected destination to be of memref type");
1362 unsigned numExpectedOperands = getSrcMemRefRank() + getDstMemRefRank() + 4;
1363 if (numOperands < numExpectedOperands)
1364 return emitOpError() << "expected at least " << numExpectedOperands
1365 << " operands";
1366 if (!getDstIndices().empty() &&
1367 !llvm::all_of(getDstIndices().getTypes(),
1368 [](Type t) { return t.isIndex(); }))
1369 return emitOpError("expected destination indices to be of index type");
1370
1371 // 3. Number of elements.
1372 if (!getNumElements().getType().isIndex())
1373 return emitOpError("expected num elements to be of index type");
1374
1375 // 4. Tag memref.
1376 if (!llvm::isa<MemRefType>(getTagMemRef().getType()))
1377 return emitOpError("expected tag to be of memref type");
1378 numExpectedOperands += getTagMemRefRank();
1379 if (numOperands < numExpectedOperands)
1380 return emitOpError() << "expected at least " << numExpectedOperands
1381 << " operands";
1382 if (!getTagIndices().empty() &&
1383 !llvm::all_of(getTagIndices().getTypes(),
1384 [](Type t) { return t.isIndex(); }))
1385 return emitOpError("expected tag indices to be of index type");
1386
1387 // Optional stride-related operands must be either both present or both
1388 // absent.
1389 if (numOperands != numExpectedOperands &&
1390 numOperands != numExpectedOperands + 2)
1391 return emitOpError("incorrect number of operands");
1392
1393 // 5. Strides.
1394 if (isStrided()) {
1395 if (!getStride().getType().isIndex() ||
1396 !getNumElementsPerStride().getType().isIndex())
1397 return emitOpError(
1398 "expected stride and num elements per stride to be of type index");
1399 }
1400
1401 return success();
1402}
1403
1404LogicalResult DmaStartOp::fold(FoldAdaptor adaptor,
1405 SmallVectorImpl<OpFoldResult> &results) {
1406 /// dma_start(memrefcast) -> dma_start
1407 return foldMemRefCast(*this);
1408}
1409
1410void DmaStartOp::setMemrefsAndIndices(RewriterBase &rewriter, Value newSrc,
1411 ValueRange newSrcIndices, Value newDst,
1412 ValueRange newDstIndices) {
1413 /// dma_start has special handling for variadic rank
1414 SmallVector<Value> newOperands;
1415 newOperands.push_back(newSrc);
1416 llvm::append_range(newOperands, newSrcIndices);
1417 newOperands.push_back(newDst);
1418 llvm::append_range(newOperands, newDstIndices);
1419 newOperands.push_back(getNumElements());
1420 newOperands.push_back(getTagMemRef());
1421 llvm::append_range(newOperands, getTagIndices());
1422 if (isStrided()) {
1423 newOperands.push_back(getStride());
1424 newOperands.push_back(getNumElementsPerStride());
1425 }
1426
1427 rewriter.modifyOpInPlace(*this, [&]() { (*this)->setOperands(newOperands); });
1428}
1429
1430// ---------------------------------------------------------------------------
1431// DmaWaitOp
1432// ---------------------------------------------------------------------------
1433
1434LogicalResult DmaWaitOp::fold(FoldAdaptor adaptor,
1435 SmallVectorImpl<OpFoldResult> &results) {
1436 /// dma_wait(memrefcast) -> dma_wait
1437 return foldMemRefCast(*this);
1438}
1439
1440LogicalResult DmaWaitOp::verify() {
1441 // Check that the number of tag indices matches the tagMemRef rank.
1442 unsigned numTagIndices = getTagIndices().size();
1443 unsigned tagMemRefRank = getTagMemRefRank();
1444 if (numTagIndices != tagMemRefRank)
1445 return emitOpError() << "expected tagIndices to have the same number of "
1446 "elements as the tagMemRef rank, expected "
1447 << tagMemRefRank << ", but got " << numTagIndices;
1448 return success();
1449}
1450
1451//===----------------------------------------------------------------------===//
1452// ExtractAlignedPointerAsIndexOp
1453//===----------------------------------------------------------------------===//
1454
1455void ExtractAlignedPointerAsIndexOp::getAsmResultNames(
1456 function_ref<void(Value, StringRef)> setNameFn) {
1457 setNameFn(getResult(), "intptr");
1458}
1459
1460//===----------------------------------------------------------------------===//
1461// ExtractStridedMetadataOp
1462//===----------------------------------------------------------------------===//
1463
1464/// The number and type of the results are inferred from the
1465/// shape of the source.
1466LogicalResult ExtractStridedMetadataOp::inferReturnTypes(
1467 MLIRContext *context, std::optional<Location> location,
1468 ExtractStridedMetadataOp::Adaptor adaptor,
1469 SmallVectorImpl<Type> &inferredReturnTypes) {
1470 auto sourceType = llvm::dyn_cast<MemRefType>(adaptor.getSource().getType());
1471 if (!sourceType)
1472 return failure();
1473
1474 unsigned sourceRank = sourceType.getRank();
1475 IndexType indexType = IndexType::get(context);
1476 auto memrefType =
1477 MemRefType::get({}, sourceType.getElementType(),
1478 MemRefLayoutAttrInterface{}, sourceType.getMemorySpace());
1479 // Base.
1480 inferredReturnTypes.push_back(memrefType);
1481 // Offset.
1482 inferredReturnTypes.push_back(indexType);
1483 // Sizes and strides.
1484 for (unsigned i = 0; i < sourceRank * 2; ++i)
1485 inferredReturnTypes.push_back(indexType);
1486 return success();
1487}
1488
1489void ExtractStridedMetadataOp::getAsmResultNames(
1490 function_ref<void(Value, StringRef)> setNameFn) {
1491 setNameFn(getBaseBuffer(), "base_buffer");
1492 setNameFn(getOffset(), "offset");
1493 // For multi-result to work properly with pretty names and packed syntax `x:3`
1494 // we can only give a pretty name to the first value in the pack.
1495 if (!getSizes().empty()) {
1496 setNameFn(getSizes().front(), "sizes");
1497 setNameFn(getStrides().front(), "strides");
1498 }
1499}
1500
1501/// Helper function to perform the replacement of all constant uses of `values`
1502/// by a materialized constant extracted from `maybeConstants`.
1503/// `values` and `maybeConstants` are expected to have the same size.
1504template <typename Container>
1505static bool replaceConstantUsesOf(OpBuilder &rewriter, Location loc,
1506 Container values,
1507 ArrayRef<OpFoldResult> maybeConstants) {
1508 assert(values.size() == maybeConstants.size() &&
1509 " expected values and maybeConstants of the same size");
1510 bool atLeastOneReplacement = false;
1511 for (auto [maybeConstant, result] : llvm::zip(maybeConstants, values)) {
1512 // Don't materialize a constant if there are no uses: this would indice
1513 // infinite loops in the driver.
1514 if (result.use_empty() || maybeConstant == getAsOpFoldResult(result))
1515 continue;
1516 assert(isa<Attribute>(maybeConstant) &&
1517 "The constified value should be either unchanged (i.e., == result) "
1518 "or a constant");
1520 rewriter, loc,
1521 llvm::cast<IntegerAttr>(cast<Attribute>(maybeConstant)).getInt());
1522 for (Operation *op : llvm::make_early_inc_range(result.getUsers())) {
1523 // modifyOpInPlace: lambda cannot capture structured bindings in C++17
1524 // yet.
1525 op->replaceUsesOfWith(result, constantVal);
1526 atLeastOneReplacement = true;
1527 }
1528 }
1529 return atLeastOneReplacement;
1530}
1531
1532LogicalResult
1533ExtractStridedMetadataOp::fold(FoldAdaptor adaptor,
1534 SmallVectorImpl<OpFoldResult> &results) {
1535 OpBuilder builder(*this);
1536
1537 bool atLeastOneReplacement = replaceConstantUsesOf(
1538 builder, getLoc(), ArrayRef<TypedValue<IndexType>>(getOffset()),
1539 getConstifiedMixedOffset());
1540 atLeastOneReplacement |= replaceConstantUsesOf(builder, getLoc(), getSizes(),
1541 getConstifiedMixedSizes());
1542 atLeastOneReplacement |= replaceConstantUsesOf(
1543 builder, getLoc(), getStrides(), getConstifiedMixedStrides());
1544
1545 // extract_strided_metadata(cast(x)) -> extract_strided_metadata(x).
1546 if (auto prev = getSource().getDefiningOp<CastOp>())
1547 if (isa<MemRefType>(prev.getSource().getType())) {
1548 getSourceMutable().assign(prev.getSource());
1549 atLeastOneReplacement = true;
1550 }
1551
1552 return success(atLeastOneReplacement);
1553}
1554
1555SmallVector<OpFoldResult> ExtractStridedMetadataOp::getConstifiedMixedSizes() {
1556 SmallVector<OpFoldResult> values = getAsOpFoldResult(getSizes());
1557 constifyIndexValues(values, getSource().getType().getShape());
1558 return values;
1559}
1560
1561SmallVector<OpFoldResult>
1562ExtractStridedMetadataOp::getConstifiedMixedStrides() {
1563 SmallVector<OpFoldResult> values = getAsOpFoldResult(getStrides());
1564 SmallVector<int64_t> staticValues;
1565 int64_t unused;
1566 LogicalResult status =
1567 getSource().getType().getStridesAndOffset(staticValues, unused);
1568 (void)status;
1569 assert(succeeded(status) && "could not get strides from type");
1570 constifyIndexValues(values, staticValues);
1571 return values;
1572}
1573
1574OpFoldResult ExtractStridedMetadataOp::getConstifiedMixedOffset() {
1575 OpFoldResult offsetOfr = getAsOpFoldResult(getOffset());
1576 SmallVector<OpFoldResult> values(1, offsetOfr);
1577 SmallVector<int64_t> staticValues, unused;
1578 int64_t offset;
1579 LogicalResult status =
1580 getSource().getType().getStridesAndOffset(unused, offset);
1581 (void)status;
1582 assert(succeeded(status) && "could not get offset from type");
1583 staticValues.push_back(offset);
1584 constifyIndexValues(values, staticValues);
1585 return values[0];
1586}
1587
1588//===----------------------------------------------------------------------===//
1589// GenericAtomicRMWOp
1590//===----------------------------------------------------------------------===//
1591
1592void GenericAtomicRMWOp::build(OpBuilder &builder, OperationState &result,
1593 Value memref, ValueRange ivs) {
1594 OpBuilder::InsertionGuard g(builder);
1595 result.addOperands(memref);
1596 result.addOperands(ivs);
1597
1598 if (auto memrefType = llvm::dyn_cast<MemRefType>(memref.getType())) {
1599 Type elementType = memrefType.getElementType();
1600 result.addTypes(elementType);
1601
1602 Region *bodyRegion = result.addRegion();
1603 builder.createBlock(bodyRegion);
1604 bodyRegion->addArgument(elementType, memref.getLoc());
1605 }
1606}
1607
1608LogicalResult GenericAtomicRMWOp::verify() {
1609 auto &body = getRegion();
1610 if (body.getNumArguments() != 1)
1611 return emitOpError("expected single number of entry block arguments");
1612
1613 if (getResult().getType() != body.getArgument(0).getType())
1614 return emitOpError("expected block argument of the same type result type");
1615
1616 bool hasSideEffects =
1617 body.walk([&](Operation *nestedOp) {
1618 if (isMemoryEffectFree(nestedOp))
1619 return WalkResult::advance();
1620 nestedOp->emitError(
1621 "body of 'memref.generic_atomic_rmw' should contain "
1622 "only operations with no side effects");
1623 return WalkResult::interrupt();
1624 })
1625 .wasInterrupted();
1626 return hasSideEffects ? failure() : success();
1627}
1628
1629ParseResult GenericAtomicRMWOp::parse(OpAsmParser &parser,
1630 OperationState &result) {
1631 OpAsmParser::UnresolvedOperand memref;
1632 Type memrefType;
1633 SmallVector<OpAsmParser::UnresolvedOperand, 4> ivs;
1634
1635 Type indexType = parser.getBuilder().getIndexType();
1636 if (parser.parseOperand(memref) ||
1638 parser.parseColonType(memrefType) ||
1639 parser.resolveOperand(memref, memrefType, result.operands) ||
1640 parser.resolveOperands(ivs, indexType, result.operands))
1641 return failure();
1642
1643 Region *body = result.addRegion();
1644 if (parser.parseRegion(*body, {}) ||
1645 parser.parseOptionalAttrDict(result.attributes))
1646 return failure();
1647 result.types.push_back(llvm::cast<MemRefType>(memrefType).getElementType());
1648 return success();
1649}
1650
1651void GenericAtomicRMWOp::print(OpAsmPrinter &p) {
1652 p << ' ' << getMemref() << "[" << getIndices()
1653 << "] : " << getMemref().getType() << ' ';
1654 p.printRegion(getRegion());
1655 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary());
1656}
1657
1658TypedValue<MemRefType> GenericAtomicRMWOp::getAccessedMemref() {
1659 return getMemref();
1660}
1661
1662std::optional<SmallVector<Value>> GenericAtomicRMWOp::updateMemrefAndIndices(
1663 RewriterBase &rewriter, Value newMemref, ValueRange newIndices) {
1664 rewriter.modifyOpInPlace(*this, [&]() {
1665 getMemrefMutable().assign(newMemref);
1666 getIndicesMutable().assign(newIndices);
1667 });
1668 return std::nullopt;
1669}
1670
1671//===----------------------------------------------------------------------===//
1672// AtomicYieldOp
1673//===----------------------------------------------------------------------===//
1674
1675LogicalResult AtomicYieldOp::verify() {
1676 Type parentType = (*this)->getParentOp()->getResultTypes().front();
1677 Type resultType = getResult().getType();
1678 if (parentType != resultType)
1679 return emitOpError() << "types mismatch between yield op: " << resultType
1680 << " and its parent: " << parentType;
1681 return success();
1682}
1683
1684//===----------------------------------------------------------------------===//
1685// GlobalOp
1686//===----------------------------------------------------------------------===//
1687
1689 TypeAttr type,
1690 Attribute initialValue) {
1691 p << type;
1692 if (!op.isExternal()) {
1693 p << " = ";
1694 if (op.isUninitialized())
1695 p << "uninitialized";
1696 else
1697 p.printAttributeWithoutType(initialValue);
1698 }
1699}
1700
1701static ParseResult
1703 Attribute &initialValue) {
1704 Type type;
1705 if (parser.parseType(type))
1706 return failure();
1707
1708 auto memrefType = llvm::dyn_cast<MemRefType>(type);
1709 if (!memrefType || !memrefType.hasStaticShape())
1710 return parser.emitError(parser.getNameLoc())
1711 << "type should be static shaped memref, but got " << type;
1712 typeAttr = TypeAttr::get(type);
1713
1714 if (parser.parseOptionalEqual())
1715 return success();
1716
1717 if (succeeded(parser.parseOptionalKeyword("uninitialized"))) {
1718 initialValue = UnitAttr::get(parser.getContext());
1719 return success();
1720 }
1721
1722 Type tensorType = getTensorTypeFromMemRefType(memrefType);
1723 if (parser.parseAttribute(initialValue, tensorType))
1724 return failure();
1725 if (!llvm::isa<ElementsAttr>(initialValue))
1726 return parser.emitError(parser.getNameLoc())
1727 << "initial value should be a unit or elements attribute";
1728 return success();
1729}
1730
1731LogicalResult GlobalOp::verify() {
1732 auto memrefType = llvm::dyn_cast<MemRefType>(getType());
1733 if (!memrefType || !memrefType.hasStaticShape())
1734 return emitOpError("type should be static shaped memref, but got ")
1735 << getType();
1736
1737 // Verify that the initial value, if present, is either a unit attribute or
1738 // an elements attribute.
1739 if (getInitialValue().has_value()) {
1740 Attribute initValue = getInitialValue().value();
1741 if (!llvm::isa<UnitAttr>(initValue) && !llvm::isa<ElementsAttr>(initValue))
1742 return emitOpError("initial value should be a unit or elements "
1743 "attribute, but got ")
1744 << initValue;
1745
1746 // Check that the type of the initial value is compatible with the type of
1747 // the global variable.
1748 if (auto elementsAttr = llvm::dyn_cast<ElementsAttr>(initValue)) {
1749 // Check the element types match.
1750 auto initElementType =
1751 cast<TensorType>(elementsAttr.getType()).getElementType();
1752 auto memrefElementType = memrefType.getElementType();
1753
1754 if (initElementType != memrefElementType)
1755 return emitOpError("initial value element expected to be of type ")
1756 << memrefElementType << ", but was of type " << initElementType;
1757
1758 // Check the shapes match, given that memref globals can only produce
1759 // statically shaped memrefs and elements literal type must have a static
1760 // shape we can assume both types are shaped.
1761 auto initShape = elementsAttr.getShapedType().getShape();
1762 auto memrefShape = memrefType.getShape();
1763 if (initShape != memrefShape)
1764 return emitOpError("initial value shape expected to be ")
1765 << memrefShape << " but was " << initShape;
1766 }
1767 }
1768
1769 // TODO: verify visibility for declarations.
1770 return success();
1771}
1772
1773ElementsAttr GlobalOp::getConstantInitValue() {
1774 auto initVal = getInitialValue();
1775 if (getConstant() && initVal.has_value())
1776 return llvm::cast<ElementsAttr>(initVal.value());
1777 return {};
1778}
1779
1780//===----------------------------------------------------------------------===//
1781// GetGlobalOp
1782//===----------------------------------------------------------------------===//
1783
1784LogicalResult
1785GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1786 // Verify that the result type is same as the type of the referenced
1787 // memref.global op.
1788 auto global =
1789 symbolTable.lookupNearestSymbolFrom<GlobalOp>(*this, getNameAttr());
1790 if (!global)
1791 return emitOpError("'")
1792 << getName() << "' does not reference a valid global memref";
1793
1794 Type resultType = getResult().getType();
1795 if (global.getType() != resultType)
1796 return emitOpError("result type ")
1797 << resultType << " does not match type " << global.getType()
1798 << " of the global memref @" << getName();
1799 return success();
1800}
1801
1802//===----------------------------------------------------------------------===//
1803// LoadOp
1804//===----------------------------------------------------------------------===//
1805
1806static ParseResult parseBoolAttr(OpAsmParser &parser, BoolAttr &result) {
1807 Attribute attr;
1808 if (parser.parseAttribute(attr))
1809 return failure();
1810 result = dyn_cast<BoolAttr>(attr);
1811 if (!result)
1812 return parser.emitError(parser.getCurrentLocation(),
1813 "expected boolean attribute");
1814 return success();
1815}
1816
1817static void printBoolAttr(OpAsmPrinter &printer, Operation *, BoolAttr attr) {
1818 printer.printAttribute(attr);
1819}
1820
1821OpFoldResult LoadOp::fold(FoldAdaptor adaptor) {
1822 /// load(memrefcast) -> load
1823 if (succeeded(foldMemRefCast(*this)))
1824 return getResult();
1825
1826 // Fold load from a global constant memref.
1827 auto getGlobalOp = getMemref().getDefiningOp<memref::GetGlobalOp>();
1828 if (!getGlobalOp)
1829 return {};
1830
1831 // Get to the memref.global defining the symbol.
1833 getGlobalOp, getGlobalOp.getNameAttr());
1834 if (!global)
1835 return {};
1836 // If it's a splat constant, we can fold irrespective of indices.
1837 auto splatAttr =
1838 dyn_cast_or_null<SplatElementsAttr>(global.getConstantInitValue());
1839 if (!splatAttr)
1840 return {};
1841
1842 return splatAttr.getSplatValue<Attribute>();
1843}
1844
1845TypedValue<MemRefType> LoadOp::getAccessedMemref() { return getMemref(); }
1846
1847std::optional<SmallVector<Value>>
1848LoadOp::updateMemrefAndIndices(RewriterBase &rewriter, Value newMemref,
1849 ValueRange newIndices) {
1850 rewriter.modifyOpInPlace(*this, [&]() {
1851 getMemrefMutable().assign(newMemref);
1852 getIndicesMutable().assign(newIndices);
1853 });
1854 return std::nullopt;
1855}
1856
1857FailureOr<std::optional<SmallVector<Value>>>
1858LoadOp::bubbleDownCasts(OpBuilder &builder) {
1860 getResult());
1861}
1862
1863//===----------------------------------------------------------------------===//
1864// MemorySpaceCastOp
1865//===----------------------------------------------------------------------===//
1866
1867void MemorySpaceCastOp::getAsmResultNames(
1868 function_ref<void(Value, StringRef)> setNameFn) {
1869 setNameFn(getResult(), "memspacecast");
1870}
1871
1872bool MemorySpaceCastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1873 if (inputs.size() != 1 || outputs.size() != 1)
1874 return false;
1875 Type a = inputs.front(), b = outputs.front();
1876 auto aT = llvm::dyn_cast<MemRefType>(a);
1877 auto bT = llvm::dyn_cast<MemRefType>(b);
1878
1879 auto uaT = llvm::dyn_cast<UnrankedMemRefType>(a);
1880 auto ubT = llvm::dyn_cast<UnrankedMemRefType>(b);
1881
1882 if (aT && bT) {
1883 if (aT.getElementType() != bT.getElementType())
1884 return false;
1885 if (aT.getLayout() != bT.getLayout())
1886 return false;
1887 if (aT.getShape() != bT.getShape())
1888 return false;
1889 return true;
1890 }
1891 if (uaT && ubT) {
1892 return uaT.getElementType() == ubT.getElementType();
1893 }
1894 return false;
1895}
1896
1897OpFoldResult MemorySpaceCastOp::fold(FoldAdaptor adaptor) {
1898 // memory_space_cast(memory_space_cast(v, t1), t2) -> memory_space_cast(v,
1899 // t2)
1900 if (auto parentCast = getSource().getDefiningOp<MemorySpaceCastOp>()) {
1901 getSourceMutable().assign(parentCast.getSource());
1902 return getResult();
1903 }
1904 return Value{};
1905}
1906
1907TypedValue<PtrLikeTypeInterface> MemorySpaceCastOp::getSourcePtr() {
1908 return getSource();
1909}
1910
1911TypedValue<PtrLikeTypeInterface> MemorySpaceCastOp::getTargetPtr() {
1912 return getDest();
1913}
1914
1915bool MemorySpaceCastOp::isValidMemorySpaceCast(PtrLikeTypeInterface tgt,
1916 PtrLikeTypeInterface src) {
1917 return isa<BaseMemRefType>(tgt) &&
1918 tgt.clonePtrWith(src.getMemorySpace(), std::nullopt) == src;
1919}
1920
1921MemorySpaceCastOpInterface MemorySpaceCastOp::cloneMemorySpaceCastOp(
1922 OpBuilder &b, PtrLikeTypeInterface tgt,
1924 assert(isValidMemorySpaceCast(tgt, src.getType()) && "invalid arguments");
1925 return MemorySpaceCastOp::create(b, getLoc(), tgt, src);
1926}
1927
1928/// The only cast we recognize as promotable is to the generic space.
1929bool MemorySpaceCastOp::isSourcePromotable() {
1930 return getDest().getType().getMemorySpace() == nullptr;
1931}
1932
1933//===----------------------------------------------------------------------===//
1934// PrefetchOp
1935//===----------------------------------------------------------------------===//
1936
1937void PrefetchOp::print(OpAsmPrinter &p) {
1938 p << " " << getMemref() << '[';
1940 p << ']' << ", " << (getIsWrite() ? "write" : "read");
1941 p << ", locality<" << getLocalityHint();
1942 p << ">, " << (getIsDataCache() ? "data" : "instr");
1944 (*this)->getDiscardableAttrDictionary(),
1945 /*elidedAttrs=*/{"localityHint", "isWrite", "isDataCache"});
1946 p << " : " << getMemRefType();
1947}
1948
1949ParseResult PrefetchOp::parse(OpAsmParser &parser, OperationState &result) {
1950 OpAsmParser::UnresolvedOperand memrefInfo;
1951 SmallVector<OpAsmParser::UnresolvedOperand, 4> indexInfo;
1952 IntegerAttr localityHint;
1953 MemRefType type;
1954 StringRef readOrWrite, cacheType;
1955
1956 auto indexTy = parser.getBuilder().getIndexType();
1957 auto i32Type = parser.getBuilder().getIntegerType(32);
1958 if (parser.parseOperand(memrefInfo) ||
1960 parser.parseComma() || parser.parseKeyword(&readOrWrite) ||
1961 parser.parseComma() || parser.parseKeyword("locality") ||
1962 parser.parseLess() ||
1963 parser.parseAttribute(localityHint, i32Type, "localityHint",
1964 result.attributes) ||
1965 parser.parseGreater() || parser.parseComma() ||
1966 parser.parseKeyword(&cacheType) || parser.parseColonType(type) ||
1967 parser.resolveOperand(memrefInfo, type, result.operands) ||
1968 parser.resolveOperands(indexInfo, indexTy, result.operands))
1969 return failure();
1970
1971 if (readOrWrite != "read" && readOrWrite != "write")
1972 return parser.emitError(parser.getNameLoc(),
1973 "rw specifier has to be 'read' or 'write'");
1974 result.addAttribute(PrefetchOp::getIsWriteAttrStrName(),
1975 parser.getBuilder().getBoolAttr(readOrWrite == "write"));
1976
1977 if (cacheType != "data" && cacheType != "instr")
1978 return parser.emitError(parser.getNameLoc(),
1979 "cache type has to be 'data' or 'instr'");
1980
1981 result.addAttribute(PrefetchOp::getIsDataCacheAttrStrName(),
1982 parser.getBuilder().getBoolAttr(cacheType == "data"));
1983
1984 return success();
1985}
1986
1987LogicalResult PrefetchOp::verify() {
1988 if (getNumOperands() != 1 + getMemRefType().getRank())
1989 return emitOpError("too few indices");
1990
1991 return success();
1992}
1993
1994LogicalResult PrefetchOp::fold(FoldAdaptor adaptor,
1995 SmallVectorImpl<OpFoldResult> &results) {
1996 // prefetch(memrefcast) -> prefetch
1997 return foldMemRefCast(*this);
1998}
1999
2000TypedValue<MemRefType> PrefetchOp::getAccessedMemref() { return getMemref(); }
2001
2002std::optional<SmallVector<Value>>
2003PrefetchOp::updateMemrefAndIndices(RewriterBase &rewriter, Value newMemref,
2004 ValueRange newIndices) {
2005 rewriter.modifyOpInPlace(*this, [&]() {
2006 getMemrefMutable().assign(newMemref);
2007 getIndicesMutable().assign(newIndices);
2008 });
2009 return std::nullopt;
2010}
2011
2012//===----------------------------------------------------------------------===//
2013// RankOp
2014//===----------------------------------------------------------------------===//
2015
2016OpFoldResult RankOp::fold(FoldAdaptor adaptor) {
2017 // Constant fold rank when the rank of the operand is known.
2018 auto type = getOperand().getType();
2019 auto shapedType = llvm::dyn_cast<ShapedType>(type);
2020 if (shapedType && shapedType.hasRank())
2021 return IntegerAttr::get(IndexType::get(getContext()), shapedType.getRank());
2022 return IntegerAttr();
2023}
2024
2025//===----------------------------------------------------------------------===//
2026// ReinterpretCastOp
2027//===----------------------------------------------------------------------===//
2028
2029void ReinterpretCastOp::getAsmResultNames(
2030 function_ref<void(Value, StringRef)> setNameFn) {
2031 setNameFn(getResult(), "reinterpret_cast");
2032}
2033
2034/// Build a ReinterpretCastOp with all dynamic entries: `staticOffsets`,
2035/// `staticSizes` and `staticStrides` are automatically filled with
2036/// source-memref-rank sentinel values that encode dynamic entries.
2037void ReinterpretCastOp::build(OpBuilder &b, OperationState &result,
2038 MemRefType resultType, Value source,
2039 OpFoldResult offset, ArrayRef<OpFoldResult> sizes,
2040 ArrayRef<OpFoldResult> strides,
2041 ArrayRef<NamedAttribute> attrs) {
2042 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2043 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2044 dispatchIndexOpFoldResults(offset, dynamicOffsets, staticOffsets);
2045 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2046 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2047 result.addAttributes(attrs);
2048 build(b, result, resultType, source, dynamicOffsets, dynamicSizes,
2049 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
2050 b.getDenseI64ArrayAttr(staticSizes),
2051 b.getDenseI64ArrayAttr(staticStrides));
2052}
2053
2054void ReinterpretCastOp::build(OpBuilder &b, OperationState &result,
2055 Value source, OpFoldResult offset,
2056 ArrayRef<OpFoldResult> sizes,
2057 ArrayRef<OpFoldResult> strides,
2058 ArrayRef<NamedAttribute> attrs) {
2059 auto sourceType = cast<BaseMemRefType>(source.getType());
2060 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2061 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2062 dispatchIndexOpFoldResults(offset, dynamicOffsets, staticOffsets);
2063 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2064 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2065 auto stridedLayout = StridedLayoutAttr::get(
2066 b.getContext(), staticOffsets.front(), staticStrides);
2067 auto resultType = MemRefType::get(staticSizes, sourceType.getElementType(),
2068 stridedLayout, sourceType.getMemorySpace());
2069 build(b, result, resultType, source, offset, sizes, strides, attrs);
2070}
2071
2072void ReinterpretCastOp::build(OpBuilder &b, OperationState &result,
2073 MemRefType resultType, Value source,
2074 int64_t offset, ArrayRef<int64_t> sizes,
2075 ArrayRef<int64_t> strides,
2076 ArrayRef<NamedAttribute> attrs) {
2077 SmallVector<OpFoldResult> sizeValues = llvm::map_to_vector<4>(
2078 sizes, [&](int64_t v) -> OpFoldResult { return b.getI64IntegerAttr(v); });
2079 SmallVector<OpFoldResult> strideValues =
2080 llvm::map_to_vector<4>(strides, [&](int64_t v) -> OpFoldResult {
2081 return b.getI64IntegerAttr(v);
2082 });
2083 build(b, result, resultType, source, b.getI64IntegerAttr(offset), sizeValues,
2084 strideValues, attrs);
2085}
2086
2087void ReinterpretCastOp::build(OpBuilder &b, OperationState &result,
2088 MemRefType resultType, Value source, Value offset,
2089 ValueRange sizes, ValueRange strides,
2090 ArrayRef<NamedAttribute> attrs) {
2091 SmallVector<OpFoldResult> sizeValues =
2092 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
2093 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
2094 strides, [](Value v) -> OpFoldResult { return v; });
2095 build(b, result, resultType, source, offset, sizeValues, strideValues, attrs);
2096}
2097
2098// TODO: ponder whether we want to allow missing trailing sizes/strides that are
2099// completed automatically, like we have for subview and extract_slice.
2100LogicalResult ReinterpretCastOp::verify() {
2101 // The source and result memrefs should be in the same memory space.
2102 auto srcType = llvm::cast<BaseMemRefType>(getSource().getType());
2103 auto resultType = llvm::cast<MemRefType>(getType());
2104 if (srcType.getMemorySpace() != resultType.getMemorySpace())
2105 return emitError("different memory spaces specified for source type ")
2106 << srcType << " and result memref type " << resultType;
2107 if (failed(verifyElementTypesMatch(*this, srcType, resultType, "source",
2108 "result")))
2109 return failure();
2110
2111 // Match sizes in result memref type and in static_sizes attribute.
2112 for (auto [idx, resultSize, expectedSize] :
2113 llvm::enumerate(resultType.getShape(), getStaticSizes())) {
2114 if (ShapedType::isStatic(resultSize) && resultSize != expectedSize)
2115 return emitError("expected result type with size = ")
2116 << (ShapedType::isDynamic(expectedSize)
2117 ? std::string("dynamic")
2118 : std::to_string(expectedSize))
2119 << " instead of " << resultSize << " in dim = " << idx;
2120 }
2121
2122 // Match offset and strides in static_offset and static_strides attributes. If
2123 // result memref type has no affine map specified, this will assume an
2124 // identity layout.
2125 int64_t resultOffset;
2126 SmallVector<int64_t, 4> resultStrides;
2127 if (failed(resultType.getStridesAndOffset(resultStrides, resultOffset)))
2128 return emitError("expected result type to have strided layout but found ")
2129 << resultType;
2130
2131 // Match offset in result memref type and in static_offsets attribute.
2132 int64_t expectedOffset = getStaticOffsets().front();
2133 if (ShapedType::isStatic(resultOffset) && resultOffset != expectedOffset)
2134 return emitError("expected result type with offset = ")
2135 << (ShapedType::isDynamic(expectedOffset)
2136 ? std::string("dynamic")
2137 : std::to_string(expectedOffset))
2138 << " instead of " << resultOffset;
2139
2140 // Match strides in result memref type and in static_strides attribute.
2141 for (auto [idx, resultStride, expectedStride] :
2142 llvm::enumerate(resultStrides, getStaticStrides())) {
2143 if (ShapedType::isStatic(resultStride) && resultStride != expectedStride)
2144 return emitError("expected result type with stride = ")
2145 << (ShapedType::isDynamic(expectedStride)
2146 ? std::string("dynamic")
2147 : std::to_string(expectedStride))
2148 << " instead of " << resultStride << " in dim = " << idx;
2149 }
2150
2151 return success();
2152}
2153
2154OpFoldResult ReinterpretCastOp::fold(FoldAdaptor /*operands*/) {
2155 Value src = getSource();
2156 auto getPrevSrc = [&]() -> Value {
2157 // reinterpret_cast(reinterpret_cast(x)) -> reinterpret_cast(x).
2158 if (auto prev = src.getDefiningOp<ReinterpretCastOp>())
2159 return prev.getSource();
2160
2161 // reinterpret_cast(cast(x)) -> reinterpret_cast(x).
2162 if (auto prev = src.getDefiningOp<CastOp>())
2163 return prev.getSource();
2164
2165 // reinterpret_cast(subview(x)) -> reinterpret_cast(x) if subview offsets
2166 // are 0.
2167 if (auto prev = src.getDefiningOp<SubViewOp>())
2168 if (llvm::all_of(prev.getMixedOffsets(), isZeroInteger))
2169 return prev.getSource();
2170
2171 return nullptr;
2172 };
2173
2174 if (auto prevSrc = getPrevSrc()) {
2175 getSourceMutable().assign(prevSrc);
2176 return getResult();
2177 }
2178
2179 // reinterpret_cast(x) w/o offset/shape/stride changes -> x
2180 if (ShapedType::isStaticShape(getType().getShape()) &&
2181 src.getType() == getType() && getStaticOffsets().front() == 0) {
2182 return src;
2183 }
2184
2185 return nullptr;
2186}
2187
2188SmallVector<OpFoldResult> ReinterpretCastOp::getConstifiedMixedSizes() {
2189 SmallVector<OpFoldResult> values = getMixedSizes();
2191 return values;
2192}
2193
2194SmallVector<OpFoldResult> ReinterpretCastOp::getConstifiedMixedStrides() {
2195 SmallVector<OpFoldResult> values = getMixedStrides();
2196 SmallVector<int64_t> staticValues;
2197 int64_t unused;
2198 LogicalResult status = getType().getStridesAndOffset(staticValues, unused);
2199 (void)status;
2200 assert(succeeded(status) && "could not get strides from type");
2201 constifyIndexValues(values, staticValues);
2202 return values;
2203}
2204
2205OpFoldResult ReinterpretCastOp::getConstifiedMixedOffset() {
2206 SmallVector<OpFoldResult> values = getMixedOffsets();
2207 assert(values.size() == 1 &&
2208 "reinterpret_cast must have one and only one offset");
2209 SmallVector<int64_t> staticValues, unused;
2210 int64_t offset;
2211 LogicalResult status = getType().getStridesAndOffset(unused, offset);
2212 (void)status;
2213 assert(succeeded(status) && "could not get offset from type");
2214 staticValues.push_back(offset);
2215 constifyIndexValues(values, staticValues);
2216 return values[0];
2217}
2218
2219namespace {
2220/// Replace the sequence:
2221/// ```
2222/// base, offset, sizes, strides = extract_strided_metadata src
2223/// dst = reinterpret_cast base to offset, sizes, strides
2224/// ```
2225/// With
2226///
2227/// ```
2228/// dst = memref.cast src
2229/// ```
2230///
2231/// Note: The cast operation is only inserted when the type of dst and src
2232/// are not the same. E.g., when going from <4xf32> to <?xf32>.
2233///
2234/// This pattern also matches when the offset, sizes, and strides don't come
2235/// directly from the `extract_strided_metadata`'s results but it can be
2236/// statically proven that they would hold the same values.
2237///
2238/// For instance, the following sequence would be replaced:
2239/// ```
2240/// base, offset, sizes, strides =
2241/// extract_strided_metadata memref : memref<3x4xty>
2242/// dst = reinterpret_cast base to 0, [3, 4], strides
2243/// ```
2244/// Because we know (thanks to the type of the input memref) that variable
2245/// `offset` and `sizes` will respectively hold 0 and [3, 4].
2246///
2247/// Similarly, the following sequence would be replaced:
2248/// ```
2249/// c0 = arith.constant 0
2250/// c4 = arith.constant 4
2251/// base, offset, sizes, strides =
2252/// extract_strided_metadata memref : memref<3x4xty>
2253/// dst = reinterpret_cast base to c0, [3, c4], strides
2254/// ```
2255/// Because we know that `offset`and `c0` will hold 0
2256/// and `c4` will hold 4.
2257///
2258/// If the pattern above does not match, the input of the
2259/// extract_strided_metadata is always folded into the input of the
2260/// reinterpret_cast operator. This allows for dead code elimination to get rid
2261/// of the extract_strided_metadata in some cases.
2262struct ReinterpretCastOpExtractStridedMetadataFolder
2263 : public OpRewritePattern<ReinterpretCastOp> {
2264public:
2265 using OpRewritePattern<ReinterpretCastOp>::OpRewritePattern;
2266
2267 LogicalResult matchAndRewrite(ReinterpretCastOp op,
2268 PatternRewriter &rewriter) const override {
2269 auto extractStridedMetadata =
2270 op.getSource().getDefiningOp<ExtractStridedMetadataOp>();
2271 if (!extractStridedMetadata)
2272 return failure();
2273
2274 // Check if the reinterpret cast reconstructs a memref with the exact same
2275 // properties as the extract strided metadata.
2276 auto isReinterpretCastNoop = [&]() -> bool {
2277 // First, check that the strides are the same.
2278 if (!llvm::equal(extractStridedMetadata.getConstifiedMixedStrides(),
2279 op.getConstifiedMixedStrides()))
2280 return false;
2281
2282 // Second, check the sizes.
2283 if (!llvm::equal(extractStridedMetadata.getConstifiedMixedSizes(),
2284 op.getConstifiedMixedSizes()))
2285 return false;
2286
2287 // Finally, check the offset.
2288 assert(op.getMixedOffsets().size() == 1 &&
2289 "reinterpret_cast with more than one offset should have been "
2290 "rejected by the verifier");
2291 return extractStridedMetadata.getConstifiedMixedOffset() ==
2292 op.getConstifiedMixedOffset();
2293 };
2294
2295 if (!isReinterpretCastNoop()) {
2296 // If the extract_strided_metadata / reinterpret_cast pair can't be
2297 // completely folded, then we could fold the input of the
2298 // extract_strided_metadata into the input of the reinterpret_cast
2299 // input. For some cases (e.g., static dimensions) the
2300 // the extract_strided_metadata is eliminated by dead code elimination.
2301 //
2302 // reinterpret_cast(extract_strided_metadata(x)) -> reinterpret_cast(x).
2303 //
2304 // We can always fold the input of a extract_strided_metadata operator
2305 // to the input of a reinterpret_cast operator, because they point to
2306 // the same memory. Note that the reinterpret_cast does not use the
2307 // layout of its input memref, only its base memory pointer which is
2308 // the same as the base pointer returned by the extract_strided_metadata
2309 // operator and the base pointer of the extract_strided_metadata memref
2310 // input.
2311 rewriter.modifyOpInPlace(op, [&]() {
2312 op.getSourceMutable().assign(extractStridedMetadata.getSource());
2313 });
2314 return success();
2315 }
2316
2317 // At this point, we know that the back and forth between extract strided
2318 // metadata and reinterpret cast is a noop. However, the final type of the
2319 // reinterpret cast may not be exactly the same as the original memref.
2320 // E.g., it could be changing a dimension from static to dynamic. Check that
2321 // here and add a cast if necessary.
2322 Type srcTy = extractStridedMetadata.getSource().getType();
2323 if (srcTy == op.getResult().getType())
2324 rewriter.replaceOp(op, extractStridedMetadata.getSource());
2325 else
2326 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(),
2327 extractStridedMetadata.getSource());
2328
2329 return success();
2330 }
2331};
2332
2333struct ReinterpretCastOpConstantFolder
2334 : public OpRewritePattern<ReinterpretCastOp> {
2335public:
2336 using OpRewritePattern<ReinterpretCastOp>::OpRewritePattern;
2337
2338 LogicalResult matchAndRewrite(ReinterpretCastOp op,
2339 PatternRewriter &rewriter) const override {
2340 unsigned srcStaticCount = llvm::count_if(
2341 llvm::concat<OpFoldResult>(op.getMixedOffsets(), op.getMixedSizes(),
2342 op.getMixedStrides()),
2343 [](OpFoldResult ofr) { return isa<Attribute>(ofr); });
2344
2345 SmallVector<OpFoldResult> offsets = {op.getConstifiedMixedOffset()};
2346 SmallVector<OpFoldResult> sizes = op.getConstifiedMixedSizes();
2347 SmallVector<OpFoldResult> strides = op.getConstifiedMixedStrides();
2348
2349 // If the offset is a negative constant, we can't fold it because the
2350 // resulting memref type would be invalid. In that case, we keep the
2351 // original offset.
2352 if (auto cst = getConstantIntValue(offsets[0]))
2353 if (*cst < 0)
2354 offsets[0] = op.getMixedOffsets()[0];
2355
2356 // If the size is a negative constant, we can't fold it because the
2357 // resulting memref type would be invalid. In that case, we keep the
2358 // original size.
2359 for (auto it : llvm::zip(op.getMixedSizes(), sizes)) {
2360 auto &srcSizeOfr = std::get<0>(it);
2361 auto &sizeOfr = std::get<1>(it);
2362 if (auto cst = getConstantIntValue(sizeOfr))
2363 if (*cst < 0)
2364 sizeOfr = srcSizeOfr;
2365 }
2366
2367 // TODO: Using counting comparison instead of direct comparison because
2368 // getMixedValues (and therefore ReinterpretCastOp::getMixed...) returns
2369 // IntegerAttrs, while constifyIndexValues (and therefore
2370 // ReinterpretCastOp::getConstifiedMixed...) returns IndexAttrs.
2371 if (srcStaticCount ==
2372 llvm::count_if(llvm::concat<OpFoldResult>(offsets, sizes, strides),
2373 [](OpFoldResult ofr) { return isa<Attribute>(ofr); }))
2374 return failure();
2375
2376 auto newReinterpretCast = ReinterpretCastOp::create(
2377 rewriter, op->getLoc(), op.getSource(), offsets[0], sizes, strides);
2378
2379 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(), newReinterpretCast);
2380 return success();
2381 }
2382};
2383} // namespace
2384
2385void ReinterpretCastOp::getCanonicalizationPatterns(RewritePatternSet &results,
2386 MLIRContext *context) {
2387 results.add<ReinterpretCastOpExtractStridedMetadataFolder,
2388 ReinterpretCastOpConstantFolder>(context);
2389}
2390
2391FailureOr<std::optional<SmallVector<Value>>>
2392ReinterpretCastOp::bubbleDownCasts(OpBuilder &builder) {
2393 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSourceMutable());
2394}
2395
2396//===----------------------------------------------------------------------===//
2397// Reassociative reshape ops
2398//===----------------------------------------------------------------------===//
2399
2400void CollapseShapeOp::getAsmResultNames(
2401 function_ref<void(Value, StringRef)> setNameFn) {
2402 setNameFn(getResult(), "collapse_shape");
2403}
2404
2405void ExpandShapeOp::getAsmResultNames(
2406 function_ref<void(Value, StringRef)> setNameFn) {
2407 setNameFn(getResult(), "expand_shape");
2408}
2409
2410LogicalResult ExpandShapeOp::reifyResultShapes(
2411 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedResultShapes) {
2412 reifiedResultShapes = {
2413 getMixedValues(getStaticOutputShape(), getOutputShape(), builder)};
2414 return success();
2415}
2416
2417/// Helper function for verifying the shape of ExpandShapeOp and ResultShapeOp
2418/// result and operand. Layout maps are verified separately.
2419///
2420/// If `allowMultipleDynamicDimsPerGroup`, multiple dynamic dimensions are
2421/// allowed in a reassocation group.
2422static LogicalResult
2424 ArrayRef<int64_t> expandedShape,
2425 ArrayRef<ReassociationIndices> reassociation,
2426 bool allowMultipleDynamicDimsPerGroup) {
2427 // There must be one reassociation group per collapsed dimension.
2428 if (collapsedShape.size() != reassociation.size())
2429 return op->emitOpError("invalid number of reassociation groups: found ")
2430 << reassociation.size() << ", expected " << collapsedShape.size();
2431
2432 // The next expected expanded dimension index (while iterating over
2433 // reassociation indices).
2434 int64_t nextDim = 0;
2435 for (const auto &it : llvm::enumerate(reassociation)) {
2436 ReassociationIndices group = it.value();
2437 int64_t collapsedDim = it.index();
2438
2439 bool foundDynamic = false;
2440 for (int64_t expandedDim : group) {
2441 if (expandedDim != nextDim++)
2442 return op->emitOpError("reassociation indices must be contiguous");
2443
2444 if (expandedDim >= static_cast<int64_t>(expandedShape.size()))
2445 return op->emitOpError("reassociation index ")
2446 << expandedDim << " is out of bounds";
2447
2448 // Check if there are multiple dynamic dims in a reassociation group.
2449 if (ShapedType::isDynamic(expandedShape[expandedDim])) {
2450 if (foundDynamic && !allowMultipleDynamicDimsPerGroup)
2451 return op->emitOpError(
2452 "at most one dimension in a reassociation group may be dynamic");
2453 foundDynamic = true;
2454 }
2455 }
2456
2457 // ExpandShapeOp/CollapseShapeOp may not be used to cast dynamicity.
2458 if (ShapedType::isDynamic(collapsedShape[collapsedDim]) != foundDynamic)
2459 return op->emitOpError("collapsed dim (")
2460 << collapsedDim
2461 << ") must be dynamic if and only if reassociation group is "
2462 "dynamic";
2463
2464 // If all dims in the reassociation group are static, the size of the
2465 // collapsed dim can be verified.
2466 if (!foundDynamic) {
2467 int64_t groupSize = 1;
2468 for (int64_t expandedDim : group)
2469 groupSize *= expandedShape[expandedDim];
2470 if (groupSize != collapsedShape[collapsedDim])
2471 return op->emitOpError("collapsed dim size (")
2472 << collapsedShape[collapsedDim]
2473 << ") must equal reassociation group size (" << groupSize << ")";
2474 }
2475 }
2476
2477 if (collapsedShape.empty()) {
2478 // Rank 0: All expanded dimensions must be 1.
2479 for (int64_t d : expandedShape)
2480 if (d != 1)
2481 return op->emitOpError(
2482 "rank 0 memrefs can only be extended/collapsed with/from ones");
2483 } else if (nextDim != static_cast<int64_t>(expandedShape.size())) {
2484 // Rank >= 1: Number of dimensions among all reassociation groups must match
2485 // the result memref rank.
2486 return op->emitOpError("expanded rank (")
2487 << expandedShape.size()
2488 << ") inconsistent with number of reassociation indices (" << nextDim
2489 << ")";
2490 }
2491
2492 return success();
2493}
2494
2495SmallVector<AffineMap, 4> CollapseShapeOp::getReassociationMaps() {
2496 return getSymbolLessAffineMaps(getReassociationExprs());
2497}
2498
2499SmallVector<ReassociationExprs, 4> CollapseShapeOp::getReassociationExprs() {
2501 getReassociationIndices());
2502}
2503
2504SmallVector<AffineMap, 4> ExpandShapeOp::getReassociationMaps() {
2505 return getSymbolLessAffineMaps(getReassociationExprs());
2506}
2507
2508SmallVector<ReassociationExprs, 4> ExpandShapeOp::getReassociationExprs() {
2510 getReassociationIndices());
2511}
2512
2513/// Compute the layout map after expanding a given source MemRef type with the
2514/// specified reassociation indices.
2515static FailureOr<StridedLayoutAttr>
2516computeExpandedLayoutMap(MemRefType srcType, ArrayRef<int64_t> resultShape,
2517 ArrayRef<ReassociationIndices> reassociation) {
2518 int64_t srcOffset;
2519 SmallVector<int64_t> srcStrides;
2520 if (failed(srcType.getStridesAndOffset(srcStrides, srcOffset)))
2521 return failure();
2522 assert(srcStrides.size() == reassociation.size() && "invalid reassociation");
2523
2524 // 1-1 mapping between srcStrides and reassociation packs.
2525 // Each srcStride starts with the given value and gets expanded according to
2526 // the proper entries in resultShape.
2527 // Example:
2528 // srcStrides = [10000, 1 , 100 ],
2529 // reassociations = [ [0], [1], [2, 3, 4]],
2530 // resultSizes = [2, 5, 4, 3, 2] = [ [2], [5], [4, 3, 2]]
2531 // -> For the purpose of stride calculation, the useful sizes are:
2532 // [x, x, x, 3, 2] = [ [x], [x], [x, 3, 2]].
2533 // resultStrides = [10000, 1, 600, 200, 100]
2534 // Note that a stride does not get expanded along the first entry of each
2535 // shape pack.
2536 SmallVector<int64_t> reverseResultStrides;
2537 reverseResultStrides.reserve(resultShape.size());
2538 unsigned shapeIndex = resultShape.size() - 1;
2539 for (auto it : llvm::reverse(llvm::zip(reassociation, srcStrides))) {
2540 ReassociationIndices reassoc = std::get<0>(it);
2541 int64_t currentStrideToExpand = std::get<1>(it);
2542 for (unsigned idx = 0, e = reassoc.size(); idx < e; ++idx) {
2543 reverseResultStrides.push_back(currentStrideToExpand);
2544 currentStrideToExpand =
2545 (SaturatedInteger::wrap(currentStrideToExpand) *
2546 SaturatedInteger::wrap(resultShape[shapeIndex--]))
2547 .asInteger();
2548 }
2549 }
2550 auto resultStrides = llvm::to_vector<8>(llvm::reverse(reverseResultStrides));
2551 resultStrides.resize(resultShape.size(), 1);
2552 return StridedLayoutAttr::get(srcType.getContext(), srcOffset, resultStrides);
2553}
2554
2555FailureOr<MemRefType> ExpandShapeOp::computeExpandedType(
2556 MemRefType srcType, ArrayRef<int64_t> resultShape,
2557 ArrayRef<ReassociationIndices> reassociation) {
2558 if (srcType.getLayout().isIdentity()) {
2559 // If the source is contiguous (i.e., no layout map specified), so is the
2560 // result.
2561 MemRefLayoutAttrInterface layout;
2562 return MemRefType::get(resultShape, srcType.getElementType(), layout,
2563 srcType.getMemorySpace());
2564 }
2565
2566 // Source may not be contiguous. Compute the layout map.
2567 FailureOr<StridedLayoutAttr> computedLayout =
2568 computeExpandedLayoutMap(srcType, resultShape, reassociation);
2569 if (failed(computedLayout))
2570 return failure();
2571 return MemRefType::get(resultShape, srcType.getElementType(), *computedLayout,
2572 srcType.getMemorySpace());
2573}
2574
2575FailureOr<SmallVector<OpFoldResult>>
2576ExpandShapeOp::inferOutputShape(OpBuilder &b, Location loc,
2577 MemRefType expandedType,
2578 ArrayRef<ReassociationIndices> reassociation,
2579 ArrayRef<OpFoldResult> inputShape) {
2580 std::optional<SmallVector<OpFoldResult>> outputShape =
2581 inferExpandShapeOutputShape(b, loc, expandedType, reassociation,
2582 inputShape);
2583 if (!outputShape)
2584 return failure();
2585 return *outputShape;
2586}
2587
2588void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
2589 Type resultType, Value src,
2590 ArrayRef<ReassociationIndices> reassociation,
2591 ArrayRef<OpFoldResult> outputShape) {
2592 auto [staticOutputShape, dynamicOutputShape] =
2593 decomposeMixedValues(SmallVector<OpFoldResult>(outputShape));
2594 build(builder, result, llvm::cast<MemRefType>(resultType), src,
2595 getReassociationIndicesAttribute(builder, reassociation),
2596 dynamicOutputShape, staticOutputShape);
2597}
2598
2599void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
2600 Type resultType, Value src,
2601 ArrayRef<ReassociationIndices> reassociation) {
2602 SmallVector<OpFoldResult> inputShape =
2603 getMixedSizes(builder, result.location, src);
2604 MemRefType memrefResultTy = llvm::cast<MemRefType>(resultType);
2605 FailureOr<SmallVector<OpFoldResult>> outputShape = inferOutputShape(
2606 builder, result.location, memrefResultTy, reassociation, inputShape);
2607 // Failure of this assertion usually indicates presence of multiple
2608 // dynamic dimensions in the same reassociation group.
2609 assert(succeeded(outputShape) && "unable to infer output shape");
2610 build(builder, result, memrefResultTy, src, reassociation, *outputShape);
2611}
2612
2613void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
2614 ArrayRef<int64_t> resultShape, Value src,
2615 ArrayRef<ReassociationIndices> reassociation) {
2616 // Only ranked memref source values are supported.
2617 auto srcType = llvm::cast<MemRefType>(src.getType());
2618 FailureOr<MemRefType> resultType =
2619 ExpandShapeOp::computeExpandedType(srcType, resultShape, reassociation);
2620 // Failure of this assertion usually indicates a problem with the source
2621 // type, e.g., could not get strides/offset.
2622 assert(succeeded(resultType) && "could not compute layout");
2623 build(builder, result, *resultType, src, reassociation);
2624}
2625
2626void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
2627 ArrayRef<int64_t> resultShape, Value src,
2628 ArrayRef<ReassociationIndices> reassociation,
2629 ArrayRef<OpFoldResult> outputShape) {
2630 // Only ranked memref source values are supported.
2631 auto srcType = llvm::cast<MemRefType>(src.getType());
2632 FailureOr<MemRefType> resultType =
2633 ExpandShapeOp::computeExpandedType(srcType, resultShape, reassociation);
2634 // Failure of this assertion usually indicates a problem with the source
2635 // type, e.g., could not get strides/offset.
2636 assert(succeeded(resultType) && "could not compute layout");
2637 build(builder, result, *resultType, src, reassociation, outputShape);
2638}
2639
2640LogicalResult ExpandShapeOp::verify() {
2641 MemRefType srcType = getSrcType();
2642 MemRefType resultType = getResultType();
2643
2644 if (srcType.getRank() > resultType.getRank()) {
2645 auto r0 = srcType.getRank();
2646 auto r1 = resultType.getRank();
2647 return emitOpError("has source rank ")
2648 << r0 << " and result rank " << r1 << ". This is not an expansion ("
2649 << r0 << " > " << r1 << ").";
2650 }
2651
2652 // Verify result shape.
2653 if (failed(verifyCollapsedShape(getOperation(), srcType.getShape(),
2654 resultType.getShape(),
2655 getReassociationIndices(),
2656 /*allowMultipleDynamicDimsPerGroup=*/true)))
2657 return failure();
2658
2659 // Compute expected result type (including layout map).
2660 FailureOr<MemRefType> expectedResultType = ExpandShapeOp::computeExpandedType(
2661 srcType, resultType.getShape(), getReassociationIndices());
2662 if (failed(expectedResultType))
2663 return emitOpError("invalid source layout map");
2664
2665 // Check actual result type.
2666 if (*expectedResultType != resultType)
2667 return emitOpError("expected expanded type to be ")
2668 << *expectedResultType << " but found " << resultType;
2669
2670 if ((int64_t)getStaticOutputShape().size() != resultType.getRank())
2671 return emitOpError("expected number of static shape bounds to be equal to "
2672 "the output rank (")
2673 << resultType.getRank() << ") but found "
2674 << getStaticOutputShape().size() << " inputs instead";
2675
2676 if ((int64_t)getOutputShape().size() !=
2677 llvm::count(getStaticOutputShape(), ShapedType::kDynamic))
2678 return emitOpError("mismatch in dynamic dims in output_shape and "
2679 "static_output_shape: static_output_shape has ")
2680 << llvm::count(getStaticOutputShape(), ShapedType::kDynamic)
2681 << " dynamic dims while output_shape has " << getOutputShape().size()
2682 << " values";
2683
2684 // Verify that the number of dynamic dims in output_shape matches the number
2685 // of dynamic dims in the result type.
2686 if (failed(verifyDynamicDimensionCount(getOperation(), resultType,
2687 getOutputShape())))
2688 return failure();
2689
2690 // Verify if provided output shapes are in agreement with output type.
2691 DenseI64ArrayAttr staticOutputShapes = getStaticOutputShapeAttr();
2692 ArrayRef<int64_t> resShape = getResult().getType().getShape();
2693 for (auto [pos, shape] : llvm::enumerate(resShape)) {
2694 if (ShapedType::isStatic(shape) && shape != staticOutputShapes[pos]) {
2695 return emitOpError("invalid output shape provided at pos ") << pos;
2696 }
2697 }
2698
2699 return success();
2700}
2701
2702struct ExpandShapeOpMemRefCastFolder : public OpRewritePattern<ExpandShapeOp> {
2703public:
2704 using OpRewritePattern<ExpandShapeOp>::OpRewritePattern;
2705
2706 LogicalResult matchAndRewrite(ExpandShapeOp op,
2707 PatternRewriter &rewriter) const override {
2708 auto cast = op.getSrc().getDefiningOp<CastOp>();
2709 if (!cast)
2710 return failure();
2711
2712 if (!CastOp::canFoldIntoConsumerOp(cast))
2713 return failure();
2714
2715 SmallVector<OpFoldResult> originalOutputShape = op.getMixedOutputShape();
2716 SmallVector<OpFoldResult> newOutputShape = originalOutputShape;
2717 SmallVector<int64_t> newOutputShapeSizes;
2718
2719 // Convert output shape dims from dynamic to static where possible.
2720 for (auto [dimIdx, dimSize] : enumerate(originalOutputShape)) {
2721 std::optional<int64_t> sizeOpt = getConstantIntValue(dimSize);
2722 if (!sizeOpt.has_value()) {
2723 newOutputShapeSizes.push_back(ShapedType::kDynamic);
2724 continue;
2725 }
2726
2727 newOutputShapeSizes.push_back(sizeOpt.value());
2728 newOutputShape[dimIdx] = rewriter.getIndexAttr(sizeOpt.value());
2729 }
2730
2731 Value castSource = cast.getSource();
2732 auto castSourceType = llvm::cast<MemRefType>(castSource.getType());
2733 SmallVector<ReassociationIndices> reassociationIndices =
2734 op.getReassociationIndices();
2735 for (auto [idx, group] : llvm::enumerate(reassociationIndices)) {
2736 auto newOutputShapeSizesSlice =
2737 ArrayRef(newOutputShapeSizes).slice(group.front(), group.size());
2738 bool newOutputDynamic =
2739 llvm::is_contained(newOutputShapeSizesSlice, ShapedType::kDynamic);
2740 if (castSourceType.isDynamicDim(idx) != newOutputDynamic)
2741 return rewriter.notifyMatchFailure(
2742 op, "folding cast will result in changing dynamicity in "
2743 "reassociation group");
2744 }
2745
2746 FailureOr<MemRefType> newResultTypeOrFailure =
2747 ExpandShapeOp::computeExpandedType(castSourceType, newOutputShapeSizes,
2748 reassociationIndices);
2749
2750 if (failed(newResultTypeOrFailure))
2751 return rewriter.notifyMatchFailure(
2752 op, "could not compute new expanded type after folding cast");
2753
2754 if (*newResultTypeOrFailure == op.getResultType()) {
2755 rewriter.modifyOpInPlace(
2756 op, [&]() { op.getSrcMutable().assign(castSource); });
2757 } else {
2758 Value newOp = ExpandShapeOp::create(rewriter, op->getLoc(),
2759 *newResultTypeOrFailure, castSource,
2760 reassociationIndices, newOutputShape);
2761 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(), newOp);
2762 }
2763 return success();
2764 }
2765};
2766
2767void ExpandShapeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2768 MLIRContext *context) {
2769 results.add<
2770 ComposeReassociativeReshapeOps<ExpandShapeOp, ReshapeOpKind::kExpand>,
2771 ComposeExpandOfCollapseOp<ExpandShapeOp, CollapseShapeOp, CastOp>,
2772 ExpandShapeOpMemRefCastFolder>(context);
2773}
2774
2775FailureOr<std::optional<SmallVector<Value>>>
2776ExpandShapeOp::bubbleDownCasts(OpBuilder &builder) {
2777 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSrcMutable());
2778}
2779
2780/// Compute the layout map after collapsing a given source MemRef type with the
2781/// specified reassociation indices.
2782///
2783/// Note: All collapsed dims in a reassociation group must be contiguous. It is
2784/// not possible to check this by inspecting a MemRefType in the general case.
2785/// If non-contiguity cannot be checked statically, the collapse is assumed to
2786/// be valid (and thus accepted by this function) unless `strict = true`.
2787static FailureOr<StridedLayoutAttr>
2788computeCollapsedLayoutMap(MemRefType srcType,
2789 ArrayRef<ReassociationIndices> reassociation,
2790 bool strict = false) {
2791 int64_t srcOffset;
2792 SmallVector<int64_t> srcStrides;
2793 auto srcShape = srcType.getShape();
2794 if (failed(srcType.getStridesAndOffset(srcStrides, srcOffset)))
2795 return failure();
2796
2797 // The result stride of a reassociation group is the stride of the last entry
2798 // of the reassociation. (TODO: Should be the minimum stride in the
2799 // reassociation because strides are not necessarily sorted. E.g., when using
2800 // memref.transpose.) Dimensions of size 1 should be skipped, because their
2801 // strides are meaningless and could have any arbitrary value.
2802 SmallVector<int64_t> resultStrides;
2803 resultStrides.reserve(reassociation.size());
2804 for (const ReassociationIndices &reassoc : reassociation) {
2805 ArrayRef<int64_t> ref = llvm::ArrayRef(reassoc);
2806 while (srcShape[ref.back()] == 1 && ref.size() > 1)
2807 ref = ref.drop_back();
2808 if (ShapedType::isStatic(srcShape[ref.back()]) || ref.size() == 1) {
2809 resultStrides.push_back(srcStrides[ref.back()]);
2810 } else {
2811 // Dynamically-sized dims may turn out to be dims of size 1 at runtime, so
2812 // the corresponding stride may have to be skipped. (See above comment.)
2813 // Therefore, the result stride cannot be statically determined and must
2814 // be dynamic.
2815 resultStrides.push_back(ShapedType::kDynamic);
2816 }
2817 }
2818
2819 // Validate that each reassociation group is contiguous.
2820 unsigned resultStrideIndex = resultStrides.size() - 1;
2821 for (const ReassociationIndices &reassoc : llvm::reverse(reassociation)) {
2822 auto trailingReassocs = ArrayRef<int64_t>(reassoc).drop_front();
2823 auto stride = SaturatedInteger::wrap(resultStrides[resultStrideIndex--]);
2824 for (int64_t idx : llvm::reverse(trailingReassocs)) {
2825 stride = stride * SaturatedInteger::wrap(srcShape[idx]);
2826
2827 // Dimensions of size 1 should be skipped, because their strides are
2828 // meaningless and could have any arbitrary value.
2829 if (srcShape[idx - 1] == 1)
2830 continue;
2831
2832 // Both source and result stride must have the same static value. In that
2833 // case, we can be sure, that the dimensions are collapsible (because they
2834 // are contiguous).
2835 // If `strict = false` (default during op verification), we accept cases
2836 // where one or both strides are dynamic. This is best effort: We reject
2837 // ops where obviously non-contiguous dims are collapsed, but accept ops
2838 // where we cannot be sure statically. Such ops may fail at runtime. See
2839 // the op documentation for details.
2840 auto srcStride = SaturatedInteger::wrap(srcStrides[idx - 1]);
2841 if (strict && (stride.saturated || srcStride.saturated))
2842 return failure();
2843
2844 if (!stride.saturated && !srcStride.saturated && stride != srcStride)
2845 return failure();
2846 }
2847 }
2848 return StridedLayoutAttr::get(srcType.getContext(), srcOffset, resultStrides);
2849}
2850
2851bool CollapseShapeOp::isGuaranteedCollapsible(
2852 MemRefType srcType, ArrayRef<ReassociationIndices> reassociation) {
2853 // MemRefs with identity layout are always collapsible.
2854 if (srcType.getLayout().isIdentity())
2855 return true;
2856
2857 return succeeded(computeCollapsedLayoutMap(srcType, reassociation,
2858 /*strict=*/true));
2859}
2860
2861MemRefType CollapseShapeOp::computeCollapsedType(
2862 MemRefType srcType, ArrayRef<ReassociationIndices> reassociation) {
2863 SmallVector<int64_t> resultShape;
2864 resultShape.reserve(reassociation.size());
2865 for (const ReassociationIndices &group : reassociation) {
2866 auto groupSize = SaturatedInteger::wrap(1);
2867 for (int64_t srcDim : group)
2868 groupSize =
2869 groupSize * SaturatedInteger::wrap(srcType.getDimSize(srcDim));
2870 resultShape.push_back(groupSize.asInteger());
2871 }
2872
2873 if (srcType.getLayout().isIdentity()) {
2874 // If the source is contiguous (i.e., no layout map specified), so is the
2875 // result.
2876 MemRefLayoutAttrInterface layout;
2877 return MemRefType::get(resultShape, srcType.getElementType(), layout,
2878 srcType.getMemorySpace());
2879 }
2880
2881 // Source may not be fully contiguous. Compute the layout map.
2882 // Note: Dimensions that are collapsed into a single dim are assumed to be
2883 // contiguous.
2884 FailureOr<StridedLayoutAttr> computedLayout =
2885 computeCollapsedLayoutMap(srcType, reassociation);
2886 assert(succeeded(computedLayout) &&
2887 "invalid source layout map or collapsing non-contiguous dims");
2888 return MemRefType::get(resultShape, srcType.getElementType(), *computedLayout,
2889 srcType.getMemorySpace());
2890}
2891
2892void CollapseShapeOp::build(OpBuilder &b, OperationState &result, Value src,
2893 ArrayRef<ReassociationIndices> reassociation,
2894 ArrayRef<NamedAttribute> attrs) {
2895 auto srcType = llvm::cast<MemRefType>(src.getType());
2896 MemRefType resultType =
2897 CollapseShapeOp::computeCollapsedType(srcType, reassociation);
2899 getReassociationIndicesAttribute(b, reassociation));
2900 build(b, result, resultType, src, attrs);
2901}
2902
2903LogicalResult CollapseShapeOp::verify() {
2904 MemRefType srcType = getSrcType();
2905 MemRefType resultType = getResultType();
2906
2907 if (srcType.getRank() < resultType.getRank()) {
2908 auto r0 = srcType.getRank();
2909 auto r1 = resultType.getRank();
2910 return emitOpError("has source rank ")
2911 << r0 << " and result rank " << r1 << ". This is not a collapse ("
2912 << r0 << " < " << r1 << ").";
2913 }
2914
2915 // Verify result shape.
2916 if (failed(verifyCollapsedShape(getOperation(), resultType.getShape(),
2917 srcType.getShape(), getReassociationIndices(),
2918 /*allowMultipleDynamicDimsPerGroup=*/true)))
2919 return failure();
2920
2921 // Compute expected result type (including layout map).
2922 MemRefType expectedResultType;
2923 if (srcType.getLayout().isIdentity()) {
2924 // If the source is contiguous (i.e., no layout map specified), so is the
2925 // result.
2926 MemRefLayoutAttrInterface layout;
2927 expectedResultType =
2928 MemRefType::get(resultType.getShape(), srcType.getElementType(), layout,
2929 srcType.getMemorySpace());
2930 } else {
2931 // Source may not be fully contiguous. Compute the layout map.
2932 // Note: Dimensions that are collapsed into a single dim are assumed to be
2933 // contiguous.
2934 FailureOr<StridedLayoutAttr> computedLayout =
2935 computeCollapsedLayoutMap(srcType, getReassociationIndices());
2936 if (failed(computedLayout))
2937 return emitOpError(
2938 "invalid source layout map or collapsing non-contiguous dims");
2939 expectedResultType =
2940 MemRefType::get(resultType.getShape(), srcType.getElementType(),
2941 *computedLayout, srcType.getMemorySpace());
2942 }
2943
2944 if (expectedResultType != resultType)
2945 return emitOpError("expected collapsed type to be ")
2946 << expectedResultType << " but found " << resultType;
2947
2948 return success();
2949}
2950
2952 : public OpRewritePattern<CollapseShapeOp> {
2953public:
2954 using OpRewritePattern<CollapseShapeOp>::OpRewritePattern;
2955
2956 LogicalResult matchAndRewrite(CollapseShapeOp op,
2957 PatternRewriter &rewriter) const override {
2958 auto cast = op.getOperand().getDefiningOp<CastOp>();
2959 if (!cast)
2960 return failure();
2961
2962 if (!CastOp::canFoldIntoConsumerOp(cast))
2963 return failure();
2964
2965 Type newResultType = CollapseShapeOp::computeCollapsedType(
2966 llvm::cast<MemRefType>(cast.getOperand().getType()),
2967 op.getReassociationIndices());
2968
2969 if (newResultType == op.getResultType()) {
2970 rewriter.modifyOpInPlace(
2971 op, [&]() { op.getSrcMutable().assign(cast.getSource()); });
2972 } else {
2973 Value newOp =
2974 CollapseShapeOp::create(rewriter, op->getLoc(), cast.getSource(),
2975 op.getReassociationIndices());
2976 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(), newOp);
2977 }
2978 return success();
2979 }
2980};
2981
2982void CollapseShapeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2983 MLIRContext *context) {
2984 results.add<
2985 ComposeReassociativeReshapeOps<CollapseShapeOp, ReshapeOpKind::kCollapse>,
2986 ComposeCollapseOfExpandOp<CollapseShapeOp, ExpandShapeOp, CastOp,
2987 memref::DimOp, MemRefType>,
2988 CollapseShapeOpMemRefCastFolder>(context);
2989}
2990
2991OpFoldResult ExpandShapeOp::fold(FoldAdaptor adaptor) {
2993 adaptor.getOperands());
2994}
2995
2996OpFoldResult CollapseShapeOp::fold(FoldAdaptor adaptor) {
2998 adaptor.getOperands());
2999}
3000
3001FailureOr<std::optional<SmallVector<Value>>>
3002CollapseShapeOp::bubbleDownCasts(OpBuilder &builder) {
3003 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSrcMutable());
3004}
3005
3006//===----------------------------------------------------------------------===//
3007// ReshapeOp
3008//===----------------------------------------------------------------------===//
3009
3010void ReshapeOp::getAsmResultNames(
3011 function_ref<void(Value, StringRef)> setNameFn) {
3012 setNameFn(getResult(), "reshape");
3013}
3014
3015LogicalResult ReshapeOp::verify() {
3016 Type operandType = getSource().getType();
3017 Type resultType = getResult().getType();
3018
3019 Type operandElementType =
3020 llvm::cast<ShapedType>(operandType).getElementType();
3021 Type resultElementType = llvm::cast<ShapedType>(resultType).getElementType();
3022 if (operandElementType != resultElementType)
3023 return emitOpError("element types of source and destination memref "
3024 "types should be the same");
3025
3026 if (auto operandMemRefType = llvm::dyn_cast<MemRefType>(operandType))
3027 if (!operandMemRefType.getLayout().isIdentity())
3028 return emitOpError("source memref type should have identity affine map");
3029
3030 int64_t shapeSize =
3031 llvm::cast<MemRefType>(getShape().getType()).getDimSize(0);
3032 auto resultMemRefType = llvm::dyn_cast<MemRefType>(resultType);
3033 if (resultMemRefType) {
3034 if (!resultMemRefType.getLayout().isIdentity())
3035 return emitOpError("result memref type should have identity affine map");
3036 if (shapeSize == ShapedType::kDynamic)
3037 return emitOpError("cannot use shape operand with dynamic length to "
3038 "reshape to statically-ranked memref type");
3039 if (shapeSize != resultMemRefType.getRank())
3040 return emitOpError(
3041 "length of shape operand differs from the result's memref rank");
3042 }
3043 return success();
3044}
3045
3046FailureOr<std::optional<SmallVector<Value>>>
3047ReshapeOp::bubbleDownCasts(OpBuilder &builder) {
3048 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSourceMutable());
3049}
3050
3051//===----------------------------------------------------------------------===//
3052// StoreOp
3053//===----------------------------------------------------------------------===//
3054
3055LogicalResult StoreOp::fold(FoldAdaptor adaptor,
3056 SmallVectorImpl<OpFoldResult> &results) {
3057 /// store(memrefcast) -> store
3058 return foldMemRefCast(*this, getValueToStore());
3059}
3060
3061TypedValue<MemRefType> StoreOp::getAccessedMemref() { return getMemref(); }
3062
3063std::optional<SmallVector<Value>>
3064StoreOp::updateMemrefAndIndices(RewriterBase &rewriter, Value newMemref,
3065 ValueRange newIndices) {
3066 rewriter.modifyOpInPlace(*this, [&]() {
3067 getMemrefMutable().assign(newMemref);
3068 getIndicesMutable().assign(newIndices);
3069 });
3070 return std::nullopt;
3071}
3072
3073FailureOr<std::optional<SmallVector<Value>>>
3074StoreOp::bubbleDownCasts(OpBuilder &builder) {
3076 ValueRange());
3077}
3078
3079//===----------------------------------------------------------------------===//
3080// SubViewOp
3081//===----------------------------------------------------------------------===//
3082
3083void SubViewOp::getAsmResultNames(
3084 function_ref<void(Value, StringRef)> setNameFn) {
3085 setNameFn(getResult(), "subview");
3086}
3087
3088/// A subview result type can be fully inferred from the source type and the
3089/// static representation of offsets, sizes and strides. Special sentinels
3090/// encode the dynamic case.
3091MemRefType SubViewOp::inferResultType(MemRefType sourceMemRefType,
3092 ArrayRef<int64_t> staticOffsets,
3093 ArrayRef<int64_t> staticSizes,
3094 ArrayRef<int64_t> staticStrides) {
3095 unsigned rank = sourceMemRefType.getRank();
3096 (void)rank;
3097 assert(staticOffsets.size() == rank && "staticOffsets length mismatch");
3098 assert(staticSizes.size() == rank && "staticSizes length mismatch");
3099 assert(staticStrides.size() == rank && "staticStrides length mismatch");
3100
3101 // Extract source offset and strides.
3102 auto [sourceStrides, sourceOffset] = sourceMemRefType.getStridesAndOffset();
3103
3104 // Compute target offset whose value is:
3105 // `sourceOffset + sum_i(staticOffset_i * sourceStrides_i)`.
3106 int64_t targetOffset = sourceOffset;
3107 for (auto it : llvm::zip(staticOffsets, sourceStrides)) {
3108 auto staticOffset = std::get<0>(it), sourceStride = std::get<1>(it);
3109 targetOffset = (SaturatedInteger::wrap(targetOffset) +
3110 SaturatedInteger::wrap(staticOffset) *
3111 SaturatedInteger::wrap(sourceStride))
3112 .asInteger();
3113 }
3114
3115 // Compute target stride whose value is:
3116 // `sourceStrides_i * staticStrides_i`.
3117 SmallVector<int64_t, 4> targetStrides;
3118 targetStrides.reserve(staticOffsets.size());
3119 for (auto it : llvm::zip(sourceStrides, staticStrides)) {
3120 auto sourceStride = std::get<0>(it), staticStride = std::get<1>(it);
3121 targetStrides.push_back((SaturatedInteger::wrap(sourceStride) *
3122 SaturatedInteger::wrap(staticStride))
3123 .asInteger());
3124 }
3125
3126 // The type is now known.
3127 return MemRefType::get(staticSizes, sourceMemRefType.getElementType(),
3128 StridedLayoutAttr::get(sourceMemRefType.getContext(),
3129 targetOffset, targetStrides),
3130 sourceMemRefType.getMemorySpace());
3131}
3132
3133MemRefType SubViewOp::inferResultType(MemRefType sourceMemRefType,
3134 ArrayRef<OpFoldResult> offsets,
3135 ArrayRef<OpFoldResult> sizes,
3136 ArrayRef<OpFoldResult> strides) {
3137 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
3138 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
3139 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
3140 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
3141 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
3142 if (!hasValidSizesOffsets(staticOffsets))
3143 return {};
3144 if (!hasValidSizesOffsets(staticSizes))
3145 return {};
3146 if (!hasValidStrides(staticStrides))
3147 return {};
3148 return SubViewOp::inferResultType(sourceMemRefType, staticOffsets,
3149 staticSizes, staticStrides);
3150}
3151
3152MemRefType SubViewOp::inferRankReducedResultType(
3153 ArrayRef<int64_t> resultShape, MemRefType sourceRankedTensorType,
3154 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes,
3155 ArrayRef<int64_t> strides) {
3156 MemRefType inferredType =
3157 inferResultType(sourceRankedTensorType, offsets, sizes, strides);
3158 assert(inferredType.getRank() >= static_cast<int64_t>(resultShape.size()) &&
3159 "expected ");
3160 if (inferredType.getRank() == static_cast<int64_t>(resultShape.size()))
3161 return inferredType;
3162
3163 // Compute which dimensions are dropped.
3164 std::optional<llvm::SmallDenseSet<unsigned>> dimsToProject =
3165 computeRankReductionMask(inferredType.getShape(), resultShape);
3166 assert(dimsToProject.has_value() && "invalid rank reduction");
3167
3168 // Compute the layout and result type.
3169 auto inferredLayout = llvm::cast<StridedLayoutAttr>(inferredType.getLayout());
3170 SmallVector<int64_t> rankReducedStrides;
3171 rankReducedStrides.reserve(resultShape.size());
3172 for (auto [idx, value] : llvm::enumerate(inferredLayout.getStrides())) {
3173 if (!dimsToProject->contains(idx))
3174 rankReducedStrides.push_back(value);
3175 }
3176 return MemRefType::get(resultShape, inferredType.getElementType(),
3177 StridedLayoutAttr::get(inferredLayout.getContext(),
3178 inferredLayout.getOffset(),
3179 rankReducedStrides),
3180 inferredType.getMemorySpace());
3181}
3182
3183MemRefType SubViewOp::inferRankReducedResultType(
3184 ArrayRef<int64_t> resultShape, MemRefType sourceRankedTensorType,
3185 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
3186 ArrayRef<OpFoldResult> strides) {
3187 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
3188 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
3189 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
3190 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
3191 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
3192 return SubViewOp::inferRankReducedResultType(
3193 resultShape, sourceRankedTensorType, staticOffsets, staticSizes,
3194 staticStrides);
3195}
3196
3197// Build a SubViewOp with mixed static and dynamic entries and custom result
3198// type. If the type passed is nullptr, it is inferred.
3199void SubViewOp::build(OpBuilder &b, OperationState &result,
3200 MemRefType resultType, Value source,
3201 ArrayRef<OpFoldResult> offsets,
3202 ArrayRef<OpFoldResult> sizes,
3203 ArrayRef<OpFoldResult> strides,
3204 ArrayRef<NamedAttribute> attrs) {
3205 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
3206 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
3207 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
3208 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
3209 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
3210 auto sourceMemRefType = llvm::cast<MemRefType>(source.getType());
3211 // Structuring implementation this way avoids duplication between builders.
3212 if (!resultType) {
3213 resultType = SubViewOp::inferResultType(sourceMemRefType, staticOffsets,
3214 staticSizes, staticStrides);
3215 }
3216 result.addAttributes(attrs);
3217 build(b, result, resultType, source, dynamicOffsets, dynamicSizes,
3218 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
3219 b.getDenseI64ArrayAttr(staticSizes),
3220 b.getDenseI64ArrayAttr(staticStrides));
3221}
3222
3223// Build a SubViewOp with mixed static and dynamic entries and inferred result
3224// type.
3225void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
3226 ArrayRef<OpFoldResult> offsets,
3227 ArrayRef<OpFoldResult> sizes,
3228 ArrayRef<OpFoldResult> strides,
3229 ArrayRef<NamedAttribute> attrs) {
3230 build(b, result, MemRefType(), source, offsets, sizes, strides, attrs);
3231}
3232
3233// Build a SubViewOp with static entries and inferred result type.
3234void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
3235 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes,
3236 ArrayRef<int64_t> strides,
3237 ArrayRef<NamedAttribute> attrs) {
3238 SmallVector<OpFoldResult> offsetValues =
3239 llvm::map_to_vector<4>(offsets, [&](int64_t v) -> OpFoldResult {
3240 return b.getI64IntegerAttr(v);
3241 });
3242 SmallVector<OpFoldResult> sizeValues = llvm::map_to_vector<4>(
3243 sizes, [&](int64_t v) -> OpFoldResult { return b.getI64IntegerAttr(v); });
3244 SmallVector<OpFoldResult> strideValues =
3245 llvm::map_to_vector<4>(strides, [&](int64_t v) -> OpFoldResult {
3246 return b.getI64IntegerAttr(v);
3247 });
3248 build(b, result, source, offsetValues, sizeValues, strideValues, attrs);
3249}
3250
3251// Build a SubViewOp with dynamic entries and custom result type. If the
3252// type passed is nullptr, it is inferred.
3253void SubViewOp::build(OpBuilder &b, OperationState &result,
3254 MemRefType resultType, Value source,
3255 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes,
3256 ArrayRef<int64_t> strides,
3257 ArrayRef<NamedAttribute> attrs) {
3258 SmallVector<OpFoldResult> offsetValues =
3259 llvm::map_to_vector<4>(offsets, [&](int64_t v) -> OpFoldResult {
3260 return b.getI64IntegerAttr(v);
3261 });
3262 SmallVector<OpFoldResult> sizeValues = llvm::map_to_vector<4>(
3263 sizes, [&](int64_t v) -> OpFoldResult { return b.getI64IntegerAttr(v); });
3264 SmallVector<OpFoldResult> strideValues =
3265 llvm::map_to_vector<4>(strides, [&](int64_t v) -> OpFoldResult {
3266 return b.getI64IntegerAttr(v);
3267 });
3268 build(b, result, resultType, source, offsetValues, sizeValues, strideValues,
3269 attrs);
3270}
3271
3272// Build a SubViewOp with dynamic entries and custom result type. If the type
3273// passed is nullptr, it is inferred.
3274void SubViewOp::build(OpBuilder &b, OperationState &result,
3275 MemRefType resultType, Value source, ValueRange offsets,
3276 ValueRange sizes, ValueRange strides,
3277 ArrayRef<NamedAttribute> attrs) {
3278 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
3279 offsets, [](Value v) -> OpFoldResult { return v; });
3280 SmallVector<OpFoldResult> sizeValues =
3281 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
3282 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
3283 strides, [](Value v) -> OpFoldResult { return v; });
3284 build(b, result, resultType, source, offsetValues, sizeValues, strideValues);
3285}
3286
3287// Build a SubViewOp with dynamic entries and inferred result type.
3288void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
3289 ValueRange offsets, ValueRange sizes, ValueRange strides,
3290 ArrayRef<NamedAttribute> attrs) {
3291 build(b, result, MemRefType(), source, offsets, sizes, strides, attrs);
3292}
3293
3294/// For ViewLikeOpInterface.
3295Value SubViewOp::getViewSource() { return getSource(); }
3296
3297/// Return true if `t1` and `t2` have equal offsets (both dynamic or of same
3298/// static value).
3299static bool haveCompatibleOffsets(MemRefType t1, MemRefType t2) {
3300 int64_t t1Offset, t2Offset;
3301 SmallVector<int64_t> t1Strides, t2Strides;
3302 auto res1 = t1.getStridesAndOffset(t1Strides, t1Offset);
3303 auto res2 = t2.getStridesAndOffset(t2Strides, t2Offset);
3304 return succeeded(res1) && succeeded(res2) && t1Offset == t2Offset;
3305}
3306
3307/// Return true if `t1` and `t2` have equal strides (both dynamic or of same
3308/// static value). Dimensions of `t1` may be dropped in `t2`; these must be
3309/// marked as dropped in `droppedDims`.
3310static bool haveCompatibleStrides(MemRefType t1, MemRefType t2,
3311 const llvm::SmallBitVector &droppedDims) {
3312 assert(size_t(t1.getRank()) == droppedDims.size() &&
3313 "incorrect number of bits");
3314 assert(size_t(t1.getRank() - t2.getRank()) == droppedDims.count() &&
3315 "incorrect number of dropped dims");
3316 int64_t t1Offset, t2Offset;
3317 SmallVector<int64_t> t1Strides, t2Strides;
3318 auto res1 = t1.getStridesAndOffset(t1Strides, t1Offset);
3319 auto res2 = t2.getStridesAndOffset(t2Strides, t2Offset);
3320 if (failed(res1) || failed(res2))
3321 return false;
3322 for (int64_t i = 0, j = 0, e = t1.getRank(); i < e; ++i) {
3323 if (droppedDims[i])
3324 continue;
3325 if (t1Strides[i] != t2Strides[j])
3326 return false;
3327 ++j;
3328 }
3329 return true;
3330}
3331
3333 SubViewOp op, Type expectedType) {
3334 auto memrefType = llvm::cast<ShapedType>(expectedType);
3335 switch (result) {
3337 return success();
3339 return op->emitError("expected result rank to be smaller or equal to ")
3340 << "the source rank, but got " << op.getType();
3342 return op->emitError("expected result type to be ")
3343 << expectedType
3344 << " or a rank-reduced version. (mismatch of result sizes), but got "
3345 << op.getType();
3347 return op->emitError("expected result element type to be ")
3348 << memrefType.getElementType() << ", but got " << op.getType();
3350 return op->emitError(
3351 "expected result and source memory spaces to match, but got ")
3352 << op.getType();
3354 return op->emitError("expected result type to be ")
3355 << expectedType
3356 << " or a rank-reduced version. (mismatch of result layout), but "
3357 "got "
3358 << op.getType();
3359 }
3360 llvm_unreachable("unexpected subview verification result");
3361}
3362
3363/// Verifier for SubViewOp.
3364LogicalResult SubViewOp::verify() {
3365 MemRefType baseType = getSourceType();
3366 MemRefType subViewType = getType();
3367 ArrayRef<int64_t> staticOffsets = getStaticOffsets();
3368 ArrayRef<int64_t> staticSizes = getStaticSizes();
3369 ArrayRef<int64_t> staticStrides = getStaticStrides();
3370
3371 // The base memref and the view memref should be in the same memory space.
3372 if (baseType.getMemorySpace() != subViewType.getMemorySpace())
3373 return emitError("different memory spaces specified for base memref "
3374 "type ")
3375 << baseType << " and subview memref type " << subViewType;
3376
3377 // Verify that the base memref type has a strided layout map.
3378 if (!baseType.isStrided())
3379 return emitError("base type ") << baseType << " is not strided";
3380
3381 // Compute the expected result type, assuming that there are no rank
3382 // reductions.
3383 MemRefType expectedType = SubViewOp::inferResultType(
3384 baseType, staticOffsets, staticSizes, staticStrides);
3385
3386 // Verify all properties of a shaped type: rank, element type and dimension
3387 // sizes. This takes into account potential rank reductions.
3388 auto shapedTypeVerification = isRankReducedType(
3389 /*originalType=*/expectedType, /*candidateReducedType=*/subViewType);
3390 if (shapedTypeVerification != SliceVerificationResult::Success)
3391 return produceSubViewErrorMsg(shapedTypeVerification, *this, expectedType);
3392
3393 // Make sure that the memory space did not change.
3394 if (expectedType.getMemorySpace() != subViewType.getMemorySpace())
3396 *this, expectedType);
3397
3398 // Verify the offset of the layout map.
3399 if (!haveCompatibleOffsets(expectedType, subViewType))
3401 *this, expectedType);
3402
3403 // The only thing that's left to verify now are the strides. First, compute
3404 // the unused dimensions due to rank reductions. We have to look at sizes and
3405 // strides to decide which dimensions were dropped. This function also
3406 // partially verifies strides in case of rank reductions.
3407 auto unusedDims = computeMemRefRankReductionMask(expectedType, subViewType,
3408 getMixedSizes());
3409 if (failed(unusedDims))
3411 *this, expectedType);
3412
3413 // Strides must match.
3414 if (!haveCompatibleStrides(expectedType, subViewType, *unusedDims))
3416 *this, expectedType);
3417
3418 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
3419 // to the base memref.
3420 SliceBoundsVerificationResult boundsResult =
3421 verifyInBoundsSlice(baseType.getShape(), staticOffsets, staticSizes,
3422 staticStrides, /*generateErrorMessage=*/true);
3423 if (!boundsResult.isValid)
3424 return getOperation()->emitError(boundsResult.errorMessage);
3425
3426 return success();
3427}
3428
3430 return os << "range " << range.offset << ":" << range.size << ":"
3431 << range.stride;
3432}
3433
3434/// Return the list of Range (i.e. offset, size, stride). Each Range
3435/// entry contains either the dynamic value or a ConstantIndexOp constructed
3436/// with `b` at location `loc`.
3437SmallVector<Range, 8> mlir::getOrCreateRanges(OffsetSizeAndStrideOpInterface op,
3438 OpBuilder &b, Location loc) {
3439 std::array<unsigned, 3> ranks = op.getArrayAttrMaxRanks();
3440 assert(ranks[0] == ranks[1] && "expected offset and sizes of equal ranks");
3441 assert(ranks[1] == ranks[2] && "expected sizes and strides of equal ranks");
3443 unsigned rank = ranks[0];
3444 res.reserve(rank);
3445 for (unsigned idx = 0; idx < rank; ++idx) {
3446 Value offset =
3447 op.isDynamicOffset(idx)
3448 ? op.getDynamicOffset(idx)
3449 : arith::ConstantIndexOp::create(b, loc, op.getStaticOffset(idx));
3450 Value size =
3451 op.isDynamicSize(idx)
3452 ? op.getDynamicSize(idx)
3453 : arith::ConstantIndexOp::create(b, loc, op.getStaticSize(idx));
3454 Value stride =
3455 op.isDynamicStride(idx)
3456 ? op.getDynamicStride(idx)
3457 : arith::ConstantIndexOp::create(b, loc, op.getStaticStride(idx));
3458 res.emplace_back(Range{offset, size, stride});
3459 }
3460 return res;
3461}
3462
3463/// Compute the canonical result type of a SubViewOp. Call `inferResultType`
3464/// to deduce the result type for the given `sourceType`. Additionally, reduce
3465/// the rank of the inferred result type if `currentResultType` is lower rank
3466/// than `currentSourceType`. Use this signature if `sourceType` is updated
3467/// together with the result type. In this case, it is important to compute
3468/// the dropped dimensions using `currentSourceType` whose strides align with
3469/// `currentResultType`.
3471 MemRefType currentResultType, MemRefType currentSourceType,
3472 MemRefType sourceType, ArrayRef<OpFoldResult> mixedOffsets,
3473 ArrayRef<OpFoldResult> mixedSizes, ArrayRef<OpFoldResult> mixedStrides) {
3474 MemRefType nonRankReducedType = SubViewOp::inferResultType(
3475 sourceType, mixedOffsets, mixedSizes, mixedStrides);
3476 FailureOr<llvm::SmallBitVector> unusedDims = computeMemRefRankReductionMask(
3477 currentSourceType, currentResultType, mixedSizes);
3478 if (failed(unusedDims))
3479 return nullptr;
3480
3481 auto layout = llvm::cast<StridedLayoutAttr>(nonRankReducedType.getLayout());
3482 SmallVector<int64_t> shape, strides;
3483 unsigned numDimsAfterReduction =
3484 nonRankReducedType.getRank() - unusedDims->count();
3485 shape.reserve(numDimsAfterReduction);
3486 strides.reserve(numDimsAfterReduction);
3487 for (const auto &[idx, size, stride] :
3488 llvm::zip(llvm::seq<unsigned>(0, nonRankReducedType.getRank()),
3489 nonRankReducedType.getShape(), layout.getStrides())) {
3490 if (unusedDims->test(idx))
3491 continue;
3492 shape.push_back(size);
3493 strides.push_back(stride);
3494 }
3495
3496 return MemRefType::get(shape, nonRankReducedType.getElementType(),
3497 StridedLayoutAttr::get(sourceType.getContext(),
3498 layout.getOffset(), strides),
3499 nonRankReducedType.getMemorySpace());
3500}
3501
3503 OpBuilder &b, Location loc, Value memref, ArrayRef<int64_t> targetShape) {
3504 auto memrefType = llvm::cast<MemRefType>(memref.getType());
3505 unsigned rank = memrefType.getRank();
3506 SmallVector<OpFoldResult> offsets(rank, b.getIndexAttr(0));
3508 SmallVector<OpFoldResult> strides(rank, b.getIndexAttr(1));
3509 MemRefType targetType = SubViewOp::inferRankReducedResultType(
3510 targetShape, memrefType, offsets, sizes, strides);
3511 return b.createOrFold<memref::SubViewOp>(loc, targetType, memref, offsets,
3512 sizes, strides);
3513}
3514
3515FailureOr<Value> SubViewOp::rankReduceIfNeeded(OpBuilder &b, Location loc,
3516 Value value,
3517 ArrayRef<int64_t> desiredShape) {
3518 auto sourceMemrefType = llvm::dyn_cast<MemRefType>(value.getType());
3519 assert(sourceMemrefType && "not a ranked memref type");
3520 auto sourceShape = sourceMemrefType.getShape();
3521 if (sourceShape.equals(desiredShape))
3522 return value;
3523 auto maybeRankReductionMask =
3524 mlir::computeRankReductionMask(sourceShape, desiredShape);
3525 if (!maybeRankReductionMask)
3526 return failure();
3527 return createCanonicalRankReducingSubViewOp(b, loc, value, desiredShape);
3528}
3529
3530/// Helper method to check if a `subview` operation is trivially a no-op. This
3531/// is the case if the all offsets are zero, all strides are 1, and the source
3532/// shape is same as the size of the subview. In such cases, the subview can
3533/// be folded into its source.
3534static bool isTrivialSubViewOp(SubViewOp subViewOp) {
3535 if (subViewOp.getSourceType().getRank() != subViewOp.getType().getRank())
3536 return false;
3537
3538 auto mixedOffsets = subViewOp.getMixedOffsets();
3539 auto mixedSizes = subViewOp.getMixedSizes();
3540 auto mixedStrides = subViewOp.getMixedStrides();
3541
3542 // Check offsets are zero.
3543 if (llvm::any_of(mixedOffsets, [](OpFoldResult ofr) {
3544 std::optional<int64_t> intValue = getConstantIntValue(ofr);
3545 return !intValue || intValue.value() != 0;
3546 }))
3547 return false;
3548
3549 // Check strides are one.
3550 if (llvm::any_of(mixedStrides, [](OpFoldResult ofr) {
3551 std::optional<int64_t> intValue = getConstantIntValue(ofr);
3552 return !intValue || intValue.value() != 1;
3553 }))
3554 return false;
3555
3556 // Check all size values are static and matches the (static) source shape.
3557 ArrayRef<int64_t> sourceShape = subViewOp.getSourceType().getShape();
3558 for (const auto &size : llvm::enumerate(mixedSizes)) {
3559 std::optional<int64_t> intValue = getConstantIntValue(size.value());
3560 if (!intValue || *intValue != sourceShape[size.index()])
3561 return false;
3562 }
3563 // All conditions met. The `SubViewOp` is foldable as a no-op.
3564 return true;
3565}
3566
3567namespace {
3568/// Pattern to rewrite a subview op with MemRefCast arguments.
3569/// This essentially pushes memref.cast past its consuming subview when
3570/// `canFoldIntoConsumerOp` is true.
3571///
3572/// Example:
3573/// ```
3574/// %0 = memref.cast %V : memref<16x16xf32> to memref<?x?xf32>
3575/// %1 = memref.subview %0[0, 0][3, 4][1, 1] :
3576/// memref<?x?xf32> to memref<3x4xf32, strided<[?, 1], offset: ?>>
3577/// ```
3578/// is rewritten into:
3579/// ```
3580/// %0 = memref.subview %V: memref<16x16xf32> to memref<3x4xf32, #[[map0]]>
3581/// %1 = memref.cast %0: memref<3x4xf32, strided<[16, 1], offset: 0>> to
3582/// memref<3x4xf32, strided<[?, 1], offset: ?>>
3583/// ```
3584class SubViewOpMemRefCastFolder final : public OpRewritePattern<SubViewOp> {
3585public:
3586 using OpRewritePattern<SubViewOp>::OpRewritePattern;
3587
3588 LogicalResult matchAndRewrite(SubViewOp subViewOp,
3589 PatternRewriter &rewriter) const override {
3590 // Any constant operand, just return to let SubViewOpConstantFolder kick
3591 // in.
3592 if (llvm::any_of(subViewOp.getOperands(), [](Value operand) {
3593 return matchPattern(operand, matchConstantIndex());
3594 }))
3595 return failure();
3596
3597 auto castOp = subViewOp.getSource().getDefiningOp<CastOp>();
3598 if (!castOp)
3599 return failure();
3600
3601 if (!CastOp::canFoldIntoConsumerOp(castOp))
3602 return failure();
3603
3604 // Compute the SubViewOp result type after folding the MemRefCastOp. Use
3605 // the MemRefCastOp source operand type to infer the result type and the
3606 // current SubViewOp source operand type to compute the dropped dimensions
3607 // if the operation is rank-reducing.
3608 auto resultType = getCanonicalSubViewResultType(
3609 subViewOp.getType(), subViewOp.getSourceType(),
3610 llvm::cast<MemRefType>(castOp.getSource().getType()),
3611 subViewOp.getMixedOffsets(), subViewOp.getMixedSizes(),
3612 subViewOp.getMixedStrides());
3613 if (!resultType)
3614 return failure();
3615
3616 Value newSubView = SubViewOp::create(
3617 rewriter, subViewOp.getLoc(), resultType, castOp.getSource(),
3618 subViewOp.getOffsets(), subViewOp.getSizes(), subViewOp.getStrides(),
3619 subViewOp.getStaticOffsets(), subViewOp.getStaticSizes(),
3620 subViewOp.getStaticStrides());
3621 rewriter.replaceOpWithNewOp<CastOp>(subViewOp, subViewOp.getType(),
3622 newSubView);
3623 return success();
3624 }
3625};
3626
3627/// Canonicalize subview ops that are no-ops. When the source shape is not
3628/// same as a result shape due to use of `affine_map`.
3629class TrivialSubViewOpFolder final : public OpRewritePattern<SubViewOp> {
3630public:
3631 using OpRewritePattern<SubViewOp>::OpRewritePattern;
3632
3633 LogicalResult matchAndRewrite(SubViewOp subViewOp,
3634 PatternRewriter &rewriter) const override {
3635 if (!isTrivialSubViewOp(subViewOp))
3636 return failure();
3637 if (subViewOp.getSourceType() == subViewOp.getType()) {
3638 rewriter.replaceOp(subViewOp, subViewOp.getSource());
3639 return success();
3640 }
3641 rewriter.replaceOpWithNewOp<CastOp>(subViewOp, subViewOp.getType(),
3642 subViewOp.getSource());
3643 return success();
3644 }
3645};
3646} // namespace
3647
3648/// Return the canonical type of the result of a subview.
3650 MemRefType operator()(SubViewOp op, ArrayRef<OpFoldResult> mixedOffsets,
3651 ArrayRef<OpFoldResult> mixedSizes,
3652 ArrayRef<OpFoldResult> mixedStrides) {
3653 // Infer a memref type without taking into account any rank reductions.
3654 MemRefType resTy = SubViewOp::inferResultType(
3655 op.getSourceType(), mixedOffsets, mixedSizes, mixedStrides);
3656 if (!resTy)
3657 return {};
3658 MemRefType nonReducedType = resTy;
3659
3660 // Directly return the non-rank reduced type if there are no dropped dims.
3661 llvm::SmallBitVector droppedDims = op.getDroppedDims();
3662 if (droppedDims.none())
3663 return nonReducedType;
3664
3665 // Take the strides and offset from the non-rank reduced type.
3666 auto [nonReducedStrides, offset] = nonReducedType.getStridesAndOffset();
3667
3668 // Drop dims from shape and strides.
3669 SmallVector<int64_t> targetShape;
3670 SmallVector<int64_t> targetStrides;
3671 for (int64_t i = 0; i < static_cast<int64_t>(mixedSizes.size()); ++i) {
3672 if (droppedDims.test(i))
3673 continue;
3674 targetStrides.push_back(nonReducedStrides[i]);
3675 targetShape.push_back(nonReducedType.getDimSize(i));
3676 }
3677
3678 return MemRefType::get(targetShape, nonReducedType.getElementType(),
3679 StridedLayoutAttr::get(nonReducedType.getContext(),
3680 offset, targetStrides),
3681 nonReducedType.getMemorySpace());
3682 }
3683};
3684
3685/// A canonicalizer wrapper to replace SubViewOps.
3687 void operator()(PatternRewriter &rewriter, SubViewOp op, SubViewOp newOp) {
3688 rewriter.replaceOpWithNewOp<CastOp>(op, op.getType(), newOp);
3689 }
3690};
3691
3692void SubViewOp::getCanonicalizationPatterns(RewritePatternSet &results,
3693 MLIRContext *context) {
3694 results
3695 .add<OpWithOffsetSizesAndStridesConstantArgumentFolder<
3696 SubViewOp, SubViewReturnTypeCanonicalizer, SubViewCanonicalizer>,
3697 SubViewOpMemRefCastFolder, TrivialSubViewOpFolder>(context);
3698}
3699
3700OpFoldResult SubViewOp::fold(FoldAdaptor adaptor) {
3701 MemRefType sourceMemrefType = getSource().getType();
3702 MemRefType resultMemrefType = getResult().getType();
3703 auto resultLayout =
3704 dyn_cast_if_present<StridedLayoutAttr>(resultMemrefType.getLayout());
3705
3706 if (resultMemrefType == sourceMemrefType &&
3707 resultMemrefType.hasStaticShape() &&
3708 (!resultLayout || resultLayout.hasStaticLayout())) {
3709 return getViewSource();
3710 }
3711
3712 // Fold subview(subview(x)), where both subviews have the same size and the
3713 // second subview's offsets are all zero. (I.e., the second subview is a
3714 // no-op.)
3715 if (auto srcSubview = getViewSource().getDefiningOp<SubViewOp>()) {
3716 auto srcSizes = srcSubview.getMixedSizes();
3717 auto sizes = getMixedSizes();
3718 auto offsets = getMixedOffsets();
3719 bool allOffsetsZero = llvm::all_of(offsets, isZeroInteger);
3720 auto strides = getMixedStrides();
3721 bool allStridesOne = llvm::all_of(strides, isOneInteger);
3722 bool allSizesSame = llvm::equal(sizes, srcSizes);
3723 if (allOffsetsZero && allStridesOne && allSizesSame &&
3724 resultMemrefType == sourceMemrefType)
3725 return getViewSource();
3726 }
3727
3728 return {};
3729}
3730
3731FailureOr<std::optional<SmallVector<Value>>>
3732SubViewOp::bubbleDownCasts(OpBuilder &builder) {
3733 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSourceMutable());
3734}
3735
3736void SubViewOp::inferStridedMetadataRanges(
3737 ArrayRef<StridedMetadataRange> ranges, GetIntRangeFn getIntRange,
3738 SetStridedMetadataRangeFn setMetadata, int32_t indexBitwidth) {
3739 auto isUninitialized =
3740 +[](IntegerValueRange range) { return range.isUninitialized(); };
3741
3742 // Bail early if any of the operands metadata is not ready:
3743 SmallVector<IntegerValueRange> offsetOperands =
3744 getIntValueRanges(getMixedOffsets(), getIntRange, indexBitwidth);
3745 if (llvm::any_of(offsetOperands, isUninitialized))
3746 return;
3747
3748 SmallVector<IntegerValueRange> sizeOperands =
3749 getIntValueRanges(getMixedSizes(), getIntRange, indexBitwidth);
3750 if (llvm::any_of(sizeOperands, isUninitialized))
3751 return;
3752
3753 SmallVector<IntegerValueRange> stridesOperands =
3754 getIntValueRanges(getMixedStrides(), getIntRange, indexBitwidth);
3755 if (llvm::any_of(stridesOperands, isUninitialized))
3756 return;
3757
3758 StridedMetadataRange sourceRange =
3759 ranges[getSourceMutable().getOperandNumber()];
3760 if (sourceRange.isUninitialized())
3761 return;
3762
3763 ArrayRef<ConstantIntRanges> srcStrides = sourceRange.getStrides();
3764
3765 // Get the dropped dims.
3766 llvm::SmallBitVector droppedDims = getDroppedDims();
3767
3768 // Compute the new offset, strides and sizes.
3769 ConstantIntRanges offset = sourceRange.getOffsets()[0];
3770 SmallVector<ConstantIntRanges> strides, sizes;
3771
3772 for (size_t i = 0, e = droppedDims.size(); i < e; ++i) {
3773 bool dropped = droppedDims.test(i);
3774 // Compute the new offset.
3775 ConstantIntRanges off =
3776 intrange::inferMul({offsetOperands[i].getValue(), srcStrides[i]});
3777 offset = intrange::inferAdd({offset, off});
3778
3779 // Skip dropped dimensions.
3780 if (dropped)
3781 continue;
3782 // Multiply the strides.
3783 strides.push_back(
3784 intrange::inferMul({stridesOperands[i].getValue(), srcStrides[i]}));
3785 // Get the sizes.
3786 sizes.push_back(sizeOperands[i].getValue());
3787 }
3788
3789 setMetadata(getResult(),
3791 SmallVector<ConstantIntRanges>({std::move(offset)}),
3792 std::move(sizes), std::move(strides)));
3793}
3794
3795//===----------------------------------------------------------------------===//
3796// TransposeOp
3797//===----------------------------------------------------------------------===//
3798
3799void TransposeOp::getAsmResultNames(
3800 function_ref<void(Value, StringRef)> setNameFn) {
3801 setNameFn(getResult(), "transpose");
3802}
3803
3804/// Build a strided memref type by applying `permutationMap` to `memRefType`.
3805static MemRefType inferTransposeResultType(MemRefType memRefType,
3806 AffineMap permutationMap) {
3807 auto originalSizes = memRefType.getShape();
3808 auto [originalStrides, offset] = memRefType.getStridesAndOffset();
3809 assert(originalStrides.size() == static_cast<unsigned>(memRefType.getRank()));
3810
3811 // Compute permuted sizes and strides.
3812 auto sizes = applyPermutationMap<int64_t>(permutationMap, originalSizes);
3813 auto strides = applyPermutationMap<int64_t>(permutationMap, originalStrides);
3814
3815 return MemRefType::Builder(memRefType)
3816 .setShape(sizes)
3817 .setLayout(
3818 StridedLayoutAttr::get(memRefType.getContext(), offset, strides));
3819}
3820
3821Value TransposeOp::getViewSource() { return getIn(); }
3822
3823void TransposeOp::build(OpBuilder &b, OperationState &result, Value in,
3824 AffineMapAttr permutation,
3825 ArrayRef<NamedAttribute> attrs) {
3826 auto permutationMap = permutation.getValue();
3827 assert(permutationMap);
3828
3829 auto memRefType = llvm::cast<MemRefType>(in.getType());
3830 // Compute result type.
3831 MemRefType resultType = inferTransposeResultType(memRefType, permutationMap);
3832
3833 result.addAttribute(TransposeOp::getPermutationAttrStrName(), permutation);
3834 build(b, result, resultType, in, attrs);
3835}
3836
3837// transpose $in $permutation attr-dict : type($in) `to` type(results)
3838void TransposeOp::print(OpAsmPrinter &p) {
3839 p << " " << getIn() << " " << getPermutation();
3840 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary(),
3841 {getPermutationAttrStrName()});
3842 p << " : " << getIn().getType() << " to " << getType();
3843}
3844
3845ParseResult TransposeOp::parse(OpAsmParser &parser, OperationState &result) {
3846 OpAsmParser::UnresolvedOperand in;
3847 AffineMap permutation;
3848 MemRefType srcType, dstType;
3849 if (parser.parseOperand(in) || parser.parseAffineMap(permutation) ||
3850 parser.parseOptionalAttrDict(result.attributes) ||
3851 parser.parseColonType(srcType) ||
3852 parser.resolveOperand(in, srcType, result.operands) ||
3853 parser.parseKeywordType("to", dstType) ||
3854 parser.addTypeToList(dstType, result.types))
3855 return failure();
3856
3857 result.addAttribute(TransposeOp::getPermutationAttrStrName(),
3858 AffineMapAttr::get(permutation));
3859 return success();
3860}
3861
3862LogicalResult TransposeOp::verify() {
3863 if (!getPermutation().isPermutation())
3864 return emitOpError("expected a permutation map");
3865 if (getPermutation().getNumDims() != getIn().getType().getRank())
3866 return emitOpError("expected a permutation map of same rank as the input");
3867
3868 auto srcType = llvm::cast<MemRefType>(getIn().getType());
3869 auto resultType = llvm::cast<MemRefType>(getType());
3870 auto canonicalResultType = inferTransposeResultType(srcType, getPermutation())
3871 .canonicalizeStridedLayout();
3872
3873 if (resultType.canonicalizeStridedLayout() != canonicalResultType)
3874 return emitOpError("result type ")
3875 << resultType
3876 << " is not equivalent to the canonical transposed input type "
3877 << canonicalResultType;
3878 return success();
3879}
3880
3881OpFoldResult TransposeOp::fold(FoldAdaptor) {
3882 // First check for identity permutation, we can fold it away if input and
3883 // result types are identical already.
3884 if (getPermutation().isIdentity() && getType() == getIn().getType())
3885 return getIn();
3886 // Fold two consecutive memref.transpose Ops into one by composing their
3887 // permutation maps.
3888 if (auto otherTransposeOp = getIn().getDefiningOp<memref::TransposeOp>()) {
3889 AffineMap composedPermutation =
3890 getPermutation().compose(otherTransposeOp.getPermutation());
3891 getInMutable().assign(otherTransposeOp.getIn());
3892 setPermutation(composedPermutation);
3893 return getResult();
3894 }
3895 return {};
3896}
3897
3898FailureOr<std::optional<SmallVector<Value>>>
3899TransposeOp::bubbleDownCasts(OpBuilder &builder) {
3900 return bubbleDownCastsPassthroughOpImpl(*this, builder, getInMutable());
3901}
3902
3903//===----------------------------------------------------------------------===//
3904// ViewOp
3905//===----------------------------------------------------------------------===//
3906
3907void ViewOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
3908 setNameFn(getResult(), "view");
3909}
3910
3911LogicalResult ViewOp::verify() {
3912 auto baseType = llvm::cast<MemRefType>(getOperand(0).getType());
3913 auto viewType = getType();
3914
3915 // The base memref should have identity layout map (or none).
3916 if (!baseType.getLayout().isIdentity())
3917 return emitError("unsupported map for base memref type ") << baseType;
3918
3919 // The result memref should have identity layout map (or none).
3920 if (!viewType.getLayout().isIdentity())
3921 return emitError("unsupported map for result memref type ") << viewType;
3922
3923 // The base memref and the view memref should be in the same memory space.
3924 if (baseType.getMemorySpace() != viewType.getMemorySpace())
3925 return emitError("different memory spaces specified for base memref "
3926 "type ")
3927 << baseType << " and view memref type " << viewType;
3928
3929 // Verify that we have the correct number of sizes for the result type.
3930 if (failed(verifyDynamicDimensionCount(getOperation(), viewType, getSizes())))
3931 return failure();
3932
3933 return success();
3934}
3935
3936Value ViewOp::getViewSource() { return getSource(); }
3937
3938OpFoldResult ViewOp::fold(FoldAdaptor adaptor) {
3939 MemRefType sourceMemrefType = getSource().getType();
3940 MemRefType resultMemrefType = getResult().getType();
3941
3942 if (resultMemrefType == sourceMemrefType &&
3943 resultMemrefType.hasStaticShape() && isZeroInteger(getByteShift()))
3944 return getViewSource();
3945
3946 return {};
3947}
3948
3949SmallVector<OpFoldResult> ViewOp::getMixedSizes() {
3950 SmallVector<OpFoldResult> result;
3951 unsigned ctr = 0;
3952 Builder b(getContext());
3953 for (int64_t dim : getType().getShape()) {
3954 if (ShapedType::isDynamic(dim)) {
3955 result.push_back(getSizes()[ctr++]);
3956 } else {
3957 result.push_back(b.getIndexAttr(dim));
3958 }
3959 }
3960 return result;
3961}
3962
3963namespace {
3964/// Given a memref type and a range of values that defines its dynamic
3965/// dimension sizes, turn all dynamic sizes that have a constant value into
3966/// static dimension sizes.
3967static MemRefType
3968foldDynamicToStaticDimSizes(MemRefType type, ValueRange dynamicSizes,
3969 SmallVectorImpl<Value> &foldedDynamicSizes) {
3970 SmallVector<int64_t> staticShape(type.getShape());
3971 assert(type.getNumDynamicDims() == dynamicSizes.size() &&
3972 "incorrect number of dynamic sizes");
3973
3974 // Compute new static and dynamic sizes.
3975 unsigned ctr = 0;
3976 for (auto [dim, dimSize] : llvm::enumerate(type.getShape())) {
3977 if (ShapedType::isStatic(dimSize))
3978 continue;
3979
3980 Value dynamicSize = dynamicSizes[ctr++];
3981 if (auto cst = getConstantIntValue(dynamicSize)) {
3982 // Dynamic size must be non-negative.
3983 if (cst.value() < 0) {
3984 foldedDynamicSizes.push_back(dynamicSize);
3985 continue;
3986 }
3987 staticShape[dim] = cst.value();
3988 } else {
3989 foldedDynamicSizes.push_back(dynamicSize);
3990 }
3991 }
3992
3993 return MemRefType::Builder(type).setShape(staticShape);
3994}
3995
3996/// Change the result type of a `memref.view` by making originally dynamic
3997/// dimensions static when their sizes come from `constant` ops.
3998/// Example:
3999/// ```
4000/// %c5 = arith.constant 5: index
4001/// %0 = memref.view %src[%offset][%c5] : memref<?xi8> to memref<?x4xf32>
4002/// ```
4003/// to
4004/// ```
4005/// %0 = memref.view %src[%offset][] : memref<?xi8> to memref<5x4xf32>
4006/// ```
4007struct ViewOpShapeFolder : public OpRewritePattern<ViewOp> {
4008 using Base::Base;
4009
4010 LogicalResult matchAndRewrite(ViewOp viewOp,
4011 PatternRewriter &rewriter) const override {
4012 SmallVector<Value> foldedDynamicSizes;
4013 MemRefType resultType = viewOp.getType();
4014 MemRefType foldedMemRefType = foldDynamicToStaticDimSizes(
4015 resultType, viewOp.getSizes(), foldedDynamicSizes);
4016
4017 // Stop here if no dynamic size was promoted to static.
4018 if (foldedMemRefType == resultType)
4019 return failure();
4020
4021 // Create new ViewOp.
4022 auto newViewOp = ViewOp::create(rewriter, viewOp.getLoc(), foldedMemRefType,
4023 viewOp.getSource(), viewOp.getByteShift(),
4024 foldedDynamicSizes);
4025 // Insert a cast so we have the same type as the old memref type.
4026 rewriter.replaceOpWithNewOp<CastOp>(viewOp, resultType, newViewOp);
4027 return success();
4028 }
4029};
4030
4031/// view(memref.cast(%source)) -> view(%source).
4032struct ViewOpMemrefCastFolder : public OpRewritePattern<ViewOp> {
4033 using Base::Base;
4034
4035 LogicalResult matchAndRewrite(ViewOp viewOp,
4036 PatternRewriter &rewriter) const override {
4037 auto memrefCastOp = viewOp.getSource().getDefiningOp<CastOp>();
4038 if (!memrefCastOp)
4039 return failure();
4040
4041 rewriter.replaceOpWithNewOp<ViewOp>(
4042 viewOp, viewOp.getType(), memrefCastOp.getSource(),
4043 viewOp.getByteShift(), viewOp.getSizes());
4044 return success();
4045 }
4046};
4047} // namespace
4048
4049void ViewOp::getCanonicalizationPatterns(RewritePatternSet &results,
4050 MLIRContext *context) {
4051 results.add<ViewOpShapeFolder, ViewOpMemrefCastFolder>(context);
4052}
4053
4054FailureOr<std::optional<SmallVector<Value>>>
4055ViewOp::bubbleDownCasts(OpBuilder &builder) {
4056 return bubbleDownCastsPassthroughOpImpl(*this, builder, getSourceMutable());
4057}
4058
4059//===----------------------------------------------------------------------===//
4060// AtomicRMWOp
4061//===----------------------------------------------------------------------===//
4062
4063LogicalResult AtomicRMWOp::verify() {
4064 switch (getKind()) {
4065 case arith::AtomicRMWKind::addf:
4066 case arith::AtomicRMWKind::maximumf:
4067 case arith::AtomicRMWKind::minimumf:
4068 case arith::AtomicRMWKind::mulf:
4069 if (!llvm::isa<FloatType>(getValue().getType()))
4070 return emitOpError() << "with kind '"
4071 << arith::stringifyAtomicRMWKind(getKind())
4072 << "' expects a floating-point type";
4073 break;
4074 case arith::AtomicRMWKind::addi:
4075 case arith::AtomicRMWKind::maxs:
4076 case arith::AtomicRMWKind::maxu:
4077 case arith::AtomicRMWKind::mins:
4078 case arith::AtomicRMWKind::minu:
4079 case arith::AtomicRMWKind::muli:
4080 case arith::AtomicRMWKind::ori:
4081 case arith::AtomicRMWKind::xori:
4082 case arith::AtomicRMWKind::andi:
4083 if (!llvm::isa<IntegerType>(getValue().getType()))
4084 return emitOpError() << "with kind '"
4085 << arith::stringifyAtomicRMWKind(getKind())
4086 << "' expects an integer type";
4087 break;
4088 default:
4089 break;
4090 }
4091 return success();
4092}
4093
4094OpFoldResult AtomicRMWOp::fold(FoldAdaptor adaptor) {
4095 /// atomicrmw(memrefcast) -> atomicrmw
4096 if (succeeded(foldMemRefCast(*this, getValue())))
4097 return getResult();
4098 return OpFoldResult();
4099}
4100
4101FailureOr<std::optional<SmallVector<Value>>>
4102AtomicRMWOp::bubbleDownCasts(OpBuilder &builder) {
4104 getResult());
4105}
4106
4107TypedValue<MemRefType> AtomicRMWOp::getAccessedMemref() { return getMemref(); }
4108
4109std::optional<SmallVector<Value>>
4110AtomicRMWOp::updateMemrefAndIndices(RewriterBase &rewriter, Value newMemref,
4111 ValueRange newIndices) {
4112 rewriter.modifyOpInPlace(*this, [&]() {
4113 getMemrefMutable().assign(newMemref);
4114 getIndicesMutable().assign(newIndices);
4115 });
4116 return std::nullopt;
4117}
4118
4119//===----------------------------------------------------------------------===//
4120// TableGen'd op method definitions
4121//===----------------------------------------------------------------------===//
4122
4123#define GET_OP_CLASSES
4124#include "mlir/Dialect/MemRef/IR/MemRefOps.cpp.inc"
return success()
getNumOperands() - 1))) return failure()
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static bool hasSideEffects(Operation *op)
static bool isPermutation(const std::vector< PermutationTy > &permutation)
Definition IRAffine.cpp:60
static int64_t getNumElements(Type t)
Compute the total number of elements in the given type, also taking into account nested types.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
auto load
static LogicalResult foldCopyOfCast(CopyOp op)
If the source/target of a CopyOp is a CastOp that does not modify the shape and element type,...
static void constifyIndexValues(SmallVectorImpl< OpFoldResult > &values, ArrayRef< int64_t > constValues)
Helper function that sets values[i] to constValues[i] if the latter is a static value,...
Definition MemRefOps.cpp:98
static void printGlobalMemrefOpTypeAndInitialValue(OpAsmPrinter &p, GlobalOp op, TypeAttr type, Attribute initialValue)
static LogicalResult verifyCollapsedShape(Operation *op, ArrayRef< int64_t > collapsedShape, ArrayRef< int64_t > expandedShape, ArrayRef< ReassociationIndices > reassociation, bool allowMultipleDynamicDimsPerGroup)
Helper function for verifying the shape of ExpandShapeOp and ResultShapeOp result and operand.
static bool isOpItselfPotentialAutomaticAllocation(Operation *op)
Given an operation, return whether this op itself could allocate an AutomaticAllocationScopeResource.
static MemRefType inferTransposeResultType(MemRefType memRefType, AffineMap permutationMap)
Build a strided memref type by applying permutationMap to memRefType.
static ParseResult parseBoolAttr(OpAsmParser &parser, BoolAttr &result)
static bool isGuaranteedAutomaticAllocation(Operation *op)
Given an operation, return whether this op is guaranteed to allocate an AutomaticAllocationScopeResou...
static FailureOr< llvm::SmallBitVector > computeMemRefRankReductionMaskByStrides(MemRefType originalType, MemRefType reducedType, ArrayRef< int64_t > originalStrides, ArrayRef< int64_t > candidateStrides, llvm::SmallBitVector unusedDims)
Returns the set of source dimensions that are dropped in a rank reduction.
static FailureOr< StridedLayoutAttr > computeExpandedLayoutMap(MemRefType srcType, ArrayRef< int64_t > resultShape, ArrayRef< ReassociationIndices > reassociation)
Compute the layout map after expanding a given source MemRef type with the specified reassociation in...
static bool haveCompatibleOffsets(MemRefType t1, MemRefType t2)
Return true if t1 and t2 have equal offsets (both dynamic or of same static value).
static void printBoolAttr(OpAsmPrinter &printer, Operation *, BoolAttr attr)
static bool replaceConstantUsesOf(OpBuilder &rewriter, Location loc, Container values, ArrayRef< OpFoldResult > maybeConstants)
Helper function to perform the replacement of all constant uses of values by a materialized constant ...
static LogicalResult produceSubViewErrorMsg(SliceVerificationResult result, SubViewOp op, Type expectedType)
static MemRefType getCanonicalSubViewResultType(MemRefType currentResultType, MemRefType currentSourceType, MemRefType sourceType, ArrayRef< OpFoldResult > mixedOffsets, ArrayRef< OpFoldResult > mixedSizes, ArrayRef< OpFoldResult > mixedStrides)
Compute the canonical result type of a SubViewOp.
static ParseResult parseGlobalMemrefOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr, Attribute &initialValue)
static std::tuple< MemorySpaceCastOpInterface, PtrLikeTypeInterface, Type > getMemorySpaceCastInfo(BaseMemRefType resultTy, Value src)
Helper function to retrieve a lossless memory-space cast, and the corresponding new result memref typ...
static FailureOr< llvm::SmallBitVector > computeMemRefRankReductionMask(MemRefType originalType, MemRefType reducedType, ArrayRef< OpFoldResult > sizes)
Given the originalType and a candidateReducedType whose shape is assumed to be a subset of originalTy...
static bool isTrivialSubViewOp(SubViewOp subViewOp)
Helper method to check if a subview operation is trivially a no-op.
static bool lastNonTerminatorInRegion(Operation *op)
Return whether this op is the last non terminating op in a region.
static std::map< int64_t, unsigned > getNumOccurences(ArrayRef< int64_t > vals)
Return a map with key being elements in vals and data being number of occurences of it.
static bool haveCompatibleStrides(MemRefType t1, MemRefType t2, const llvm::SmallBitVector &droppedDims)
Return true if t1 and t2 have equal strides (both dynamic or of same static value).
static FailureOr< StridedLayoutAttr > computeCollapsedLayoutMap(MemRefType srcType, ArrayRef< ReassociationIndices > reassociation, bool strict=false)
Compute the layout map after collapsing a given source MemRef type with the specified reassociation i...
static FailureOr< std::optional< SmallVector< Value > > > bubbleDownCastsPassthroughOpImpl(ConcreteOpTy op, OpBuilder &builder, OpOperand &src)
Implementation of bubbleDownCasts method for memref operations that return a single memref result.
static FailureOr< llvm::SmallBitVector > computeMemRefRankReductionMaskByPosition(MemRefType originalType, MemRefType reducedType, ArrayRef< OpFoldResult > sizes)
Returns the set of source dimensions that are dropped in a rank reduction.
static LogicalResult verifyAllocLikeOp(AllocLikeOp op)
static Type getElementType(Type type, ArrayRef< int32_t > indices, function_ref< InFlightDiagnostic(StringRef)> emitErrorFn)
Walks the given type hierarchy with the given indices, potentially down to component granularity,...
Definition SPIRVOps.cpp:229
static RankedTensorType foldDynamicToStaticDimSizes(RankedTensorType type, ValueRange dynamicSizes, SmallVector< Value > &foldedDynamicSizes)
Given a ranked tensor type and a range of values that defines its dynamic dimension sizes,...
static llvm::SmallBitVector getDroppedDims(ArrayRef< int64_t > reducedShape, ArrayRef< OpFoldResult > mixedSizes)
Compute the dropped dimensions of a rank-reducing tensor.extract_slice op or rank-extending tensor....
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
@ Square
Square brackets surrounding zero or more operands.
virtual ParseResult parseColonTypeList(SmallVectorImpl< Type > &result)=0
Parse a colon followed by a type list, which must have at least one type.
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 parseOptionalEqual()=0
Parse a = token if present.
virtual ParseResult parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
MLIRContext * getContext() const
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
virtual ParseResult parseAffineMap(AffineMap &map)=0
Parse an affine map instance into 'map'.
ParseResult addTypeToList(Type type, SmallVectorImpl< Type > &result)
Add the specified type to the end of the specified type list and return success.
virtual ParseResult parseLess()=0
Parse a '<' token.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseGreater()=0
Parse a '>' token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseComma()=0
Parse a , token.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
ParseResult parseKeywordType(const char *keyword, Type &result)
Parse a keyword followed by a type.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
virtual void printAttributeWithoutType(Attribute attr)
Print the given attribute without its type.
virtual void printAttribute(Attribute attr)
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class provides a shared interface for ranked and unranked memref types.
ArrayRef< int64_t > getShape() const
Returns the shape of this memref type.
FailureOr< PtrLikeTypeInterface > clonePtrWith(Attribute memorySpace, std::optional< Type > elementType) const
Clone this type with the given memory space and element type.
bool hasRank() const
Returns if this type is ranked, i.e. it has a known number of dimensions.
Block represents an ordered list of Operations.
Definition Block.h:33
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
bool mightHaveTerminator()
Return "true" if this block might have a terminator.
Definition Block.cpp:255
Special case of IntegerAttr to represent boolean integers, i.e., signless i1 integers.
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
IndexType getIndexType()
Definition Builders.cpp:59
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
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This is a builder type that keeps local references to arguments.
Builder & setShape(ArrayRef< int64_t > newShape)
Builder & setLayout(MemRefLayoutAttrInterface newLayout)
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
ParseResult parseTrailingOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None)
Parse zero or more trailing SSA comma-separated trailing operand references with a specified surround...
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...
void printOperands(const ContainerType &container)
Print a comma separated list of operands.
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
virtual void printRegion(Region &blocks, bool printEntryBlockArgs=true, bool printBlockTerminators=true, bool printEmptyBlock=false)=0
Prints a region.
This class helps build Operations.
Definition Builders.h:210
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:571
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
A trait of region holding operations that define a new scope for automatic allocations,...
This trait indicates that the memory effects of an operation includes the effects of operations neste...
type_range getType() const
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
void replaceUsesOfWith(Value from, Value to)
Replace any uses of 'from' with 'to' within this operation.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
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 represents a point being branched from in the methods of the RegionBranchOpInterface.
bool isParent() const
Returns true if branching from the parent op.
This class provides an abstraction over the different types of ranges over Regions.
Definition Region.h:378
This class represents a successor of a region.
bool isOperation() const
Return true if the successor is an operation.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Region.h:111
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
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.
virtual void inlineBlockBefore(Block *source, Block *dest, Block::iterator before, ValueRange argValues={})
Inline the operations of block 'source' into block 'dest' before the given position.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
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...
static StridedMetadataRange getRanked(SmallVectorImpl< ConstantIntRanges > &&offsets, SmallVectorImpl< ConstantIntRanges > &&sizes, SmallVectorImpl< ConstantIntRanges > &&strides)
Returns a ranked strided metadata range.
ArrayRef< ConstantIntRanges > getStrides() const
Get the strides ranges.
bool isUninitialized() const
Returns whether the metadata is uninitialized.
ArrayRef< ConstantIntRanges > getOffsets() const
Get the offsets range.
virtual Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
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
bool isIndex() const
Definition Types.cpp:56
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getTypes() const
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
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto Speculatable
constexpr auto NotSpeculatable
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
FailureOr< std::optional< SmallVector< Value > > > bubbleDownInPlaceMemorySpaceCastImpl(OpOperand &operand, ValueRange results)
Tries to bubble-down inplace a MemorySpaceCastOpInterface operation referenced by operand.
ConstantIntRanges inferAdd(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
ConstantIntRanges inferMul(ArrayRef< ConstantIntRanges > argRanges, OverflowFlags ovfFlags=OverflowFlags::None)
ConstantIntRanges inferShapedDimOpInterface(ShapedDimOpInterface op, const IntegerValueRange &maybeDim)
Returns the integer range for the result of a ShapedDimOpInterface given the optional inferred ranges...
Type getTensorTypeFromMemRefType(Type type)
Return an unranked/ranked tensor type for the given unranked/ranked memref type.
Definition MemRefOps.cpp:62
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given memref value.
Definition MemRefOps.cpp:70
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
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given memref value.
Definition MemRefOps.cpp:79
Value createCanonicalRankReducingSubViewOp(OpBuilder &b, Location loc, Value memref, ArrayRef< int64_t > targetShape)
Create a rank-reducing SubViewOp @[0 .
Operation::operand_range getIndices(Operation *op)
Get the indices that the given load/store operation is operating on.
Definition Utils.cpp:18
DynamicAPInt getIndex(const ConeV &cone)
Get the index of a cone, i.e., the volume of the parallelepiped spanned by its generators,...
Definition Barvinok.cpp:63
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Value constantIndex(OpBuilder &builder, Location loc, int64_t i)
Generates a constant of index type.
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
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
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
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
SliceVerificationResult
Enum that captures information related to verifier error conditions on slice insert/extract type of o...
constexpr StringRef getReassociationAttrName()
Attribute name for the ArrayAttr which encodes reassociation indices.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
raw_ostream & operator<<(raw_ostream &os, const AliasResult &result)
llvm::function_ref< void(Value, const IntegerValueRange &)> SetIntLatticeFn
Similar to SetIntRangeFn, but operating on IntegerValueRange lattice values.
SliceBoundsVerificationResult verifyInBoundsSlice(ArrayRef< int64_t > shape, ArrayRef< int64_t > staticOffsets, ArrayRef< int64_t > staticSizes, ArrayRef< int64_t > staticStrides, bool generateErrorMessage=false)
Verify that the offsets/sizes/strides-style access into the given shape is in-bounds.
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
SmallVector< Range, 8 > getOrCreateRanges(OffsetSizeAndStrideOpInterface op, OpBuilder &b, Location loc)
Return the list of Range (i.e.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
SmallVector< AffineMap, 4 > getSymbolLessAffineMaps(ArrayRef< ReassociationExprs > reassociation)
Constructs affine maps out of Array<Array<AffineExpr>>.
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
OpFoldResult foldReshapeOp(ReshapeOpTy reshapeOp, ArrayRef< Attribute > operands)
bool hasValidSizesOffsets(SmallVector< int64_t > sizesOrOffsets)
Helper function to check whether the passed in sizes or offsets are valid.
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
SmallVector< IntegerValueRange > getIntValueRanges(ArrayRef< OpFoldResult > values, GetIntRangeFn getIntRange, int32_t indexBitwidth)
Helper function to collect the integer range values of an array of op fold results.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:494
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
bool hasValidStrides(SmallVector< int64_t > strides)
Helper function to check whether the passed in strides are valid.
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
SmallVector< SmallVector< AffineExpr, 2 >, 2 > convertReassociationIndicesToExprs(MLIRContext *context, ArrayRef< ReassociationIndices > reassociationIndices)
Convert reassociation indices to affine expressions.
std::optional< SmallVector< OpFoldResult > > inferExpandShapeOutputShape(OpBuilder &b, Location loc, ShapedType expandedType, ArrayRef< ReassociationIndices > reassociation, ArrayRef< OpFoldResult > inputShape)
Infer the output shape for a {memref|tensor}.expand_shape when it is possible to do so.
Definition Utils.cpp:26
LogicalResult verifyElementTypesMatch(Operation *op, ShapedType lhs, ShapedType rhs, StringRef lhsName, StringRef rhsName)
Verify that two shaped types have matching element types.
SmallVector< T > applyPermutationMap(AffineMap map, llvm::ArrayRef< T > source)
Apply a permutation from map to source and return the result.
Definition AffineMap.h:675
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
function_ref< void(Value, const StridedMetadataRange &)> SetStridedMetadataRangeFn
Callback function type for setting the strided metadata of a value.
std::optional< llvm::SmallDenseSet< unsigned > > computeRankReductionMask(ArrayRef< int64_t > originalShape, ArrayRef< int64_t > reducedShape, bool matchDynamic=false)
Given an originalShape and a reducedShape assumed to be a subset of originalShape with some 1 entries...
SmallVector< int64_t, 2 > ReassociationIndices
Definition Utils.h:27
SliceVerificationResult isRankReducedType(ShapedType originalType, ShapedType candidateReducedType)
Check if originalType can be rank reduced to candidateReducedType type by dropping some dimensions wi...
ArrayAttr getReassociationIndicesAttribute(Builder &b, ArrayRef< ReassociationIndices > reassociation)
Wraps a list of reassociations in an ArrayAttr.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
bool isOneInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 1.
std::pair< SmallVector< int64_t >, SmallVector< Value > > decomposeMixedValues(ArrayRef< OpFoldResult > mixedValues)
Decompose a vector of mixed static or dynamic values into the corresponding pair of arrays.
function_ref< IntegerValueRange(Value)> GetIntRangeFn
Helper callback type to get the integer range of a value.
Move allocations into an allocation scope, if it is legal to move them (e.g.
LogicalResult matchAndRewrite(AllocaScopeOp op, PatternRewriter &rewriter) const override
Inline an AllocaScopeOp if either the direct parent is an allocation scope or it contains no allocati...
LogicalResult matchAndRewrite(AllocaScopeOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(CollapseShapeOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ExpandShapeOp op, PatternRewriter &rewriter) const override
A canonicalizer wrapper to replace SubViewOps.
void operator()(PatternRewriter &rewriter, SubViewOp op, SubViewOp newOp)
Return the canonical type of the result of a subview.
MemRefType operator()(SubViewOp op, ArrayRef< OpFoldResult > mixedOffsets, ArrayRef< OpFoldResult > mixedSizes, ArrayRef< OpFoldResult > mixedStrides)
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
OpFoldResult stride
OpFoldResult size
OpFoldResult offset
static SaturatedInteger wrap(int64_t v)
bool isValid
If set to "true", the slice bounds verification was successful.
std::string errorMessage
An error message that can be printed during op verification.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.