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