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
2378// TODO: This uses neither offsets nor strides!
2379RankedTensorType
2380ExtractSliceOp::inferResultType(RankedTensorType sourceTensorType,
2381 ArrayRef<OpFoldResult> sizes) {
2382 SmallVector<int64_t> staticSizes;
2383 std::tie(staticSizes, std::ignore) = decomposeMixedValues(sizes);
2384
2385 assert(static_cast<int64_t>(staticSizes.size()) ==
2386 sourceTensorType.getRank() &&
2387 "unexpected staticSizes not equal to rank of source");
2388 return RankedTensorType::get(staticSizes, sourceTensorType.getElementType(),
2389 sourceTensorType.getEncoding());
2390}
2391
2392/// If the rank is reduced (i.e. the desiredResultRank is smaller than the
2393/// number of sizes), drop as many size 1 as needed to produce an inferred
2394/// type with the desired rank.
2395///
2396/// Note that there may be multiple ways to compute this rank-reduced type:
2397/// e.g. 1x6x1 can rank-reduce to either 1x6 or 6x1 2-D tensors.
2398///
2399/// To disambiguate, this function always drops the first 1 sizes occurrences.
2400RankedTensorType ExtractSliceOp::inferCanonicalRankReducedResultType(
2401 unsigned desiredResultRank, RankedTensorType sourceRankedTensorType,
2402 ArrayRef<int64_t> sizes) {
2403 // Type inferred in the absence of rank-reducing behavior.
2404 auto inferredType = llvm::cast<RankedTensorType>(
2405 inferResultType(sourceRankedTensorType, sizes));
2406 int rankDiff = inferredType.getRank() - desiredResultRank;
2407 if (rankDiff > 0) {
2408 auto shape = inferredType.getShape();
2409 llvm::SmallBitVector dimsToProject =
2410 getPositionsOfShapeOne(rankDiff, shape);
2411 SmallVector<int64_t> projectedShape;
2412 // Best effort rank-reducing: drop 1s in order.
2413 for (unsigned pos = 0, e = shape.size(); pos < e; ++pos)
2414 if (!dimsToProject.test(pos))
2415 projectedShape.push_back(shape[pos]);
2416 inferredType =
2417 RankedTensorType::get(projectedShape, inferredType.getElementType(),
2418 inferredType.getEncoding());
2419 }
2420 return inferredType;
2421}
2422
2423RankedTensorType ExtractSliceOp::inferCanonicalRankReducedResultType(
2424 unsigned desiredResultRank, RankedTensorType sourceRankedTensorType,
2425 ArrayRef<OpFoldResult> sizes) {
2426 SmallVector<int64_t> staticSizes;
2427 SmallVector<Value> dynamicSizes;
2428 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2429 return ExtractSliceOp::inferCanonicalRankReducedResultType(
2430 desiredResultRank, sourceRankedTensorType, staticSizes);
2431}
2432
2433/// Build an ExtractSliceOp with mixed static and dynamic entries and custom
2434/// result type. If the type passed is nullptr, it is inferred.
2435void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2436 RankedTensorType resultType, Value source,
2437 ArrayRef<OpFoldResult> offsets,
2438 ArrayRef<OpFoldResult> sizes,
2439 ArrayRef<OpFoldResult> strides,
2440 ArrayRef<NamedAttribute> attrs) {
2441 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2442 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2443 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
2444 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2445 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2446 auto sourceRankedTensorType = llvm::cast<RankedTensorType>(source.getType());
2447 // Structuring implementation this way avoids duplication between builders.
2448 if (!resultType) {
2449 resultType = llvm::cast<RankedTensorType>(
2450 ExtractSliceOp::inferResultType(sourceRankedTensorType, staticSizes));
2451 }
2452 result.addAttributes(attrs);
2453 build(b, result, resultType, source, dynamicOffsets, dynamicSizes,
2454 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
2455 b.getDenseI64ArrayAttr(staticSizes),
2456 b.getDenseI64ArrayAttr(staticStrides));
2457}
2458
2459/// Build an ExtractSliceOp with mixed static and dynamic entries and inferred
2460/// result type.
2461void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2462 ArrayRef<OpFoldResult> offsets,
2463 ArrayRef<OpFoldResult> sizes,
2464 ArrayRef<OpFoldResult> strides,
2465 ArrayRef<NamedAttribute> attrs) {
2466 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2467}
2468
2469/// Build an ExtractSliceOp with mixed static and dynamic entries packed into
2470/// a Range vector.
2471void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2472 ArrayRef<Range> ranges,
2473 ArrayRef<NamedAttribute> attrs) {
2474 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
2475 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2476}
2477
2478/// Build an ExtractSliceOp with dynamic entries and custom result type. If
2479/// the type passed is nullptr, it is inferred.
2480void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2481 RankedTensorType resultType, Value source,
2482 ValueRange offsets, ValueRange sizes,
2483 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2484 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
2485 offsets, [](Value v) -> OpFoldResult { return v; });
2486 SmallVector<OpFoldResult> sizeValues =
2487 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
2488 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
2489 strides, [](Value v) -> OpFoldResult { return v; });
2490 build(b, result, resultType, source, offsetValues, sizeValues, strideValues);
2491}
2492
2493/// Build an ExtractSliceOp with dynamic entries and inferred result type.
2494void ExtractSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2495 ValueRange offsets, ValueRange sizes,
2496 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2497 build(b, result, RankedTensorType(), source, offsets, sizes, strides, attrs);
2498}
2499
2501 Operation *op,
2502 RankedTensorType expectedType) {
2503 switch (result) {
2505 return success();
2507 return op->emitError("expected rank to be smaller or equal to ")
2508 << "the other rank. ";
2510 return op->emitError("expected type to be ")
2511 << expectedType << " or a rank-reduced version. (size mismatch) ";
2513 return op->emitError("expected element type to be ")
2514 << expectedType.getElementType();
2515 default:
2516 llvm_unreachable("unexpected extract_slice op verification result");
2517 }
2518}
2519
2520/// Build an ExtractSliceOp with mixed static and dynamic sizes, inferred
2521/// result type, offsets set to 0 and strides set to 1.
2522void ExtractSliceOp::build(OpBuilder &b, OperationState &result,
2523 RankedTensorType resultType, Value source,
2524 ArrayRef<OpFoldResult> sizes,
2525 ArrayRef<NamedAttribute> attrs) {
2526 Attribute zeroIdxAttr = b.getIndexAttr(0);
2527 Attribute oneIdxAttr = b.getIndexAttr(1);
2528 SmallVector<OpFoldResult> readStrides(sizes.size(), oneIdxAttr);
2529 SmallVector<OpFoldResult> readOffsets(sizes.size(), zeroIdxAttr);
2530 build(b, result, resultType, source, readOffsets, sizes, readStrides, attrs);
2531}
2532
2533/// Verifier for ExtractSliceOp.
2534LogicalResult ExtractSliceOp::verify() {
2535 RankedTensorType sourceType = getSourceType();
2536
2537 // Verify result type against inferred type.
2538 RankedTensorType expectedType =
2539 ExtractSliceOp::inferResultType(sourceType, getMixedSizes());
2542 return produceSliceErrorMsg(result, *this, expectedType);
2543
2544 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
2545 // to the source tensor.
2546 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
2547 sourceType.getShape(), getStaticOffsets(), getStaticSizes(),
2548 getStaticStrides(), /*generateErrorMessage=*/true);
2549 if (!boundsResult.isValid)
2550 return getOperation()->emitError(boundsResult.errorMessage);
2551
2552 return success();
2553}
2554
2555llvm::SmallBitVector ExtractSliceOp::getDroppedDims() {
2556 return ::getDroppedDims(getType().getShape(), getMixedSizes());
2557}
2558
2559FailureOr<Value>
2560ExtractSliceOp::rankReduceIfNeeded(OpBuilder &b, Location loc, Value value,
2561 ArrayRef<int64_t> desiredShape) {
2562 auto sourceTensorType = llvm::dyn_cast<RankedTensorType>(value.getType());
2563 assert(sourceTensorType && "not a ranked tensor type");
2564 auto sourceShape = sourceTensorType.getShape();
2565 if (sourceShape.equals(desiredShape))
2566 return value;
2567 auto maybeRankReductionMask =
2568 mlir::computeRankReductionMask(sourceShape, desiredShape);
2569 if (!maybeRankReductionMask)
2570 return failure();
2572 b, loc, value,
2573 RankedTensorType::Builder(sourceTensorType).setShape(desiredShape));
2574}
2575
2576LogicalResult ExtractSliceOp::reifyResultShapes(
2577 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
2578 reifiedReturnShapes.resize(1);
2579 reifiedReturnShapes[0].reserve(getType().getRank());
2580 SmallVector<OpFoldResult> mixedSizes = getMixedSizes();
2581 llvm::SmallBitVector droppedDims = getDroppedDims();
2582 for (const auto &size : enumerate(mixedSizes)) {
2583 if (droppedDims.test(size.index()))
2584 continue;
2585 reifiedReturnShapes[0].push_back(size.value());
2586 }
2587 return success();
2588}
2589
2590namespace {
2591/// Pattern to rewrite an extract_slice op with tensor::Cast arguments.
2592/// This essentially pushes memref_cast past its consuming slice when
2593/// `canFoldIntoConsumerOp` is true.
2594///
2595/// Example:
2596/// ```
2597/// %0 = tensor.cast %V : tensor<16x16xf32> to tensor<?x?xf32>
2598/// %1 = tensor.extract_slice %0[0, 0][3, 4][1, 1] : tensor<?x?xf32> to
2599/// tensor<3x4xf32>
2600/// ```
2601/// is rewritten into:
2602/// ```
2603/// %0 = tensor.extract_slice %V[0, 0][3, 4][1, 1] : tensor<16x16xf32> to
2604/// tensor<3x4xf32> %1 = tensor.cast %0: tensor<3x4xf32> to tensor<3x4xf32>
2605/// ```
2606class ExtractSliceOpCastFolder final : public OpRewritePattern<ExtractSliceOp> {
2607public:
2608 using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
2609
2610 LogicalResult matchAndRewrite(ExtractSliceOp sliceOp,
2611 PatternRewriter &rewriter) const override {
2612 // Any constant operand, just return to let the constant folder kick in.
2613 if (llvm::any_of(sliceOp.getOperands(), [](Value operand) {
2614 return matchPattern(operand, matchConstantIndex());
2615 }))
2616 return failure();
2617
2618 auto castOp = sliceOp.getSource().getDefiningOp<CastOp>();
2619 if (!castOp)
2620 return failure();
2621
2622 if (!canFoldIntoConsumerOp(castOp))
2623 return failure();
2624
2625 // Pattern does not apply if the produced op would not verify.
2626 SliceBoundsVerificationResult sliceResult = verifyInBoundsSlice(
2627 cast<RankedTensorType>(castOp.getSource().getType()).getShape(),
2628 sliceOp.getStaticOffsets(), sliceOp.getStaticSizes(),
2629 sliceOp.getStaticStrides());
2630 if (!sliceResult.isValid)
2631 return failure();
2632
2633 // Create folded extract.
2634 Location loc = sliceOp.getLoc();
2635 Value newResult = ExtractSliceOp::create(
2636 rewriter, loc, sliceOp.getType(), castOp.getSource(),
2637 sliceOp.getOffsets(), sliceOp.getSizes(), sliceOp.getStrides(),
2638 sliceOp.getStaticOffsets(), sliceOp.getStaticSizes(),
2639 sliceOp.getStaticStrides());
2640 rewriter.replaceOp(sliceOp, newResult);
2641 return success();
2642 }
2643};
2644
2645/// Slice elements from `values` into `outValues`. `counts` represents the
2646/// numbers of elements to stride in the original values for each dimension.
2647/// The output values can be used to construct a DenseElementsAttr.
2648template <typename IterTy, typename ElemTy>
2649static void sliceElements(IterTy values, ArrayRef<int64_t> counts,
2650 ArrayRef<int64_t> offsets, ArrayRef<int64_t> sizes,
2651 ArrayRef<int64_t> strides,
2652 llvm::SmallVectorImpl<ElemTy> *outValues) {
2653 assert(offsets.size() == sizes.size());
2654 assert(offsets.size() == strides.size());
2655 if (offsets.empty())
2656 return;
2657
2658 int64_t offset = offsets.front();
2659 int64_t size = sizes.front();
2660 int64_t stride = strides.front();
2661 if (offsets.size() == 1) {
2662 for (int64_t i = 0; i < size; ++i, offset += stride)
2663 outValues->push_back(*(values + offset));
2664
2665 return;
2666 }
2667
2668 for (int64_t i = 0; i < size; ++i, offset += stride) {
2669 auto begin = values + offset * counts.front();
2670 sliceElements<IterTy, ElemTy>(begin, counts.drop_front(),
2671 offsets.drop_front(), sizes.drop_front(),
2672 strides.drop_front(), outValues);
2673 }
2674}
2675
2676/// Fold arith.constant and tensor.extract_slice into arith.constant. The
2677/// folded operation might introduce more constant data; Users can control
2678/// their heuristics by the control function.
2679class ConstantOpExtractSliceFolder final
2680 : public OpRewritePattern<ExtractSliceOp> {
2681public:
2682 using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
2683
2684 ConstantOpExtractSliceFolder(MLIRContext *context,
2686 : OpRewritePattern<ExtractSliceOp>(context),
2687 controlFn(std::move(controlFn)) {}
2688
2689 LogicalResult matchAndRewrite(ExtractSliceOp op,
2690 PatternRewriter &rewriter) const override {
2691 DenseElementsAttr attr;
2692 if (!matchPattern(op.getSource(), m_Constant(&attr)))
2693 return failure();
2694
2695 // A constant splat is handled by fold().
2696 if (attr.isSplat())
2697 return failure();
2698
2699 // Dynamic result shape is not supported.
2700 auto sourceType = llvm::cast<ShapedType>(op.getSource().getType());
2701 auto resultType = llvm::cast<ShapedType>(op.getResult().getType());
2702 if (!sourceType.hasStaticShape() || !resultType.hasStaticShape())
2703 return failure();
2704
2705 // Customized control over the folding.
2706 if (!controlFn(op))
2707 return failure();
2708
2709 int64_t count = sourceType.getNumElements();
2710 if (count == 0)
2711 return failure();
2712
2713 // Check if there are any dynamic parts, which are not supported.
2714 auto offsets = op.getStaticOffsets();
2715 if (llvm::is_contained(offsets, ShapedType::kDynamic))
2716 return failure();
2717 auto sizes = op.getStaticSizes();
2718 if (llvm::is_contained(sizes, ShapedType::kDynamic))
2719 return failure();
2720 auto strides = op.getStaticStrides();
2721 if (llvm::is_contained(strides, ShapedType::kDynamic))
2722 return failure();
2723
2724 // Compute the stride for each dimension.
2725 SmallVector<int64_t> counts;
2726 ArrayRef<int64_t> shape = sourceType.getShape();
2727 counts.reserve(shape.size());
2728 for (int64_t v : shape) {
2729 count = count / v;
2730 counts.push_back(count);
2731 }
2732
2733 // Slice the elements and construct a new attribute.
2734 SmallVector<Attribute> outValues;
2735 outValues.reserve(resultType.getNumElements());
2736 sliceElements(attr.value_begin<Attribute>(), counts, offsets, sizes,
2737 strides, &outValues);
2738 auto newAttr = DenseElementsAttr::get(resultType, outValues);
2739 rewriter.replaceOpWithNewOp<arith::ConstantOp>(op, resultType, newAttr);
2740 return success();
2741 }
2742
2743private:
2744 /// This additionally controls whether the fold happens or not. Users can
2745 /// impose their heuristics in the function.
2747};
2748
2749} // namespace
2750
2752 RewritePatternSet &patterns,
2753 const ControlConstantExtractSliceFusionFn &controlFn) {
2754 patterns.add<ConstantOpExtractSliceFolder>(patterns.getContext(), controlFn);
2755}
2756
2757/// Return the canonical type of the result of an extract_slice op.
2759 RankedTensorType operator()(ExtractSliceOp op,
2760 ArrayRef<OpFoldResult> mixedOffsets,
2761 ArrayRef<OpFoldResult> mixedSizes,
2762 ArrayRef<OpFoldResult> mixedStrides) {
2763 // Infer a tensor type without taking into account any rank reductions.
2764 RankedTensorType nonReducedType =
2765 ExtractSliceOp::inferResultType(op.getSourceType(), mixedSizes);
2766
2767 // Directly return the non-rank reduced type if there are no dropped
2768 // dims.
2769 llvm::SmallBitVector droppedDims = op.getDroppedDims();
2770 if (droppedDims.none())
2771 return nonReducedType;
2772
2773 // Build the reduced shape, preserving the original rank reduction pattern.
2774 SmallVector<int64_t> targetShape;
2775 for (auto i : llvm::seq<int64_t>(mixedSizes.size()))
2776 if (!droppedDims.test(i))
2777 targetShape.push_back(nonReducedType.getDimSize(i));
2778
2779 return RankedTensorType::get(targetShape, nonReducedType.getElementType(),
2780 nonReducedType.getEncoding());
2781 }
2782};
2783
2784/// A canonicalizer wrapper to replace ExtractSliceOps.
2786 void operator()(PatternRewriter &rewriter, ExtractSliceOp op,
2787 ExtractSliceOp newOp) {
2788 Value replacement = newOp.getResult();
2789 if (replacement.getType() != op.getType())
2790 replacement = tensor::CastOp::create(rewriter, op.getLoc(), op.getType(),
2791 replacement);
2792 rewriter.replaceOp(op, replacement);
2793 }
2794};
2795
2796void ExtractSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
2797 MLIRContext *context) {
2798 results.add<
2799 OpWithOffsetSizesAndStridesConstantArgumentFolder<
2800 ExtractSliceOp, SliceReturnTypeCanonicalizer, SliceCanonicalizer>,
2801 ExtractSliceOpCastFolder>(context);
2802}
2803
2804//
2805static LogicalResult
2806foldIdentityOffsetSizeAndStrideOpInterface(OffsetSizeAndStrideOpInterface op,
2807 ShapedType shapedType) {
2808 OpBuilder b(op.getContext());
2809 for (OpFoldResult ofr : op.getMixedOffsets())
2810 if (getConstantIntValue(ofr) != static_cast<int64_t>(0))
2811 return failure();
2812 // Rank-reducing noops only need to inspect the leading dimensions:
2813 // llvm::zip is appropriate.
2814 auto shape = shapedType.getShape();
2815 for (auto it : llvm::zip(op.getMixedSizes(), shape))
2816 if (getConstantIntValue(std::get<0>(it)) != std::get<1>(it))
2817 return failure();
2818 for (OpFoldResult ofr : op.getMixedStrides())
2819 if (getConstantIntValue(ofr) != static_cast<int64_t>(1))
2820 return failure();
2821 return success();
2822}
2823
2824/// If we have an ExtractSliceOp consuming an InsertSliceOp with the same
2825/// slice, we can return the InsertSliceOp's source directly.
2826// TODO: This only checks the immediate producer; extend to go up the
2827// insert/extract chain if the slices are disjoint.
2828static Value foldExtractAfterInsertSlice(ExtractSliceOp extractOp) {
2829 auto insertOp = extractOp.getSource().getDefiningOp<InsertSliceOp>();
2830
2831 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2832 if (insertOp && insertOp.getSource().getType() == extractOp.getType() &&
2833 insertOp.isSameAs(extractOp, isSame))
2834 return insertOp.getSource();
2835
2836 return {};
2837}
2838
2839OpFoldResult ExtractSliceOp::fold(FoldAdaptor adaptor) {
2840 if (OpFoldResult reshapedSource = reshapeConstantSource(
2841 llvm::dyn_cast_if_present<SplatElementsAttr>(adaptor.getSource()),
2842 getResult().getType()))
2843 return reshapedSource;
2844 if (getSourceType() == getType() &&
2846 return this->getSource();
2847 if (Value slice = foldExtractAfterInsertSlice(*this))
2848 return slice;
2849
2850 return OpFoldResult();
2851}
2852
2854 OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType) {
2855 auto rankedTensorType = llvm::cast<RankedTensorType>(tensor.getType());
2856 unsigned rank = rankedTensorType.getRank();
2857 SmallVector<OpFoldResult> offsets(rank, b.getIndexAttr(0));
2859 SmallVector<OpFoldResult> strides(rank, b.getIndexAttr(1));
2860 return b.createOrFold<tensor::ExtractSliceOp>(loc, targetType, tensor,
2861 offsets, sizes, strides);
2862}
2863
2864//===----------------------------------------------------------------------===//
2865// InsertSliceOp
2866//===----------------------------------------------------------------------===//
2867
2868void InsertSliceOp::getAsmResultNames(
2869 function_ref<void(Value, StringRef)> setNameFn) {
2870 setNameFn(getResult(), "inserted_slice");
2871}
2872
2873// Build a InsertSliceOp with mixed static and dynamic entries.
2874void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2875 Value dest, ArrayRef<OpFoldResult> offsets,
2877 ArrayRef<OpFoldResult> strides,
2879 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
2880 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
2881 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
2882 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
2883 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
2884 result.addAttributes(attrs);
2885 build(b, result, dest.getType(), source, dest, dynamicOffsets, dynamicSizes,
2886 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
2887 b.getDenseI64ArrayAttr(staticSizes),
2888 b.getDenseI64ArrayAttr(staticStrides));
2889}
2890
2891/// Build an InsertSliceOp with mixed static and dynamic entries packed into a
2892/// Range vector.
2893void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2894 Value dest, ArrayRef<Range> ranges,
2895 ArrayRef<NamedAttribute> attrs) {
2896 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
2897 build(b, result, source, dest, offsets, sizes, strides, attrs);
2898}
2899
2900// Build a InsertSliceOp with dynamic entries.
2901void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
2902 Value dest, ValueRange offsets, ValueRange sizes,
2903 ValueRange strides, ArrayRef<NamedAttribute> attrs) {
2904 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
2905 offsets, [](Value v) -> OpFoldResult { return v; });
2906 SmallVector<OpFoldResult> sizeValues =
2907 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
2908 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
2909 strides, [](Value v) -> OpFoldResult { return v; });
2910 build(b, result, source, dest, offsetValues, sizeValues, strideValues);
2911}
2912
2913/// Rank-reducing type verification for both InsertSliceOp and
2914/// ParallelInsertSliceOp.
2916 RankedTensorType srcType, RankedTensorType dstType,
2917 ArrayRef<int64_t> staticOffsets, ArrayRef<int64_t> staticSizes,
2918 ArrayRef<int64_t> staticStrides, RankedTensorType *expectedType = nullptr) {
2919 // insert_slice is the inverse of extract_slice, use the same type
2920 // inference.
2921 RankedTensorType expected =
2922 ExtractSliceOp::inferResultType(dstType, staticSizes);
2923 if (expectedType)
2924 *expectedType = expected;
2925 return isRankReducedType(expected, srcType);
2926}
2927
2928/// Verifier for InsertSliceOp.
2929LogicalResult InsertSliceOp::verify() {
2930 // Verify result type against inferred type.
2931 RankedTensorType expectedType;
2933 verifyInsertSliceOp(getSourceType(), getType(), getStaticOffsets(),
2934 getStaticSizes(), getStaticStrides(), &expectedType);
2936 return produceSliceErrorMsg(result, *this, expectedType);
2937
2938 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
2939 // to the destination tensor.
2940 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
2941 getDestType().getShape(), getStaticOffsets(), getStaticSizes(),
2942 getStaticStrides(), /*generateErrorMessage=*/true);
2943 if (!boundsResult.isValid)
2944 return getOperation()->emitError(boundsResult.errorMessage);
2945
2946 return success();
2947}
2948
2949/// If we have two consecutive InsertSliceOp writing to the same slice, we
2950/// can mutate the second InsertSliceOp's destination to the first one's.
2951///
2952/// Example:
2953///
2954/// ```mlir
2955/// %0 = tensor.insert_slice %slice0 into %input[0, 0] [64, 64] [1, 1]
2956/// %1 = tensor.insert_slice %slice1 into %0[0, 0] [64, 64] [1, 1]
2957/// ```
2958///
2959/// folds into:
2960///
2961/// ```mlir
2962/// %1 = tensor.insert_slice %slice1 into %input[0, 0] [64, 64] [1, 1]
2963/// ```
2964///
2965/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
2966static LogicalResult foldInsertAfterInsertSlice(InsertSliceOp insertOp) {
2967 auto prevInsertOp = insertOp.getDest().getDefiningOp<InsertSliceOp>();
2968
2969 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2970 if (!prevInsertOp ||
2971 prevInsertOp.getSource().getType() != insertOp.getSource().getType() ||
2972 !prevInsertOp.isSameAs(insertOp, isSame))
2973 return failure();
2974
2975 insertOp.getDestMutable().assign(prevInsertOp.getDest());
2976 return success();
2977}
2978
2979/// Folds round-trip extract/insert slice op pairs.
2980/// Example:
2981/// ```mlir
2982/// %0 = tensor.extract_slice %val[0, 0, 0, 0] [1, 1, 2, 4] [1, 1, 1, 1]
2983/// %1 = tensor.insert_slice %0 into %val[0, 0, 0, 0] [1, 1, 2, 4] [1, 1, 1, 1]
2984/// ```
2985/// can be folded into %val.
2986static Value foldInsertAfterExtractSlice(InsertSliceOp insertOp) {
2987 auto extractOp = insertOp.getSource().getDefiningOp<ExtractSliceOp>();
2988
2989 auto isSame = [](OpFoldResult a, OpFoldResult b) { return a == b; };
2990 if (!extractOp || extractOp.getSource() != insertOp.getDest() ||
2991 !extractOp.isSameAs(insertOp, isSame))
2992 return nullptr;
2993
2994 return extractOp.getSource();
2995}
2996
2997OpFoldResult InsertSliceOp::fold(FoldAdaptor) {
2998 if (getSourceType().hasStaticShape() && getType().hasStaticShape() &&
2999 getSourceType() == getType() &&
3001 return this->getSource();
3002 if (succeeded(foldInsertAfterInsertSlice(*this)))
3003 return getResult();
3004 if (auto result = foldInsertAfterExtractSlice(*this))
3005 return result;
3006 if (llvm::any_of(getMixedSizes(), isZeroInteger))
3007 return getDest();
3008 return OpFoldResult();
3009}
3010
3011LogicalResult InsertSliceOp::reifyResultShapes(
3012 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3013 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
3014 reifiedReturnShapes[0] = tensor::getMixedSizes(builder, getLoc(), getDest());
3015 return success();
3016}
3017
3018namespace {
3019/// Pattern to rewrite a insert_slice op with constant arguments.
3020///
3021/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
3022template <typename InsertOpTy>
3023class InsertSliceOpConstantArgumentFolder final
3024 : public OpRewritePattern<InsertOpTy> {
3025public:
3026 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3027
3028 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3029 PatternRewriter &rewriter) const override {
3030 SmallVector<OpFoldResult> mixedOffsets(insertSliceOp.getMixedOffsets());
3031 SmallVector<OpFoldResult> mixedSizes(insertSliceOp.getMixedSizes());
3032 SmallVector<OpFoldResult> mixedStrides(insertSliceOp.getMixedStrides());
3033
3034 // No constant operands were folded, just return;
3035 if (failed(foldDynamicOffsetSizeList(mixedOffsets)) &&
3036 failed(foldDynamicOffsetSizeList(mixedSizes)) &&
3037 failed(foldDynamicStrideList(mixedStrides)))
3038 return failure();
3039
3040 // Pattern does not apply if the produced op would not verify.
3041 SliceBoundsVerificationResult sliceResult =
3042 verifyInBoundsSlice(insertSliceOp.getDest().getType().getShape(),
3043 mixedOffsets, mixedSizes, mixedStrides);
3044 if (!sliceResult.isValid)
3045 return failure();
3046
3047 // Create the new op in canonical form. The refined shape is inferred from
3048 // the destination type, but the encoding is a per-value property of the
3049 // source: insert_slice does not convert between encodings, so the
3050 // produced cast/op must carry the source's encoding (dropping it would
3051 // silently discard downstream metadata such as bounds, layout, or
3052 // sparsity descriptors). If the source's encoding no longer holds on the
3053 // refined shape (e.g. a `VerifiableTensorEncoding` that self-invalidates),
3054 // it is dropped in accordance with the encoding's own contract.
3055 auto sourceTypeBase = ExtractSliceOp::inferCanonicalRankReducedResultType(
3056 insertSliceOp.getSourceType().getRank(), insertSliceOp.getDestType(),
3057 mixedSizes);
3058 auto sourceType = RankedTensorType::get(
3059 sourceTypeBase.getShape(), sourceTypeBase.getElementType(),
3060 propagateEncoding(insertSliceOp.getSourceType().getEncoding(),
3061 sourceTypeBase.getShape(),
3062 sourceTypeBase.getElementType()));
3063 Value toInsert = insertSliceOp.getSource();
3064 if (sourceType != insertSliceOp.getSourceType()) {
3065 OpBuilder::InsertionGuard g(rewriter);
3066 // The only difference between InsertSliceOp and ParallelInsertSliceOp
3067 // is that the insertion point is just before the InParallelOp in
3068 // the parallel case.
3069 if (isa<InParallelOpInterface>(insertSliceOp->getParentOp()))
3070 rewriter.setInsertionPoint(insertSliceOp->getParentOp());
3071 toInsert = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3072 sourceType, toInsert);
3073 }
3074 rewriter.replaceOpWithNewOp<InsertOpTy>(
3075 insertSliceOp, toInsert, insertSliceOp.getDest(), mixedOffsets,
3076 mixedSizes, mixedStrides);
3077 return success();
3078 }
3079};
3080
3081/// Fold tensor_casts with insert_slice operations. If the source or
3082/// destination tensor is a tensor_cast that removes static type information,
3083/// the cast is folded into the insert_slice operation. E.g.:
3084///
3085/// ```mlir
3086/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
3087/// %2 = tensor.insert_slice %1 into ... : tensor<?x?xf32> into ...
3088/// ```
3089///
3090/// folds into:
3091///
3092/// ```mlir
3093/// %2 = tensor.insert_slice %0 into ... : tensor<8x16xf32> into ...
3094/// ```
3095///
3096/// Note: When folding a cast on the destination tensor, the result of the
3097/// insert_slice operation is casted to ensure that the type of the result did
3098/// not change.
3099///
3100/// This pattern works with both InsertSliceOp and ParallelInsertSliceOp.
3101template <typename InsertOpTy>
3102struct InsertSliceOpCastFolder final : public OpRewritePattern<InsertOpTy> {
3103 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3104
3105 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3106 PatternRewriter &rewriter) const override {
3107 if (llvm::any_of(insertSliceOp.getOperands(), [](Value operand) {
3108 return matchPattern(operand, matchConstantIndex());
3109 }))
3110 return failure();
3111
3112 auto getSourceOfCastOp = [](Value v) -> std::optional<Value> {
3113 auto castOp = v.getDefiningOp<tensor::CastOp>();
3114 if (!castOp || !canFoldIntoConsumerOp(castOp))
3115 return std::nullopt;
3116 return castOp.getSource();
3117 };
3118 std::optional<Value> sourceCastSource =
3119 getSourceOfCastOp(insertSliceOp.getSource());
3120 std::optional<Value> destCastSource =
3121 getSourceOfCastOp(insertSliceOp.getDest());
3122 if (!sourceCastSource && !destCastSource)
3123 return failure();
3124
3125 auto src =
3126 (sourceCastSource ? *sourceCastSource : insertSliceOp.getSource());
3127 auto dst = (destCastSource ? *destCastSource : insertSliceOp.getDest());
3128 auto srcType = llvm::dyn_cast<RankedTensorType>(src.getType());
3129 auto dstType = llvm::dyn_cast<RankedTensorType>(dst.getType());
3130 if (!srcType || !dstType)
3131 return failure();
3132
3133 // The tensor.cast source could have additional static information not seen
3134 // in the insert slice op static sizes, so we ignore dynamic dims when
3135 // computing the rank reduction mask.
3136 SmallVector<int64_t> staticSizes(insertSliceOp.getStaticSizes());
3137 auto rankReductionMask = computeRankReductionMask(
3138 staticSizes, srcType.getShape(), /*matchDynamic=*/true);
3139 if (!rankReductionMask.has_value())
3140 return failure();
3141 // Replace dimensions in the insert slice op with corresponding static dims
3142 // from the cast source type. If the insert slice sizes have static dims
3143 // that are not static in the tensor.cast source (i.e., when the cast op
3144 // casts a dynamic dim to static), the dim should not be replaced, and the
3145 // pattern will fail later in `verifyInsertSliceOp`.
3146 SmallVector<OpFoldResult> mixedSizes(insertSliceOp.getMixedSizes());
3147 int64_t rankReducedIdx = 0;
3148 for (auto [idx, size] : enumerate(staticSizes)) {
3149 if (!rankReductionMask.value().contains(idx) &&
3150 !srcType.isDynamicDim(rankReducedIdx)) {
3151 mixedSizes[idx] = getAsIndexOpFoldResult(
3152 rewriter.getContext(), srcType.getDimSize(rankReducedIdx));
3153 size = srcType.getDimSize(rankReducedIdx++);
3154 }
3155 }
3156
3157 // Pattern does not apply if the produced op would not verify.
3158 if (verifyInsertSliceOp(srcType, dstType, insertSliceOp.getStaticOffsets(),
3159 staticSizes, insertSliceOp.getStaticStrides()) !=
3160 SliceVerificationResult::Success)
3161 return failure();
3162 SliceBoundsVerificationResult sliceResult =
3163 verifyInBoundsSlice(dstType.getShape(), insertSliceOp.getMixedOffsets(),
3164 mixedSizes, insertSliceOp.getMixedStrides());
3165 if (!sliceResult.isValid)
3166 return failure();
3167
3168 Operation *replacement =
3169 InsertOpTy::create(rewriter, insertSliceOp.getLoc(), src, dst,
3170 insertSliceOp.getMixedOffsets(), mixedSizes,
3171 insertSliceOp.getMixedStrides());
3172
3173 // In the parallel case there is no result and so nothing to cast.
3174 bool isParallelInsert =
3175 std::is_same<InsertOpTy, ParallelInsertSliceOp>::value;
3176 if (!isParallelInsert && dst.getType() != insertSliceOp.getDestType()) {
3177 replacement = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3178 insertSliceOp.getDestType(),
3179 replacement->getResult(0));
3180 }
3181 rewriter.replaceOp(insertSliceOp, replacement->getResults());
3182 return success();
3183 }
3184};
3185
3186/// If additional static type information can be deduced from a insert_slice's
3187/// size operands, insert an explicit cast of the op's source operand. This
3188/// enables other canonicalization patterns that are matching for tensor_cast
3189/// ops such as `ForOpTensorCastFolder` in SCF.
3190///
3191/// Example:
3192///
3193/// ```mlir
3194/// %r = tensor.insert_slice %0 into %1[...] [64, 64] [1, 1]
3195/// : tensor<?x?xf32> into ...
3196/// ```
3197///
3198/// folds into:
3199///
3200/// ```mlir
3201/// %tmp = tensor.cast %0 : tensor<?x?xf32> to tensor<64x64xf32>
3202/// %r = tensor.insert_slice %tmp into %1[...] [64, 64] [1, 1]
3203/// : tensor<64x64xf32> into ...
3204/// ```
3205///
3206/// This patterns works with both InsertSliceOp and ParallelInsertSliceOp.
3207template <typename InsertOpTy>
3208struct InsertSliceOpSourceCastInserter final
3209 : public OpRewritePattern<InsertOpTy> {
3210 using OpRewritePattern<InsertOpTy>::OpRewritePattern;
3211
3212 LogicalResult matchAndRewrite(InsertOpTy insertSliceOp,
3213 PatternRewriter &rewriter) const override {
3214 RankedTensorType srcType = insertSliceOp.getSourceType();
3215 if (srcType.getRank() != insertSliceOp.getDestType().getRank())
3216 return failure();
3217 SmallVector<int64_t> newSrcShape(srcType.getShape());
3218 for (int64_t i = 0; i < srcType.getRank(); ++i) {
3219 if (std::optional<int64_t> constInt =
3220 getConstantIntValue(insertSliceOp.getMixedSizes()[i])) {
3221 // Bail on invalid IR.
3222 if (*constInt < 0)
3223 return failure();
3224 newSrcShape[i] = *constInt;
3225 }
3226 }
3227 if (!hasValidSizesOffsets(newSrcShape))
3228 return failure();
3229
3230 RankedTensorType newSrcType = RankedTensorType::get(
3231 newSrcShape, srcType.getElementType(), srcType.getEncoding());
3232 if (srcType == newSrcType ||
3233 !preservesStaticInformation(srcType, newSrcType) ||
3234 !tensor::CastOp::areCastCompatible(srcType, newSrcType))
3235 return failure();
3236
3237 // newSrcType is:
3238 // 1) Different from srcType.
3239 // 2) "More static" than srcType.
3240 // 3) Cast-compatible with srcType.
3241 // Insert the cast.
3242 OpBuilder::InsertionGuard g(rewriter);
3243 // The only difference between InsertSliceOp and ParallelInsertSliceOp is
3244 // that the insertion point is just before the InParallelOp in the
3245 // parallel case.
3246 if (isa<ParallelCombiningOpInterface>(insertSliceOp->getParentOp()))
3247 rewriter.setInsertionPoint(insertSliceOp->getParentOp());
3248 Value cast = tensor::CastOp::create(rewriter, insertSliceOp.getLoc(),
3249 newSrcType, insertSliceOp.getSource());
3250 rewriter.replaceOpWithNewOp<InsertOpTy>(
3251 insertSliceOp, cast, insertSliceOp.getDest(),
3252 insertSliceOp.getMixedOffsets(), insertSliceOp.getMixedSizes(),
3253 insertSliceOp.getMixedStrides());
3254 return success();
3255 }
3256};
3257} // namespace
3258
3259llvm::SmallBitVector InsertSliceOp::getDroppedDims() {
3260 return ::getDroppedDims(getSourceType().getShape(), getMixedSizes());
3261}
3262
3263void InsertSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
3264 MLIRContext *context) {
3265 results.add<InsertSliceOpConstantArgumentFolder<InsertSliceOp>,
3266 InsertSliceOpCastFolder<InsertSliceOp>,
3267 InsertSliceOpSourceCastInserter<InsertSliceOp>>(context);
3268}
3269
3271 Location loc,
3272 Value tensor,
3273 Value dest) {
3274 auto rankedTensorType = llvm::cast<RankedTensorType>(dest.getType());
3275 unsigned rank = rankedTensorType.getRank();
3276 SmallVector<OpFoldResult> offsets(rank, b.getIndexAttr(0));
3277 SmallVector<OpFoldResult> sizes = getMixedSizes(b, loc, dest);
3278 SmallVector<OpFoldResult> strides(rank, b.getIndexAttr(1));
3279 return b.createOrFold<tensor::InsertSliceOp>(loc, tensor, dest, offsets,
3280 sizes, strides);
3281}
3282
3283//===----------------------------------------------------------------------===//
3284// PadOp
3285//===----------------------------------------------------------------------===//
3286
3287void PadOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
3288 setNameFn(getResult(), "padded");
3289}
3290
3291LogicalResult PadOp::verify() {
3292 auto sourceType = llvm::cast<RankedTensorType>(getSource().getType());
3293 auto resultType = llvm::cast<RankedTensorType>(getResult().getType());
3294 auto expectedType =
3295 PadOp::inferResultType(sourceType, getStaticLow(), getStaticHigh());
3296 if (!expectedType) {
3297 return emitError("failed to infer expectedType from sourceType ")
3298 << sourceType << ", specified resultType is " << resultType;
3299 }
3300 if (resultType.getRank() != expectedType.getRank()) {
3301 return emitError("specified type ")
3302 << resultType << " does not match the inferred type "
3303 << expectedType;
3304 }
3305 for (int i = 0, e = sourceType.getRank(); i < e; ++i) {
3306 if (resultType.getDimSize(i) == expectedType.getDimSize(i))
3307 continue;
3308 if (expectedType.isDynamicDim(i))
3309 continue;
3310 return emitError("specified type ")
3311 << resultType << " does not match the inferred type "
3312 << expectedType;
3313 }
3314
3315 return success();
3316}
3317
3318LogicalResult PadOp::verifyRegions() {
3319 auto &region = getRegion();
3320 unsigned rank = llvm::cast<RankedTensorType>(getResult().getType()).getRank();
3321 Block &block = region.front();
3322 if (block.getNumArguments() != rank)
3323 return emitError("expected the block to have ") << rank << " arguments";
3324
3325 // Note: the number and type of yield values are checked in the YieldOp.
3326 for (const auto &en : llvm::enumerate(block.getArgumentTypes())) {
3327 if (!en.value().isIndex())
3328 return emitOpError("expected block argument ")
3329 << (en.index() + 1) << " to be an index";
3330 }
3331
3332 // Ensure that the region yields an element of the right type.
3333 auto yieldOp = llvm::cast<YieldOp>(block.getTerminator());
3334 if (yieldOp.getValue().getType() !=
3335 llvm::cast<ShapedType>(getType()).getElementType())
3336 return emitOpError("expected yield type to match shape element type");
3337
3338 return success();
3339}
3340
3341RankedTensorType PadOp::inferResultType(RankedTensorType sourceType,
3342 ArrayRef<int64_t> staticLow,
3343 ArrayRef<int64_t> staticHigh,
3344 ArrayRef<int64_t> resultShape) {
3345 unsigned rank = sourceType.getRank();
3346 if (staticLow.size() != rank)
3347 return RankedTensorType();
3348 if (staticHigh.size() != rank)
3349 return RankedTensorType();
3350 if (!resultShape.empty() && resultShape.size() != rank)
3351 return RankedTensorType();
3352
3353 SmallVector<int64_t, 4> inferredShape;
3354 for (auto i : llvm::seq<unsigned>(0, rank)) {
3355 if (sourceType.isDynamicDim(i) || staticLow[i] == ShapedType::kDynamic ||
3356 staticHigh[i] == ShapedType::kDynamic) {
3357 inferredShape.push_back(resultShape.empty() ? ShapedType::kDynamic
3358 : resultShape[i]);
3359 } else {
3360 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i];
3361 assert((resultShape.empty() || size == resultShape[i] ||
3362 resultShape[i] == ShapedType::kDynamic) &&
3363 "mismatch between inferred shape and result shape");
3364 inferredShape.push_back(size);
3365 }
3366 }
3367
3368 Type elementType = sourceType.getElementType();
3369 return RankedTensorType::get(
3370 inferredShape, elementType,
3371 propagateEncoding(sourceType.getEncoding(), inferredShape, elementType));
3372}
3373
3374void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3375 Value source, ArrayRef<int64_t> staticLow,
3376 ArrayRef<int64_t> staticHigh, ValueRange low, ValueRange high,
3377 bool nofold, ArrayRef<NamedAttribute> attrs) {
3378 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3379 if (!resultType)
3380 resultType = inferResultType(sourceType, staticLow, staticHigh);
3381 result.addAttributes(attrs);
3382 build(b, result, resultType, source, low, high,
3383 b.getDenseI64ArrayAttr(staticLow), b.getDenseI64ArrayAttr(staticHigh),
3384 nofold ? b.getUnitAttr() : UnitAttr());
3385}
3386
3387void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3388 Value source, ValueRange low, ValueRange high, bool nofold,
3389 ArrayRef<NamedAttribute> attrs) {
3390 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3391 unsigned rank = sourceType.getRank();
3392 SmallVector<int64_t, 4> staticVector(rank, ShapedType::kDynamic);
3393 build(b, result, resultType, source, staticVector, staticVector, low, high,
3394 nofold, attrs);
3395}
3396
3397void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3398 Value source, ArrayRef<OpFoldResult> low,
3399 ArrayRef<OpFoldResult> high, bool nofold,
3400 ArrayRef<NamedAttribute> attrs) {
3401 auto sourceType = llvm::cast<RankedTensorType>(source.getType());
3402 SmallVector<Value, 4> dynamicLow, dynamicHigh;
3403 SmallVector<int64_t, 4> staticLow, staticHigh;
3404 // staticLow and staticHigh have full information of the padding config.
3405 // This will grow staticLow and staticHigh with 1 value. If the config is
3406 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1
3407 // value as well.
3408 dispatchIndexOpFoldResults(low, dynamicLow, staticLow);
3409 dispatchIndexOpFoldResults(high, dynamicHigh, staticHigh);
3410 if (!resultType) {
3411 resultType = PadOp::inferResultType(sourceType, staticLow, staticHigh);
3412 }
3413 assert(llvm::isa<RankedTensorType>(resultType));
3414 result.addAttributes(attrs);
3415 build(b, result, resultType, source, dynamicLow, dynamicHigh,
3416 b.getDenseI64ArrayAttr(staticLow), b.getDenseI64ArrayAttr(staticHigh),
3417 nofold ? b.getUnitAttr() : UnitAttr());
3418}
3419
3420void PadOp::build(OpBuilder &b, OperationState &result, Type resultType,
3421 Value source, ArrayRef<OpFoldResult> low,
3422 ArrayRef<OpFoldResult> high, Value constantPadValue,
3423 bool nofold, ArrayRef<NamedAttribute> attrs) {
3424 build(b, result, resultType, source, low, high, nofold, attrs);
3425
3426 // Add a region and a block to yield the pad value.
3427 Region *region = result.regions[0].get();
3428 int sourceRank = llvm::cast<RankedTensorType>(source.getType()).getRank();
3429 Repeated<Type> blockArgTypes(sourceRank, b.getIndexType());
3430 SmallVector<Location> blockArgLocs(sourceRank, result.location);
3431
3432 // `builder.createBlock` changes the insertion point within the block. Create
3433 // a guard to reset the insertion point of the builder after it is destroyed.
3434 OpBuilder::InsertionGuard guard(b);
3435 b.createBlock(region, region->end(), blockArgTypes, blockArgLocs);
3436 tensor::YieldOp::create(b, result.location, constantPadValue);
3437}
3438
3439llvm::SmallBitVector PadOp::getPaddedDims() {
3440 llvm::SmallBitVector paddedDims(getSourceType().getRank());
3441 auto extractPaddedDims = [&](ArrayRef<OpFoldResult> paddingWidths) {
3442 for (const auto &en : enumerate(paddingWidths))
3443 if (getConstantIntValue(en.value()) != static_cast<int64_t>(0))
3444 paddedDims.set(en.index());
3445 };
3446 extractPaddedDims(getMixedLowPad());
3447 extractPaddedDims(getMixedHighPad());
3448 return paddedDims;
3449}
3450
3451namespace {
3452// Folds tensor.pad when padding is static zeros and the attribute
3453// doesn't request otherwise.
3454struct FoldStaticZeroPadding : public OpRewritePattern<PadOp> {
3455 using OpRewritePattern<PadOp>::OpRewritePattern;
3456
3457 LogicalResult matchAndRewrite(PadOp padTensorOp,
3458 PatternRewriter &rewriter) const override {
3459 if (!padTensorOp.hasZeroLowPad() || !padTensorOp.hasZeroHighPad())
3460 return failure();
3461 if (padTensorOp.getNofold())
3462 return failure();
3463 rewriter.replaceOpWithNewOp<tensor::CastOp>(
3464 padTensorOp, padTensorOp.getResult().getType(),
3465 padTensorOp.getSource());
3466 return success();
3467 }
3468};
3469
3470// Fold CastOp into PadOp when adding static information.
3471struct FoldSourceTensorCast : public OpRewritePattern<PadOp> {
3472 using OpRewritePattern<PadOp>::OpRewritePattern;
3473
3474 LogicalResult matchAndRewrite(PadOp padTensorOp,
3475 PatternRewriter &rewriter) const override {
3476 auto castOp = padTensorOp.getSource().getDefiningOp<tensor::CastOp>();
3477 if (!tensor::canFoldIntoConsumerOp(castOp))
3478 return failure();
3479
3480 auto newResultType = PadOp::inferResultType(
3481 llvm::cast<RankedTensorType>(castOp.getSource().getType()),
3482 padTensorOp.getStaticLow(), padTensorOp.getStaticHigh(),
3483 padTensorOp.getResultType().getShape());
3484
3485 if (newResultType == padTensorOp.getResultType()) {
3486 rewriter.modifyOpInPlace(padTensorOp, [&]() {
3487 padTensorOp.getSourceMutable().assign(castOp.getSource());
3488 });
3489 } else {
3490 auto newOp = PadOp::create(
3491 rewriter, padTensorOp->getLoc(), newResultType,
3492 padTensorOp.getSource(), padTensorOp.getStaticLow(),
3493 padTensorOp.getStaticHigh(), padTensorOp.getLow(),
3494 padTensorOp.getHigh(), padTensorOp.getNofold(),
3495 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3496 IRMapping mapper;
3497 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper);
3498
3499 rewriter.replaceOpWithNewOp<tensor::CastOp>(
3500 padTensorOp, padTensorOp.getResultType(), newOp);
3501 }
3502 return success();
3503 }
3504};
3505
3506// Fold CastOp using the result of PadOp back into the latter if it adds
3507// static information.
3508struct FoldTargetTensorCast : public OpRewritePattern<PadOp> {
3509 using OpRewritePattern<PadOp>::OpRewritePattern;
3510
3511 LogicalResult matchAndRewrite(PadOp padTensorOp,
3512 PatternRewriter &rewriter) const override {
3513 if (!padTensorOp.getResult().hasOneUse())
3514 return failure();
3515 auto tensorCastOp =
3516 dyn_cast<tensor::CastOp>(*padTensorOp->getUsers().begin());
3517 if (!tensorCastOp)
3518 return failure();
3519 if (!tensor::preservesStaticInformation(padTensorOp.getResult().getType(),
3520 tensorCastOp.getDest().getType()))
3521 return failure();
3522
3523 auto replacementOp = PadOp::create(
3524 rewriter, padTensorOp.getLoc(), tensorCastOp.getDest().getType(),
3525 padTensorOp.getSource(), padTensorOp.getStaticLow(),
3526 padTensorOp.getStaticHigh(), padTensorOp.getLow(),
3527 padTensorOp.getHigh(), padTensorOp.getNofold(),
3528 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3529 replacementOp.getRegion().takeBody(padTensorOp.getRegion());
3530
3531 rewriter.replaceOp(padTensorOp, replacementOp.getResult());
3532 rewriter.replaceOp(tensorCastOp, replacementOp.getResult());
3533 return success();
3534 }
3535};
3536
3537/// Fold chains of tensor::ExtractSliceOp, tensor::PadOp pairs that pad
3538/// different dimensions. The pattern applies if the following preconditions
3539/// hold:
3540/// 1) the tensor::ExtractSliceOps are not rank-reducing,
3541/// 2) the tensor::ExtractSliceOps have only unit-strides,
3542/// 3) the tensor::PadOps perform only high-padding,
3543/// 4) the tensor::PadOps have the same constant padding value,
3544/// 5) the tensor::PadOps do not have common padding dimensions,
3545/// 6) one tensor::ExtractSliceOp, tensor::PadOp pair has zero-padding and
3546/// zero-offset for every dimension.
3547/// 7) the tensor::ExtractSliceOp sizes match the source tensor sizes for
3548/// the
3549/// padded source dimensions.
3550///
3551/// Example:
3552///
3553/// ```mlir
3554/// %0 = tensor.extract_slice %input[16, 0] [%sz0, 64] [1, 1]
3555/// : tensor<64x64xf32> to tensor<?x64xf32>
3556/// %1 = tensor.pad %0 low[0, 0] high[%pw0, 0] { ...
3557/// } : tensor<?x64xf32> to tensor<8x64xf32>
3558/// %2 = tensor.extract_slice %1[0, 4] [8, %sz1] [1, 1]
3559/// : tensor<8x64xf32> to tensor<8x?xf32>
3560/// %res = tensor.pad %2 nofold low[0, 0] high[0, %pw1] { ...
3561/// } : tensor<8x?xf32> to tensor<8x4xf32>
3562/// ```
3563///
3564/// folds into:
3565///
3566/// ```mlir
3567/// %0 = tensor.extract_slice %input[16, 4] [%sz0, %sz1] [1, 1]
3568/// : tensor<64x64xf32> to tensor<?x?xf32>
3569/// %res = tensor.pad %0 nofold low[0, 0] high[%pw0, %pw1] { ...
3570/// } : tensor<?x?xf32> to tensor<8x4xf32>
3571/// ```
3572struct FoldOrthogonalPaddings : public OpRewritePattern<PadOp> {
3573 using OpRewritePattern<PadOp>::OpRewritePattern;
3574
3575 LogicalResult matchAndRewrite(PadOp padOp,
3576 PatternRewriter &rewriter) const override {
3577 auto innerSliceOp = padOp.getSource().getDefiningOp<ExtractSliceOp>();
3578 if (!innerSliceOp)
3579 return failure();
3580 auto outerPadOp = innerSliceOp.getSource().getDefiningOp<PadOp>();
3581 if (!outerPadOp || outerPadOp.getNofold())
3582 return failure();
3583 auto outerSliceOp = outerPadOp.getSource().getDefiningOp<ExtractSliceOp>();
3584 if (!outerSliceOp)
3585 return failure();
3586
3587 // 1) Fail if the chain is rank-reducing.
3588 int64_t rank = padOp.getSourceType().getRank();
3589 if (outerSliceOp.getSourceType().getRank() != rank) {
3590 return rewriter.notifyMatchFailure(padOp,
3591 "cannot fold rank-reducing chain");
3592 }
3593
3594 // 2) Fail if the tensor::ExtractSliceOps have non-unit strides.
3595 if (!innerSliceOp.hasUnitStride() || !outerSliceOp.hasUnitStride()) {
3596 return rewriter.notifyMatchFailure(
3597 padOp, "cannot fold non-unit stride ExtractSliceOps");
3598 }
3599
3600 // 3) Fail if the tensor::PadOps have non-zero low padding.
3601 if (!padOp.hasZeroLowPad() || !outerPadOp.hasZeroLowPad()) {
3602 return rewriter.notifyMatchFailure(padOp,
3603 "cannot fold PadOps with low padding");
3604 }
3605
3606 // 4) Fail if the tensor::PadOps padding values do not match.
3607 Attribute innerAttr, outerAttr;
3608 Value innerValue = padOp.getConstantPaddingValue();
3609 Value outerValue = outerPadOp.getConstantPaddingValue();
3610 if (!innerValue || !outerValue ||
3611 !matchPattern(innerValue, m_Constant(&innerAttr)) ||
3612 !matchPattern(outerValue, m_Constant(&outerAttr)) ||
3613 innerAttr != outerAttr) {
3614 return rewriter.notifyMatchFailure(
3615 padOp, "cannot fold PadOps with different padding values");
3616 }
3617
3618 // 5) Fail if a dimension is padded by both tensor::PadOps.
3619 llvm::SmallBitVector innerDims = padOp.getPaddedDims();
3620 llvm::SmallBitVector outerDims = outerPadOp.getPaddedDims();
3621 if (innerDims.anyCommon(outerDims)) {
3622 return rewriter.notifyMatchFailure(
3623 padOp, "cannot fold PadOps with common padding dimensions");
3624 }
3625
3626 // 6) Combine the offsets of the two tensor::ExtractSliceOps. Find the
3627 // zero-offset and zero-padding tensor::ExtractSliceOp, tensor::PadOp pair
3628 // for every dimension, and use the offset the other pair. Fail if no
3629 // zero-offset and zero-padding tensor::ExtractSliceOp, tensor::PadOp pair
3630 // exists.
3631 SmallVector<OpFoldResult> newOffsets(rank, rewriter.getIndexAttr(0));
3632 for (auto en : enumerate(newOffsets)) {
3633 OpFoldResult innerOffset = innerSliceOp.getMixedOffsets()[en.index()];
3634 OpFoldResult outerOffset = outerSliceOp.getMixedOffsets()[en.index()];
3635 if (!innerDims.test(en.index()) &&
3636 (getConstantIntValue(innerOffset) == static_cast<int64_t>(0))) {
3637 en.value() = outerOffset;
3638 continue;
3639 }
3640 if (!outerDims.test(en.index()) &&
3641 (getConstantIntValue(outerOffset) == static_cast<int64_t>(0))) {
3642 en.value() = innerOffset;
3643 continue;
3644 }
3645 return rewriter.notifyMatchFailure(
3646 padOp, "cannot find zero-offset and zero-padding pair");
3647 }
3648
3649 // 7) Combine the sizes of the two tensor::ExtractSliceOps. Take the size
3650 // of the outer tensor::ExtractSliceOp for the dimensions padded by the
3651 // outer tensor::PadOp and fail if the size of the inner
3652 // tensor::ExtractSliceOp does not match the size of the padded dimension.
3653 // Otherwise, take the size of the inner tensor::ExtractSliceOp.
3654 SmallVector<OpFoldResult> newSizes = innerSliceOp.getMixedSizes();
3655 for (auto en : enumerate(newSizes)) {
3656 if (!outerDims.test(en.index()))
3657 continue;
3658 OpFoldResult sliceSize = innerSliceOp.getMixedSizes()[en.index()];
3659 int64_t sourceSize = innerSliceOp.getSourceType().getShape()[en.index()];
3660 assert(ShapedType::isStatic(sourceSize) &&
3661 "expected padded dimension to have a static size");
3662 if (getConstantIntValue(sliceSize) != sourceSize) {
3663 return rewriter.notifyMatchFailure(
3664 padOp, "cannot fold since the inner ExtractSliceOp size does not "
3665 "match the size of the outer padding");
3666 }
3667 en.value() = outerSliceOp.getMixedSizes()[en.index()];
3668 }
3669
3670 // Combine the high paddings of the two tensor::PadOps.
3671 SmallVector<OpFoldResult> newHighPad(rank, rewriter.getIndexAttr(0));
3672 for (auto en : enumerate(newHighPad)) {
3673 if (innerDims.test(en.index()))
3674 newHighPad[en.index()] = padOp.getMixedHighPad()[en.index()];
3675 if (outerDims.test(en.index()))
3676 newHighPad[en.index()] = outerPadOp.getMixedHighPad()[en.index()];
3677 }
3678
3679 // Create a new tensor::ExtractSliceOp, tensor::PadOp pair that performs
3680 // the two paddings in one step.
3681 auto newSliceOp = ExtractSliceOp::create(
3682 rewriter, padOp.getLoc(), outerSliceOp.getSource(), newOffsets,
3683 newSizes, innerSliceOp.getMixedStrides());
3684 auto newPadOp = PadOp::create(
3685 rewriter, padOp.getLoc(), padOp.getResultType(), newSliceOp.getResult(),
3686 padOp.getMixedLowPad(), newHighPad, padOp.getNofold(),
3687 getPrunedAttributeList(padOp, PadOp::getAttributeNames()));
3688 rewriter.inlineRegionBefore(padOp.getRegion(), newPadOp.getRegion(),
3689 newPadOp.getRegion().begin());
3690 rewriter.replaceOp(padOp, newPadOp.getResult());
3691 return success();
3692 }
3693};
3694
3695struct FoldStaticPadding : public OpRewritePattern<PadOp> {
3696 using OpRewritePattern<PadOp>::OpRewritePattern;
3697
3698 LogicalResult matchAndRewrite(PadOp padTensorOp,
3699 PatternRewriter &rewriter) const override {
3700 Value input = padTensorOp.getSource();
3701 if (!llvm::isa<RankedTensorType>(input.getType()))
3702 return failure();
3703 auto inputDims = llvm::cast<RankedTensorType>(input.getType()).getShape();
3704 auto inputRank = inputDims.size();
3705
3706 auto oldResultType =
3707 dyn_cast<RankedTensorType>(padTensorOp.getResult().getType());
3708 if (!oldResultType)
3709 return failure();
3710
3711 auto outputDims = oldResultType.getShape();
3712
3713 // Extract the static info from the high and low operands.
3714 SmallVector<int64_t> constOperandsLow;
3715 SmallVector<Value> newLows;
3716 for (auto operand : padTensorOp.getLow()) {
3717 APSInt intOp;
3718 if (!matchPattern(operand, m_ConstantInt(&intOp))) {
3719 constOperandsLow.push_back(ShapedType::kDynamic);
3720 newLows.push_back(operand);
3721 continue;
3722 }
3723 constOperandsLow.push_back(intOp.getExtValue());
3724 }
3725 SmallVector<int64_t> constOperandsHigh;
3726 SmallVector<Value> newHighs;
3727 for (auto operand : padTensorOp.getHigh()) {
3728 APSInt intOp;
3729 if (!matchPattern(operand, m_ConstantInt(&intOp))) {
3730 constOperandsHigh.push_back(ShapedType::kDynamic);
3731 newHighs.push_back(operand);
3732 continue;
3733 }
3734 constOperandsHigh.push_back(intOp.getExtValue());
3735 }
3736
3737 SmallVector<int64_t> constLow(padTensorOp.getStaticLow());
3738 SmallVector<int64_t> constHigh(padTensorOp.getStaticHigh());
3739
3740 // Verify the op is well-formed.
3741 if (inputDims.size() != outputDims.size() ||
3742 inputDims.size() != constLow.size() ||
3743 inputDims.size() != constHigh.size())
3744 return failure();
3745
3746 auto lowCount = 0;
3747 auto highCount = 0;
3748 for (size_t i = 0; i < inputRank; i++) {
3749 if (constLow[i] == ShapedType::kDynamic)
3750 constLow[i] = constOperandsLow[lowCount++];
3751 if (constHigh[i] == ShapedType::kDynamic)
3752 constHigh[i] = constOperandsHigh[highCount++];
3753 }
3754
3755 auto staticLow = ArrayRef<int64_t>(constLow);
3756 auto staticHigh = ArrayRef<int64_t>(constHigh);
3757
3758 // Calculate the output sizes with the static information.
3759 SmallVector<int64_t> newOutDims;
3760 for (size_t i = 0; i < inputRank; i++) {
3761 if (outputDims[i] == ShapedType::kDynamic) {
3762 newOutDims.push_back(
3763 (staticLow[i] == ShapedType::kDynamic ||
3764 staticHigh[i] == ShapedType::kDynamic ||
3765 inputDims[i] == ShapedType::kDynamic
3766 ? ShapedType::kDynamic
3767 : inputDims[i] + staticLow[i] + staticHigh[i]));
3768 } else {
3769 newOutDims.push_back(outputDims[i]);
3770 }
3771 }
3772
3773 if (SmallVector<int64_t>(outputDims) == newOutDims ||
3774 llvm::all_of(newOutDims,
3775 [&](int64_t x) { return x == ShapedType::kDynamic; }))
3776 return failure();
3777
3778 Type elementType = padTensorOp.getType().getElementType();
3779 auto newResultType = RankedTensorType::get(
3780 newOutDims, elementType,
3781 propagateEncoding(padTensorOp.getType().getEncoding(), newOutDims,
3782 elementType));
3783 auto newOp = PadOp::create(
3784 rewriter, padTensorOp->getLoc(), newResultType, input, staticLow,
3785 staticHigh, newLows, newHighs, padTensorOp.getNofold(),
3786 getPrunedAttributeList(padTensorOp, PadOp::getAttributeNames()));
3787
3788 IRMapping mapper;
3789 padTensorOp.getRegion().cloneInto(&newOp.getRegion(), mapper);
3790 rewriter.replaceOpWithNewOp<tensor::CastOp>(padTensorOp, oldResultType,
3791 newOp);
3792
3793 return success();
3794 }
3795};
3796
3797/// Folds a chain of `tensor.pad` ops with the same constant padding value.
3798///
3799/// Example:
3800///
3801/// ```mlir
3802/// %1 = tensor.pad %0 low[0, 1] high[0, 2] {
3803/// tensor.yield %val
3804/// } : tensor<1x2xf32> to tensor<2x5xf32>
3805/// %res = tensor.pad %1 low[0, 2] high[3, 0] {
3806/// tensor.yield %val
3807/// } : tensor<1x5xf32> to tensor<5x7xf32>
3808/// ```
3809///
3810/// folds into:
3811///
3812/// ```mlir
3813/// %res = tensor.pad %0 low[0, 3] high[3, 2] {
3814/// tensor.yield %val
3815/// } : tensor<1x2xf32> to tensor<5x7xf32>
3816/// ```
3817struct FoldConsecutiveConstantPadding : public OpRewritePattern<tensor::PadOp> {
3818 using OpRewritePattern<tensor::PadOp>::OpRewritePattern;
3819
3820 LogicalResult matchAndRewrite(tensor::PadOp padOp,
3821 PatternRewriter &rewriter) const override {
3822 if (padOp.getNofold()) {
3823 return rewriter.notifyMatchFailure(padOp, "skipping unfoldable pad");
3824 }
3825
3826 auto producerPad = padOp.getSource().getDefiningOp<tensor::PadOp>();
3827 if (!producerPad || producerPad.getNofold()) {
3828 return rewriter.notifyMatchFailure(
3829 padOp, "producer is not a foldable tensor.pad op");
3830 }
3831
3832 // Fail if the tensor::PadOps padding values do not match.
3833 Value consumerPadValue = padOp.getConstantPaddingValue();
3834 Value producerPadValue = producerPad.getConstantPaddingValue();
3835 if (!consumerPadValue || !producerPadValue ||
3836 consumerPadValue != producerPadValue) {
3837 return rewriter.notifyMatchFailure(
3838 padOp,
3839 "cannot fold PadOps with different or non-constant padding values");
3840 }
3841
3842 Location loc = padOp.getLoc();
3843 AffineExpr d0, d1;
3844 bindDims(rewriter.getContext(), d0, d1);
3845
3846 // Combine the low/high paddings of the two tensor::PadOps.
3847 auto addPaddings = [&](ArrayRef<OpFoldResult> consumerPaddings,
3848 ArrayRef<OpFoldResult> producerPaddings) {
3849 SmallVector<OpFoldResult> sumPaddings;
3850 for (auto [consumerIndex, producerIndex] :
3851 llvm::zip_equal(consumerPaddings, producerPaddings)) {
3852 sumPaddings.push_back(affine::makeComposedFoldedAffineApply(
3853 rewriter, loc, d0 + d1, {consumerIndex, producerIndex}));
3854 }
3855 return sumPaddings;
3856 };
3857
3858 SmallVector<OpFoldResult> newHighPad =
3859 addPaddings(padOp.getMixedHighPad(), producerPad.getMixedHighPad());
3860 SmallVector<OpFoldResult> newLowPad =
3861 addPaddings(padOp.getMixedLowPad(), producerPad.getMixedLowPad());
3862
3863 auto newPadOp = tensor::PadOp::create(
3864 rewriter, padOp.getLoc(), padOp.getResultType(),
3865 producerPad.getSource(), newLowPad, newHighPad, padOp.getNofold(),
3866 getPrunedAttributeList(padOp, tensor::PadOp::getAttributeNames()));
3867 rewriter.inlineRegionBefore(padOp.getRegion(), newPadOp.getRegion(),
3868 newPadOp.getRegion().begin());
3869 rewriter.replaceOp(padOp, newPadOp.getResult());
3870 return success();
3871 }
3872};
3873
3874} // namespace
3875
3876LogicalResult
3877PadOp::reifyResultShapes(OpBuilder &b,
3878 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3879 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
3880 SmallVector<OpFoldResult> lp = getMixedLowPad();
3881 SmallVector<OpFoldResult> hp = getMixedHighPad();
3882 for (int64_t i = 0; i < getResultType().getRank(); ++i) {
3883 if (!getType().isDynamicDim(i)) {
3884 reifiedReturnShapes[0][i] = b.getIndexAttr(getType().getDimSize(i));
3885 continue;
3886 }
3887 Location loc = getLoc();
3888 Value dim = b.createOrFold<tensor::DimOp>(
3889 loc, getSource(), arith::ConstantIndexOp::create(b, loc, i));
3890
3891 AffineExpr d0, d1, d2;
3892 bindDims(b.getContext(), d0, d1, d2);
3893 reifiedReturnShapes[0][i] = affine::makeComposedFoldedAffineApply(
3894 b, loc, {d0 + d1 + d2}, {dim, lp[i], hp[i]});
3895 }
3896 return success();
3897}
3898
3899void PadOp::getCanonicalizationPatterns(RewritePatternSet &results,
3900 MLIRContext *context) {
3901 results.add<FoldStaticZeroPadding, FoldSourceTensorCast, FoldTargetTensorCast,
3902 FoldOrthogonalPaddings, FoldStaticPadding,
3903 FoldConsecutiveConstantPadding>(context);
3904}
3905
3906/// Return the padding value of the PadOp if it constant. In this context,
3907/// "constant" means an actual constant or "defined outside of the block".
3908///
3909/// Values are considered constant in three cases:
3910/// - A ConstantLike value.
3911/// - A basic block argument from a different block.
3912/// - A value defined outside of the block.
3913///
3914/// If the padding value is not constant, an empty Value is returned.
3915Value PadOp::getConstantPaddingValue() {
3916 auto yieldOp = dyn_cast<YieldOp>(getRegion().front().getTerminator());
3917 if (!yieldOp)
3918 return {};
3919 Value padValue = yieldOp.getValue();
3920 // Check if yield value is a constant.
3921 if (matchPattern(padValue, m_Constant()))
3922 return padValue;
3923 // Check if yield value is defined inside the PadOp block.
3924 if (padValue.getParentBlock() == &getRegion().front())
3925 return {};
3926 // Else: Yield value defined outside of the PadOp block.
3927 return padValue;
3928}
3929
3930OpFoldResult PadOp::fold(FoldAdaptor) {
3931 if (getResultType().hasStaticShape() && getResultType() == getSourceType() &&
3932 !getNofold())
3933 return getSource();
3934 return {};
3935}
3936
3937//===----------------------------------------------------------------------===//
3938// ParallelInsertSliceOp
3939//===----------------------------------------------------------------------===//
3940
3941OpResult ParallelInsertSliceOp::getTiedOpResult() {
3942 InParallelOpInterface parallelCombiningParent = getParallelCombiningParent();
3943 for (const auto &it :
3944 llvm::enumerate(parallelCombiningParent.getYieldingOps())) {
3945 Operation &nextOp = it.value();
3946 if (&nextOp == getOperation())
3947 return parallelCombiningParent.getParentResult(it.index());
3948 }
3949 llvm_unreachable("ParallelInsertSliceOp no tied OpResult found");
3950}
3951
3952// Build a ParallelInsertSliceOp with mixed static and dynamic entries.
3953void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3954 Value source, Value dest,
3955 ArrayRef<OpFoldResult> offsets,
3956 ArrayRef<OpFoldResult> sizes,
3957 ArrayRef<OpFoldResult> strides,
3958 ArrayRef<NamedAttribute> attrs) {
3959 SmallVector<int64_t> staticOffsets, staticSizes, staticStrides;
3960 SmallVector<Value> dynamicOffsets, dynamicSizes, dynamicStrides;
3961 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
3962 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticSizes);
3963 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
3964 result.addAttributes(attrs);
3965 build(b, result, {}, source, dest, dynamicOffsets, dynamicSizes,
3966 dynamicStrides, b.getDenseI64ArrayAttr(staticOffsets),
3967 b.getDenseI64ArrayAttr(staticSizes),
3968 b.getDenseI64ArrayAttr(staticStrides));
3969}
3970
3971/// Build an ParallelInsertSliceOp with mixed static and dynamic entries
3972/// packed into a Range vector.
3973void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3974 Value source, Value dest,
3975 ArrayRef<Range> ranges,
3976 ArrayRef<NamedAttribute> attrs) {
3977 auto [offsets, sizes, strides] = getOffsetsSizesAndStrides(ranges);
3978 build(b, result, source, dest, offsets, sizes, strides, attrs);
3979}
3980
3981// Build a ParallelInsertSliceOp with dynamic entries.
3982void ParallelInsertSliceOp::build(OpBuilder &b, OperationState &result,
3983 Value source, Value dest, ValueRange offsets,
3984 ValueRange sizes, ValueRange strides,
3985 ArrayRef<NamedAttribute> attrs) {
3986 SmallVector<OpFoldResult> offsetValues = llvm::map_to_vector<4>(
3987 offsets, [](Value v) -> OpFoldResult { return v; });
3988 SmallVector<OpFoldResult> sizeValues =
3989 llvm::map_to_vector<4>(sizes, [](Value v) -> OpFoldResult { return v; });
3990 SmallVector<OpFoldResult> strideValues = llvm::map_to_vector<4>(
3991 strides, [](Value v) -> OpFoldResult { return v; });
3992 build(b, result, source, dest, offsetValues, sizeValues, strideValues);
3993}
3994
3995// Build an InsertSliceOp with mixed static and dynamic sizes, offsets set
3996// to 0, strides set to 1 and inferred result type.
3997void InsertSliceOp::build(OpBuilder &b, OperationState &result, Value source,
3998 Value dest, ArrayRef<OpFoldResult> sizes,
3999 ArrayRef<NamedAttribute> attrs) {
4000 Attribute zeroIdxAttr = b.getIndexAttr(0);
4001 Attribute oneIdxAttr = b.getIndexAttr(1);
4002 SmallVector<OpFoldResult> writeStrides(sizes.size(), oneIdxAttr);
4003 SmallVector<OpFoldResult> writeOffsets(sizes.size(), zeroIdxAttr);
4004 build(b, result, source, dest, writeOffsets, sizes, writeStrides, attrs);
4005}
4006
4007LogicalResult ParallelInsertSliceOp::verify() {
4008 if (!isa<InParallelOpInterface>(getOperation()->getParentOp()))
4009 return this->emitError("expected InParallelOpInterface parent, got:")
4010 << *(getOperation()->getParentOp());
4011
4012 // Verify result type against inferred type.
4013 RankedTensorType expectedType;
4015 verifyInsertSliceOp(getSourceType(), getDestType(), getStaticOffsets(),
4016 getStaticSizes(), getStaticStrides(), &expectedType);
4018 return produceSliceErrorMsg(result, *this, expectedType);
4019
4020 // Verify that offsets, sizes, strides do not run out-of-bounds with respect
4021 // to the destination tensor.
4022 SliceBoundsVerificationResult boundsResult = verifyInBoundsSlice(
4023 getDestType().getShape(), getStaticOffsets(), getStaticSizes(),
4024 getStaticStrides(), /*generateErrorMessage=*/true);
4025 if (!boundsResult.isValid)
4026 return getOperation()->emitError(boundsResult.errorMessage);
4027
4028 return success();
4029}
4030
4031void ParallelInsertSliceOp::getCanonicalizationPatterns(
4032 RewritePatternSet &results, MLIRContext *context) {
4033 results.add<InsertSliceOpConstantArgumentFolder<ParallelInsertSliceOp>,
4034 InsertSliceOpCastFolder<ParallelInsertSliceOp>,
4035 InsertSliceOpSourceCastInserter<ParallelInsertSliceOp>>(context);
4036}
4037
4038llvm::SmallBitVector ParallelInsertSliceOp::getDroppedDims() {
4039 return ::getDroppedDims(getSourceType().getShape(), getMixedSizes());
4040}
4041
4042// ParallelCombiningOpInterface implementation.
4043MutableOperandRange ParallelInsertSliceOp::getUpdatedDestinations() {
4044 return getDestMutable();
4045}
4046
4047Operation *ParallelInsertSliceOp::getIteratingParent() {
4048 // Return the parent InParallelOpInterface's parent.
4049 if (auto combiningOp =
4050 dyn_cast<InParallelOpInterface>(getOperation()->getParentOp()))
4051 return combiningOp->getParentOp();
4052 return nullptr;
4053}
4054
4055//===----------------------------------------------------------------------===//
4056// ScatterOp
4057//===----------------------------------------------------------------------===//
4058
4059void ScatterOp::getAsmResultNames(
4060 function_ref<void(Value, StringRef)> setNameFn) {
4061 setNameFn(getResult(), "scatter");
4062}
4063
4064LogicalResult ScatterOp::verify() {
4065 int64_t destRank = getDestType().getRank();
4066 ArrayRef<int64_t> scatterDims = getScatterDims();
4067 if (failed(verifyGatherOrScatterDims(getOperation(), scatterDims,
4068 getIndicesType().getShape(), destRank,
4069 "scatter", "dest")))
4070 return failure();
4071
4072 if (!getUnique())
4073 return emitOpError("requires 'unique' attribute to be set");
4074 // TODO: we could also check statically that there are fewer leading index
4075 // tensor dims than the dest dims. If this is not the case, the unique
4076 // attribute cannot be true.
4077
4078 // Use the GatherOp::inferResultType on the `dest` type and verify the
4079 // expected type matches the source type.
4080 RankedTensorType expectedSourceType = GatherOp::inferResultType(
4081 getDestType(), getIndicesType(), scatterDims, /*rankReduced=*/false);
4082 RankedTensorType expectedRankReducedSourceType = GatherOp::inferResultType(
4083 getDestType(), getIndicesType(), scatterDims, /*rankReduced=*/true);
4084 if (getSourceType() != expectedSourceType &&
4085 getSourceType() != expectedRankReducedSourceType) {
4086 return emitOpError("source type "
4087 "mismatch: "
4088 "expected ")
4089 << expectedSourceType << " or its rank-reduced variant "
4090 << expectedRankReducedSourceType << " (got: " << getSourceType()
4091 << ")";
4092 }
4093
4094 return success();
4095}
4096
4097//===----------------------------------------------------------------------===//
4098// SplatOp
4099//===----------------------------------------------------------------------===//
4100
4101void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4102 Type aggregateType, ValueRange dynamicSizes) {
4103 build(builder, result, aggregateType, element, dynamicSizes);
4104}
4105
4106void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4107 ArrayRef<int64_t> staticShape, ValueRange dynamicSizes) {
4108 auto aggregateType = RankedTensorType::get(staticShape, element.getType());
4109 build(builder, result, aggregateType, element, dynamicSizes);
4110}
4111
4112void SplatOp::build(OpBuilder &builder, OperationState &result, Value element,
4113 ArrayRef<OpFoldResult> sizes) {
4114 SmallVector<int64_t> staticShape;
4115 SmallVector<Value> dynamicSizes;
4116 dispatchIndexOpFoldResults(sizes, dynamicSizes, staticShape);
4117 build(builder, result, element, staticShape, dynamicSizes);
4118}
4119
4120void SplatOp::getAsmResultNames(
4121 function_ref<void(Value, StringRef)> setNameFn) {
4122 setNameFn(getResult(), "splat");
4123}
4124
4125LogicalResult SplatOp::verify() {
4126 return verifyDynamicDimensionCount(getOperation(), getType(),
4127 getDynamicSizes());
4128}
4129
4130LogicalResult
4131SplatOp::reifyResultShapes(OpBuilder &builder,
4132 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
4133 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(getType().getRank()));
4134 unsigned ctr = 0;
4135 for (int64_t i = 0; i < getType().getRank(); ++i) {
4136 if (getType().isDynamicDim(i)) {
4137 reifiedReturnShapes[0][i] = getDynamicSizes()[ctr++];
4138 } else {
4139 reifiedReturnShapes[0][i] = builder.getIndexAttr(getType().getDimSize(i));
4140 }
4141 }
4142 return success();
4143}
4144
4145OpFoldResult SplatOp::fold(FoldAdaptor adaptor) {
4146 auto constOperand = adaptor.getInput();
4147 if (!isa_and_nonnull<IntegerAttr, FloatAttr>(constOperand))
4148 return {};
4149
4150 // Do not fold if the splat is not statically shaped
4151 if (!getType().hasStaticShape())
4152 return {};
4153
4154 // SplatElementsAttr::get treats single value for second arg as being a
4155 // splat.
4156 return SplatElementsAttr::get(getType(), {constOperand});
4157}
4158
4159//===----------------------------------------------------------------------===//
4160// Common Canonicalizers and Folders.
4161//===----------------------------------------------------------------------===//
4162static bool foldTensorCastPrecondition(DestinationStyleOpInterface op) {
4163 // 1. InsertSliceOp has its own logic about folding tensor.cast ops.
4164 // 2. Exclude DPS ops that are also LoopLike from this interface as they
4165 // might need special handling of attached regions.
4166 if (isa<InsertSliceOp>(op.getOperation()) ||
4167 isa<LoopLikeOpInterface>(op.getOperation()))
4168 return false;
4169
4171}
4172
4173/// Folds a tensor.cast op into a consuming DestinationStyleOpInterface op if
4174/// the `tensor.cast` has source that is more static than the consuming op.
4175///
4176/// Example:
4177/// ```mlir
4178/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
4179/// %2 = consumer %1 ... : tensor<?x?xf32> ...
4180/// ```
4181///
4182/// folds into:
4183///
4184/// ```mlir
4185/// %2 = consumer %0 ... : tensor<8x16xf32> ...
4186/// ```
4187/// TODO: Move the pattern to a proper place, so all other DestinationStyleOp
4188/// can add the pattern to their canonicalizers.
4190 : public OpInterfaceRewritePattern<DestinationStyleOpInterface> {
4192 DestinationStyleOpInterface>::OpInterfaceRewritePattern;
4193
4194 LogicalResult matchAndRewrite(DestinationStyleOpInterface op,
4195 PatternRewriter &rewriter) const override {
4196
4197 // Reject PackOp/UnpackOp (i.e. RelayoutOps) - there are dedicated patterns
4198 // for that instead.
4199 if (!foldTensorCastPrecondition(op) ||
4200 isa<linalg::RelayoutOpInterface>(*op))
4201 return failure();
4202
4203 SmallVector<Type> newResultTypes(op->getResultTypes());
4204 SmallVector<Value> newOperands =
4205 getUpdatedOperandsAfterCastOpFolding(op, newResultTypes);
4206
4207 // Clone op
4208 auto newOp = clone(rewriter, op, newResultTypes, newOperands);
4209
4210 SmallVector<Value, 4> replacements;
4211 replacements.reserve(newOp->getNumResults());
4212 for (auto [oldResult, newResult] :
4213 llvm::zip(op->getResults(), newOp->getResults())) {
4214 if (newResult.getType() != oldResult.getType()) {
4215 replacements.push_back(tensor::CastOp::create(
4216 rewriter, op->getLoc(), oldResult.getType(), newResult));
4217 } else {
4218 replacements.push_back(newResult);
4219 }
4220 }
4221 rewriter.replaceOp(op, replacements);
4222
4223 return success();
4224 }
4225};
4226
4227//===----------------------------------------------------------------------===//
4228// TensorDialect
4229//===----------------------------------------------------------------------===//
4230
4231void TensorDialect::getCanonicalizationPatterns(
4232 RewritePatternSet &results) const {
4233 results.add<FoldTensorCastProducerOp>(getContext());
4234}
4235
4236//===----------------------------------------------------------------------===//
4237// TableGen'd op method definitions
4238//===----------------------------------------------------------------------===//
4239
4240#define GET_OP_CLASSES
4241#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 Type getElementType(Type type)
Determine the element type of type.
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 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:529
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.
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:167
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.
llvm::SmallBitVector getPositionsOfShapeOne(unsigned rank, ArrayRef< int64_t > shape)
Definition Utils.cpp:93
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.