MLIR 24.0.0git
TensorOps.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
20#include "mlir/IR/Builders.h"
24#include "mlir/IR/IRMapping.h"
25#include "mlir/IR/Matchers.h"
35#include "mlir/Support/LLVM.h"
36#include "llvm/ADT/DenseSet.h"
37#include "llvm/ADT/Repeated.h"
38#include "llvm/ADT/STLExtras.h"
39#include "llvm/ADT/SmallBitVector.h"
40#include "llvm/ADT/SmallVectorExtras.h"
41#include "llvm/ADT/StringRef.h"
42#include "llvm/Support/Casting.h"
43#include "llvm/Support/MathExtras.h"
44#include <optional>
45
46using namespace mlir;
47using namespace mlir::tensor;
48
49/// Implements the `VerifiableTensorEncoding` contract documented in
50/// TensorEncoding.td for patterns that refine a tensor's shape to be more
51/// static: verifiable encodings are re-checked against the refined shape and
52/// dropped if they no longer hold; opaque encodings (not implementing the
53/// interface) are propagated unconditionally.
55 Type elementType) {
56 auto verifiable = dyn_cast_or_null<VerifiableTensorEncoding>(encoding);
57 if (!verifiable)
58 return encoding;
59
60 MLIRContext *ctx = encoding.getContext();
61 // to avoid user's error stream
62 ScopedDiagnosticHandler swallow(ctx, [](Diagnostic &) { return success(); });
63 auto emit = [ctx]() { return mlir::emitError(UnknownLoc::get(ctx)); };
64 return succeeded(verifiable.verifyEncoding(shape, elementType, emit))
65 ? encoding
66 : Attribute{};
67}
68
69/// Materialize a single constant operation from a given attribute value with
70/// the desired resultant type.
71Operation *TensorDialect::materializeConstant(OpBuilder &builder,
72 Attribute value, Type type,
73 Location loc) {
74 if (auto op = arith::ConstantOp::materialize(builder, value, type, loc))
75 return op;
76 if (complex::ConstantOp::isBuildableWith(value, type))
77 return complex::ConstantOp::create(builder, loc, type,
78 llvm::cast<ArrayAttr>(value));
79 return nullptr;
80}
81
83 int64_t dim) {
84 auto tensorType = llvm::cast<RankedTensorType>(value.getType());
85 if (tensorType.isDynamicDim(dim))
86 return builder.createOrFold<tensor::DimOp>(loc, value, dim);
87
88 return builder.getIndexAttr(tensorType.getDimSize(dim));
89}
90
92 Location loc, Value value) {
93 auto tensorType = llvm::cast<RankedTensorType>(value.getType());
95 for (int64_t i = 0; i < tensorType.getRank(); ++i)
96 result.push_back(getMixedSize(builder, loc, value, i));
97 return result;
98}
99
101 OpResult opResult) {
102 auto tensorType = llvm::dyn_cast<TensorType>(opResult.getType());
103 assert(tensorType && "expected tensor type");
104
105 // If the op has a destination, it implements DestinationStyleOpInterface and
106 // we can query the destination operand from that interface.
107 auto destOp = opResult.getDefiningOp<DestinationStyleOpInterface>();
108 if (destOp)
109 return destOp.getTiedOpOperand(opResult)->get();
110
111 // Otherwise, create a new destination tensor with the same shape.
113 b.setInsertionPoint(opResult.getDefiningOp());
114
115 // Compute sizes.
116 SmallVector<OpFoldResult> mixedSizes;
117 if (!tensorType.hasStaticShape()) {
118 // Dynamic shape: Query ReifyRankedShapedTypeOpInterface.
119 ReifiedRankedShapedTypeDims reifiedShapes;
120 if (failed(reifyResultShapes(b, opResult.getDefiningOp(), reifiedShapes)))
121 return failure();
122 mixedSizes = reifiedShapes[opResult.getResultNumber()];
123 } else {
124 // Static shape: Take static sizes directly.
125 for (int64_t sz : tensorType.getShape())
126 mixedSizes.push_back(b.getIndexAttr(sz));
127 }
128
129 // Create empty tensor with the same encoding as the result type.
130 Attribute encoding;
131 if (auto rankedTensorType = dyn_cast<RankedTensorType>(tensorType))
132 encoding = rankedTensorType.getEncoding();
133 Value emptyTensor = tensor::EmptyOp::create(
134 b, loc, mixedSizes, tensorType.getElementType(), encoding);
135 return emptyTensor;
136}
137
139 Operation *op,
141 for (OpResult opResult : op->getResults()) {
142 if (llvm::isa<TensorType>(opResult.getType())) {
143 FailureOr<Value> destination = getOrCreateDestination(b, loc, opResult);
144 if (failed(destination))
145 return failure();
146 result.push_back(*destination);
147 }
148 }
149 return success();
150}
151
153 if (auto rtp1 = llvm::dyn_cast<RankedTensorType>(tp1)) {
154 if (auto rtp2 = llvm::dyn_cast<RankedTensorType>(tp2))
155 return rtp1.getShape() == rtp2.getShape() &&
156 rtp1.getElementType() == rtp2.getElementType();
157 return false;
158 }
159 return tp1 == tp2; // default implementation
160}
161
162/// Compute the dropped dimensions of a rank-reducing tensor.extract_slice op or
163/// rank-extending tensor.insert_slice op.
164static llvm::SmallBitVector getDroppedDims(ArrayRef<int64_t> reducedShape,
165 ArrayRef<OpFoldResult> mixedSizes) {
166 llvm::SmallBitVector droppedDims(mixedSizes.size());
167 int64_t shapePos = reducedShape.size() - 1;
168
169 for (const auto &size : enumerate(llvm::reverse(mixedSizes))) {
170 size_t idx = mixedSizes.size() - size.index() - 1;
171 // Rank-reduced dims must have a static unit dimension.
172 bool isStaticUnitSize =
173 isa<Attribute>(size.value()) &&
174 llvm::cast<IntegerAttr>(cast<Attribute>(size.value())).getInt() == 1;
175
176 if (shapePos < 0) {
177 // There are no more dims in the reduced shape. All remaining sizes must
178 // be rank-reduced dims.
179 assert(isStaticUnitSize && "expected unit dim");
180 droppedDims.set(idx);
181 continue;
182 }
183
184 // Dim is preserved if the size is not a static 1.
185 if (!isStaticUnitSize) {
186 --shapePos;
187 continue;
188 }
189
190 // Dim is preserved if the reduced shape dim is also 1.
191 if (reducedShape[shapePos] == 1) {
192 --shapePos;
193 continue;
194 }
195
196 // Otherwise: Dim is dropped.
197 droppedDims.set(idx);
198 }
199
200 assert(shapePos < 0 && "dimension mismatch");
201 return droppedDims;
202}
203
204/// Given a ranked tensor type and a range of values that defines its dynamic
205/// dimension sizes, turn all dynamic sizes that have a constant value into
206/// static dimension sizes.
207static RankedTensorType
208foldDynamicToStaticDimSizes(RankedTensorType type, ValueRange dynamicSizes,
209 SmallVector<Value> &foldedDynamicSizes) {
210 SmallVector<int64_t> staticShape(type.getShape());
211 assert(type.getNumDynamicDims() == dynamicSizes.size() &&
212 "incorrect number of dynamic sizes");
213
214 // Compute new static and dynamic sizes.
215 unsigned ctr = 0;
216 for (int64_t i = 0, e = type.getRank(); i < e; ++i) {
217 if (type.isDynamicDim(i)) {
218 Value dynamicSize = dynamicSizes[ctr++];
219 std::optional<int64_t> cst = getConstantIntValue(dynamicSize);
220 if (cst.has_value()) {
221 // Dynamic size must be non-negative.
222 if (cst.value() < 0) {
223 foldedDynamicSizes.push_back(dynamicSize);
224 continue;
225 }
226 staticShape[i] = *cst;
227 } else {
228 foldedDynamicSizes.push_back(dynamicSize);
229 }
230 }
231 }
232
233 return RankedTensorType::get(staticShape, type.getElementType(),
234 type.getEncoding());
235}
236
237//===----------------------------------------------------------------------===//
238// BitcastOp
239//===----------------------------------------------------------------------===//
240
241bool BitcastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
242 if (inputs.size() != 1 || outputs.size() != 1)
243 return false;
244 Type a = inputs.front(), b = outputs.front();
245 auto aT = dyn_cast<TensorType>(a);
246 auto bT = dyn_cast<TensorType>(b);
247 if (!aT || !bT)
248 return false;
249
250 if (aT.getElementTypeBitWidth() != bT.getElementTypeBitWidth())
251 return false;
252
253 return succeeded(verifyCompatibleShape(aT, bT));
254}
255
256namespace {
257
258/// Replaces chains of two tensor.bitcast operations by a single tensor.bitcast
259/// operation.
260struct ChainedTensorBitcast : public OpRewritePattern<BitcastOp> {
261 using OpRewritePattern<BitcastOp>::OpRewritePattern;
262
263 LogicalResult matchAndRewrite(BitcastOp tensorBitcast,
264 PatternRewriter &rewriter) const final {
265 auto tensorBitcastOperand =
266 tensorBitcast.getOperand().getDefiningOp<BitcastOp>();
267 if (!tensorBitcastOperand)
268 return failure();
269
270 auto resultType = cast<TensorType>(tensorBitcast.getType());
271 rewriter.replaceOpWithNewOp<BitcastOp>(tensorBitcast, resultType,
272 tensorBitcastOperand.getOperand());
273 return success();
274 }
275};
276
277} // namespace
278
279void BitcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
280 MLIRContext *context) {
281 results.add<ChainedTensorBitcast>(context);
282}
283
284//===----------------------------------------------------------------------===//
285// CastOp
286//===----------------------------------------------------------------------===//
287
288void CastOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
289 setNameFn(getResult(), "cast");
290}
291
292/// Returns true if `target` is a ranked tensor type that preserves static
293/// information available in the `source` ranked tensor type.
295 auto sourceType = llvm::dyn_cast<RankedTensorType>(source);
296 auto targetType = llvm::dyn_cast<RankedTensorType>(target);
297
298 // Requires RankedTensorType.
299 if (!sourceType || !targetType)
300 return false;
301
302 // Requires same elemental type.
303 if (sourceType.getElementType() != targetType.getElementType())
304 return false;
305
306 // Requires same rank.
307 if (sourceType.getRank() != targetType.getRank())
308 return false;
309
310 // Requires same encoding.
311 if (sourceType.getEncoding() != targetType.getEncoding())
312 return false;
313
314 // If cast is towards more static sizes along any dimension, don't fold.
315 for (auto t : llvm::zip(sourceType.getShape(), targetType.getShape())) {
316 if (ShapedType::isStatic(std::get<0>(t)) &&
317 ShapedType::isDynamic(std::get<1>(t)))
318 return false;
319 }
320
321 return true;
322}
323
324/// Determines whether tensor::CastOp casts to a more dynamic version of the
325/// source tensor. This is useful to fold a tensor.cast into a consuming op and
326/// implement canonicalization patterns for ops in different dialects that may
327/// consume the results of tensor.cast operations. Such foldable tensor.cast
328/// operations are typically inserted as `slice` ops and are canonicalized,
329/// to preserve the type compatibility of their uses.
330///
331/// Returns true when all conditions are met:
332/// 1. source and result are ranked tensors with same element type and rank.
333/// 2. the tensor type has more static information than the result
334///
335/// Example:
336/// ```mlir
337/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
338/// %2 = consumer %1 ... : tensor<?x?xf32> ...
339/// ```
340///
341/// folds into:
342///
343/// ```mlir
344/// %2 = consumer %0 ... : tensor<8x16xf32> ...
345/// ```
347 if (!castOp)
348 return false;
349
350 // Can fold if the source of cast has at least as much static information as
351 // its results.
352 return preservesStaticInformation(castOp.getType(),
353 castOp.getSource().getType());
354}
355
356/// Determines whether the tensor::CastOp casts to a more static version of the
357/// source tensor. This is useful to fold into a producing op and implement
358/// canonicalization patterns with the `tensor.cast` op as the root, but
359/// producer being from different dialects. Returns true when all conditions are
360/// met:
361/// 1. source and result and ranked tensors with same element type and rank.
362/// 2. the result type has more static information than the source.
363///
364/// Example:
365/// ```mlir
366/// %1 = producer ... : tensor<?x?xf32>
367/// %2 = tensor.cast %1 : tensor<?x?xf32> to tensor<8x16xf32>
368/// ```
369///
370/// can be canonicalized to :
371///
372/// ```mlir
373/// %2 = producer ... : tensor<8x16xf32>
374/// ```
375/// Not all ops might be canonicalizable this way, but for those that can be,
376/// this method provides a check that it is worth doing the canonicalization.
378 if (!castOp)
379 return false;
380 return preservesStaticInformation(castOp.getSource().getType(),
381 castOp.getType());
382}
383
385 return llvm::any_of(op->getOpOperands(), [&](OpOperand &opOperand) {
386 if (llvm::isa<BlockArgument>(opOperand.get()))
387 return false;
388 auto castOp = opOperand.get().getDefiningOp<tensor::CastOp>();
389 return castOp && canFoldIntoConsumerOp(castOp);
390 });
391}
392
394 DestinationStyleOpInterface op, SmallVector<Type> &newResTy) {
395 SmallVector<Value> newOperands;
396 newOperands.reserve(op->getNumOperands());
397
398 assert(hasFoldableTensorCastOperand(op) && "No foldable CastOp operands!");
399
400 // Assumes that the result has dpsInits followed by nonDpsInits.
401 int64_t dpsInitIdx = 0;
402 for (OpOperand &opOperand : op->getOpOperands()) {
403 auto tensorCastOp = opOperand.get().getDefiningOp<tensor::CastOp>();
404 bool fold = canFoldIntoConsumerOp(tensorCastOp);
405 newOperands.push_back(fold ? tensorCastOp.getOperand() : opOperand.get());
406 if (op.isDpsInit(&opOperand) &&
407 !llvm::isa<MemRefType>(newOperands.back().getType()))
408 newResTy[dpsInitIdx++] = newOperands.back().getType();
409 }
410 return newOperands;
411}
412
413/// Performs folding of any operand of `op` if it comes from a tensor::CastOp
414/// that can be folded.
416 bool folded = false;
417 for (OpOperand &operand : op->getOpOperands()) {
418 auto castOp = operand.get().getDefiningOp<tensor::CastOp>();
419 if (castOp && tensor::canFoldIntoConsumerOp(castOp)) {
420 operand.set(castOp.getOperand());
421 folded = true;
422 }
423 }
424 return success(folded);
425}
426
427bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
428 if (inputs.size() != 1 || outputs.size() != 1)
429 return false;
430 Type a = inputs.front(), b = outputs.front();
431 auto aT = llvm::dyn_cast<TensorType>(a);
432 auto bT = llvm::dyn_cast<TensorType>(b);
433 if (!aT || !bT)
434 return false;
435
436 if (aT.getElementType() != bT.getElementType())
437 return false;
438
439 return succeeded(verifyCompatibleShape(aT, bT));
440}
441
442/// Compute a TensorType that has the joined shape knowledge of the two
443/// given TensorTypes. The element types need to match.
445 assert(one.getElementType() == two.getElementType());
446
447 if (!one.hasRank())
448 return two;
449 if (!two.hasRank())
450 return one;
451
452 int64_t rank = one.getRank();
453 if (rank != two.getRank())
454 return {};
455
457 join.reserve(rank);
458 for (int64_t i = 0; i < rank; ++i) {
459 if (one.isDynamicDim(i)) {
460 join.push_back(two.getDimSize(i));
461 continue;
462 }
463 if (two.isDynamicDim(i)) {
464 join.push_back(one.getDimSize(i));
465 continue;
466 }
467 if (one.getDimSize(i) != two.getDimSize(i))
468 return {};
469 join.push_back(one.getDimSize(i));
470 }
471 return RankedTensorType::get(join, one.getElementType());
472}
473
474namespace {
475
476/// Replaces chains of two tensor.cast operations by a single tensor.cast
477/// operation if doing so does not remove runtime constraints.
478struct ChainedTensorCast : public OpRewritePattern<CastOp> {
479 using OpRewritePattern<CastOp>::OpRewritePattern;
480
481 LogicalResult matchAndRewrite(CastOp tensorCast,
482 PatternRewriter &rewriter) const final {
483 auto tensorCastOperand = tensorCast.getOperand().getDefiningOp<CastOp>();
484
485 if (!tensorCastOperand)
486 return failure();
487
488 auto sourceType =
489 llvm::cast<TensorType>(tensorCastOperand.getOperand().getType());
490 auto intermediateType = llvm::cast<TensorType>(tensorCastOperand.getType());
491 auto resultType = llvm::cast<TensorType>(tensorCast.getType());
492
493 // We can remove the intermediate cast if joining all three produces the
494 // same result as just joining the source and result shapes.
495 auto firstJoin =
496 joinShapes(joinShapes(sourceType, intermediateType), resultType);
497
498 // The join might not exist if the cast sequence would fail at runtime.
499 if (!firstJoin)
500 return failure();
501
502 // The newJoin always exists if the above join exists, it might just contain
503 // less information. If so, we cannot drop the intermediate cast, as doing
504 // so would remove runtime checks.
505 auto newJoin = joinShapes(sourceType, resultType);
506 if (firstJoin != newJoin)
507 return failure();
508
509 rewriter.replaceOpWithNewOp<CastOp>(tensorCast, resultType,
510 tensorCastOperand.getOperand());
511 return success();
512 }
513};
514
515/// Fold tensor.cast into tesor.extract_slice producer.
516/// Example:
517/// ```
518/// %0 = tensor.extract_slice %arg0[%o, 0] [%s, 512] [1, 1] :
519/// tensor<128x512xf32> to tensor<?x512xf32>
520/// %1 = tensor.cast %0 : tensor<?x512xf32> to tensor<16x512xf32>
521/// ```
522/// ->
523/// ```
524/// %1 = tensor.extract_slice %arg0[%o, 0] [16, 512] [1, 1] :
525/// tensor<128x512xf32> to tensor<16x512xf32>
526/// ```
527struct TensorCastExtractSlice : public OpRewritePattern<CastOp> {
528 using OpRewritePattern<CastOp>::OpRewritePattern;
529
530 LogicalResult matchAndRewrite(CastOp tensorCast,
531 PatternRewriter &rewriter) const final {
532 auto extractOperand =
533 tensorCast.getOperand().getDefiningOp<ExtractSliceOp>();
534
535 // Cannot fold cast to unranked tensor.
536 auto rankedResultType =
537 llvm::dyn_cast<RankedTensorType>(tensorCast.getType());
538 if (!rankedResultType)
539 return failure();
540
541 if (!extractOperand || !canFoldIntoProducerOp(tensorCast) ||
542 rankedResultType.getShape() ==
543 llvm::cast<RankedTensorType>(tensorCast.getSource().getType())
544 .getShape())
545 return failure();
546
547 SmallVector<OpFoldResult, 4> sizes = extractOperand.getMixedSizes();
548 auto dimMask = computeRankReductionMask(
549 extractOperand.getStaticSizes(), extractOperand.getType().getShape());
550 size_t dimIndex = 0;
551 for (size_t i = 0, e = sizes.size(); i < e; i++) {
552 if (dimMask && dimMask->count(i))
553 continue;
554 int64_t dim = rankedResultType.getShape()[dimIndex++];
555 if (ShapedType::isDynamic(dim))
556 continue;
557 sizes[i] = rewriter.getIndexAttr(dim);
558 }
559
560 rewriter.replaceOpWithNewOp<ExtractSliceOp>(
561 tensorCast, rankedResultType, extractOperand.getSource(),
562 extractOperand.getMixedOffsets(), sizes,
563 extractOperand.getMixedStrides());
564 return success();
565 }
566};
567
568} // namespace
569
570void CastOp::getCanonicalizationPatterns(RewritePatternSet &results,
571 MLIRContext *context) {
572 results.add<ChainedTensorCast, TensorCastExtractSlice>(context);
573}
574
575//===----------------------------------------------------------------------===//
576// ConcatOp
577//===----------------------------------------------------------------------===//
578
579RankedTensorType ConcatOp::inferResultType(int64_t dim, TypeRange inputTypes) {
580 assert(!inputTypes.empty() && "cannot concatenate 0 tensors");
581 auto tensorTypes =
582 llvm::map_to_vector<4>(inputTypes, llvm::CastTo<RankedTensorType>);
583 int64_t concatRank = tensorTypes[0].getRank();
584
585 // The concatenation dim must be in the range [0, rank).
586 assert(dim >= 0 && dim < concatRank && "Invalid concatenation dim");
587
588 SmallVector<int64_t> sizes(concatRank);
589 for (int64_t i = 0, e = concatRank; i < e; ++i) {
590 if (i == dim)
591 continue;
592 SaturatedInteger size;
593 for (auto tensorType : tensorTypes)
594 size = *size.desaturate(SaturatedInteger::wrap(tensorType.getDimSize(i)));
595 sizes[i] = size.asInteger();
596 }
597 auto concatSize = SaturatedInteger::wrap(0);
598 for (auto tensorType : tensorTypes)
599 concatSize =
600 concatSize + SaturatedInteger::wrap(tensorType.getDimSize(dim));
601 sizes[dim] = concatSize.asInteger();
602 return RankedTensorType::get(sizes, tensorTypes[0].getElementType());
603}
604
605void ConcatOp::build(OpBuilder &builder, OperationState &result, int64_t dim,
606 ValueRange inputs) {
607 FailureOr<RankedTensorType> resultType =
608 inferResultType(dim, inputs.getTypes());
609 assert(succeeded(resultType) && "failed to infer concatenation result type");
610 build(builder, result, *resultType, dim, inputs);
611}
612
613LogicalResult ConcatOp::verify() {
614 if (getInputs().size() < 1)
615 return emitOpError("requires at least one input");
616
618 for (auto input : getInputs())
619 inputTypes.push_back(cast<RankedTensorType>(input.getType()));
620
621 RankedTensorType resultType = getResultType();
622 int64_t resultRank = getRank();
623 if (llvm::any_of(inputTypes, [resultRank](RankedTensorType type) {
624 return type.getRank() != resultRank;
625 }))
626 return emitOpError("rank of concatenated inputs must match result rank");
627
628 Type resultElementType = resultType.getElementType();
629 if (llvm::any_of(inputTypes, [&](RankedTensorType type) {
630 return type.getElementType() != resultElementType;
631 }))
632 return emitOpError("inputs and result element type must match");
633
634 int64_t dim = getDim();
635 if (dim >= resultRank)
636 return emitOpError("concatenation dim must be less than the tensor rank");
637
638 SmallVector<int64_t> sizes(resultRank);
639 for (int64_t i = 0, e = resultRank; i < e; ++i) {
640 if (i == dim)
641 continue;
642 SaturatedInteger size;
643 for (auto tensorType : inputTypes) {
644 FailureOr<SaturatedInteger> maybeSize =
645 size.desaturate(SaturatedInteger::wrap(tensorType.getDimSize(i)));
646 if (failed(maybeSize))
647 return emitOpError("static concatenation size mismatch along ")
648 << "non-concatenated dimension " << i;
649 size = *maybeSize;
650 }
651 sizes[i] = size.asInteger();
652 }
653 auto concatSize = SaturatedInteger::wrap(0);
654 for (auto tensorType : inputTypes)
655 concatSize =
656 concatSize + SaturatedInteger::wrap(tensorType.getDimSize(dim));
657 sizes[dim] = concatSize.asInteger();
658 auto inferredResultType =
659 RankedTensorType::get(sizes, inputTypes[0].getElementType());
660
661 for (auto [inferredSize, actualSize] :
662 llvm::zip_equal(inferredResultType.getShape(), resultType.getShape())) {
663 bool hasDynamic = ShapedType::isDynamic(inferredSize) ||
664 ShapedType::isDynamic(actualSize);
665 if (!hasDynamic && inferredSize != actualSize)
666 return emitOpError("result type ")
667 << resultType << "does not match inferred shape "
668 << inferredResultType << " static sizes";
669 }
670
671 return success();
672}
673
674FailureOr<SmallVector<Value>> ConcatOp::decomposeOperation(OpBuilder &builder) {
675 size_t numInputs = getInputs().size();
676 uint64_t concatDim = getDim();
677
679 inputShapes.reserve(numInputs);
680 SmallVector<OpFoldResult> concatOffsets;
681 concatOffsets.reserve(numInputs);
682 SmallVector<OpFoldResult> outputShape;
683
684 AffineExpr addExpr =
685 builder.getAffineSymbolExpr(0) + builder.getAffineSymbolExpr(1);
686 OpFoldResult zero = builder.getIndexAttr(0);
687 Location loc = getLoc();
688 for (auto [index, input] : llvm::enumerate(getInputs())) {
689 SmallVector<OpFoldResult> inputShape =
690 tensor::getMixedSizes(builder, input.getLoc(), input);
691 if (index == 0) {
692 outputShape = inputShape;
693 concatOffsets.push_back(zero);
694 } else {
695 concatOffsets.push_back(outputShape[concatDim]);
696 outputShape[concatDim] = affine::makeComposedFoldedAffineApply(
697 builder, loc, addExpr,
698 {outputShape[concatDim], inputShape[concatDim]});
699 }
700 inputShapes.emplace_back(std::move(inputShape));
701 }
702
703 Value replacement = tensor::EmptyOp::create(builder, loc, outputShape,
705
706 int64_t rank = getType().getRank();
707 OpFoldResult one = builder.getIndexAttr(1);
708 SmallVector<OpFoldResult> strides(rank, one);
709 SmallVector<OpFoldResult> offsets(rank, zero);
710 for (auto [index, input] : llvm::enumerate(getInputs())) {
711 offsets[concatDim] = concatOffsets[index];
712 auto insertSlice = tensor::InsertSliceOp::create(
713 builder, loc, input, replacement, offsets, inputShapes[index], strides);
714 replacement = insertSlice.getResult();
715 }
716 if (replacement.getType() != getType()) {
717 replacement = tensor::CastOp::create(builder, loc, getType(), replacement);
718 }
720}
721
722LogicalResult
723ConcatOp::reifyResultShapes(OpBuilder &builder,
724 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
725 ValueRange inputs = getInputs();
726 int64_t dim = getDim();
727 RankedTensorType inferredResultType = inferResultType(dim, inputs.getTypes());
728
729 Value init = inputs[0];
730 int64_t rank = getType().getRank();
731
732 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(rank));
733
734 // Pre-populate the result sizes with as much static information as possible
735 // from the given result type, as well as the inferred result type, otherwise
736 // use the dim sizes from the first input.
737 for (int64_t i = 0; i < rank; ++i) {
738 if (i == dim)
739 continue;
740 if (!getType().isDynamicDim(i)) {
741 reifiedReturnShapes[0][i] = builder.getIndexAttr(getType().getDimSize(i));
742 } else if (!inferredResultType.isDynamicDim(i)) {
743 reifiedReturnShapes[0][i] = getValueOrCreateConstantIndexOp(
744 builder, getLoc(),
745 builder.getIndexAttr(inferredResultType.getDimSize(i)));
746 } else {
747 reifiedReturnShapes[0][i] =
748 tensor::DimOp::create(builder, init.getLoc(), init, i).getResult();
749 }
750 }
751
752 if (getType().isDynamicDim(dim)) {
753 // Take the sum of the input sizes along the concatenated dim.
754 AffineExpr sum = builder.getAffineDimExpr(0);
756 builder.createOrFold<tensor::DimOp>(init.getLoc(), init, dim)};
757 for (auto [idx, input] : llvm::enumerate(inputs.drop_front())) {
758 sum = sum + builder.getAffineDimExpr(idx + 1);
759 sizes.push_back(
760 builder.createOrFold<tensor::DimOp>(input.getLoc(), input, dim));
761 }
762 reifiedReturnShapes[0][dim] = getValueOrCreateConstantIndexOp(
763 builder, getLoc(),
764 affine::makeComposedFoldedAffineApply(builder, getLoc(), sum, sizes));
765 } else {
766 // If the result shape is static along the concatenated dim, use the static
767 // shape.
768 reifiedReturnShapes[0][dim] =
769 builder.getIndexAttr(getType().getDimSize(dim));
770 }
771 return success();
772}
773
774void ConcatOp::getAsmResultNames(
775 function_ref<void(Value, StringRef)> setNameFn) {
776 setNameFn(getResult(), "concat");
777}
778
779OpFoldResult ConcatOp::fold(FoldAdaptor) {
780 ValueRange inputs = getInputs();
781 if (inputs.size() == 1 && inputs[0].getType() == getResultType())
782 return inputs[0];
783 return {};
784}
785
786namespace {
787/// Fold a concat op with a single input to a cast.
788struct SingleInputConcatOp : public OpRewritePattern<ConcatOp> {
789 using OpRewritePattern<ConcatOp>::OpRewritePattern;
790
791 LogicalResult matchAndRewrite(ConcatOp concatOp,
792 PatternRewriter &rewriter) const override {
793 if (concatOp.getInputs().size() != 1)
794 return failure();
795 rewriter.replaceOpWithNewOp<CastOp>(concatOp, concatOp.getResultType(),
796 concatOp.getInputs()[0]);
797 return success();
798 }
799};
800
801/// Propagate static shapes into the operands of a `tensor.concat`.
802///
803/// `tensor.concat` requires every operand to match on all dimensions except the
804/// concatenation dimension. If one operand is already static in those
805/// dimensions, the other operands may safely be refined to that same static
806/// shape.
807///
808/// Example:
809///
810/// ```mlir
811/// %2 = tensor.concat dim(0) %0, %1: (tensor<?x12xi32>, tensor<?x?xi32>) ->
812/// tensor<?x12xi32>
813/// ```
814/// ->
815/// ```mlir
816/// %cast = tensor.cast %1 : tensor<?x?xi32> to tensor<?x12xi32>
817/// %2 = tensor.concat dim(0) %0, %cast :
818/// (tensor<?x12xi32>, tensor<?x12xi32>) -> tensor<?x12xi32>
819/// ```
820struct InferConcatOperandTypes : public OpRewritePattern<ConcatOp> {
821 using OpRewritePattern<ConcatOp>::OpRewritePattern;
822
823 LogicalResult matchAndRewrite(ConcatOp concatOp,
824 PatternRewriter &rewriter) const override {
825 int64_t dim = concatOp.getDim();
826 RankedTensorType inferredResultType =
827 ConcatOp::inferResultType(dim, concatOp->getOperandTypes());
828
829 // Find operands for which a more static shape can be inferred.
830 LogicalResult matched = failure();
831 // Inferred operand shapes are identical in every dimension except the
832 // concatenation dimension.
833 SmallVector<int64_t> inferredOperandShape(inferredResultType.getShape());
834 for (auto [operandIdx, operandType] :
835 llvm::enumerate(concatOp->getOperandTypes())) {
836 // Compute inferred type for operand.
837 inferredOperandShape[dim] =
838 cast<RankedTensorType>(operandType).getDimSize(dim);
839 auto inferredOperandType = RankedTensorType::get(
840 inferredOperandShape, inferredResultType.getElementType());
841
842 // Check if inferred type is more static.
843 if (!preservesStaticInformation(inferredOperandType, operandType)) {
844 matched = success();
845
846 // Use refined operand type and create cast from original operand.
847 auto castOp =
848 CastOp::create(rewriter, concatOp->getLoc(), inferredOperandType,
849 concatOp.getOperand(operandIdx));
850 rewriter.modifyOpInPlace(concatOp, [=, operandIdx = operandIdx] {
851 concatOp->setOperand(operandIdx, castOp->getResult(0));
852 });
853 }
854 }
855
856 return matched;
857 }
858};
859
860// Ensure `tensor.concat`'s result type is at least as static as can be inferred
861// from its operand types.
862///
863/// Example:
864/// ```mlir
865/// %2 = tensor.concat dim(0) %0, %1: (tensor<?x12xi32>, tensor<?x12xi32>) ->
866/// tensor<?x?xi32>
867/// ```
868/// ->
869/// ```mlir
870/// %2 = tensor.concat dim(0) %0, %cast : (tensor<?x12xi32>, tensor<?x12xi32>)
871/// -> tensor<?x12xi32> %cast = tensor.cast %2 : tensor<?x12xi32> to
872/// tensor<?x?xi32>
873/// ```
874struct InferConcatResultType : public OpRewritePattern<ConcatOp> {
875 using OpRewritePattern<ConcatOp>::OpRewritePattern;
876
877 LogicalResult matchAndRewrite(ConcatOp concatOp,
878 PatternRewriter &rewriter) const override {
879 int64_t dim = concatOp.getDim();
880 RankedTensorType inferredResultType =
881 ConcatOp::inferResultType(dim, concatOp->getOperandTypes());
882
883 // The result type should be at least as static as inferred result type.
884 if (preservesStaticInformation(inferredResultType,
885 concatOp.getResultType())) {
886 return failure();
887 }
888
889 auto newConcatOp =
890 ConcatOp::create(rewriter, concatOp->getLoc(), inferredResultType, dim,
891 concatOp->getOperands());
892 rewriter.replaceOpWithNewOp<CastOp>(concatOp, concatOp.getResultType(),
893 newConcatOp);
894
895 return success();
896 }
897};
898} // namespace
899
900void ConcatOp::getCanonicalizationPatterns(RewritePatternSet &results,
901 MLIRContext *context) {
902 results
903 .add<SingleInputConcatOp, InferConcatOperandTypes, InferConcatResultType>(
904 context);
905}
906
907//===----------------------------------------------------------------------===//
908// DimOp
909//===----------------------------------------------------------------------===//
910
911void DimOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
912 setNameFn(getResult(), "dim");
913}
914
915void DimOp::build(OpBuilder &builder, OperationState &result, Value source,
916 int64_t index) {
917 auto loc = result.location;
918 Value indexValue = arith::ConstantIndexOp::create(builder, loc, index);
919 build(builder, result, source, indexValue);
920}
921
922std::optional<int64_t> DimOp::getConstantIndex() {
924}
925
926Speculation::Speculatability DimOp::getSpeculatability() {
927 auto constantIndex = getConstantIndex();
928 if (!constantIndex)
930
931 auto rankedSourceType = dyn_cast<RankedTensorType>(getSource().getType());
932 if (!rankedSourceType)
934
935 if (rankedSourceType.getRank() <= constantIndex)
937
939}
940
941void DimOp::inferResultRangesFromOptional(ArrayRef<IntegerValueRange> argRanges,
942 SetIntLatticeFn setResultRange) {
943 setResultRange(getResult(),
944 intrange::inferShapedDimOpInterface(*this, argRanges[1]));
945}
946
947OpFoldResult DimOp::fold(FoldAdaptor adaptor) {
948 // All forms of folding require a known index.
949 std::optional<int64_t> index = getConstantIndex();
950 if (!index)
951 return {};
952
953 // Folding for unranked types (UnrankedTensorType) is not supported.
954 auto tensorType = llvm::dyn_cast<RankedTensorType>(getSource().getType());
955 if (!tensorType)
956 return {};
957
958 // Out of bound indices produce undefined behavior but are still valid IR.
959 // Don't choke on them.
960 int64_t indexVal = index.value();
961 if (indexVal < 0 || indexVal >= tensorType.getRank())
962 return {};
963
964 // Fold if the shape extent along the given index is known.
965 if (!tensorType.isDynamicDim(indexVal)) {
966 Builder builder(getContext());
967 return builder.getIndexAttr(tensorType.getShape()[indexVal]);
968 }
969
970 Operation *definingOp = getSource().getDefiningOp();
971
972 // Fold dim to the operand of tensor.generate.
973 if (auto fromElements = dyn_cast_or_null<tensor::GenerateOp>(definingOp)) {
974 auto resultType =
975 llvm::cast<RankedTensorType>(fromElements.getResult().getType());
976 // The case where the type encodes the size of the dimension is handled
977 // above.
978 assert(ShapedType::isDynamic(resultType.getShape()[indexVal]));
979
980 // Find the operand of the fromElements that corresponds to this index.
981 auto dynExtents = fromElements.getDynamicExtents().begin();
982 for (auto dim : resultType.getShape().take_front(indexVal))
983 if (ShapedType::isDynamic(dim))
984 dynExtents++;
985
986 return Value{*dynExtents};
987 }
988
989 // The size at the given index is now known to be a dynamic size.
990 if (auto sliceOp = dyn_cast_or_null<tensor::ExtractSliceOp>(definingOp)) {
991 // Fold only for non-rank reduced ops. For the rank-reduced version, rely on
992 // `resolve-shaped-type-result-dims` pass.
993 if (sliceOp.getType().getRank() == sliceOp.getSourceType().getRank() &&
994 sliceOp.isDynamicSize(indexVal)) {
995 return {sliceOp.getDynamicSize(indexVal)};
996 }
997 }
998
999 // dim(cast) -> dim
1000 if (succeeded(foldTensorCast(*this)))
1001 return getResult();
1002
1003 return {};
1004}
1005
1006namespace {
1007/// Fold dim of a cast into the dim of the source of the tensor cast.
1008struct DimOfCastOp : public OpRewritePattern<DimOp> {
1009 using OpRewritePattern<DimOp>::OpRewritePattern;
1010
1011 LogicalResult matchAndRewrite(DimOp dimOp,
1012 PatternRewriter &rewriter) const override {
1013 auto castOp = dimOp.getSource().getDefiningOp<CastOp>();
1014 if (!castOp)
1015 return failure();
1016 Value newSource = castOp.getOperand();
1017 rewriter.replaceOpWithNewOp<DimOp>(dimOp, newSource, dimOp.getIndex());
1018 return success();
1019 }
1020};
1021
1022/// Fold dim of a destination passing style op into the dim of the corresponding
1023/// init.
1024struct DimOfDestStyleOp : public OpRewritePattern<DimOp> {
1025 using OpRewritePattern<DimOp>::OpRewritePattern;
1026
1027 LogicalResult matchAndRewrite(DimOp dimOp,
1028 PatternRewriter &rewriter) const override {
1029 auto source = dimOp.getSource();
1030 auto destOp = source.getDefiningOp<DestinationStyleOpInterface>();
1031 if (!destOp)
1032 return failure();
1033
1034 auto resultIndex = cast<OpResult>(source).getResultNumber();
1035 auto *initOperand = destOp.getDpsInitOperand(resultIndex);
1036
1037 rewriter.modifyOpInPlace(
1038 dimOp, [&]() { dimOp.getSourceMutable().assign(initOperand->get()); });
1039 return success();
1040 }
1041};
1042
1043/// Fold dim of a tensor reshape operation to a extract into the reshape's shape
1044/// operand.
1045struct DimOfReshapeOp : public OpRewritePattern<DimOp> {
1046 using OpRewritePattern<DimOp>::OpRewritePattern;
1047
1048 LogicalResult matchAndRewrite(DimOp dim,
1049 PatternRewriter &rewriter) const override {
1050 auto reshape = dim.getSource().getDefiningOp<ReshapeOp>();
1051
1052 if (!reshape)
1053 return failure();
1054
1055 // Since tensors are immutable we don't need to worry about where to place
1056 // the extract call
1057 rewriter.setInsertionPointAfter(dim);
1058 Location loc = dim.getLoc();
1059 Value extract =
1060 ExtractOp::create(rewriter, loc, reshape.getShape(), dim.getIndex());
1061 if (extract.getType() != dim.getType())
1062 extract =
1063 arith::IndexCastOp::create(rewriter, loc, dim.getType(), extract);
1064 rewriter.replaceOp(dim, extract);
1065 return success();
1066 }
1067};
1068} // namespace
1069
1070void DimOp::getCanonicalizationPatterns(RewritePatternSet &results,
1071 MLIRContext *context) {
1072 results.add<DimOfCastOp, DimOfDestStyleOp, DimOfReshapeOp>(context);
1073}
1074
1075//===----------------------------------------------------------------------===//
1076// EmptyOp
1077//===----------------------------------------------------------------------===//
1078
1079void EmptyOp::build(OpBuilder &builder, OperationState &result,
1080 ArrayRef<int64_t> staticShape, Type elementType,
1081 Attribute encoding) {
1082 assert(none_of(staticShape, ShapedType::isDynamic) &&
1083 "expected only static sizes");
1084 build(builder, result, staticShape, elementType, ValueRange{}, encoding);
1085}
1086
1087void EmptyOp::build(OpBuilder &builder, OperationState &result,
1088 ArrayRef<int64_t> staticShape, Type elementType,
1089 ValueRange dynamicSizes, Attribute encoding) {
1090 auto tensorType = RankedTensorType::get(staticShape, elementType, encoding);
1091 build(builder, result, tensorType, dynamicSizes);
1092}
1093
1094void EmptyOp::build(OpBuilder &builder, OperationState &result,
1095 ArrayRef<OpFoldResult> sizes, Type elementType,
1096 Attribute encoding) {
1097 SmallVector<int64_t> staticShape;
1098 SmallVector<Value> dynamicSizes;
1099 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticShape);
1100 build(builder, result, staticShape, elementType, dynamicSizes, encoding);
1101}
1102
1103LogicalResult EmptyOp::verify() {
1104 return verifyDynamicDimensionCount(getOperation(), getType(),
1105 getDynamicSizes());
1106}
1107
1108LogicalResult
1109EmptyOp::reifyResultShapes(OpBuilder &builder,
1110 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
1111 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
1112 unsigned ctr = 0;
1113 for (int64_t i = 0; i < getType().getRank(); ++i) {
1114 if (getType().isDynamicDim(i)) {
1115 reifiedReturnShapes[0][i] = getDynamicSizes()[ctr++];
1116 } else {
1117 reifiedReturnShapes[0][i] = builder.getIndexAttr(getType().getDimSize(i));
1118 }
1119 }
1120 return success();
1121}
1122
1123Value EmptyOp::getDynamicSize(unsigned idx) {
1124 assert(getType().isDynamicDim(idx) && "expected dynamic dim");
1125 unsigned ctr = 0;
1126 for (int64_t i = 0; i < static_cast<int64_t>(idx); ++i)
1127 if (getType().isDynamicDim(i))
1128 ++ctr;
1129 return getDynamicSizes()[ctr];
1130}
1131
1132SmallVector<OpFoldResult> EmptyOp::getMixedSizes() {
1133 SmallVector<OpFoldResult> result;
1134 unsigned ctr = 0;
1135 Builder b(getContext());
1136 for (int64_t dim : getType().getShape()) {
1137 if (ShapedType::isDynamic(dim)) {
1138 result.push_back(getDynamicSizes()[ctr++]);
1139 } else {
1140 result.push_back(b.getIndexAttr(dim));
1141 }
1142 }
1143 return result;
1144}
1145
1146namespace {
1147/// Change the type of the result of a `tensor.empty` by making the result
1148/// type statically sized along dimensions that in the original operation were
1149/// defined as dynamic, but the size was defined using a `constant` op. For
1150/// example
1151///
1152/// %c5 = arith.constant 5: index
1153/// %0 = tensor.empty(%arg0, %c5) : tensor<?x?xf32>
1154///
1155/// to
1156///
1157/// %0 = tensor.empty(%arg0) : tensor<?x5xf32>
1158struct ReplaceEmptyTensorStaticShapeDims : OpRewritePattern<EmptyOp> {
1159 using OpRewritePattern<EmptyOp>::OpRewritePattern;
1160
1161 LogicalResult matchAndRewrite(EmptyOp op,
1162 PatternRewriter &rewriter) const override {
1163 SmallVector<Value> foldedDynamicSizes;
1164 RankedTensorType foldedTensorType = foldDynamicToStaticDimSizes(
1165 op.getType(), op.getDynamicSizes(), foldedDynamicSizes);
1166
1167 // Stop here if no dynamic size was promoted to static.
1168 if (foldedTensorType == op.getType())
1169 return failure();
1170
1171 auto newOp = EmptyOp::create(rewriter, op.getLoc(), foldedTensorType,
1172 foldedDynamicSizes);
1173 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
1174 return success();
1175 }
1176};
1177
1178struct FoldEmptyTensorWithDimOp : public OpRewritePattern<DimOp> {
1179 using OpRewritePattern<DimOp>::OpRewritePattern;
1180
1181 LogicalResult matchAndRewrite(tensor::DimOp dimOp,
1182 PatternRewriter &rewriter) const override {
1183 std::optional<int64_t> maybeConstantIndex = dimOp.getConstantIndex();
1184 auto emptyTensorOp = dimOp.getSource().getDefiningOp<EmptyOp>();
1185 if (!emptyTensorOp || !maybeConstantIndex)
1186 return failure();
1187 auto emptyTensorType = emptyTensorOp.getType();
1188 if (*maybeConstantIndex < 0 ||
1189 *maybeConstantIndex >= emptyTensorType.getRank() ||
1190 !emptyTensorType.isDynamicDim(*maybeConstantIndex))
1191 return failure();
1192 rewriter.replaceOp(dimOp,
1193 emptyTensorOp.getDynamicSize(*maybeConstantIndex));
1194 return success();
1195 }
1196};
1197
1198/// Canonicalize
1199///
1200/// ```mlir
1201/// %0 = tensor.empty(%d0, %d1) : tensor<?x?xf32>
1202/// %1 = tensor.cast %0 : tensor<?x?xf32> to tensor<4x?xf32>
1203/// ```
1204///
1205/// into
1206///
1207/// ```mlir
1208/// %0 = tensor.empty(%d1) : tensor<4x?xf32>
1209/// ```
1210///
1211/// This assumes the input program is correct in terms of its shape. So it is
1212/// safe to assume that `%d0` is in fact 4.
1213struct FoldEmptyTensorWithCastOp : public OpRewritePattern<CastOp> {
1214 using OpRewritePattern<CastOp>::OpRewritePattern;
1215
1216 LogicalResult matchAndRewrite(CastOp castOp,
1217 PatternRewriter &rewriter) const override {
1218 if (!canFoldIntoProducerOp(castOp))
1219 return failure();
1220 auto producer = castOp.getSource().getDefiningOp<EmptyOp>();
1221 if (!producer)
1222 return failure();
1223
1224 auto resultType =
1225 llvm::cast<RankedTensorType>(castOp->getResult(0).getType());
1226 ArrayRef<int64_t> resultShape = resultType.getShape();
1227 SmallVector<OpFoldResult> currMixedSizes = producer.getMixedSizes();
1228 SmallVector<OpFoldResult> newMixedSizes;
1229 newMixedSizes.reserve(currMixedSizes.size());
1230 assert(resultShape.size() == currMixedSizes.size() &&
1231 "mismatch in result shape and sizes of empty op");
1232 for (auto [newDim, currDim] : llvm::zip(resultShape, currMixedSizes)) {
1233 // Case 1: The empty tensor dim is static. Check that the tensor cast
1234 // result dim matches.
1235 if (auto attr = llvm::dyn_cast_if_present<Attribute>(currDim)) {
1236 if (ShapedType::isDynamic(newDim) ||
1237 newDim != llvm::cast<IntegerAttr>(attr).getInt()) {
1238 // Something is off, the cast result shape cannot be more dynamic
1239 // than the empty tensor result shape (enforced by
1240 // `canFoldIntoProducer`). Abort for now.
1241 return rewriter.notifyMatchFailure(
1242 producer, "mismatch in static value of shape of empty tensor "
1243 "result and cast result");
1244 }
1245 newMixedSizes.push_back(attr);
1246 continue;
1247 }
1248
1249 // Case 2 : The tensor cast shape is static, but empty tensor result
1250 // shape is dynamic.
1251 if (ShapedType::isStatic(newDim)) {
1252 newMixedSizes.push_back(rewriter.getIndexAttr(newDim));
1253 continue;
1254 }
1255
1256 // Case 3 : The tensor cast shape is dynamic and empty tensor result
1257 // shape is dynamic. Use the dynamic value from the empty tensor op.
1258 newMixedSizes.push_back(currDim);
1259 }
1260
1261 rewriter.replaceOpWithNewOp<EmptyOp>(castOp, newMixedSizes,
1262 resultType.getElementType(),
1263 resultType.getEncoding());
1264 return success();
1265 }
1266};
1267
1268} // namespace
1269
1270void EmptyOp::getCanonicalizationPatterns(RewritePatternSet &results,
1271 MLIRContext *context) {
1272 results.add<FoldEmptyTensorWithCastOp, FoldEmptyTensorWithDimOp,
1273 ReplaceEmptyTensorStaticShapeDims>(context);
1274}
1275
1276//===----------------------------------------------------------------------===//
1277// ExtractOp
1278//===----------------------------------------------------------------------===//
1279
1280namespace {
1281
1282/// Canonicalizes the pattern of the form
1283///
1284/// %val = tensor.cast %source : : tensor<?xi32> to tensor<2xi32>
1285/// %extracted_element = tensor.extract %val[%c0] : tensor<2xi32>
1286///
1287/// to
1288///
1289/// %extracted_element = tensor.extract %source[%c0] : tensor<?xi32>
1290struct ExtractFromTensorCast : public OpRewritePattern<tensor::ExtractOp> {
1291 using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
1292
1293 LogicalResult matchAndRewrite(tensor::ExtractOp extract,
1294 PatternRewriter &rewriter) const final {
1295 auto tensorCast = extract.getTensor().getDefiningOp<tensor::CastOp>();
1296 if (!tensorCast)
1297 return failure();
1298 if (!llvm::isa<RankedTensorType>(tensorCast.getSource().getType()))
1299 return failure();
1300 rewriter.replaceOpWithNewOp<tensor::ExtractOp>(
1301 extract, tensorCast.getSource(), extract.getIndices());
1302 return success();
1303 }
1304};
1305
1306/// Canonicalizes the pattern of the form
1307///
1308/// %val = tensor.collapse_shape %src[[0, 1]] : tensor<3x4xf64> into
1309/// tensor<12xf64>
1310/// %extracted_element = tensor.extract %val[%c10] :
1311/// tensor<12xf64>
1312///
1313/// to
1314///
1315/// %extracted_element = tensor.extract %src[%c2, %c2] : tensor<3x4xf64>
1316struct ExtractFromCollapseShape : public OpRewritePattern<tensor::ExtractOp> {
1317 using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
1318
1319 LogicalResult matchAndRewrite(tensor::ExtractOp extractOp,
1320 PatternRewriter &rewriter) const final {
1321 auto collapseOp =
1322 extractOp.getTensor().getDefiningOp<tensor::CollapseShapeOp>();
1323 if (!collapseOp)
1324 return failure();
1325 if (!collapseOp.getSrcType().hasStaticShape())
1326 return failure();
1327
1328 auto sourceSizes = collapseOp.getSrcType().getShape();
1329
1330 SmallVector<Value> indices(extractOp.getIndices().begin(),
1331 extractOp.getIndices().end());
1332 SmallVector<Value> sourceIndices;
1333 for (auto [index, group] :
1334 llvm::zip(indices, collapseOp.getReassociationIndices())) {
1335 assert(!group.empty() && "association indices groups cannot be empty");
1336 auto groupSize = group.size();
1337
1338 if (groupSize == 1) {
1339 sourceIndices.push_back(index);
1340 continue;
1341 }
1342
1343 SmallVector<int64_t> basis =
1344 llvm::map_to_vector(group, [&](int64_t d) { return sourceSizes[d]; });
1345 auto delinearize = affine::AffineDelinearizeIndexOp::create(
1346 rewriter, extractOp.getLoc(), index, basis, /*hasOuterBound=*/true);
1347 llvm::append_range(sourceIndices, delinearize.getResults());
1348 }
1349 if (collapseOp.getReassociationIndices().empty()) {
1350 auto zeroAffineMap = rewriter.getConstantAffineMap(0);
1351 int64_t srcRank =
1352 cast<RankedTensorType>(collapseOp.getSrcType()).getRank();
1353 OpFoldResult ofr = affine::makeComposedFoldedAffineApply(
1354 rewriter, extractOp.getLoc(), zeroAffineMap,
1355 ArrayRef<OpFoldResult>{});
1356 for (int64_t i = 0; i < srcRank; i++) {
1357 sourceIndices.push_back(
1358 getValueOrCreateConstantIndexOp(rewriter, extractOp.getLoc(), ofr));
1359 }
1360 }
1361
1362 rewriter.replaceOpWithNewOp<tensor::ExtractOp>(
1363 extractOp, collapseOp.getSrc(), sourceIndices);
1364 return success();
1365 }
1366};
1367
1368} // namespace
1369
1370void ExtractOp::getAsmResultNames(
1371 function_ref<void(Value, StringRef)> setNameFn) {
1372 setNameFn(getResult(), "extracted");
1373}
1374
1375LogicalResult ExtractOp::verify() {
1376 // Verify the # indices match if we have a ranked type.
1377 auto tensorType = llvm::cast<RankedTensorType>(getTensor().getType());
1378 if (tensorType.getRank() != static_cast<int64_t>(getIndices().size()))
1379 return emitOpError("incorrect number of indices for extract_element");
1380 return success();
1381}
1382
1383/// If we have an ExtractOp consuming an InsertOp with the same
1384/// indices, we can return the InsertOp's scalar directly.
1385// TODO: This only checks the immediate producer; extend to go up the
1386// insert/extract chain if the slices are disjoint.
1387static Value foldExtractAfterInsert(ExtractOp extractOp) {
1388 auto insertOp = extractOp.getTensor().getDefiningOp<InsertOp>();
1389
1390 auto isSame = [](Value a, Value b) {
1392 };
1393 if (insertOp && insertOp.getScalar().getType() == extractOp.getType() &&
1394 llvm::equal(insertOp.getIndices(), extractOp.getIndices(), isSame))
1395 return insertOp.getScalar();
1396
1397 return {};
1398}
1399
1400OpFoldResult ExtractOp::fold(FoldAdaptor adaptor) {
1401 if (Attribute tensor = adaptor.getTensor()) {
1402 // If this is a splat elements attribute, simply return the value.
1403 // All of the elements of a splat attribute are the same.
1404 if (auto splatTensor = llvm::dyn_cast<SplatElementsAttr>(tensor))
1405 return splatTensor.getSplatValue<Attribute>();
1406
1407 // If this is a dense resource elements attribute, return.
1408 if (isa<DenseResourceElementsAttr>(tensor))
1409 return {};
1410 }
1411
1412 // Collect the constant indices into the tensor.
1413 SmallVector<uint64_t, 8> indices;
1414 for (Attribute indice : adaptor.getIndices()) {
1415 if (!indice || !llvm::isa<IntegerAttr>(indice))
1416 return {};
1417 indices.push_back(llvm::cast<IntegerAttr>(indice).getInt());
1418 }
1419
1420 // Fold extract(from_elements(...)).
1421 if (auto fromElementsOp = getTensor().getDefiningOp<FromElementsOp>()) {
1422 auto tensorType = llvm::cast<RankedTensorType>(fromElementsOp.getType());
1423 auto rank = tensorType.getRank();
1424 assert(static_cast<int64_t>(indices.size()) == tensorType.getRank() &&
1425 "rank mismatch");
1426 int flatIndex = 0;
1427 int stride = 1;
1428 for (int i = rank - 1; i >= 0; --i) {
1429 flatIndex += indices[i] * stride;
1430 stride *= tensorType.getDimSize(i);
1431 }
1432 // Prevent out of bounds accesses. This can happen in invalid code that
1433 // will never execute.
1434 if (static_cast<int>(fromElementsOp.getElements().size()) <= flatIndex ||
1435 flatIndex < 0)
1436 return {};
1437 return fromElementsOp.getElements()[flatIndex];
1438 }
1439
1440 // If this is an elements attribute, query the value at the given indices.
1441 if (Attribute tensor = adaptor.getTensor()) {
1442 auto elementsAttr = llvm::dyn_cast<ElementsAttr>(tensor);
1443 if (elementsAttr && elementsAttr.isValidIndex(indices))
1444 return elementsAttr.getValues<Attribute>()[indices];
1445 }
1446
1447 if (Value result = foldExtractAfterInsert(*this))
1448 return result;
1449
1450 return {};
1451}
1452
1453void ExtractOp::getCanonicalizationPatterns(RewritePatternSet &results,
1454 MLIRContext *context) {
1455 results.add<ExtractFromTensorCast>(context);
1456}
1457
1459 RewritePatternSet &patterns) {
1460 patterns.add<ExtractFromCollapseShape>(patterns.getContext());
1461}
1462
1463//===----------------------------------------------------------------------===//
1464// FromElementsOp
1465//===----------------------------------------------------------------------===//
1466
1467void FromElementsOp::getAsmResultNames(
1468 function_ref<void(Value, StringRef)> setNameFn) {
1469 setNameFn(getResult(), "from_elements");
1470}
1471
1472void FromElementsOp::build(OpBuilder &builder, OperationState &result,
1473 ValueRange elements) {
1474 assert(!elements.empty() && "expected at least one element");
1475 Type resultType = RankedTensorType::get(
1476 {static_cast<int64_t>(elements.size())}, elements.front().getType());
1477 build(builder, result, resultType, elements);
1478}
1479
1480OpFoldResult FromElementsOp::fold(FoldAdaptor adaptor) {
1481 // DenseElementsAttr::get requires StringAttr for element types that are not
1482 // integer, index, float, or complex (e.g. vector types), but folded constants
1483 // won't be StringAttr instances. Only fold for element types directly
1484 // supported by DenseElementsAttr.
1485 Type eltType = getType().getElementType();
1486 if (!eltType.isIntOrIndexOrFloat() && !isa<ComplexType>(eltType))
1487 return {};
1488 if (!llvm::is_contained(adaptor.getElements(), nullptr))
1489 return DenseElementsAttr::get(getType(), adaptor.getElements());
1490 return {};
1491}
1492
1493namespace {
1494
1495// Pushes the index_casts that occur before extractions to after the extract.
1496// This minimizes type conversion in some cases and enables the extract
1497// canonicalizer. This changes:
1498//
1499// %cast = arith.index_cast %tensor : tensor<1xi32> to tensor<1xindex>
1500// %extract = tensor.extract %cast[%index] : tensor<1xindex>
1501//
1502// to the following:
1503//
1504// %extract = tensor.extract %tensor[%index] : tensor<1xindex>
1505// %cast = arith.index_cast %extract : i32 to index
1506//
1507// to just %element.
1508//
1509// Consider expanding this to a template and handle all tensor cast
1510// operations.
1511struct ExtractElementFromIndexCast
1512 : public OpRewritePattern<tensor::ExtractOp> {
1513 using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
1514
1515 LogicalResult matchAndRewrite(tensor::ExtractOp extract,
1516 PatternRewriter &rewriter) const final {
1517 Location loc = extract.getLoc();
1518 auto indexCast = extract.getTensor().getDefiningOp<arith::IndexCastOp>();
1519 if (!indexCast)
1520 return failure();
1521
1522 Type elementTy = getElementTypeOrSelf(indexCast.getIn());
1523
1524 auto newExtract = tensor::ExtractOp::create(
1525 rewriter, loc, elementTy, indexCast.getIn(), extract.getIndices());
1526
1527 rewriter.replaceOpWithNewOp<arith::IndexCastOp>(extract, extract.getType(),
1528 newExtract);
1529
1530 return success();
1531 }
1532};
1533
1534} // namespace
1535
1536void FromElementsOp::getCanonicalizationPatterns(RewritePatternSet &results,
1537 MLIRContext *context) {
1538 results.add<ExtractElementFromIndexCast>(context);
1539}
1540
1541//===----------------------------------------------------------------------===//
1542// GatherOp
1543//===----------------------------------------------------------------------===//
1544
1545void GatherOp::getAsmResultNames(
1546 function_ref<void(Value, StringRef)> setNameFn) {
1547 setNameFn(getResult(), "gather");
1548}
1549
1550/// Return the inferred result type for a gatherOp where:
1551/// - sourceType is the type of the source tensor gathered from
1552/// - indicesType is the type of the indices used to gather
1553/// - gatherDims are the dims along which the gather occurs.
1554/// Return a full rank or ranked-reduced variant of the type depending on
1555/// the value of rankReduced.
1556///
1557/// The leading dimensions of the index tensor give the result tensor its
1558/// leading dimensions.
1559/// The trailing dimensions of the result tensor are obtained from the source
1560/// tensor by setting the dimensions specified in gather_dims to `1` (if
1561/// rankedReduced is false), or skipping them (otherwise).
1562RankedTensorType GatherOp::inferResultType(RankedTensorType sourceType,
1563 RankedTensorType indicesType,
1564 ArrayRef<int64_t> gatherDims,
1565 bool rankReduced) {
1566 SmallVector<int64_t> resultShape(indicesType.getShape().drop_back());
1567 resultShape.reserve(resultShape.size() + sourceType.getRank());
1568 for (int64_t idx : llvm::seq<int64_t>(0, sourceType.getRank())) {
1569 if (llvm::binary_search(gatherDims, idx)) {
1570 if (!rankReduced)
1571 resultShape.push_back(1);
1572 continue;
1573 }
1574 resultShape.push_back(sourceType.getDimSize(idx));
1575 }
1576 return RankedTensorType::Builder(sourceType).setShape(resultShape);
1577}
1578
1579static LogicalResult
1582 StringRef gatherOrScatter, StringRef sourceOrDest) {
1583 if (dims.empty())
1584 return op->emitOpError(gatherOrScatter) << "_dims must be non-empty";
1585
1586 int64_t numGatherDims = dims.size();
1587 if (numGatherDims > rank)
1588 return op->emitOpError(gatherOrScatter)
1589 << "_dims overflow " << sourceOrDest << " rank";
1590 if (indices.empty() || indices.back() != numGatherDims)
1591 return op->emitOpError(gatherOrScatter)
1592 << "_dims length must match the size of last dimension of indices";
1593 for (int64_t val : dims) {
1594 if (val < 0)
1595 return op->emitOpError(gatherOrScatter)
1596 << "_dims value must be non-negative";
1597 if (val >= rank)
1598 return op->emitOpError(gatherOrScatter)
1599 << "_dims value must be smaller than " << sourceOrDest << " rank";
1600 }
1601 for (int64_t i = 1; i < numGatherDims; ++i) {
1602 if (dims[i - 1] >= dims[i])
1603 return op->emitOpError(gatherOrScatter)
1604 << "_dims values must be strictly increasing";
1605 }
1606 return success();
1607}
1608
1609LogicalResult GatherOp::verify() {
1610 int64_t sourceRank = getSourceType().getRank();
1611 ArrayRef<int64_t> gatherDims = getGatherDims();
1612 if (failed(verifyGatherOrScatterDims(getOperation(), gatherDims,
1613 getIndicesType().getShape(), sourceRank,
1614 "gather", "source")))
1615 return failure();
1616
1617 RankedTensorType expectedResultType = GatherOp::inferResultType(
1618 getSourceType(), getIndicesType(), gatherDims, /*rankReduced=*/false);
1619 RankedTensorType expectedRankReducedResultType = GatherOp::inferResultType(
1620 getSourceType(), getIndicesType(), gatherDims, /*rankReduced=*/true);
1621 if (getResultType() != expectedResultType &&
1622 getResultType() != expectedRankReducedResultType) {
1623 return emitOpError("result type "
1624 "mismatch: "
1625 "expected ")
1626 << expectedResultType << " or its rank-reduced variant "
1627 << expectedRankReducedResultType << " (got: " << getResultType()
1628 << ")";
1629 }
1630
1631 return success();
1632}
1633
1634OpFoldResult GatherOp::fold(FoldAdaptor adaptor) {
1635 if (OpFoldResult reshapedSource = reshapeConstantSource(
1636 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
1637 getResult().getType()))
1638 return reshapedSource;
1639 return {};
1640}
1641
1642//===----------------------------------------------------------------------===//
1643// InsertOp
1644//===----------------------------------------------------------------------===//
1645
1646void InsertOp::getAsmResultNames(
1647 function_ref<void(Value, StringRef)> setNameFn) {
1648 setNameFn(getResult(), "inserted");
1649}
1650
1651LogicalResult InsertOp::verify() {
1652 // Verify the # indices match if we have a ranked type.
1653 auto destType = llvm::cast<RankedTensorType>(getDest().getType());
1654 if (destType.getRank() != static_cast<int64_t>(getIndices().size()))
1655 return emitOpError("incorrect number of indices");
1656 return success();
1657}
1658
1659OpFoldResult InsertOp::fold(FoldAdaptor adaptor) {
1660 Attribute scalar = adaptor.getScalar();
1661 Attribute dest = adaptor.getDest();
1662 if (scalar && dest)
1663 if (auto splatDest = llvm::dyn_cast<SplatElementsAttr>(dest))
1664 if (scalar == splatDest.getSplatValue<Attribute>())
1665 return dest;
1666 return {};
1667}
1668
1669//===----------------------------------------------------------------------===//
1670// GenerateOp
1671//===----------------------------------------------------------------------===//
1672
1673void GenerateOp::getAsmResultNames(
1674 function_ref<void(Value, StringRef)> setNameFn) {
1675 setNameFn(getResult(), "generated");
1676}
1677
1678LogicalResult GenerateOp::reifyResultShapes(
1679 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
1680 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
1681 int idx = 0;
1682 for (auto dim : llvm::seq<int64_t>(0, getType().getRank())) {
1683 if (getType().isDynamicDim(dim)) {
1684 reifiedReturnShapes[0][dim] = getOperand(idx++);
1685 } else {
1686 reifiedReturnShapes[0][dim] =
1687 builder.getIndexAttr(getType().getDimSize(dim));
1688 }
1689 }
1690 return success();
1691}
1692
1693LogicalResult GenerateOp::verify() {
1694 // Ensure that the tensor type has as many dynamic dimensions as are
1695 // specified by the operands.
1696 RankedTensorType resultType = llvm::cast<RankedTensorType>(getType());
1697 if (failed(verifyDynamicDimensionCount(getOperation(), resultType,
1698 getOperands())))
1699 return failure();
1700 return success();
1701}
1702
1703LogicalResult GenerateOp::verifyRegions() {
1704 RankedTensorType resultTy = llvm::cast<RankedTensorType>(getType());
1705 // Ensure that region arguments span the index space.
1706 if (!llvm::all_of(getBody().getArgumentTypes(),
1707 [](Type ty) { return ty.isIndex(); }))
1708 return emitError("all body arguments must be index");
1709 if (getBody().getNumArguments() != resultTy.getRank())
1710 return emitError("must have one body argument per input dimension");
1711
1712 // Ensure that the region yields an element of the right type.
1713 auto yieldOp = cast<YieldOp>(getBody().getBlocks().front().getTerminator());
1714
1715 if (yieldOp.getValue().getType() != resultTy.getElementType())
1716 return emitOpError(
1717 "body must be terminated with a `yield` operation of the tensor "
1718 "element type");
1719
1720 return success();
1721}
1722
1723void GenerateOp::build(
1724 OpBuilder &b, OperationState &result, Type resultTy,
1725 ValueRange dynamicExtents,
1726 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilder) {
1727 build(b, result, resultTy, dynamicExtents);
1728
1729 // Build and populate body.
1730 OpBuilder::InsertionGuard guard(b);
1731 Region *bodyRegion = result.regions.front().get();
1732 auto rank = llvm::cast<RankedTensorType>(resultTy).getRank();
1733 SmallVector<Type, 2> argumentTypes(rank, b.getIndexType());
1734 SmallVector<Location, 2> argumentLocs(rank, result.location);
1735 Block *bodyBlock =
1736 b.createBlock(bodyRegion, bodyRegion->end(), argumentTypes, argumentLocs);
1737 bodyBuilder(b, result.location, bodyBlock->getArguments());
1738}
1739
1740namespace {
1741
1742/// Canonicalizes tensor.generate operations with a constant
1743/// operand into the equivalent operation with the operand expressed in the
1744/// result type, instead. We also insert a type cast to make sure that the
1745/// resulting IR is still well-typed.
1746struct StaticTensorGenerate : public OpRewritePattern<GenerateOp> {
1747 using OpRewritePattern<GenerateOp>::OpRewritePattern;
1748
1749 LogicalResult matchAndRewrite(GenerateOp generateOp,
1750 PatternRewriter &rewriter) const final {
1751 SmallVector<Value> foldedDynamicSizes;
1752 RankedTensorType foldedTensorType = foldDynamicToStaticDimSizes(
1753 generateOp.getType(), generateOp.getDynamicExtents(),
1754 foldedDynamicSizes);
1755
1756 // Stop here if no dynamic size was promoted to static.
1757 if (foldedTensorType == generateOp.getType())
1758 return failure();
1759
1760 auto loc = generateOp.getLoc();
1761 auto newOp =
1762 GenerateOp::create(rewriter, loc, foldedTensorType, foldedDynamicSizes);
1763 rewriter.inlineRegionBefore(generateOp.getBody(), newOp.getBody(),
1764 newOp.getBody().begin());
1765 rewriter.replaceOpWithNewOp<tensor::CastOp>(generateOp,
1766 generateOp.getType(), newOp);
1767 return success();
1768 }
1769};
1770
1771/// Canonicalizes the pattern of the form
1772///
1773/// %tensor = tensor.generate %x {
1774/// ^bb0(%arg0: index):
1775/// <computation>
1776/// yield %1 : index
1777/// } : tensor<?xindex>
1778/// %extracted_element = tensor.extract %tensor[%c0] : tensor<?xi32>
1779///
1780/// to just <computation> with %arg0 replaced by %c0. We only do this if the
1781/// tensor.generate operation has no side-effects.
1782struct ExtractFromTensorGenerate : public OpRewritePattern<tensor::ExtractOp> {
1783 using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
1784
1785 LogicalResult matchAndRewrite(tensor::ExtractOp extract,
1786 PatternRewriter &rewriter) const final {
1787 auto tensorFromElements = extract.getTensor().getDefiningOp<GenerateOp>();
1788 if (!tensorFromElements || !wouldOpBeTriviallyDead(tensorFromElements))
1789 return failure();
1790
1791 IRMapping mapping;
1792 Block *body = &tensorFromElements.getBody().front();
1793 mapping.map(body->getArguments(), extract.getIndices());
1794 for (auto &op : body->without_terminator())
1795 rewriter.clone(op, mapping);
1796
1797 auto yield = cast<YieldOp>(body->getTerminator());
1798
1799 rewriter.replaceOp(extract, mapping.lookupOrDefault(yield.getValue()));
1800 return success();
1801 }
1802};
1803
1804} // namespace
1805
1806void GenerateOp::getCanonicalizationPatterns(RewritePatternSet &results,
1807 MLIRContext *context) {
1808 // TODO: Move extract pattern to tensor::ExtractOp.
1809 results.add<ExtractFromTensorGenerate, StaticTensorGenerate>(context);
1810}
1811
1812//===----------------------------------------------------------------------===//
1813// RankOp
1814//===----------------------------------------------------------------------===//
1815
1816void RankOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
1817 setNameFn(getResult(), "rank");
1818}
1819
1820OpFoldResult RankOp::fold(FoldAdaptor adaptor) {
1821 // Constant fold rank when the rank of the operand is known.
1822 auto type = getOperand().getType();
1823 auto shapedType = llvm::dyn_cast<ShapedType>(type);
1824 if (shapedType && shapedType.hasRank())
1825 return IntegerAttr::get(IndexType::get(getContext()), shapedType.getRank());
1826 return IntegerAttr();
1827}
1828
1829//===----------------------------------------------------------------------===//
1830// ReshapeOp
1831//===----------------------------------------------------------------------===//
1832
1833void ReshapeOp::getAsmResultNames(
1834 function_ref<void(Value, StringRef)> setNameFn) {
1835 setNameFn(getResult(), "reshape");
1836}
1837
1838static int64_t getNumElements(ShapedType type) {
1839 int64_t numElements = 1;
1840 for (auto dim : type.getShape())
1841 numElements *= dim;
1842 return numElements;
1843}
1844
1845LogicalResult ReshapeOp::verify() {
1846 TensorType operandType = llvm::cast<TensorType>(getSource().getType());
1847 TensorType resultType = llvm::cast<TensorType>(getResult().getType());
1848
1849 if (operandType.getElementType() != resultType.getElementType())
1850 return emitOpError("element types of source and destination tensor "
1851 "types should be the same");
1852
1853 int64_t shapeSize =
1854 llvm::cast<RankedTensorType>(getShape().getType()).getDimSize(0);
1855 auto resultRankedType = llvm::dyn_cast<RankedTensorType>(resultType);
1856 auto operandRankedType = llvm::dyn_cast<RankedTensorType>(operandType);
1857
1858 if (resultRankedType) {
1859 if (operandRankedType && resultRankedType.hasStaticShape() &&
1860 operandRankedType.hasStaticShape()) {
1861 if (getNumElements(operandRankedType) != getNumElements(resultRankedType))
1862 return emitOpError("source and destination tensor should have the "
1863 "same number of elements");
1864 }
1865 if (ShapedType::isDynamic(shapeSize))
1866 return emitOpError("cannot use shape operand with dynamic length to "
1867 "reshape to statically-ranked tensor type");
1868 if (shapeSize != resultRankedType.getRank())
1869 return emitOpError(
1870 "length of shape operand differs from the result's tensor rank");
1871 }
1872 return success();
1873}
1874
1875OpFoldResult ReshapeOp::fold(FoldAdaptor adaptor) {
1876 if (OpFoldResult reshapedSource = reshapeConstantSource(
1877 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
1878 getResult().getType()))
1879 return reshapedSource;
1880
1881 // If the producer of operand 'source' is another 'tensor.reshape' op, use the
1882 // producer's input instead as the original tensor to reshape. This could
1883 // render such producer dead code.
1884 if (auto reshapeOpProducer = getSource().getDefiningOp<ReshapeOp>()) {
1885 getSourceMutable().assign(reshapeOpProducer.getSource());
1886 return getResult();
1887 }
1888
1889 auto source = getSource();
1890 auto sourceTy = dyn_cast<RankedTensorType>(source.getType());
1891 auto resultTy = dyn_cast<RankedTensorType>(getType());
1892 if (!sourceTy || !resultTy || sourceTy != resultTy)
1893 return {};
1894
1895 // If the source and result are both 0D or 1D tensors and have the same type,
1896 // the reshape has no effect, even if the tensor is dynamically shaped.
1897 if (sourceTy.getRank() <= 1)
1898 return source;
1899
1900 if (auto fromElements = getShape().getDefiningOp<tensor::FromElementsOp>()) {
1901 auto elements = fromElements.getElements();
1902 bool dynamicNoop =
1903 sourceTy.getRank() == static_cast<int64_t>(elements.size());
1904 for (int id = 0, s = elements.size(); id < s && dynamicNoop; ++id) {
1905 auto element = elements[id];
1906
1907 if (auto cst = getConstantIntValue(element)) {
1908 dynamicNoop &= cst.value() == sourceTy.getDimSize(id);
1909 continue;
1910 }
1911
1912 if (auto dimOp = element.getDefiningOp<tensor::DimOp>()) {
1913 dynamicNoop &= dimOp.getSource() == source;
1914
1915 auto cst = getConstantIntValue(dimOp.getIndex());
1916 dynamicNoop &=
1917 cst.has_value() && cst.value() == static_cast<int64_t>(id);
1918 continue;
1919 }
1920
1921 dynamicNoop = false;
1922 break;
1923 }
1924
1925 if (dynamicNoop)
1926 return source;
1927 }
1928
1929 return {};
1930}
1931
1932//===----------------------------------------------------------------------===//
1933// Reassociative reshape ops
1934//===----------------------------------------------------------------------===//
1935
1936void CollapseShapeOp::getAsmResultNames(
1937 function_ref<void(Value, StringRef)> setNameFn) {
1938 setNameFn(getResult(), "collapsed");
1939}
1940
1941void ExpandShapeOp::getAsmResultNames(
1942 function_ref<void(Value, StringRef)> setNameFn) {
1943 setNameFn(getResult(), "expanded");
1944}
1945
1946int64_t ExpandShapeOp::getCorrespondingSourceDim(int64_t resultDim) {
1947 assert(resultDim >= 0 && resultDim < getResultType().getRank() &&
1948 "invalid resultDim");
1949 for (const auto &it : llvm::enumerate(getReassociationIndices()))
1950 if (llvm::is_contained(it.value(), resultDim))
1951 return it.index();
1952 llvm_unreachable("could not find reassociation group");
1953}
1954
1955FailureOr<SmallVector<OpFoldResult>>
1956ExpandShapeOp::inferOutputShape(OpBuilder &b, Location loc,
1957 RankedTensorType expandedType,
1958 ArrayRef<ReassociationIndices> reassociation,
1959 ArrayRef<OpFoldResult> inputShape) {
1960 std::optional<SmallVector<OpFoldResult>> outputShape =
1961 inferExpandShapeOutputShape(b, loc, expandedType, reassociation,
1962 inputShape);
1963 if (!outputShape)
1964 return failure();
1965 return *outputShape;
1966}
1967
1968SmallVector<OpFoldResult> ExpandShapeOp::getMixedOutputShape() {
1969 return getMixedValues(getStaticOutputShape(), getOutputShape(), getContext());
1970}
1971
1972void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
1973 Type resultType, Value src,
1974 ArrayRef<ReassociationIndices> reassociation,
1975 ArrayRef<OpFoldResult> outputShape) {
1976 auto [staticOutputShape, dynamicOutputShape] =
1977 decomposeMixedValues(SmallVector<OpFoldResult>(outputShape));
1978 build(builder, result, cast<RankedTensorType>(resultType), src,
1979 getReassociationIndicesAttribute(builder, reassociation),
1980 dynamicOutputShape, staticOutputShape);
1981}
1982
1983void ExpandShapeOp::build(OpBuilder &builder, OperationState &result,
1984 Type resultType, Value src,
1985 ArrayRef<ReassociationIndices> reassociation) {
1986 SmallVector<OpFoldResult> inputShape =
1987 getMixedSizes(builder, result.location, src);
1988 auto tensorResultTy = cast<RankedTensorType>(resultType);
1989 FailureOr<SmallVector<OpFoldResult>> outputShape = inferOutputShape(
1990 builder, result.location, tensorResultTy, reassociation, inputShape);
1991 SmallVector<OpFoldResult> outputShapeOrEmpty;
1992 if (succeeded(outputShape)) {
1993 outputShapeOrEmpty = *outputShape;
1994 }
1995 build(builder, result, tensorResultTy, src, reassociation,
1996 outputShapeOrEmpty);
1997}
1998
1999SmallVector<AffineMap, 4> CollapseShapeOp::getReassociationMaps() {
2000 return getSymbolLessAffineMaps(getReassociationExprs());
2001}
2002SmallVector<ReassociationExprs, 4> CollapseShapeOp::getReassociationExprs() {
2004 getReassociationIndices());
2005}
2006
2007SmallVector<AffineMap, 4> ExpandShapeOp::getReassociationMaps() {
2008 return getSymbolLessAffineMaps(getReassociationExprs());
2009}
2010SmallVector<ReassociationExprs, 4> ExpandShapeOp::getReassociationExprs() {
2012 getReassociationIndices());
2013}
2014
2015RankedTensorType CollapseShapeOp::inferCollapsedType(
2016 RankedTensorType type, ArrayRef<ReassociationIndices> reassociation) {
2017 return inferCollapsedType(
2019 type.getContext(), reassociation)));
2020}
2021
2022/// Compute the RankedTensorType obtained by applying `reassociation` to
2023/// `type`.
2024RankedTensorType
2025CollapseShapeOp::inferCollapsedType(RankedTensorType type,
2026 ArrayRef<AffineMap> reassociation) {
2027 auto shape = type.getShape();
2028 SmallVector<int64_t, 4> newShape;
2029 newShape.reserve(reassociation.size());
2030
2031 // Use the fact that reassociation is valid to simplify the logic: only use
2032 // each map's rank.
2033 assert(isReassociationValid(reassociation) && "invalid reassociation");
2034 unsigned currentDim = 0;
2035 for (AffineMap m : reassociation) {
2036 unsigned dim = m.getNumResults();
2037 auto band = shape.slice(currentDim, dim);
2038 int64_t size = 1;
2039 if (llvm::is_contained(band, ShapedType::kDynamic))
2040 size = ShapedType::kDynamic;
2041 else
2042 for (unsigned d = 0; d < dim; ++d)
2043 size *= shape[currentDim + d];
2044 newShape.push_back(size);
2045 currentDim += dim;
2046 }
2047
2048 return RankedTensorType::get(newShape, type.getElementType());
2049}
2050
2051void CollapseShapeOp::build(OpBuilder &b, OperationState &result, Value src,
2052 ArrayRef<ReassociationIndices> reassociation,
2053 ArrayRef<NamedAttribute> attrs) {
2054 auto srcType = llvm::cast<RankedTensorType>(src.getType());
2055 RankedTensorType collapsedType = inferCollapsedType(srcType, reassociation);
2056 auto resultType =
2057 RankedTensorType::get(collapsedType.getShape(), srcType.getElementType(),
2058 srcType.getEncoding());
2059 buildPropertiesAndDiscardableAttributes(result, attrs);
2060 result.getOrAddProperties<Properties>().reassociation =
2061 getReassociationIndicesAttribute(b, reassociation);
2062 result.addOperands(src);
2063 result.addTypes(resultType);
2064}
2065
2066template <typename TensorReshapeOp, bool isExpansion = std::is_same<
2067 TensorReshapeOp, ExpandShapeOp>::value>
2068static LogicalResult verifyTensorReshapeOp(TensorReshapeOp op,
2069 RankedTensorType expandedType,
2070 RankedTensorType collapsedType) {
2071 if (failed(
2072 verifyReshapeLikeTypes(op, expandedType, collapsedType, isExpansion)))
2073 return failure();
2074
2075 // Reshape must preserve the number of elements when statically known.
2076 if (expandedType.hasStaticShape() && collapsedType.hasStaticShape()) {
2077 int64_t expandedNumElements = expandedType.getNumElements();
2078 int64_t collapsedNumElements = collapsedType.getNumElements();
2079 if (expandedNumElements != collapsedNumElements) {
2080 return op.emitOpError("number of elements must be preserved: ")
2081 << expandedNumElements << " != " << collapsedNumElements;
2082 }
2083 }
2084
2085 auto maps = op.getReassociationMaps();
2086 RankedTensorType expectedType =
2087 CollapseShapeOp::inferCollapsedType(expandedType, maps);
2088 if (!isSameTypeWithoutEncoding(collapsedType, expectedType))
2089 return op.emitOpError("expected collapsed type to be ")
2090 << expectedType << ", but got " << collapsedType;
2091 return success();
2092}
2093
2094LogicalResult ExpandShapeOp::verify() {
2096 return failure();
2097
2098 RankedTensorType srcType = getSrc().getType();
2099 RankedTensorType resultType = getResult().getType();
2100
2101 if ((int64_t)getStaticOutputShape().size() != resultType.getRank())
2102 return emitOpError("expected number of static shape dims to be equal to "
2103 "the output rank (")
2104 << resultType.getRank() << ") but found "
2105 << getStaticOutputShape().size() << " inputs instead";
2106
2107 if ((int64_t)getOutputShape().size() !=
2108 llvm::count(getStaticOutputShape(), ShapedType::kDynamic))
2109 return emitOpError("mismatch in dynamic dims in output_shape and "
2110 "static_output_shape: static_output_shape has ")
2111 << llvm::count(getStaticOutputShape(), ShapedType::kDynamic)
2112 << " dynamic dims while output_shape has " << getOutputShape().size()
2113 << " values";
2114
2115 // Verify that the number of dynamic dims in output_shape matches the number
2116 // of dynamic dims in the result type.
2117 if (failed(verifyDynamicDimensionCount(getOperation(), resultType,
2118 getOutputShape())))
2119 return failure();
2120
2121 // Verify if provided output shapes are in agreement with output type.
2122 DenseI64ArrayAttr staticOutputShapes = getStaticOutputShapeAttr();
2123 ArrayRef<int64_t> resShape = getResult().getType().getShape();
2124 for (auto [pos, shape] : llvm::enumerate(resShape))
2125 if (ShapedType::isStatic(shape) && shape != staticOutputShapes[pos])
2126 return emitOpError("invalid output shape provided at pos ") << pos;
2127
2128 return verifyTensorReshapeOp(*this, resultType, srcType);
2129}
2130
2131LogicalResult CollapseShapeOp::verify() {
2132 CollapseShapeOp op = *this;
2134 return failure();
2135
2136 RankedTensorType srcType = op.getSrc().getType();
2137 RankedTensorType resultType = op.getResult().getType();
2138
2139 return verifyTensorReshapeOp(op, srcType, resultType);
2140}
2141
2142namespace {
2143/// Reshape of a splat constant can be replaced with a constant of the result
2144/// type.
2145template <typename TensorReshapeOp>
2146struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> {
2147 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
2148 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
2149 PatternRewriter &rewriter) const override {
2150 DenseElementsAttr attr;
2151 if (!matchPattern(reshapeOp.getSrc(), m_Constant(&attr)))
2152 return failure();
2153 if (!attr || !attr.isSplat())
2154 return failure();
2155 // DenseElementsAttr requires a static shape; skip folding for dynamic
2156 // result types.
2157 if (!reshapeOp.getResultType().hasStaticShape())
2158 return failure();
2159 DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer(
2160 reshapeOp.getResultType(), attr.getRawData());
2161 rewriter.replaceOpWithNewOp<arith::ConstantOp>(reshapeOp, newAttr);
2162 return success();
2163 }
2164};
2165
2166// Folds TensorReshapeOp(splat x : src_type) : res_type into splat x : res_type.
2167template <typename TensorReshapeOp>
2168class FoldReshapeWithSplat : public OpRewritePattern<TensorReshapeOp> {
2169public:
2170 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
2171
2172 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
2173 PatternRewriter &rewriter) const override {
2174 auto splatOp = reshapeOp.getSrc().template getDefiningOp<tensor::SplatOp>();
2175 if (!splatOp || !splatOp.getAggregate().getType().hasStaticShape())
2176 return failure();
2177
2178 rewriter.replaceOpWithNewOp<tensor::SplatOp>(
2179 reshapeOp, reshapeOp.getResultType(), splatOp.getInput());
2180 return success();
2181 }
2182};
2183
2184/// Reshape of a FromElements can be replaced with a FromElements of the
2185/// result type
2186template <typename TensorReshapeOp>
2187struct FoldReshapeWithFromElements : OpRewritePattern<TensorReshapeOp> {
2188 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
2189 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
2190 PatternRewriter &rewriter) const override {
2191 auto fromElements =
2192 reshapeOp.getSrc().template getDefiningOp<FromElementsOp>();
2193 if (!fromElements)
2194 return failure();
2195
2196 auto shapedTy = llvm::cast<ShapedType>(reshapeOp.getType());
2197
2198 if (!shapedTy.hasStaticShape())
2199 return failure();
2200
2201 rewriter.replaceOpWithNewOp<FromElementsOp>(reshapeOp, reshapeOp.getType(),
2202 fromElements.getElements());
2203 return success();
2204 }
2205};
2206
2207// Fold CastOp into CollapseShapeOp when adding static information.
2208struct FoldCollapseOfCastOp : public OpRewritePattern<CollapseShapeOp> {
2209 using OpRewritePattern<CollapseShapeOp>::OpRewritePattern;
2210
2211 LogicalResult matchAndRewrite(CollapseShapeOp collapseShapeOp,
2212 PatternRewriter &rewriter) const override {
2213 auto castOp = collapseShapeOp.getSrc().getDefiningOp<tensor::CastOp>();
2214 if (!tensor::canFoldIntoConsumerOp(castOp))
2215 return failure();
2216
2217 RankedTensorType srcType =
2218 llvm::cast<RankedTensorType>(castOp.getSource().getType());
2219 RankedTensorType newResultType = CollapseShapeOp::inferCollapsedType(
2220 srcType, collapseShapeOp.getReassociationMaps());
2221
2222 if (newResultType == collapseShapeOp.getResultType()) {
2223 rewriter.modifyOpInPlace(collapseShapeOp, [&]() {
2224 collapseShapeOp.getSrcMutable().assign(castOp.getSource());
2225 });
2226 } else {
2227 auto newOp = CollapseShapeOp::create(rewriter, collapseShapeOp.getLoc(),
2228 newResultType, castOp.getSource(),
2229 collapseShapeOp.getReassociation());
2230 rewriter.replaceOpWithNewOp<tensor::CastOp>(
2231 collapseShapeOp, collapseShapeOp.getResultType(), newOp);
2232 }
2233 return success();
2234 }
2235};
2236
2237/// Fold/sink a producer `tensor.cast` with a consumer `tensor.expand_shape` by
2238/// matching constant output_shape operands of the expand. This makes the
2239/// `tensor.expand_shape` more static and creates a consumer cast that can be
2240/// propagated further.
2241struct ConvertToStaticExpandShape : public OpRewritePattern<ExpandShapeOp> {
2242 using OpRewritePattern<ExpandShapeOp>::OpRewritePattern;
2243
2244 LogicalResult matchAndRewrite(ExpandShapeOp expandOp,
2245 PatternRewriter &rewriter) const override {
2246 auto castOp = expandOp.getSrc().getDefiningOp<CastOp>();
2247 if (!canFoldIntoConsumerOp(castOp))
2248 return failure();
2249
2250 ArrayRef<int64_t> castSrcShape = castOp.getSource().getType().getShape();
2251 SmallVector<ReassociationIndices, 4> reassoc =
2252 expandOp.getReassociationIndices();
2253
2254 SmallVector<int64_t> newOutputShape(expandOp.getResultType().getShape());
2255 SmallVector<Value> dynamicOutputShape;
2256 auto outputIt = expandOp.getOutputShape().begin();
2257
2258 for (const auto &[inputDim, innerReassoc] : llvm::enumerate(reassoc)) {
2259 for (uint64_t outDim : innerReassoc) {
2260 if (ShapedType::isStatic(newOutputShape[outDim]))
2261 continue;
2262
2263 // If the cast's src type is dynamic, don't infer any of the
2264 // corresponding expanded dimensions. `tensor.expand_shape` requires at
2265 // least one of the expanded dimensions to be dynamic if the input is
2266 // dynamic.
2267 Value val = *outputIt;
2268 ++outputIt;
2269 if (ShapedType::isDynamic(castSrcShape[inputDim])) {
2270 dynamicOutputShape.push_back(val);
2271 continue;
2272 }
2273
2274 APInt cst;
2275 if (matchPattern(val, m_ConstantInt(&cst))) {
2276 newOutputShape[outDim] = cst.getSExtValue();
2277 } else {
2278 dynamicOutputShape.push_back(val);
2279 }
2280 }
2281 }
2282
2283 // Couldn't match any values, nothing to change
2284 if (expandOp.getOutputShape().size() == dynamicOutputShape.size())
2285 return failure();
2286
2287 // Calculate the input shape from the output
2288 SmallVector<int64_t> newInputShape(expandOp.getSrcType().getRank(), 1l);
2289 for (auto inDim : llvm::seq<int>(0, newInputShape.size())) {
2290 for (auto outDim : reassoc[inDim]) {
2291 auto ofr = newOutputShape[outDim];
2292 if (ShapedType::isDynamic(ofr)) {
2293 newInputShape[inDim] = ShapedType::kDynamic;
2294 break;
2295 }
2296 newInputShape[inDim] *= ofr;
2297 }
2298 }
2299
2300 SmallVector<OpFoldResult> outputOfr =
2301 getMixedValues(newOutputShape, dynamicOutputShape, rewriter);
2302 // The refined types are still applied to the same src/result values, so
2303 // propagate their encodings, letting each encoding self-decide whether it
2304 // still holds on the more-static shape.
2305 Type elementType = expandOp.getSrcType().getElementType();
2306 auto inputType = RankedTensorType::get(
2307 newInputShape, elementType,
2308 propagateEncoding(expandOp.getSrcType().getEncoding(), newInputShape,
2309 elementType));
2310 auto outputType = RankedTensorType::get(
2311 newOutputShape, elementType,
2312 propagateEncoding(expandOp.getResultType().getEncoding(),
2313 newOutputShape, elementType));
2314 auto inputCast = CastOp::create(rewriter, expandOp.getLoc(), inputType,
2315 expandOp.getSrc());
2316 auto newExpand = ExpandShapeOp::create(
2317 rewriter, expandOp.getLoc(), outputType, inputCast.getResult(),
2318 expandOp.getReassociationIndices(), outputOfr);
2319 rewriter.replaceOpWithNewOp<CastOp>(expandOp, expandOp.getType(),
2320 newExpand.getResult());
2321 return success();
2322 }
2323};
2324} // namespace
2325
2326void ExpandShapeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2327 MLIRContext *context) {
2328 results.add<
2329 ComposeReassociativeReshapeOps<ExpandShapeOp, ReshapeOpKind::kExpand>,
2330 ComposeExpandOfCollapseOp<ExpandShapeOp, CollapseShapeOp, CastOp>,
2331 ConvertToStaticExpandShape, FoldReshapeWithConstant<ExpandShapeOp>,
2332 FoldReshapeWithSplat<ExpandShapeOp>,
2333 FoldReshapeWithFromElements<ExpandShapeOp>>(context);
2334}
2335
2336void CollapseShapeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2337 MLIRContext *context) {
2338 results.add<
2339 ComposeReassociativeReshapeOps<CollapseShapeOp, ReshapeOpKind::kCollapse>,
2340 ComposeCollapseOfExpandOp<CollapseShapeOp, ExpandShapeOp, CastOp,
2341 tensor::DimOp, RankedTensorType>,
2342 FoldReshapeWithConstant<CollapseShapeOp>,
2343 FoldReshapeWithSplat<CollapseShapeOp>,
2344 FoldReshapeWithFromElements<CollapseShapeOp>, FoldCollapseOfCastOp>(
2345 context);
2346}
2347
2348OpFoldResult ExpandShapeOp::fold(FoldAdaptor adaptor) {
2350 adaptor.getOperands());
2351}
2352
2353OpFoldResult CollapseShapeOp::fold(FoldAdaptor adaptor) {
2355 adaptor.getOperands());
2356}
2357
2358//===----------------------------------------------------------------------===//
2359// ExtractSliceOp
2360//===----------------------------------------------------------------------===//
2361
2362void ExtractSliceOp::getAsmResultNames(
2363 function_ref<void(Value, StringRef)> setNameFn) {
2364 setNameFn(getResult(), "extracted_slice");
2365}
2366
2367/// An extract_slice result type can be inferred, when it is not
2368/// rank-reduced, from the source type and the static representation of
2369/// offsets, sizes and strides. Special sentinels encode the dynamic case.
2370RankedTensorType
2371ExtractSliceOp::inferResultType(RankedTensorType sourceTensorType,
2372 ArrayRef<int64_t> staticSizes) {
2373 // An extract_slice op may specify only a leading subset of offset/sizes/
2374 // strides in which case we complete with offset=0, sizes from memref type
2375 // and strides=1.
2376 assert(static_cast<int64_t>(staticSizes.size()) ==
2377 sourceTensorType.getRank() &&
2378 "unexpected staticSizes not equal to rank of source");
2379 return RankedTensorType::get(staticSizes, sourceTensorType.getElementType(),
2380 sourceTensorType.getEncoding());
2381}
2382
2383RankedTensorType
2384ExtractSliceOp::inferResultType(RankedTensorType sourceTensorType,
2385 ArrayRef<OpFoldResult> sizes) {
2386 SmallVector<int64_t> staticSizes;
2387 std::tie(staticSizes, std::ignore) = decomposeMixedValues(sizes);
2388
2389 assert(static_cast<int64_t>(staticSizes.size()) ==
2390 sourceTensorType.getRank() &&
2391 "unexpected staticSizes not equal to rank of source");
2392 return RankedTensorType::get(staticSizes, sourceTensorType.getElementType(),
2393 sourceTensorType.getEncoding());
2394}
2395
2396RankedTensorType
2397mlir::tensor::inferSliceType(RankedTensorType sourceTensorType,
2398 ArrayRef<int64_t> staticSizes,
2399 const llvm::SmallBitVector &droppedDims) {
2400 assert(staticSizes.size() == droppedDims.size() &&
2401 "expected one dropped-dimension bit per size");
2402
2403 SmallVector<int64_t> resultShape;
2404 resultShape.reserve(staticSizes.size() - droppedDims.count());
2405 for (auto [idx, size] : llvm::enumerate(staticSizes))
2406 if (!droppedDims.test(idx))
2407 resultShape.push_back(size);
2408
2409 Type elementType = sourceTensorType.getElementType();
2410 return RankedTensorType::get(resultShape, elementType,
2411 propagateEncoding(sourceTensorType.getEncoding(),
2412 resultShape, elementType));
2413}
2414
2415RankedTensorType
2416mlir::tensor::inferSliceType(RankedTensorType sourceTensorType,
2418 const llvm::SmallBitVector &droppedDims) {
2419 SmallVector<int64_t> staticSizes;
2420 std::tie(staticSizes, std::ignore) = decomposeMixedValues(sizes);
2421 return inferSliceType(sourceTensorType, staticSizes, droppedDims);
2422}
2423
2424/// Build an ExtractSliceOp with mixed static and dynamic entries and custom
2425/// result type. If the type passed is nullptr, it is inferred.
2426void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2427 RankedTensorType resultType, Value source,
2428 ArrayRef<OpFoldResult> offsets,
2430 ArrayRef<OpFoldResult> strides,
2432 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2433 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2434 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
2435 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2436 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2437 auto sourceRankedTensorType = llvm::cast<RankedTensorType>(source.getType());
2438 // Structuring implementation this way avoids duplication between builders.
2439 if (!resultType) {
2440 resultType = llvm::cast<RankedTensorType>(
2441 ExtractSliceOp::inferResultType(sourceRankedTensorType, staticSizes));
2442 }
2443 result.addAttributes(attrs);
2444 build(b, result, resultType, source, dynamicOffsets, dynamicSizes,
2445 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
2446 b.getDenseI64ArrayAttr(staticSizes),
2447 b.getDenseI64ArrayAttr(staticStrides));
2448}
2449
2450/// Build an ExtractSliceOp with mixed static and dynamic entries and inferred
2451/// result type.
2452void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2453 ArrayRef<OpFoldResult> offsets,
2454 ArrayRef<OpFoldResult> sizes,
2455 ArrayRef<OpFoldResult> strides,
2456 ArrayRef<NamedAttribute> attrs) {
2457 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2458}
2459
2460/// Build an ExtractSliceOp with mixed static and dynamic entries packed into
2461/// a Range vector.
2462void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2463 ArrayRef<Range> ranges,
2464 ArrayRef<NamedAttribute> attrs) {
2465 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
2466 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2467}
2468
2469/// Build an ExtractSliceOp with dynamic entries and custom result type. If
2470/// the type passed is nullptr, it is inferred.
2471void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2472 RankedTensorType resultType, Value source,
2473 ValueRange offsets, ValueRange sizes,
2474 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2475 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
2476 offsets, [](Value v) -> OpFoldResult { return v; });
2477 SmallVector<OpFoldResult> sizeValues =
2478 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
2479 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
2480 strides, [](Value v) -> OpFoldResult { return v; });
2481 build(b, result, resultType, source, offsetValues, sizeValues, strideValues);
2482}
2483
2484/// Build an ExtractSliceOp with dynamic entries and inferred result type.
2485void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2486 ValueRange offsets, ValueRange sizes,
2487 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2488 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2489}
2490
2492 Operation *op,
2493 RankedTensorType expectedType) {
2494 switch (result) {
2496 return success();
2498 return op->emitError("expected rank to be smaller or equal to ")
2499 << "the other rank. ";
2501 return op->emitError("expected type to be ")
2502 << expectedType << " or a rank-reduced version. (size mismatch) ";
2504 return op->emitError("expected element type to be ")
2505 << expectedType.getElementType();
2506 default:
2507 llvm_unreachable("unexpected extract_slice op verification result");
2508 }
2509}
2510
2511/// Build an ExtractSliceOp with mixed static and dynamic sizes, inferred
2512/// result type, offsets set to 0 and strides set to 1.
2513void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2514 RankedTensorType resultType, Value source,
2515 ArrayRef<OpFoldResult> sizes,
2516 ArrayRef<NamedAttribute> attrs) {
2517 Attribute zeroIdxAttr = b.getIndexAttr(0);
2518 Attribute oneIdxAttr = b.getIndexAttr(1);
2519 SmallVector<OpFoldResult> readStrides(sizes.size(), oneIdxAttr);
2520 SmallVector<OpFoldResult> readOffsets(sizes.size(), zeroIdxAttr);
2521 build(b, result, resultType, source, readOffsets, sizes, readStrides, attrs);
2522}
2523
2524/// Verifier for ExtractSliceOp.
2525LogicalResult ExtractSliceOp::verify() {
2526 RankedTensorType sourceType = getSourceType();
2527
2528 // Verify result type against inferred type.
2529 RankedTensorType expectedType =
2530 ExtractSliceOp::inferResultType(sourceType, getMixedSizes());
2533 return produceSliceErrorMsg(result, *this, expectedType);
2534
2535 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
2536 // to the source tensor.
2537 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
2538 sourceType.getShape(), getStaticOffsets(), getStaticSizes(),
2539 getStaticStrides(), /*generateErrorMessage=*/true);
2540 if (!boundsResult.isValid)
2541 return getOperation()->emitError(boundsResult.errorMessage);
2542
2543 return success();
2544}
2545
2546llvm::SmallBitVector ExtractSliceOp::getDroppedDims() {
2547 return ::getDroppedDims(getType().getShape(), getMixedSizes());
2548}
2549
2550FailureOr<Value>
2551ExtractSliceOp::rankReduceIfNeeded(OpBuilder &b, Location loc, Value value,
2552 ArrayRef<int64_t> desiredShape) {
2553 auto sourceTensorType = llvm::dyn_cast<RankedTensorType>(value.getType());
2554 assert(sourceTensorType && "not a ranked tensor type");
2555 auto sourceShape = sourceTensorType.getShape();
2556 if (sourceShape.equals(desiredShape))
2557 return value;
2558 auto maybeRankReductionMask =
2559 mlir::computeRankReductionMask(sourceShape, desiredShape);
2560 if (!maybeRankReductionMask)
2561 return failure();
2563 b, loc, value,
2564 RankedTensorType::Builder(sourceTensorType).setShape(desiredShape));
2565}
2566
2567LogicalResult ExtractSliceOp::reifyResultShapes(
2568 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
2569 reifiedReturnShapes.resize(1);
2570 reifiedReturnShapes[0].reserve(getType().getRank());
2571 SmallVector<OpFoldResult> mixedSizes = getMixedSizes();
2572 llvm::SmallBitVector droppedDims = getDroppedDims();
2573 for (const auto &size : enumerate(mixedSizes)) {
2574 if (droppedDims.test(size.index()))
2575 continue;
2576 reifiedReturnShapes[0].push_back(size.value());
2577 }
2578 return success();
2579}
2580
2581namespace {
2582/// Pattern to rewrite an extract_slice op with tensor::Cast arguments.
2583/// This essentially pushes memref_cast past its consuming slice when
2584/// `canFoldIntoConsumerOp` is true.
2585///
2586/// Example:
2587/// ```
2588/// %0 = tensor.cast %V : tensor<16x16xf32> to tensor<?x?xf32>
2589/// %1 = tensor.extract_slice %0[0, 0][3, 4][1, 1] : tensor<?x?xf32> to
2590/// tensor<3x4xf32>
2591/// ```
2592/// is rewritten into:
2593/// ```
2594/// %0 = tensor.extract_slice %V[0, 0][3, 4][1, 1] : tensor<16x16xf32> to
2595/// tensor<3x4xf32> %1 = tensor.cast %0: tensor<3x4xf32> to tensor<3x4xf32>
2596/// ```
2597class ExtractSliceOpCastFolder final : public OpRewritePattern<ExtractSliceOp> {
2598public:
2599 using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
2600
2601 LogicalResult matchAndRewrite(ExtractSliceOp sliceOp,
2602 PatternRewriter &rewriter) const override {
2603 // Any constant operand, just return to let the constant folder kick in.
2604 if (llvm::any_of(sliceOp.getOperands(), [](Value operand) {
2605 return matchPattern(operand, matchConstantIndex());
2606 }))
2607 return failure();
2608
2609 auto castOp = sliceOp.getSource().getDefiningOp<CastOp>();
2610 if (!castOp)
2611 return failure();
2612
2613 if (!canFoldIntoConsumerOp(castOp))
2614 return failure();
2615
2616 // Pattern does not apply if the produced op would not verify.
2617 SliceBoundsVerificationResult sliceResult = verifyInBoundsSlice(
2618 cast<RankedTensorType>(castOp.getSource().getType()).getShape(),
2619 sliceOp.getStaticOffsets(), sliceOp.getStaticSizes(),
2620 sliceOp.getStaticStrides());
2621 if (!sliceResult.isValid)
2622 return failure();
2623
2624 // Create folded extract.
2625 Location loc = sliceOp.getLoc();
2626 Value newResult = ExtractSliceOp::create(
2627 rewriter, loc, sliceOp.getType(), castOp.getSource(),
2628 sliceOp.getOffsets(), sliceOp.getSizes(), sliceOp.getStrides(),
2629 sliceOp.getStaticOffsets(), sliceOp.getStaticSizes(),
2630 sliceOp.getStaticStrides());
2631 rewriter.replaceOp(sliceOp, newResult);
2632 return success();
2633 }
2634};
2635
2636/// Slice elements from `values` into `outValues`. `counts` represents the
2637/// numbers of elements to stride in the original values for each dimension.
2638/// The output values can be used to construct a DenseElementsAttr.
2639template <typename IterTy, typename ElemTy>
2640static void sliceElements(IterTy values, ArrayRef<int64_t> counts,
2641 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes,
2642 ArrayRef<int64_t> strides,
2643 llvm::SmallVectorImpl<ElemTy> *outValues) {
2644 assert(offsets.size() == sizes.size());
2645 assert(offsets.size() == strides.size());
2646 if (offsets.empty())
2647 return;
2648
2649 int64_t offset = offsets.front();
2650 int64_t size = sizes.front();
2651 int64_t stride = strides.front();
2652 if (offsets.size() == 1) {
2653 for (int64_t i = 0; i < size; ++i, offset += stride)
2654 outValues->push_back(*(values + offset));
2655
2656 return;
2657 }
2658
2659 for (int64_t i = 0; i < size; ++i, offset += stride) {
2660 auto begin = values + offset * counts.front();
2661 sliceElements<IterTy, ElemTy>(begin, counts.drop_front(),
2662 offsets.drop_front(), sizes.drop_front(),
2663 strides.drop_front(), outValues);
2664 }
2665}
2666
2667/// Fold arith.constant and tensor.extract_slice into arith.constant. The
2668/// folded operation might introduce more constant data; Users can control
2669/// their heuristics by the control function.
2670class ConstantOpExtractSliceFolder final
2671 : public OpRewritePattern<ExtractSliceOp> {
2672public:
2673 using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
2674
2675 ConstantOpExtractSliceFolder(MLIRContext *context,
2677 : OpRewritePattern<ExtractSliceOp>(context),
2678 controlFn(std::move(controlFn)) {}
2679
2680 LogicalResult matchAndRewrite(ExtractSliceOp op,
2681 PatternRewriter &rewriter) const override {
2682 DenseElementsAttr attr;
2683 if (!matchPattern(op.getSource(), m_Constant(&attr)))
2684 return failure();
2685
2686 // A constant splat is handled by fold().
2687 if (attr.isSplat())
2688 return failure();
2689
2690 // Dynamic result shape is not supported.
2691 auto sourceType = llvm::cast<ShapedType>(op.getSource().getType());
2692 auto resultType = llvm::cast<ShapedType>(op.getResult().getType());
2693 if (!sourceType.hasStaticShape() || !resultType.hasStaticShape())
2694 return failure();
2695
2696 // Customized control over the folding.
2697 if (!controlFn(op))
2698 return failure();
2699
2700 int64_t count = sourceType.getNumElements();
2701 if (count == 0)
2702 return failure();
2703
2704 // Check if there are any dynamic parts, which are not supported.
2705 auto offsets = op.getStaticOffsets();
2706 if (llvm::is_contained(offsets, ShapedType::kDynamic))
2707 return failure();
2708 auto sizes = op.getStaticSizes();
2709 if (llvm::is_contained(sizes, ShapedType::kDynamic))
2710 return failure();
2711 auto strides = op.getStaticStrides();
2712 if (llvm::is_contained(strides, ShapedType::kDynamic))
2713 return failure();
2714
2715 // Compute the stride for each dimension.
2716 SmallVector<int64_t> counts;
2717 ArrayRef<int64_t> shape = sourceType.getShape();
2718 counts.reserve(shape.size());
2719 for (int64_t v : shape) {
2720 count = count / v;
2721 counts.push_back(count);
2722 }
2723
2724 // Slice the elements and construct a new attribute.
2725 SmallVector<Attribute> outValues;
2726 outValues.reserve(resultType.getNumElements());
2727 sliceElements(attr.value_begin<Attribute>(), counts, offsets, sizes,
2728 strides, &outValues);
2729 auto newAttr = DenseElementsAttr::get(resultType, outValues);
2730 rewriter.replaceOpWithNewOp<arith::ConstantOp>(op, resultType, newAttr);
2731 return success();
2732 }
2733
2734private:
2735 /// This additionally controls whether the fold happens or not. Users can
2736 /// impose their heuristics in the function.
2738};
2739
2740} // namespace
2741
2743 RewritePatternSet &patterns,
2744 const ControlConstantExtractSliceFusionFn &controlFn) {
2745 patterns.add<ConstantOpExtractSliceFolder>(patterns.getContext(), controlFn);
2746}
2747
2748/// Return the canonical type of the result of an extract_slice op.
2749/// Note: offsets and strides are not needed to determine the result type of
2750/// an extract_slice. The operator arguments are just there for interface
2751/// compatibility.
2753 RankedTensorType operator()(ExtractSliceOp op,
2754 ArrayRef<OpFoldResult> mixedOffsets,
2755 ArrayRef<OpFoldResult> mixedSizes,
2756 ArrayRef<OpFoldResult> mixedStrides) {
2757 return inferSliceType(op.getSourceType(), mixedSizes, op.getDroppedDims());
2758 }
2759};
2760
2761/// A canonicalizer wrapper to replace ExtractSliceOps.
2763 void operator()(PatternRewriter &rewriter, ExtractSliceOp op,
2764 ExtractSliceOp newOp) {
2765 Value replacement = newOp.getResult();
2766 if (replacement.getType() != op.getType())
2767 replacement = tensor::CastOp::create(rewriter, op.getLoc(), op.getType(),
2768 replacement);
2769 rewriter.replaceOp(op, replacement);
2770 }
2771};
2772
2773void ExtractSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
2774 MLIRContext *context) {
2775 results.add<
2776 OpWithOffsetSizesAndStridesConstantArgumentFolder<
2777 ExtractSliceOp, SliceReturnTypeCanonicalizer, SliceCanonicalizer>,
2778 ExtractSliceOpCastFolder>(context);
2779}
2780
2781//
2782static LogicalResult
2783foldIdentityOffsetSizeAndStrideOpInterface(OffsetSizeAndStrideOpInterface op,
2784 ShapedType shapedType) {
2785 OpBuilder b(op.getContext());
2786 for (OpFoldResult ofr : op.getMixedOffsets())
2787 if (getConstantIntValue(ofr) != static_cast<int64_t>(0))
2788 return failure();
2789 // Rank-reducing noops only need to inspect the leading dimensions:
2790 // llvm::zip is appropriate.
2791 auto shape = shapedType.getShape();
2792 for (auto it : llvm::zip(op.getMixedSizes(), shape))
2793 if (getConstantIntValue(std::get<0>(it)) != std::get<1>(it))
2794 return failure();
2795 for (OpFoldResult ofr : op.getMixedStrides())
2796 if (getConstantIntValue(ofr) != static_cast<int64_t>(1))
2797 return failure();
2798 return success();
2799}
2800
2801/// If we have an ExtractSliceOp consuming an InsertSliceOp with the same
2802/// slice, we can return the InsertSliceOp's source directly.
2803// TODO: This only checks the immediate producer; extend to go up the
2804// insert/extract chain if the slices are disjoint.
2805static Value foldExtractAfterInsertSlice(ExtractSliceOp extractOp) {
2806 auto insertOp = extractOp.getSource().getDefiningOp<InsertSliceOp>();
2807
2808 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2809 if (insertOp && insertOp.getSource().getType() == extractOp.getType() &&
2810 insertOp.isSameAs(extractOp, isSame))
2811 return insertOp.getSource();
2812
2813 return {};
2814}
2815
2816OpFoldResult ExtractSliceOp::fold(FoldAdaptor adaptor) {
2817 if (OpFoldResult reshapedSource = reshapeConstantSource(
2818 llvm::dyn_cast_if_present<SplatElementsAttr>(adaptor.getSource()),
2819 getResult().getType()))
2820 return reshapedSource;
2821 if (getSourceType() == getType() &&
2823 return this->getSource();
2824 if (Value slice = foldExtractAfterInsertSlice(*this))
2825 return slice;
2826
2827 return OpFoldResult();
2828}
2829
2831 OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType) {
2832 auto rankedTensorType = llvm::cast<RankedTensorType>(tensor.getType());
2833 unsigned rank = rankedTensorType.getRank();
2834 SmallVector<OpFoldResult> offsets(rank, b.getIndexAttr(0));
2836 SmallVector<OpFoldResult> strides(rank, b.getIndexAttr(1));
2837 return b.createOrFold<tensor::ExtractSliceOp>(loc, targetType, tensor,
2838 offsets, sizes, strides);
2839}
2840
2841//===----------------------------------------------------------------------===//
2842// InsertSliceOp
2843//===----------------------------------------------------------------------===//
2844
2845void InsertSliceOp::getAsmResultNames(
2846 function_ref<void(Value, StringRef)> setNameFn) {
2847 setNameFn(getResult(), "inserted_slice");
2848}
2849
2850// Build a InsertSliceOp with mixed static and dynamic entries.
2851void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2852 Value dest, ArrayRef<OpFoldResult> offsets,
2854 ArrayRef<OpFoldResult> strides,
2856 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2857 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2858 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
2859 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2860 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2861 result.addAttributes(attrs);
2862 build(b, result, dest.getType(), source, dest, dynamicOffsets, dynamicSizes,
2863 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
2864 b.getDenseI64ArrayAttr(staticSizes),
2865 b.getDenseI64ArrayAttr(staticStrides));
2866}
2867
2868/// Build an InsertSliceOp with mixed static and dynamic entries packed into a
2869/// Range vector.
2870void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2871 Value dest, ArrayRef<Range> ranges,
2872 ArrayRef<NamedAttribute> attrs) {
2873 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
2874 build(b, result, source, dest, offsets, sizes, strides, attrs);
2875}
2876
2877// Build a InsertSliceOp with dynamic entries.
2878void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2879 Value dest, ValueRange offsets, ValueRange sizes,
2880 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2881 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
2882 offsets, [](Value v) -> OpFoldResult { return v; });
2883 SmallVector<OpFoldResult> sizeValues =
2884 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
2885 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
2886 strides, [](Value v) -> OpFoldResult { return v; });
2887 build(b, result, source, dest, offsetValues, sizeValues, strideValues);
2888}
2889
2890/// Rank-reducing type verification for both InsertSliceOp and
2891/// ParallelInsertSliceOp.
2893 RankedTensorType srcType, RankedTensorType dstType,
2894 ArrayRef<int64_t> staticOffsets, ArrayRef<int64_t> staticSizes,
2895 ArrayRef<int64_t> staticStrides, RankedTensorType *expectedType = nullptr) {
2896 // insert_slice is the inverse of extract_slice, use the same type
2897 // inference.
2898 RankedTensorType expected =
2899 ExtractSliceOp::inferResultType(dstType, staticSizes);
2900 if (expectedType)
2901 *expectedType = expected;
2902 return isRankReducedType(expected, srcType);
2903}
2904
2905/// Verifier for InsertSliceOp.
2906LogicalResult InsertSliceOp::verify() {
2907 // Verify result type against inferred type.
2908 RankedTensorType expectedType;
2910 verifyInsertSliceOp(getSourceType(), getType(), getStaticOffsets(),
2911 getStaticSizes(), getStaticStrides(), &expectedType);
2913 return produceSliceErrorMsg(result, *this, expectedType);
2914
2915 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
2916 // to the destination tensor.
2917 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
2918 getDestType().getShape(), getStaticOffsets(), getStaticSizes(),
2919 getStaticStrides(), /*generateErrorMessage=*/true);
2920 if (!boundsResult.isValid)
2921 return getOperation()->emitError(boundsResult.errorMessage);
2922
2923 return success();
2924}
2925
2926/// If we have two consecutive InsertSliceOp writing to the same slice, we
2927/// can mutate the second InsertSliceOp's destination to the first one's.
2928///
2929/// Example:
2930///
2931/// ```mlir
2932/// %0 = tensor.insert_slice %slice0 into %input[0, 0] [64, 64] [1, 1]
2933/// %1 = tensor.insert_slice %slice1 into %0[0, 0] [64, 64] [1, 1]
2934/// ```
2935///
2936/// folds into:
2937///
2938/// ```mlir
2939/// %1 = tensor.insert_slice %slice1 into %input[0, 0] [64, 64] [1, 1]
2940/// ```
2941///
2942/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
2943static LogicalResult foldInsertAfterInsertSlice(InsertSliceOp insertOp) {
2944 auto prevInsertOp = insertOp.getDest().getDefiningOp<InsertSliceOp>();
2945
2946 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2947 if (!prevInsertOp ||
2948 prevInsertOp.getSource().getType() != insertOp.getSource().getType() ||
2949 !prevInsertOp.isSameAs(insertOp, isSame))
2950 return failure();
2951
2952 insertOp.getDestMutable().assign(prevInsertOp.getDest());
2953 return success();
2954}
2955
2956/// Folds round-trip extract/insert slice op pairs.
2957/// Example:
2958/// ```mlir
2959/// %0 = tensor.extract_slice %val[0, 0, 0, 0] [1, 1, 2, 4] [1, 1, 1, 1]
2960/// %1 = tensor.insert_slice %0 into %val[0, 0, 0, 0] [1, 1, 2, 4] [1, 1, 1, 1]
2961/// ```
2962/// can be folded into %val.
2963static Value foldInsertAfterExtractSlice(InsertSliceOp insertOp) {
2964 auto extractOp = insertOp.getSource().getDefiningOp<ExtractSliceOp>();
2965
2966 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2967 if (!extractOp || extractOp.getSource() != insertOp.getDest() ||
2968 !extractOp.isSameAs(insertOp, isSame))
2969 return nullptr;
2970
2971 return extractOp.getSource();
2972}
2973
2974OpFoldResult InsertSliceOp::fold(FoldAdaptor) {
2975 if (getSourceType().hasStaticShape() && getType().hasStaticShape() &&
2976 getSourceType() == getType() &&
2978 return this->getSource();
2979 if (succeeded(foldInsertAfterInsertSlice(*this)))
2980 return getResult();
2981 if (auto result = foldInsertAfterExtractSlice(*this))
2982 return result;
2983 if (llvm::any_of(getMixedSizes(), isZeroInteger))
2984 return getDest();
2985 return OpFoldResult();
2986}
2987
2988LogicalResult InsertSliceOp::reifyResultShapes(
2989 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
2990 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
2991 reifiedReturnShapes[0] = tensor::getMixedSizes(builder, getLoc(), getDest());
2992 return success();
2993}
2994
2995namespace {
2996/// Pattern to rewrite a insert_slice op with constant arguments.
2997///
2998/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
2999template <typename InsertOpTy>
3000class InsertSliceOpConstantArgumentFolder final
3001 : public OpRewritePattern<InsertOpTy> {
3002public:
3003 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3004
3005 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3006 PatternRewriter &rewriter) const override {
3007 SmallVector<OpFoldResult> mixedOffsets(insertSliceOp.getMixedOffsets());
3008 SmallVector<OpFoldResult> mixedSizes(insertSliceOp.getMixedSizes());
3009 SmallVector<OpFoldResult> mixedStrides(insertSliceOp.getMixedStrides());
3010
3011 // No constant operands were folded, just return;
3012 if (failed(foldDynamicOffsetSizeList(mixedOffsets)) &&
3013 failed(foldDynamicOffsetSizeList(mixedSizes)) &&
3014 failed(foldDynamicStrideList(mixedStrides)))
3015 return failure();
3016
3017 // Pattern does not apply if the produced op would not verify.
3018 SliceBoundsVerificationResult sliceResult =
3019 verifyInBoundsSlice(insertSliceOp.getDest().getType().getShape(),
3020 mixedOffsets, mixedSizes, mixedStrides);
3021 if (!sliceResult.isValid)
3022 return failure();
3023
3024 auto sourceType = inferSliceType(insertSliceOp.getSourceType(), mixedSizes,
3025 insertSliceOp.getDroppedDims());
3026 Value toInsert = insertSliceOp.getSource();
3027 if (sourceType != insertSliceOp.getSourceType()) {
3028 OpBuilder::InsertionGuard g(rewriter);
3029 // The only difference between InsertSliceOp and ParallelInsertSliceOp
3030 // is that the insertion point is just before the InParallelOp in
3031 // the parallel case.
3032 if (isa<InParallelOpInterface>(insertSliceOp->getParentOp()))
3033 rewriter.setInsertionPoint(insertSliceOp->getParentOp());
3034 toInsert = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3035 sourceType, toInsert);
3036 }
3037 rewriter.replaceOpWithNewOp<InsertOpTy>(
3038 insertSliceOp, toInsert, insertSliceOp.getDest(), mixedOffsets,
3039 mixedSizes, mixedStrides);
3040 return success();
3041 }
3042};
3043
3044/// Fold tensor_casts with insert_slice operations. If the source or
3045/// destination tensor is a tensor_cast that removes static type information,
3046/// the cast is folded into the insert_slice operation. E.g.:
3047///
3048/// ```mlir
3049/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
3050/// %2 = tensor.insert_slice %1 into ... : tensor<?x?xf32> into ...
3051/// ```
3052///
3053/// folds into:
3054///
3055/// ```mlir
3056/// %2 = tensor.insert_slice %0 into ... : tensor<8x16xf32> into ...
3057/// ```
3058///
3059/// Note: When folding a cast on the destination tensor, the result of the
3060/// insert_slice operation is casted to ensure that the type of the result did
3061/// not change.
3062///
3063/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
3064template <typename InsertOpTy>
3065struct InsertSliceOpCastFolder final : public OpRewritePattern<InsertOpTy> {
3066 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3067
3068 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3069 PatternRewriter &rewriter) const override {
3070 if (llvm::any_of(insertSliceOp.getOperands(), [](Value operand) {
3071 return matchPattern(operand, matchConstantIndex());
3072 }))
3073 return failure();
3074
3075 auto getSourceOfCastOp = [](Value v) -> std::optional<Value> {
3076 auto castOp = v.getDefiningOp<tensor::CastOp>();
3077 if (!castOp || !canFoldIntoConsumerOp(castOp))
3078 return std::nullopt;
3079 return castOp.getSource();
3080 };
3081 std::optional<Value> sourceCastSource =
3082 getSourceOfCastOp(insertSliceOp.getSource());
3083 std::optional<Value> destCastSource =
3084 getSourceOfCastOp(insertSliceOp.getDest());
3085 if (!sourceCastSource && !destCastSource)
3086 return failure();
3087
3088 auto src =
3089 (sourceCastSource ? *sourceCastSource : insertSliceOp.getSource());
3090 auto dst = (destCastSource ? *destCastSource : insertSliceOp.getDest());
3091 auto srcType = llvm::dyn_cast<RankedTensorType>(src.getType());
3092 auto dstType = llvm::dyn_cast<RankedTensorType>(dst.getType());
3093 if (!srcType || !dstType)
3094 return failure();
3095
3096 // The tensor.cast source could have additional static information not seen
3097 // in the insert slice op static sizes, so we ignore dynamic dims when
3098 // computing the rank reduction mask.
3099 SmallVector<int64_t> staticSizes(insertSliceOp.getStaticSizes());
3100 auto rankReductionMask = computeRankReductionMask(
3101 staticSizes, srcType.getShape(), /*matchDynamic=*/true);
3102 if (!rankReductionMask.has_value())
3103 return failure();
3104 // Replace dimensions in the insert slice op with corresponding static dims
3105 // from the cast source type. If the insert slice sizes have static dims
3106 // that are not static in the tensor.cast source (i.e., when the cast op
3107 // casts a dynamic dim to static), the dim should not be replaced, and the
3108 // pattern will fail later in `verifyInsertSliceOp`.
3109 SmallVector<OpFoldResult> mixedSizes(insertSliceOp.getMixedSizes());
3110 int64_t rankReducedIdx = 0;
3111 for (auto [idx, size] : enumerate(staticSizes)) {
3112 if (!rankReductionMask.value().contains(idx) &&
3113 !srcType.isDynamicDim(rankReducedIdx)) {
3114 mixedSizes[idx] = getAsIndexOpFoldResult(
3115 rewriter.getContext(), srcType.getDimSize(rankReducedIdx));
3116 size = srcType.getDimSize(rankReducedIdx++);
3117 }
3118 }
3119
3120 // Pattern does not apply if the produced op would not verify.
3121 if (verifyInsertSliceOp(srcType, dstType, insertSliceOp.getStaticOffsets(),
3122 staticSizes, insertSliceOp.getStaticStrides()) !=
3123 SliceVerificationResult::Success)
3124 return failure();
3125 SliceBoundsVerificationResult sliceResult =
3126 verifyInBoundsSlice(dstType.getShape(), insertSliceOp.getMixedOffsets(),
3127 mixedSizes, insertSliceOp.getMixedStrides());
3128 if (!sliceResult.isValid)
3129 return failure();
3130
3131 Operation *replacement =
3132 InsertOpTy::create(rewriter, insertSliceOp.getLoc(), src, dst,
3133 insertSliceOp.getMixedOffsets(), mixedSizes,
3134 insertSliceOp.getMixedStrides());
3135
3136 // In the parallel case there is no result and so nothing to cast.
3137 bool isParallelInsert =
3138 std::is_same<InsertOpTy, ParallelInsertSliceOp>::value;
3139 if (!isParallelInsert && dst.getType() != insertSliceOp.getDestType()) {
3140 replacement = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3141 insertSliceOp.getDestType(),
3142 replacement->getResult(0));
3143 }
3144 rewriter.replaceOp(insertSliceOp, replacement->getResults());
3145 return success();
3146 }
3147};
3148
3149/// If additional static type information can be deduced from a insert_slice's
3150/// size operands, insert an explicit cast of the op's source operand. This
3151/// enables other canonicalization patterns that are matching for tensor_cast
3152/// ops such as `ForOpTensorCastFolder` in SCF.
3153///
3154/// Example:
3155///
3156/// ```mlir
3157/// %r = tensor.insert_slice %0 into %1[...] [64, 64] [1, 1]
3158/// : tensor<?x?xf32> into ...
3159/// ```
3160///
3161/// folds into:
3162///
3163/// ```mlir
3164/// %tmp = tensor.cast %0 : tensor<?x?xf32> to tensor<64x64xf32>
3165/// %r = tensor.insert_slice %tmp into %1[...] [64, 64] [1, 1]
3166/// : tensor<64x64xf32> into ...
3167/// ```
3168///
3169/// This patterns works with both InsertSliceOp and ParallelInsertSliceOp.
3170template <typename InsertOpTy>
3171struct InsertSliceOpSourceCastInserter final
3172 : public OpRewritePattern<InsertOpTy> {
3173 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3174
3175 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3176 PatternRewriter &rewriter) const override {
3177 RankedTensorType srcType = insertSliceOp.getSourceType();
3178 if (srcType.getRank() != insertSliceOp.getDestType().getRank())
3179 return failure();
3180 SmallVector<int64_t> newSrcShape(srcType.getShape());
3181 for (int64_t i = 0; i < srcType.getRank(); ++i) {
3182 if (std::optional<int64_t> constInt =
3183 getConstantIntValue(insertSliceOp.getMixedSizes()[i])) {
3184 // Bail on invalid IR.
3185 if (*constInt < 0)
3186 return failure();
3187 newSrcShape[i] = *constInt;
3188 }
3189 }
3190 if (!hasValidSizesOffsets(newSrcShape))
3191 return failure();
3192
3193 RankedTensorType newSrcType = RankedTensorType::get(
3194 newSrcShape, srcType.getElementType(), srcType.getEncoding());
3195 if (srcType == newSrcType ||
3196 !preservesStaticInformation(srcType, newSrcType) ||
3197 !tensor::CastOp::areCastCompatible(srcType, newSrcType))
3198 return failure();
3199
3200 // newSrcType is:
3201 // 1) Different from srcType.
3202 // 2) "More static" than srcType.
3203 // 3) Cast-compatible with srcType.
3204 // Insert the cast.
3205 OpBuilder::InsertionGuard g(rewriter);
3206 // The only difference between InsertSliceOp and ParallelInsertSliceOp is
3207 // that the insertion point is just before the InParallelOp in the
3208 // parallel case.
3209 if (isa<ParallelCombiningOpInterface>(insertSliceOp->getParentOp()))
3210 rewriter.setInsertionPoint(insertSliceOp->getParentOp());
3211 Value cast = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3212 newSrcType, insertSliceOp.getSource());
3213 rewriter.replaceOpWithNewOp<InsertOpTy>(
3214 insertSliceOp, cast, insertSliceOp.getDest(),
3215 insertSliceOp.getMixedOffsets(), insertSliceOp.getMixedSizes(),
3216 insertSliceOp.getMixedStrides());
3217 return success();
3218 }
3219};
3220} // namespace
3221
3222llvm::SmallBitVector InsertSliceOp::getDroppedDims() {
3223 return ::getDroppedDims(getSourceType().getShape(), getMixedSizes());
3224}
3225
3226void InsertSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
3227 MLIRContext *context) {
3228 results.add<InsertSliceOpConstantArgumentFolder<InsertSliceOp>,
3229 InsertSliceOpCastFolder<InsertSliceOp>,
3230 InsertSliceOpSourceCastInserter<InsertSliceOp>>(context);
3231}
3232
3234 Location loc,
3235 Value tensor,
3236 Value dest) {
3237 auto rankedTensorType = llvm::cast<RankedTensorType>(dest.getType());
3238 unsigned rank = rankedTensorType.getRank();
3239 SmallVector<OpFoldResult> offsets(rank, b.getIndexAttr(0));
3240 SmallVector<OpFoldResult> sizes = getMixedSizes(b, loc, dest);
3241 SmallVector<OpFoldResult> strides(rank, b.getIndexAttr(1));
3242 return b.createOrFold<tensor::InsertSliceOp>(loc, tensor, dest, offsets,
3243 sizes, strides);
3244}
3245
3246//===----------------------------------------------------------------------===//
3247// PadOp
3248//===----------------------------------------------------------------------===//
3249
3250void PadOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
3251 setNameFn(getResult(), "padded");
3252}
3253
3254LogicalResult PadOp::verify() {
3255 auto sourceType = llvm::cast<RankedTensorType>(getSource().getType());
3256 auto resultType = llvm::cast<RankedTensorType>(getResult().getType());
3257 auto expectedType =
3258 PadOp::inferResultType(sourceType, getStaticLow(), getStaticHigh());
3259 if (!expectedType) {
3260 return emitError("failed to infer expectedType from sourceType ")
3261 << sourceType << ", specified resultType is " << resultType;
3262 }
3263 if (resultType.getRank() != expectedType.getRank()) {
3264 return emitError("specified type ")
3265 << resultType << " does not match the inferred type "
3266 << expectedType;
3267 }
3268 for (int i = 0, e = sourceType.getRank(); i < e; ++i) {
3269 if (resultType.getDimSize(i) == expectedType.getDimSize(i))
3270 continue;
3271 if (expectedType.isDynamicDim(i))
3272 continue;
3273 return emitError("specified type ")
3274 << resultType << " does not match the inferred type "
3275 << expectedType;
3276 }
3277
3278 return success();
3279}
3280
3281LogicalResult PadOp::verifyRegions() {
3282 auto &region = getRegion();
3283 unsigned rank = llvm::cast<RankedTensorType>(getResult().getType()).getRank();
3284 Block &block = region.front();
3285 if (block.getNumArguments() != rank)
3286 return emitError("expected the block to have ") << rank << " arguments";
3287
3288 // Note: the number and type of yield values are checked in the YieldOp.
3289 for (const auto &en : llvm::enumerate(block.getArgumentTypes())) {
3290 if (!en.value().isIndex())
3291 return emitOpError("expected block argument ")
3292 << (en.index() + 1) << " to be an index";
3293 }
3294
3295 // Ensure that the region yields an element of the right type.
3296 auto yieldOp = llvm::cast<YieldOp>(block.getTerminator());
3297 if (yieldOp.getValue().getType() !=
3298 llvm::cast<ShapedType>(getType()).getElementType())
3299 return emitOpError("expected yield type to match shape element type");
3300
3301 return success();
3302}
3303
3304RankedTensorType PadOp::inferResultType(RankedTensorType sourceType,
3305 ArrayRef<int64_t> staticLow,
3306 ArrayRef<int64_t> staticHigh,
3307 ArrayRef<int64_t> resultShape) {
3308 unsigned rank = sourceType.getRank();
3309 if (staticLow.size() != rank)
3310 return RankedTensorType();
3311 if (staticHigh.size() != rank)
3312 return RankedTensorType();
3313 if (!resultShape.empty() && resultShape.size() != rank)
3314 return RankedTensorType();
3315
3316 SmallVector<int64_t, 4> inferredShape;
3317 for (auto i : llvm::seq<unsigned>(0, rank)) {
3318 if (sourceType.isDynamicDim(i) || staticLow[i] == ShapedType::kDynamic ||
3319 staticHigh[i] == ShapedType::kDynamic) {
3320 inferredShape.push_back(resultShape.empty() ? ShapedType::kDynamic
3321 : resultShape[i]);
3322 } else {
3323 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i];
3324 assert((resultShape.empty() || size == resultShape[i] ||
3325 resultShape[i] == ShapedType::kDynamic) &&
3326 "mismatch between inferred shape and result shape");
3327 inferredShape.push_back(size);
3328 }
3329 }
3330
3331 Type elementType = sourceType.getElementType();
3332 return RankedTensorType::get(
3333 inferredShape, elementType,
3334 propagateEncoding(sourceType.getEncoding(), inferredShape, elementType));
3335}
3336
3337void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3338 Value source, ArrayRef<int64_t> staticLow,
3339 ArrayRef<int64_t> staticHigh, ValueRange low, ValueRange high,
3340 bool nofold, ArrayRef<NamedAttribute> attrs) {
3341 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3342 if (!resultType)
3343 resultType = inferResultType(sourceType, staticLow, staticHigh);
3344 result.addAttributes(attrs);
3345 build(b, result, resultType, source, low, high,
3346 b.getDenseI64ArrayAttr(staticLow), b.getDenseI64ArrayAttr(staticHigh),
3347 nofold ? b.getUnitAttr() : UnitAttr());
3348}
3349
3350void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3351 Value source, ValueRange low, ValueRange high, bool nofold,
3352 ArrayRef<NamedAttribute> attrs) {
3353 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3354 unsigned rank = sourceType.getRank();
3355 SmallVector<int64_t, 4> staticVector(rank, ShapedType::kDynamic);
3356 build(b, result, resultType, source, staticVector, staticVector, low, high,
3357 nofold, attrs);
3358}
3359
3360void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3361 Value source, ArrayRef<OpFoldResult> low,
3362 ArrayRef<OpFoldResult> high, bool nofold,
3363 ArrayRef<NamedAttribute> attrs) {
3364 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3365 SmallVector<Value, 4> dynamicLow, dynamicHigh;
3366 SmallVector<int64_t, 4> staticLow, staticHigh;
3367 // staticLow and staticHigh have full information of the padding config.
3368 // This will grow staticLow and staticHigh with 1 value. If the config is
3369 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1
3370 // value as well.
3371 dispatchIndexOpFoldResults(low, dynamicLow, staticLow);
3372 dispatchIndexOpFoldResults(high, dynamicHigh, staticHigh);
3373 if (!resultType) {
3374 resultType = PadOp::inferResultType(sourceType, staticLow, staticHigh);
3375 }
3376 assert(llvm::isa<RankedTensorType>(resultType));
3377 result.addAttributes(attrs);
3378 build(b, result, resultType, source, dynamicLow, dynamicHigh,
3379 b.getDenseI64ArrayAttr(staticLow), b.getDenseI64ArrayAttr(staticHigh),
3380 nofold ? b.getUnitAttr() : UnitAttr());
3381}
3382
3383void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3384 Value source, ArrayRef<OpFoldResult> low,
3385 ArrayRef<OpFoldResult> high, Value constantPadValue,
3386 bool nofold, ArrayRef<NamedAttribute> attrs) {
3387 build(b, result, resultType, source, low, high, nofold, attrs);
3388
3389 // Add a region and a block to yield the pad value.
3390 Region *region = result.regions[0].get();
3391 int sourceRank = llvm::cast<RankedTensorType>(source.getType()).getRank();
3392 Repeated<Type> blockArgTypes(sourceRank, b.getIndexType());
3393 SmallVector<Location> blockArgLocs(sourceRank, result.location);
3394
3395 // `builder.createBlock` changes the insertion point within the block. Create
3396 // a guard to reset the insertion point of the builder after it is destroyed.
3397 OpBuilder::InsertionGuard guard(b);
3398 b.createBlock(region, region->end(), blockArgTypes, blockArgLocs);
3399 tensor::YieldOp::create(b, result.location, constantPadValue);
3400}
3401
3402llvm::SmallBitVector PadOp::getPaddedDims() {
3403 llvm::SmallBitVector paddedDims(getSourceType().getRank());
3404 auto extractPaddedDims = [&](ArrayRef<OpFoldResult> paddingWidths) {
3405 for (const auto &en : enumerate(paddingWidths))
3406 if (getConstantIntValue(en.value()) != static_cast<int64_t>(0))
3407 paddedDims.set(en.index());
3408 };
3409 extractPaddedDims(getMixedLowPad());
3410 extractPaddedDims(getMixedHighPad());
3411 return paddedDims;
3412}
3413
3414namespace {
3415// Folds tensor.pad when padding is static zeros and the attribute
3416// doesn't request otherwise.
3417struct FoldStaticZeroPadding : public OpRewritePattern<PadOp> {
3418 using OpRewritePattern<PadOp>::OpRewritePattern;
3419
3420 LogicalResult matchAndRewrite(PadOp padTensorOp,
3421 PatternRewriter &rewriter) const override {
3422 if (!padTensorOp.hasZeroLowPad() || !padTensorOp.hasZeroHighPad())
3423 return failure();
3424 if (padTensorOp.getNofold())
3425 return failure();
3426 rewriter.replaceOpWithNewOp<tensor::CastOp>(
3427 padTensorOp, padTensorOp.getResult().getType(),
3428 padTensorOp.getSource());
3429 return success();
3430 }
3431};
3432
3433// Fold CastOp into PadOp when adding static information.
3434struct FoldSourceTensorCast : public OpRewritePattern<PadOp> {
3435 using OpRewritePattern<PadOp>::OpRewritePattern;
3436
3437 LogicalResult matchAndRewrite(PadOp padTensorOp,
3438 PatternRewriter &rewriter) const override {
3439 auto castOp = padTensorOp.getSource().getDefiningOp<tensor::CastOp>();
3440 if (!tensor::canFoldIntoConsumerOp(castOp))
3441 return failure();
3442
3443 auto newResultType = PadOp::inferResultType(
3444 llvm::cast<RankedTensorType>(castOp.getSource().getType()),
3445 padTensorOp.getStaticLow(), padTensorOp.getStaticHigh(),
3446 padTensorOp.getResultType().getShape());
3447
3448 if (newResultType == padTensorOp.getResultType()) {
3449 rewriter.modifyOpInPlace(padTensorOp, [&]() {
3450 padTensorOp.getSourceMutable().assign(castOp.getSource());
3451 });
3452 } else {
3453 auto newOp = PadOp::create(
3454 rewriter, padTensorOp->getLoc(), newResultType,
3455 padTensorOp.getSource(), padTensorOp.getStaticLow(),
3456 padTensorOp.getStaticHigh(), padTensorOp.getLow(),
3457 padTensorOp.getHigh(), padTensorOp.getNofold(),
3458 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3459 IRMapping mapper;
3460 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper);
3461
3462 rewriter.replaceOpWithNewOp<tensor::CastOp>(
3463 padTensorOp, padTensorOp.getResultType(), newOp);
3464 }
3465 return success();
3466 }
3467};
3468
3469// Fold CastOp using the result of PadOp back into the latter if it adds
3470// static information.
3471struct FoldTargetTensorCast : public OpRewritePattern<PadOp> {
3472 using OpRewritePattern<PadOp>::OpRewritePattern;
3473
3474 LogicalResult matchAndRewrite(PadOp padTensorOp,
3475 PatternRewriter &rewriter) const override {
3476 if (!padTensorOp.getResult().hasOneUse())
3477 return failure();
3478 auto tensorCastOp =
3479 dyn_cast<tensor::CastOp>(*padTensorOp->getUsers().begin());
3480 if (!tensorCastOp)
3481 return failure();
3482 if (!tensor::preservesStaticInformation(padTensorOp.getResult().getType(),
3483 tensorCastOp.getDest().getType()))
3484 return failure();
3485
3486 auto replacementOp = PadOp::create(
3487 rewriter, padTensorOp.getLoc(), tensorCastOp.getDest().getType(),
3488 padTensorOp.getSource(), padTensorOp.getStaticLow(),
3489 padTensorOp.getStaticHigh(), padTensorOp.getLow(),
3490 padTensorOp.getHigh(), padTensorOp.getNofold(),
3491 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3492 replacementOp.getRegion().takeBody(padTensorOp.getRegion());
3493
3494 rewriter.replaceOp(padTensorOp, replacementOp.getResult());
3495 rewriter.replaceOp(tensorCastOp, replacementOp.getResult());
3496 return success();
3497 }
3498};
3499
3500/// Fold chains of tensor::ExtractSliceOp, tensor::PadOp pairs that pad
3501/// different dimensions. The pattern applies if the following preconditions
3502/// hold:
3503/// 1) the tensor::ExtractSliceOps are not rank-reducing,
3504/// 2) the tensor::ExtractSliceOps have only unit-strides,
3505/// 3) the tensor::PadOps perform only high-padding,
3506/// 4) the tensor::PadOps have the same constant padding value,
3507/// 5) the tensor::PadOps do not have common padding dimensions,
3508/// 6) one tensor::ExtractSliceOp, tensor::PadOp pair has zero-padding and
3509/// zero-offset for every dimension.
3510/// 7) the tensor::ExtractSliceOp sizes match the source tensor sizes for
3511/// the
3512/// padded source dimensions.
3513///
3514/// Example:
3515///
3516/// ```mlir
3517/// %0 = tensor.extract_slice %input[16, 0] [%sz0, 64] [1, 1]
3518/// : tensor<64x64xf32> to tensor<?x64xf32>
3519/// %1 = tensor.pad %0 low[0, 0] high[%pw0, 0] { ...
3520/// } : tensor<?x64xf32> to tensor<8x64xf32>
3521/// %2 = tensor.extract_slice %1[0, 4] [8, %sz1] [1, 1]
3522/// : tensor<8x64xf32> to tensor<8x?xf32>
3523/// %res = tensor.pad %2 nofold low[0, 0] high[0, %pw1] { ...
3524/// } : tensor<8x?xf32> to tensor<8x4xf32>
3525/// ```
3526///
3527/// folds into:
3528///
3529/// ```mlir
3530/// %0 = tensor.extract_slice %input[16, 4] [%sz0, %sz1] [1, 1]
3531/// : tensor<64x64xf32> to tensor<?x?xf32>
3532/// %res = tensor.pad %0 nofold low[0, 0] high[%pw0, %pw1] { ...
3533/// } : tensor<?x?xf32> to tensor<8x4xf32>
3534/// ```
3535struct FoldOrthogonalPaddings : public OpRewritePattern<PadOp> {
3536 using OpRewritePattern<PadOp>::OpRewritePattern;
3537
3538 LogicalResult matchAndRewrite(PadOp padOp,
3539 PatternRewriter &rewriter) const override {
3540 auto innerSliceOp = padOp.getSource().getDefiningOp<ExtractSliceOp>();
3541 if (!innerSliceOp)
3542 return failure();
3543 auto outerPadOp = innerSliceOp.getSource().getDefiningOp<PadOp>();
3544 if (!outerPadOp || outerPadOp.getNofold())
3545 return failure();
3546 auto outerSliceOp = outerPadOp.getSource().getDefiningOp<ExtractSliceOp>();
3547 if (!outerSliceOp)
3548 return failure();
3549
3550 // 1) Fail if the chain is rank-reducing.
3551 int64_t rank = padOp.getSourceType().getRank();
3552 if (outerSliceOp.getSourceType().getRank() != rank) {
3553 return rewriter.notifyMatchFailure(padOp,
3554 "cannot fold rank-reducing chain");
3555 }
3556
3557 // 2) Fail if the tensor::ExtractSliceOps have non-unit strides.
3558 if (!innerSliceOp.hasUnitStride() || !outerSliceOp.hasUnitStride()) {
3559 return rewriter.notifyMatchFailure(
3560 padOp, "cannot fold non-unit stride ExtractSliceOps");
3561 }
3562
3563 // 3) Fail if the tensor::PadOps have non-zero low padding.
3564 if (!padOp.hasZeroLowPad() || !outerPadOp.hasZeroLowPad()) {
3565 return rewriter.notifyMatchFailure(padOp,
3566 "cannot fold PadOps with low padding");
3567 }
3568
3569 // 4) Fail if the tensor::PadOps padding values do not match.
3570 Attribute innerAttr, outerAttr;
3571 Value innerValue = padOp.getConstantPaddingValue();
3572 Value outerValue = outerPadOp.getConstantPaddingValue();
3573 if (!innerValue || !outerValue ||
3574 !matchPattern(innerValue, m_Constant(&innerAttr)) ||
3575 !matchPattern(outerValue, m_Constant(&outerAttr)) ||
3576 innerAttr != outerAttr) {
3577 return rewriter.notifyMatchFailure(
3578 padOp, "cannot fold PadOps with different padding values");
3579 }
3580
3581 // 5) Fail if a dimension is padded by both tensor::PadOps.
3582 llvm::SmallBitVector innerDims = padOp.getPaddedDims();
3583 llvm::SmallBitVector outerDims = outerPadOp.getPaddedDims();
3584 if (innerDims.anyCommon(outerDims)) {
3585 return rewriter.notifyMatchFailure(
3586 padOp, "cannot fold PadOps with common padding dimensions");
3587 }
3588
3589 // 6) Combine the offsets of the two tensor::ExtractSliceOps. Find the
3590 // zero-offset and zero-padding tensor::ExtractSliceOp, tensor::PadOp pair
3591 // for every dimension, and use the offset the other pair. Fail if no
3592 // zero-offset and zero-padding tensor::ExtractSliceOp, tensor::PadOp pair
3593 // exists.
3594 SmallVector<OpFoldResult> newOffsets(rank, rewriter.getIndexAttr(0));
3595 for (auto en : enumerate(newOffsets)) {
3596 OpFoldResult innerOffset = innerSliceOp.getMixedOffsets()[en.index()];
3597 OpFoldResult outerOffset = outerSliceOp.getMixedOffsets()[en.index()];
3598 if (!innerDims.test(en.index()) &&
3599 (getConstantIntValue(innerOffset) == static_cast<int64_t>(0))) {
3600 en.value() = outerOffset;
3601 continue;
3602 }
3603 if (!outerDims.test(en.index()) &&
3604 (getConstantIntValue(outerOffset) == static_cast<int64_t>(0))) {
3605 en.value() = innerOffset;
3606 continue;
3607 }
3608 return rewriter.notifyMatchFailure(
3609 padOp, "cannot find zero-offset and zero-padding pair");
3610 }
3611
3612 // 7) Combine the sizes of the two tensor::ExtractSliceOps. Take the size
3613 // of the outer tensor::ExtractSliceOp for the dimensions padded by the
3614 // outer tensor::PadOp and fail if the size of the inner
3615 // tensor::ExtractSliceOp does not match the size of the padded dimension.
3616 // Otherwise, take the size of the inner tensor::ExtractSliceOp.
3617 SmallVector<OpFoldResult> newSizes = innerSliceOp.getMixedSizes();
3618 for (auto en : enumerate(newSizes)) {
3619 if (!outerDims.test(en.index()))
3620 continue;
3621 OpFoldResult sliceSize = innerSliceOp.getMixedSizes()[en.index()];
3622 int64_t sourceSize = innerSliceOp.getSourceType().getShape()[en.index()];
3623 assert(ShapedType::isStatic(sourceSize) &&
3624 "expected padded dimension to have a static size");
3625 if (getConstantIntValue(sliceSize) != sourceSize) {
3626 return rewriter.notifyMatchFailure(
3627 padOp, "cannot fold since the inner ExtractSliceOp size does not "
3628 "match the size of the outer padding");
3629 }
3630 en.value() = outerSliceOp.getMixedSizes()[en.index()];
3631 }
3632
3633 // Combine the high paddings of the two tensor::PadOps.
3634 SmallVector<OpFoldResult> newHighPad(rank, rewriter.getIndexAttr(0));
3635 for (auto en : enumerate(newHighPad)) {
3636 if (innerDims.test(en.index()))
3637 newHighPad[en.index()] = padOp.getMixedHighPad()[en.index()];
3638 if (outerDims.test(en.index()))
3639 newHighPad[en.index()] = outerPadOp.getMixedHighPad()[en.index()];
3640 }
3641
3642 // Create a new tensor::ExtractSliceOp, tensor::PadOp pair that performs
3643 // the two paddings in one step.
3644 auto newSliceOp = ExtractSliceOp::create(
3645 rewriter, padOp.getLoc(), outerSliceOp.getSource(), newOffsets,
3646 newSizes, innerSliceOp.getMixedStrides());
3647 auto newPadOp = PadOp::create(
3648 rewriter, padOp.getLoc(), padOp.getResultType(), newSliceOp.getResult(),
3649 padOp.getMixedLowPad(), newHighPad, padOp.getNofold(),
3650 getPrunedAttributeList(padOp, PadOp::getAttributeNames()));
3651 rewriter.inlineRegionBefore(padOp.getRegion(), newPadOp.getRegion(),
3652 newPadOp.getRegion().begin());
3653 rewriter.replaceOp(padOp, newPadOp.getResult());
3654 return success();
3655 }
3656};
3657
3658struct FoldStaticPadding : public OpRewritePattern<PadOp> {
3659 using OpRewritePattern<PadOp>::OpRewritePattern;
3660
3661 LogicalResult matchAndRewrite(PadOp padTensorOp,
3662 PatternRewriter &rewriter) const override {
3663 Value input = padTensorOp.getSource();
3664 if (!llvm::isa<RankedTensorType>(input.getType()))
3665 return failure();
3666 auto inputDims = llvm::cast<RankedTensorType>(input.getType()).getShape();
3667 auto inputRank = inputDims.size();
3668
3669 auto oldResultType =
3670 dyn_cast<RankedTensorType>(padTensorOp.getResult().getType());
3671 if (!oldResultType)
3672 return failure();
3673
3674 auto outputDims = oldResultType.getShape();
3675
3676 // Extract the static info from the high and low operands.
3677 SmallVector<int64_t> constOperandsLow;
3678 SmallVector<Value> newLows;
3679 for (auto operand : padTensorOp.getLow()) {
3680 APSInt intOp;
3681 if (!matchPattern(operand, m_ConstantInt(&intOp))) {
3682 constOperandsLow.push_back(ShapedType::kDynamic);
3683 newLows.push_back(operand);
3684 continue;
3685 }
3686 constOperandsLow.push_back(intOp.getExtValue());
3687 }
3688 SmallVector<int64_t> constOperandsHigh;
3689 SmallVector<Value> newHighs;
3690 for (auto operand : padTensorOp.getHigh()) {
3691 APSInt intOp;
3692 if (!matchPattern(operand, m_ConstantInt(&intOp))) {
3693 constOperandsHigh.push_back(ShapedType::kDynamic);
3694 newHighs.push_back(operand);
3695 continue;
3696 }
3697 constOperandsHigh.push_back(intOp.getExtValue());
3698 }
3699
3700 SmallVector<int64_t> constLow(padTensorOp.getStaticLow());
3701 SmallVector<int64_t> constHigh(padTensorOp.getStaticHigh());
3702
3703 // Verify the op is well-formed.
3704 if (inputDims.size() != outputDims.size() ||
3705 inputDims.size() != constLow.size() ||
3706 inputDims.size() != constHigh.size())
3707 return failure();
3708
3709 auto lowCount = 0;
3710 auto highCount = 0;
3711 for (size_t i = 0; i < inputRank; i++) {
3712 if (constLow[i] == ShapedType::kDynamic)
3713 constLow[i] = constOperandsLow[lowCount++];
3714 if (constHigh[i] == ShapedType::kDynamic)
3715 constHigh[i] = constOperandsHigh[highCount++];
3716 }
3717
3718 auto staticLow = ArrayRef<int64_t>(constLow);
3719 auto staticHigh = ArrayRef<int64_t>(constHigh);
3720
3721 // Calculate the output sizes with the static information.
3722 SmallVector<int64_t> newOutDims;
3723 for (size_t i = 0; i < inputRank; i++) {
3724 if (outputDims[i] == ShapedType::kDynamic) {
3725 newOutDims.push_back(
3726 (staticLow[i] == ShapedType::kDynamic ||
3727 staticHigh[i] == ShapedType::kDynamic ||
3728 inputDims[i] == ShapedType::kDynamic
3729 ? ShapedType::kDynamic
3730 : inputDims[i] + staticLow[i] + staticHigh[i]));
3731 } else {
3732 newOutDims.push_back(outputDims[i]);
3733 }
3734 }
3735
3736 if (SmallVector<int64_t>(outputDims) == newOutDims ||
3737 llvm::all_of(newOutDims,
3738 [&](int64_t x) { return x == ShapedType::kDynamic; }))
3739 return failure();
3740
3741 Type elementType = padTensorOp.getType().getElementType();
3742 auto newResultType = RankedTensorType::get(
3743 newOutDims, elementType,
3744 propagateEncoding(padTensorOp.getType().getEncoding(), newOutDims,
3745 elementType));
3746 auto newOp = PadOp::create(
3747 rewriter, padTensorOp->getLoc(), newResultType, input, staticLow,
3748 staticHigh, newLows, newHighs, padTensorOp.getNofold(),
3749 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3750
3751 IRMapping mapper;
3752 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper);
3753 rewriter.replaceOpWithNewOp<tensor::CastOp>(padTensorOp, oldResultType,
3754 newOp);
3755
3756 return success();
3757 }
3758};
3759
3760/// Folds a chain of `tensor.pad` ops with the same constant padding value.
3761///
3762/// Example:
3763///
3764/// ```mlir
3765/// %1 = tensor.pad %0 low[0, 1] high[0, 2] {
3766/// tensor.yield %val
3767/// } : tensor<1x2xf32> to tensor<2x5xf32>
3768/// %res = tensor.pad %1 low[0, 2] high[3, 0] {
3769/// tensor.yield %val
3770/// } : tensor<1x5xf32> to tensor<5x7xf32>
3771/// ```
3772///
3773/// folds into:
3774///
3775/// ```mlir
3776/// %res = tensor.pad %0 low[0, 3] high[3, 2] {
3777/// tensor.yield %val
3778/// } : tensor<1x2xf32> to tensor<5x7xf32>
3779/// ```
3780struct FoldConsecutiveConstantPadding : public OpRewritePattern<tensor::PadOp> {
3781 using OpRewritePattern<tensor::PadOp>::OpRewritePattern;
3782
3783 LogicalResult matchAndRewrite(tensor::PadOp padOp,
3784 PatternRewriter &rewriter) const override {
3785 if (padOp.getNofold()) {
3786 return rewriter.notifyMatchFailure(padOp, "skipping unfoldable pad");
3787 }
3788
3789 auto producerPad = padOp.getSource().getDefiningOp<tensor::PadOp>();
3790 if (!producerPad || producerPad.getNofold()) {
3791 return rewriter.notifyMatchFailure(
3792 padOp, "producer is not a foldable tensor.pad op");
3793 }
3794
3795 // Fail if the tensor::PadOps padding values do not match.
3796 Value consumerPadValue = padOp.getConstantPaddingValue();
3797 Value producerPadValue = producerPad.getConstantPaddingValue();
3798 if (!consumerPadValue || !producerPadValue ||
3799 consumerPadValue != producerPadValue) {
3800 return rewriter.notifyMatchFailure(
3801 padOp,
3802 "cannot fold PadOps with different or non-constant padding values");
3803 }
3804
3805 Location loc = padOp.getLoc();
3806 AffineExpr d0, d1;
3807 bindDims(rewriter.getContext(), d0, d1);
3808
3809 // Combine the low/high paddings of the two tensor::PadOps.
3810 auto addPaddings = [&](ArrayRef<OpFoldResult> consumerPaddings,
3811 ArrayRef<OpFoldResult> producerPaddings) {
3812 SmallVector<OpFoldResult> sumPaddings;
3813 for (auto [consumerIndex, producerIndex] :
3814 llvm::zip_equal(consumerPaddings, producerPaddings)) {
3815 sumPaddings.push_back(affine::makeComposedFoldedAffineApply(
3816 rewriter, loc, d0 + d1, {consumerIndex, producerIndex}));
3817 }
3818 return sumPaddings;
3819 };
3820
3821 SmallVector<OpFoldResult> newHighPad =
3822 addPaddings(padOp.getMixedHighPad(), producerPad.getMixedHighPad());
3823 SmallVector<OpFoldResult> newLowPad =
3824 addPaddings(padOp.getMixedLowPad(), producerPad.getMixedLowPad());
3825
3826 auto newPadOp = tensor::PadOp::create(
3827 rewriter, padOp.getLoc(), padOp.getResultType(),
3828 producerPad.getSource(), newLowPad, newHighPad, padOp.getNofold(),
3829 getPrunedAttributeList(padOp, tensor::PadOp::getAttributeNames()));
3830 rewriter.inlineRegionBefore(padOp.getRegion(), newPadOp.getRegion(),
3831 newPadOp.getRegion().begin());
3832 rewriter.replaceOp(padOp, newPadOp.getResult());
3833 return success();
3834 }
3835};
3836
3837} // namespace
3838
3839LogicalResult
3840PadOp::reifyResultShapes(OpBuilder &b,
3841 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3842 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
3843 SmallVector<OpFoldResult> lp = getMixedLowPad();
3844 SmallVector<OpFoldResult> hp = getMixedHighPad();
3845 for (int64_t i = 0; i < getResultType().getRank(); ++i) {
3846 if (!getType().isDynamicDim(i)) {
3847 reifiedReturnShapes[0][i] = b.getIndexAttr(getType().getDimSize(i));
3848 continue;
3849 }
3850 Location loc = getLoc();
3851 Value dim = b.createOrFold<tensor::DimOp>(
3852 loc, getSource(), arith::ConstantIndexOp::create(b, loc, i));
3853
3854 AffineExpr d0, d1, d2;
3855 bindDims(b.getContext(), d0, d1, d2);
3856 reifiedReturnShapes[0][i] = affine::makeComposedFoldedAffineApply(
3857 b, loc, {d0 + d1 + d2}, {dim, lp[i], hp[i]});
3858 }
3859 return success();
3860}
3861
3862void PadOp::getCanonicalizationPatterns(RewritePatternSet &results,
3863 MLIRContext *context) {
3864 results.add<FoldStaticZeroPadding, FoldSourceTensorCast, FoldTargetTensorCast,
3865 FoldOrthogonalPaddings, FoldStaticPadding,
3866 FoldConsecutiveConstantPadding>(context);
3867}
3868
3869/// Return the padding value of the PadOp if it constant. In this context,
3870/// "constant" means an actual constant or "defined outside of the block".
3871///
3872/// Values are considered constant in three cases:
3873/// - A ConstantLike value.
3874/// - A basic block argument from a different block.
3875/// - A value defined outside of the block.
3876///
3877/// If the padding value is not constant, an empty Value is returned.
3878Value PadOp::getConstantPaddingValue() {
3879 auto yieldOp = dyn_cast<YieldOp>(getRegion().front().getTerminator());
3880 if (!yieldOp)
3881 return {};
3882 Value padValue = yieldOp.getValue();
3883 // Check if yield value is a constant.
3884 if (matchPattern(padValue, m_Constant()))
3885 return padValue;
3886 // Check if yield value is defined inside the PadOp block.
3887 if (padValue.getParentBlock() == &getRegion().front())
3888 return {};
3889 // Else: Yield value defined outside of the PadOp block.
3890 return padValue;
3891}
3892
3893OpFoldResult PadOp::fold(FoldAdaptor) {
3894 if (getResultType().hasStaticShape() && getResultType() == getSourceType() &&
3895 !getNofold())
3896 return getSource();
3897 return {};
3898}
3899
3900//===----------------------------------------------------------------------===//
3901// ParallelInsertSliceOp
3902//===----------------------------------------------------------------------===//
3903
3904OpResult ParallelInsertSliceOp::getTiedOpResult() {
3905 InParallelOpInterface parallelCombiningParent = getParallelCombiningParent();
3906 for (const auto &it :
3907 llvm::enumerate(parallelCombiningParent.getYieldingOps())) {
3908 Operation &nextOp = it.value();
3909 if (&nextOp == getOperation())
3910 return parallelCombiningParent.getParentResult(it.index());
3911 }
3912 llvm_unreachable("ParallelInsertSliceOp no tied OpResult found");
3913}
3914
3915// Build a ParallelInsertSliceOp with mixed static and dynamic entries.
3916void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3917 Value source, Value dest,
3918 ArrayRef<OpFoldResult> offsets,
3919 ArrayRef<OpFoldResult> sizes,
3920 ArrayRef<OpFoldResult> strides,
3921 ArrayRef<NamedAttribute> attrs) {
3922 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
3923 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
3924 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
3925 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
3926 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
3927 result.addAttributes(attrs);
3928 build(b, result, {}, source, dest, dynamicOffsets, dynamicSizes,
3929 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
3930 b.getDenseI64ArrayAttr(staticSizes),
3931 b.getDenseI64ArrayAttr(staticStrides));
3932}
3933
3934/// Build an ParallelInsertSliceOp with mixed static and dynamic entries
3935/// packed into a Range vector.
3936void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3937 Value source, Value dest,
3938 ArrayRef<Range> ranges,
3939 ArrayRef<NamedAttribute> attrs) {
3940 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
3941 build(b, result, source, dest, offsets, sizes, strides, attrs);
3942}
3943
3944// Build a ParallelInsertSliceOp with dynamic entries.
3945void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3946 Value source, Value dest, ValueRange offsets,
3947 ValueRange sizes, ValueRange strides,
3948 ArrayRef<NamedAttribute> attrs) {
3949 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
3950 offsets, [](Value v) -> OpFoldResult { return v; });
3951 SmallVector<OpFoldResult> sizeValues =
3952 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
3953 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
3954 strides, [](Value v) -> OpFoldResult { return v; });
3955 build(b, result, source, dest, offsetValues, sizeValues, strideValues);
3956}
3957
3958// Build an InsertSliceOp with mixed static and dynamic sizes, offsets set
3959// to 0, strides set to 1 and inferred result type.
3960void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
3961 Value dest, ArrayRef<OpFoldResult> sizes,
3962 ArrayRef<NamedAttribute> attrs) {
3963 Attribute zeroIdxAttr = b.getIndexAttr(0);
3964 Attribute oneIdxAttr = b.getIndexAttr(1);
3965 SmallVector<OpFoldResult> writeStrides(sizes.size(), oneIdxAttr);
3966 SmallVector<OpFoldResult> writeOffsets(sizes.size(), zeroIdxAttr);
3967 build(b, result, source, dest, writeOffsets, sizes, writeStrides, attrs);
3968}
3969
3970LogicalResult ParallelInsertSliceOp::verify() {
3971 if (!isa<InParallelOpInterface>(getOperation()->getParentOp()))
3972 return this->emitError("expected InParallelOpInterface parent, got:")
3973 << *(getOperation()->getParentOp());
3974
3975 // Verify result type against inferred type.
3976 RankedTensorType expectedType;
3978 verifyInsertSliceOp(getSourceType(), getDestType(), getStaticOffsets(),
3979 getStaticSizes(), getStaticStrides(), &expectedType);
3981 return produceSliceErrorMsg(result, *this, expectedType);
3982
3983 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
3984 // to the destination tensor.
3985 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
3986 getDestType().getShape(), getStaticOffsets(), getStaticSizes(),
3987 getStaticStrides(), /*generateErrorMessage=*/true);
3988 if (!boundsResult.isValid)
3989 return getOperation()->emitError(boundsResult.errorMessage);
3990
3991 return success();
3992}
3993
3994void ParallelInsertSliceOp::getCanonicalizationPatterns(
3995 RewritePatternSet &results, MLIRContext *context) {
3996 results.add<InsertSliceOpConstantArgumentFolder<ParallelInsertSliceOp>,
3997 InsertSliceOpCastFolder<ParallelInsertSliceOp>,
3998 InsertSliceOpSourceCastInserter<ParallelInsertSliceOp>>(context);
3999}
4000
4001llvm::SmallBitVector ParallelInsertSliceOp::getDroppedDims() {
4002 return ::getDroppedDims(getSourceType().getShape(), getMixedSizes());
4003}
4004
4005// ParallelCombiningOpInterface implementation.
4006MutableOperandRange ParallelInsertSliceOp::getUpdatedDestinations() {
4007 return getDestMutable();
4008}
4009
4010Operation *ParallelInsertSliceOp::getIteratingParent() {
4011 // Return the parent InParallelOpInterface's parent.
4012 if (auto combiningOp =
4013 dyn_cast<InParallelOpInterface>(getOperation()->getParentOp()))
4014 return combiningOp->getParentOp();
4015 return nullptr;
4016}
4017
4018//===----------------------------------------------------------------------===//
4019// ScatterOp
4020//===----------------------------------------------------------------------===//
4021
4022void ScatterOp::getAsmResultNames(
4023 function_ref<void(Value, StringRef)> setNameFn) {
4024 setNameFn(getResult(), "scatter");
4025}
4026
4027LogicalResult ScatterOp::verify() {
4028 int64_t destRank = getDestType().getRank();
4029 ArrayRef<int64_t> scatterDims = getScatterDims();
4030 if (failed(verifyGatherOrScatterDims(getOperation(), scatterDims,
4031 getIndicesType().getShape(), destRank,
4032 "scatter", "dest")))
4033 return failure();
4034
4035 if (!getUnique())
4036 return emitOpError("requires 'unique' attribute to be set");
4037 // TODO: we could also check statically that there are fewer leading index
4038 // tensor dims than the dest dims. If this is not the case, the unique
4039 // attribute cannot be true.
4040
4041 // Use the GatherOp::inferResultType on the `dest` type and verify the
4042 // expected type matches the source type.
4043 RankedTensorType expectedSourceType = GatherOp::inferResultType(
4044 getDestType(), getIndicesType(), scatterDims, /*rankReduced=*/false);
4045 RankedTensorType expectedRankReducedSourceType = GatherOp::inferResultType(
4046 getDestType(), getIndicesType(), scatterDims, /*rankReduced=*/true);
4047 if (getSourceType() != expectedSourceType &&
4048 getSourceType() != expectedRankReducedSourceType) {
4049 return emitOpError("source type "
4050 "mismatch: "
4051 "expected ")
4052 << expectedSourceType << " or its rank-reduced variant "
4053 << expectedRankReducedSourceType << " (got: " << getSourceType()
4054 << ")";
4055 }
4056
4057 return success();
4058}
4059
4060//===----------------------------------------------------------------------===//
4061// SplatOp
4062//===----------------------------------------------------------------------===//
4063
4064void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4065 Type aggregateType, ValueRange dynamicSizes) {
4066 build(builder, result, aggregateType, element, dynamicSizes);
4067}
4068
4069void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4070 ArrayRef<int64_t> staticShape, ValueRange dynamicSizes) {
4071 auto aggregateType = RankedTensorType::get(staticShape, element.getType());
4072 build(builder, result, aggregateType, element, dynamicSizes);
4073}
4074
4075void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4076 ArrayRef<OpFoldResult> sizes) {
4077 SmallVector<int64_t> staticShape;
4078 SmallVector<Value> dynamicSizes;
4079 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticShape);
4080 build(builder, result, element, staticShape, dynamicSizes);
4081}
4082
4083void SplatOp::getAsmResultNames(
4084 function_ref<void(Value, StringRef)> setNameFn) {
4085 setNameFn(getResult(), "splat");
4086}
4087
4088LogicalResult SplatOp::verify() {
4089 return verifyDynamicDimensionCount(getOperation(), getType(),
4090 getDynamicSizes());
4091}
4092
4093LogicalResult
4094SplatOp::reifyResultShapes(OpBuilder &builder,
4095 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
4096 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
4097 unsigned ctr = 0;
4098 for (int64_t i = 0; i < getType().getRank(); ++i) {
4099 if (getType().isDynamicDim(i)) {
4100 reifiedReturnShapes[0][i] = getDynamicSizes()[ctr++];
4101 } else {
4102 reifiedReturnShapes[0][i] = builder.getIndexAttr(getType().getDimSize(i));
4103 }
4104 }
4105 return success();
4106}
4107
4108OpFoldResult SplatOp::fold(FoldAdaptor adaptor) {
4109 auto constOperand = adaptor.getInput();
4110 if (!isa_and_nonnull<IntegerAttr, FloatAttr>(constOperand))
4111 return {};
4112
4113 // Do not fold if the splat is not statically shaped
4114 if (!getType().hasStaticShape())
4115 return {};
4116
4117 // SplatElementsAttr::get treats single value for second arg as being a
4118 // splat.
4119 return SplatElementsAttr::get(getType(), {constOperand});
4120}
4121
4122//===----------------------------------------------------------------------===//
4123// Common Canonicalizers and Folders.
4124//===----------------------------------------------------------------------===//
4125static bool foldTensorCastPrecondition(DestinationStyleOpInterface op) {
4126 // 1. InsertSliceOp has its own logic about folding tensor.cast ops.
4127 // 2. Exclude DPS ops that are also LoopLike from this interface as they
4128 // might need special handling of attached regions.
4129 if (isa<InsertSliceOp>(op.getOperation()) ||
4130 isa<LoopLikeOpInterface>(op.getOperation()))
4131 return false;
4132
4134}
4135
4136/// Folds a tensor.cast op into a consuming DestinationStyleOpInterface op if
4137/// the `tensor.cast` has source that is more static than the consuming op.
4138///
4139/// Example:
4140/// ```mlir
4141/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
4142/// %2 = consumer %1 ... : tensor<?x?xf32> ...
4143/// ```
4144///
4145/// folds into:
4146///
4147/// ```mlir
4148/// %2 = consumer %0 ... : tensor<8x16xf32> ...
4149/// ```
4150/// TODO: Move the pattern to a proper place, so all other DestinationStyleOp
4151/// can add the pattern to their canonicalizers.
4153 : public OpInterfaceRewritePattern<DestinationStyleOpInterface> {
4155 DestinationStyleOpInterface>::OpInterfaceRewritePattern;
4156
4157 LogicalResult matchAndRewrite(DestinationStyleOpInterface op,
4158 PatternRewriter &rewriter) const override {
4159
4160 // Reject PackOp/UnpackOp (i.e. RelayoutOps) - there are dedicated patterns
4161 // for that instead.
4162 if (!foldTensorCastPrecondition(op) ||
4163 isa<linalg::RelayoutOpInterface>(*op))
4164 return failure();
4165
4166 SmallVector<Type> newResultTypes(op->getResultTypes());
4167 SmallVector<Value> newOperands =
4168 getUpdatedOperandsAfterCastOpFolding(op, newResultTypes);
4169
4170 // Clone op
4171 auto newOp = clone(rewriter, op, newResultTypes, newOperands);
4172
4173 SmallVector<Value, 4> replacements;
4174 replacements.reserve(newOp->getNumResults());
4175 for (auto [oldResult, newResult] :
4176 llvm::zip(op->getResults(), newOp->getResults())) {
4177 if (newResult.getType() != oldResult.getType()) {
4178 replacements.push_back(tensor::CastOp::create(
4179 rewriter, op->getLoc(), oldResult.getType(), newResult));
4180 } else {
4181 replacements.push_back(newResult);
4182 }
4183 }
4184 rewriter.replaceOp(op, replacements);
4185
4186 return success();
4187 }
4188};
4189
4190//===----------------------------------------------------------------------===//
4191// TensorDialect
4192//===----------------------------------------------------------------------===//
4193
4194void TensorDialect::getCanonicalizationPatterns(
4195 RewritePatternSet &results) const {
4196 results.add<FoldTensorCastProducerOp>(getContext());
4197}
4198
4199//===----------------------------------------------------------------------===//
4200// TableGen'd op method definitions
4201//===----------------------------------------------------------------------===//
4202
4203#define GET_OP_CLASSES
4204#include "mlir/Dialect/Tensor/IR/TensorOps.cpp.inc"
return success()
std::string join(const Ts &...args)
Helper function to concatenate arguments into a std::string.
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())
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static 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 void getDynamicSizes(RankedTensorType tp, ValueRange sizes, SmallVectorImpl< Value > &dynSizes)
Collects the dynamic dimension sizes for tp with the assumption that sizes are the dimension sizes fo...
static LogicalResult emit(SolverOp solver, const SMTEmissionOptions &options, mlir::raw_indented_ostream &stream)
Emit the SMT operations in the given 'solver' to the 'stream'.
static TensorType joinShapes(TensorType one, TensorType two)
Compute a TensorType that has the joined shape knowledge of the two given TensorTypes.
static Value foldExtractAfterInsert(ExtractOp extractOp)
If we have an ExtractOp consuming an InsertOp with the same indices, we can return the InsertOp's sca...
static LogicalResult verifyGatherOrScatterDims(Operation *op, ArrayRef< int64_t > dims, ArrayRef< int64_t > indices, int64_t rank, StringRef gatherOrScatter, StringRef sourceOrDest)
static LogicalResult produceSliceErrorMsg(SliceVerificationResult result, Operation *op, RankedTensorType expectedType)
static bool foldTensorCastPrecondition(DestinationStyleOpInterface op)
static LogicalResult foldInsertAfterInsertSlice(InsertSliceOp insertOp)
If we have two consecutive InsertSliceOp writing to the same slice, we can mutate the second InsertSl...
static Attribute propagateEncoding(Attribute encoding, ArrayRef< int64_t > shape, Type elementType)
Implements the VerifiableTensorEncoding contract documented in TensorEncoding.td for patterns that re...
Definition TensorOps.cpp:54
static LogicalResult foldIdentityOffsetSizeAndStrideOpInterface(OffsetSizeAndStrideOpInterface op, ShapedType shapedType)
static Value foldExtractAfterInsertSlice(ExtractSliceOp extractOp)
If we have an ExtractSliceOp consuming an InsertSliceOp with the same slice, we can return the Insert...
static SliceVerificationResult verifyInsertSliceOp(RankedTensorType srcType, RankedTensorType dstType, ArrayRef< int64_t > staticOffsets, ArrayRef< int64_t > staticSizes, ArrayRef< int64_t > staticStrides, RankedTensorType *expectedType=nullptr)
Rank-reducing type verification for both InsertSliceOp and ParallelInsertSliceOp.
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 Value foldInsertAfterExtractSlice(InsertSliceOp insertOp)
Folds round-trip extract/insert slice op pairs.
static LogicalResult verifyTensorReshapeOp(TensorReshapeOp op, RankedTensorType expandedType, RankedTensorType collapsedType)
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
Base type for affine expression.
Definition AffineExpr.h:68
Attributes are known-constant values of operations.
Definition Attributes.h:25
MLIRContext * getContext() const
Return the context this attribute belongs to.
ValueTypeRange< BlockArgListType > getArgumentTypes()
Return a range containing the types of the arguments for this block.
Definition Block.cpp:154
unsigned getNumArguments()
Definition Block.h:153
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgListType getArguments()
Definition Block.h:112
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:222
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
AffineExpr getAffineSymbolExpr(unsigned position)
Definition Builders.cpp:377
Ty getType(Args &&...args)
Get or construct an instance of the type Ty with provided arguments.
Definition Builders.h:94
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
AffineMap getConstantAffineMap(int64_t val)
Returns a single constant result affine map with 0 dimensions and 0 symbols.
Definition Builders.cpp:387
MLIRContext * getContext() const
Definition Builders.h:56
auto value_begin() const
Get an iterator of the given type to the start of the held element values.
static DenseElementsAttr getFromRawBuffer(ShapedType type, ArrayRef< char > rawBuffer)
Construct a dense elements attribute from a raw buffer representing the data for this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
ArrayRef< char > getRawData() const
Return the raw storage data held by this attribute.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
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:64
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
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:581
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
This is a value defined by a result of an operation.
Definition Value.h:454
unsigned getResultNumber() const
Returns the number of this result.
Definition Value.h:466
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
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...
result_range getResults()
Definition Operation.h:440
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...
iterator end()
Definition Region.h:56
MLIRContext * getContext() const
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...
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.
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This diagnostic handler is a simple RAII class that registers and erases a diagnostic handler on a gi...
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
bool hasRank() const
Returns if this type is ranked, i.e. it has a known number of dimensions.
Type getElementType() const
Returns the element type of this tensor type.
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
bool isIndex() const
Definition Types.cpp:56
bool isIntOrIndexOrFloat() const
Return true if this is an integer (of any signedness), index, or float type.
Definition Types.cpp:122
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getType() const
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
Block * getParentBlock()
Return the Block in which this Value is defined.
Definition Value.cpp:46
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 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
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
ConstantIntRanges inferShapedDimOpInterface(ShapedDimOpInterface op, const IntegerValueRange &maybeDim)
Returns the integer range for the result of a ShapedDimOpInterface given the optional inferred ranges...
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:734
Value constantIndex(OpBuilder &builder, Location loc, int64_t i)
Generates a constant of index type.
LogicalResult foldTensorCast(Operation *op)
Performs folding of any operand of op if it comes from a tensor::CastOp that can be folded.
bool hasFoldableTensorCastOperand(Operation *op)
Return true if any of the operands of op is a CastOp that can be folded into its consumer,...
void populateFoldConstantExtractSlicePatterns(RewritePatternSet &patterns, const ControlConstantExtractSliceFusionFn &controlFn=[](ExtractSliceOp op) { return false;})
Patterns to fold the extract slice op with its constant operand.
bool canFoldIntoProducerOp(CastOp castOp)
Determines whether the tensor::CastOp casts to a more static version of the source tensor.
SmallVector< Value > getUpdatedOperandsAfterCastOpFolding(DestinationStyleOpInterface op, SmallVector< Type > &newResTy)
Assuming that op contains at least one operand that is a foldable CastOp (i.e.
bool canFoldIntoConsumerOp(CastOp castOp)
Determines whether tensor::CastOp casts to a more dynamic version of the source tensor.
Value createCanonicalRankReducingInsertSliceOp(OpBuilder &b, Location loc, Value tensor, Value dest)
Create a rank-reducing InsertSliceOp @[0 .
Value createCanonicalRankReducingExtractSliceOp(OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType)
Create a rank-reducing ExtractSliceOp @[0 .
bool isSameTypeWithoutEncoding(Type tp1, Type tp2)
Tests if types are the same when ignoring encoding on ranked tensors.
RankedTensorType inferSliceType(RankedTensorType sourceTensorType, ArrayRef< int64_t > staticSizes, const llvm::SmallBitVector &droppedDims)
Infer a slice type for the given sizes and exact dropped-dimension mask.
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:82
void populateFoldCollapseExtractPatterns(RewritePatternSet &patterns)
Patterns to fold extracts of a collapse_shaped tensor to an extract of the source tensor.
FailureOr< Value > getOrCreateDestination(OpBuilder &b, Location loc, OpResult opResult)
This is a helper function for DestinationStyleOpInterface.
bool preservesStaticInformation(Type source, Type target)
Returns true if target is a ranked tensor type that preserves static information available in the sou...
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
LogicalResult getOrCreateDestinations(OpBuilder &b, Location loc, Operation *op, SmallVector< Value > &result)
This is a helper function for DestinationStyleOpInterface.
std::function< bool(ExtractSliceOp)> ControlConstantExtractSliceFusionFn
Function to control the folding of constant and extract slice.
Definition Tensor.h:179
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
OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val)
Convert int64_t to integer attributes of index type and return them as OpFoldResult.
std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getOffsetsSizesAndStrides(ArrayRef< Range > ranges)
Given an array of Range values, return a tuple of (offset vector, sizes vector, and strides vector) f...
SliceVerificationResult
Enum that captures information related to verifier error conditions on slice insert/extract type of o...
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
LogicalResult foldDynamicStrideList(SmallVectorImpl< OpFoldResult > &strides)
Returns "success" when any of the elements in strides is a constant value.
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:311
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
SmallVector< int64_t > delinearize(int64_t linearIndex, ArrayRef< int64_t > strides)
Given the strides together with a linear index in the dimension space, return the vector-space offset...
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>>.
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.
bool wouldOpBeTriviallyDead(Operation *op)
Return true if the given operation would be dead if unused, and has no side effects on memory that wo...
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
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.
bool isReassociationValid(ArrayRef< AffineMap > reassociation, int *invalidIndex=nullptr)
Return true if the reassociation specification is valid, false otherwise.
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
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
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
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...
LogicalResult verifyCompatibleShape(ArrayRef< int64_t > shape1, ArrayRef< int64_t > shape2)
Returns success if the given two shapes are compatible.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
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:155
SmallVector< NamedAttribute > getPrunedAttributeList(Operation *op, ArrayRef< StringRef > elidedAttrs)
LogicalResult foldDynamicOffsetSizeList(SmallVectorImpl< OpFoldResult > &offsetsOrSizes)
Returns "success" when any of the elements in offsetsOrSizes is a constant value.
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.
LogicalResult verifyReassociationIndicesNotEmpty(ReshapeOpTy op)
Verify that none of the reassociation groups is empty.
Folds a tensor.cast op into a consuming DestinationStyleOpInterface op if the tensor....
LogicalResult matchAndRewrite(DestinationStyleOpInterface op, PatternRewriter &rewriter) const override
A canonicalizer wrapper to replace ExtractSliceOps.
void operator()(PatternRewriter &rewriter, ExtractSliceOp op, ExtractSliceOp newOp)
Return the canonical type of the result of an extract_slice op.
RankedTensorType operator()(ExtractSliceOp op, ArrayRef< OpFoldResult > mixedOffsets, ArrayRef< OpFoldResult > mixedSizes, ArrayRef< OpFoldResult > mixedStrides)
OpInterfaceRewritePattern(MLIRContext *context, PatternBenefit benefit=1)
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
This represents an operation in an abstracted form, suitable for use with the builder APIs.
Idiomatic saturated operations on values like offsets, sizes, and strides.
static SaturatedInteger wrap(int64_t v)
FailureOr< SaturatedInteger > desaturate(SaturatedInteger other)
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.