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