MLIR 24.0.0git
VectorOps.cpp
Go to the documentation of this file.
1//===- VectorOps.cpp - MLIR Vector Dialect Operations ---------------------===//
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//
9// This file implements convenience types for working with super-vectorization
10// operations, in particular super-vector loads and stores.
11//
12//===----------------------------------------------------------------------===//
13
15
29#include "mlir/IR/AffineExpr.h"
30#include "mlir/IR/AffineMap.h"
31#include "mlir/IR/Builders.h"
34#include "mlir/IR/IRMapping.h"
38#include "mlir/IR/ValueRange.h"
41#include "mlir/Support/LLVM.h"
43#include "llvm/ADT/ArrayRef.h"
44#include "llvm/ADT/Repeated.h"
45#include "llvm/ADT/STLExtras.h"
46#include "llvm/ADT/SmallVector.h"
47#include "llvm/ADT/SmallVectorExtras.h"
48#include "llvm/ADT/StringSet.h"
49#include "llvm/Support/Casting.h"
50
51#include <cassert>
52#include <cstdint>
53#include <numeric>
54
55#include "mlir/Dialect/Vector/IR/VectorDialect.cpp.inc"
56// Pull in all enum type and utility function definitions.
57#include "mlir/Dialect/Vector/IR/VectorEnums.cpp.inc"
58
59using namespace mlir;
60using namespace mlir::vector;
61
62/// Helper enum to classify mask value.
63enum class MaskFormat {
67};
68
69/// Helper method to classify a mask value. Currently, the method
70/// looks "under the hood" of a constant value with dense attributes
71/// and a constant mask operation (since the client may be called at
72/// various stages during progressive lowering).
74 if (auto c = mask.getDefiningOp<arith::ConstantOp>()) {
75 // Inspect constant dense values. We count up for bits that
76 // are set, count down for bits that are cleared, and bail
77 // when a mix is detected.
78 if (auto denseElts = llvm::dyn_cast<DenseIntElementsAttr>(c.getValue())) {
79 int64_t val = 0;
80 for (bool b : denseElts.getValues<bool>())
81 if (b && val >= 0)
82 val++;
83 else if (!b && val <= 0)
84 val--;
85 else
87 if (val > 0)
89 if (val < 0)
91 }
92 } else if (auto m = mask.getDefiningOp<ConstantMaskOp>()) {
93 // Inspect constant mask index. If the index exceeds the
94 // dimension size, all bits are set. If the index is zero
95 // or less, no bits are set.
96 ArrayRef<int64_t> masks = m.getMaskDimSizes();
97 auto shape = m.getType().getShape();
98 bool allTrue = true;
99 bool allFalse = true;
100 for (auto [maskIdx, dimSize] : llvm::zip_equal(masks, shape)) {
101 if (maskIdx < dimSize)
102 allTrue = false;
103 if (maskIdx > 0)
104 allFalse = false;
105 }
106 if (allTrue)
107 return MaskFormat::AllTrue;
108 if (allFalse)
110 } else if (auto m = mask.getDefiningOp<CreateMaskOp>()) {
111 // Finds all-false create_masks. An all-true create_mask requires all
112 // dims to be constants, so that'll be folded to a constant_mask, then
113 // detected in the constant_mask case.
114 auto maskOperands = m.getOperands();
115 for (Value operand : maskOperands) {
116 if (auto constantOp = operand.getDefiningOp<arith::ConstantOp>()) {
117 int64_t dimSize =
118 llvm::cast<IntegerAttr>(constantOp.getValue()).getInt();
119 if (dimSize <= 0)
121 }
122 }
123 return MaskFormat::Unknown;
124 }
125 return MaskFormat::Unknown;
126}
127
128/// Default callback to build a region with a 'vector.yield' terminator with no
129/// arguments.
131 vector::YieldOp::create(builder, loc);
132}
133
134// Helper for verifying combining kinds in contractions and reductions.
135static bool isSupportedCombiningKind(CombiningKind combiningKind,
136 Type elementType) {
137 switch (combiningKind) {
138 case CombiningKind::ADD:
139 case CombiningKind::MUL:
140 return elementType.isIntOrIndexOrFloat();
141 case CombiningKind::MINUI:
142 case CombiningKind::MINSI:
143 case CombiningKind::MAXUI:
144 case CombiningKind::MAXSI:
145 case CombiningKind::AND:
146 case CombiningKind::OR:
147 case CombiningKind::XOR:
148 return elementType.isIntOrIndex();
149 case CombiningKind::MINNUMF:
150 case CombiningKind::MAXNUMF:
151 case CombiningKind::MINIMUMF:
152 case CombiningKind::MAXIMUMF:
153 case CombiningKind::MINIMUMNUMF:
154 case CombiningKind::MAXIMUMNUMF:
155 return llvm::isa<FloatType>(elementType);
156 }
157 return false;
158}
159
160/// Returns the effective rank of the vector to read/write for Xfer Ops
161///
162/// When the element type of the shaped type is _a scalar_, this will simply
163/// return the rank of the vector ( the result for xfer_read or the value to
164/// store for xfer_write).
165///
166/// When the element type of the base shaped type is _a vector_, returns the
167/// difference between the original vector type and the element type of the
168/// shaped type.
169///
170/// EXAMPLE 1 (element type is _a scalar_):
171/// - shapedType = tensor<10x20xf32>, vectorType = vector<2x4xf32>
172/// - shapedType.getElementType() = f32 (rank 0)
173/// - vectorType.getRank() = 2
174/// - Result = 2 - 0 = 2
175///
176/// EXAMPLE 2 (element type is _a vector_):
177/// - shapedType = tensor<10xvector<20xf32>>, vectorType = vector<20xf32>
178/// - shapedType.getElementType() = vector<20xf32> (rank 1)
179/// - vectorType.getRank() = 1
180/// - Result = 1 - 1 = 0
181///
182/// This is used to determine the number of minor dimensions for identity maps
183/// in vector transfer Ops.
184static unsigned getEffectiveVectorRankForXferOp(ShapedType shapedType,
185 VectorType vectorType) {
186 unsigned elementVectorRank = 0;
187 VectorType elementVectorType =
188 llvm::dyn_cast<VectorType>(shapedType.getElementType());
189 if (elementVectorType)
190 elementVectorRank += elementVectorType.getRank();
191 return vectorType.getRank() - elementVectorRank;
192}
193
195 VectorType vectorType) {
196 // 0-d transfers are to/from tensor<t>/memref<t> and vector<1xt>.
197 // TODO: replace once we have 0-d vectors.
198 if (shapedType.getRank() == 0 &&
199 vectorType.getShape() == ArrayRef<int64_t>{1})
200 return AffineMap::get(
201 /*numDims=*/0, /*numSymbols=*/0,
202 getAffineConstantExpr(0, shapedType.getContext()));
204 shapedType.getRank(),
205 getEffectiveVectorRankForXferOp(shapedType, vectorType),
206 shapedType.getContext());
207}
208
209/// Check if `write` is of a constant splat and the masked `read` is padded with
210/// the same splat value -- meaning it could be the same value as the initial
211/// constant splat.
212static bool isSplatWriteConsistentWithMaskedRead(vector::TransferWriteOp write,
213 vector::TransferReadOp read) {
214 auto readMask = read.getMask();
215 auto writeMask = write.getMask();
216 // Check if the masks are consistent. The splat value could be the same if the
217 // read is masked (and padded with the splat value), and the write is unmasked
218 // or has the same mask. Note this does not allow the case where the write is
219 // masked and the read is unmasked, as then the read could be of more elements
220 // than the write (which may not be the same value).
221 bool couldBeSameSplat = readMask && (!writeMask || writeMask == readMask);
222 if (!couldBeSameSplat)
223 return false;
224 // Check for constant splat (as the source of the write).
225 DenseElementsAttr splatAttr;
226 if (!matchPattern(write.getVector(),
227 m_Constant<DenseElementsAttr>(&splatAttr)) ||
228 !splatAttr.isSplat()) {
229 return false;
230 }
231 // The padding of the read and the constant splat value must be the same.
232 Attribute padAttr;
233 if (!matchPattern(read.getPadding(), m_Constant(&padAttr)))
234 return false;
235 return padAttr == splatAttr.getSplatValue<Attribute>();
236}
237
238bool mlir::vector::checkSameValueRAW(vector::TransferWriteOp defWrite,
239 vector::TransferReadOp read) {
240 // An enclosing vector.mask may leave some lanes unwritten or padded, so the
241 // read may not see the written vector as a whole. This conservatively
242 // includes all-true masks. MaskOp::fold removes them during canonicalization,
243 // so this only matters when a caller such as transferOpflowOpt runs first.
244 // TODO: Account for compatible region masks instead of conservatively
245 // rejecting all region-masked operations.
246 if (defWrite.isMasked() || read.isMasked())
247 return false;
248 return !defWrite.hasOutOfBoundsDim() &&
249 defWrite.getIndices() == read.getIndices() &&
250 defWrite.getVectorType() == read.getVectorType() &&
251 defWrite.getPermutationMap() == read.getPermutationMap() &&
252 ((!defWrite.getMask() && !read.getMask()) ||
254}
255
256bool mlir::vector::checkSameValueWAW(vector::TransferWriteOp write,
257 vector::TransferWriteOp priorWrite) {
258 // A write under an enclosing vector.mask may not overwrite all lanes of the
259 // prior write. This conservatively includes all-true masks, which
260 // MaskOp::fold removes during canonicalization. Only the later write needs
261 // checking: a region-masked prior write is still dead if the later write
262 // fully overwrites it.
263 // TODO: Handle a region-masked later write when it is known to fully
264 // overwrite the prior write.
265 if (write.isMasked())
266 return false;
267 return priorWrite.getIndices() == write.getIndices() &&
268 priorWrite.getMask() == write.getMask() &&
269 priorWrite.getVectorType() == write.getVectorType() &&
270 priorWrite.getPermutationMap() == write.getPermutationMap();
271}
272
274 VectorTransferOpInterface transferA, VectorTransferOpInterface transferB,
275 bool testDynamicValueUsingBounds) {
276 // For simplicity only look at transfer of same type.
277 if (transferA.getVectorType() != transferB.getVectorType())
278 return false;
279 unsigned rankOffset = transferA.getLeadingShapedRank();
280 for (unsigned i = 0, e = transferA.getIndices().size(); i < e; i++) {
281 Value indexA = transferA.getIndices()[i];
282 Value indexB = transferB.getIndices()[i];
283 std::optional<int64_t> cstIndexA = getConstantIntValue(indexA);
284 std::optional<int64_t> cstIndexB = getConstantIntValue(indexB);
285
286 if (i < rankOffset) {
287 // For leading dimensions, if we can prove that index are different we
288 // know we are accessing disjoint slices.
289 if (cstIndexA.has_value() && cstIndexB.has_value()) {
290 if (*cstIndexA != *cstIndexB)
291 return true;
292 continue;
293 }
294 if (testDynamicValueUsingBounds) {
295 // First try to see if we can fully compose and simplify the affine
296 // expression as a fast track.
297 FailureOr<uint64_t> delta =
299 if (succeeded(delta) && *delta != 0)
300 return true;
301
302 FailureOr<bool> testEqual =
304 if (succeeded(testEqual) && !testEqual.value())
305 return true;
306 }
307 } else {
308 // For this dimension, we slice a part of the memref we need to make sure
309 // the intervals accessed don't overlap.
310 int64_t vectorDim = transferA.getVectorType().getDimSize(i - rankOffset);
311 if (cstIndexA.has_value() && cstIndexB.has_value()) {
312 int64_t distance = std::abs(*cstIndexA - *cstIndexB);
313 if (distance >= vectorDim)
314 return true;
315 continue;
316 }
317 if (testDynamicValueUsingBounds) {
318 // First try to see if we can fully compose and simplify the affine
319 // expression as a fast track.
320 FailureOr<int64_t> delta =
322 if (succeeded(delta) && std::abs(*delta) >= vectorDim)
323 return true;
324
325 FailureOr<int64_t> computeDelta =
327 if (succeeded(computeDelta)) {
328 if (std::abs(computeDelta.value()) >= vectorDim)
329 return true;
330 }
331 }
332 }
333 }
334 return false;
335}
336
337bool mlir::vector::isDisjointTransferSet(VectorTransferOpInterface transferA,
338 VectorTransferOpInterface transferB,
339 bool testDynamicValueUsingBounds) {
340 if (transferA.getBase() != transferB.getBase())
341 return false;
342 return isDisjointTransferIndices(transferA, transferB,
343 testDynamicValueUsingBounds);
344}
345
346// Helper to iterate over n-D vector slice elements. Calculate the next
347// `position` in the n-D vector of size `shape`, applying an offset `offsets`.
348// Modifies the `position` in place. Returns a failure when `position` becomes
349// the end position.
350static LogicalResult incSlicePosition(MutableArrayRef<int64_t> position,
352 ArrayRef<int64_t> offsets) {
353 for (auto [posInDim, dimSize, offsetInDim] :
354 llvm::reverse(llvm::zip_equal(position, shape, offsets))) {
355 ++posInDim;
356 if (posInDim < dimSize + offsetInDim)
357 return success();
358
359 // Carry the overflow to the next loop iteration.
360 posInDim = offsetInDim;
361 }
362
363 return failure();
364}
365
366/// Returns the integer numbers in `values`. `values` are expected to be
367/// constant operations.
370 llvm::transform(values, std::back_inserter(ints), [](Value value) {
371 auto constOp = value.getDefiningOp<arith::ConstantIndexOp>();
372 assert(constOp && "Unexpected non-constant index");
373 return constOp.value();
374 });
375 return ints;
376}
377
378/// Returns the integer numbers in `foldResults`. `foldResults` are expected to
379/// be constant operations.
382 llvm::transform(
383 foldResults, std::back_inserter(ints), [](OpFoldResult foldResult) {
384 assert(isa<Attribute>(foldResult) && "Unexpected non-constant index");
385 return cast<IntegerAttr>(cast<Attribute>(foldResult)).getInt();
386 });
387 return ints;
388}
389
390/// Convert `foldResults` into Values. Integer attributes are converted to
391/// constant op.
393 ArrayRef<OpFoldResult> foldResults) {
394 SmallVector<Value> values;
395 llvm::transform(foldResults, std::back_inserter(values),
396 [&](OpFoldResult foldResult) {
397 if (auto attr = dyn_cast<Attribute>(foldResult))
399 builder, loc, cast<IntegerAttr>(attr).getInt())
400 .getResult();
401
402 return cast<Value>(foldResult);
403 });
404 return values;
405}
406
407std::optional<int64_t> vector::getConstantVscaleMultiplier(Value value) {
408 if (value.getDefiningOp<vector::VectorScaleOp>())
409 return 1;
410 auto mul = value.getDefiningOp<arith::MulIOp>();
411 if (!mul)
412 return {};
413 auto lhs = mul.getLhs();
414 auto rhs = mul.getRhs();
415 if (lhs.getDefiningOp<vector::VectorScaleOp>())
416 return getConstantIntValue(rhs);
417 if (rhs.getDefiningOp<vector::VectorScaleOp>())
418 return getConstantIntValue(lhs);
419 return {};
420}
421
422/// Converts numeric attributes to the expected type. Supports
423/// integer-to-integer and float-to-integer conversions. Returns the original
424/// attribute if no conversion is needed or supported.
425static Attribute convertNumericAttr(Attribute attr, Type expectedType) {
426 // Integer-to-integer conversion
427 if (auto intAttr = dyn_cast<IntegerAttr>(attr)) {
428 if (auto intType = dyn_cast<IntegerType>(expectedType)) {
429 if (intAttr.getType() != expectedType)
430 return IntegerAttr::get(expectedType, intAttr.getInt());
431 }
432 return attr;
433 }
434
435 // Float-to-integer bitcast (preserves bit representation)
436 if (auto floatAttr = dyn_cast<FloatAttr>(attr)) {
437 auto intType = dyn_cast<IntegerType>(expectedType);
438 if (!intType)
439 return attr;
440
441 APFloat floatVal = floatAttr.getValue();
442 APInt intVal = floatVal.bitcastToAPInt();
443 return IntegerAttr::get(expectedType, intVal);
444 }
445
446 return attr;
447}
448
449/// Return whether `srcType` can be broadcast to `dstVectorType` under the
450/// semantics of the `vector.broadcast` op.
452 Type srcType, VectorType dstVectorType,
453 std::pair<VectorDim, VectorDim> *mismatchingDims) {
454 // Broadcast scalar to vector of the same element type.
455 if (isa<VectorElementTypeInterface>(srcType) && dstVectorType &&
456 srcType == getElementTypeOrSelf(dstVectorType))
458 // From now on, only vectors broadcast.
459 VectorType srcVectorType = llvm::dyn_cast<VectorType>(srcType);
460 if (!srcVectorType)
462
463 int64_t srcRank = srcVectorType.getRank();
464 int64_t dstRank = dstVectorType.getRank();
465 if (srcRank > dstRank)
467 // Source has an exact match or singleton value for all trailing dimensions
468 // (all leading dimensions are simply duplicated).
469 int64_t lead = dstRank - srcRank;
470 for (int64_t dimIdx = 0; dimIdx < srcRank; ++dimIdx) {
471 // Have mismatching dims (in the sense of vector.broadcast semantics) been
472 // encountered?
473 bool foundMismatchingDims = false;
474
475 // Check fixed-width dims.
476 int64_t srcDim = srcVectorType.getDimSize(dimIdx);
477 int64_t dstDim = dstVectorType.getDimSize(lead + dimIdx);
478 if (srcDim != 1 && srcDim != dstDim)
479 foundMismatchingDims = true;
480
481 // Check scalable flags.
482 bool srcDimScalableFlag = srcVectorType.getScalableDims()[dimIdx];
483 bool dstDimScalableFlag = dstVectorType.getScalableDims()[lead + dimIdx];
484 if ((srcDim == 1 && srcDimScalableFlag && dstDim != 1) ||
485 // 1 -> [N] is fine, everything else should be rejected when mixing
486 // fixed-width and scalable dims
487 (srcDimScalableFlag != dstDimScalableFlag &&
488 (srcDim != 1 || srcDimScalableFlag)))
489 foundMismatchingDims = true;
490
491 if (foundMismatchingDims) {
492 if (mismatchingDims != nullptr) {
493 mismatchingDims->first.dim = srcDim;
494 mismatchingDims->first.isScalable = srcDimScalableFlag;
495
496 mismatchingDims->second.dim = dstDim;
497 mismatchingDims->second.isScalable = dstDimScalableFlag;
498 }
500 }
501 }
502
504}
505
506//===----------------------------------------------------------------------===//
507// CombiningKindAttr
508//===----------------------------------------------------------------------===//
509
510namespace mlir {
511namespace vector {
512namespace detail {
514 using KeyTy = uint64_t;
515
517
518 bool operator==(const KeyTy &key) const { return value == key; }
519
521 const KeyTy &key) {
522 return new (allocator.allocate<BitmaskEnumStorage>())
524 }
525
527};
528} // namespace detail
529} // namespace vector
530} // namespace mlir
531
532//===----------------------------------------------------------------------===//
533// VectorDialect
534//===----------------------------------------------------------------------===//
535
536namespace {
537/// This class defines the interface for handling inlining with vector dialect
538/// operations.
539struct VectorInlinerInterface : public DialectInlinerInterface {
540 using DialectInlinerInterface::DialectInlinerInterface;
541
542 /// All vector dialect ops can be inlined.
543 bool isLegalToInline(Operation *, Region *, bool, IRMapping &) const final {
544 return true;
545 }
546};
547} // namespace
548
549void VectorDialect::initialize() {
550 addAttributes<
551#define GET_ATTRDEF_LIST
552#include "mlir/Dialect/Vector/IR/VectorAttributes.cpp.inc"
553 >();
554
555 addOperations<
556#define GET_OP_LIST
557#include "mlir/Dialect/Vector/IR/VectorOps.cpp.inc"
558 >();
559
560 addInterfaces<VectorInlinerInterface>();
561
562 declarePromisedInterfaces<memref::IndexedAccessOpInterface, LoadOp, StoreOp,
563 MaskedLoadOp, MaskedStoreOp, ExpandLoadOp,
564 CompressStoreOp>();
565 declarePromisedInterfaces<bufferization::BufferizableOpInterface,
566 TransferReadOp, TransferWriteOp, GatherOp, MaskOp,
567 YieldOp>();
568 declarePromisedInterfaces<SubsetOpInterface, TransferReadOp,
569 TransferWriteOp>();
570 declarePromisedInterface<SubsetExtractionOpInterface, TransferReadOp>();
571 declarePromisedInterface<SubsetInsertionOpInterface, TransferWriteOp>();
572 declarePromisedInterface<ConvertToLLVMPatternInterface, VectorDialect>();
573}
574
575/// Materialize a single constant operation from a given attribute value with
576/// the desired resultant type.
577Operation *VectorDialect::materializeConstant(OpBuilder &builder,
578 Attribute value, Type type,
579 Location loc) {
580 if (matchPattern(value, ub::m_Poison()))
581 return value.getDialect().materializeConstant(builder, value, type, loc);
582
583 return arith::ConstantOp::materialize(builder, value, type, loc);
584}
585
587 return builder.getIntegerType(64);
588}
589
591 ArrayRef<int64_t> values) {
592 return builder.getI64ArrayAttr(values);
593}
594
595//===----------------------------------------------------------------------===//
596// MultiDimReductionOp
597//===----------------------------------------------------------------------===//
598
599void vector::MultiDimReductionOp::build(OpBuilder &builder,
600 OperationState &result, Value source,
601 Value acc, ArrayRef<bool> reductionMask,
602 CombiningKind kind,
603 arith::FastMathFlags fastMathFlags) {
604 SmallVector<int64_t> reductionDims;
605 for (const auto &en : llvm::enumerate(reductionMask))
606 if (en.value())
607 reductionDims.push_back(en.index());
608 build(builder, result, kind, source, acc, reductionDims, fastMathFlags);
609}
610
611OpFoldResult MultiDimReductionOp::fold(FoldAdaptor adaptor) {
612 // No reduction dims: this is a noop regardless of rank.
613 if (getReductionDims().empty())
614 return getSource();
615 return {};
616}
617
618std::optional<SmallVector<int64_t, 4>>
619MultiDimReductionOp::getIterationShapeToUnroll() {
620 return llvm::to_vector<4>(getSourceVectorType().getShape());
621}
622
623LogicalResult MultiDimReductionOp::verify() {
624 // Verify the reduction dimensions.
625 int64_t sourceRank = getSourceVectorType().getRank();
626 SmallVector<bool> isReduced(sourceRank, false);
627 for (int64_t dim : getReductionDims()) {
628 if (dim < 0 || dim >= sourceRank)
629 return emitOpError("reduction dimension out of range: ") << dim;
630 if (isReduced[dim])
631 return emitOpError("duplicate reduction dimension: ") << dim;
632 isReduced[dim] = true;
633 }
634
635 SmallVector<int64_t> targetShape;
636 SmallVector<bool> scalableDims;
637 Type inferredReturnType;
638 auto sourceScalableDims = getSourceVectorType().getScalableDims();
639 for (auto [dimIdx, dimSize] :
640 llvm::enumerate(getSourceVectorType().getShape()))
641 if (!isReduced[dimIdx]) {
642 targetShape.push_back(dimSize);
643 scalableDims.push_back(sourceScalableDims[dimIdx]);
644 }
645 // TODO: update to also allow 0-d vectors when available.
646 if (targetShape.empty())
647 inferredReturnType = getSourceVectorType().getElementType();
648 else
649 inferredReturnType = VectorType::get(
650 targetShape, getSourceVectorType().getElementType(), scalableDims);
651 if (getType() != inferredReturnType)
652 return emitOpError() << "destination type " << getType()
653 << " is incompatible with source type "
654 << getSourceVectorType();
655
656 return success();
657}
658
659/// Returns the mask type expected by this operation.
660Type MultiDimReductionOp::getExpectedMaskType() {
661 auto vecType = getSourceVectorType();
662 return VectorType::get(vecType.getShape(),
663 IntegerType::get(vecType.getContext(), /*width=*/1),
664 vecType.getScalableDims());
665}
666
667namespace {
668// Only unit dimensions that are being reduced are folded. If the dimension is
669// unit, but not reduced, it is not folded, thereby keeping the output type the
670// same. If not all dimensions which are reduced are of unit dimension, this
671// transformation does nothing. This is just a generalization of
672// ElideSingleElementReduction for ReduceOp.
673struct ElideUnitDimsInMultiDimReduction
674 : public OpRewritePattern<MultiDimReductionOp> {
675 using Base::Base;
676
677 LogicalResult matchAndRewrite(MultiDimReductionOp reductionOp,
678 PatternRewriter &rewriter) const override {
679 ArrayRef<int64_t> shape = reductionOp.getSourceVectorType().getShape();
680 for (const auto &dim : enumerate(shape)) {
681 if (reductionOp.isReducedDim(dim.index()) && dim.value() != 1)
682 return failure();
683 }
684
685 // Vector mask setup.
686 OpBuilder::InsertionGuard guard(rewriter);
687 Operation *rootOp;
688 Value mask;
689 if (reductionOp.isMasked()) {
690 rewriter.setInsertionPoint(reductionOp.getMaskingOp());
691 rootOp = reductionOp.getMaskingOp();
692 mask = reductionOp.getMaskingOp().getMask();
693 } else {
694 rootOp = reductionOp;
695 }
696
697 Location loc = reductionOp.getLoc();
698 Value acc = reductionOp.getAcc();
699 Value cast;
700 if (auto dstVecType = dyn_cast<VectorType>(reductionOp.getDestType())) {
701 if (mask) {
702 VectorType newMaskType =
703 VectorType::get(dstVecType.getShape(), rewriter.getI1Type(),
704 dstVecType.getScalableDims());
705 mask = vector::ShapeCastOp::create(rewriter, loc, newMaskType, mask);
706 }
707 cast = vector::ShapeCastOp::create(
708 rewriter, loc, reductionOp.getDestType(), reductionOp.getSource());
709 } else {
710 // This means we are reducing all the dimensions, and all reduction
711 // dimensions are of size 1. So a simple extraction would do.
712 if (mask)
713 mask = vector::ExtractOp::create(rewriter, loc, mask);
714 cast = vector::ExtractOp::create(rewriter, loc, reductionOp.getSource());
715 }
716
717 Value result =
718 vector::makeArithReduction(rewriter, loc, reductionOp.getKind(), acc,
719 cast, reductionOp.getFastmathAttr(), mask);
720 rewriter.replaceOp(rootOp, result);
721 return success();
722 }
723};
724} // namespace
725
726void MultiDimReductionOp::getCanonicalizationPatterns(
727 RewritePatternSet &results, MLIRContext *context) {
728 results.add<ElideUnitDimsInMultiDimReduction>(context);
729}
730
731//===----------------------------------------------------------------------===//
732// ReductionOp
733//===----------------------------------------------------------------------===//
734
735void vector::ReductionOp::build(OpBuilder &builder, OperationState &result,
736 CombiningKind kind, Value vector,
737 arith::FastMathFlags fastMathFlags) {
738 build(builder, result, kind, vector, /*acc=*/Value(), fastMathFlags);
739}
740
741void vector::ReductionOp::build(OpBuilder &builder, OperationState &result,
742 CombiningKind kind, Value vector, Value acc,
743 arith::FastMathFlags fastMathFlags) {
744 build(builder, result,
745 llvm::cast<VectorType>(vector.getType()).getElementType(), kind, vector,
746 acc, fastMathFlags);
747}
748
749LogicalResult ReductionOp::verify() {
750 // Verify for 0-D and 1-D vector.
751 int64_t rank = getSourceVectorType().getRank();
752 if (rank > 1)
753 return emitOpError("unsupported reduction rank: ") << rank;
754
755 // Verify supported reduction kind.
756 Type eltType = getDest().getType();
757 if (!isSupportedCombiningKind(getKind(), eltType))
758 return emitOpError("unsupported reduction type '")
759 << eltType << "' for kind '" << stringifyCombiningKind(getKind())
760 << "'";
761
762 return success();
763}
764
765// MaskableOpInterface methods.
766
767/// Returns the mask type expected by this operation.
768Type ReductionOp::getExpectedMaskType() {
769 auto vecType = getSourceVectorType();
770 return VectorType::get(vecType.getShape(),
771 IntegerType::get(vecType.getContext(), /*width=*/1),
772 vecType.getScalableDims());
773}
774
776 OpBuilder &builder, Location loc,
777 Value vector) {
778 switch (op) {
779 case arith::AtomicRMWKind::addf:
780 case arith::AtomicRMWKind::addi:
781 return vector::ReductionOp::create(builder, vector.getLoc(),
782 CombiningKind::ADD, vector);
783 case arith::AtomicRMWKind::mulf:
784 case arith::AtomicRMWKind::muli:
785 return vector::ReductionOp::create(builder, vector.getLoc(),
786 CombiningKind::MUL, vector);
787 case arith::AtomicRMWKind::minimumf:
788 return vector::ReductionOp::create(builder, vector.getLoc(),
789 CombiningKind::MINIMUMF, vector);
790 case arith::AtomicRMWKind::mins:
791 return vector::ReductionOp::create(builder, vector.getLoc(),
792 CombiningKind::MINSI, vector);
793 case arith::AtomicRMWKind::minu:
794 return vector::ReductionOp::create(builder, vector.getLoc(),
795 CombiningKind::MINUI, vector);
796 case arith::AtomicRMWKind::maximumf:
797 return vector::ReductionOp::create(builder, vector.getLoc(),
798 CombiningKind::MAXIMUMF, vector);
799 case arith::AtomicRMWKind::maxs:
800 return vector::ReductionOp::create(builder, vector.getLoc(),
801 CombiningKind::MAXSI, vector);
802 case arith::AtomicRMWKind::maxu:
803 return vector::ReductionOp::create(builder, vector.getLoc(),
804 CombiningKind::MAXUI, vector);
805 case arith::AtomicRMWKind::andi:
806 return vector::ReductionOp::create(builder, vector.getLoc(),
807 CombiningKind::AND, vector);
808 case arith::AtomicRMWKind::ori:
809 return vector::ReductionOp::create(builder, vector.getLoc(),
810 CombiningKind::OR, vector);
811 case arith::AtomicRMWKind::minnumf:
812 return vector::ReductionOp::create(builder, vector.getLoc(),
813 CombiningKind::MINNUMF, vector);
814 case arith::AtomicRMWKind::maxnumf:
815 return vector::ReductionOp::create(builder, vector.getLoc(),
816 CombiningKind::MAXNUMF, vector);
817 case arith::AtomicRMWKind::xori:
818 return vector::ReductionOp::create(builder, vector.getLoc(),
819 CombiningKind::XOR, vector);
820 default:
821 (void)emitOptionalError(loc, "Reduction operation type not supported");
822 break;
823 }
824 return nullptr;
825}
826
827std::optional<SmallVector<int64_t, 4>>
828ReductionOp::getIterationShapeToUnroll() {
829 return llvm::to_vector<4>(getSourceVectorType().getShape());
830}
831
832namespace {
833struct ElideSingleElementReduction : public OpRewritePattern<ReductionOp> {
834 using Base::Base;
835
836 LogicalResult matchAndRewrite(ReductionOp reductionOp,
837 PatternRewriter &rewriter) const override {
838 // Vector mask setup.
839 OpBuilder::InsertionGuard guard(rewriter);
840 auto maskableOp =
841 cast<vector::MaskableOpInterface>(reductionOp.getOperation());
842 Operation *rootOp;
843 Value mask;
844 if (maskableOp.isMasked()) {
845 rewriter.setInsertionPoint(maskableOp.getMaskingOp());
846 rootOp = maskableOp.getMaskingOp();
847 mask = maskableOp.getMaskingOp().getMask();
848 } else {
849 rootOp = reductionOp;
850 }
851
852 auto vectorType = reductionOp.getSourceVectorType();
853 if (vectorType.getRank() != 0 && vectorType.getDimSize(0) != 1)
854 return failure();
855
856 Location loc = reductionOp.getLoc();
857 if (mask)
858 mask = ExtractOp::create(rewriter, loc, mask);
859 Value result = ExtractOp::create(rewriter, loc, reductionOp.getVector());
860
861 if (Value acc = reductionOp.getAcc())
862 result = vector::makeArithReduction(rewriter, loc, reductionOp.getKind(),
863 result, acc,
864 reductionOp.getFastmathAttr(), mask);
865
866 rewriter.replaceOp(rootOp, result);
867 return success();
868 }
869};
870} // namespace
871
872void ReductionOp::getCanonicalizationPatterns(RewritePatternSet &results,
873 MLIRContext *context) {
874 results.add<ElideSingleElementReduction>(context);
875}
876
877//===----------------------------------------------------------------------===//
878// ContractionOp
879//===----------------------------------------------------------------------===//
880
881void vector::ContractionOp::build(OpBuilder &builder, OperationState &result,
882 Value lhs, Value rhs, Value acc,
883 ArrayRef<ArrayRef<AffineExpr>> indexingExprs,
884 ArrayRef<IteratorType> iteratorTypes) {
885 result.addOperands({lhs, rhs, acc});
886 result.addTypes(acc.getType());
887 result.addAttribute(
888 getIndexingMapsAttrName(result.name),
889 builder.getAffineMapArrayAttr(
890 AffineMap::inferFromExprList(indexingExprs, builder.getContext())));
891 result.addAttribute(
892 getIteratorTypesAttrName(result.name),
893 builder.getArrayAttr(llvm::map_to_vector(
894 iteratorTypes, [&](IteratorType t) -> mlir::Attribute {
895 return IteratorTypeAttr::get(builder.getContext(), t);
896 })));
897}
898
899void vector::ContractionOp::build(OpBuilder &builder, OperationState &result,
900 Value lhs, Value rhs, Value acc,
901 ArrayAttr indexingMaps,
902 ArrayAttr iteratorTypes) {
903 build(builder, result, lhs, rhs, acc, indexingMaps, iteratorTypes,
904 ContractionOp::getDefaultKind());
905}
906
907void vector::ContractionOp::build(OpBuilder &builder, OperationState &result,
908 Value lhs, Value rhs, Value acc,
909 ArrayAttr indexingMaps,
910 ArrayAttr iteratorTypes, CombiningKind kind,
911 arith::FastMathFlags fastMathFlags) {
912 result.addOperands({lhs, rhs, acc});
913 result.addTypes(acc.getType());
914 result.addAttribute(getIndexingMapsAttrName(result.name), indexingMaps);
915 result.addAttribute(getIteratorTypesAttrName(result.name), iteratorTypes);
916 result.addAttribute(getKindAttrName(result.name),
917 CombiningKindAttr::get(builder.getContext(), kind));
918 if (fastMathFlags != arith::FastMathFlags::none)
919 result.addAttribute(
920 getFastmathAttrName(result.name),
921 arith::FastMathFlagsAttr::get(builder.getContext(), fastMathFlags));
922}
923
924ParseResult ContractionOp::parse(OpAsmParser &parser, OperationState &result) {
930 Type resultType;
931 auto loc = parser.getCurrentLocation();
932 DictionaryAttr dictAttr;
933 // TODO: Unify linalg op attribute parsing.
934 if (parser.parseAttribute(dictAttr) || parser.parseOperand(lhsInfo) ||
935 parser.parseComma() || parser.parseOperand(rhsInfo) ||
936 parser.parseComma() || parser.parseOperand(accInfo) ||
937 parser.parseTrailingOperandList(masksInfo) ||
938 parser.parseOptionalAttrDict(result.attributes) ||
939 parser.parseColonTypeList(types) ||
940 parser.parseKeywordType("into", resultType) ||
941 parser.resolveOperand(lhsInfo, types[0], result.operands) ||
942 parser.resolveOperand(rhsInfo, types[1], result.operands) ||
943 parser.resolveOperand(accInfo, resultType, result.operands) ||
944 parser.addTypeToList(resultType, result.types))
945 return failure();
946 result.attributes.append(dictAttr.getValue().begin(),
947 dictAttr.getValue().end());
948
949 // Convert array of string into an array of IteratyType enums. This is needed,
950 // because tests still use the old format when 'iterator_types' attribute is
951 // represented as an array of strings.
952 // TODO: Remove this conversion once tests are fixed.
953 auto iteratorTypes = dyn_cast_or_null<ArrayAttr>(
954 result.attributes.get(getIteratorTypesAttrName(result.name)));
955 if (!iteratorTypes) {
956 return parser.emitError(loc)
957 << "expected " << getIteratorTypesAttrName(result.name)
958 << " array attribute";
959 }
960
961 SmallVector<Attribute> iteratorTypeAttrs;
962
963 for (StringRef s : iteratorTypes.getAsValueRange<StringAttr>()) {
964 auto maybeIteratorType = symbolizeIteratorType(s);
965 if (!maybeIteratorType.has_value())
966 return parser.emitError(loc) << "unexpected iterator_type (" << s << ")";
967
968 iteratorTypeAttrs.push_back(
969 IteratorTypeAttr::get(parser.getContext(), maybeIteratorType.value()));
970 }
971 result.attributes.set(getIteratorTypesAttrName(result.name),
972 parser.getBuilder().getArrayAttr(iteratorTypeAttrs));
973
974 if (!result.attributes.get(getKindAttrName(result.name))) {
975 result.addAttribute(
976 getKindAttrName(result.name),
977 CombiningKindAttr::get(result.getContext(),
978 ContractionOp::getDefaultKind()));
979 }
980 if (masksInfo.empty())
981 return success();
982 if (masksInfo.size() != 2)
983 return parser.emitError(parser.getNameLoc(),
984 "expected zero or exactly 2 vector mask operands");
985 auto lhsType = llvm::cast<VectorType>(types[0]);
986 auto rhsType = llvm::cast<VectorType>(types[1]);
987 auto maskElementType = parser.getBuilder().getI1Type();
988 std::array<VectorType, 2> maskTypes = {
989 VectorType::Builder(lhsType).setElementType(maskElementType),
990 VectorType::Builder(rhsType).setElementType(maskElementType)};
991 if (parser.resolveOperands(masksInfo, maskTypes, loc, result.operands))
992 return failure();
993 return success();
994}
995
996void ContractionOp::print(OpAsmPrinter &p) {
997 // TODO: Unify printing code with linalg ops.
998 auto attrNames = getTraitAttrNames();
999 llvm::StringSet<> traitAttrsSet;
1000 traitAttrsSet.insert_range(attrNames);
1001 NamedAttrList allAttrs(getOperation()->getRawDictionaryAttrs());
1002 getOperation()->getName().walkInherentAttrs(
1003 getOperation(),
1004 [&](StringRef name, Attribute &attr) { allAttrs.append(name, attr); });
1006 for (auto attr : allAttrs) {
1007 if (attr.getName() == getIteratorTypesAttrName()) {
1008 auto iteratorTypes =
1009 llvm::cast<ArrayAttr>(attr.getValue())
1010 .getAsValueRange<IteratorTypeAttr, IteratorType>();
1011 // Convert IteratorType enums into the string representation. This is
1012 // needed, because tests still use the old format when 'iterator_types'
1013 // attribute is represented as an array of strings.
1014 // TODO: Remove this conversion once tests are fixed.
1015 SmallVector<Attribute> iteratorTypeNames =
1016 llvm::map_to_vector(iteratorTypes, [&](IteratorType t) -> Attribute {
1017 return StringAttr::get(getContext(), stringifyIteratorType(t));
1018 });
1019
1020 attrs.emplace_back(getIteratorTypesAttrName(),
1021 ArrayAttr::get(getContext(), iteratorTypeNames));
1022 } else if (traitAttrsSet.count(attr.getName().strref()) > 0) {
1023 // Omit fastmath when it equals the default (none) to keep output clean.
1024 if (attr.getName() == getFastmathAttrName() &&
1025 llvm::cast<arith::FastMathFlagsAttr>(attr.getValue()).getValue() ==
1026 arith::FastMathFlags::none)
1027 continue;
1028 attrs.push_back(attr);
1029 }
1030 }
1031
1032 auto dictAttr = DictionaryAttr::get(getContext(), attrs);
1033 p << " " << dictAttr << " " << getLhs() << ", ";
1034 p << getRhs() << ", " << getAcc();
1035
1036 p.printOptionalAttrDict(allAttrs.getAttrs(), attrNames);
1037 p << " : " << getLhs().getType() << ", " << getRhs().getType() << " into "
1038 << getResultType();
1039}
1040
1041static bool verifyDimMap(VectorType lhsType, VectorType rhsType,
1042 const std::vector<std::pair<int64_t, int64_t>> &map) {
1043 for (auto &dimPair : map) {
1044 if (dimPair.first < 0 || dimPair.first >= lhsType.getRank() ||
1045 dimPair.second < 0 || dimPair.second >= rhsType.getRank() ||
1046 lhsType.getDimSize(dimPair.first) != rhsType.getDimSize(dimPair.second))
1047 return false;
1048 }
1049 return true;
1050}
1051
1052static LogicalResult verifyOutputShape(
1053 ContractionOp op, VectorType lhsType, VectorType rhsType, Type accType,
1054 Type resType,
1055 const std::vector<std::pair<int64_t, int64_t>> &contractingDimMap,
1056 const std::vector<std::pair<int64_t, int64_t>> &batchDimMap) {
1057 DenseSet<int64_t> lhsContractingDimSet;
1058 DenseSet<int64_t> rhsContractingDimSet;
1059 for (auto &dimPair : contractingDimMap) {
1060 lhsContractingDimSet.insert(dimPair.first);
1061 rhsContractingDimSet.insert(dimPair.second);
1062 }
1063 DenseSet<int64_t> rhsBatchDimSet(llvm::from_range,
1064 llvm::make_second_range(batchDimMap));
1065
1066 // Add free and batch dimensions from 'lhsType' to 'expectedResultDims'.
1067 SmallVector<int64_t, 4> expectedResultDims;
1068 for (int64_t i = 0, e = lhsType.getRank(); i < e; ++i) {
1069 if (lhsContractingDimSet.count(i) > 0)
1070 continue;
1071 expectedResultDims.push_back(lhsType.getDimSize(i));
1072 }
1073
1074 // Add free dimensions from 'rhsType' to 'expectedResultDims'.
1075 for (int64_t i = 0, e = rhsType.getRank(); i < e; ++i) {
1076 if (rhsContractingDimSet.count(i) > 0 || rhsBatchDimSet.count(i) > 0)
1077 continue;
1078 expectedResultDims.push_back(rhsType.getDimSize(i));
1079 }
1080
1081 // Verify 'expectedResultDims'.
1082 if (expectedResultDims.empty()) {
1083 // No batch or free dimension implies a scalar result.
1084 if (llvm::isa<VectorType>(resType) || llvm::isa<VectorType>(accType))
1085 return op.emitOpError("invalid accumulator/result vector shape");
1086 } else {
1087 // At least one batch or free dimension implies a vector result.
1088 auto resVectorType = llvm::dyn_cast<VectorType>(resType);
1089 auto accVectorType = llvm::dyn_cast<VectorType>(accType);
1090 if (!resVectorType || !accVectorType)
1091 return op.emitOpError("invalid accumulator/result vector shape");
1092
1093 // Infer expected result vector type. Lhs + rhs map and lhs + rhs vector
1094 // types fully define the result vector type. This assumes the affine maps
1095 // are well-formed, which must have been verified already.
1096 MLIRContext *ctx = op.getContext();
1097 AffineMap lhsMap = op.getIndexingMapsArray()[0];
1098 AffineMap rhsMap = op.getIndexingMapsArray()[1];
1099 if (getUnusedDimsBitVector({lhsMap, rhsMap}).any())
1100 return op.emitOpError(
1101 "expected all dimensions to be either a LHS or a RHS dimension");
1102 SmallVector<AffineExpr, 4> extents(lhsMap.getNumInputs());
1103 for (auto pair :
1104 {std::make_pair(lhsType, lhsMap), std::make_pair(rhsType, rhsMap)}) {
1105 VectorType v = pair.first;
1106 auto map = pair.second;
1107 for (unsigned idx = 0, e = v.getRank(); idx < e; ++idx) {
1108 unsigned pos = map.getDimPosition(idx);
1109 if (!extents[pos])
1110 extents[pos] = getAffineConstantExpr(v.getShape()[idx], ctx);
1111 }
1112 }
1113 if (!llvm::all_of(extents, [](AffineExpr e) { return e; }))
1114 return op.emitOpError("expected all dimensions to get an extent as "
1115 "either a LHS or a RHS dimension");
1116
1117 AffineMap resMap = op.getIndexingMapsArray()[2];
1118 auto extentsMap = AffineMap::get(/*dimCount=*/extents.size(),
1119 /*symbolCount=*/0, extents, ctx);
1120 // Compose the resMap with the extentsMap, which is a constant map.
1121 AffineMap expectedMap = simplifyAffineMap(resMap.compose(extentsMap));
1122 assert(llvm::all_of(expectedMap.getResults(),
1123 llvm::IsaPred<AffineConstantExpr>) &&
1124 "expected constant extent along all dimensions.");
1125 // Extract the expected shape and build the type.
1126 auto expectedShape =
1127 llvm::map_to_vector<4>(expectedMap.getResults(), [](AffineExpr e) {
1128 return cast<AffineConstantExpr>(e).getValue();
1129 });
1130 auto expected =
1131 VectorType::get(expectedShape, resVectorType.getElementType(),
1132 resVectorType.getScalableDims());
1133 if (resVectorType != expected || accVectorType != expected)
1134 return op.emitOpError(
1135 "invalid accumulator/result vector shape, expected: ")
1136 << expected;
1137 }
1138 return success();
1139}
1140
1141// Find a one-sided reduction dim, i.e. a reduction dim that is present in only
1142// one of the inputs.
1143static std::optional<int64_t>
1144getOneSidedReductionDim(vector::ContractionOp *contract) {
1145 auto maps = contract->getIndexingMapsArray();
1146 AffineMap lhsMap = maps[0];
1147 AffineMap rhsMap = maps[1];
1148
1149 for (auto [dim, attr] : llvm::enumerate(contract->getIteratorTypes())) {
1150 auto iteratorType = cast<vector::IteratorTypeAttr>(attr).getValue();
1151 if (iteratorType == vector::IteratorType::reduction &&
1152 (!lhsMap.isFunctionOfDim(dim) || !rhsMap.isFunctionOfDim(dim)))
1153 return dim;
1154 }
1155
1156 return std::nullopt;
1157}
1158
1159LogicalResult ContractionOp::verify() {
1160 VectorType lhsType = getLhsType();
1161 VectorType rhsType = getRhsType();
1162 Type accType = getAccType();
1163 Type resType = getResultType();
1164
1165 if (llvm::isa<IntegerType>(lhsType.getElementType())) {
1166 if (!lhsType.getElementType().isSignlessInteger())
1167 return emitOpError("only supports signless integer types");
1168 }
1169
1170 // Verify that an indexing map was specified for each vector operand.
1171 if (getIndexingMapsArray().size() != 3)
1172 return emitOpError("expected an indexing map for each vector operand");
1173
1174 // Verify that each index map has 'numIterators' inputs, no symbols, and
1175 // that the number of map outputs equals the rank of its associated
1176 // vector operand.
1177 unsigned numIterators = getIteratorTypes().getValue().size();
1178 for (const auto &it : llvm::enumerate(getIndexingMapsArray())) {
1179 auto index = it.index();
1180 auto map = it.value();
1181 if (map.getNumSymbols() != 0)
1182 return emitOpError("expected indexing map ")
1183 << index << " to have no symbols";
1184 auto vectorType = llvm::dyn_cast<VectorType>(getOperand(index).getType());
1185 unsigned rank = vectorType ? vectorType.getShape().size() : 0;
1186 // Verify that the map has the right number of inputs, outputs, and indices.
1187 // This also correctly accounts for (..) -> () for rank-0 results.
1188 if (map.getNumDims() != numIterators)
1189 return emitOpError("expected indexing map ")
1190 << index << " to have " << numIterators << " number of inputs";
1191 if (map.getNumResults() != rank)
1192 return emitOpError("expected indexing map ")
1193 << index << " to have " << rank << " number of outputs";
1194 if (!map.isProjectedPermutation())
1195 return emitOpError("expected indexing map ")
1196 << index << " to be a projected permutation of its inputs";
1197 }
1198
1199 // TODO: For better code re-use, unify this with linalg.contract.
1200 auto dimIdx = getOneSidedReductionDim(this);
1201 if (dimIdx.has_value())
1202 return emitOpError("reduction dim at index ")
1203 << dimIdx.value() << " must appear in both LHS and RHS";
1204
1205 auto contractingDimMap = getContractingDimMap();
1206 auto batchDimMap = getBatchDimMap();
1207
1208 // Verify at least one contracting dimension pair was specified.
1209 if (contractingDimMap.empty())
1210 return emitOpError("expected at least one contracting dimension pair");
1211
1212 // Verify contracting dimension map was properly constructed.
1213 if (!verifyDimMap(lhsType, rhsType, contractingDimMap))
1214 return emitOpError("invalid contracting dimension map");
1215
1216 // Verify batch dimension map was properly constructed.
1217 if (!verifyDimMap(lhsType, rhsType, batchDimMap))
1218 return emitOpError("invalid batch dimension map");
1219
1220 // Verify 'accType' and 'resType' shape.
1221 if (failed(verifyOutputShape(*this, lhsType, rhsType, accType, resType,
1222 contractingDimMap, batchDimMap)))
1223 return failure();
1224
1225 if (!getKindAttr()) {
1226 return emitOpError("expected 'kind' attribute of type CombiningKind (e.g. "
1227 "'vector.kind<add>')");
1228 }
1229
1230 // Verify supported combining kind.
1231 auto vectorType = llvm::dyn_cast<VectorType>(resType);
1232 auto elementType = vectorType ? vectorType.getElementType() : resType;
1233 if (!isSupportedCombiningKind(getKind(), elementType))
1234 return emitOpError("unsupported contraction type");
1235
1236 // Delayed calling of IndexingMapOpInterface::verifyImpl.
1237 return cast<IndexingMapOpInterface>(this->getOperation()).verifyImpl();
1238}
1239
1240// MaskableOpInterface methods.
1241
1242/// Returns the mask type expected by this operation. Mostly used for
1243/// verification purposes. It requires the operation to be vectorized."
1244Type ContractionOp::getExpectedMaskType() {
1245 auto indexingMaps = this->getIndexingMapsArray();
1246 AffineMap lhsIdxMap = indexingMaps[0];
1247 AffineMap rhsIdxMap = indexingMaps[1];
1248 VectorType lhsType = this->getLhsType();
1249 VectorType rhsType = this->getRhsType();
1250
1251 unsigned numVecDims = lhsIdxMap.getNumDims();
1252 SmallVector<int64_t> maskShape(numVecDims, ShapedType::kDynamic);
1253 SmallVector<bool> maskShapeScalableDims(numVecDims, false);
1254
1255 // Using the information in the indexing maps, extract the size of each
1256 // dimension in the vector.contract operation from the two input operands.
1257 for (auto [dimIdx, dimSize] : llvm::enumerate(lhsType.getShape())) {
1258 maskShape[lhsIdxMap.getDimPosition(dimIdx)] = dimSize;
1259 maskShapeScalableDims[lhsIdxMap.getDimPosition(dimIdx)] =
1260 lhsType.getScalableDims()[dimIdx];
1261 }
1262 for (auto [dimIdx, dimSize] : llvm::enumerate(rhsType.getShape())) {
1263 maskShape[rhsIdxMap.getDimPosition(dimIdx)] = dimSize;
1264 maskShapeScalableDims[rhsIdxMap.getDimPosition(dimIdx)] =
1265 rhsType.getScalableDims()[dimIdx];
1266 }
1267
1268 assert(ShapedType::isStaticShape(maskShape) &&
1269 "Mask shape couldn't be computed");
1270
1271 return VectorType::get(maskShape,
1272 IntegerType::get(lhsType.getContext(), /*width=*/1),
1273 maskShapeScalableDims);
1274}
1275
1276SmallVector<StringRef> ContractionOp::getTraitAttrNames() {
1277 return SmallVector<StringRef>{getIndexingMapsAttrName(),
1278 getIteratorTypesAttrName(), getKindAttrName(),
1279 getFastmathAttrName()};
1280}
1281
1283 for (int64_t i = 0, e = map.getNumResults(); i < e; ++i)
1284 if (targetExpr == map.getResult(i))
1285 return i;
1286 return -1;
1287}
1288
1289// For every contraction `dim` for which:
1290// * the corresponding iteratory type matches `targetIteratorType`,
1291// returns a pair: <lhsIdx, rhsIdx>, where:
1292// * {lhs|rhs}Idx is the index of `dim` in the (result of) indexing map for
1293// {lhs|rhs}.
1294static std::vector<std::pair<int64_t, int64_t>>
1295getDimPairForIterTy(ContractionOp *contract, IteratorType targetIteratorType) {
1296 SmallVector<AffineMap, 4> indexingMaps(contract->getIndexingMapsArray());
1297 ArrayAttr iteratorTypes = contract->getIteratorTypes();
1298 std::vector<std::pair<int64_t, int64_t>> dimMap;
1299 for (const auto &it : llvm::enumerate(iteratorTypes)) {
1300 auto iteratorType = llvm::cast<IteratorTypeAttr>(it.value()).getValue();
1301 if (iteratorType != targetIteratorType)
1302 continue;
1303 // Search lhs/rhs map results for 'targetExpr'.
1304 auto targetExpr = getAffineDimExpr(it.index(), contract->getContext());
1305 int64_t lhsDim = getResultIndex(indexingMaps[0], targetExpr);
1306 int64_t rhsDim = getResultIndex(indexingMaps[1], targetExpr);
1307 if (lhsDim >= 0 && rhsDim >= 0)
1308 dimMap.emplace_back(lhsDim, rhsDim);
1309 }
1310 return dimMap;
1311}
1312
1313std::vector<std::pair<int64_t, int64_t>> ContractionOp::getContractingDimMap() {
1314 return getDimPairForIterTy(this, IteratorType::reduction);
1315}
1316
1317std::vector<std::pair<int64_t, int64_t>> ContractionOp::getBatchDimMap() {
1318 return getDimPairForIterTy(this, IteratorType::parallel);
1319}
1320
1321void ContractionOp::getIterationBounds(
1322 SmallVectorImpl<int64_t> &iterationBounds) {
1323 auto lhsShape = getLhsType().getShape();
1324 auto resVectorType = llvm::dyn_cast<VectorType>(getResultType());
1325 SmallVector<AffineMap, 4> indexingMaps(getIndexingMapsArray());
1326 for (const auto &it : llvm::enumerate(getIteratorTypes())) {
1327 // Search lhs/rhs map results for 'targetExpr'.
1328 auto targetExpr = getAffineDimExpr(it.index(), getContext());
1329 auto iteratorType = llvm::cast<IteratorTypeAttr>(it.value()).getValue();
1330 if (iteratorType == IteratorType::reduction) {
1331 // Get reduction dim size from lhs shape (same size in rhsShape).
1332 int64_t lhsDimIndex = getResultIndex(indexingMaps[0], targetExpr);
1333 assert(lhsDimIndex >= 0);
1334 iterationBounds.push_back(lhsShape[lhsDimIndex]);
1335 continue;
1336 }
1337 // Get parallel dimension size from result shape.
1338 int64_t resDimIndex = getResultIndex(indexingMaps[2], targetExpr);
1339 assert(resDimIndex >= 0);
1340 assert(resVectorType != nullptr);
1341 iterationBounds.push_back(resVectorType.getShape()[resDimIndex]);
1342 }
1343}
1344
1345void ContractionOp::getIterationIndexMap(
1346 std::vector<DenseMap<int64_t, int64_t>> &iterationIndexMap) {
1347 unsigned numMaps = getIndexingMapsArray().size();
1348 iterationIndexMap.resize(numMaps);
1349 for (const auto &it : llvm::enumerate(getIndexingMapsArray())) {
1350 auto index = it.index();
1351 auto map = it.value();
1352 for (unsigned i = 0, e = map.getNumResults(); i < e; ++i) {
1353 auto dim = cast<AffineDimExpr>(map.getResult(i));
1354 iterationIndexMap[index][dim.getPosition()] = i;
1355 }
1356 }
1357}
1358
1359std::optional<SmallVector<int64_t, 4>>
1360ContractionOp::getIterationShapeToUnroll() {
1362 getIterationBounds(shape);
1363 return shape;
1364}
1365
1366/// Return a fused vector::ContractionOp which represents a patterns such as:
1367///
1368/// ```mlir
1369/// %c0 = vector.constant 0: ...
1370/// %c = vector.contract %a, %b, %c0: ...
1371/// %e = add %c, %d: ...
1372/// ```
1373///
1374/// by:
1375///
1376/// ```mlir
1377/// %e = vector.contract %a, %b, %d: ...
1378/// ```
1379///
1380/// Return null if the canonicalization does not apply.
1381// TODO: This should be a folding of Add into Contract in core but while they
1382// live in different dialects, it is not possible without unnatural
1383// dependencies.
1384template <typename AddOpType>
1385struct CanonicalizeContractAdd : public OpRewritePattern<AddOpType> {
1386 using OpRewritePattern<AddOpType>::OpRewritePattern;
1387
1388 LogicalResult matchAndRewrite(AddOpType addOp,
1389 PatternRewriter &rewriter) const override {
1390 auto canonicalize = [&](Value maybeContraction,
1391 Value otherOperand) -> vector::ContractionOp {
1392 vector::ContractionOp contractionOp =
1393 dyn_cast_or_null<vector::ContractionOp>(
1394 maybeContraction.getDefiningOp());
1395 if (!contractionOp)
1396 return vector::ContractionOp();
1397 if (auto maybeZero = dyn_cast_or_null<arith::ConstantOp>(
1398 contractionOp.getAcc().getDefiningOp())) {
1399 if (maybeZero.getValue() ==
1400 rewriter.getZeroAttr(contractionOp.getAcc().getType())) {
1401 IRMapping bvm;
1402 bvm.map(contractionOp.getAcc(), otherOperand);
1403 auto newContraction =
1404 cast<vector::ContractionOp>(rewriter.clone(*contractionOp, bvm));
1405 rewriter.replaceOp(addOp, newContraction.getResult());
1406 return newContraction;
1407 }
1408 }
1409 return vector::ContractionOp();
1410 };
1411
1412 Value a = addOp->getOperand(0), b = addOp->getOperand(1);
1413 vector::ContractionOp contract = canonicalize(a, b);
1414 contract = contract ? contract : canonicalize(b, a);
1415 return contract ? success() : failure();
1416 }
1417};
1418
1419void ContractionOp::getCanonicalizationPatterns(RewritePatternSet &results,
1420 MLIRContext *context) {
1423}
1424
1425// Returns `true` if `index` is either within [0, maxIndex) or equal to
1426// `poisonValue`.
1428 int64_t maxIndex) {
1429 return index == poisonValue || (index >= 0 && index < maxIndex);
1430}
1431
1432//===----------------------------------------------------------------------===//
1433// ExtractOp
1434//===----------------------------------------------------------------------===//
1435
1436void ExtractOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
1437 SetIntRangeFn setResultRanges) {
1438 setResultRanges(getResult(), argRanges.front());
1439}
1440
1441void vector::ExtractOp::build(OpBuilder &builder, OperationState &result,
1442 Value source) {
1443 auto vectorTy = cast<VectorType>(source.getType());
1444 build(builder, result, source, SmallVector<int64_t>(vectorTy.getRank(), 0));
1445}
1446
1447void vector::ExtractOp::build(OpBuilder &builder, OperationState &result,
1448 Value source, int64_t position) {
1449 build(builder, result, source, ArrayRef<int64_t>{position});
1450}
1451
1452void vector::ExtractOp::build(OpBuilder &builder, OperationState &result,
1453 Value source, OpFoldResult position) {
1454 build(builder, result, source, ArrayRef<OpFoldResult>{position});
1455}
1456
1457void vector::ExtractOp::build(OpBuilder &builder, OperationState &result,
1458 Value source, ArrayRef<int64_t> position) {
1459 build(builder, result, source, /*dynamic_position=*/ArrayRef<Value>(),
1460 builder.getDenseI64ArrayAttr(position));
1461}
1462
1463void vector::ExtractOp::build(OpBuilder &builder, OperationState &result,
1464 Value source, ArrayRef<OpFoldResult> position) {
1465 SmallVector<int64_t> staticPos;
1466 SmallVector<Value> dynamicPos;
1467 dispatchIndexOpFoldResults(position, dynamicPos, staticPos);
1468 build(builder, result, source, dynamicPos,
1469 builder.getDenseI64ArrayAttr(staticPos));
1470}
1471
1472LogicalResult
1473ExtractOp::inferReturnTypes(MLIRContext *, std::optional<Location>,
1474 ExtractOp::Adaptor adaptor,
1475 SmallVectorImpl<Type> &inferredReturnTypes) {
1476 auto vectorType = llvm::cast<VectorType>(adaptor.getSource().getType());
1477 if (static_cast<int64_t>(adaptor.getStaticPosition().size()) ==
1478 vectorType.getRank()) {
1479 inferredReturnTypes.push_back(vectorType.getElementType());
1480 } else {
1481 auto n = std::min<size_t>(adaptor.getStaticPosition().size(),
1482 vectorType.getRank());
1483 inferredReturnTypes.push_back(VectorType::get(
1484 vectorType.getShape().drop_front(n), vectorType.getElementType(),
1485 vectorType.getScalableDims().drop_front(n)));
1486 }
1487 return success();
1488}
1489
1490LogicalResult vector::ExtractOp::verify() {
1491 if (auto resTy = dyn_cast<VectorType>(getResult().getType()))
1492 if (resTy.getRank() == 0)
1493 return emitError(
1494 "expected a scalar instead of a 0-d vector as the result type");
1495
1496 // Note: This check must come before getMixedPosition() to prevent a crash.
1497 auto dynamicMarkersCount =
1498 llvm::count_if(getStaticPosition(), ShapedType::isDynamic);
1499 if (static_cast<size_t>(dynamicMarkersCount) != getDynamicPosition().size())
1500 return emitOpError(
1501 "mismatch between dynamic and static positions (kDynamic marker but no "
1502 "corresponding dynamic position) -- this can only happen due to an "
1503 "incorrect fold/rewrite");
1504 auto position = getMixedPosition();
1505 if (position.size() > static_cast<unsigned>(getSourceVectorType().getRank()))
1506 return emitOpError(
1507 "expected position attribute of rank no greater than vector rank");
1508 for (auto [idx, pos] : llvm::enumerate(position)) {
1509 if (auto attr = dyn_cast<Attribute>(pos)) {
1510 int64_t constIdx = cast<IntegerAttr>(attr).getInt();
1512 constIdx, kPoisonIndex, getSourceVectorType().getDimSize(idx))) {
1513 return emitOpError("expected position attribute #")
1514 << (idx + 1)
1515 << " to be a non-negative integer smaller than the "
1516 "corresponding vector dimension or poison (-1)";
1517 }
1518 }
1519 }
1520 return success();
1521}
1522
1523template <typename IntType>
1525 return llvm::map_to_vector<4>(
1526 arrayAttr.getAsRange<IntegerAttr>(),
1527 [](IntegerAttr attr) { return static_cast<IntType>(attr.getInt()); });
1528}
1529
1530/// Fold the result of chains of ExtractOp in place by simply concatenating the
1531/// positions. Both static and dynamic positions are supported.
1532///
1533/// BEFORE:
1534/// ```mlir
1535/// %0 = vector.extract %v[1, 2] : vector<3x4x5xf32>
1536/// %1 = vector.extract %0[%idx, 3] : vector<5xf32>
1537/// ```
1538///
1539/// AFTER:
1540/// ```mlir
1541/// %1 = vector.extract %v[1, 2, %idx, 3] : vector<3x4x5xf32>
1542/// ```
1543static LogicalResult foldExtractOpFromExtractChain(ExtractOp extractOp) {
1544 if (!extractOp.getSource().getDefiningOp<ExtractOp>())
1545 return failure();
1546
1547 SmallVector<OpFoldResult> globalPosition = extractOp.getMixedPosition();
1548 ExtractOp currentOp = extractOp;
1549 while (ExtractOp nextOp = currentOp.getSource().getDefiningOp<ExtractOp>()) {
1550 currentOp = nextOp;
1551 SmallVector<OpFoldResult> pos = currentOp.getMixedPosition();
1552 globalPosition.insert(globalPosition.begin(), pos.begin(), pos.end());
1553 }
1554 auto [staticPos, dynPos] = decomposeMixedValues(globalPosition);
1555 Value source = currentOp.getSource();
1556 extractOp->setOperands(
1557 llvm::to_vector(llvm::concat<Value>(ValueRange(source), dynPos)));
1558 extractOp.setStaticPosition(staticPos);
1559 return success();
1560}
1561
1562namespace {
1563/// Fold an ExtractOp that is fed by a chain of InsertOps and TransposeOps.
1564/// Walk back a chain of InsertOp/TransposeOp until we hit a match.
1565/// Compose TransposeOp permutations as we walk back.
1566/// This helper class keeps an updated extraction position `extractPosition`
1567/// with extra trailing sentinels.
1568/// The sentinels encode the internal transposition status of the result vector.
1569/// As we iterate, extractPosition is permuted and updated.
1570class ExtractFromInsertTransposeChainState {
1571public:
1572 ExtractFromInsertTransposeChainState(ExtractOp e);
1573
1574 /// Iterate over producing insert and transpose ops until we find a fold.
1575 Value fold();
1576
1577private:
1578 /// Return true if the vector at position `a` is contained within the vector
1579 /// at position `b`. Under insert/extract semantics, this is the same as `a`
1580 /// is a prefix of `b`.
1581 template <typename ContainerA, typename ContainerB>
1582 bool isContainedWithin(const ContainerA &a, const ContainerB &b) {
1583 return a.size() <= b.size() &&
1584 std::equal(a.begin(), a.begin() + a.size(), b.begin());
1585 }
1586
1587 /// Return true if the vector at position `a` intersects the vector at
1588 /// position `b`. Under insert/extract semantics, this is the same as equality
1589 /// of all entries of `a` that are >=0 with the corresponding entries of b.
1590 /// Comparison is on the common prefix (i.e. zip).
1591 template <typename ContainerA, typename ContainerB>
1592 bool intersectsWhereNonNegative(const ContainerA &a, const ContainerB &b) {
1593 for (auto [elemA, elemB] : llvm::zip(a, b)) {
1594 if (elemA < 0 || elemB < 0)
1595 continue;
1596 if (elemA != elemB)
1597 return false;
1598 }
1599 return true;
1600 }
1601
1602 /// Folding is only possible in the absence of an internal permutation in the
1603 /// result vector.
1604 bool canFold() {
1605 return (sentinels == ArrayRef(extractPosition).drop_front(extractedRank));
1606 }
1607
1608 // Helper to get the next defining op of interest.
1609 void updateStateForNextIteration(Value v) {
1610 nextInsertOp = v.getDefiningOp<vector::InsertOp>();
1611 nextTransposeOp = v.getDefiningOp<vector::TransposeOp>();
1612 };
1613
1614 // Case 1. If we hit a transpose, just compose the map and iterate.
1615 // Invariant: insert + transpose do not change rank, we can always compose.
1616 LogicalResult handleTransposeOp();
1617
1618 // Case 2: the insert position matches extractPosition exactly, early return.
1619 LogicalResult handleInsertOpWithMatchingPos(Value &res);
1620
1621 /// Case 3: if the insert position is a prefix of extractPosition, extract a
1622 /// portion of the source of the insert.
1623 /// Example:
1624 /// ```
1625 /// %ins = vector.insert %source, %vest[1]: vector<3x4> into vector<2x3x4x5>
1626 /// // extractPosition == [1, 2, 3]
1627 /// %ext = vector.extract %ins[1, 0]: vector<5> from vector<3x4x5>
1628 /// // can fold to vector.extract %source[0, 3]
1629 /// %ext = vector.extract %source[3]: vector<6> from vector<5x6>
1630 /// ```
1631 /// To traverse through %source, we need to set the leading dims to 0 and
1632 /// drop the extra leading dims.
1633 /// This method updates the internal state.
1634 LogicalResult handleInsertOpWithPrefixPos(Value &res);
1635
1636 /// Try to fold in place to extract(source, extractPosition) and return the
1637 /// folded result. Return null if folding is not possible (e.g. due to an
1638 /// internal transposition in the result).
1639 Value tryToFoldExtractOpInPlace(Value source);
1640
1641 ExtractOp extractOp;
1642 int64_t vectorRank;
1643 int64_t extractedRank;
1644
1645 InsertOp nextInsertOp;
1646 TransposeOp nextTransposeOp;
1647
1648 /// Sentinel values that encode the internal permutation status of the result.
1649 /// They are set to (-1, ... , -k) at the beginning and appended to
1650 /// `extractPosition`.
1651 /// In the end, the tail of `extractPosition` must be exactly `sentinels` to
1652 /// ensure that there is no internal transposition.
1653 /// Internal transposition cannot be accounted for with a folding pattern.
1654 // TODO: We could relax the internal transposition with an extra transposition
1655 // operation in a future canonicalizer.
1656 SmallVector<int64_t> sentinels;
1657 SmallVector<int64_t> extractPosition;
1658};
1659} // namespace
1660
1661ExtractFromInsertTransposeChainState::ExtractFromInsertTransposeChainState(
1662 ExtractOp e)
1663 : extractOp(e), vectorRank(extractOp.getSourceVectorType().getRank()),
1664 extractedRank(extractOp.getNumIndices()) {
1665 assert(vectorRank >= extractedRank && "Extracted position overflow");
1666 sentinels.reserve(vectorRank - extractedRank);
1667 for (int64_t i = 0, e = vectorRank - extractedRank; i < e; ++i)
1668 sentinels.push_back(-(i + 1));
1669 extractPosition.assign(extractOp.getStaticPosition().begin(),
1670 extractOp.getStaticPosition().end());
1671 llvm::append_range(extractPosition, sentinels);
1672}
1673
1674// Case 1. If we hit a transpose, just compose the map and iterate.
1675// Invariant: insert + transpose do not change rank, we can always compose.
1676LogicalResult ExtractFromInsertTransposeChainState::handleTransposeOp() {
1677 // TODO: Canonicalization for dynamic position not implemented yet.
1678 if (extractOp.hasDynamicPosition())
1679 return failure();
1680
1681 if (!nextTransposeOp)
1682 return failure();
1683
1684 if (nextTransposeOp.getResultVectorType().getRank() == 0)
1685 return success();
1686
1688 nextTransposeOp.getPermutation(), extractOp.getContext()));
1690 return success();
1691}
1692
1693// Case 2: the insert position matches extractPosition exactly, early return.
1694LogicalResult
1695ExtractFromInsertTransposeChainState::handleInsertOpWithMatchingPos(
1696 Value &res) {
1697 // TODO: Canonicalization for dynamic position not implemented yet.
1698 if (extractOp.hasDynamicPosition() || nextInsertOp.hasDynamicPosition())
1699 return failure();
1700
1701 ArrayRef<int64_t> insertedPos = nextInsertOp.getStaticPosition();
1702 if (insertedPos != llvm::ArrayRef(extractPosition).take_front(extractedRank))
1703 return failure();
1704 // Case 2.a. early-exit fold.
1705 res = nextInsertOp.getValueToStore();
1706 // Case 2.b. if internal transposition is present, canFold will be false.
1707 return success(canFold());
1708}
1709
1710/// Case 3: if inserted position is a prefix of extractPosition,
1711/// extract a portion of the source of the insertion.
1712/// This method updates the internal state.
1713LogicalResult
1714ExtractFromInsertTransposeChainState::handleInsertOpWithPrefixPos(Value &res) {
1715 // TODO: Canonicalization for dynamic position not implemented yet.
1716 if (extractOp.hasDynamicPosition() || nextInsertOp.hasDynamicPosition())
1717 return failure();
1718
1719 ArrayRef<int64_t> insertedPos = nextInsertOp.getStaticPosition();
1720 if (!isContainedWithin(insertedPos, extractPosition))
1721 return failure();
1722 // Set leading dims to zero.
1723 std::fill_n(extractPosition.begin(), insertedPos.size(), 0);
1724 // Drop extra leading dims.
1725 extractPosition.erase(extractPosition.begin(),
1726 extractPosition.begin() + insertedPos.size());
1727 extractedRank = extractPosition.size() - sentinels.size();
1728 // Case 3.a. early-exit fold (break and delegate to post-while path).
1729 res = nextInsertOp.getValueToStore();
1730 // Case 3.b. if internal transposition is present, canFold will be false.
1731 return success();
1732}
1733
1734/// Try to fold in place to extract(source, extractPosition) and return the
1735/// folded result. Return null if folding is not possible (e.g. due to an
1736/// internal transposition in the result).
1737Value ExtractFromInsertTransposeChainState::tryToFoldExtractOpInPlace(
1738 Value source) {
1739 // TODO: Canonicalization for dynamic position not implemented yet.
1740 if (extractOp.hasDynamicPosition())
1741 return Value();
1742
1743 // If we can't fold (either internal transposition, or nothing to fold), bail.
1744 bool nothingToFold = (source == extractOp.getSource());
1745 if (nothingToFold || !canFold())
1746 return Value();
1747
1748 // Otherwise, fold by updating the op inplace and return its result.
1749 OpBuilder b(extractOp.getContext());
1750 extractOp.setStaticPosition(
1751 ArrayRef(extractPosition).take_front(extractedRank));
1752 extractOp.getSourceMutable().assign(source);
1753 return extractOp.getResult();
1754}
1755
1756/// Iterate over producing insert and transpose ops until we find a fold.
1757Value ExtractFromInsertTransposeChainState::fold() {
1758 // TODO: Canonicalization for dynamic position not implemented yet.
1759 if (extractOp.hasDynamicPosition())
1760 return Value();
1761
1762 Value valueToExtractFrom = extractOp.getSource();
1763 updateStateForNextIteration(valueToExtractFrom);
1764 while (nextInsertOp || nextTransposeOp) {
1765 // Case 1. If we hit a transpose, just compose the map and iterate.
1766 // Invariant: insert + transpose do not change rank, we can always compose.
1767 if (succeeded(handleTransposeOp())) {
1768 valueToExtractFrom = nextTransposeOp.getVector();
1769 updateStateForNextIteration(valueToExtractFrom);
1770 continue;
1771 }
1772
1773 Value result;
1774 // Case 2: the position match exactly.
1775 if (succeeded(handleInsertOpWithMatchingPos(result)))
1776 return result;
1777
1778 // Case 3: if the inserted position is a prefix of extractPosition, we can
1779 // just extract a portion of the source of the insert.
1780 if (succeeded(handleInsertOpWithPrefixPos(result)))
1781 return tryToFoldExtractOpInPlace(result);
1782
1783 // Case 4: extractPositionRef intersects insertedPosRef on non-sentinel
1784 // values. This is a more difficult case and we bail.
1785 ArrayRef<int64_t> insertedPos = nextInsertOp.getStaticPosition();
1786 if (isContainedWithin(extractPosition, insertedPos) ||
1787 intersectsWhereNonNegative(extractPosition, insertedPos))
1788 return Value();
1789
1790 // Case 5: No intersection, we forward the extract to insertOp.dest().
1791 valueToExtractFrom = nextInsertOp.getDest();
1792 updateStateForNextIteration(valueToExtractFrom);
1793 }
1794 // If after all this we can fold, go for it.
1795 return tryToFoldExtractOpInPlace(valueToExtractFrom);
1796}
1797
1798/// Returns true if the operation has a 0-D vector type operand or result.
1800 auto hasZeroDimVectorType = [](Type type) -> bool {
1801 auto vecType = dyn_cast<VectorType>(type);
1802 return vecType && vecType.getRank() == 0;
1803 };
1804
1805 return llvm::any_of(op->getOperandTypes(), hasZeroDimVectorType) ||
1806 llvm::any_of(op->getResultTypes(), hasZeroDimVectorType);
1807}
1808
1809/// All BroadcastOps, as well as ShapeCastOps that only prepend 1s, are
1810/// considered to be 'broadcastlike'.
1811static bool isBroadcastLike(Operation *op) {
1812 if (isa<BroadcastOp>(op))
1813 return true;
1814
1815 auto shapeCast = dyn_cast<ShapeCastOp>(op);
1816 if (!shapeCast)
1817 return false;
1818
1819 // Check that shape_cast **only** prepends 1s, like (2,3) -> (1,1,2,3).
1820 // Checking that the destination shape has a prefix of 1s is not sufficient,
1821 // for example (2,3) -> (1,3,2) is not broadcastlike. A sufficient condition
1822 // is that the source shape is a suffix of the destination shape.
1823 VectorType srcType = shapeCast.getSourceVectorType();
1824 ArrayRef<int64_t> srcShape = srcType.getShape();
1825 uint64_t srcRank = srcType.getRank();
1826 ArrayRef<int64_t> dstShape = shapeCast.getType().getShape();
1827 return dstShape.size() >= srcRank && dstShape.take_back(srcRank) == srcShape;
1828}
1829
1830/// Fold extract(broadcast(X)) to either extract(X) or just X.
1831///
1832/// Example:
1833///
1834/// broadcast extract [1][2]
1835/// (3, 4) --------> (2, 3, 4) ----------------> (4)
1836///
1837/// becomes
1838/// extract [1]
1839/// (3,4) -------------------------------------> (4)
1840///
1841///
1842/// The variable names used in this implementation correspond to the above
1843/// shapes as,
1844///
1845/// - (3, 4) is `input` shape.
1846/// - (2, 3, 4) is `broadcast` shape.
1847/// - (4) is `extract` shape.
1848///
1849/// This folding is possible when the suffix of `input` shape is the same as
1850/// `extract` shape.
1851static Value foldExtractFromBroadcast(ExtractOp extractOp) {
1852
1853 Operation *defOp = extractOp.getSource().getDefiningOp();
1854 if (!defOp || !isBroadcastLike(defOp))
1855 return Value();
1856
1857 Value input = defOp->getOperand(0);
1858
1859 // Replace extract(broadcast(X)) with X
1860 if (extractOp.getType() == input.getType())
1861 return input;
1862
1863 // Get required types and ranks in the chain
1864 // input -> broadcast -> extract
1865 // (scalars are treated as rank-0).
1866 auto inputType = llvm::dyn_cast<VectorType>(input.getType());
1867 auto extractType = llvm::dyn_cast<VectorType>(extractOp.getType());
1868 unsigned inputRank = inputType ? inputType.getRank() : 0;
1869 unsigned broadcastRank = extractOp.getSourceVectorType().getRank();
1870 unsigned extractRank = extractType ? extractType.getRank() : 0;
1871
1872 // Cannot do without the broadcast if overall the rank increases.
1873 if (extractRank > inputRank)
1874 return Value();
1875
1876 // The above condition guarantees that input is a vector.
1877 assert(inputType && "input must be a vector type because of previous checks");
1878 ArrayRef<int64_t> inputShape = inputType.getShape();
1879
1880 // In the case where there is a broadcast dimension in the suffix, it is not
1881 // possible to replace extract(broadcast(X)) with extract(X). Example:
1882 //
1883 // broadcast extract
1884 // (1) --------> (3,4) ------> (4)
1885 if (extractType &&
1886 extractType.getShape() != inputShape.take_back(extractRank))
1887 return Value();
1888
1889 // Replace extract(broadcast(X)) with extract(X).
1890 // First, determine the new extraction position.
1891 unsigned deltaOverall = inputRank - extractRank;
1892 unsigned deltaBroadcast = broadcastRank - inputRank;
1893 SmallVector<OpFoldResult> oldPositions = extractOp.getMixedPosition();
1894 SmallVector<OpFoldResult> newPositions(deltaOverall);
1895 IntegerAttr zero = OpBuilder(extractOp.getContext()).getIndexAttr(0);
1896 for (auto [i, size] : llvm::enumerate(inputShape.take_front(deltaOverall))) {
1897 newPositions[i] = size == 1 ? zero : oldPositions[i + deltaBroadcast];
1898 }
1899 auto [staticPos, dynPos] = decomposeMixedValues(newPositions);
1900 extractOp->setOperands(
1901 llvm::to_vector(llvm::concat<Value>(ValueRange(input), dynPos)));
1902 extractOp.setStaticPosition(staticPos);
1903 return extractOp.getResult();
1904}
1905
1906/// Fold extractOp coming from ShuffleOp.
1907///
1908/// Example:
1909///
1910/// %shuffle = vector.shuffle %a, %b [0, 8, 7, 15]
1911/// : vector<8xf32>, vector<8xf32>
1912/// %extract = vector.extract %shuffle[3] : f32 from vector<4xf32>
1913/// ->
1914/// %extract = vector.extract %b[7] : f32 from vector<8xf32>
1915///
1916static Value foldExtractFromShuffle(ExtractOp extractOp) {
1917 // Dynamic positions are not folded as the resulting code would be more
1918 // complex than the input code.
1919 if (extractOp.hasDynamicPosition())
1920 return Value();
1921
1922 auto shuffleOp = extractOp.getSource().getDefiningOp<ShuffleOp>();
1923 if (!shuffleOp)
1924 return Value();
1925
1926 // TODO: 0-D or multi-dimensional vectors not supported yet.
1927 if (shuffleOp.getResultVectorType().getRank() != 1)
1928 return Value();
1929
1930 int64_t inputVecSize = shuffleOp.getV1().getType().getShape()[0];
1931 auto shuffleMask = shuffleOp.getMask();
1932 int64_t extractIdx = extractOp.getStaticPosition()[0];
1933 int64_t shuffleIdx = shuffleMask[extractIdx];
1934
1935 // Find the shuffled vector to extract from based on the shuffle index.
1936 if (shuffleIdx < inputVecSize) {
1937 extractOp.setOperand(0, shuffleOp.getV1());
1938 extractOp.setStaticPosition({shuffleIdx});
1939 } else {
1940 extractOp.setOperand(0, shuffleOp.getV2());
1941 extractOp.setStaticPosition({shuffleIdx - inputVecSize});
1942 }
1943
1944 return extractOp.getResult();
1945}
1946
1947// Fold extractOp with source coming from ShapeCast op.
1948static Value foldExtractFromShapeCast(ExtractOp extractOp) {
1949 // TODO: Canonicalization for dynamic position not implemented yet.
1950 if (extractOp.hasDynamicPosition())
1951 return Value();
1952
1953 auto shapeCastOp = extractOp.getSource().getDefiningOp<vector::ShapeCastOp>();
1954 if (!shapeCastOp)
1955 return Value();
1956
1957 // Get the nth dimension size starting from lowest dimension.
1958 auto getDimReverse = [](VectorType type, int64_t n) {
1959 return type.getShape().take_back(n + 1).front();
1960 };
1961 int64_t destinationRank =
1962 llvm::isa<VectorType>(extractOp.getType())
1963 ? llvm::cast<VectorType>(extractOp.getType()).getRank()
1964 : 0;
1965 if (destinationRank > shapeCastOp.getSourceVectorType().getRank())
1966 return Value();
1967 if (destinationRank > 0) {
1968 auto destinationType =
1969 llvm::cast<VectorType>(extractOp.getResult().getType());
1970 for (int64_t i = 0; i < destinationRank; i++) {
1971 // The lowest dimension of the destination must match the lowest
1972 // dimension of the shapecast op source.
1973 // TODO: This case could be support in a canonicalization pattern.
1974 if (getDimReverse(shapeCastOp.getSourceVectorType(), i) !=
1975 getDimReverse(destinationType, i))
1976 return Value();
1977 }
1978 }
1979 // Extract the strides associated with the extract op vector source. Then use
1980 // this to calculate a linearized position for the extract.
1981 SmallVector<int64_t> extractedPos(extractOp.getStaticPosition());
1982 std::reverse(extractedPos.begin(), extractedPos.end());
1984 int64_t stride = 1;
1985 for (int64_t i = 0, e = extractedPos.size(); i < e; i++) {
1986 strides.push_back(stride);
1987 stride *=
1988 getDimReverse(extractOp.getSourceVectorType(), i + destinationRank);
1989 }
1990
1991 int64_t position = linearize(extractedPos, strides);
1992 // Then extract the strides associated to the shapeCast op vector source and
1993 // delinearize the position using those strides.
1994 SmallVector<int64_t, 4> newStrides;
1995 int64_t numDimension =
1996 shapeCastOp.getSourceVectorType().getRank() - destinationRank;
1997 stride = 1;
1998 for (int64_t i = 0; i < numDimension; i++) {
1999 newStrides.push_back(stride);
2000 stride *=
2001 getDimReverse(shapeCastOp.getSourceVectorType(), i + destinationRank);
2002 }
2003 std::reverse(newStrides.begin(), newStrides.end());
2004 SmallVector<int64_t, 4> newPosition = delinearize(position, newStrides);
2005 // OpBuilder is only used as a helper to build an I64ArrayAttr.
2006 OpBuilder b(extractOp.getContext());
2007 extractOp.setStaticPosition(newPosition);
2008 extractOp.setOperand(0, shapeCastOp.getSource());
2009 return extractOp.getResult();
2010}
2011
2012/// Fold an ExtractOp from ExtractStridedSliceOp.
2013static Value foldExtractFromExtractStrided(ExtractOp extractOp) {
2014 // TODO: Canonicalization for dynamic position not implemented yet.
2015 if (extractOp.hasDynamicPosition())
2016 return Value();
2017
2018 auto extractStridedSliceOp =
2019 extractOp.getSource().getDefiningOp<vector::ExtractStridedSliceOp>();
2020 if (!extractStridedSliceOp)
2021 return Value();
2022
2023 // 0-D vectors not supported.
2024 assert(!hasZeroDimVectors(extractOp) && "0-D vectors not supported");
2025 if (hasZeroDimVectors(extractStridedSliceOp))
2026 return Value();
2027
2028 // Return if 'extractStridedSliceOp' has non-unit strides.
2029 if (extractStridedSliceOp.hasNonUnitStrides())
2030 return Value();
2031
2032 // Trim offsets for dimensions fully extracted.
2033 auto sliceOffsets =
2034 extractVector<int64_t>(extractStridedSliceOp.getOffsets());
2035 while (!sliceOffsets.empty()) {
2036 size_t lastOffset = sliceOffsets.size() - 1;
2037 if (sliceOffsets.back() != 0 ||
2038 extractStridedSliceOp.getType().getDimSize(lastOffset) !=
2039 extractStridedSliceOp.getSourceVectorType().getDimSize(lastOffset))
2040 break;
2041 sliceOffsets.pop_back();
2042 }
2043 unsigned destinationRank = 0;
2044 if (auto vecType = llvm::dyn_cast<VectorType>(extractOp.getType()))
2045 destinationRank = vecType.getRank();
2046 // The dimensions of the result need to be untouched by the
2047 // extractStridedSlice op.
2048 if (destinationRank > extractStridedSliceOp.getSourceVectorType().getRank() -
2049 sliceOffsets.size())
2050 return Value();
2051
2052 SmallVector<int64_t> extractedPos(extractOp.getStaticPosition());
2053 assert(extractedPos.size() >= sliceOffsets.size());
2054 for (size_t i = 0, e = sliceOffsets.size(); i < e; i++)
2055 extractedPos[i] = extractedPos[i] + sliceOffsets[i];
2056 extractOp.getSourceMutable().assign(extractStridedSliceOp.getSource());
2057
2058 // OpBuilder is only used as a helper to build an I64ArrayAttr.
2059 OpBuilder b(extractOp.getContext());
2060 extractOp.setStaticPosition(extractedPos);
2061 return extractOp.getResult();
2062}
2063
2064/// Fold extract_op fed from a chain of insertStridedSlice ops.
2065static Value foldExtractStridedOpFromInsertChain(ExtractOp extractOp) {
2066 // TODO: Canonicalization for dynamic position not implemented yet.
2067 if (extractOp.hasDynamicPosition())
2068 return Value();
2069
2070 int64_t destinationRank =
2071 llvm::isa<VectorType>(extractOp.getType())
2072 ? llvm::cast<VectorType>(extractOp.getType()).getRank()
2073 : 0;
2074 auto insertOp = extractOp.getSource().getDefiningOp<InsertStridedSliceOp>();
2075 if (!insertOp)
2076 return Value();
2077
2078 // 0-D vectors not supported.
2079 assert(!hasZeroDimVectors(extractOp) && "0-D vectors not supported");
2080 if (hasZeroDimVectors(insertOp))
2081 return Value();
2082
2083 while (insertOp) {
2084 int64_t insertRankDiff = insertOp.getDestVectorType().getRank() -
2085 insertOp.getSourceVectorType().getRank();
2086 if (destinationRank > insertOp.getSourceVectorType().getRank())
2087 return Value();
2088 auto insertOffsets = extractVector<int64_t>(insertOp.getOffsets());
2089 ArrayRef<int64_t> extractOffsets = extractOp.getStaticPosition();
2090
2091 if (llvm::any_of(insertOp.getStrides(), [](Attribute attr) {
2092 return llvm::cast<IntegerAttr>(attr).getInt() != 1;
2093 }))
2094 return Value();
2095 bool disjoint = false;
2096 SmallVector<int64_t, 4> offsetDiffs;
2097 for (unsigned dim = 0, e = extractOffsets.size(); dim < e; ++dim) {
2098 int64_t start = insertOffsets[dim];
2099 int64_t size =
2100 (dim < insertRankDiff)
2101 ? 1
2102 : insertOp.getSourceVectorType().getDimSize(dim - insertRankDiff);
2103 int64_t end = start + size;
2104 int64_t offset = extractOffsets[dim];
2105 // Check if the start of the extract offset is in the interval inserted.
2106 if (start <= offset && offset < end) {
2107 if (dim >= insertRankDiff)
2108 offsetDiffs.push_back(offset - start);
2109 continue;
2110 }
2111 disjoint = true;
2112 break;
2113 }
2114 // The extract element chunk overlap with the vector inserted.
2115 if (!disjoint) {
2116 // If any of the inner dimensions are only partially inserted we have a
2117 // partial overlap.
2118 int64_t srcRankDiff =
2119 insertOp.getSourceVectorType().getRank() - destinationRank;
2120 for (int64_t i = 0; i < destinationRank; i++) {
2121 if (insertOp.getSourceVectorType().getDimSize(i + srcRankDiff) !=
2122 insertOp.getDestVectorType().getDimSize(i + srcRankDiff +
2123 insertRankDiff))
2124 return Value();
2125 }
2126 extractOp.getSourceMutable().assign(insertOp.getValueToStore());
2127 // OpBuilder is only used as a helper to build an I64ArrayAttr.
2128 OpBuilder b(extractOp.getContext());
2129 extractOp.setStaticPosition(offsetDiffs);
2130 return extractOp.getResult();
2131 }
2132 // If the chunk extracted is disjoint from the chunk inserted, keep
2133 // looking in the insert chain.
2134 insertOp = insertOp.getDest().getDefiningOp<InsertStridedSliceOp>();
2135 }
2136 return Value();
2137}
2138
2139/// Try to fold the extraction of a scalar from a vector defined by
2140/// vector.from_elements. E.g.:
2141///
2142/// %0 = vector.from_elements %a, %b : vector<2xf32>
2143/// %1 = vector.extract %0[0] : f32 from vector<2xf32>
2144/// ==> fold to %a
2145static Value foldScalarExtractFromFromElements(ExtractOp extractOp) {
2146 // Dynamic extractions cannot be folded.
2147 if (extractOp.hasDynamicPosition())
2148 return {};
2149
2150 // Look for extract(from_elements).
2151 auto fromElementsOp = extractOp.getSource().getDefiningOp<FromElementsOp>();
2152 if (!fromElementsOp)
2153 return {};
2154
2155 // Scalable vectors are not supported.
2156 auto vecType = llvm::cast<VectorType>(fromElementsOp.getType());
2157 if (vecType.isScalable())
2158 return {};
2159
2160 // Only extractions of scalars are supported.
2161 int64_t rank = vecType.getRank();
2162 ArrayRef<int64_t> indices = extractOp.getStaticPosition();
2163 if (extractOp.getType() != vecType.getElementType())
2164 return {};
2165 assert(static_cast<int64_t>(indices.size()) == rank &&
2166 "unexpected number of indices");
2167
2168 // Compute flattened/linearized index and fold to operand.
2169 int flatIndex = 0;
2170 int stride = 1;
2171 for (int i = rank - 1; i >= 0; --i) {
2172 flatIndex += indices[i] * stride;
2173 stride *= vecType.getDimSize(i);
2174 }
2175 return fromElementsOp.getElements()[flatIndex];
2176}
2177
2178/// If the dynamic indices of `extractOp` or `insertOp` are in fact constants,
2179/// then fold it.
2180template <typename OpType, typename AdaptorType>
2181static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor,
2182 SmallVectorImpl<Value> &operands) {
2183 std::vector<int64_t> staticPosition = op.getStaticPosition().vec();
2184 OperandRange dynamicPosition = op.getDynamicPosition();
2185 ArrayRef<Attribute> dynamicPositionAttr = adaptor.getDynamicPosition();
2187 if constexpr (std::is_same_v<OpType, ExtractOp>)
2188 vectorShape = op.getSourceVectorType().getShape();
2189 else
2190 vectorShape = op.getDestVectorType().getShape();
2191
2192 // If the dynamic operands is empty, it is returned directly.
2193 if (!dynamicPosition.size())
2194 return {};
2195
2196 // `index` is used to iterate over the `dynamicPosition`.
2197 unsigned index = 0;
2198
2199 // `opChange` is a flag. If it is true, it means to update `op` in place.
2200 bool opChange = false;
2201 for (unsigned i = 0, e = staticPosition.size(); i < e; ++i) {
2202 if (ShapedType::isStatic(staticPosition[i]))
2203 continue;
2204 Attribute positionAttr = dynamicPositionAttr[index];
2205 Value position = dynamicPosition[index++];
2206 if (auto attr = mlir::dyn_cast_if_present<IntegerAttr>(positionAttr)) {
2207 int64_t value = attr.getInt();
2208 // Do not fold if the value is out of bounds (-1 signifies a poison
2209 // value rather than OOB index).
2210 if (value >= -1 && value < vectorShape[i]) {
2211 staticPosition[i] = attr.getInt();
2212 opChange = true;
2213 continue;
2214 }
2215 }
2216 operands.push_back(position);
2217 }
2218
2219 if (opChange) {
2220 op.setStaticPosition(staticPosition);
2221 op.getOperation()->setOperands(operands);
2222 // Return the original result to indicate an in-place folding happened.
2223 return op.getResult();
2224 }
2225 return {};
2226}
2227
2228/// Fold an insert or extract operation into an poison value when a poison index
2229/// is found at any dimension of the static position.
2231 ArrayRef<int64_t> staticPos,
2232 int64_t poisonVal) {
2233 if (!is_contained(staticPos, poisonVal))
2234 return {};
2235
2236 return ub::PoisonAttr::get(context);
2237}
2238
2239/// Fold a vector extract from is a poison source.
2241 if (matchPattern(srcAttr, ub::m_Poison()))
2242 return srcAttr;
2243
2244 return {};
2245}
2246
2247/// Fold a vector extract extracting from a DenseElementsAttr.
2249 Attribute srcAttr) {
2250 auto denseAttr = dyn_cast_if_present<DenseElementsAttr>(srcAttr);
2251 if (!denseAttr) {
2252 return {};
2253 }
2254
2255 if (denseAttr.isSplat()) {
2256 Attribute newAttr = denseAttr.getSplatValue<Attribute>();
2257 if (auto vecDstType = dyn_cast<VectorType>(extractOp.getType()))
2258 newAttr = DenseElementsAttr::get(vecDstType, newAttr);
2259 return newAttr;
2260 }
2261
2262 auto vecTy = cast<VectorType>(extractOp.getSourceVectorType());
2263 if (vecTy.isScalable())
2264 return {};
2265
2266 if (extractOp.hasDynamicPosition()) {
2267 return {};
2268 }
2269
2270 // Materializing subsets of a large constant array can generally lead to
2271 // explosion in IR size because of different combination of subsets that
2272 // can exist. However, vector.extract is a restricted form of subset
2273 // extract where you can only extract non-overlapping (or the same) subset for
2274 // a given rank of the subset. Because of this property, the IR size can only
2275 // increase at most by `rank * size(array)` from a single constant array being
2276 // extracted by multiple extracts.
2277
2278 // Calculate the linearized position of the continuous chunk of elements to
2279 // extract.
2280 SmallVector<int64_t> completePositions(vecTy.getRank(), 0);
2281 copy(extractOp.getStaticPosition(), completePositions.begin());
2282 int64_t startPos =
2283 linearize(completePositions, computeStrides(vecTy.getShape()));
2284 auto denseValuesBegin = denseAttr.value_begin<TypedAttr>() + startPos;
2285
2286 TypedAttr newAttr;
2287 if (auto resVecTy = dyn_cast<VectorType>(extractOp.getType())) {
2288 SmallVector<Attribute> elementValues(
2289 denseValuesBegin, denseValuesBegin + resVecTy.getNumElements());
2290 newAttr = DenseElementsAttr::get(resVecTy, elementValues);
2291 } else {
2292 newAttr = *denseValuesBegin;
2293 }
2294
2295 return newAttr;
2296}
2297
2298OpFoldResult ExtractOp::fold(FoldAdaptor adaptor) {
2299 // Fold "vector.extract %v[] : vector<2x2xf32> from vector<2x2xf32>" to %v.
2300 // Note: Do not fold "vector.extract %v[] : f32 from vector<f32>" (type
2301 // mismatch).
2302 if (getNumIndices() == 0 && getSource().getType() == getResult().getType())
2303 return getSource();
2304 if (auto res = foldPoisonSrcExtractOp(adaptor.getSource()))
2305 return res;
2306 // Fold `arith.constant` indices into the `vector.extract` operation.
2307 // Do not stop here as this fold may enable subsequent folds that require
2308 // constant indices.
2309 SmallVector<Value> operands = {getSource()};
2310 auto inplaceFolded = extractInsertFoldConstantOp(*this, adaptor, operands);
2311
2312 if (auto res = foldPoisonIndexInsertExtractOp(
2313 getContext(), adaptor.getStaticPosition(), kPoisonIndex))
2314 return res;
2315 if (auto res = foldDenseElementsAttrSrcExtractOp(*this, adaptor.getSource()))
2316 return res;
2317 if (succeeded(foldExtractOpFromExtractChain(*this)))
2318 return getResult();
2319 if (auto res = ExtractFromInsertTransposeChainState(*this).fold())
2320 return res;
2321 if (auto res = foldExtractFromBroadcast(*this))
2322 return res;
2323 if (auto res = foldExtractFromShuffle(*this))
2324 return res;
2325 if (auto res = foldExtractFromShapeCast(*this))
2326 return res;
2327 if (auto val = foldExtractFromExtractStrided(*this))
2328 return val;
2329 if (auto val = foldExtractStridedOpFromInsertChain(*this))
2330 return val;
2331 if (auto val = foldScalarExtractFromFromElements(*this))
2332 return val;
2333
2334 return inplaceFolded;
2335}
2336
2337namespace {
2338
2339// Pattern to rewrite a ExtractOp(Broadcast) -> Broadcast.
2340class ExtractOpFromBroadcast final : public OpRewritePattern<ExtractOp> {
2341public:
2342 using Base::Base;
2343
2344 LogicalResult matchAndRewrite(ExtractOp extractOp,
2345 PatternRewriter &rewriter) const override {
2346
2347 Operation *defOp = extractOp.getSource().getDefiningOp();
2348 VectorType outType = dyn_cast<VectorType>(extractOp.getType());
2349 if (!defOp || !isBroadcastLike(defOp) || !outType)
2350 return failure();
2351
2352 Value source = defOp->getOperand(0);
2353 if (isBroadcastableTo(source.getType(), outType) !=
2354 BroadcastableToResult::Success)
2355 return failure();
2356
2357 rewriter.replaceOpWithNewOp<BroadcastOp>(extractOp, outType, source);
2358 return success();
2359 }
2360};
2361
2362// Pattern to rewrite a ExtractOp(CreateMask) -> CreateMask.
2363class ExtractOpFromCreateMask final : public OpRewritePattern<ExtractOp> {
2364public:
2365 using Base::Base;
2366
2367 LogicalResult matchAndRewrite(ExtractOp extractOp,
2368 PatternRewriter &rewriter) const override {
2369 auto createMaskOp =
2370 extractOp.getSource().getDefiningOp<vector::CreateMaskOp>();
2371 if (!createMaskOp)
2372 return failure();
2373
2374 VectorType extractedMaskType =
2375 llvm::dyn_cast<VectorType>(extractOp.getResult().getType());
2376
2377 if (!extractedMaskType)
2378 return failure();
2379
2380 auto maskOperands = createMaskOp.getOperands();
2381 ArrayRef<int64_t> extractOpPos = extractOp.getStaticPosition();
2382 VectorType maskType = createMaskOp.getVectorType();
2383
2384 bool containsUnknownDims = false;
2385 bool allFalse = getMaskFormat(createMaskOp) == MaskFormat::AllFalse;
2386
2387 for (size_t dimIdx = 0; !allFalse && dimIdx < extractOpPos.size();
2388 dimIdx++) {
2389 int64_t pos = extractOpPos[dimIdx];
2390 Value operand = maskOperands[dimIdx];
2391 auto constantOp = operand.getDefiningOp<arith::ConstantOp>();
2392 if (!constantOp) {
2393 // Bounds of this dim unknown.
2394 containsUnknownDims = true;
2395 continue;
2396 }
2397
2398 int64_t createMaskBound =
2399 llvm::cast<IntegerAttr>(constantOp.getValue()).getInt();
2400
2401 if (pos != ShapedType::kDynamic) {
2402 // If any position is outside the range from the `create_mask`, then the
2403 // extracted mask will be all-false.
2404 allFalse |= pos >= createMaskBound;
2405 } else if (createMaskBound < maskType.getDimSize(dimIdx)) {
2406 // This dim is not all-true and since this is a dynamic index we don't
2407 // know if the extraction is within the true or false region.
2408 // Note: Zero dims have already handled via getMaskFormat().
2409 containsUnknownDims = true;
2410 }
2411 }
2412
2413 if (allFalse) {
2414 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
2415 extractOp, DenseElementsAttr::get(extractedMaskType, false));
2416 } else if (!containsUnknownDims) {
2417 rewriter.replaceOpWithNewOp<vector::CreateMaskOp>(
2418 extractOp, extractedMaskType,
2419 maskOperands.drop_front(extractOpPos.size()));
2420 } else {
2421 return failure();
2422 }
2423 return success();
2424 }
2425};
2426
2427// Pattern to rewrite a ExtractOp(ConstantMask) -> ConstantMask.
2428class ExtractOpFromConstantMask final : public OpRewritePattern<ExtractOp> {
2429public:
2430 using Base::Base;
2431
2432 LogicalResult matchAndRewrite(ExtractOp extractOp,
2433 PatternRewriter &rewriter) const override {
2434 auto constantMaskOp =
2435 extractOp.getSource().getDefiningOp<vector::ConstantMaskOp>();
2436 if (!constantMaskOp)
2437 return failure();
2438
2439 Type resultType = extractOp.getResult().getType();
2440 auto extractedMaskType = dyn_cast<VectorType>(resultType);
2441
2442 ArrayRef<int64_t> extractOpPos = extractOp.getStaticPosition();
2443 ArrayRef<int64_t> maskDimSizes = constantMaskOp.getMaskDimSizes();
2444
2445 VectorType maskType = constantMaskOp.getVectorType();
2446
2447 // Check if any extracted position is outside the mask bounds.
2448 for (size_t dimIdx = 0; dimIdx < extractOpPos.size(); dimIdx++) {
2449 int64_t pos = extractOpPos[dimIdx];
2450 if (pos == ShapedType::kDynamic) {
2451 // If the dim is all-true, a dynamic index is fine — any position
2452 // is within the masked region.
2453 if (maskDimSizes[dimIdx] == maskType.getDimSize(dimIdx))
2454 continue;
2455 // Otherwise we don't know if the position is inside or outside of
2456 // the masked area, so bail out.
2457 return failure();
2458 }
2459
2460 // If the position is statically outside of the masked area, the result
2461 // will be all-false.
2462 if (pos >= maskDimSizes[dimIdx]) {
2463 if (extractedMaskType) {
2464 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
2465 extractOp, DenseElementsAttr::get(extractedMaskType, false));
2466 } else {
2467 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
2468 extractOp, rewriter.getIntegerAttr(resultType, false));
2469 }
2470 return success();
2471 }
2472 }
2473
2474 // All positions are within the mask bounds.
2475 if (extractedMaskType) {
2476 // Vector result: the result is a constant_mask with the remaining
2477 // dimensions.
2478 rewriter.replaceOpWithNewOp<vector::ConstantMaskOp>(
2479 extractOp, extractedMaskType,
2480 maskDimSizes.drop_front(extractOpPos.size()));
2481 } else {
2482 // Scalar result: all positions are within the masked region, so the
2483 // result is true.
2484 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
2485 extractOp, rewriter.getIntegerAttr(resultType, true));
2486 }
2487 return success();
2488 }
2489};
2490
2491// Folds extract(shape_cast(..)) into shape_cast when the total element count
2492// does not change.
2493LogicalResult foldExtractFromShapeCastToShapeCast(ExtractOp extractOp,
2494 PatternRewriter &rewriter) {
2495 auto castOp = extractOp.getSource().getDefiningOp<ShapeCastOp>();
2496 if (!castOp)
2497 return failure();
2498
2499 VectorType sourceType = castOp.getSourceVectorType();
2500 auto targetType = dyn_cast<VectorType>(extractOp.getResult().getType());
2501 if (!targetType)
2502 return failure();
2503
2504 if (sourceType.getNumElements() != targetType.getNumElements())
2505 return failure();
2506
2507 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(extractOp, targetType,
2508 castOp.getSource());
2509 return success();
2510}
2511
2512/// Try to canonicalize the extraction of a subvector from a vector defined by
2513/// vector.from_elements. E.g.:
2514///
2515/// %0 = vector.from_elements %a, %b, %a, %a : vector<2x2xf32>
2516/// %1 = vector.extract %0[0] : vector<2xf32> from vector<2x2xf32>
2517/// ==> canonicalize to vector.from_elements %a, %b : vector<2xf32>
2518LogicalResult foldExtractFromFromElements(ExtractOp extractOp,
2519 PatternRewriter &rewriter) {
2520 // Dynamic positions are not supported.
2521 if (extractOp.hasDynamicPosition())
2522 return failure();
2523
2524 // Scalar extracts are handled by the folder.
2525 auto resultType = dyn_cast<VectorType>(extractOp.getType());
2526 if (!resultType)
2527 return failure();
2528
2529 // Look for extracts from a from_elements op.
2530 auto fromElementsOp = extractOp.getSource().getDefiningOp<FromElementsOp>();
2531 if (!fromElementsOp)
2532 return failure();
2533 VectorType inputType = fromElementsOp.getType();
2534
2535 // Scalable vectors are not supported.
2536 if (resultType.isScalable() || inputType.isScalable())
2537 return failure();
2538
2539 // Compute the position of first extracted element and flatten/linearize the
2540 // position.
2541 SmallVector<int64_t> firstElementPos =
2542 llvm::to_vector(extractOp.getStaticPosition());
2543 firstElementPos.append(/*NumInputs=*/resultType.getRank(), /*Elt=*/0);
2544 int flatIndex = 0;
2545 int stride = 1;
2546 for (int64_t i = inputType.getRank() - 1; i >= 0; --i) {
2547 flatIndex += firstElementPos[i] * stride;
2548 stride *= inputType.getDimSize(i);
2549 }
2550
2551 // Replace the op with a smaller from_elements op.
2552 rewriter.replaceOpWithNewOp<FromElementsOp>(
2553 extractOp, resultType,
2554 fromElementsOp.getElements().slice(flatIndex,
2555 resultType.getNumElements()));
2556 return success();
2557}
2558
2559/// Replace `vector.extract` with `vector.shape_cast`.
2560///
2561/// BEFORE:
2562/// %0 = vector.extract %arg0[0] : vector<4xf32> from vector<1x4xf32>
2563/// AFTER:
2564/// %0 = vector.shape_cast %arg0 : vector<1x4xf32> to vector<4xf32>
2565///
2566/// The canonical form of vector operations that reshape vectors is shape_cast.
2567struct ExtractToShapeCast final : OpRewritePattern<vector::ExtractOp> {
2568 using Base::Base;
2569 LogicalResult matchAndRewrite(vector::ExtractOp extractOp,
2570 PatternRewriter &rewriter) const override {
2571 VectorType sourceType = extractOp.getSourceVectorType();
2572 VectorType outType = dyn_cast<VectorType>(extractOp.getType());
2573 if (!outType)
2574 return failure();
2575
2576 if (sourceType.getNumElements() != outType.getNumElements())
2577 return rewriter.notifyMatchFailure(
2578 extractOp, "extract to vector with fewer elements");
2579
2580 // Negative values in `position` means that the extacted value is poison.
2581 // There is a vector.extract folder for this.
2582 if (llvm::any_of(extractOp.getMixedPosition(),
2583 [](OpFoldResult v) { return !isConstantIntValue(v, 0); }))
2584 return rewriter.notifyMatchFailure(extractOp,
2585 "leaving for extract poison folder");
2586
2587 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(extractOp, outType,
2588 extractOp.getSource());
2589
2590 return success();
2591 }
2592};
2593
2594/// Folds vector.extract from vector.insert when the extract position is a
2595/// prefix of the insert position and the remaining (un-indexed) dimensions
2596/// of the extracted sub-vector are all size 1. In that case the extracted
2597/// value is fully determined by the inserted value.
2598///
2599/// Examples:
2600/// %ins = vector.insert %s, %v [3, 0] : f32 into vector<16x1xf32>
2601/// %ext = vector.extract %ins [3] : vector<1xf32> from vector<16x1xf32>
2602/// folds to:
2603/// %ext = vector.broadcast %s : f32 to vector<1xf32>
2604///
2605/// %ins = vector.insert %s, %v [0, 0] : vector<1xf32> into vector<16x1x1xf32>
2606// %ext = vector.extract %ins [0] : vector<1x1xf32> from vector<16x1x1xf32>
2607/// folds to:
2608/// %ext = vector.shape_cast %arg0 : vector<1xf32> to vector<1x1xf32>
2609struct FoldExtractFromInsertUnitDim final
2610 : OpRewritePattern<vector::ExtractOp> {
2611 using Base::Base;
2612
2613 LogicalResult matchAndRewrite(vector::ExtractOp extractOp,
2614 PatternRewriter &rewriter) const override {
2615 if (extractOp.hasDynamicPosition())
2616 return failure();
2617
2618 auto insertOp = extractOp.getSource().getDefiningOp<vector::InsertOp>();
2619 if (!insertOp || insertOp.hasDynamicPosition())
2620 return failure();
2621
2622 ArrayRef<int64_t> extractPos = extractOp.getStaticPosition();
2623 ArrayRef<int64_t> insertPos = insertOp.getStaticPosition();
2624
2625 // The extract position must be a strict prefix of the insert position.
2626 if (extractPos.size() >= insertPos.size() ||
2627 extractPos != insertPos.take_front(extractPos.size()))
2628 return failure();
2629
2630 // The remaining dimensions (those not indexed by the extract) must all
2631 // be size 1 in the source vector type. This guarantees that the inserted
2632 // value fully determines the extracted sub-vector.
2633 auto srcVecType = extractOp.getSourceVectorType();
2634 for (int64_t i = extractPos.size(), e = srcVecType.getRank(); i < e; ++i)
2635 if (srcVecType.getDimSize(i) != 1)
2636 return failure();
2637
2638 Value inserted = insertOp.getValueToStore();
2639 Type extractedType = extractOp.getResult().getType();
2640 if (isa<VectorType>(inserted.getType())) {
2641 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(extractOp, extractedType,
2642 inserted);
2643 } else {
2644 // The inserted value fully determines the extracted sub-vector; broadcast
2645 // it to the extracted type.
2646 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(
2647 extractOp, extractOp.getResult().getType(),
2648 insertOp.getValueToStore());
2649 }
2650 return success();
2651 }
2652};
2653
2654} // namespace
2655
2656void ExtractOp::getCanonicalizationPatterns(RewritePatternSet &results,
2657 MLIRContext *context) {
2658 results.add<ExtractOpFromBroadcast, ExtractOpFromCreateMask,
2659 ExtractOpFromConstantMask, ExtractToShapeCast,
2660 FoldExtractFromInsertUnitDim>(context);
2661 results.add(foldExtractFromShapeCastToShapeCast);
2662 results.add(foldExtractFromFromElements);
2663}
2664
2666 SmallVectorImpl<int64_t> &results) {
2667 for (auto attr : arrayAttr)
2668 results.push_back(llvm::cast<IntegerAttr>(attr).getInt());
2669}
2670
2671//===----------------------------------------------------------------------===//
2672// FmaOp
2673//===----------------------------------------------------------------------===//
2674
2675std::optional<SmallVector<int64_t, 4>> FMAOp::getIterationShapeToUnroll() {
2676 return llvm::to_vector<4>(getVectorType().getShape());
2677}
2678
2679//===----------------------------------------------------------------------===//
2680// ToElementsOp
2681//===----------------------------------------------------------------------===//
2682
2683/// Returns true if all the `operands` are defined by `defOp`.
2684/// Otherwise, returns false.
2685static bool haveSameDefiningOp(OperandRange operands, Operation *defOp) {
2686 if (operands.empty())
2687 return false;
2688
2689 return llvm::all_of(operands, [&](Value operand) {
2690 Operation *currentDef = operand.getDefiningOp();
2691 return currentDef == defOp;
2692 });
2693}
2694
2695/// Folds vector.to_elements(vector.from_elements(%e0, %e1, ...)) into
2696/// (%e0, %e1, ...). For example:
2697///
2698/// %0 = vector.from_elements %a, %b, %c : vector<3xf32>
2699/// %1:3 = vector.to_elements %0 : vector<3xf32>
2700/// user_op %1#0, %1#1, %1#2
2701///
2702/// becomes:
2703///
2704/// user_op %a, %b, %c
2705///
2706static LogicalResult
2707foldToElementsFromElements(ToElementsOp toElementsOp,
2709 auto fromElementsOp =
2710 toElementsOp.getSource().getDefiningOp<FromElementsOp>();
2711 if (!fromElementsOp)
2712 return failure();
2713
2714 llvm::append_range(results, fromElementsOp.getElements());
2715 return success();
2716}
2717
2718/// Folds vector.to_elements(vector.broadcast(%x)) for the scalar case only.
2719///
2720/// Example:
2721/// %b = vector.broadcast %x : i32 to vector<3xf32>
2722/// %e:3 = vector.to_elements %b : vector<3xf32>
2723/// user_op %e#0, %e#1, %e#2
2724/// becomes:
2725/// user_op %x, %x, %x
2726///
2727/// The vector source case is handled by a canonicalization pattern.
2728static LogicalResult
2729foldToElementsOfBroadcast(ToElementsOp toElementsOp,
2731 auto bcastOp = toElementsOp.getSource().getDefiningOp<BroadcastOp>();
2732 if (!bcastOp)
2733 return failure();
2734 // Vectors are handled in the ToElementsOfBroadcast RewritePattern.
2735 if (isa<VectorType>(bcastOp.getSource().getType()))
2736 return failure();
2737
2738 auto resultVecType = cast<VectorType>(toElementsOp.getSource().getType());
2739
2740 Value scalar = bcastOp.getSource();
2741 results.assign(resultVecType.getNumElements(), scalar);
2742 return success();
2743}
2744
2745LogicalResult ToElementsOp::fold(FoldAdaptor adaptor,
2746 SmallVectorImpl<OpFoldResult> &results) {
2747 if (succeeded(foldToElementsFromElements(*this, results)))
2748 return success();
2749
2750 // Y = ToElements(ShapeCast(X)) -> Y = ToElements(X)
2751 if (auto shapeCast = getSource().getDefiningOp<ShapeCastOp>()) {
2752 setOperand(shapeCast.getSource());
2753 return success();
2754 }
2755
2756 return foldToElementsOfBroadcast(*this, results);
2757}
2758
2759LogicalResult
2760ToElementsOp::inferReturnTypes(MLIRContext *ctx, std::optional<Location> loc,
2761 ToElementsOp::Adaptor adaptor,
2762 SmallVectorImpl<Type> &inferredReturnTypes) {
2763 auto vecType = cast<VectorType>(adaptor.getSource().getType());
2764 Type elType = vecType.getElementType();
2765 inferredReturnTypes.append(vecType.getNumElements(), elType);
2766 return success();
2767}
2768
2769/// Canonicalize `vector.to_elements(vector.broadcast(%v))` where `%v` is a
2770/// vector.
2771/// - Build `vector.to_elements %v` and remap each destination element to the
2772/// corresponding source element using broadcast rules (match or 1 →
2773/// replicate).
2774///
2775/// Example:
2776/// %v = vector.broadcast %src : vector<2xf32> to vector<3x2xf32>
2777/// %e:6 = vector.to_elements %v : vector<3x2xf32>
2778/// becomes:
2779/// %src_elems:2 = vector.to_elements %src : vector<2xf32>
2780/// // uses: %src_elems#0, %src_elems#1, %src_elems#0,
2781/// // %src_elems#1, %src_elems#0, %src_elems#1
2782struct ToElementsOfBroadcast final : OpRewritePattern<ToElementsOp> {
2783 using Base::Base;
2784
2785 LogicalResult matchAndRewrite(ToElementsOp toElementsOp,
2786 PatternRewriter &rewriter) const override {
2787 auto bcastOp = toElementsOp.getSource().getDefiningOp<BroadcastOp>();
2788 if (!bcastOp)
2789 return failure();
2790
2791 // Only handle broadcasts from a vector source here.
2792 auto srcType = dyn_cast<VectorType>(bcastOp.getSource().getType());
2793 if (!srcType)
2794 return failure();
2795
2796 auto dstType = cast<VectorType>(toElementsOp.getSource().getType());
2797
2798 ArrayRef<int64_t> dstShape = dstType.getShape();
2799 ArrayRef<int64_t> srcShape = srcType.getShape();
2800
2801 int64_t dstRank = dstShape.size();
2802 int64_t srcRank = srcShape.size();
2803
2804 // Create elements for the broadcast source vector.
2805 auto srcElems = vector::ToElementsOp::create(
2806 rewriter, toElementsOp.getLoc(), bcastOp.getSource());
2807
2808 int64_t dstCount = llvm::product_of(dstShape);
2809
2810 SmallVector<Value> replacements;
2811 replacements.reserve(dstCount);
2812
2813 // For each element of the destination, determine which element of the
2814 // source should be used. We walk all destination positions using a single
2815 // counter, decode it into per-dimension indices, then build the matching
2816 // source position: use the same index where sizes match, and use 0 where
2817 // the source size is 1 (replication). This mapping is needed so we can
2818 // replace each result of to_elements with the corresponding element from
2819 // the broadcast source.
2820 // Inner-dimension stretch example:
2821 // %v = vector.broadcast %src : vector<2x1x2xf32> to vector<2x3x2xf32>
2822 // %e:12 = vector.to_elements %v : vector<2x3x2xf32>
2823 // becomes:
2824 // %src_elems:4 = vector.to_elements %src : vector<2x1x2xf32>
2825 // // uses: %src_elems#0, %src_elems#1, %src_elems#0,
2826 // // %src_elems#1, %src_elems#0, %src_elems#1,
2827 // // %src_elems#2, %src_elems#3, %src_elems#2,
2828 // // %src_elems#3, %src_elems#2, %src_elems#3
2829
2830 // Row-major strides for the destination shape.
2831 SmallVector<int64_t> dstStrides = computeStrides(dstShape);
2832 // Row-major strides for the source shape.
2833 SmallVector<int64_t> srcStrides = computeStrides(srcShape);
2834 SmallVector<int64_t> dstIdx(dstRank);
2835 SmallVector<int64_t> srcIdx(srcRank);
2836 for (int64_t lin = 0; lin < dstCount; ++lin) {
2837 // Convert linear destination index to per-dimension indices.
2838 dstIdx = delinearize(lin, dstStrides);
2839 for (int64_t k = 0; k < srcRank; ++k)
2840 srcIdx[k] = (srcShape[k] == 1) ? 0 : dstIdx[dstRank - srcRank + k];
2841 // Convert per-dimension source indices back to a linear index.
2842 int64_t srcLin = linearize(srcIdx, srcStrides);
2843 replacements.push_back(srcElems.getResult(srcLin));
2844 }
2845
2846 rewriter.replaceOp(toElementsOp, replacements);
2847 return success();
2848 }
2849};
2850
2851void ToElementsOp::getCanonicalizationPatterns(RewritePatternSet &results,
2852 MLIRContext *context) {
2853 results.add<ToElementsOfBroadcast>(context);
2854}
2855
2856//===----------------------------------------------------------------------===//
2857// FromElementsOp
2858//===----------------------------------------------------------------------===//
2859
2860/// Folds vector.from_elements(vector.to_elements(%vector)) into %vector.
2861///
2862/// Case #1: Input and output vectors are the same.
2863///
2864/// %0:3 = vector.to_elements %a : vector<3xf32>
2865/// %1 = vector.from_elements %0#0, %0#1, %0#2 : vector<3xf32>
2866/// user_op %1
2867///
2868/// becomes:
2869///
2870/// user_op %a
2871///
2872static OpFoldResult foldFromElementsToElements(FromElementsOp fromElementsOp) {
2873 OperandRange fromElemsOperands = fromElementsOp.getElements();
2874 if (fromElemsOperands.empty())
2875 return {};
2876
2877 auto toElementsOp = fromElemsOperands[0].getDefiningOp<ToElementsOp>();
2878 if (!toElementsOp)
2879 return {};
2880
2881 if (!haveSameDefiningOp(fromElemsOperands, toElementsOp))
2882 return {};
2883
2884 // Case #1: Input and output vectors are the same. Forward the input vector.
2885 Value toElementsInput = toElementsOp.getSource();
2886 if (fromElementsOp.getType() == toElementsInput.getType() &&
2887 llvm::equal(fromElemsOperands, toElementsOp.getResults())) {
2888 return toElementsInput;
2889 }
2890
2891 // TODO: Support cases with different input and output shapes and different
2892 // number of elements.
2893
2894 return {};
2895}
2896
2897/// Fold vector.from_elements to a constant when all operands are constants.
2898/// Example:
2899/// %c1 = arith.constant 1 : i32
2900/// %c2 = arith.constant 2 : i32
2901/// %v = vector.from_elements %c1, %c2 : vector<2xi32>
2902/// =>
2903/// %v = arith.constant dense<[1, 2]> : vector<2xi32>
2904///
2905static OpFoldResult foldFromElementsToConstant(FromElementsOp fromElementsOp,
2906 ArrayRef<Attribute> elements) {
2907 // Check for null or poison attributes before any processing.
2908 if (llvm::any_of(elements, [](Attribute attr) {
2909 return !attr || matchPattern(attr, ub::m_Poison());
2910 }))
2911 return {};
2912
2913 // DenseElementsAttr only supports int/index/float/complex types.
2914 auto destVecType = fromElementsOp.getDest().getType();
2915 auto destEltType = destVecType.getElementType();
2916 if (!destEltType.isIntOrIndexOrFloat() && !isa<ComplexType>(destEltType))
2917 return {};
2918
2919 // Constant attributes might have a different type than the return type.
2920 // Convert them before creating the dense elements attribute.
2921 auto convertedElements = llvm::map_to_vector(elements, [&](Attribute attr) {
2922 return convertNumericAttr(attr, destEltType);
2923 });
2924
2925 return DenseElementsAttr::get(destVecType, convertedElements);
2926}
2927
2928OpFoldResult FromElementsOp::fold(FoldAdaptor adaptor) {
2929 if (auto res = foldFromElementsToElements(*this))
2930 return res;
2931 if (auto res = foldFromElementsToConstant(*this, adaptor.getElements()))
2932 return res;
2933
2934 return {};
2935}
2936
2937/// Rewrite vector.from_elements as vector.broadcast if the elements are the
2938/// same. Example:
2939/// %0 = vector.from_elements %a, %a, %a : vector<3xf32>
2940/// =>
2941/// %0 = vector.broadcast %a : f32 to vector<3xf32>
2942static LogicalResult
2943rewriteFromElementsAsBroadcast(FromElementsOp fromElementsOp,
2944 PatternRewriter &rewriter) {
2945 if (!llvm::all_equal(fromElementsOp.getElements()))
2946 return failure();
2947 rewriter.replaceOpWithNewOp<BroadcastOp>(
2948 fromElementsOp, fromElementsOp.getType(),
2949 fromElementsOp.getElements().front());
2950 return success();
2951}
2952
2953/// Rewrite from_elements on multiple scalar extracts as a shape_cast
2954/// on a single extract. Example:
2955/// %0 = vector.extract %source[0, 0] : i8 from vector<2x2xi8>
2956/// %1 = vector.extract %source[0, 1] : i8 from vector<2x2xi8>
2957/// %2 = vector.from_elements %0, %1 : vector<2xi8>
2958///
2959/// becomes
2960/// %1 = vector.extract %source[0] : vector<1x2xi8> from vector<2x2xi8>
2961/// %2 = vector.shape_cast %1 : vector<1x2xi8> to vector<2xi8>
2962///
2963/// The requirements for this to be valid are
2964///
2965/// i) The elements are extracted from the same vector (%source).
2966///
2967/// ii) The elements form a suffix of %source. Specifically, the number
2968/// of elements is the same as the product of the last N dimension sizes
2969/// of %source, for some N.
2970///
2971/// iii) The elements are extracted contiguously in ascending order.
2972
2973class FromElementsToShapeCast : public OpRewritePattern<FromElementsOp> {
2974
2975 using Base::Base;
2976
2977 LogicalResult matchAndRewrite(FromElementsOp fromElements,
2978 PatternRewriter &rewriter) const override {
2979
2980 // Handled by `rewriteFromElementsAsBroadcast`.
2981 if (fromElements.getType().getNumElements() == 1)
2982 return failure();
2983
2984 // The common source that all elements are extracted from, if one exists.
2986 // The position of the combined extract operation, if one is created.
2987 ArrayRef<int64_t> combinedPosition;
2988 // The expected index of extraction of the current element in the loop, if
2989 // elements are extracted contiguously in ascending order.
2990 SmallVector<int64_t> expectedPosition;
2991
2992 for (auto [insertIndex, element] :
2993 llvm::enumerate(fromElements.getElements())) {
2994
2995 // Check that the element is from a vector.extract operation.
2996 auto extractOp = element.getDefiningOp<vector::ExtractOp>();
2997 if (!extractOp) {
2998 return rewriter.notifyMatchFailure(fromElements,
2999 "element not from vector.extract");
3000 }
3001
3002 // Check condition (i) by checking that all elements have the same source
3003 // as the first element.
3004 if (insertIndex == 0) {
3005 source = extractOp.getSource();
3006 } else if (extractOp.getSource() != source) {
3007 return rewriter.notifyMatchFailure(fromElements,
3008 "element from different vector");
3009 }
3010
3011 ArrayRef<int64_t> position = extractOp.getStaticPosition();
3012 int64_t rank = position.size();
3013 assert(rank == source.getType().getRank() &&
3014 "scalar extract must have full rank position");
3015
3016 // Check condition (ii) by checking that the position that the first
3017 // element is extracted from has sufficient trailing 0s. For example, in
3018 //
3019 // %elm0 = vector.extract %source[1, 0, 0] : i8 from vector<2x3x4xi8>
3020 // [...]
3021 // %elms = vector.from_elements %elm0, [...] : vector<12xi8>
3022 //
3023 // The 2 trailing 0s in the position of extraction of %elm0 cover 3*4 = 12
3024 // elements, which is the number of elements of %n, so this is valid.
3025 if (insertIndex == 0) {
3026 const int64_t numElms = fromElements.getType().getNumElements();
3027 int64_t numSuffixElms = 1;
3028 int64_t index = rank;
3029 while (index > 0 && position[index - 1] == 0 &&
3030 numSuffixElms < numElms) {
3031 numSuffixElms *= source.getType().getDimSize(index - 1);
3032 --index;
3033 }
3034 if (numSuffixElms != numElms) {
3035 return rewriter.notifyMatchFailure(
3036 fromElements, "elements do not form a suffix of source");
3037 }
3038 expectedPosition = llvm::to_vector(position);
3039 combinedPosition = position.drop_back(rank - index);
3040 }
3041
3042 // Check condition (iii).
3043 else if (expectedPosition != position) {
3044 return rewriter.notifyMatchFailure(
3045 fromElements, "elements not in ascending order (static order)");
3046 }
3047 increment(expectedPosition, source.getType().getShape());
3048 }
3049
3050 auto extracted = rewriter.createOrFold<vector::ExtractOp>(
3051 fromElements.getLoc(), source, combinedPosition);
3052
3053 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(
3054 fromElements, fromElements.getType(), extracted);
3055
3056 return success();
3057 }
3058
3059 /// Increments n-D `indices` by 1 starting from the innermost dimension.
3060 static void increment(MutableArrayRef<int64_t> indices,
3062 for (int dim : llvm::reverse(llvm::seq<int>(0, indices.size()))) {
3063 indices[dim] += 1;
3064 if (indices[dim] < shape[dim])
3065 break;
3066 indices[dim] = 0;
3067 }
3068 }
3069};
3070
3071void FromElementsOp::getCanonicalizationPatterns(RewritePatternSet &results,
3072 MLIRContext *context) {
3074 results.add<FromElementsToShapeCast>(context);
3075}
3076
3077//===----------------------------------------------------------------------===//
3078// BroadcastOp
3079//===----------------------------------------------------------------------===//
3080
3081void BroadcastOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
3082 SetIntRangeFn setResultRanges) {
3083 setResultRanges(getResult(), argRanges.front());
3084}
3085
3086std::optional<SmallVector<int64_t, 4>>
3087BroadcastOp::getIterationShapeToUnroll() {
3088 return llvm::to_vector<4>(getResultVectorType().getShape());
3089}
3090
3091/// Return the dimensions of the result vector that were formerly ones in the
3092/// source tensor and thus correspond to "dim-1" broadcasting.
3093static llvm::SetVector<int64_t>
3095 ArrayRef<int64_t> dstShape) {
3096 int64_t rankDiff = dstShape.size() - srcShape.size();
3097 int64_t dstDim = rankDiff;
3099 for (auto [s1, s2] :
3100 llvm::zip_equal(srcShape, dstShape.drop_front(rankDiff))) {
3101 if (s1 != s2) {
3102 assert(s1 == 1 && "expected \"dim-1\" broadcasting");
3103 res.insert(dstDim);
3104 }
3105 ++dstDim;
3106 }
3107 return res;
3108}
3109
3110llvm::SetVector<int64_t> BroadcastOp::computeBroadcastedUnitDims() {
3111 // Scalar broadcast is without any unit dim broadcast.
3112 auto srcVectorType = llvm::dyn_cast<VectorType>(getSourceType());
3113 if (!srcVectorType)
3114 return {};
3115 return ::computeBroadcastedUnitDims(srcVectorType.getShape(),
3116 getResultVectorType().getShape());
3117}
3118
3119/// Broadcast `value` to a vector of `dstShape`, knowing that exactly the
3120/// `broadcastedDims` dimensions in the dstShape are broadcasted.
3121/// This requires (and asserts) that the broadcast is free of "dim-1"
3122/// broadcasting.
3123/// Since vector.broadcast only allows expanding leading dimensions, an extra
3124/// vector.transpose may be inserted to make the broadcast possible.
3125/// `value`, `dstShape` and `broadcastedDims` must be properly specified or
3126/// the helper will assert. This means:
3127/// 1. `dstShape` must not be empty.
3128/// 2. `broadcastedDims` must be confined to [0 .. rank(value.getVectorType)]
3129/// 2. `dstShape` trimmed of the dimensions specified in `broadcastedDims`
3130// must match the `value` shape.
3131Value BroadcastOp::createOrFoldBroadcastOp(
3132 OpBuilder &b, Value value, ArrayRef<int64_t> dstShape,
3133 const llvm::SetVector<int64_t> &broadcastedDims) {
3134 assert(!dstShape.empty() && "unexpected empty dst shape");
3135
3136 // Well-formedness check.
3137 SmallVector<int64_t> checkShape;
3138 for (int i = 0, e = dstShape.size(); i < e; ++i) {
3139 if (broadcastedDims.contains(i))
3140 continue;
3141 checkShape.push_back(dstShape[i]);
3142 }
3143 assert(broadcastedDims.size() == dstShape.size() - checkShape.size() &&
3144 "ill-formed broadcastedDims contains values not confined to "
3145 "destVectorShape");
3146
3147 Location loc = value.getLoc();
3148 Type elementType = getElementTypeOrSelf(value.getType());
3149 VectorType srcVectorType = llvm::dyn_cast<VectorType>(value.getType());
3150 VectorType dstVectorType = VectorType::get(dstShape, elementType);
3151
3152 // Step 2. If scalar -> dstShape broadcast, just do it.
3153 if (!srcVectorType) {
3154 assert(checkShape.empty() &&
3155 "ill-formed createOrFoldBroadcastOp arguments");
3156 return b.createOrFold<vector::BroadcastOp>(loc, dstVectorType, value);
3157 }
3158
3159 assert(srcVectorType.getShape().equals(checkShape) &&
3160 "ill-formed createOrFoldBroadcastOp arguments");
3161
3162 // Step 3. Since vector.broadcast only allows creating leading dims,
3163 // vector -> dstShape broadcast may require a transpose.
3164 // Traverse the dims in order and construct:
3165 // 1. The leading entries of the broadcastShape that is guaranteed to be
3166 // achievable by a simple broadcast.
3167 // 2. The induced permutation for the subsequent vector.transpose that will
3168 // bring us from `broadcastShape` back to he desired `dstShape`.
3169 // If the induced permutation is not the identity, create a vector.transpose.
3170 SmallVector<int64_t> broadcastShape, permutation(dstShape.size(), -1);
3171 broadcastShape.reserve(dstShape.size());
3172 // Consider the example:
3173 // srcShape = 2x4
3174 // dstShape = 1x2x3x4x5
3175 // broadcastedDims = [0, 2, 4]
3176 //
3177 // We want to build:
3178 // broadcastShape = 1x3x5x2x4
3179 // permutation = [0, 2, 4, 1, 3]
3180 // ---V--- -----V-----
3181 // leading broadcast part src shape part
3182 //
3183 // Note that the trailing dims of broadcastShape are exactly the srcShape
3184 // by construction.
3185 // nextSrcShapeDim is used to keep track of where in the permutation the
3186 // "src shape part" occurs.
3187 int64_t nextSrcShapeDim = broadcastedDims.size();
3188 for (int64_t i = 0, e = dstShape.size(); i < e; ++i) {
3189 if (broadcastedDims.contains(i)) {
3190 // 3.a. For each dim in the dst shape, if it is a broadcasted dim,
3191 // bring it to the head of the broadcastShape.
3192 // It will need to be permuted back from `broadcastShape.size() - 1` into
3193 // position `i`.
3194 broadcastShape.push_back(dstShape[i]);
3195 permutation[i] = broadcastShape.size() - 1;
3196 } else {
3197 // 3.b. Otherwise, the dim is not broadcasted, it comes from the src
3198 // shape and needs to be permuted into position `i`.
3199 // Don't touch `broadcastShape` here, the whole srcShape will be
3200 // appended after.
3201 permutation[i] = nextSrcShapeDim++;
3202 }
3203 }
3204 // 3.c. Append the srcShape.
3205 llvm::append_range(broadcastShape, srcVectorType.getShape());
3206
3207 // Ensure there are no "dim-1" broadcasts.
3208 assert(::computeBroadcastedUnitDims(srcVectorType.getShape(), broadcastShape)
3209 .empty() &&
3210 "unexpected \"dim-1\" broadcast");
3211
3212 VectorType broadcastType = VectorType::get(broadcastShape, elementType);
3213 assert(vector::isBroadcastableTo(value.getType(), broadcastType) ==
3214 vector::BroadcastableToResult::Success &&
3215 "must be broadcastable");
3216 Value res = b.createOrFold<vector::BroadcastOp>(loc, broadcastType, value);
3217 // Step 4. If we find any dimension that indeed needs to be permuted,
3218 // immediately return a new vector.transpose.
3219 for (int64_t i = 0, e = permutation.size(); i < e; ++i)
3220 if (permutation[i] != i)
3221 return b.createOrFold<vector::TransposeOp>(loc, res, permutation);
3222 // Otherwise return res.
3223 return res;
3224}
3225
3226LogicalResult BroadcastOp::verify() {
3227 std::pair<VectorDim, VectorDim> mismatchingDims;
3229 getSourceType(), getResultVectorType(), &mismatchingDims);
3230 if (res == BroadcastableToResult::Success)
3231 return success();
3232 if (res == BroadcastableToResult::SourceRankHigher)
3233 return emitOpError("source rank higher than destination rank");
3234 if (res == BroadcastableToResult::DimensionMismatch) {
3235 return emitOpError("dimension mismatch (")
3236 << (mismatchingDims.first.isScalable ? "[" : "")
3237 << mismatchingDims.first.dim
3238 << (mismatchingDims.first.isScalable ? "]" : "") << " vs. "
3239 << (mismatchingDims.second.isScalable ? "[" : "")
3240 << mismatchingDims.second.dim
3241 << (mismatchingDims.second.isScalable ? "]" : "") << ")";
3242 }
3243 if (res == BroadcastableToResult::SourceTypeNotAVector)
3244 return emitOpError("source type is not a vector");
3245 llvm_unreachable("unexpected vector.broadcast op error");
3246}
3247
3248// Fold broadcast(shape_cast(x)) into broadcast(x) if x's type is compatible
3249// with broadcast's result type and shape_cast only adds or removes ones in the
3250// leading dimensions.
3251static LogicalResult foldBroadcastOfShapeCast(BroadcastOp broadcastOp) {
3252 auto srcShapeCast = broadcastOp.getSource().getDefiningOp<ShapeCastOp>();
3253 if (!srcShapeCast)
3254 return failure();
3255
3256 VectorType srcType = srcShapeCast.getSourceVectorType();
3257 VectorType destType = broadcastOp.getResultVectorType();
3258 // Check type compatibility.
3259 if (vector::isBroadcastableTo(srcType, destType) !=
3261 return failure();
3262
3263 ArrayRef<int64_t> srcShape = srcType.getShape();
3264 ArrayRef<int64_t> shapecastShape =
3265 srcShapeCast.getResultVectorType().getShape();
3266 // Trailing dimensions should be the same if shape_cast only alters the
3267 // leading dimensions.
3268 unsigned numTrailingDims = std::min(srcShape.size(), shapecastShape.size());
3269 if (!llvm::equal(srcShape.take_back(numTrailingDims),
3270 shapecastShape.take_back(numTrailingDims)))
3271 return failure();
3272
3273 assert(all_of(srcShape.drop_back(numTrailingDims),
3274 [](int64_t E) { return E == 1; }) &&
3275 all_of(shapecastShape.drop_back(numTrailingDims),
3276 [](int64_t E) { return E == 1; }) &&
3277 "ill-formed shape_cast");
3278
3279 broadcastOp.getSourceMutable().assign(srcShapeCast.getSource());
3280 return success();
3281}
3282
3283OpFoldResult BroadcastOp::fold(FoldAdaptor adaptor) {
3284 if (getSourceType() == getResultVectorType())
3285 return getSource();
3286 if (succeeded(foldBroadcastOfShapeCast(*this)))
3287 return getResult();
3288
3289 if (!adaptor.getSource())
3290 return {};
3291 auto vectorType = getResultVectorType();
3292 if (auto attr = llvm::dyn_cast<IntegerAttr>(adaptor.getSource())) {
3293 if (vectorType.getElementType() != attr.getType())
3294 return {};
3295 return DenseElementsAttr::get(vectorType, attr);
3296 }
3297 if (auto attr = llvm::dyn_cast<FloatAttr>(adaptor.getSource())) {
3298 if (vectorType.getElementType() != attr.getType())
3299 return {};
3300 return DenseElementsAttr::get(vectorType, attr);
3301 }
3302 if (auto attr = llvm::dyn_cast<SplatElementsAttr>(adaptor.getSource()))
3303 return DenseElementsAttr::get(vectorType, attr.getSplatValue<Attribute>());
3304 if (matchPattern(adaptor.getSource(), ub::m_Poison()))
3305 return ub::PoisonAttr::get(getContext());
3306 return {};
3307}
3308
3309namespace {
3310
3311// Fold broadcast1(broadcast2(x)) into broadcast1(x).
3312struct BroadcastFolder : public OpRewritePattern<BroadcastOp> {
3313 using Base::Base;
3314
3315 LogicalResult matchAndRewrite(BroadcastOp broadcastOp,
3316 PatternRewriter &rewriter) const override {
3317 auto srcBroadcast = broadcastOp.getSource().getDefiningOp<BroadcastOp>();
3318 if (!srcBroadcast)
3319 return failure();
3320 rewriter.replaceOpWithNewOp<BroadcastOp>(broadcastOp,
3321 broadcastOp.getResultVectorType(),
3322 srcBroadcast.getSource());
3323 return success();
3324 }
3325};
3326
3327/// Replace `vector.broadcast` with `vector.shape_cast`.
3328///
3329/// BEFORE:
3330/// %0 = vector.broadcast %arg0 : vector<4xi8> to vector<1x1x4xi8>
3331/// AFTER:
3332/// %0 = vector.shape_cast %arg0 : vector<4xi8> to vector<1x1x4xi8>
3333///
3334/// The canonical form of vector operations that reshape vectors is shape_cast.
3335struct BroadcastToShapeCast final
3336 : public OpRewritePattern<vector::BroadcastOp> {
3337 using Base::Base;
3338 LogicalResult matchAndRewrite(vector::BroadcastOp broadcast,
3339 PatternRewriter &rewriter) const override {
3340
3341 auto sourceType = dyn_cast<VectorType>(broadcast.getSourceType());
3342 if (!sourceType) {
3343 return rewriter.notifyMatchFailure(
3344 broadcast, "source is a scalar, shape_cast doesn't support scalar");
3345 }
3346
3347 VectorType outType = broadcast.getType();
3348 if (sourceType.getNumElements() != outType.getNumElements()) {
3349 return rewriter.notifyMatchFailure(
3350 broadcast, "broadcast to a greater number of elements");
3351 }
3352
3353 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(broadcast, outType,
3354 broadcast.getSource());
3355 return success();
3356 }
3357};
3358} // namespace
3359
3360void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
3361 MLIRContext *context) {
3362 results.add<BroadcastFolder, BroadcastToShapeCast>(context);
3363}
3364
3365//===----------------------------------------------------------------------===//
3366// ShuffleOp
3367//===----------------------------------------------------------------------===//
3368
3369LogicalResult ShuffleOp::verify() {
3370 VectorType resultType = getResultVectorType();
3371 VectorType v1Type = getV1VectorType();
3372 VectorType v2Type = getV2VectorType();
3373 // Verify ranks.
3374 int64_t resRank = resultType.getRank();
3375 int64_t v1Rank = v1Type.getRank();
3376 int64_t v2Rank = v2Type.getRank();
3377 bool wellFormed0DCase = v1Rank == 0 && v2Rank == 0 && resRank == 1;
3378 bool wellFormedNDCase = v1Rank == resRank && v2Rank == resRank;
3379 if (!wellFormed0DCase && !wellFormedNDCase)
3380 return emitOpError("rank mismatch");
3381
3382 // Verify all but leading dimension sizes.
3383 for (int64_t r = 1; r < v1Rank; ++r) {
3384 int64_t resDim = resultType.getDimSize(r);
3385 int64_t v1Dim = v1Type.getDimSize(r);
3386 int64_t v2Dim = v2Type.getDimSize(r);
3387 if (resDim != v1Dim || v1Dim != v2Dim)
3388 return emitOpError("dimension mismatch");
3389 }
3390 // Verify mask length.
3391 ArrayRef<int64_t> mask = getMask();
3392 int64_t maskLength = mask.size();
3393 if (maskLength <= 0)
3394 return emitOpError("invalid mask length");
3395 if (maskLength != resultType.getDimSize(0))
3396 return emitOpError("mask length mismatch");
3397 // Verify all indices.
3398 int64_t indexSize = (v1Type.getRank() == 0 ? 1 : v1Type.getDimSize(0)) +
3399 (v2Type.getRank() == 0 ? 1 : v2Type.getDimSize(0));
3400 for (auto [idx, maskPos] : llvm::enumerate(mask)) {
3401 if (!isValidPositiveIndexOrPoison(maskPos, kPoisonIndex, indexSize))
3402 return emitOpError("mask index #") << (idx + 1) << " out of range";
3403 }
3404 return success();
3405}
3406
3407LogicalResult
3408ShuffleOp::inferReturnTypes(MLIRContext *, std::optional<Location> loc,
3409 ShuffleOp::Adaptor adaptor,
3410 SmallVectorImpl<Type> &inferredReturnTypes) {
3411 auto v1Type = llvm::dyn_cast<VectorType>(adaptor.getV1().getType());
3412 if (!v1Type) {
3413 return emitOptionalError(loc, "expected vector type");
3414 }
3415 auto v1Rank = v1Type.getRank();
3416 // Construct resulting type: leading dimension matches mask
3417 // length, all trailing dimensions match the operands.
3418 SmallVector<int64_t, 4> shape;
3419 shape.reserve(v1Rank);
3420 shape.push_back(std::max<size_t>(1, adaptor.getMask().size()));
3421 // In the 0-D case there is no trailing shape to append.
3422 if (v1Rank > 0)
3423 llvm::append_range(shape, v1Type.getShape().drop_front());
3424 inferredReturnTypes.push_back(
3425 VectorType::get(shape, v1Type.getElementType()));
3426 return success();
3427}
3428
3429template <typename T>
3430static bool isStepIndexArray(ArrayRef<T> idxArr, uint64_t begin, size_t width) {
3431 T expected = begin;
3432 return idxArr.size() == width && llvm::all_of(idxArr, [&expected](T value) {
3433 return value == expected++;
3434 });
3435}
3436
3437/// Fold shuffle V1, V2, [0, 1, 2, 3] : <4xi32>, <2xi32> -> V1.
3438/// Fold shuffle V1, V2, [4, 5] : <4xi32>, <2xi32> -> V2.
3440 auto v1Type = op.getV1VectorType();
3441 auto v2Type = op.getV2VectorType();
3442 auto mask = op.getMask();
3443 if (isStepIndexArray(mask, 0, v1Type.getDimSize(0)))
3444 return op.getV1();
3445 if (isStepIndexArray(mask, v1Type.getDimSize(0), v2Type.getDimSize(0)))
3446 return op.getV2();
3447 return {};
3448}
3449
3450/// If a shuffle operand is poison, replace all mask indices that reference it
3451/// with kPoisonIndex. This is an in-place fold.
3453 bool isV1Poison = matchPattern(op.getV1(), ub::m_Poison());
3454 bool isV2Poison = matchPattern(op.getV2(), ub::m_Poison());
3455 if (!isV1Poison && !isV2Poison)
3456 return {};
3457
3458 int64_t v1Size = op.getV1VectorType().getDimSize(0);
3459 bool changed = false;
3460 SmallVector<int64_t> newMask = llvm::to_vector(op.getMask());
3461 for (int64_t &idx : newMask) {
3462 if (idx == ShuffleOp::kPoisonIndex)
3463 continue;
3464 if ((isV1Poison && idx < v1Size) || (isV2Poison && idx >= v1Size)) {
3465 idx = ShuffleOp::kPoisonIndex;
3466 changed = true;
3467 }
3468 }
3469
3470 if (!changed)
3471 return {};
3472
3473 op.setMask(newMask);
3474 return op.getResult();
3475}
3476
3477/// Fold shuffle poison, poison -> poison.
3479 Attribute v1Attr,
3480 Attribute v2Attr) {
3481 if (matchPattern(v1Attr, ub::m_Poison()) &&
3482 matchPattern(v2Attr, ub::m_Poison()))
3483 return ub::PoisonAttr::get(context);
3484 return {};
3485}
3486
3487/// Fold a shuffle of constant 1-D inputs by evaluating the mask.
3489 Attribute v2Attr) {
3490 auto v1Type = op.getV1VectorType();
3491 if (v1Type.getRank() != 1)
3492 return {};
3493
3494 bool isV1Poison = matchPattern(v1Attr, ub::m_Poison());
3495 bool isV2Poison = matchPattern(v2Attr, ub::m_Poison());
3496
3497 // Poison input attributes need special handling as they are not
3498 // DenseElementsAttr. If an index is poison, we select the first element of
3499 // the first non-poison input.
3500 SmallVector<Attribute> v1Elements, v2Elements;
3501 Attribute poisonElement;
3502 if (!isV2Poison) {
3503 auto v2DenseAttr = dyn_cast<DenseElementsAttr>(v2Attr);
3504 if (!v2DenseAttr)
3505 return {};
3506 v2Elements = to_vector(v2DenseAttr.getValues<Attribute>());
3507 poisonElement = v2Elements[0];
3508 }
3509 if (!isV1Poison) {
3510 auto v1DenseAttr = dyn_cast<DenseElementsAttr>(v1Attr);
3511 if (!v1DenseAttr)
3512 return {};
3513 v1Elements = to_vector(v1DenseAttr.getValues<Attribute>());
3514 poisonElement = v1Elements[0];
3515 }
3516
3517 ArrayRef<int64_t> mask = op.getMask();
3518 SmallVector<Attribute> results;
3519 int64_t v1Size = v1Type.getDimSize(0);
3520 for (int64_t maskIdx : mask) {
3521 Attribute indexedElm;
3522 // TODO: Return a partial poison vector when supported by the UB dialect.
3523 if (maskIdx == ShuffleOp::kPoisonIndex) {
3524 indexedElm = poisonElement;
3525 } else {
3526 if (maskIdx < v1Size)
3527 indexedElm = isV1Poison ? poisonElement : v1Elements[maskIdx];
3528 else
3529 indexedElm = isV2Poison ? poisonElement : v2Elements[maskIdx - v1Size];
3530 }
3531
3532 results.push_back(indexedElm);
3533 }
3534
3535 return DenseElementsAttr::get(op.getResultVectorType(), results);
3536}
3537
3538OpFoldResult vector::ShuffleOp::fold(FoldAdaptor adaptor) {
3539 auto v1Type = getV1VectorType();
3540
3541 assert(!v1Type.isScalable() && !getV2VectorType().isScalable() &&
3542 "Vector shuffle does not support scalable vectors");
3543
3544 // For consistency: 0-D shuffle return type is 1-D, this cannot be a folding
3545 // but must be a canonicalization into a vector.broadcast.
3546 if (v1Type.getRank() == 0)
3547 return {};
3548
3549 if (auto res = foldShuffleIdentityMask(*this))
3550 return res;
3551 if (auto res = foldShufflePoisonOperandToMask(*this))
3552 return res;
3553
3554 Attribute v1Attr = adaptor.getV1(), v2Attr = adaptor.getV2();
3555 if (!v1Attr || !v2Attr)
3556 return {};
3557
3558 if (auto res = foldShufflePoisonInputs(getContext(), v1Attr, v2Attr))
3559 return res;
3560 if (auto res = foldShuffleConstantInputs(*this, v1Attr, v2Attr))
3561 return res;
3562
3563 return {};
3564}
3565
3566namespace {
3567
3568// Pattern to rewrite a 0-D shuffle with [0] or [1] mask returning a 1-D vector
3569// to a broadcast.
3570struct Canonicalize0DShuffleOp : public OpRewritePattern<ShuffleOp> {
3571 using Base::Base;
3572
3573 LogicalResult matchAndRewrite(ShuffleOp shuffleOp,
3574 PatternRewriter &rewriter) const override {
3575 VectorType v1VectorType = shuffleOp.getV1VectorType();
3576 ArrayRef<int64_t> mask = shuffleOp.getMask();
3577 if (v1VectorType.getRank() > 0)
3578 return failure();
3579 if (mask.size() != 1)
3580 return failure();
3581 VectorType resType = VectorType::Builder(v1VectorType).setShape({1});
3582 if (mask[0] == 0)
3583 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(shuffleOp, resType,
3584 shuffleOp.getV1());
3585 else
3586 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(shuffleOp, resType,
3587 shuffleOp.getV2());
3588 return success();
3589 }
3590};
3591
3592/// Consider the defining operation `defOp` of `value`. If `defOp` is a
3593/// vector.broadcast with a scalar operand, return the scalar value that is
3594/// splatted. Otherwise return null.
3595///
3596/// Example:
3597///
3598/// scalar_source --> vector.broadcast --> value - return scalar_source
3599static Value getScalarSplatSource(Value value) {
3600 // Block argument:
3601 Operation *defOp = value.getDefiningOp();
3602 if (!defOp)
3603 return {};
3604
3605 auto broadcast = dyn_cast<vector::BroadcastOp>(defOp);
3606
3607 // Not broadcast (and not splat):
3608 if (!broadcast)
3609 return {};
3610
3611 // Broadcast of a vector:
3612 if (isa<VectorType>(broadcast.getSourceType()))
3613 return {};
3614
3615 // Broadcast of a scalar:
3616 return broadcast.getSource();
3617}
3618
3619/// Pattern to rewrite shuffle(splat-like(v), splat-like(v)) as broadcast(v).
3620class ShuffleSplat final : public OpRewritePattern<ShuffleOp> {
3621public:
3622 using Base::Base;
3623
3624 LogicalResult matchAndRewrite(ShuffleOp op,
3625 PatternRewriter &rewriter) const override {
3626 Value splat = getScalarSplatSource(op.getV1());
3627 if (!splat || getScalarSplatSource(op.getV2()) != splat)
3628 return failure();
3629
3630 rewriter.replaceOpWithNewOp<BroadcastOp>(op, op.getType(), splat);
3631 return success();
3632 }
3633};
3634
3635/// Pattern to rewrite a fixed-size interleave via vector.shuffle to
3636/// vector.interleave.
3637class ShuffleInterleave : public OpRewritePattern<ShuffleOp> {
3638public:
3639 using Base::Base;
3640
3641 LogicalResult matchAndRewrite(ShuffleOp op,
3642 PatternRewriter &rewriter) const override {
3643 VectorType resultType = op.getResultVectorType();
3644 if (resultType.isScalable())
3645 return rewriter.notifyMatchFailure(
3646 op, "ShuffleOp can't represent a scalable interleave");
3647
3648 if (resultType.getRank() != 1)
3649 return rewriter.notifyMatchFailure(
3650 op, "ShuffleOp can't represent an n-D interleave");
3651
3652 VectorType sourceType = op.getV1VectorType();
3653 if (sourceType != op.getV2VectorType() ||
3654 sourceType.getNumElements() * 2 != resultType.getNumElements()) {
3655 return rewriter.notifyMatchFailure(
3656 op, "ShuffleOp types don't match an interleave");
3657 }
3658
3659 ArrayRef<int64_t> shuffleMask = op.getMask();
3660 int64_t resultVectorSize = resultType.getNumElements();
3661 for (int i = 0, e = resultVectorSize / 2; i < e; ++i) {
3662 int64_t maskValueA = shuffleMask[i * 2];
3663 int64_t maskValueB = shuffleMask[(i * 2) + 1];
3664 if (maskValueA != i || maskValueB != (resultVectorSize / 2) + i)
3665 return rewriter.notifyMatchFailure(op,
3666 "ShuffleOp mask not interleaving");
3667 }
3668
3669 rewriter.replaceOpWithNewOp<InterleaveOp>(op, op.getV1(), op.getV2());
3670 return success();
3671 }
3672};
3673
3674/// Pattern to replace usused shuffle operands / results with poison.
3675///
3676/// Example Input:
3677/// %r = vector.shuffle %v1, %v2 [2, 3, 3, 3] : vector<2xi32>, vector<2xi32>
3678///
3679/// Example Output:
3680/// %0 = ub.poison : vector<2xi32>
3681/// %r = vector.shuffle %0, %v2 [2, 3, 3, 3] : vector<2xi32>, vector<2xi32>
3682class FoldUnusedShuffleOperand final : public OpRewritePattern<ShuffleOp> {
3683public:
3684 using Base::Base;
3685
3686 LogicalResult matchAndRewrite(ShuffleOp op,
3687 PatternRewriter &rewriter) const override {
3688 // Replace with poison if all mask elements are poison.
3689 if (llvm::all_of(op.getMask(), [](int64_t mask) {
3690 return mask == ShuffleOp::kPoisonIndex;
3691 })) {
3692 rewriter.replaceOpWithNewOp<ub::PoisonOp>(op, op.getType());
3693 return success();
3694 }
3695
3696 // Helper function to replace an operand with poison.
3697 auto replaceOperandWithPoison = [&](OpOperand &operand) {
3698 // Do not replace if the operand is already poison.
3699 if (!matchPattern(operand.get(), ub::m_Poison())) {
3700 Value poison = ub::PoisonOp::create(rewriter, op.getLoc(),
3701 operand.get().getType());
3702 rewriter.modifyOpInPlace(op, [&]() { operand.set(poison); });
3703 return success();
3704 }
3705 return failure();
3706 };
3707
3708 // Replace V1 with poison if it is not used.
3709 int64_t leadingV1Size = op.getV1VectorType().getRank() > 0
3710 ? op.getV1VectorType().getDimSize(0)
3711 : 1;
3712 bool isV1Used = llvm::any_of(op.getMask(), [&](int64_t mask) {
3713 return mask != ShuffleOp::kPoisonIndex && mask < leadingV1Size;
3714 });
3715 if (!isV1Used && succeeded(replaceOperandWithPoison(op.getV1Mutable())))
3716 return success();
3717
3718 // Replace V2 with poison if it is not used.
3719 bool isV2Used = llvm::any_of(op.getMask(), [&](int64_t mask) {
3720 return mask != ShuffleOp::kPoisonIndex && mask >= leadingV1Size;
3721 });
3722 if (!isV2Used && succeeded(replaceOperandWithPoison(op.getV2Mutable())))
3723 return success();
3724
3725 return failure();
3726 }
3727};
3728} // namespace
3729
3730void ShuffleOp::getCanonicalizationPatterns(RewritePatternSet &results,
3731 MLIRContext *context) {
3732 results.add<ShuffleSplat, ShuffleInterleave, Canonicalize0DShuffleOp,
3733 FoldUnusedShuffleOperand>(context);
3734}
3735
3736//===----------------------------------------------------------------------===//
3737// InsertOp
3738//===----------------------------------------------------------------------===//
3739
3740void vector::InsertOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
3741 SetIntRangeFn setResultRanges) {
3742 setResultRanges(getResult(), argRanges[0].rangeUnion(argRanges[1]));
3743}
3744
3745void vector::InsertOp::build(OpBuilder &builder, OperationState &result,
3746 Value source, Value dest) {
3747 auto vectorTy = cast<VectorType>(dest.getType());
3748 build(builder, result, source, dest,
3749 SmallVector<int64_t>(vectorTy.getRank(), 0));
3750}
3751
3752void vector::InsertOp::build(OpBuilder &builder, OperationState &result,
3753 Value source, Value dest, int64_t position) {
3754 build(builder, result, source, dest, ArrayRef<int64_t>{position});
3755}
3756
3757void vector::InsertOp::build(OpBuilder &builder, OperationState &result,
3758 Value source, Value dest, OpFoldResult position) {
3759 build(builder, result, source, dest, ArrayRef<OpFoldResult>{position});
3760}
3761
3762void vector::InsertOp::build(OpBuilder &builder, OperationState &result,
3763 Value source, Value dest,
3764 ArrayRef<int64_t> position) {
3765 SmallVector<OpFoldResult> posVals;
3766 posVals.reserve(position.size());
3767 llvm::transform(position, std::back_inserter(posVals),
3768 [&](int64_t pos) { return builder.getI64IntegerAttr(pos); });
3769 build(builder, result, source, dest, posVals);
3770}
3771
3772void vector::InsertOp::build(OpBuilder &builder, OperationState &result,
3773 Value source, Value dest,
3774 ArrayRef<OpFoldResult> position) {
3775 SmallVector<int64_t> staticPos;
3776 SmallVector<Value> dynamicPos;
3777 dispatchIndexOpFoldResults(position, dynamicPos, staticPos);
3778 build(builder, result, source, dest, dynamicPos,
3779 builder.getDenseI64ArrayAttr(staticPos));
3780}
3781
3782LogicalResult InsertOp::verify() {
3783 if (auto srcTy = dyn_cast<VectorType>(getValueToStoreType()))
3784 if (srcTy.getRank() == 0)
3785 return emitError(
3786 "expected a scalar instead of a 0-d vector as the source operand");
3787
3788 SmallVector<OpFoldResult> position = getMixedPosition();
3789 auto destVectorType = getDestVectorType();
3790 if (position.size() > static_cast<unsigned>(destVectorType.getRank()))
3791 return emitOpError(
3792 "expected position attribute of rank no greater than dest vector rank");
3793 auto srcVectorType = llvm::dyn_cast<VectorType>(getValueToStoreType());
3794 if (srcVectorType &&
3795 (static_cast<unsigned>(srcVectorType.getRank()) + position.size() !=
3796 static_cast<unsigned>(destVectorType.getRank())))
3797 return emitOpError("expected position attribute rank + source rank to "
3798 "match dest vector rank");
3799 if (!srcVectorType &&
3800 (position.size() != static_cast<unsigned>(destVectorType.getRank())))
3801 return emitOpError(
3802 "expected position attribute rank to match the dest vector rank");
3803 for (auto [idx, pos] : llvm::enumerate(position)) {
3804 if (auto attr = dyn_cast<Attribute>(pos)) {
3805 int64_t constIdx = cast<IntegerAttr>(attr).getInt();
3806 if (!isValidPositiveIndexOrPoison(constIdx, kPoisonIndex,
3807 destVectorType.getDimSize(idx))) {
3808 return emitOpError("expected position attribute #")
3809 << (idx + 1)
3810 << " to be a non-negative integer smaller than the "
3811 "corresponding "
3812 "dest vector dimension";
3813 }
3814 }
3815 }
3816 return success();
3817}
3818
3819// Calculate the linearized position of the continuous chunk of elements to
3820// insert, based on the shape of the value to insert and the positions to insert
3821// at.
3822static int64_t calculateInsertPosition(VectorType destTy,
3823 ArrayRef<int64_t> positions) {
3824 llvm::SmallVector<int64_t> completePositions(destTy.getRank(), 0);
3825 assert(positions.size() <= completePositions.size() &&
3826 "positions size must be less than or equal to destTy rank");
3827 copy(positions, completePositions.begin());
3828 return linearize(completePositions, computeStrides(destTy.getShape()));
3829}
3830
3831namespace {
3832
3833// If insertOp is only inserting unit dimensions it can be transformed to a
3834// broadcast.
3835class InsertToBroadcast final : public OpRewritePattern<InsertOp> {
3836public:
3837 using Base::Base;
3838
3839 LogicalResult matchAndRewrite(InsertOp insertOp,
3840 PatternRewriter &rewriter) const override {
3841 auto srcVecType =
3842 llvm::dyn_cast<VectorType>(insertOp.getValueToStoreType());
3843 if (!srcVecType || insertOp.getDestVectorType().getNumElements() !=
3844 srcVecType.getNumElements())
3845 return failure();
3846 rewriter.replaceOpWithNewOp<BroadcastOp>(
3847 insertOp, insertOp.getDestVectorType(), insertOp.getValueToStore());
3848 return success();
3849 }
3850};
3851
3852/// Pattern to rewrite a insert(splat-like(v), splat-like(v)) as broadcast(v).
3853class InsertSplatToSplat final : public OpRewritePattern<InsertOp> {
3854public:
3855 using Base::Base;
3856
3857 LogicalResult matchAndRewrite(InsertOp op,
3858 PatternRewriter &rewriter) const override {
3859
3860 Value splat = getScalarSplatSource(op.getValueToStore());
3861 if (!splat || getScalarSplatSource(op.getDest()) != splat)
3862 return failure();
3863
3864 rewriter.replaceOpWithNewOp<BroadcastOp>(op, op.getType(), splat);
3865 return success();
3866 }
3867};
3868
3869/// Pattern to optimize a chain of insertions.
3870///
3871/// This pattern identifies chains of vector.insert operations that:
3872/// 1. Only insert values at static positions.
3873/// 2. Completely initialize all elements in the resulting vector.
3874/// 3. All intermediate insert operations have only one use.
3875///
3876/// When these conditions are met, the entire chain can be replaced with a
3877/// single vector.from_elements operation.
3878///
3879/// To keep this pattern simple, and avoid spending too much time on matching
3880/// fragmented insert chains, this pattern only considers the last insert op in
3881/// the chain.
3882///
3883/// Example transformation:
3884/// %poison = ub.poison : vector<2xi32>
3885/// %0 = vector.insert %c1, %poison[0] : i32 into vector<2xi32>
3886/// %1 = vector.insert %c2, %0[1] : i32 into vector<2xi32>
3887/// ->
3888/// %result = vector.from_elements %c1, %c2 : vector<2xi32>
3889class InsertChainFullyInitialized final : public OpRewritePattern<InsertOp> {
3890public:
3891 using Base::Base;
3892 LogicalResult matchAndRewrite(InsertOp op,
3893 PatternRewriter &rewriter) const override {
3894
3895 VectorType destTy = op.getDestVectorType();
3896 if (destTy.isScalable())
3897 return failure();
3898 // Ensure this is the trailing vector.insert op in a chain of inserts.
3899 for (Operation *user : op.getResult().getUsers())
3900 if (auto insertOp = dyn_cast<InsertOp>(user))
3901 if (insertOp.getDest() == op.getResult())
3902 return failure();
3903
3904 InsertOp currentOp = op;
3905 SmallVector<InsertOp> chainInsertOps;
3906 while (currentOp) {
3907 // Check cond 1: Dynamic position is not supported.
3908 if (currentOp.hasDynamicPosition())
3909 return failure();
3910
3911 chainInsertOps.push_back(currentOp);
3912 currentOp = currentOp.getDest().getDefiningOp<InsertOp>();
3913 // Check cond 3: Intermediate inserts have only one use to avoid an
3914 // explosion of vectors.
3915 if (currentOp && !currentOp->hasOneUse())
3916 return failure();
3917 }
3918
3919 int64_t vectorSize = destTy.getNumElements();
3920 int64_t initializedCount = 0;
3921 SmallVector<bool> initializedDestIdxs(vectorSize, false);
3922 SmallVector<int64_t> pendingInsertPos;
3923 SmallVector<int64_t> pendingInsertSize;
3924 SmallVector<Value> pendingInsertValues;
3925
3926 for (auto insertOp : chainInsertOps) {
3927 // This pattern can do nothing with poison index.
3928 if (is_contained(insertOp.getStaticPosition(), InsertOp::kPoisonIndex))
3929 return failure();
3930
3931 // Calculate the linearized position for inserting elements.
3932 int64_t insertBeginPosition =
3933 calculateInsertPosition(destTy, insertOp.getStaticPosition());
3934
3935 // The valueToStore operand may be a vector or a scalar. Need to handle
3936 // both cases.
3937 int64_t insertSize = 1;
3938 if (auto srcVectorType =
3939 llvm::dyn_cast<VectorType>(insertOp.getValueToStoreType()))
3940 insertSize = srcVectorType.getNumElements();
3941
3942 assert(insertBeginPosition + insertSize <= vectorSize &&
3943 "insert would overflow the vector");
3944
3945 for (auto index : llvm::seq<int64_t>(insertBeginPosition,
3946 insertBeginPosition + insertSize)) {
3947 if (initializedDestIdxs[index])
3948 continue;
3949 initializedDestIdxs[index] = true;
3950 ++initializedCount;
3951 }
3952
3953 // Defer the creation of ops before we can make sure the pattern can
3954 // succeed.
3955 pendingInsertPos.push_back(insertBeginPosition);
3956 pendingInsertSize.push_back(insertSize);
3957 pendingInsertValues.push_back(insertOp.getValueToStore());
3958
3959 if (initializedCount == vectorSize)
3960 break;
3961 }
3962
3963 // Check cond 2: all positions must be initialized.
3964 if (initializedCount != vectorSize)
3965 return failure();
3966
3967 SmallVector<Value> elements(vectorSize);
3968 for (auto [insertBeginPosition, insertSize, valueToStore] :
3969 llvm::reverse(llvm::zip(pendingInsertPos, pendingInsertSize,
3970 pendingInsertValues))) {
3971 auto srcVectorType = llvm::dyn_cast<VectorType>(valueToStore.getType());
3972
3973 if (!srcVectorType) {
3974 elements[insertBeginPosition] = valueToStore;
3975 continue;
3976 }
3977
3978 Repeated<Type> elementToInsertTypes(insertSize,
3979 srcVectorType.getElementType());
3980 // Get all elements from the vector in row-major order.
3981 auto elementsToInsert = vector::ToElementsOp::create(
3982 rewriter, op.getLoc(), elementToInsertTypes, valueToStore);
3983 for (int64_t linearIdx = 0; linearIdx < insertSize; linearIdx++) {
3984 elements[insertBeginPosition + linearIdx] =
3985 elementsToInsert.getResult(linearIdx);
3986 }
3987 }
3988
3989 rewriter.replaceOpWithNewOp<vector::FromElementsOp>(op, destTy, elements);
3990 return success();
3991 }
3992};
3993
3994} // namespace
3995
3996static Attribute
3998 Attribute dstAttr,
3999 int64_t maxVectorSizeFoldThreshold) {
4000 if (insertOp.hasDynamicPosition())
4001 return {};
4002
4003 auto denseDst = llvm::dyn_cast_if_present<DenseElementsAttr>(dstAttr);
4004 if (!denseDst)
4005 return {};
4006
4007 if (!srcAttr) {
4008 return {};
4009 }
4010
4011 VectorType destTy = insertOp.getDestVectorType();
4012 if (destTy.isScalable())
4013 return {};
4014
4015 // Make sure we do not create too many large constants.
4016 if (destTy.getNumElements() > maxVectorSizeFoldThreshold &&
4017 !insertOp->hasOneUse())
4018 return {};
4019
4020 // Bail out on poison indices (kPoisonIndex = -1) to avoid computing an
4021 // invalid (negative) linearized position which would cause UB below.
4022 if (is_contained(insertOp.getStaticPosition(), InsertOp::kPoisonIndex))
4023 return {};
4024
4025 // Calculate the linearized position for inserting elements.
4026 int64_t insertBeginPosition =
4027 calculateInsertPosition(destTy, insertOp.getStaticPosition());
4028 SmallVector<Attribute> insertedValues;
4029 Type destEltType = destTy.getElementType();
4030
4031 /// Converts attribute to the expected type if there's
4032 /// a mismatch.
4033 if (auto denseSource = llvm::dyn_cast<DenseElementsAttr>(srcAttr)) {
4034 for (auto value : denseSource.getValues<Attribute>())
4035 insertedValues.push_back(convertNumericAttr(value, destEltType));
4036 } else {
4037 insertedValues.push_back(convertNumericAttr(srcAttr, destEltType));
4038 }
4039
4040 auto allValues = llvm::to_vector(denseDst.getValues<Attribute>());
4041 copy(insertedValues, allValues.begin() + insertBeginPosition);
4042 auto newAttr = DenseElementsAttr::get(destTy, allValues);
4043
4044 return newAttr;
4045}
4046
4047/// Folder to replace the `dest` operand of the insert op with the root dest of
4048/// the insert op use chain.
4049static Value foldInsertUseChain(InsertOp insertOp) {
4050 auto destInsert = insertOp.getDest().getDefiningOp<InsertOp>();
4051 if (!destInsert)
4052 return {};
4053
4054 if (insertOp.getMixedPosition() != destInsert.getMixedPosition())
4055 return {};
4056
4057 insertOp.setOperand(1, destInsert.getDest());
4058 return insertOp.getResult();
4059}
4060
4061void InsertOp::getCanonicalizationPatterns(RewritePatternSet &results,
4062 MLIRContext *context) {
4063 results.add<InsertToBroadcast, BroadcastFolder, InsertSplatToSplat,
4064 InsertChainFullyInitialized>(context);
4065}
4066
4067OpFoldResult InsertOp::fold(FoldAdaptor adaptor) {
4068 // Do not create constants with more than `vectorSizeFoldThreashold` elements,
4069 // unless the source vector constant has a single use.
4070 constexpr int64_t vectorSizeFoldThreshold = 256;
4071 // Fold "vector.insert %v, %dest [] : vector<2x2xf32> from vector<2x2xf32>" to
4072 // %v. Note: Do not fold "vector.insert %v, %dest [] : f32 into vector<f32>"
4073 // (type mismatch).
4074 if (getNumIndices() == 0 && getValueToStoreType() == getType())
4075 return getValueToStore();
4076 // Fold `arith.constant` indices into the `vector.insert` operation.
4077 // Do not stop here as this fold may enable subsequent folds that require
4078 // constant indices.
4079 SmallVector<Value> operands = {getValueToStore(), getDest()};
4080 auto inplaceFolded = extractInsertFoldConstantOp(*this, adaptor, operands);
4081
4082 if (auto res = foldInsertUseChain(*this))
4083 return res;
4084 if (auto res = foldPoisonIndexInsertExtractOp(
4085 getContext(), adaptor.getStaticPosition(), kPoisonIndex))
4086 return res;
4087 if (auto res = foldDenseElementsAttrDestInsertOp(
4088 *this, adaptor.getValueToStore(), adaptor.getDest(),
4089 vectorSizeFoldThreshold)) {
4090 return res;
4091 }
4092
4093 return inplaceFolded;
4094}
4095
4096//===----------------------------------------------------------------------===//
4097// InsertStridedSliceOp
4098//===----------------------------------------------------------------------===//
4099
4100void InsertStridedSliceOp::build(OpBuilder &builder, OperationState &result,
4101 Value source, Value dest,
4102 ArrayRef<int64_t> offsets,
4103 ArrayRef<int64_t> strides) {
4104 result.addOperands({source, dest});
4105 auto offsetsAttr = getVectorSubscriptAttr(builder, offsets);
4106 auto stridesAttr = getVectorSubscriptAttr(builder, strides);
4107 result.addTypes(dest.getType());
4108 result.addAttribute(InsertStridedSliceOp::getOffsetsAttrName(result.name),
4109 offsetsAttr);
4110 result.addAttribute(InsertStridedSliceOp::getStridesAttrName(result.name),
4111 stridesAttr);
4112}
4113
4114// TODO: Should be moved to Tablegen ConfinedAttr attributes.
4115template <typename OpType>
4116static LogicalResult isIntegerArrayAttrSmallerThanShape(OpType op,
4117 ArrayAttr arrayAttr,
4119 StringRef attrName) {
4120 if (arrayAttr.size() > shape.size())
4121 return op.emitOpError("expected ")
4122 << attrName << " attribute of rank no greater than vector rank";
4123 return success();
4124}
4125
4126// Returns true if all integers in `arrayAttr` are in the half-open [min, max}
4127// interval. If `halfOpen` is true then the admissible interval is [min, max).
4128// Otherwise, the admissible interval is [min, max].
4129template <typename OpType>
4130static LogicalResult
4132 int64_t max, StringRef attrName,
4133 bool halfOpen = true) {
4134 for (auto attr : arrayAttr) {
4135 auto val = llvm::cast<IntegerAttr>(attr).getInt();
4136 auto upper = max;
4137 if (!halfOpen)
4138 upper += 1;
4139 if (val < min || val >= upper)
4140 return op.emitOpError("expected ") << attrName << " to be confined to ["
4141 << min << ", " << upper << ")";
4142 }
4143 return success();
4144}
4145
4146// Returns true if all integers in `arrayAttr` are in the half-open [min, max}
4147// interval. If `halfOpen` is true then the admissible interval is [min, max).
4148// Otherwise, the admissible interval is [min, max].
4149template <typename OpType>
4150static LogicalResult
4152 ArrayRef<int64_t> shape, StringRef attrName,
4153 bool halfOpen = true, int64_t min = 0) {
4154 for (auto [index, attrDimPair] :
4155 llvm::enumerate(llvm::zip_first(arrayAttr, shape))) {
4156 int64_t val = llvm::cast<IntegerAttr>(std::get<0>(attrDimPair)).getInt();
4157 int64_t max = std::get<1>(attrDimPair);
4158 if (!halfOpen)
4159 max += 1;
4160 if (val < min || val >= max)
4161 return op.emitOpError("expected ")
4162 << attrName << " dimension " << index << " to be confined to ["
4163 << min << ", " << max << ")";
4164 }
4165 return success();
4166}
4167
4168// Returns true if, for all indices i = 0..shape.size()-1, val is in the
4169// [min, max} interval:
4170// val = `arrayAttr1[i]` + `arrayAttr2[i]`,
4171// If `halfOpen` is true then the admissible interval is [min, max). Otherwise,
4172// the admissible interval is [min, max].
4173template <typename OpType>
4175 OpType op, ArrayAttr arrayAttr1, ArrayAttr arrayAttr2,
4176 ArrayRef<int64_t> shape, StringRef attrName1, StringRef attrName2,
4177 bool halfOpen = true, int64_t min = 1) {
4178 assert(arrayAttr1.size() <= shape.size());
4179 assert(arrayAttr2.size() <= shape.size());
4180 for (auto [index, it] :
4181 llvm::enumerate(llvm::zip(arrayAttr1, arrayAttr2, shape))) {
4182 auto val1 = llvm::cast<IntegerAttr>(std::get<0>(it)).getInt();
4183 auto val2 = llvm::cast<IntegerAttr>(std::get<1>(it)).getInt();
4184 int64_t max = std::get<2>(it);
4185 if (!halfOpen)
4186 max += 1;
4187 if (val1 + val2 < 0 || val1 + val2 >= max)
4188 return op.emitOpError("expected sum(")
4189 << attrName1 << ", " << attrName2 << ") dimension " << index
4190 << " to be confined to [" << min << ", " << max << ")";
4191 }
4192 return success();
4193}
4194
4196 MLIRContext *context) {
4197 auto attrs = llvm::map_range(values, [context](int64_t v) -> Attribute {
4198 return IntegerAttr::get(IntegerType::get(context, 64), APInt(64, v));
4199 });
4200 return ArrayAttr::get(context, llvm::to_vector<8>(attrs));
4201}
4202
4203LogicalResult InsertStridedSliceOp::verify() {
4204 auto sourceVectorType = getSourceVectorType();
4205 auto destVectorType = getDestVectorType();
4206 auto offsets = getOffsetsAttr();
4207 auto strides = getStridesAttr();
4208 if (offsets.size() != static_cast<unsigned>(destVectorType.getRank()))
4209 return emitOpError(
4210 "expected offsets of same size as destination vector rank");
4211 if (strides.size() != static_cast<unsigned>(sourceVectorType.getRank()))
4212 return emitOpError("expected strides of same size as source vector rank");
4213 if (sourceVectorType.getRank() > destVectorType.getRank())
4214 return emitOpError(
4215 "expected source rank to be no greater than destination rank");
4216
4217 auto sourceShape = sourceVectorType.getShape();
4218 auto destShape = destVectorType.getShape();
4219 SmallVector<int64_t, 4> sourceShapeAsDestShape(
4220 destShape.size() - sourceShape.size(), 0);
4221 sourceShapeAsDestShape.append(sourceShape.begin(), sourceShape.end());
4222 auto offName = InsertStridedSliceOp::getOffsetsAttrName();
4223 auto stridesName = InsertStridedSliceOp::getStridesAttrName();
4224 if (failed(isIntegerArrayAttrConfinedToShape(*this, offsets, destShape,
4225 offName)) ||
4226 failed(isIntegerArrayAttrConfinedToRange(*this, strides, /*min=*/1,
4227 /*max=*/1, stridesName,
4228 /*halfOpen=*/false)) ||
4230 *this, offsets,
4231 makeI64ArrayAttr(sourceShapeAsDestShape, getContext()), destShape,
4232 offName, "source vector shape",
4233 /*halfOpen=*/false, /*min=*/1)))
4234 return failure();
4235
4236 unsigned rankDiff = destShape.size() - sourceShape.size();
4237 for (unsigned idx = 0; idx < sourceShape.size(); ++idx) {
4238 if (sourceVectorType.getScalableDims()[idx] !=
4239 destVectorType.getScalableDims()[idx + rankDiff]) {
4240 return emitOpError("mismatching scalable flags (at source vector idx=")
4241 << idx << ")";
4242 }
4243 if (sourceVectorType.getScalableDims()[idx]) {
4244 auto sourceSize = sourceShape[idx];
4245 auto destSize = destShape[idx + rankDiff];
4246 if (sourceSize != destSize) {
4247 return emitOpError("expected size at idx=")
4248 << idx
4249 << (" to match the corresponding base size from the input "
4250 "vector (")
4251 << sourceSize << (" vs ") << destSize << (")");
4252 }
4253 }
4254 }
4255
4256 return success();
4257}
4258
4259namespace {
4260/// Rewrite insert_strided_slice(splat-like(v), splat-like(v)) as v.
4261class FoldInsertStridedSliceSplat final
4262 : public OpRewritePattern<InsertStridedSliceOp> {
4263public:
4264 using Base::Base;
4265
4266 LogicalResult matchAndRewrite(InsertStridedSliceOp insertStridedSliceOp,
4267 PatternRewriter &rewriter) const override {
4268
4269 auto dst = insertStridedSliceOp.getDest();
4270 auto splat = getScalarSplatSource(insertStridedSliceOp.getValueToStore());
4271 if (!splat || getScalarSplatSource(dst) != splat)
4272 return failure();
4273
4274 rewriter.replaceOp(insertStridedSliceOp, dst);
4275 return success();
4276 }
4277};
4278
4279/// Pattern to rewrite an InsertStridedSliceOp(ExtractStridedSliceOp(dst), dst)
4280/// to dst.
4281class FoldInsertStridedSliceOfExtract final
4282 : public OpRewritePattern<InsertStridedSliceOp> {
4283public:
4284 using Base::Base;
4285
4286 LogicalResult matchAndRewrite(InsertStridedSliceOp insertStridedSliceOp,
4287 PatternRewriter &rewriter) const override {
4288 auto extractStridedSliceOp =
4289 insertStridedSliceOp.getValueToStore()
4290 .getDefiningOp<vector::ExtractStridedSliceOp>();
4291
4292 if (!extractStridedSliceOp)
4293 return failure();
4294
4295 if (extractStridedSliceOp.getOperand() != insertStridedSliceOp.getDest())
4296 return failure();
4297
4298 // Check if have the same strides and offsets.
4299 if (extractStridedSliceOp.getStrides() !=
4300 insertStridedSliceOp.getStrides() ||
4301 extractStridedSliceOp.getOffsets() != insertStridedSliceOp.getOffsets())
4302 return failure();
4303
4304 rewriter.replaceOp(insertStridedSliceOp, insertStridedSliceOp.getDest());
4305 return success();
4306 }
4307};
4308
4309// Pattern to rewrite an InsertStridedSliceOp(ConstantOp into ConstantOp) ->
4310// ConstantOp.
4311class InsertStridedSliceConstantFolder final
4312 : public OpRewritePattern<InsertStridedSliceOp> {
4313public:
4314 using Base::Base;
4315
4316 // Do not create constants with more than `vectorSizeFoldThreashold` elements,
4317 // unless the source vector constant has a single use.
4318 static constexpr int64_t vectorSizeFoldThreshold = 256;
4319
4320 LogicalResult matchAndRewrite(InsertStridedSliceOp op,
4321 PatternRewriter &rewriter) const override {
4322 // Return if 'InsertOp' operand is not defined by a compatible vector
4323 // ConstantOp.
4324 TypedValue<VectorType> destVector = op.getDest();
4325 Attribute vectorDestCst;
4326 if (!matchPattern(destVector, m_Constant(&vectorDestCst)))
4327 return failure();
4328
4329 VectorType destTy = destVector.getType();
4330 if (destTy.isScalable())
4331 return failure();
4332
4333 // Make sure we do not create too many large constants.
4334 if (destTy.getNumElements() > vectorSizeFoldThreshold &&
4335 !destVector.hasOneUse())
4336 return failure();
4337
4338 TypedValue<VectorType> sourceValue = op.getValueToStore();
4339 Attribute sourceCst;
4340 if (!matchPattern(sourceValue, m_Constant(&sourceCst)))
4341 return failure();
4342
4343 // TODO: Support poison.
4344 if (matchPattern(vectorDestCst, ub::m_Poison()) ||
4345 matchPattern(sourceCst, ub::m_Poison()))
4346 return failure();
4347
4348 // TODO: Handle non-unit strides when they become available.
4349 if (op.hasNonUnitStrides())
4350 return failure();
4351
4352 VectorType sliceVecTy = sourceValue.getType();
4353 ArrayRef<int64_t> sliceShape = sliceVecTy.getShape();
4354 int64_t rankDifference = destTy.getRank() - sliceVecTy.getRank();
4355 SmallVector<int64_t, 4> offsets = getI64SubArray(op.getOffsets());
4356 SmallVector<int64_t, 4> destStrides = computeStrides(destTy.getShape());
4357
4358 // Calcualte the destination element indices by enumerating all slice
4359 // positions within the destination and linearizing them. The enumeration
4360 // order is lexicographic which yields a sequence of monotonically
4361 // increasing linearized position indices.
4362 // Because the destination may have higher dimensionality then the slice,
4363 // we keep track of two overlapping sets of positions and offsets.
4364 auto denseDest = llvm::cast<DenseElementsAttr>(vectorDestCst);
4365 auto denseSlice = llvm::cast<DenseElementsAttr>(sourceCst);
4366 auto sliceValuesIt = denseSlice.value_begin<Attribute>();
4367 auto newValues = llvm::to_vector(denseDest.getValues<Attribute>());
4368 SmallVector<int64_t> currDestPosition(offsets.begin(), offsets.end());
4369 MutableArrayRef<int64_t> currSlicePosition(
4370 currDestPosition.begin() + rankDifference, currDestPosition.end());
4371 ArrayRef<int64_t> sliceOffsets(offsets.begin() + rankDifference,
4372 offsets.end());
4373 do {
4374 int64_t linearizedPosition = linearize(currDestPosition, destStrides);
4375 assert(linearizedPosition < destTy.getNumElements() && "Invalid index");
4376 assert(sliceValuesIt != denseSlice.value_end<Attribute>() &&
4377 "Invalid slice element");
4378 newValues[linearizedPosition] = *sliceValuesIt;
4379 ++sliceValuesIt;
4380 } while (succeeded(
4381 incSlicePosition(currSlicePosition, sliceShape, sliceOffsets)));
4382
4383 auto newAttr = DenseElementsAttr::get(destTy, newValues);
4384 rewriter.replaceOpWithNewOp<arith::ConstantOp>(op, newAttr);
4385 return success();
4386 }
4387};
4388
4389} // namespace
4390
4391void vector::InsertStridedSliceOp::getCanonicalizationPatterns(
4392 RewritePatternSet &results, MLIRContext *context) {
4393 results.add<FoldInsertStridedSliceSplat, FoldInsertStridedSliceOfExtract,
4394 InsertStridedSliceConstantFolder>(context);
4395}
4396
4397OpFoldResult InsertStridedSliceOp::fold(FoldAdaptor adaptor) {
4398 if (getSourceVectorType() == getDestVectorType())
4399 return getValueToStore();
4400 return {};
4401}
4402
4403//===----------------------------------------------------------------------===//
4404// OuterProductOp
4405//===----------------------------------------------------------------------===//
4406
4407/// Build an op without mask, use the type of `acc` as the return type.
4408void OuterProductOp::build(OpBuilder &builder, OperationState &result,
4409 Value lhs, Value rhs, Value acc) {
4410 result.addOperands({lhs, rhs, acc});
4411 result.addTypes(acc.getType());
4412}
4413
4414void OuterProductOp::print(OpAsmPrinter &p) {
4415 p << " " << getLhs() << ", " << getRhs();
4416 if (getAcc()) {
4417 p << ", " << getAcc();
4418 SmallVector<NamedAttribute> attrs((*this)->getDiscardableAttrs());
4419 attrs.emplace_back(getKindAttrName(), getKindAttr());
4420 llvm::sort(attrs);
4421 p.printOptionalAttrDict(attrs);
4422 }
4423 p << " : " << getLhs().getType() << ", " << getRhs().getType();
4424}
4425
4426ParseResult OuterProductOp::parse(OpAsmParser &parser, OperationState &result) {
4427 SmallVector<OpAsmParser::UnresolvedOperand, 3> operandsInfo;
4428 Type tLHS, tRHS;
4429 if (parser.parseOperandList(operandsInfo) ||
4430 parser.parseOptionalAttrDict(result.attributes) ||
4431 parser.parseColonType(tLHS) || parser.parseComma() ||
4432 parser.parseType(tRHS))
4433 return failure();
4434 if (operandsInfo.size() < 2)
4435 return parser.emitError(parser.getNameLoc(),
4436 "expected at least 2 operands");
4437 VectorType vLHS = llvm::dyn_cast<VectorType>(tLHS);
4438 VectorType vRHS = llvm::dyn_cast<VectorType>(tRHS);
4439 if (!vLHS)
4440 return parser.emitError(parser.getNameLoc(),
4441 "expected vector type for operand #1");
4442 // The result type is built below from dimension 0 of the operands, which a
4443 // 0-d vector does not have. Only that case has to be caught here; a higher
4444 // rank still reaches the verifier, which rejects it with the same wording.
4445 if (vLHS.getRank() == 0)
4446 return parser.emitError(parser.getNameLoc(),
4447 "expected 1-d vector for operand #1");
4448 if (vRHS && vRHS.getRank() == 0)
4449 return parser.emitError(parser.getNameLoc(),
4450 "expected 1-d vector for operand #2");
4451
4452 VectorType resType;
4453 if (vRHS) {
4454 SmallVector<bool> scalableDimsRes{vLHS.getScalableDims()[0],
4455 vRHS.getScalableDims()[0]};
4456 resType = VectorType::get({vLHS.getDimSize(0), vRHS.getDimSize(0)},
4457 vLHS.getElementType(), scalableDimsRes);
4458 } else {
4459 // Scalar RHS operand
4460 SmallVector<bool> scalableDimsRes{vLHS.getScalableDims()[0]};
4461 resType = VectorType::get({vLHS.getDimSize(0)}, vLHS.getElementType(),
4462 scalableDimsRes);
4463 }
4464
4465 if (!result.attributes.get(OuterProductOp::getKindAttrName(result.name))) {
4466 result.attributes.append(
4467 OuterProductOp::getKindAttrName(result.name),
4468 CombiningKindAttr::get(result.getContext(),
4469 OuterProductOp::getDefaultKind()));
4470 }
4471
4472 return failure(
4473 parser.resolveOperand(operandsInfo[0], tLHS, result.operands) ||
4474 parser.resolveOperand(operandsInfo[1], tRHS, result.operands) ||
4475 (operandsInfo.size() > 2 &&
4476 parser.resolveOperand(operandsInfo[2], resType, result.operands)) ||
4477 parser.addTypeToList(resType, result.types));
4478}
4479
4480LogicalResult OuterProductOp::verify() {
4481 Type tRHS = getOperandTypeRHS();
4482 VectorType vLHS = getOperandVectorTypeLHS(),
4483 vRHS = llvm::dyn_cast<VectorType>(tRHS),
4484 vACC = getOperandVectorTypeACC(), vRES = getResultVectorType();
4485
4486 if (vLHS.getRank() != 1)
4487 return emitOpError("expected 1-d vector for operand #1");
4488
4489 if (vRHS) {
4490 // Proper OUTER operation.
4491 if (vRHS.getRank() != 1)
4492 return emitOpError("expected 1-d vector for operand #2");
4493 if (vRES.getRank() != 2)
4494 return emitOpError("expected 2-d vector result");
4495 if (vLHS.getDimSize(0) != vRES.getDimSize(0))
4496 return emitOpError("expected #1 operand dim to match result dim #1");
4497 if (vRHS.getDimSize(0) != vRES.getDimSize(1))
4498 return emitOpError("expected #2 operand dim to match result dim #2");
4499 if (vLHS.isScalable() && !vRHS.isScalable()) {
4500 // This restriction reflects what's currently supported in terms of
4501 // scalable vectors. However, we could relax this if there's a use case.
4502 return emitOpError(
4503 "expected either both or only #2 operand dim to be scalable");
4504 }
4505 } else {
4506 // An AXPY operation.
4507 if (vRES.getRank() != 1)
4508 return emitOpError("expected 1-d vector result");
4509 if (vLHS.getDimSize(0) != vRES.getDimSize(0))
4510 return emitOpError("expected #1 operand dim to match result dim #1");
4511 }
4512
4513 if (vACC && vACC != vRES)
4514 return emitOpError("expected operand #3 of same type as result type");
4515
4516 if (!getKindAttr()) {
4517 return emitOpError("expected 'kind' attribute of type CombiningKind (e.g. "
4518 "'vector.kind<add>')");
4519 }
4520
4521 // Verify supported combining kind.
4522 if (!isSupportedCombiningKind(getKind(), vRES.getElementType()))
4523 return emitOpError("unsupported outerproduct type");
4524
4525 return success();
4526}
4527
4528// MaskableOpInterface methods.
4529
4530/// Returns the mask type expected by this operation. Mostly used for
4531/// verification purposes. It requires the operation to be vectorized."
4532Type OuterProductOp::getExpectedMaskType() {
4533 auto vecType = this->getResultVectorType();
4534 return VectorType::get(vecType.getShape(),
4535 IntegerType::get(vecType.getContext(), /*width=*/1),
4536 vecType.getScalableDims());
4537}
4538
4539//===----------------------------------------------------------------------===//
4540// ExtractStridedSliceOp
4541//===----------------------------------------------------------------------===//
4542
4543// Inference works as follows:
4544// 1. Add 'sizes' from prefix of dims in 'offsets'.
4545// 2. Add sizes from 'vectorType' for remaining dims.
4546// Scalable flags are inherited from 'vectorType'.
4547static Type inferStridedSliceOpResultType(VectorType vectorType,
4548 ArrayAttr offsets, ArrayAttr sizes,
4549 ArrayAttr strides) {
4550 assert(offsets.size() == sizes.size() && offsets.size() == strides.size());
4552 shape.reserve(vectorType.getRank());
4553 unsigned idx = 0;
4554 for (unsigned e = offsets.size(); idx < e; ++idx)
4555 shape.push_back(llvm::cast<IntegerAttr>(sizes[idx]).getInt());
4556 for (unsigned e = vectorType.getShape().size(); idx < e; ++idx)
4557 shape.push_back(vectorType.getShape()[idx]);
4558
4559 return VectorType::get(shape, vectorType.getElementType(),
4560 vectorType.getScalableDims());
4561}
4562
4563void ExtractStridedSliceOp::build(OpBuilder &builder, OperationState &result,
4564 Value source, ArrayRef<int64_t> offsets,
4565 ArrayRef<int64_t> sizes,
4566 ArrayRef<int64_t> strides) {
4567 result.addOperands(source);
4568 auto offsetsAttr = getVectorSubscriptAttr(builder, offsets);
4569 auto sizesAttr = getVectorSubscriptAttr(builder, sizes);
4570 auto stridesAttr = getVectorSubscriptAttr(builder, strides);
4571 result.addTypes(
4572 inferStridedSliceOpResultType(llvm::cast<VectorType>(source.getType()),
4573 offsetsAttr, sizesAttr, stridesAttr));
4574 result.addAttribute(ExtractStridedSliceOp::getOffsetsAttrName(result.name),
4575 offsetsAttr);
4576 result.addAttribute(ExtractStridedSliceOp::getSizesAttrName(result.name),
4577 sizesAttr);
4578 result.addAttribute(ExtractStridedSliceOp::getStridesAttrName(result.name),
4579 stridesAttr);
4580}
4581
4582LogicalResult ExtractStridedSliceOp::verify() {
4583 auto type = getSourceVectorType();
4584 auto offsets = getOffsetsAttr();
4585 auto sizes = getSizesAttr();
4586 auto strides = getStridesAttr();
4587 if (offsets.size() != sizes.size() || offsets.size() != strides.size())
4588 return emitOpError(
4589 "expected offsets, sizes and strides attributes of same size");
4590
4591 auto shape = type.getShape();
4592 auto offName = getOffsetsAttrName();
4593 auto sizesName = getSizesAttrName();
4594 auto stridesName = getStridesAttrName();
4595 if (failed(
4596 isIntegerArrayAttrSmallerThanShape(*this, offsets, shape, offName)) ||
4597 failed(
4598 isIntegerArrayAttrSmallerThanShape(*this, sizes, shape, sizesName)) ||
4599 failed(isIntegerArrayAttrSmallerThanShape(*this, strides, shape,
4600 stridesName)) ||
4601 failed(
4602 isIntegerArrayAttrConfinedToShape(*this, offsets, shape, offName)) ||
4603 failed(isIntegerArrayAttrConfinedToShape(*this, sizes, shape, sizesName,
4604 /*halfOpen=*/false,
4605 /*min=*/1)) ||
4606 failed(isIntegerArrayAttrConfinedToRange(*this, strides, /*min=*/1,
4607 /*max=*/1, stridesName,
4608 /*halfOpen=*/false)) ||
4609 failed(isSumOfIntegerArrayAttrConfinedToShape(*this, offsets, sizes,
4610 shape, offName, sizesName,
4611 /*halfOpen=*/false)))
4612 return failure();
4613
4614 auto resultType = inferStridedSliceOpResultType(getSourceVectorType(),
4615 offsets, sizes, strides);
4616 if (getResult().getType() != resultType)
4617 return emitOpError("expected result type to be ") << resultType;
4618
4619 for (unsigned idx = 0; idx < sizes.size(); ++idx) {
4620 if (type.getScalableDims()[idx]) {
4621 auto inputDim = type.getShape()[idx];
4622 auto inputSize = llvm::cast<IntegerAttr>(sizes[idx]).getInt();
4623 if (inputDim != inputSize)
4624 return emitOpError("expected size at idx=")
4625 << idx
4626 << (" to match the corresponding base size from the input "
4627 "vector (")
4628 << inputSize << (" vs ") << inputDim << (")");
4629 }
4630 }
4631
4632 return success();
4633}
4634
4635// When the source of ExtractStrided comes from a chain of InsertStrided ops try
4636// to use the source of the InsertStrided ops if we can detect that the
4637// extracted vector is a subset of one of the vector inserted.
4638static LogicalResult
4639foldExtractStridedOpFromInsertChain(ExtractStridedSliceOp op) {
4640 // Helper to extract integer out of ArrayAttr.
4641 auto getElement = [](ArrayAttr array, int idx) {
4642 return llvm::cast<IntegerAttr>(array[idx]).getInt();
4643 };
4644 ArrayAttr extractOffsets = op.getOffsets();
4645 ArrayAttr extractStrides = op.getStrides();
4646 ArrayAttr extractSizes = op.getSizes();
4647 auto insertOp = op.getSource().getDefiningOp<InsertStridedSliceOp>();
4648 while (insertOp) {
4649 if (op.getSourceVectorType().getRank() !=
4650 insertOp.getSourceVectorType().getRank())
4651 return failure();
4652 ArrayAttr insertOffsets = insertOp.getOffsets();
4653 ArrayAttr insertStrides = insertOp.getStrides();
4654 // If the rank of extract is greater than the rank of insert, we are likely
4655 // extracting a partial chunk of the vector inserted.
4656 if (extractOffsets.size() > insertOffsets.size())
4657 return failure();
4658 bool patialoverlap = false;
4659 bool disjoint = false;
4660 SmallVector<int64_t, 4> offsetDiffs;
4661 for (unsigned dim = 0, e = extractOffsets.size(); dim < e; ++dim) {
4662 if (getElement(extractStrides, dim) != getElement(insertStrides, dim))
4663 return failure();
4664 int64_t start = getElement(insertOffsets, dim);
4665 int64_t end = start + insertOp.getSourceVectorType().getDimSize(dim);
4666 int64_t offset = getElement(extractOffsets, dim);
4667 int64_t size = getElement(extractSizes, dim);
4668 // Check if the start of the extract offset is in the interval inserted.
4669 if (start <= offset && offset < end) {
4670 // If the extract interval overlaps but is not fully included we may
4671 // have a partial overlap that will prevent any folding.
4672 if (offset + size > end)
4673 patialoverlap = true;
4674 offsetDiffs.push_back(offset - start);
4675 continue;
4676 }
4677 disjoint = true;
4678 break;
4679 }
4680 // The extract element chunk is a subset of the insert element.
4681 if (!disjoint && !patialoverlap) {
4682 op.setOperand(insertOp.getValueToStore());
4683 // OpBuilder is only used as a helper to build an I64ArrayAttr.
4684 OpBuilder b(op.getContext());
4685 op.setOffsetsAttr(b.getI64ArrayAttr(offsetDiffs));
4686 return success();
4687 }
4688 // If the chunk extracted is disjoint from the chunk inserted, keep looking
4689 // in the insert chain.
4690 if (disjoint)
4691 insertOp = insertOp.getDest().getDefiningOp<InsertStridedSliceOp>();
4692 else {
4693 // The extracted vector partially overlap the inserted vector, we cannot
4694 // fold.
4695 return failure();
4696 }
4697 }
4698 return failure();
4699}
4700
4701// ExtractStridedSliceOp(non-splat ConstantOp) -> ConstantOp.
4702static OpFoldResult
4704 Attribute foldInput) {
4705
4706 auto dense = llvm::dyn_cast_if_present<DenseElementsAttr>(foldInput);
4707 if (!dense)
4708 return {};
4709
4710 // TODO: Handle non-unit strides when they become available.
4711 if (op.hasNonUnitStrides())
4712 return {};
4713
4714 VectorType sourceVecTy = op.getSourceVectorType();
4715 ArrayRef<int64_t> sourceShape = sourceVecTy.getShape();
4716 SmallVector<int64_t, 4> sourceStrides = computeStrides(sourceShape);
4717
4718 VectorType sliceVecTy = op.getType();
4719 ArrayRef<int64_t> sliceShape = sliceVecTy.getShape();
4720 int64_t rank = sliceVecTy.getRank();
4721
4722 // Expand offsets and sizes to match the vector rank.
4723 SmallVector<int64_t, 4> offsets(rank, 0);
4724 copy(getI64SubArray(op.getOffsets()), offsets.begin());
4725
4726 SmallVector<int64_t, 4> sizes(sourceShape);
4727 copy(getI64SubArray(op.getSizes()), sizes.begin());
4728
4729 // Calculate the slice elements by enumerating all slice positions and
4730 // linearizing them. The enumeration order is lexicographic which yields a
4731 // sequence of monotonically increasing linearized position indices.
4732 const auto denseValuesBegin = dense.value_begin<Attribute>();
4733 SmallVector<Attribute> sliceValues;
4734 sliceValues.reserve(sliceVecTy.getNumElements());
4735 SmallVector<int64_t> currSlicePosition(offsets.begin(), offsets.end());
4736 do {
4737 int64_t linearizedPosition = linearize(currSlicePosition, sourceStrides);
4738 assert(linearizedPosition < sourceVecTy.getNumElements() &&
4739 "Invalid index");
4740 sliceValues.push_back(*(denseValuesBegin + linearizedPosition));
4741 } while (succeeded(incSlicePosition(currSlicePosition, sliceShape, offsets)));
4742
4743 assert(static_cast<int64_t>(sliceValues.size()) ==
4744 sliceVecTy.getNumElements() &&
4745 "Invalid number of slice elements");
4746 return DenseElementsAttr::get(sliceVecTy, sliceValues);
4747}
4748
4749OpFoldResult ExtractStridedSliceOp::fold(FoldAdaptor adaptor) {
4750 if (getSourceVectorType() == getResult().getType())
4751 return getSource();
4752 if (succeeded(foldExtractStridedOpFromInsertChain(*this)))
4753 return getResult();
4754
4755 // ExtractStridedSliceOp(splat ConstantOp) -> ConstantOp.
4756 if (auto splat =
4757 llvm::dyn_cast_if_present<SplatElementsAttr>(adaptor.getSource()))
4758 return DenseElementsAttr::get(getType(), splat.getSplatValue<Attribute>());
4759
4760 // ExtractStridedSliceOp(non-splat ConstantOp) -> ConstantOp.
4761 return foldExtractStridedSliceNonSplatConstant(*this, adaptor.getSource());
4762}
4763
4764void ExtractStridedSliceOp::getOffsets(SmallVectorImpl<int64_t> &results) {
4765 populateFromInt64AttrArray(getOffsets(), results);
4766}
4767
4768namespace {
4769
4770// Pattern to rewrite nested ExtractStridedSliceOp into a single one.
4771//
4772// Example:
4773//
4774// %0 = vector.extract_strided_slice %arg0
4775// {offsets = [1, 2], sizes = [3, 4], strides = [1, 1]}
4776// : vector<4x8x16xf32> to vector<3x4x16xf32>
4777// %1 = vector.extract_strided_slice %0
4778// {offsets = [0, 1], sizes = [2, 2], strides = [1, 1]}
4779// : vector<3x4x16xf32> to vector<2x2x16xf32>
4780//
4781// to
4782//
4783// %1 = vector.extract_strided_slice %arg0
4784// {offsets = [1, 3], sizes = [2, 2], strides = [1, 1]}
4785// : vector<4x8x16xf32> to vector<2x2x16xf32>
4786class StridedSliceFolder final
4787 : public OpRewritePattern<ExtractStridedSliceOp> {
4788public:
4789 using OpRewritePattern<ExtractStridedSliceOp>::OpRewritePattern;
4790
4791 LogicalResult matchAndRewrite(ExtractStridedSliceOp secondOp,
4792 PatternRewriter &rewriter) const override {
4793 auto firstOp = secondOp.getSource().getDefiningOp<ExtractStridedSliceOp>();
4794 if (!firstOp)
4795 return failure();
4796
4797 if (secondOp.hasNonUnitStrides() || firstOp.hasNonUnitStrides())
4798 return failure();
4799
4800 SmallVector<int64_t> firstOffsets = getI64SubArray(firstOp.getOffsets());
4801 SmallVector<int64_t> firstSizes = getI64SubArray(firstOp.getSizes());
4802 SmallVector<int64_t> secondOffsets = getI64SubArray(secondOp.getOffsets());
4803 SmallVector<int64_t> secondSizes = getI64SubArray(secondOp.getSizes());
4804
4805 unsigned newRank = std::max(firstOffsets.size(), secondOffsets.size());
4806 SmallVector<int64_t> combinedOffsets(newRank, 0);
4807 SmallVector<int64_t> combinedSizes(newRank);
4808 ArrayRef<int64_t> firstSourceShape =
4809 firstOp.getSourceVectorType().getShape();
4810 for (unsigned i = 0; i < newRank; ++i) {
4811 int64_t off1 = (i < firstOffsets.size()) ? firstOffsets[i] : 0;
4812 int64_t off2 = (i < secondOffsets.size()) ? secondOffsets[i] : 0;
4813 combinedOffsets[i] = off1 + off2;
4814
4815 if (i < secondSizes.size()) {
4816 combinedSizes[i] = secondSizes[i];
4817 } else if (i < firstSizes.size()) {
4818 combinedSizes[i] = firstSizes[i];
4819 } else {
4820 combinedSizes[i] = firstSourceShape[i];
4821 }
4822 }
4823
4824 SmallVector<int64_t> combinedStrides(newRank, 1);
4825 rewriter.replaceOpWithNewOp<ExtractStridedSliceOp>(
4826 secondOp, firstOp.getSource(), combinedOffsets, combinedSizes,
4827 combinedStrides);
4828 return success();
4829 }
4830};
4831
4832// Pattern to rewrite an ExtractStridedSliceOp(CreateMaskOp) to
4833// CreateMaskOp.
4834//
4835// Example:
4836//
4837// %mask = vector.create_mask %ub : vector<16xi1>
4838// %slice = vector.extract_strided_slice [%offset] [8] [1]
4839//
4840// to
4841//
4842// %new_ub = arith.subi %ub, %offset
4843// %mask = vector.create_mask %new_ub : vector<8xi1>
4844class StridedSliceCreateMaskFolder final
4845 : public OpRewritePattern<ExtractStridedSliceOp> {
4846 using Base::Base;
4847
4848public:
4849 LogicalResult matchAndRewrite(ExtractStridedSliceOp extractStridedSliceOp,
4850 PatternRewriter &rewriter) const override {
4851 Location loc = extractStridedSliceOp.getLoc();
4852 // Return if 'extractStridedSliceOp' operand is not defined by a
4853 // CreateMaskOp.
4854 auto createMaskOp =
4855 extractStridedSliceOp.getSource().getDefiningOp<CreateMaskOp>();
4856 if (!createMaskOp)
4857 return failure();
4858 // Return if 'extractStridedSliceOp' has non-unit strides.
4859 if (extractStridedSliceOp.hasNonUnitStrides())
4860 return failure();
4861 // Gather constant mask dimension sizes.
4862 SmallVector<Value> maskDimSizes(createMaskOp.getOperands());
4863 // Gather strided slice offsets and sizes.
4864 SmallVector<int64_t> sliceOffsets;
4865 populateFromInt64AttrArray(extractStridedSliceOp.getOffsets(),
4866 sliceOffsets);
4867 SmallVector<int64_t> sliceSizes;
4868 populateFromInt64AttrArray(extractStridedSliceOp.getSizes(), sliceSizes);
4869
4870 // Compute slice of vector mask region.
4871 SmallVector<Value> sliceMaskDimSizes;
4872 sliceMaskDimSizes.reserve(maskDimSizes.size());
4873 // sliceOffsets.size() <= maskDimSizes.size(), so we use llvm::zip and
4874 // only iterate on the leading dim sizes. The tail accounts for the
4875 // remaining dim sizes.
4876 for (auto [maskDimSize, sliceOffset, sliceSize] :
4877 llvm::zip(maskDimSizes, sliceOffsets, sliceSizes)) {
4878 // No need to clamp on min/max values, because create_mask has clamping
4879 // semantics, i.e. the sliceMaskDimSize is allowed to be negative or
4880 // greater than the vector dim size.
4881 IntegerAttr offsetAttr =
4882 rewriter.getIntegerAttr(maskDimSize.getType(), sliceOffset);
4883 Value offset = arith::ConstantOp::create(rewriter, loc, offsetAttr);
4884 Value sliceMaskDimSize =
4885 arith::SubIOp::create(rewriter, loc, maskDimSize, offset);
4886 sliceMaskDimSizes.push_back(sliceMaskDimSize);
4887 }
4888 // Add unchanged dimensions.
4889 llvm::append_range(
4890 sliceMaskDimSizes,
4891 llvm::drop_begin(maskDimSizes, sliceMaskDimSizes.size()));
4892 // Replace 'extractStridedSliceOp' with CreateMaskOp with sliced mask
4893 // region.
4894 rewriter.replaceOpWithNewOp<CreateMaskOp>(
4895 extractStridedSliceOp, extractStridedSliceOp.getResult().getType(),
4896 sliceMaskDimSizes);
4897 return success();
4898 }
4899};
4900
4901// Pattern to rewrite an ExtractStridedSliceOp(ConstantMaskOp) to
4902// ConstantMaskOp.
4903class StridedSliceConstantMaskFolder final
4904 : public OpRewritePattern<ExtractStridedSliceOp> {
4905public:
4906 using Base::Base;
4907
4908 LogicalResult matchAndRewrite(ExtractStridedSliceOp extractStridedSliceOp,
4909 PatternRewriter &rewriter) const override {
4910 // Return if 'extractStridedSliceOp' operand is not defined by a
4911 // ConstantMaskOp.
4912 auto *defOp = extractStridedSliceOp.getSource().getDefiningOp();
4913 auto constantMaskOp = dyn_cast_or_null<ConstantMaskOp>(defOp);
4914 if (!constantMaskOp)
4915 return failure();
4916 // Return if 'extractStridedSliceOp' has non-unit strides.
4917 if (extractStridedSliceOp.hasNonUnitStrides())
4918 return failure();
4919 // Gather constant mask dimension sizes.
4920 ArrayRef<int64_t> maskDimSizes = constantMaskOp.getMaskDimSizes();
4921 // Gather strided slice offsets and sizes.
4922 SmallVector<int64_t> sliceOffsets;
4923 populateFromInt64AttrArray(extractStridedSliceOp.getOffsets(),
4924 sliceOffsets);
4925 SmallVector<int64_t> sliceSizes;
4926 populateFromInt64AttrArray(extractStridedSliceOp.getSizes(), sliceSizes);
4927
4928 // Compute slice of vector mask region.
4929 SmallVector<int64_t> sliceMaskDimSizes;
4930 sliceMaskDimSizes.reserve(maskDimSizes.size());
4931 for (auto [maskDimSize, sliceOffset, sliceSize] :
4932 llvm::zip(maskDimSizes, sliceOffsets, sliceSizes)) {
4933 int64_t sliceMaskDimSize = std::max(
4934 static_cast<int64_t>(0),
4935 std::min(sliceOffset + sliceSize, maskDimSize) - sliceOffset);
4936 sliceMaskDimSizes.push_back(sliceMaskDimSize);
4937 }
4938 // Add unchanged dimensions.
4939 if (sliceMaskDimSizes.size() < maskDimSizes.size())
4940 for (size_t i = sliceMaskDimSizes.size(); i < maskDimSizes.size(); ++i)
4941 sliceMaskDimSizes.push_back(maskDimSizes[i]);
4942 // If any of 'sliceMaskDimSizes' are zero, then set all to zero (masked
4943 // region is a conjunction of mask dim intervals).
4944 if (llvm::is_contained(sliceMaskDimSizes, 0))
4945 sliceMaskDimSizes.assign(maskDimSizes.size(), 0);
4946
4947 // Replace 'extractStridedSliceOp' with ConstantMaskOp with sliced mask
4948 // region.
4949 rewriter.replaceOpWithNewOp<ConstantMaskOp>(
4950 extractStridedSliceOp, extractStridedSliceOp.getResult().getType(),
4951 sliceMaskDimSizes);
4952 return success();
4953 }
4954};
4955
4956// Pattern to rewrite an ExtractStridedSliceOp(BroadcastOp) to
4957// BroadcastOp(ExtractStrideSliceOp).
4958class StridedSliceBroadcast final
4959 : public OpRewritePattern<ExtractStridedSliceOp> {
4960public:
4961 using Base::Base;
4962
4963 LogicalResult matchAndRewrite(ExtractStridedSliceOp op,
4964 PatternRewriter &rewriter) const override {
4965 auto broadcast = op.getSource().getDefiningOp<BroadcastOp>();
4966 if (!broadcast)
4967 return failure();
4968 auto srcVecType =
4969 llvm::dyn_cast<VectorType>(broadcast.getSource().getType());
4970 unsigned srcRank = srcVecType ? srcVecType.getRank() : 0;
4971 auto dstVecType = llvm::cast<VectorType>(op.getType());
4972 unsigned dstRank = dstVecType.getRank();
4973 unsigned rankDiff = dstRank - srcRank;
4974 // Source dimensions can be broadcasted (1 -> n with n > 1) or sliced
4975 // (n -> m with n > m). If they are originally both broadcasted *and*
4976 // sliced, this can be simplified to just broadcasting.
4977 bool needsSlice = false;
4978 for (unsigned i = 0; i < srcRank; i++) {
4979 if (srcVecType.getDimSize(i) != 1 &&
4980 srcVecType.getDimSize(i) != dstVecType.getDimSize(i + rankDiff)) {
4981 needsSlice = true;
4982 break;
4983 }
4984 }
4985 Value source = broadcast.getSource();
4986 if (needsSlice) {
4987 SmallVector<int64_t> offsets =
4988 getI64SubArray(op.getOffsets(), /*dropFront=*/rankDiff);
4989 SmallVector<int64_t> sizes =
4990 getI64SubArray(op.getSizes(), /*dropFront=*/rankDiff);
4991 for (unsigned i = 0; i < srcRank; i++) {
4992 if (srcVecType.getDimSize(i) == 1) {
4993 // In case this dimension was broadcasted *and* sliced, the offset
4994 // and size need to be updated now that there is no broadcast before
4995 // the slice.
4996 offsets[i] = 0;
4997 sizes[i] = 1;
4998 }
4999 }
5000 source = ExtractStridedSliceOp::create(
5001 rewriter, op->getLoc(), source, offsets, sizes,
5002 getI64SubArray(op.getStrides(), /*dropFront=*/rankDiff));
5003 }
5004 rewriter.replaceOpWithNewOp<BroadcastOp>(op, op.getType(), source);
5005 return success();
5006 }
5007};
5008
5009/// Rewrite extract_strided_slice(splat-like(v)) with broadcast(v).
5010class StridedSliceSplat final : public OpRewritePattern<ExtractStridedSliceOp> {
5011public:
5012 using Base::Base;
5013
5014 LogicalResult matchAndRewrite(ExtractStridedSliceOp op,
5015 PatternRewriter &rewriter) const override {
5016
5017 Value splat = getScalarSplatSource(op.getSource());
5018 if (!splat)
5019 return failure();
5020 rewriter.replaceOpWithNewOp<BroadcastOp>(op, op.getType(), splat);
5021 return success();
5022 }
5023};
5024
5025/// Pattern to rewrite simple cases of N-D extract_strided_slice, where the
5026/// slice is contiguous, into extract and shape_cast.
5027///
5028/// Example:
5029/// Before:
5030/// %1 = vector.extract_strided_slice %arg0 {
5031/// offsets = [0, 0, 0, 0, 0],
5032/// sizes = [1, 1, 1, 1, 8],
5033/// strides = [1, 1, 1, 1, 1]
5034/// } : vector<8x1x1x2x8xi8> to vector<1x1x1x1x8xi8>
5035/// After:
5036/// %0 = vector.extract %arg0[0, 0, 0, 0]
5037/// : vector<8xi8> from vector<8x1x1x2x8xi8>
5038/// %1 = vector.shape_cast %0
5039/// : vector<8xi8> to vector<1x1x1x1x8xi8>
5040///
5041class ContiguousExtractStridedSliceToExtract final
5042 : public OpRewritePattern<ExtractStridedSliceOp> {
5043public:
5044 using Base::Base;
5045
5046 LogicalResult matchAndRewrite(ExtractStridedSliceOp op,
5047 PatternRewriter &rewriter) const override {
5048 if (op.hasNonUnitStrides())
5049 return failure();
5050 Value source = op.getOperand();
5051 auto sourceType = cast<VectorType>(source.getType());
5052 if (sourceType.isScalable() || sourceType.getRank() == 0)
5053 return failure();
5054
5055 // Compute the number of offsets to pass to ExtractOp::build. That is the
5056 // difference between the source rank and the desired slice rank. We walk
5057 // the dimensions from innermost out, and stop when the next slice dimension
5058 // is not full-size.
5059 SmallVector<int64_t> sizes = getI64SubArray(op.getSizes());
5060 int numOffsets;
5061 for (numOffsets = sizes.size(); numOffsets > 0; --numOffsets) {
5062 if (sizes[numOffsets - 1] != sourceType.getDimSize(numOffsets - 1))
5063 break;
5064 }
5065
5066 // If the created extract op would have no offsets, then this whole
5067 // extract_strided_slice is the identity and should have been handled by
5068 // other canonicalizations.
5069 if (numOffsets == 0)
5070 return failure();
5071
5072 // If not even the inner-most dimension is full-size, this op can't be
5073 // rewritten as an ExtractOp.
5074 if (numOffsets == sourceType.getRank() &&
5075 static_cast<int>(sizes.size()) == sourceType.getRank())
5076 return failure();
5077
5078 // The outer dimensions must have unit size.
5079 for (int i = 0; i < numOffsets; ++i) {
5080 if (sizes[i] != 1)
5081 return failure();
5082 }
5083
5084 // Avoid generating slices that have leading unit dimensions. The shape_cast
5085 // op that we create below would take bad generic fallback patterns
5086 // (ShapeCastOpRewritePattern).
5087 while (numOffsets < static_cast<int>(sizes.size()) - 1 &&
5088 sizes[numOffsets] == 1) {
5089 ++numOffsets;
5090 }
5091
5092 SmallVector<int64_t> offsets = getI64SubArray(op.getOffsets());
5093 auto extractOffsets = ArrayRef(offsets).take_front(numOffsets);
5094 Value extract = vector::ExtractOp::create(rewriter, op->getLoc(), source,
5095 extractOffsets);
5096 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, op.getType(), extract);
5097 return success();
5098 }
5099};
5100
5101} // namespace
5102
5103void ExtractStridedSliceOp::getCanonicalizationPatterns(
5104 RewritePatternSet &results, MLIRContext *context) {
5105 // Pattern to rewrite a ExtractStridedSliceOp(ConstantMaskOp) ->
5106 // ConstantMaskOp and ExtractStridedSliceOp(ConstantOp) -> ConstantOp.
5107 results.add<StridedSliceFolder, StridedSliceCreateMaskFolder,
5108 StridedSliceConstantMaskFolder, StridedSliceBroadcast,
5109 StridedSliceSplat, ContiguousExtractStridedSliceToExtract>(
5110 context);
5111}
5112
5113//===----------------------------------------------------------------------===//
5114// TransferReadOp
5115//===----------------------------------------------------------------------===//
5116
5117/// 1. Builder that sets padding to zero and an empty mask (variant with attrs).
5118/// If `padding` is null, a poison value is used.
5119void TransferReadOp::build(OpBuilder &builder, OperationState &result,
5120 VectorType vectorType, Value source,
5121 ValueRange indices, std::optional<Value> padding,
5122 AffineMapAttr permutationMapAttr,
5123 /*optional*/ ArrayAttr inBoundsAttr) {
5124
5125 Type elemType = llvm::cast<ShapedType>(source.getType()).getElementType();
5126 if (!padding)
5127 padding = ub::PoisonOp::create(builder, result.location, elemType);
5128 // Delegate to the most general builder (see
5129 // `mlir/Dialect/Vector/IR/VectorOps.cpp.inc`)
5130 build(builder, result, vectorType, source, indices, permutationMapAttr,
5131 *padding, /*mask=*/Value(), inBoundsAttr);
5132}
5133
5134/// 2. Builder that sets padding to zero and an empty mask (variant without
5135/// attrs).
5136/// If `padding` is null, a poison value is used.
5137/// If `permutationMap` is null, a minor identity map is used.
5138/// If `inBounds` is null, an empty mask is used.
5139void TransferReadOp::build(OpBuilder &builder, OperationState &result,
5140 VectorType vectorType, Value source,
5141 ValueRange indices, std::optional<Value> padding,
5142 AffineMap permutationMap,
5143 std::optional<ArrayRef<bool>> inBounds) {
5144 if (!permutationMap)
5145 permutationMap = getTransferMinorIdentityMap(
5146 llvm::cast<ShapedType>(source.getType()), vectorType);
5147 auto permutationMapAttr = AffineMapAttr::get(permutationMap);
5148 auto inBoundsAttr = (inBounds && !inBounds.value().empty())
5149 ? builder.getBoolArrayAttr(inBounds.value())
5150 : builder.getBoolArrayAttr(
5151 SmallVector<bool>(vectorType.getRank(), false));
5152 // Delegate to Builder 1
5153 build(builder, result, vectorType, source, indices, padding,
5154 permutationMapAttr, inBoundsAttr);
5155}
5156
5157/// 3. Builder that sets permutation map to 'getMinorIdentityMap'.
5158/// If `padding` is null, a poison value is used.
5159/// If `inBounds` is null, an empty mask is used.
5160void TransferReadOp::build(OpBuilder &builder, OperationState &result,
5161 VectorType vectorType, Value source,
5162 ValueRange indices, std::optional<Value> padding,
5163 std::optional<ArrayRef<bool>> inBounds) {
5164 // Delegate to Builder 2
5165 build(builder, result, vectorType, source, indices, padding,
5166 /*permutationMap=*/AffineMap(), inBounds);
5167}
5168
5169template <typename EmitFun>
5170static LogicalResult verifyPermutationMap(AffineMap permutationMap,
5171 EmitFun emitOpError) {
5172 SmallVector<bool, 8> seen(permutationMap.getNumInputs(), false);
5173 for (auto expr : permutationMap.getResults()) {
5174 auto dim = dyn_cast<AffineDimExpr>(expr);
5175 auto zero = dyn_cast<AffineConstantExpr>(expr);
5176 if (zero) {
5177 if (zero.getValue() != 0) {
5178 return emitOpError(
5179 "requires a projected permutation_map (at most one dim or the zero "
5180 "constant can appear in each result)");
5181 }
5182 continue;
5183 }
5184 if (!dim) {
5185 return emitOpError("requires a projected permutation_map (at most one "
5186 "dim or the zero constant can appear in each result)");
5187 }
5188 if (seen[dim.getPosition()]) {
5189 return emitOpError(
5190 "requires a permutation_map that is a permutation (found one dim "
5191 "used more than once)");
5192 }
5193 seen[dim.getPosition()] = true;
5194 }
5195 return success();
5196}
5197
5198static LogicalResult
5199verifyTransferOp(VectorTransferOpInterface op, ShapedType shapedType,
5200 VectorType vectorType, VectorType maskType,
5201 VectorType inferredMaskType, AffineMap permutationMap,
5202 ArrayAttr inBounds) {
5203 if (op->hasDiscardableAttr("masked")) {
5204 return op->emitOpError("masked attribute has been removed. "
5205 "Use in_bounds instead.");
5206 }
5207
5208 if (!llvm::isa<MemRefType, RankedTensorType>(shapedType))
5209 return op->emitOpError(
5210 "requires source to be a memref or ranked tensor type");
5211
5212 auto elementType = shapedType.getElementType();
5213 DataLayout dataLayout = DataLayout::closest(op);
5214 if (auto vectorElementType = llvm::dyn_cast<VectorType>(elementType)) {
5215 // Memref or tensor has vector element type.
5216 unsigned sourceVecSize =
5217 dataLayout.getTypeSizeInBits(vectorElementType.getElementType()) *
5218 vectorElementType.getShape().back();
5219 unsigned resultVecSize =
5220 dataLayout.getTypeSizeInBits(vectorType.getElementType()) *
5221 vectorType.getShape().back();
5222 if (resultVecSize % sourceVecSize != 0)
5223 return op->emitOpError(
5224 "requires the bitwidth of the minor 1-D vector to be an integral "
5225 "multiple of the bitwidth of the minor 1-D vector of the source");
5226
5227 unsigned sourceVecEltRank = vectorElementType.getRank();
5228 unsigned resultVecRank = vectorType.getRank();
5229 if (sourceVecEltRank > resultVecRank)
5230 return op->emitOpError(
5231 "requires source vector element and vector result ranks to match.");
5232 unsigned rankOffset = resultVecRank - sourceVecEltRank;
5233 // Check that permutation map results match 'rankOffset' of vector type.
5234 if (permutationMap.getNumResults() != rankOffset)
5235 return op->emitOpError("requires a permutation_map with result dims of "
5236 "the same rank as the vector type");
5237
5238 if (maskType)
5239 return op->emitOpError("does not support masks with vector element type");
5240 } else {
5241 // Memref or tensor has scalar element type.
5242 unsigned minorSize =
5243 vectorType.getRank() == 0 ? 1 : vectorType.getShape().back();
5244 unsigned resultVecSize =
5245 dataLayout.getTypeSizeInBits(vectorType.getElementType()) * minorSize;
5246 if (resultVecSize % dataLayout.getTypeSizeInBits(elementType) != 0)
5247 return op->emitOpError(
5248 "requires the bitwidth of the minor 1-D vector to be an integral "
5249 "multiple of the bitwidth of the source element type");
5250
5251 // Check that permutation map results match rank of vector type.
5252 if (permutationMap.getNumResults() != vectorType.getRank())
5253 return op->emitOpError("requires a permutation_map with result dims of "
5254 "the same rank as the vector type");
5255 }
5256
5257 if (permutationMap.getNumSymbols() != 0)
5258 return op->emitOpError("requires permutation_map without symbols");
5259
5260 if (permutationMap.getNumInputs() != shapedType.getRank())
5261 return op->emitOpError("requires a permutation_map with input dims of the "
5262 "same rank as the source type");
5263
5264 if (maskType && maskType != inferredMaskType)
5265 return op->emitOpError("inferred mask type (")
5266 << inferredMaskType << ") and mask operand type (" << maskType
5267 << ") don't match";
5268
5269 if (permutationMap.getNumResults() != static_cast<int64_t>(inBounds.size()))
5270 return op->emitOpError("expects the in_bounds attr of same rank "
5271 "as permutation_map results: ")
5272 << AffineMapAttr::get(permutationMap)
5273 << " vs inBounds of size: " << inBounds.size();
5274
5275 return success();
5276}
5277
5278static void printTransferAttrs(OpAsmPrinter &p, VectorTransferOpInterface op) {
5279 SmallVector<NamedAttribute> attrs(op->getDiscardableAttrs());
5280 // Elide in_bounds attribute if all dims are out-of-bounds.
5281 if (llvm::any_of(op.getInBoundsValues(), [](bool b) { return b; }))
5282 attrs.emplace_back(op.getInBoundsAttrName(), op.getInBounds());
5283 if (!op.getPermutationMap().isMinorIdentity())
5284 attrs.emplace_back(op.getPermutationMapAttrName(),
5285 AffineMapAttr::get(op.getPermutationMap()));
5286 llvm::sort(attrs);
5287 p.printOptionalAttrDict(attrs);
5288}
5289
5290void TransferReadOp::print(OpAsmPrinter &p) {
5291 p << " " << getBase() << "[" << getIndices() << "], " << getPadding();
5292 if (getMask())
5293 p << ", " << getMask();
5294 printTransferAttrs(p, *this);
5295 p << " : " << getShapedType() << ", " << getVectorType();
5296}
5297
5298VectorType mlir::vector::inferTransferOpMaskType(VectorType vecType,
5299 AffineMap permMap) {
5300 auto i1Type = IntegerType::get(permMap.getContext(), 1);
5301 AffineMap invPermMap = inversePermutation(compressUnusedDims(permMap));
5302 assert(invPermMap && "Inversed permutation map couldn't be computed");
5303 SmallVector<int64_t, 8> maskShape = invPermMap.compose(vecType.getShape());
5304
5305 // The MaskOp specification doesn't support 0-D vectors at the moment. Turn a
5306 // 0-D mask into a single-element 1-D mask.
5307 if (maskShape.empty())
5308 maskShape.push_back(1);
5309
5310 SmallVector<bool> scalableDims =
5311 applyPermutationMap(invPermMap, vecType.getScalableDims());
5312
5313 return VectorType::get(maskShape, i1Type, scalableDims);
5314}
5315
5316ParseResult TransferReadOp::parse(OpAsmParser &parser, OperationState &result) {
5317 auto &builder = parser.getBuilder();
5318 SMLoc typesLoc;
5324 // Parsing with support for paddingValue.
5325 if (parser.parseOperand(sourceInfo) ||
5327 parser.parseComma() || parser.parseOperand(paddingInfo))
5328 return failure();
5329 ParseResult hasMask = parser.parseOptionalComma();
5330 if (hasMask.succeeded()) {
5331 if (parser.parseOperand(maskInfo))
5332 return failure();
5333 }
5334 if (parser.parseOptionalAttrDict(result.attributes) ||
5335 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
5336 return failure();
5337 if (types.size() != 2)
5338 return parser.emitError(typesLoc, "requires two types");
5339 auto indexType = builder.getIndexType();
5340 auto shapedType = llvm::dyn_cast<ShapedType>(types[0]);
5341 if (!shapedType || !llvm::isa<MemRefType, RankedTensorType>(shapedType))
5342 return parser.emitError(typesLoc, "requires memref or ranked tensor type");
5343 VectorType vectorType = llvm::dyn_cast<VectorType>(types[1]);
5344 if (!vectorType)
5345 return parser.emitError(typesLoc, "requires vector type");
5346 auto permMapAttrName = TransferReadOp::getPermutationMapAttrName(result.name);
5347 Attribute permMapAttr = result.attributes.get(permMapAttrName);
5348 AffineMap permMap;
5349 if (!permMapAttr) {
5350 if (shapedType.getRank() <
5351 getEffectiveVectorRankForXferOp(shapedType, vectorType))
5352 return parser.emitError(typesLoc,
5353 "expected a custom permutation_map when "
5354 "rank(source) != rank(destination)");
5355 permMap = getTransferMinorIdentityMap(shapedType, vectorType);
5356 result.attributes.set(permMapAttrName, AffineMapAttr::get(permMap));
5357 } else {
5358 permMap = llvm::cast<AffineMapAttr>(permMapAttr).getValue();
5359 }
5360 auto inBoundsAttrName = TransferReadOp::getInBoundsAttrName(result.name);
5361 Attribute inBoundsAttr = result.attributes.get(inBoundsAttrName);
5362 if (!inBoundsAttr) {
5363 result.addAttribute(inBoundsAttrName,
5364 builder.getBoolArrayAttr(
5365 SmallVector<bool>(permMap.getNumResults(), false)));
5366 }
5367 if (parser.resolveOperand(sourceInfo, shapedType, result.operands) ||
5368 parser.resolveOperands(indexInfo, indexType, result.operands) ||
5369 parser.resolveOperand(paddingInfo, shapedType.getElementType(),
5370 result.operands))
5371 return failure();
5372 if (hasMask.succeeded()) {
5373 if (llvm::dyn_cast<VectorType>(shapedType.getElementType()))
5374 return parser.emitError(
5375 maskInfo.location, "does not support masks with vector element type");
5376 if (vectorType.getRank() != permMap.getNumResults()) {
5377 return parser.emitError(typesLoc,
5378 "expected the same rank for the vector and the "
5379 "results of the permutation map");
5380 }
5381 // Instead of adding the mask type as an op type, compute it based on the
5382 // vector type and the permutation map (to keep the type signature small).
5383 auto maskType = inferTransferOpMaskType(vectorType, permMap);
5384 if (parser.resolveOperand(maskInfo, maskType, result.operands))
5385 return failure();
5386 }
5387 result.addAttribute(TransferReadOp::getOperandSegmentSizeAttr(),
5388 builder.getDenseI32ArrayAttr(
5389 {1, static_cast<int32_t>(indexInfo.size()), 1,
5390 static_cast<int32_t>(hasMask.succeeded())}));
5391 return parser.addTypeToList(vectorType, result.types);
5392}
5393
5394LogicalResult TransferReadOp::verify() {
5395 // Consistency of elemental types in source and vector.
5396 ShapedType shapedType = getShapedType();
5397 VectorType vectorType = getVectorType();
5398 VectorType maskType = getMaskType();
5399 auto paddingType = getPadding().getType();
5400 auto permutationMap = getPermutationMap();
5401 VectorType inferredMaskType =
5402 maskType ? inferTransferOpMaskType(vectorType, permutationMap)
5403 : VectorType();
5404 auto sourceElementType = shapedType.getElementType();
5405
5406 if (static_cast<int64_t>(getIndices().size()) != shapedType.getRank())
5407 return emitOpError("requires ") << shapedType.getRank() << " indices";
5408
5409 if (failed(verifyTransferOp(cast<VectorTransferOpInterface>(getOperation()),
5410 shapedType, vectorType, maskType,
5411 inferredMaskType, permutationMap, getInBounds())))
5412 return failure();
5413
5414 if (auto sourceVectorElementType =
5415 llvm::dyn_cast<VectorType>(sourceElementType)) {
5416 // Source has vector element type.
5417 // Check that 'sourceVectorElementType' and 'paddingType' types match.
5418 if (sourceVectorElementType != paddingType)
5419 return emitOpError(
5420 "requires source element type and padding type to match.");
5421
5422 } else {
5423 // Check that 'paddingType' is valid to store in a vector type.
5424 if (!VectorType::isValidElementType(paddingType))
5425 return emitOpError("requires valid padding vector elemental type");
5426
5427 // Check that padding type and vector element types match.
5428 if (paddingType != sourceElementType)
5429 return emitOpError(
5430 "requires formal padding and source of the same elemental type");
5431 }
5432
5433 return verifyPermutationMap(permutationMap,
5434 [&](Twine t) { return emitOpError(t); });
5435}
5436
5437// MaskableOpInterface methods.
5438
5439/// Returns the mask type expected by this operation. Mostly used for
5440/// verification purposes. It requires the operation to be vectorized."
5441Type TransferReadOp::getExpectedMaskType() {
5442 return inferTransferOpMaskType(getVectorType(), getPermutationMap());
5443}
5444
5445//===----------------------------------------------------------------------===//
5446// TransferReadOp: VectorTransferOpInterface methods.
5447//===----------------------------------------------------------------------===//
5448VectorType TransferReadOp::getVectorType() {
5449 return cast<VectorType>(getVector().getType());
5450}
5451
5452template <typename TransferOp>
5453static bool isInBounds(TransferOp op, int64_t resultIdx, int64_t indicesIdx) {
5454 // TODO: support more aggressive createOrFold on:
5455 // op.getIndices()[indicesIdx] + vectorType < dim(op.getSource(), indicesIdx)
5456 if (op.getShapedType().isDynamicDim(indicesIdx))
5457 return false;
5458 // Scalable dimensions are `vscale` times larger at runtime, so the static
5459 // size is only a lower bound and cannot prove that the transfer fits.
5460 if (op.getVectorType().getScalableDims()[resultIdx])
5461 return false;
5462 Value index = op.getIndices()[indicesIdx];
5463 std::optional<int64_t> cstOp = getConstantIntValue(index);
5464 if (!cstOp.has_value())
5465 return false;
5466
5467 int64_t sourceSize = op.getShapedType().getDimSize(indicesIdx);
5468 int64_t vectorSize = op.getVectorType().getDimSize(resultIdx);
5469 // Largest index at which a full vector still fits. Computed as a subtraction
5470 // rather than adding to the index, which could overflow. The subtraction is
5471 // safe only because of the `isDynamicDim` early return above: `sourceSize`
5472 // is a real static extent here, never `ShapedType::kDynamic`, which is
5473 // `INT64_MIN` and would make this signed overflow.
5474 int64_t maxStart = sourceSize - vectorSize;
5475
5476 // `in_bounds` guarantees that the transfer stays within the source *including
5477 // its starting point*, so a negative index is not in bounds.
5478 return *cstOp >= 0 && *cstOp <= maxStart;
5479}
5480
5481template <typename TransferOp>
5482static LogicalResult foldTransferInBoundsAttribute(TransferOp op) {
5483 // TODO: support 0-d corner case.
5484 // TODO: Be less conservative.
5485 if (op.getTransferRank() == 0)
5486 return failure();
5487 AffineMap permutationMap = op.getPermutationMap();
5488 bool changed = false;
5489 SmallVector<bool, 4> newInBounds;
5490 newInBounds.reserve(op.getTransferRank());
5491 // Idxs of non-bcast dims - used when analysing bcast dims.
5492 SmallVector<unsigned> nonBcastDims;
5493
5494 // 1. Process non-broadcast dims
5495 for (unsigned i = 0; i < op.getTransferRank(); ++i) {
5496 // 1.1. Already marked as in-bounds, nothing to see here.
5497 if (op.isDimInBounds(i)) {
5498 newInBounds.push_back(true);
5499 continue;
5500 }
5501 // 1.2. Currently out-of-bounds, check whether we can statically determine
5502 // it is inBounds.
5503 bool inBounds = false;
5504 auto dimExpr = dyn_cast<AffineDimExpr>(permutationMap.getResult(i));
5505 if (dimExpr) {
5506 inBounds = isInBounds(op, /*resultIdx=*/i,
5507 /*indicesIdx=*/dimExpr.getPosition());
5508 nonBcastDims.push_back(i);
5509 }
5510
5511 newInBounds.push_back(inBounds);
5512 // We commit the pattern if it is "more inbounds".
5513 changed |= inBounds;
5514 }
5515
5516 // 2. Handle broadcast dims
5517 // If all non-broadcast dims are "in bounds", then all bcast dims should be
5518 // "in bounds" as well.
5519 bool allNonBcastDimsInBounds = llvm::all_of(
5520 nonBcastDims, [&newInBounds](unsigned idx) { return newInBounds[idx]; });
5521 if (allNonBcastDimsInBounds) {
5522 for (size_t idx : permutationMap.getBroadcastDims()) {
5523 changed |= !newInBounds[idx];
5524 newInBounds[idx] = true;
5525 }
5526 }
5527
5528 if (!changed)
5529 return failure();
5530 // OpBuilder is only used as a helper to build an I64ArrayAttr.
5531 OpBuilder b(op.getContext());
5532 op.setInBoundsAttr(b.getBoolArrayAttr(newInBounds));
5533 return success();
5534}
5535
5536template <typename TransferOp>
5537static LogicalResult foldTransferFullMask(TransferOp op) {
5538 auto mask = op.getMask();
5539 if (!mask)
5540 return failure();
5541
5543 return failure();
5544
5545 op.getMaskMutable().clear();
5546 return success();
5547}
5548
5549/// When the vector type is `vector<1xT>`, the permutation map is irrelevant:
5550/// the single vector lane always has iteration offset 0, so the element is at
5551/// `indices` regardless of which source dimension the map points at. Replace
5552/// with the minor identity to unblock lowering to vector.load / vector.store.
5553template <typename TransferOp>
5554static LogicalResult foldSize1TransferPermutationMap(TransferOp op) {
5555 VectorType vecType = op.getVectorType();
5556 if (vecType.getRank() != 1 || vecType.getShape()[0] != 1 ||
5557 vecType.isScalable())
5558 return failure();
5559
5560 AffineMap map = op.getPermutationMap();
5561 if (map.isMinorIdentity())
5562 return failure();
5563
5564 int64_t srcRank = op.getShapedType().getRank();
5565 if (srcRank < 1)
5566 return failure();
5567
5568 AffineMap minorIdentity =
5569 AffineMap::getMinorIdentityMap(srcRank, 1, op.getContext());
5570 op.setPermutationMapAttr(AffineMapAttr::get(minorIdentity));
5571 return success();
5572}
5573
5574/// ```
5575/// %w0 = vector.transfer_write %v0, %arg0[%c1, %c0] {in_bounds = [true, true]}
5576/// : vector<1x4xf32>, tensor<4x4xf32>
5577/// %0 = vector.transfer_read %w0[%c1, %c0], %cf0 {in_bounds = [true, true]}
5578/// : tensor<4x4xf32>, vector<1x4xf32>
5579/// ```
5580/// -> Folds into
5581/// ```
5582/// %v0
5583/// ```
5584static Value foldRAW(TransferReadOp readOp) {
5585 if (!llvm::isa<RankedTensorType>(readOp.getShapedType()))
5586 return {};
5587 auto defWrite = readOp.getBase().getDefiningOp<vector::TransferWriteOp>();
5588 while (defWrite) {
5589 if (checkSameValueRAW(defWrite, readOp))
5590 return defWrite.getVector();
5592 cast<VectorTransferOpInterface>(defWrite.getOperation()),
5593 cast<VectorTransferOpInterface>(readOp.getOperation())))
5594 break;
5595 defWrite = defWrite.getBase().getDefiningOp<vector::TransferWriteOp>();
5596 }
5597 return {};
5598}
5599
5600OpFoldResult TransferReadOp::fold(FoldAdaptor) {
5601 if (Value vec = foldRAW(*this))
5602 return vec;
5603 /// transfer_read(memrefcast) -> transfer_read
5604 if (succeeded(foldTransferInBoundsAttribute(*this)))
5605 return getResult();
5606 if (succeeded(foldTransferFullMask(*this)))
5607 return getResult();
5608 if (succeeded(foldSize1TransferPermutationMap(*this)))
5609 return getResult();
5610 if (succeeded(memref::foldMemRefCast(*this)))
5611 return getResult();
5612 if (succeeded(tensor::foldTensorCast(*this)))
5613 return getResult();
5614 return OpFoldResult();
5615}
5616
5617std::optional<SmallVector<int64_t, 4>>
5618TransferReadOp::getIterationShapeToUnroll() {
5619 return llvm::to_vector<4>(getVectorType().getShape());
5620}
5621
5622void TransferReadOp::getEffects(
5623 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
5624 &effects) {
5625 if (llvm::isa<MemRefType>(getShapedType()))
5626 effects.emplace_back(MemoryEffects::Read::get(), &getBaseMutable(),
5627 SideEffects::DefaultResource::get());
5628}
5629
5630Speculation::Speculatability TransferReadOp::getSpeculatability() {
5631 if (hasPureTensorSemantics())
5634}
5635
5636/// Given a projected permutation, inverse an affine map, making the unused dims
5637/// 0 in the result.
5638static AffineMap inverseWithUnusedDims(AffineMap map) {
5639 assert(map.isProjectedPermutation() &&
5640 "expected a projected permutation map");
5641 SmallVector<AffineExpr> results(map.getNumInputs(),
5643 for (auto [idx, result] : llvm::enumerate(map.getResults())) {
5644 // We should only have dim exprs because this is a projected permutation.
5645 int64_t pos = cast<AffineDimExpr>(result).getPosition();
5646 results[pos] = getAffineDimExpr(idx, map.getContext());
5647 }
5648 return AffineMap::get(/*dimCount=*/map.getNumResults(), /*symbolCount=*/0,
5649 results, map.getContext());
5650}
5651
5652namespace {
5653/// Store to load forwarding for transfer operations with permuation maps.
5654/// Even if the permutation maps are different we can still propagate the store
5655/// into the load if the size of the dimensions read and written match. Then we
5656/// can replace the transfer_read + transfer_write by vector.broadcast and
5657/// vector.transpose.
5658/// Example:
5659/// ```
5660/// %w0 = vector.transfer_write %v0, %arg0[%c0, %c0, %c0]
5661/// {in_bounds = [true, true],
5662/// permutation_map = affine_map<(d0, d1, d2) -> (d2, d1)>} :
5663/// vector<4x1xf32>, tensor<4x4x4xf32>
5664/// %r = vector.transfer_read %w0[%c0, %c0, %c0], %cf0
5665/// {in_bounds = [true, true, true, true],
5666/// permutation_map = affine_map<(d0, d1, d2) -> (d1, 0, d2, 0)>} :
5667/// tensor<4x4x4xf32>, vector<1x100x4x5xf32>
5668/// ```
5669/// To:
5670/// ```
5671/// %0 = vector.broadcast %arg1 : vector<4x1xf32> to vector<100x5x4x1xf32>
5672/// %r = vector.transpose %0, [3, 0, 2, 1] :
5673/// vector<100x5x4x1xf32> to vector<1x100x4x5xf32>
5674/// ```
5675struct TransferReadAfterWriteToBroadcast
5676 : public OpRewritePattern<TransferReadOp> {
5677 using Base::Base;
5678
5679 LogicalResult matchAndRewrite(TransferReadOp readOp,
5680 PatternRewriter &rewriter) const override {
5681 auto defWrite = readOp.getBase().getDefiningOp<vector::TransferWriteOp>();
5682 if (!defWrite)
5683 return failure();
5684 // Bail if we need an alias analysis.
5685 if (!readOp.hasPureTensorSemantics() || !defWrite.hasPureTensorSemantics())
5686 return failure();
5687 // Bail in the masked case (too complex atm and needed to properly account
5688 // for padding). This includes an enclosing vector.mask.
5689 // TODO: Support mask operands and reads nested in vector.mask while
5690 // properly accounting for padding.
5691 if (readOp.getMask() || defWrite.getMask() || readOp.isMasked())
5692 return failure();
5693 // If indices are not the same a shift may be required, bail.
5694 if (readOp.getIndices() != defWrite.getIndices())
5695 return failure();
5696 // Bail if we need a bounds analysis.
5697 if (readOp.hasOutOfBoundsDim() || defWrite.hasOutOfBoundsDim())
5698 return failure();
5699 // TODO: If the written transfer chunk is a superset of the read transfer
5700 // chunk we could do an extract_strided_slice.
5701 if (readOp.getTransferChunkAccessed() !=
5702 defWrite.getTransferChunkAccessed())
5703 return failure();
5704 // WriteMap: tensor -> w_vec
5705 // ReadMap: tensor -> r_vec
5706 //
5707 // inv(WriteMap): w_vec -> tensor
5708 // inv(WriteMap) o ReadMap: w_vec -> r_vec
5709 AffineMap readMap = readOp.getPermutationMap();
5710 AffineMap writeMap = defWrite.getPermutationMap();
5711 AffineMap invWriteMap = inverseWithUnusedDims(writeMap);
5712 AffineMap composedMap = readMap.compose(invWriteMap);
5713 // If there are any unused dims in the composedMap, we have to drop some
5714 // unit dims from the written vector before we can do transpose(broadcast).
5715 // TODO: Support this case.
5716 if (getUnusedDimsBitVector(composedMap).any())
5717 return failure();
5718 // readVec = transpose(broadcast(writeVec))
5719 //
5720 // Build a transpose permutation for the above transpose operation.
5721 //
5722 // Treat the composed map as having extra leading dimensions which are
5723 // the broadcasted dimensions, and treat the zeros as these new broadcasted
5724 // dimensions.
5725 SmallVector<unsigned> broadcastedDims = composedMap.getBroadcastDims();
5726 int64_t numBroadcastedDims = broadcastedDims.size();
5727 auto invPerm = llvm::to_vector_of<int64_t>(broadcastedDims);
5728 invPerm.resize(composedMap.getNumResults());
5729 for (auto [idx, expr] : llvm::enumerate(composedMap.getResults())) {
5730 if (auto dim = dyn_cast<AffineDimExpr>(expr)) {
5731 int64_t effectiveDim = dim.getPosition() + numBroadcastedDims;
5732 invPerm[effectiveDim] = idx;
5733 }
5734 }
5735 // Applying the inverse permutation on the readVecTy will give us the
5736 // broadcast result type.
5737 VectorType readVecTy = readOp.getVectorType();
5738 SmallVector<int64_t> permutation = invertPermutationVector(invPerm);
5739 auto broadcastedVecTy =
5740 VectorType::get(applyPermutation(readVecTy.getShape(), invPerm),
5741 readVecTy.getElementType(),
5742 applyPermutation(readVecTy.getScalableDims(), invPerm));
5743 // Build the transpose(broadcast) transformation.
5744 Value vec = defWrite.getVector();
5745 Location loc = readOp.getLoc();
5746 vec = vector::BroadcastOp::create(rewriter, loc, broadcastedVecTy, vec);
5747 rewriter.replaceOpWithNewOp<vector::TransposeOp>(readOp, vec, permutation);
5748 return success();
5749 }
5750};
5751} // namespace
5752
5753void TransferReadOp::getCanonicalizationPatterns(RewritePatternSet &results,
5754 MLIRContext *context) {
5755 results.add<TransferReadAfterWriteToBroadcast>(context);
5756}
5757
5758FailureOr<std::optional<SmallVector<Value>>>
5759TransferReadOp::bubbleDownCasts(OpBuilder &builder) {
5760 if (!hasPureBufferSemantics())
5761 return failure();
5763 getResult());
5764}
5765
5766//===----------------------------------------------------------------------===//
5767// TransferWriteOp
5768//===----------------------------------------------------------------------===//
5769
5770/// 1. Builder with type inference.
5771void TransferWriteOp::build(OpBuilder &builder, OperationState &result,
5772 Value vector, Value dest, ValueRange indices,
5773 AffineMapAttr permutationMapAttr,
5774 /*optional*/ Value mask,
5775 /*optional*/ ArrayAttr inBoundsAttr) {
5776 Type resultType = llvm::dyn_cast<RankedTensorType>(dest.getType());
5777 build(builder, result, resultType, vector, dest, indices, permutationMapAttr,
5778 mask, inBoundsAttr);
5779}
5780
5781/// 2. Builder with type inference that sets an empty mask (variant with attrs).
5782void TransferWriteOp::build(OpBuilder &builder, OperationState &result,
5783 Value vector, Value dest, ValueRange indices,
5784 AffineMapAttr permutationMapAttr,
5785 /*optional*/ ArrayAttr inBoundsAttr) {
5786 build(builder, result, vector, dest, indices, permutationMapAttr,
5787 /*mask=*/Value(), inBoundsAttr);
5788}
5789
5790/// 3. Builder with type inference that sets an empty mask (variant without
5791/// attrs). If `permutationMap` is null, a minor identity map is used.
5792void TransferWriteOp::build(OpBuilder &builder, OperationState &result,
5793 Value vector, Value dest, ValueRange indices,
5794 AffineMap permutationMap,
5795 std::optional<ArrayRef<bool>> inBounds) {
5796 if (!permutationMap)
5797 permutationMap =
5798 getTransferMinorIdentityMap(llvm::cast<ShapedType>(dest.getType()),
5799 llvm::cast<VectorType>(vector.getType()));
5800 auto permutationMapAttr = AffineMapAttr::get(permutationMap);
5801 auto inBoundsAttr =
5802 (inBounds && !inBounds.value().empty())
5803 ? builder.getBoolArrayAttr(inBounds.value())
5804 : builder.getBoolArrayAttr(SmallVector<bool>(
5805 llvm::cast<VectorType>(vector.getType()).getRank(), false));
5806 build(builder, result, vector, dest, indices, permutationMapAttr,
5807 /*mask=*/Value(), inBoundsAttr);
5808}
5809
5810/// 4. Builder with type inference that sets an empty mask and sets permutation
5811/// map to 'getMinorIdentityMap'.
5812void TransferWriteOp::build(OpBuilder &builder, OperationState &result,
5813 Value vector, Value dest, ValueRange indices,
5814 std::optional<ArrayRef<bool>> inBounds) {
5815 build(builder, result, vector, dest, indices, /*permutationMap=*/AffineMap(),
5816 inBounds);
5817}
5818
5819ParseResult TransferWriteOp::parse(OpAsmParser &parser,
5820 OperationState &result) {
5821 auto &builder = parser.getBuilder();
5822 SMLoc typesLoc;
5823 OpAsmParser::UnresolvedOperand vectorInfo, sourceInfo;
5824 SmallVector<OpAsmParser::UnresolvedOperand, 8> indexInfo;
5825 SmallVector<Type, 2> types;
5826 OpAsmParser::UnresolvedOperand maskInfo;
5827 if (parser.parseOperand(vectorInfo) || parser.parseComma() ||
5828 parser.parseOperand(sourceInfo) ||
5829 parser.parseOperandList(indexInfo, OpAsmParser::Delimiter::Square))
5830 return failure();
5831 ParseResult hasMask = parser.parseOptionalComma();
5832 if (hasMask.succeeded() && parser.parseOperand(maskInfo))
5833 return failure();
5834 if (parser.parseOptionalAttrDict(result.attributes) ||
5835 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
5836 return failure();
5837 if (types.size() != 2)
5838 return parser.emitError(typesLoc, "requires two types");
5839 auto indexType = builder.getIndexType();
5840 VectorType vectorType = llvm::dyn_cast<VectorType>(types[0]);
5841 if (!vectorType)
5842 return parser.emitError(typesLoc, "requires vector type");
5843 ShapedType shapedType = llvm::dyn_cast<ShapedType>(types[1]);
5844 if (!shapedType || !llvm::isa<MemRefType, RankedTensorType>(shapedType))
5845 return parser.emitError(typesLoc, "requires memref or ranked tensor type");
5846 auto permMapAttrName =
5847 TransferWriteOp::getPermutationMapAttrName(result.name);
5848 auto permMapAttr = result.attributes.get(permMapAttrName);
5849 AffineMap permMap;
5850 if (!permMapAttr) {
5851 if (shapedType.getRank() <
5852 getEffectiveVectorRankForXferOp(shapedType, vectorType))
5853 return parser.emitError(typesLoc,
5854 "expected a custom permutation_map when "
5855 "rank(source) != rank(destination)");
5856 permMap = getTransferMinorIdentityMap(shapedType, vectorType);
5857 result.attributes.set(permMapAttrName, AffineMapAttr::get(permMap));
5858 } else {
5859 permMap = llvm::cast<AffineMapAttr>(permMapAttr).getValue();
5860 }
5861 auto inBoundsAttrName = TransferWriteOp::getInBoundsAttrName(result.name);
5862 Attribute inBoundsAttr = result.attributes.get(inBoundsAttrName);
5863 if (!inBoundsAttr) {
5864 result.addAttribute(inBoundsAttrName,
5865 builder.getBoolArrayAttr(
5866 SmallVector<bool>(permMap.getNumResults(), false)));
5867 }
5868 if (parser.resolveOperand(vectorInfo, vectorType, result.operands) ||
5869 parser.resolveOperand(sourceInfo, shapedType, result.operands) ||
5870 parser.resolveOperands(indexInfo, indexType, result.operands))
5871 return failure();
5872 if (hasMask.succeeded()) {
5873 if (llvm::dyn_cast<VectorType>(shapedType.getElementType()))
5874 return parser.emitError(
5875 maskInfo.location, "does not support masks with vector element type");
5876 if (vectorType.getRank() != permMap.getNumResults()) {
5877 return parser.emitError(typesLoc,
5878 "expected the same rank for the vector and the "
5879 "results of the permutation map");
5880 }
5881 auto maskType = inferTransferOpMaskType(vectorType, permMap);
5882 if (parser.resolveOperand(maskInfo, maskType, result.operands))
5883 return failure();
5884 }
5885 result.addAttribute(TransferWriteOp::getOperandSegmentSizeAttr(),
5886 builder.getDenseI32ArrayAttr(
5887 {1, 1, static_cast<int32_t>(indexInfo.size()),
5888 static_cast<int32_t>(hasMask.succeeded())}));
5889 return failure(llvm::isa<RankedTensorType>(shapedType) &&
5890 parser.addTypeToList(shapedType, result.types));
5891}
5892
5893void TransferWriteOp::print(OpAsmPrinter &p) {
5894 p << " " << getVector() << ", " << getBase() << "[" << getIndices() << "]";
5895 if (getMask())
5896 p << ", " << getMask();
5897 printTransferAttrs(p, *this);
5898 p << " : " << getVectorType() << ", " << getShapedType();
5899}
5900
5901LogicalResult TransferWriteOp::verify() {
5902 // Consistency of elemental types in shape and vector.
5903 ShapedType shapedType = getShapedType();
5904 VectorType vectorType = getVectorType();
5905 VectorType maskType = getMaskType();
5906 auto permutationMap = getPermutationMap();
5907 VectorType inferredMaskType =
5908 maskType ? inferTransferOpMaskType(vectorType, permutationMap)
5909 : VectorType();
5910
5911 if (llvm::size(getIndices()) != shapedType.getRank())
5912 return emitOpError("requires ") << shapedType.getRank() << " indices";
5913
5914 // We do not allow broadcast dimensions on TransferWriteOps for the moment,
5915 // as the semantics is unclear. This can be revisited later if necessary.
5916 if (hasBroadcastDim())
5917 return emitOpError("should not have broadcast dimensions");
5918
5919 if (failed(verifyTransferOp(cast<VectorTransferOpInterface>(getOperation()),
5920 shapedType, vectorType, maskType,
5921 inferredMaskType, permutationMap, getInBounds())))
5922 return failure();
5923
5924 return verifyPermutationMap(permutationMap,
5925 [&](Twine t) { return emitOpError(t); });
5926}
5927
5928//===----------------------------------------------------------------------===//
5929// TransferWriteOp: MaskableOpInterface methods.
5930//===----------------------------------------------------------------------===//
5931
5932/// Returns the mask type expected by this operation. Mostly used for
5933/// verification purposes.
5934Type TransferWriteOp::getExpectedMaskType() {
5935 return inferTransferOpMaskType(getVectorType(), getPermutationMap());
5936}
5937
5938//===----------------------------------------------------------------------===//
5939// TransferWriteOp: VectorTransferOpInterface methods.
5940//===----------------------------------------------------------------------===//
5941Value TransferWriteOp::getVector() { return getOperand(0); }
5942VectorType TransferWriteOp::getVectorType() {
5943 return cast<VectorType>(getValueToStore().getType());
5944}
5945
5946//===----------------------------------------------------------------------===//
5947// TransferWriteOp: fold methods.
5948//===----------------------------------------------------------------------===//
5949/// Fold:
5950/// ```
5951/// %t1 = ...
5952/// %v = vector.transfer_read %t0[%c0...], {in_bounds = [true...]} :
5953/// tensor<static_sizesxf32>, vector<static_sizesxf32>
5954/// %t2 = vector.transfer_write %v, %t1[%c0...] {in_bounds = [true...]} :
5955/// vector<static_sizesxf32>, tensor<static_sizesxf32>
5956/// ```
5957///
5958/// into:
5959///
5960/// ```
5961/// %t0
5962/// ```
5963///
5964/// The producer of t1 may or may not be DCE'd depending on whether it is a
5965/// block argument or has side effects.
5966static LogicalResult foldReadInitWrite(TransferWriteOp write,
5967 ArrayRef<Attribute>,
5968 SmallVectorImpl<OpFoldResult> &results) {
5969 // TODO: support 0-d corner case.
5970 if (write.getTransferRank() == 0)
5971 return failure();
5972 auto rankedTensorType =
5973 llvm::dyn_cast<RankedTensorType>(write.getBase().getType());
5974 // If not operating on tensors, bail.
5975 if (!rankedTensorType)
5976 return failure();
5977 // If no read, bail.
5978 auto read = write.getVector().getDefiningOp<vector::TransferReadOp>();
5979 if (!read)
5980 return failure();
5981 // TODO: support 0-d corner case.
5982 if (read.getTransferRank() == 0)
5983 return failure();
5984 // For now, only accept minor identity. Future: composition is minor identity.
5985 if (!read.getPermutationMap().isMinorIdentity() ||
5986 !write.getPermutationMap().isMinorIdentity())
5987 return failure();
5988 // Bail on mismatching ranks.
5989 if (read.getTransferRank() != write.getTransferRank())
5990 return failure();
5991 // Bail on potential out-of-bounds accesses.
5992 if (read.hasOutOfBoundsDim() || write.hasOutOfBoundsDim())
5993 return failure();
5994 // Masked transfers have padding/select semantics and are not identity folds.
5995 if (read.getMask() || write.getMask())
5996 return failure();
5997 // Tensor types must be the same.
5998 if (read.getBase().getType() != rankedTensorType)
5999 return failure();
6000 // Vector types must be the same.
6001 if (read.getVectorType() != write.getVectorType())
6002 return failure();
6003 // Vector and Tensor shapes must match.
6004 if (read.getVectorType().getShape() != rankedTensorType.getShape())
6005 return failure();
6006 // If any index is nonzero.
6007 auto isNotConstantZero = [](Value v) {
6008 auto cstOp = getConstantIntValue(v);
6009 return !cstOp.has_value() || cstOp.value() != 0;
6010 };
6011 if (llvm::any_of(read.getIndices(), isNotConstantZero) ||
6012 llvm::any_of(write.getIndices(), isNotConstantZero))
6013 return failure();
6014 // Success.
6015 results.push_back(read.getBase());
6016 return success();
6017}
6018
6019static bool checkSameValueWAR(vector::TransferReadOp read,
6020 vector::TransferWriteOp write) {
6021 return read.getBase() == write.getBase() &&
6022 read.getIndices() == write.getIndices() &&
6023 read.getPermutationMap() == write.getPermutationMap() &&
6024 read.getVectorType() == write.getVectorType() && !read.getMask() &&
6025 !write.getMask();
6026}
6027/// Fold transfer_write write after read:
6028/// ```
6029/// %t0 = ...
6030/// %v = vector.transfer_read %t0[%c0...] :
6031/// tensor<static_sizesxf32>, vector<static_sizesxf32>
6032/// %t1 = vector.transfer_write %v, %t0[%c0...] :
6033/// vector<static_sizesxf32>, tensor<static_sizesxf32>
6034/// ```
6035///
6036/// into:
6037///
6038/// ```
6039/// %t0
6040/// ```
6041static LogicalResult foldWAR(TransferWriteOp write,
6042 SmallVectorImpl<OpFoldResult> &results) {
6043 if (!llvm::isa<RankedTensorType>(write.getBase().getType()))
6044 return failure();
6045 auto read = write.getVector().getDefiningOp<vector::TransferReadOp>();
6046 if (!read)
6047 return failure();
6048
6049 if (!checkSameValueWAR(read, write))
6050 return failure();
6051 results.push_back(read.getBase());
6052 return success();
6053}
6054
6055LogicalResult TransferWriteOp::fold(FoldAdaptor adaptor,
6056 SmallVectorImpl<OpFoldResult> &results) {
6057 if (succeeded(foldReadInitWrite(*this, adaptor.getOperands(), results)))
6058 return success();
6059 if (succeeded(foldWAR(*this, results)))
6060 return success();
6061 if (succeeded(foldTransferInBoundsAttribute(*this)))
6062 return success();
6063 if (succeeded(foldTransferFullMask(*this)))
6064 return success();
6065 if (succeeded(foldSize1TransferPermutationMap(*this)))
6066 return success();
6067 return memref::foldMemRefCast(*this);
6068}
6069
6070//===----------------------------------------------------------------------===//
6071// TransferWriteOp: other methods.
6072//===----------------------------------------------------------------------===//
6073std::optional<SmallVector<int64_t, 4>>
6074TransferWriteOp::getIterationShapeToUnroll() {
6075 return llvm::to_vector<4>(getVectorType().getShape());
6076}
6077
6078void TransferWriteOp::getEffects(
6079 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
6080 &effects) {
6081 if (llvm::isa<MemRefType>(getShapedType()))
6082 effects.emplace_back(MemoryEffects::Write::get(), &getBaseMutable(),
6083 SideEffects::DefaultResource::get());
6084}
6085
6086Speculation::Speculatability TransferWriteOp::getSpeculatability() {
6087 if (hasPureTensorSemantics())
6090}
6091
6092namespace {
6093/// Remove dead transfer write from the SSA chain so that it an be eliminated by
6094/// DCE
6095/// ```
6096/// %w0 = vector.transfer_write %v0, %arg0[%c1, %c0] {in_bounds = [true, true]}
6097/// : vector<1x4xf32>, tensor<4x4xf32>
6098/// %w1 = vector.transfer_write %v0, %w0[%c2, %c0] {in_bounds = [true, true]}
6099/// : vector<1x4xf32>, tensor<4x4xf32>
6100/// %w2 = vector.transfer_write %v1, %w1[%c1, %c0] {in_bounds = [true, true]}
6101/// : vector<1x4xf32>, tensor<4x4xf32>
6102/// ```
6103///
6104/// into:
6105///
6106/// ```
6107/// %w0 = vector.transfer_write %v0, %arg0[%c1, %c0] {in_bounds = [true, true]}
6108/// : vector<1x4xf32>, tensor<4x4xf32>
6109/// %w1 = vector.transfer_write %v0, %arg0[%c2, %c0] {in_bounds = [true, true]}
6110/// : vector<1x4xf32>, tensor<4x4xf32>
6111/// %w2 = vector.transfer_write %v1, %w1[%c1, %c0] {in_bounds = [true, true]}
6112/// : vector<1x4xf32>, tensor<4x4xf32>
6113/// ```
6114///
6115/// `%w0 = vector.transfer_write` op will be removed by DCE if it doesn't have
6116/// any other uses.
6117class FoldWaw final : public OpRewritePattern<TransferWriteOp> {
6118public:
6119 using Base::Base;
6120 LogicalResult matchAndRewrite(TransferWriteOp writeOp,
6121 PatternRewriter &rewriter) const override {
6122 if (!llvm::isa<RankedTensorType>(writeOp.getShapedType()))
6123 return failure();
6124 vector::TransferWriteOp writeToModify = writeOp;
6125
6126 auto defWrite = writeOp.getBase().getDefiningOp<vector::TransferWriteOp>();
6127 while (defWrite) {
6128 if (checkSameValueWAW(writeOp, defWrite)) {
6129 rewriter.modifyOpInPlace(writeToModify, [&]() {
6130 writeToModify.getBaseMutable().assign(defWrite.getBase());
6131 });
6132 return success();
6133 }
6135 cast<VectorTransferOpInterface>(defWrite.getOperation()),
6136 cast<VectorTransferOpInterface>(writeOp.getOperation())))
6137 break;
6138 // If the previous write op doesn't have any other use we an safely look
6139 // at the previous store to see if it can be removed.
6140 if (!defWrite->hasOneUse())
6141 break;
6142 writeToModify = defWrite;
6143 defWrite = defWrite.getBase().getDefiningOp<vector::TransferWriteOp>();
6144 }
6145 return failure();
6146 }
6147};
6148
6149/// Rewrite tensor::ExtractSliceOp(vector::TransferWriteOp) to
6150/// vector::TransferWriteOp(tensor::ExtractSliceOp) if the full slice is
6151/// overwritten and inserted into another tensor. After this rewrite, the
6152/// operations bufferize in-place since all of them work on the same slice.
6153///
6154/// For example:
6155/// ```mlir
6156/// %0 = vector.transfer_write %vec, %init_tensor[%c0, %c0]
6157/// : vector<8x16xf32>, tensor<8x16xf32>
6158/// %1 = tensor.extract_slice %0[0, 0] [%sz0, %sz1] [1, 1]
6159/// : tensor<8x16xf32> to tensor<?x?xf32>
6160/// %r = tensor.insert_slice %1 into %iter_arg[%iv0, %iv1] [%sz0, %sz1] [1, 1]
6161/// : tensor<?x?xf32> into tensor<27x37xf32>
6162/// ```
6163/// folds to
6164/// ```mlir
6165/// %0 = tensor.extract_slice %iter_arg[%iv0, %iv1] [%sz0, %sz1] [1, 1]
6166/// : tensor<27x37xf32> to tensor<?x?xf32>
6167/// %1 = vector.transfer_write %vec, %0[%c0, %c0]
6168/// : vector<8x16xf32>, tensor<?x?xf32>
6169/// %r = tensor.insert_slice %1 into %iter_arg[%iv0, %iv1] [%sz0, %sz1] [1, 1]
6170/// : tensor<?x?xf32> into tensor<27x37xf32>
6171/// ```
6172struct SwapExtractSliceOfTransferWrite
6173 : public OpRewritePattern<tensor::InsertSliceOp> {
6174public:
6175 using Base::Base;
6176
6177 LogicalResult matchAndRewrite(tensor::InsertSliceOp insertOp,
6178 PatternRewriter &rewriter) const override {
6179 if (!insertOp.hasUnitStride())
6180 return failure();
6181 auto extractOp =
6182 insertOp.getSource().getDefiningOp<tensor::ExtractSliceOp>();
6183 if (!extractOp || !extractOp.hasUnitStride() || !extractOp->hasOneUse())
6184 return failure();
6185 auto transferOp = extractOp.getSource().getDefiningOp<TransferWriteOp>();
6186 if (!transferOp || !transferOp->hasOneUse())
6187 return failure();
6188
6189 // Fail if vector::TransferWriteOp or tensor::ExtractSliceOp is
6190 // rank-reducing.
6191 if (insertOp.getSourceType().getRank() != transferOp.getTransferRank()) {
6192 return rewriter.notifyMatchFailure(insertOp,
6193 "use-def chain is rank-reducing");
6194 }
6195
6196 // Fail if tensor::ExtractSliceOp has non-zero offset.
6197 if (!extractOp.hasZeroOffset()) {
6198 return rewriter.notifyMatchFailure(insertOp,
6199 "ExtractSliceOp has non-zero offset");
6200 }
6201
6202 // Fail if tensor::TransferWriteOp has non-zero offset.
6203 if (!llvm::all_of(transferOp.getIndices(), [](Value value) {
6204 return getConstantIntValue(value) == static_cast<int64_t>(0);
6205 })) {
6206 return rewriter.notifyMatchFailure(insertOp,
6207 "TranferWriteOp has non-zero offset");
6208 }
6209
6210 // Fail if tensor::ExtractSliceOp and tensor::InsertSliceOp sizes differ.
6211 if (insertOp.getMixedSizes().size() != extractOp.getMixedSizes().size()) {
6212 return rewriter.notifyMatchFailure(
6213 insertOp, "InsertSliceOp and ExtractSliceOp ranks differ");
6214 }
6215
6216 for (auto [insertSize, extractSize] :
6217 llvm::zip_equal(insertOp.getMixedSizes(), extractOp.getMixedSizes())) {
6218 if (!isEqualConstantIntOrValue(insertSize, extractSize)) {
6219 return rewriter.notifyMatchFailure(
6220 insertOp, "InsertSliceOp and ExtractSliceOp sizes differ");
6221 }
6222 }
6223
6224 // Fail if the vector::TransferWriteOp may not overwrite the full tensor.
6225 assert(transferOp.getVectorType().hasStaticShape() &&
6226 "expected vector to have a static shape");
6227 ArrayRef<int64_t> vectorShape = transferOp.getVectorType().getShape();
6228 SmallVector<int64_t> resultShape = applyPermutationMap(
6229 transferOp.getPermutationMap(), transferOp.getShapedType().getShape());
6230 if (transferOp.getMask() || !vectorShape.equals(resultShape)) {
6231 return rewriter.notifyMatchFailure(
6232 insertOp, "TransferWriteOp may not write the full tensor.");
6233 }
6234
6235 // Swap the tensor::ExtractSliceOp in front of the vector::TransferWriteOp.
6236 // Set all in_bounds to false and let the folder infer them.
6237 SmallVector<bool> newInBounds(vectorShape.size(), false);
6238 auto newExtractOp = tensor::ExtractSliceOp::create(
6239 rewriter, extractOp.getLoc(), insertOp.getSourceType(),
6240 insertOp.getDest(), insertOp.getMixedOffsets(),
6241 insertOp.getMixedSizes(), insertOp.getMixedStrides());
6242 auto newTransferWriteOp = TransferWriteOp::create(
6243 rewriter, transferOp.getLoc(), transferOp.getVector(),
6244 newExtractOp.getResult(), transferOp.getIndices(),
6245 transferOp.getPermutationMapAttr(),
6246 rewriter.getBoolArrayAttr(newInBounds));
6247 rewriter.modifyOpInPlace(insertOp, [&]() {
6248 insertOp.getSourceMutable().assign(newTransferWriteOp.getResult());
6249 });
6250 return success();
6251 }
6252};
6253
6254} // namespace
6255
6256void TransferWriteOp::getCanonicalizationPatterns(RewritePatternSet &results,
6257 MLIRContext *context) {
6258 results.add<FoldWaw, SwapExtractSliceOfTransferWrite>(context);
6259}
6260
6261FailureOr<std::optional<SmallVector<Value>>>
6262TransferWriteOp::bubbleDownCasts(OpBuilder &builder) {
6263 if (!hasPureBufferSemantics())
6264 return failure();
6266 ValueRange());
6267}
6268
6269//===----------------------------------------------------------------------===//
6270// LoadOp
6271//===----------------------------------------------------------------------===//
6272
6273static ParseResult parseBoolAttr(OpAsmParser &parser, BoolAttr &result) {
6274 Attribute attr;
6275 if (parser.parseAttribute(attr))
6276 return failure();
6277 result = dyn_cast<BoolAttr>(attr);
6278 if (!result)
6279 return parser.emitError(parser.getCurrentLocation(),
6280 "expected boolean attribute");
6281 return success();
6282}
6283
6284static void printBoolAttr(OpAsmPrinter &printer, Operation *, BoolAttr attr) {
6285 printer.printAttribute(attr);
6286}
6287
6288static LogicalResult verifyLoadStoreMemRefLayout(Operation *op,
6289 VectorType vecTy,
6290 MemRefType memRefTy) {
6291 // If rank==0 or size==1 it's equivalent to scalar load/store, so we don't
6292 // need any strides limitations.
6293 if (!vecTy.isScalable() &&
6294 (vecTy.getRank() == 0 || vecTy.getNumElements() == 1))
6295 return success();
6296
6297 if (!memRefTy.isLastDimUnitStride())
6298 return op->emitOpError("most minor memref dim must have unit stride");
6299 return success();
6300}
6301
6302LogicalResult vector::LoadOp::verify() {
6303 VectorType resVecTy = getVectorType();
6304 MemRefType memRefTy = getMemRefType();
6305
6306 if (failed(verifyLoadStoreMemRefLayout(*this, resVecTy, memRefTy)))
6307 return failure();
6308
6309 // Negative strides are not supported on vector.load. The lowering to LLVM
6310 // emits arithmetic operations (e.g., GEP, mul) with nuw flags that assume
6311 // non-negative strides to avoid undefined behavior.
6312 if (memref::hasNegativeStaticStride(memRefTy))
6313 return emitOpError("memref strides must be non-negative");
6314
6315 if (memRefTy.getRank() < resVecTy.getRank())
6316 return emitOpError(
6317 "destination memref has lower rank than the result vector");
6318
6319 // Checks for vector memrefs.
6320 Type memElemTy = memRefTy.getElementType();
6321 if (auto memVecTy = llvm::dyn_cast<VectorType>(memElemTy)) {
6322 if (memVecTy != resVecTy)
6323 return emitOpError("base memref and result vector types should match");
6324 memElemTy = memVecTy.getElementType();
6325 }
6326
6327 if (resVecTy.getElementType() != memElemTy)
6328 return emitOpError("base and result element types should match");
6329 if (llvm::size(getIndices()) != memRefTy.getRank())
6330 return emitOpError("requires ") << memRefTy.getRank() << " indices";
6331 return success();
6332}
6333
6334OpFoldResult LoadOp::fold(FoldAdaptor) {
6335 if (succeeded(memref::foldMemRefCast(*this)))
6336 return getResult();
6337 return OpFoldResult();
6338}
6339
6340std::optional<SmallVector<int64_t, 4>> LoadOp::getIterationShapeToUnroll() {
6341 return llvm::to_vector<4>(getVectorType().getShape());
6342}
6343
6344FailureOr<std::optional<SmallVector<Value>>>
6345LoadOp::bubbleDownCasts(OpBuilder &builder) {
6347 getResult());
6348}
6349
6350//===----------------------------------------------------------------------===//
6351// StoreOp
6352//===----------------------------------------------------------------------===//
6353
6354LogicalResult vector::StoreOp::verify() {
6355 VectorType valueVecTy = getVectorType();
6356 MemRefType memRefTy = getMemRefType();
6357
6358 if (failed(verifyLoadStoreMemRefLayout(*this, valueVecTy, memRefTy)))
6359 return failure();
6360
6361 // Negative strides are not supported on vector.store. The lowering to LLVM
6362 // emits arithmetic operations (e.g., GEP, mul) with nuw flags that assume
6363 // non-negative strides to avoid undefined behavior.
6364 if (memref::hasNegativeStaticStride(memRefTy))
6365 return emitOpError("memref strides must be non-negative");
6366
6367 if (memRefTy.getRank() < valueVecTy.getRank())
6368 return emitOpError("source memref has lower rank than the vector to store");
6369
6370 // Checks for vector memrefs.
6371 Type memElemTy = memRefTy.getElementType();
6372 if (auto memVecTy = llvm::dyn_cast<VectorType>(memElemTy)) {
6373 if (memVecTy != valueVecTy)
6374 return emitOpError(
6375 "base memref and valueToStore vector types should match");
6376 memElemTy = memVecTy.getElementType();
6377 }
6378
6379 if (valueVecTy.getElementType() != memElemTy)
6380 return emitOpError("base and valueToStore element type should match");
6381 if (llvm::size(getIndices()) != memRefTy.getRank())
6382 return emitOpError("requires ") << memRefTy.getRank() << " indices";
6383 return success();
6384}
6385
6386LogicalResult StoreOp::fold(FoldAdaptor adaptor,
6387 SmallVectorImpl<OpFoldResult> &results) {
6388 return memref::foldMemRefCast(*this);
6389}
6390
6391std::optional<SmallVector<int64_t, 4>> StoreOp::getIterationShapeToUnroll() {
6392 return llvm::to_vector<4>(getVectorType().getShape());
6393}
6394
6395FailureOr<std::optional<SmallVector<Value>>>
6396StoreOp::bubbleDownCasts(OpBuilder &builder) {
6398 ValueRange());
6399}
6400
6401//===----------------------------------------------------------------------===//
6402// MaskedLoadOp
6403//===----------------------------------------------------------------------===//
6404
6405LogicalResult MaskedLoadOp::verify() {
6406 VectorType maskVType = getMaskVectorType();
6407 VectorType passVType = getPassThruVectorType();
6408 VectorType resVType = getVectorType();
6409 MemRefType memType = getMemRefType();
6410
6411 if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
6412 return failure();
6413
6414 // Negative strides are not supported on vector.maskedload. The lowering to
6415 // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
6416 // assume non-negative strides to avoid undefined behavior.
6418 return emitOpError("memref strides must be non-negative");
6419
6420 if (failed(
6421 verifyElementTypesMatch(*this, memType, resVType, "base", "result")))
6422 return failure();
6423 if (llvm::size(getIndices()) != memType.getRank())
6424 return emitOpError("requires ") << memType.getRank() << " indices";
6425 if (resVType.getShape() != maskVType.getShape())
6426 return emitOpError("expected result shape to match mask shape");
6427 if (resVType != passVType)
6428 return emitOpError("expected pass_thru of same type as result type");
6429 return success();
6430}
6431
6432namespace {
6433class MaskedLoadFolder final : public OpRewritePattern<MaskedLoadOp> {
6434public:
6435 using Base::Base;
6436 LogicalResult matchAndRewrite(MaskedLoadOp load,
6437 PatternRewriter &rewriter) const override {
6438 switch (getMaskFormat(load.getMask())) {
6440 rewriter.replaceOpWithNewOp<vector::LoadOp>(
6441 load, load.getType(), load.getBase(), load.getIndices());
6442 return success();
6444 rewriter.replaceOp(load, load.getPassThru());
6445 return success();
6447 return failure();
6448 }
6449 llvm_unreachable("Unexpected 1DMaskFormat on MaskedLoad");
6450 }
6451};
6452} // namespace
6453
6454void MaskedLoadOp::getCanonicalizationPatterns(RewritePatternSet &results,
6455 MLIRContext *context) {
6456 results.add<MaskedLoadFolder>(context);
6457}
6458
6459OpFoldResult MaskedLoadOp::fold(FoldAdaptor) {
6460 if (succeeded(memref::foldMemRefCast(*this)))
6461 return getResult();
6462 return OpFoldResult();
6463}
6464
6465FailureOr<std::optional<SmallVector<Value>>>
6466MaskedLoadOp::bubbleDownCasts(OpBuilder &builder) {
6468 getResult());
6469}
6470
6471//===----------------------------------------------------------------------===//
6472// MaskedStoreOp
6473//===----------------------------------------------------------------------===//
6474
6475LogicalResult MaskedStoreOp::verify() {
6476 VectorType maskVType = getMaskVectorType();
6477 VectorType valueVType = getVectorType();
6478 MemRefType memType = getMemRefType();
6479
6480 if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
6481 return failure();
6482
6483 // Negative strides are not supported on vector.maskedstore. The lowering to
6484 // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
6485 // assume non-negative strides to avoid undefined behavior.
6487 return emitOpError("memref strides must be non-negative");
6488
6489 if (failed(verifyElementTypesMatch(*this, memType, valueVType, "base",
6490 "valueToStore")))
6491 return failure();
6492 if (llvm::size(getIndices()) != memType.getRank())
6493 return emitOpError("requires ") << memType.getRank() << " indices";
6494 if (valueVType.getShape() != maskVType.getShape())
6495 return emitOpError("expected valueToStore shape to match mask shape");
6496 return success();
6497}
6498
6499namespace {
6500class MaskedStoreFolder final : public OpRewritePattern<MaskedStoreOp> {
6501public:
6502 using Base::Base;
6503 LogicalResult matchAndRewrite(MaskedStoreOp store,
6504 PatternRewriter &rewriter) const override {
6505 switch (getMaskFormat(store.getMask())) {
6507 rewriter.replaceOpWithNewOp<vector::StoreOp>(
6508 store, store.getValueToStore(), store.getBase(), store.getIndices());
6509 return success();
6511 rewriter.eraseOp(store);
6512 return success();
6514 return failure();
6515 }
6516 llvm_unreachable("Unexpected 1DMaskFormat on MaskedStore");
6517 }
6518};
6519} // namespace
6520
6521void MaskedStoreOp::getCanonicalizationPatterns(RewritePatternSet &results,
6522 MLIRContext *context) {
6523 results.add<MaskedStoreFolder>(context);
6524}
6525
6526LogicalResult MaskedStoreOp::fold(FoldAdaptor adaptor,
6527 SmallVectorImpl<OpFoldResult> &results) {
6528 return memref::foldMemRefCast(*this);
6529}
6530
6531FailureOr<std::optional<SmallVector<Value>>>
6532MaskedStoreOp::bubbleDownCasts(OpBuilder &builder) {
6534 ValueRange());
6535}
6536
6537//===----------------------------------------------------------------------===//
6538// GatherOp
6539//===----------------------------------------------------------------------===//
6540
6541LogicalResult GatherOp::verify() {
6542 VectorType indVType = getIndexVectorType();
6543 VectorType maskVType = getMaskVectorType();
6544 VectorType resVType = getVectorType();
6545 ShapedType baseType = getBaseType();
6546
6547 if (!llvm::isa<MemRefType, RankedTensorType>(baseType))
6548 return emitOpError("requires base to be a memref or ranked tensor type");
6549
6550 // Negative strides are not supported on vector.gather.
6551 // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
6552 // flags that assume non-negative strides to avoid undefined behavior.
6553 if (auto memRefType = dyn_cast<MemRefType>(baseType))
6554 if (memref::hasNegativeStaticStride(memRefType))
6555 return emitOpError("memref strides must be non-negative");
6556
6557 if (failed(
6558 verifyElementTypesMatch(*this, baseType, resVType, "base", "result")))
6559 return failure();
6560 if (llvm::size(getOffsets()) != baseType.getRank())
6561 return emitOpError("requires ") << baseType.getRank() << " indices";
6562 if (resVType.getShape() != indVType.getShape())
6563 return emitOpError("expected result dim to match indices dim");
6564 if (resVType.getShape() != maskVType.getShape())
6565 return emitOpError("expected result dim to match mask dim");
6566 if (resVType != getPassThruVectorType())
6567 return emitOpError("expected pass_thru of same type as result type");
6568 if (getAlignmentAttr() && !isa<MemRefType>(baseType)) {
6569 return emitOpError(
6570 "alignment is only supported for memref bases, not tensor bases");
6571 }
6572 return success();
6573}
6574
6575// MaskableOpInterface methods.
6576
6577/// Returns the mask type expected by this operation. Mostly used for
6578/// verification purposes. It requires the operation to be vectorized."
6579Type GatherOp::getExpectedMaskType() {
6580 auto vecType = this->getIndexVectorType();
6581 return VectorType::get(vecType.getShape(),
6582 IntegerType::get(vecType.getContext(), /*width=*/1),
6583 vecType.getScalableDims());
6584}
6585
6586std::optional<SmallVector<int64_t, 4>> GatherOp::getIterationShapeToUnroll() {
6587 return llvm::to_vector<4>(getVectorType().getShape());
6588}
6589
6590/// Cheeck if `indexVec` is constant 1D vec of consecutive values [0, 1, 2, ...]
6591static LogicalResult isZeroBasedContiguousSeq(Value indexVec) {
6592 auto vecType = dyn_cast<VectorType>(indexVec.getType());
6593 if (!vecType || vecType.getRank() != 1 || vecType.isScalable())
6594 return failure();
6595
6596 if (indexVec.getDefiningOp<StepOp>())
6597 return success();
6598
6599 DenseIntElementsAttr elements;
6600 if (!matchPattern(indexVec, m_Constant(&elements)))
6601 return failure();
6602
6603 return success(
6604 llvm::equal(elements, llvm::seq<int64_t>(0, vecType.getNumElements())));
6605}
6606
6607namespace {
6608class GatherFolder final : public OpRewritePattern<GatherOp> {
6609public:
6610 using Base::Base;
6611 LogicalResult matchAndRewrite(GatherOp gather,
6612 PatternRewriter &rewriter) const override {
6613 switch (getMaskFormat(gather.getMask())) {
6615 return failure(); // no unmasked equivalent
6617 rewriter.replaceOp(gather, gather.getPassThru());
6618 return success();
6620 return failure();
6621 }
6622 llvm_unreachable("Unexpected 1DMaskFormat on GatherFolder");
6623 }
6624};
6625
6626/// Fold gathers with consecutive offsets [0, 1, 2, ...] into contiguous
6627/// maskedload. Only 1D fixed vectors are supported for now.
6628class FoldContiguousGather final : public OpRewritePattern<GatherOp> {
6629public:
6630 using Base::Base;
6631 LogicalResult matchAndRewrite(GatherOp op,
6632 PatternRewriter &rewriter) const override {
6633 if (!isa<MemRefType>(op.getBase().getType()))
6634 return rewriter.notifyMatchFailure(op, "base must be of memref type");
6635
6636 if (failed(isZeroBasedContiguousSeq(op.getIndices())))
6637 return failure();
6638
6639 rewriter.replaceOpWithNewOp<MaskedLoadOp>(op, op.getType(), op.getBase(),
6640 op.getOffsets(), op.getMask(),
6641 op.getPassThru());
6642 return success();
6643 }
6644};
6645} // namespace
6646
6647void GatherOp::getCanonicalizationPatterns(RewritePatternSet &results,
6648 MLIRContext *context) {
6649 results.add<GatherFolder, FoldContiguousGather>(context);
6650}
6651
6652FailureOr<std::optional<SmallVector<Value>>>
6653GatherOp::bubbleDownCasts(OpBuilder &builder) {
6655 getResult());
6656}
6657
6658//===----------------------------------------------------------------------===//
6659// ScatterOp
6660//===----------------------------------------------------------------------===//
6661
6662LogicalResult ScatterOp::verify() {
6663 VectorType indVType = getIndexVectorType();
6664 VectorType maskVType = getMaskVectorType();
6665 VectorType valueVType = getVectorType();
6666 ShapedType baseType = getBaseType();
6667
6668 if (!llvm::isa<MemRefType, RankedTensorType>(baseType))
6669 return emitOpError("requires base to be a memref or ranked tensor type");
6670
6671 // Negative strides are not supported on vector.scatter.
6672 // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
6673 // flags that assume non-negative strides to avoid undefined behavior.
6674 if (auto memRefType = dyn_cast<MemRefType>(baseType))
6675 if (memref::hasNegativeStaticStride(memRefType))
6676 return emitOpError("memref strides must be non-negative");
6677
6678 if (failed(verifyElementTypesMatch(*this, baseType, valueVType, "base",
6679 "valueToStore")))
6680 return failure();
6681 if (llvm::size(getOffsets()) != baseType.getRank())
6682 return emitOpError("requires ") << baseType.getRank() << " indices";
6683 if (valueVType.getShape() != indVType.getShape())
6684 return emitOpError("expected valueToStore dim to match indices dim");
6685 if (valueVType.getShape() != maskVType.getShape())
6686 return emitOpError("expected valueToStore dim to match mask dim");
6687 if (getAlignmentAttr() && !isa<MemRefType>(baseType)) {
6688 return emitOpError(
6689 "alignment is only supported for memref bases, not tensor bases");
6690 }
6691 return success();
6692}
6693namespace {
6694class ScatterFolder final : public OpRewritePattern<ScatterOp> {
6695public:
6696 using Base::Base;
6697 LogicalResult matchAndRewrite(ScatterOp scatter,
6698 PatternRewriter &rewriter) const override {
6699 ShapedType baseType = scatter.getBaseType();
6700 bool isMemRef = isa<MemRefType>(baseType);
6701 if (!isMemRef && !isa<RankedTensorType>(baseType))
6702 return failure();
6703
6704 // Memrefs have no result, so an all-false mask can simply erase the op.
6705 // Tensors carry the updated value, so we must replace uses with the
6706 // original base tensor instead of erasing.
6707 switch (getMaskFormat(scatter.getMask())) {
6709 return failure(); // no unmasked equivalent
6711 if (isMemRef)
6712 rewriter.eraseOp(scatter);
6713 else
6714 rewriter.replaceOp(scatter, scatter.getBase());
6715 return success();
6717 return failure();
6718 }
6719 llvm_unreachable("Unexpected 1DMaskFormat on ScatterFolder");
6720 }
6721};
6722
6723/// Fold scatters with consecutive offsets [0, 1, 2, ...] into contiguous
6724/// maskedstore. Only 1D fixed vectors are supported for now.
6725class FoldContiguousScatter final : public OpRewritePattern<ScatterOp> {
6726public:
6727 using Base::Base;
6728 LogicalResult matchAndRewrite(ScatterOp op,
6729 PatternRewriter &rewriter) const override {
6730 // Fold only for memrefs: the replacement uses maskedstore, which does not
6731 // support tensor bases. Tensor cases intentionally bail out.
6732 if (!isa<MemRefType>(op.getBase().getType()))
6733 return failure();
6734
6735 if (failed(isZeroBasedContiguousSeq(op.getIndices())))
6736 return failure();
6737
6738 rewriter.replaceOpWithNewOp<MaskedStoreOp>(
6739 op, op.getBase(), op.getOffsets(), op.getMask(), op.getValueToStore());
6740 return success();
6741 }
6742};
6743} // namespace
6744
6745void ScatterOp::getCanonicalizationPatterns(RewritePatternSet &results,
6746 MLIRContext *context) {
6747 results.add<ScatterFolder, FoldContiguousScatter>(context);
6748}
6749
6750FailureOr<std::optional<SmallVector<Value>>>
6751ScatterOp::bubbleDownCasts(OpBuilder &builder) {
6753 ValueRange());
6754}
6755
6756//===----------------------------------------------------------------------===//
6757// ExpandLoadOp
6758//===----------------------------------------------------------------------===//
6759
6760LogicalResult ExpandLoadOp::verify() {
6761 VectorType maskVType = getMaskVectorType();
6762 VectorType passVType = getPassThruVectorType();
6763 VectorType resVType = getVectorType();
6764 MemRefType memType = getMemRefType();
6765
6766 if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
6767 return failure();
6768
6769 // Negative strides are not supported on vector.expandload. The lowering to
6770 // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
6771 // assume non-negative strides to avoid undefined behavior.
6773 return emitOpError("memref strides must be non-negative");
6774
6775 if (failed(
6776 verifyElementTypesMatch(*this, memType, resVType, "base", "result")))
6777 return failure();
6778 if (llvm::size(getIndices()) != memType.getRank())
6779 return emitOpError("requires ") << memType.getRank() << " indices";
6780 if (resVType.getShape() != maskVType.getShape())
6781 return emitOpError("expected result shape to match mask shape");
6782 if (resVType.getScalableDims() != maskVType.getScalableDims())
6783 return emitOpError(
6784 "expected result scalable dims to match mask scalable dims");
6785 if (resVType != passVType)
6786 return emitOpError("expected pass_thru of same type as result type");
6787 return success();
6788}
6789
6790namespace {
6791class ExpandLoadFolder final : public OpRewritePattern<ExpandLoadOp> {
6792public:
6793 using Base::Base;
6794 LogicalResult matchAndRewrite(ExpandLoadOp expand,
6795 PatternRewriter &rewriter) const override {
6796 switch (getMaskFormat(expand.getMask())) {
6798 rewriter.replaceOpWithNewOp<vector::LoadOp>(
6799 expand, expand.getType(), expand.getBase(), expand.getIndices());
6800 return success();
6802 rewriter.replaceOp(expand, expand.getPassThru());
6803 return success();
6805 return failure();
6806 }
6807 llvm_unreachable("Unexpected 1DMaskFormat on ExpandLoadFolder");
6808 }
6809};
6810} // namespace
6811
6812void ExpandLoadOp::getCanonicalizationPatterns(RewritePatternSet &results,
6813 MLIRContext *context) {
6814 results.add<ExpandLoadFolder>(context);
6815}
6816
6817FailureOr<std::optional<SmallVector<Value>>>
6818ExpandLoadOp::bubbleDownCasts(OpBuilder &builder) {
6820 getResult());
6821}
6822
6823//===----------------------------------------------------------------------===//
6824// CompressStoreOp
6825//===----------------------------------------------------------------------===//
6826
6827LogicalResult CompressStoreOp::verify() {
6828 VectorType maskVType = getMaskVectorType();
6829 VectorType valueVType = getVectorType();
6830 MemRefType memType = getMemRefType();
6831
6832 if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
6833 return failure();
6834
6835 // Negative strides are not supported on vector.compressstore. The lowering
6836 // to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
6837 // assume non-negative strides to avoid undefined behavior.
6839 return emitOpError("memref strides must be non-negative");
6840
6841 if (failed(verifyElementTypesMatch(*this, memType, valueVType, "base",
6842 "valueToStore")))
6843 return failure();
6844 if (llvm::size(getIndices()) != memType.getRank())
6845 return emitOpError("requires ") << memType.getRank() << " indices";
6846 if (valueVType.getShape() != maskVType.getShape())
6847 return emitOpError("expected valueToStore shape to match mask shape");
6848 if (valueVType.getScalableDims() != maskVType.getScalableDims())
6849 return emitOpError(
6850 "expected valueToStore scalable dims to match mask scalable dims");
6851 return success();
6852}
6853
6854namespace {
6855class CompressStoreFolder final : public OpRewritePattern<CompressStoreOp> {
6856public:
6857 using Base::Base;
6858 LogicalResult matchAndRewrite(CompressStoreOp compress,
6859 PatternRewriter &rewriter) const override {
6860 switch (getMaskFormat(compress.getMask())) {
6862 rewriter.replaceOpWithNewOp<vector::StoreOp>(
6863 compress, compress.getValueToStore(), compress.getBase(),
6864 compress.getIndices());
6865 return success();
6867 rewriter.eraseOp(compress);
6868 return success();
6870 return failure();
6871 }
6872 llvm_unreachable("Unexpected 1DMaskFormat on CompressStoreFolder");
6873 }
6874};
6875} // namespace
6876
6877void CompressStoreOp::getCanonicalizationPatterns(RewritePatternSet &results,
6878 MLIRContext *context) {
6879 results.add<CompressStoreFolder>(context);
6880}
6881
6882FailureOr<std::optional<SmallVector<Value>>>
6883CompressStoreOp::bubbleDownCasts(OpBuilder &builder) {
6885 ValueRange());
6886}
6887
6888//===----------------------------------------------------------------------===//
6889// ShapeCastOp
6890//===----------------------------------------------------------------------===//
6891
6892void ShapeCastOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
6893 SetIntRangeFn setResultRanges) {
6894 setResultRanges(getResult(), argRanges.front());
6895}
6896
6897std::optional<SmallVector<int64_t, 4>>
6898ShapeCastOp::getIterationShapeToUnroll() {
6899 return llvm::to_vector<4>(getResultVectorType().getShape());
6900}
6901
6902LogicalResult ShapeCastOp::verify() {
6903
6904 VectorType sourceType = getSourceVectorType();
6905 VectorType resultType = getResultVectorType();
6906
6907 // Check that element type is preserved
6908 if (failed(verifyElementTypesMatch(*this, sourceType, resultType, "source",
6909 "result")))
6910 return failure();
6911
6912 // Check that number of elements is preserved
6913 int64_t sourceNElms = sourceType.getNumElements();
6914 int64_t resultNElms = resultType.getNumElements();
6915 if (sourceNElms != resultNElms) {
6916 return emitOpError() << "has different number of elements at source ("
6917 << sourceNElms << ") and result (" << resultNElms
6918 << ")";
6919 }
6920
6921 // Check that (non-)scalability is preserved
6922 int64_t sourceNScalableDims = sourceType.getNumScalableDims();
6923 int64_t resultNScalableDims = resultType.getNumScalableDims();
6924 if (sourceNScalableDims != resultNScalableDims)
6925 return emitOpError() << "has different number of scalable dims at source ("
6926 << sourceNScalableDims << ") and result ("
6927 << resultNScalableDims << ")";
6928
6929 return success();
6930}
6931
6932/// Check whether this ShapeCastOp is effectively a BroadcastOp.
6933///
6934/// The only case in which this method can return `true` is when the underlying
6935/// op merely adds leading unit dimensions, e.g.:
6936/// %res = vector.shape_cast %src : vector<8x4xi32> to vector<1x8x4xi32>
6937///
6938bool ShapeCastOp::isBroadcastLike() {
6939 auto srcType = getSourceVectorType();
6940 auto resType = getResultVectorType();
6941
6942 // Is srcType broadcastable to resType?
6943 std::pair<VectorDim, VectorDim> mismatchingDims;
6944 if (isBroadcastableTo(srcType, resType, &mismatchingDims) !=
6945 BroadcastableToResult::Success)
6946 return false;
6947
6948 // Do ranks mismatch?
6949 //
6950 // The only case where ranks match and this ShapeCastOp is also a broadcast,
6951 // is when it's effectively a NOp, but that's an uninteresting edge case.
6952 size_t rankDiff = resType.getRank() - srcType.getRank();
6953 if (rankDiff == 0)
6954 return false;
6955
6956 // Are all newly added leading dims unit?
6957 if (!llvm::all_of(resType.getShape().take_front(rankDiff),
6958 [](int64_t dim) { return dim == 1; }))
6959 return false;
6960
6961 // Do all trailing dims match?
6962 return resType.getShape().take_back(srcType.getRank()) == srcType.getShape();
6963}
6964
6965/// Return true if `transpose` does not permute a pair of non-unit dims.
6966/// By `order preserving` we mean that the flattened versions of the input and
6967/// output vectors are (numerically) identical. In other words `transpose` is
6968/// effectively a shape cast.
6969static bool isOrderPreserving(TransposeOp transpose) {
6970 ArrayRef<int64_t> permutation = transpose.getPermutation();
6971 VectorType sourceType = transpose.getSourceVectorType();
6972 ArrayRef<int64_t> inShape = sourceType.getShape();
6973 ArrayRef<bool> inDimIsScalable = sourceType.getScalableDims();
6974 auto isNonScalableUnitDim = [&](int64_t dim) {
6975 return inShape[dim] == 1 && !inDimIsScalable[dim];
6976 };
6977 int64_t current = 0;
6978 for (auto p : permutation) {
6979 if (!isNonScalableUnitDim(p)) {
6980 if (p < current) {
6981 return false;
6982 }
6983 current = p;
6984 }
6985 }
6986 return true;
6987}
6988
6989OpFoldResult ShapeCastOp::fold(FoldAdaptor adaptor) {
6990
6991 VectorType resultType = getType();
6992
6993 // No-op shape cast.
6994 if (getSource().getType() == resultType)
6995 return getSource();
6996
6997 // shape_cast(shape_cast(x)) -> shape_cast(x)
6998 if (auto precedingShapeCast = getSource().getDefiningOp<ShapeCastOp>()) {
6999 setOperand(precedingShapeCast.getSource());
7000 return getResult();
7001 }
7002
7003 // shape_cast(transpose(x)) -> shape_cast(x)
7004 if (auto transpose = getSource().getDefiningOp<TransposeOp>()) {
7005 if (isOrderPreserving(transpose)) {
7006 setOperand(transpose.getVector());
7007 return getResult();
7008 }
7009 return {};
7010 }
7011
7012 // Y = shape_cast(broadcast(X))
7013 // -> X, if X and Y have same type
7014 if (auto bcastOp = getSource().getDefiningOp<BroadcastOp>()) {
7015 if (bcastOp.getSourceType() == resultType)
7016 return bcastOp.getSource();
7017 }
7018
7019 // shape_cast(constant) -> constant
7020 if (auto denseAttr =
7021 dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()))
7022 return denseAttr.reshape(getType());
7023
7024 // shape_cast(poison) -> poison
7025 if (matchPattern(adaptor.getSource(), ub::m_Poison()))
7026 return ub::PoisonAttr::get(getContext());
7027
7028 return {};
7029}
7030
7031namespace {
7032
7033/// Helper function that computes a new vector type based on the input vector
7034/// type by removing the _trailing_ unit dims:
7035///
7036/// vector<4x1x1xi1> --> vector<4x1xi1>
7037///
7038static VectorType trimTrailingUnitDims(VectorType oldType) {
7039 ArrayRef<int64_t> oldShape = oldType.getShape();
7040 ArrayRef<int64_t> newShape = oldShape;
7041
7042 ArrayRef<bool> oldScalableDims = oldType.getScalableDims();
7043 ArrayRef<bool> newScalableDims = oldScalableDims;
7044
7045 while (!newShape.empty() && newShape.back() == 1 && !newScalableDims.back()) {
7046 newShape = newShape.drop_back(1);
7047 newScalableDims = newScalableDims.drop_back(1);
7048 }
7049
7050 // Make sure we have at least 1 dimension.
7051 // TODO: Add support for 0-D vectors.
7052 if (newShape.empty()) {
7053 newShape = oldShape.take_back();
7054 newScalableDims = oldScalableDims.take_back();
7055 }
7056
7057 return VectorType::get(newShape, oldType.getElementType(), newScalableDims);
7058}
7059
7060/// Helper function that computes a new vector type based on the input vector
7061/// type by removing the _leading_ unit dims:
7062///
7063/// vector<1x1x4xi1> --> vector<4x1xi1>
7064///
7065static VectorType trimLeadingUnitDims(VectorType oldType) {
7066 ArrayRef<int64_t> oldShape = oldType.getShape();
7067 ArrayRef<int64_t> newShape = oldShape;
7068
7069 ArrayRef<bool> oldScalableDims = oldType.getScalableDims();
7070 ArrayRef<bool> newScalableDims = oldScalableDims;
7071
7072 while (!newShape.empty() && newShape.front() == 1 &&
7073 !newScalableDims.front()) {
7074 newShape = newShape.drop_front(1);
7075 newScalableDims = newScalableDims.drop_front(1);
7076 }
7077
7078 // Make sure we have at least 1 dimension.
7079 // TODO: Add support for 0-D vectors.
7080 if (newShape.empty()) {
7081 newShape = oldShape.take_back();
7082 newScalableDims = oldScalableDims.take_back();
7083 }
7084
7085 return VectorType::get(newShape, oldType.getElementType(), newScalableDims);
7086}
7087
7088enum class UnitDimSide { Leading, Trailing };
7089
7090/// Folds qualifying shape_cast(create_mask) into a new create_mask
7091///
7092/// Looks at `vector.shape_cast` Ops that simply "drop" either the _trailing_ or
7093/// the _leading_unit dimension (configured with the template parameter). If the
7094/// input vector comes from `vector.create_mask` for which the corresponding
7095/// mask input value is 1 (e.g. `%c1` below), then it is safe to fold shape_cast
7096/// into create_mask.
7097///
7098/// EX 1 (trailing unit dims)
7099/// BEFORE:
7100/// %1 = vector.create_mask %c1, %dim, %c1, %c1 : vector<1x[4]x1x1xi1>
7101/// %2 = vector.shape_cast %1 : vector<1x[4]x1x1xi1> to vector<1x[4]xi1>
7102/// AFTER:
7103/// %0 = vector.create_mask %c1, %dim : vector<1x[4]xi1>
7104///
7105/// EX 2 (leading unit dims)
7106/// BEFORE:
7107/// %1 = vector.create_mask %c1, %c1, %dim, %c1 : vector<1x1x[4]x1xi1>
7108/// %2 = vector.shape_cast %1 : vector<1x1x[4]x1xi1> to vector<[4]x1xi1>
7109/// AFTER:
7110/// %0 = vector.create_mask %c1, %dim : vector<[4]xi1>
7111template <UnitDimSide Side>
7112class ShapeCastCreateMaskFolderBoundaryUnitDim final
7113 : public OpRewritePattern<ShapeCastOp> {
7114public:
7115 using Base::Base;
7116
7117 LogicalResult matchAndRewrite(ShapeCastOp shapeOp,
7118 PatternRewriter &rewriter) const override {
7119 Value shapeOpSrc = shapeOp->getOperand(0);
7120 auto createMaskOp = shapeOpSrc.getDefiningOp<vector::CreateMaskOp>();
7121 auto constantMaskOp = shapeOpSrc.getDefiningOp<vector::ConstantMaskOp>();
7122 if (!createMaskOp && !constantMaskOp)
7123 return failure();
7124
7125 VectorType shapeOpResTy = shapeOp.getResultVectorType();
7126 VectorType shapeOpSrcTy = shapeOp.getSourceVectorType();
7127
7128 VectorType newVecType = (Side == UnitDimSide::Trailing)
7129 ? trimTrailingUnitDims(shapeOpSrcTy)
7130 : trimLeadingUnitDims(shapeOpSrcTy);
7131
7132 if (newVecType != shapeOpResTy) {
7133 return (Side == UnitDimSide::Trailing)
7134 ? rewriter.notifyMatchFailure(
7135 shapeOp, "Non-trailing-unit-dim dropping shape_cast Op")
7136 : rewriter.notifyMatchFailure(
7137 shapeOp, "Non-leading-unit-dim dropping shape_cast Op");
7138 }
7139
7140 auto numDimsToDrop = shapeOpSrcTy.getRank() - shapeOpResTy.getRank();
7141
7142 // No unit dims to drop
7143 if (!numDimsToDrop) {
7144 return (Side == UnitDimSide::Trailing)
7145 ? rewriter.notifyMatchFailure(
7146 shapeOp, "Non-trailing-unit-dim dropping shape_cast Op")
7147 : rewriter.notifyMatchFailure(
7148 shapeOp, "Non-leading-unit-dim dropping shape_cast Op");
7149 }
7150
7151 if (createMaskOp) {
7152 auto maskOperands = createMaskOp.getOperands();
7153
7154 auto maskOperandsToDrop = (Side == UnitDimSide::Trailing)
7155 ? maskOperands.take_back(numDimsToDrop)
7156 : maskOperands.take_front(numDimsToDrop);
7157
7158 // Check that every mask-dim-size to-be-dropped is constant and == 1. We
7159 // could also check for == 0, but that's left as a TODO.
7160 if (llvm::any_of(maskOperandsToDrop, [](Value maskDim) {
7161 auto cst = maskDim.getDefiningOp<arith::ConstantIndexOp>();
7162 return !cst || (cst.value() != 1);
7163 }))
7164 return failure();
7165
7166 auto newMaskOperands = (Side == UnitDimSide::Trailing)
7167 ? maskOperands.drop_back(numDimsToDrop)
7168 : maskOperands.drop_front(numDimsToDrop);
7169 rewriter.replaceOpWithNewOp<vector::CreateMaskOp>(shapeOp, shapeOpResTy,
7170 newMaskOperands);
7171 return success();
7172 }
7173
7174 if (constantMaskOp) {
7175 auto maskDimSizes = constantMaskOp.getMaskDimSizes();
7176
7177 ArrayRef<int64_t> maskDimSizesToDrop =
7178 (Side == UnitDimSide::Trailing)
7179 ? maskDimSizes.take_back(numDimsToDrop)
7180 : maskDimSizes.take_front(numDimsToDrop);
7181
7182 // Check that every mask-dim-size to-be-dropped is constant and == 1. We
7183 // could also check for == 0, but that's left as a TODO.
7184 if (llvm::any_of(maskDimSizesToDrop,
7185 [](int64_t dim) { return dim != 1; }))
7186 return failure();
7187
7188 ArrayRef<int64_t> newMaskDimSizes =
7189 (Side == UnitDimSide::Trailing)
7190 ? maskDimSizes.drop_back(numDimsToDrop)
7191 : maskDimSizes.drop_front(numDimsToDrop);
7192 rewriter.replaceOpWithNewOp<vector::ConstantMaskOp>(shapeOp, shapeOpResTy,
7193 newMaskDimSizes);
7194 return success();
7195 }
7196
7197 return failure();
7198 }
7199};
7200
7201// vector.broadcast has two distinct semantic modes: duplication across leading
7202// dimensions, and stretching across inner dimensions. This helper returns the
7203// product of the inner-dimension stretching factors.
7204int64_t getBroadcastStretchingFactor(ArrayRef<int64_t> srcShape,
7205 ArrayRef<int64_t> dstShape) {
7206 int stretchingFactor = 1;
7207 int numLeadingDims = dstShape.size() - srcShape.size();
7208 for (int i = 0, e = srcShape.size(); i < e; i++) {
7209 int64_t dstDim = dstShape[numLeadingDims + i];
7210 if (srcShape[i] == 1 && dstDim != 1) {
7211 stretchingFactor *= dstDim;
7212 }
7213 }
7214 return stretchingFactor;
7215}
7216
7217/// Pattern to rewrite Y = ShapeCast(Broadcast(X)) as Y = Broadcast(X)
7218class ShapeCastBroadcastFolder final : public OpRewritePattern<ShapeCastOp> {
7219public:
7220 using Base::Base;
7221
7222 LogicalResult matchAndRewrite(ShapeCastOp shapeCastOp,
7223 PatternRewriter &rewriter) const override {
7224 auto broadcastOp =
7225 shapeCastOp.getSource().getDefiningOp<vector::BroadcastOp>();
7226 if (!broadcastOp)
7227 return failure();
7228
7229 auto srcVectorType = dyn_cast<VectorType>(broadcastOp.getSourceType());
7230 bool srcIsScalar = !srcVectorType;
7231
7232 // Replace Y = ShapeCast(Broadcast(X)) with Y = Broadcast(X)
7233 // Example
7234 // %0 = vector.broadcast %in : vector<3xf32> to vector<2x4x3xf32>
7235 // %1 = vector.shape_cast %0 : vector<2x4x3xf32> to vector<8x3xf32>
7236 // to
7237 // %1 = vector.broadcast %in : vector<3xf32> to vector<8x3xf32>
7238 VectorType dstVectorType = shapeCastOp.getResultVectorType();
7239 ArrayRef<int64_t> dstShape = dstVectorType.getShape();
7240 ArrayRef<int64_t> srcShape =
7241 srcIsScalar ? ArrayRef<int64_t>{} : srcVectorType.getShape();
7242 ArrayRef<int64_t> broadcastShape =
7243 broadcastOp.getResultVectorType().getShape();
7244
7245 if (!srcIsScalar) {
7246 if (isBroadcastableTo(srcVectorType, dstVectorType) !=
7247 BroadcastableToResult::Success) {
7248 return failure();
7249 }
7250 // Avoid folding if this would result in switching between the two
7251 // distinct semantic modes of vector.broadcast (duplication vs
7252 // stretching). See https://github.com/llvm/llvm-project/issues/190614.
7253 // This is detected by a change in the stretching factor. However if the
7254 // source has a single element, there is no ambiguity.
7255 if (srcVectorType.getNumElements() != 1) {
7256 if (getBroadcastStretchingFactor(srcShape, dstShape) !=
7257 getBroadcastStretchingFactor(srcShape, broadcastShape)) {
7258 return failure();
7259 }
7260 }
7261 }
7262
7263 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(shapeCastOp, dstVectorType,
7264 broadcastOp.getSource());
7265 return success();
7266 }
7267};
7268
7269/// Pattern to rewrite Y = ShapeCast(FromElements(X)) as Y = FromElements(X)
7270///
7271/// BEFORE:
7272/// %1 = vector.from_elements %c1, %c2, %c3 : vector<3xf32>
7273/// %2 = vector.shape_cast %1 : vector<3xf32> to vector<1x3xf32>
7274/// AFTER:
7275/// %2 = vector.from_elements %c1, %c2, %c3 : vector<1x3xf32>
7276///
7277/// Note: this transformation is implemented as an OpRewritePattern, not as a
7278/// fold, because we have to create new op FromElementsOp with updated result
7279/// type. This cannot be done with a fold, because fold cannot create new ops
7280/// and the existing FromElementsOp result type differs from the ShapeCastOp
7281/// result type. Mutating the FromElementsOp (not root op) would violate the
7282/// fold contract and break other users.
7283class FoldShapeCastOfFromElements final : public OpRewritePattern<ShapeCastOp> {
7284public:
7285 using Base::Base;
7286
7287 LogicalResult matchAndRewrite(ShapeCastOp shapeCastOp,
7288 PatternRewriter &rewriter) const override {
7289 auto fromElements = shapeCastOp.getSource().getDefiningOp<FromElementsOp>();
7290 if (!fromElements)
7291 return failure();
7292
7293 rewriter.replaceOpWithNewOp<FromElementsOp>(
7294 shapeCastOp, shapeCastOp.getResultVectorType(),
7295 fromElements.getElements());
7296 return success();
7297 }
7298};
7299
7300} // namespace
7301
7302void ShapeCastOp::getCanonicalizationPatterns(RewritePatternSet &results,
7303 MLIRContext *context) {
7304 results.add<ShapeCastCreateMaskFolderBoundaryUnitDim<UnitDimSide::Leading>,
7305 ShapeCastCreateMaskFolderBoundaryUnitDim<UnitDimSide::Trailing>,
7306 ShapeCastBroadcastFolder, FoldShapeCastOfFromElements>(context);
7307}
7308
7309//===----------------------------------------------------------------------===//
7310// VectorBitCastOp
7311//===----------------------------------------------------------------------===//
7312
7313LogicalResult BitCastOp::verify() {
7314 auto sourceVectorType = getSourceVectorType();
7315 auto resultVectorType = getResultVectorType();
7316
7317 for (int64_t i = 0, e = sourceVectorType.getRank() - 1; i < e; i++) {
7318 if (sourceVectorType.getDimSize(i) != resultVectorType.getDimSize(i))
7319 return emitOpError("dimension size mismatch at: ") << i;
7320 }
7321
7322 DataLayout dataLayout = DataLayout::closest(*this);
7323 auto sourceElementBits =
7324 dataLayout.getTypeSizeInBits(sourceVectorType.getElementType());
7325 auto resultElementBits =
7326 dataLayout.getTypeSizeInBits(resultVectorType.getElementType());
7327
7328 if (sourceVectorType.getRank() == 0) {
7329 if (sourceElementBits != resultElementBits)
7330 return emitOpError("source/result bitwidth of the 0-D vector element "
7331 "types must be equal");
7332 } else if (sourceElementBits * sourceVectorType.getShape().back() !=
7333 resultElementBits * resultVectorType.getShape().back()) {
7334 return emitOpError(
7335 "source/result bitwidth of the minor 1-D vectors must be equal");
7336 }
7337
7338 return success();
7339}
7340
7341OpFoldResult BitCastOp::fold(FoldAdaptor adaptor) {
7342 // Nop cast.
7343 if (getSource().getType() == getResult().getType())
7344 return getSource();
7345
7346 // Canceling bitcasts.
7347 if (auto otherOp = getSource().getDefiningOp<BitCastOp>()) {
7348 if (getResult().getType() == otherOp.getSource().getType())
7349 return otherOp.getSource();
7350
7351 setOperand(otherOp.getSource());
7352 return getResult();
7353 }
7354
7355 Attribute sourceConstant = adaptor.getSource();
7356 if (!sourceConstant)
7357 return {};
7358
7359 Type srcElemType = getSourceVectorType().getElementType();
7360 Type dstElemType = getResultVectorType().getElementType();
7361
7362 if (auto floatPack = llvm::dyn_cast<DenseFPElementsAttr>(sourceConstant)) {
7363 if (floatPack.isSplat()) {
7364 auto splat = floatPack.getSplatValue<FloatAttr>();
7365
7366 // Casting fp16 into fp32.
7367 if (srcElemType.isF16() && dstElemType.isF32()) {
7368 uint32_t bits = static_cast<uint32_t>(
7369 splat.getValue().bitcastToAPInt().getZExtValue());
7370 // Duplicate the 16-bit pattern.
7371 bits = (bits << 16) | (bits & 0xffff);
7372 APInt intBits(32, bits);
7373 APFloat floatBits(llvm::APFloat::IEEEsingle(), intBits);
7374 return DenseElementsAttr::get(getResultVectorType(), floatBits);
7375 }
7376 }
7377 }
7378
7379 if (auto intPack = llvm::dyn_cast<DenseIntElementsAttr>(sourceConstant)) {
7380 if (intPack.isSplat()) {
7381 auto splat = intPack.getSplatValue<IntegerAttr>();
7382
7383 if (llvm::isa<IntegerType>(dstElemType) && srcElemType.isIntOrFloat()) {
7384 uint64_t srcBitWidth = srcElemType.getIntOrFloatBitWidth();
7385 uint64_t dstBitWidth = dstElemType.getIntOrFloatBitWidth();
7386
7387 // Casting to a larger integer bit width.
7388 if (dstBitWidth > srcBitWidth && dstBitWidth % srcBitWidth == 0) {
7389 APInt intBits = splat.getValue().zext(dstBitWidth);
7390
7391 // Duplicate the lower width element.
7392 for (uint64_t i = 0; i < dstBitWidth / srcBitWidth - 1; i++)
7393 intBits = (intBits << srcBitWidth) | intBits;
7394 return DenseElementsAttr::get(getResultVectorType(), intBits);
7395 }
7396 }
7397 }
7398 }
7399
7400 return {};
7401}
7402
7403std::optional<SmallVector<int64_t, 4>> BitCastOp::getIterationShapeToUnroll() {
7404 return llvm::to_vector<4>(getResultVectorType().getShape());
7405}
7406
7407//===----------------------------------------------------------------------===//
7408// TypeCastOp
7409//===----------------------------------------------------------------------===//
7410
7411static SmallVector<int64_t, 8> extractShape(MemRefType memRefType) {
7412 auto vectorType = llvm::dyn_cast<VectorType>(memRefType.getElementType());
7413 SmallVector<int64_t, 8> res(memRefType.getShape());
7414 if (vectorType)
7415 res.append(vectorType.getShape().begin(), vectorType.getShape().end());
7416 return res;
7417}
7418
7419/// Build the canonical memRefType with a single vector.
7420/// E.g. memref<4 x 5 x vector<6 x f32>> -> memref<vector<4 x 5 x 6 x f32>>.
7421void TypeCastOp::build(OpBuilder &builder, OperationState &result,
7422 Value source) {
7423 result.addOperands(source);
7424 MemRefType memRefType = llvm::cast<MemRefType>(source.getType());
7425 VectorType vectorType =
7426 VectorType::get(extractShape(memRefType),
7428 result.addTypes(MemRefType::get({}, vectorType, MemRefLayoutAttrInterface(),
7429 memRefType.getMemorySpace()));
7430}
7431
7432LogicalResult TypeCastOp::verify() {
7433 MemRefType canonicalType = getMemRefType().canonicalizeStridedLayout();
7434 if (!canonicalType.getLayout().isIdentity())
7435 return emitOpError("expects operand to be a memref with identity layout");
7436 if (!getResultMemRefType().getLayout().isIdentity())
7437 return emitOpError("expects result to be a memref with identity layout");
7438 if (getResultMemRefType().getMemorySpace() !=
7439 getMemRefType().getMemorySpace())
7440 return emitOpError("expects result in same memory space");
7441
7442 auto sourceType = getMemRefType();
7443 auto resultType = getResultMemRefType();
7444 if (getElementTypeOrSelf(getElementTypeOrSelf(sourceType)) !=
7446 return emitOpError(
7447 "expects result and operand with same underlying scalar type: ")
7448 << resultType;
7449 if (extractShape(sourceType) != extractShape(resultType))
7450 return emitOpError(
7451 "expects concatenated result and operand shapes to be equal: ")
7452 << resultType;
7453 return success();
7454}
7455
7456//===----------------------------------------------------------------------===//
7457// TransposeOp
7458//===----------------------------------------------------------------------===//
7459
7460void vector::TransposeOp::build(OpBuilder &builder, OperationState &result,
7461 Value vector, ArrayRef<int64_t> permutation) {
7462 VectorType vt = llvm::cast<VectorType>(vector.getType());
7463 SmallVector<int64_t, 4> transposedShape(vt.getRank());
7464 SmallVector<bool, 4> transposedScalableDims(vt.getRank());
7465 for (unsigned i = 0; i < permutation.size(); ++i) {
7466 transposedShape[i] = vt.getShape()[permutation[i]];
7467 transposedScalableDims[i] = vt.getScalableDims()[permutation[i]];
7468 }
7469
7470 result.addOperands(vector);
7471 result.addTypes(VectorType::get(transposedShape, vt.getElementType(),
7472 transposedScalableDims));
7473 result.addAttribute(TransposeOp::getPermutationAttrName(result.name),
7474 builder.getDenseI64ArrayAttr(permutation));
7475}
7476
7477OpFoldResult vector::TransposeOp::fold(FoldAdaptor adaptor) {
7478 // Eliminate splat constant transpose ops.
7479 if (auto splat =
7480 llvm::dyn_cast_if_present<SplatElementsAttr>(adaptor.getVector()))
7481 return splat.reshape(getResultVectorType());
7482
7483 // Eliminate poison transpose ops.
7484 if (matchPattern(adaptor.getVector(), ub::m_Poison()))
7485 return ub::PoisonAttr::get(getContext());
7486
7487 // Eliminate identity transposes, and more generally any transposes that
7488 // preserves the shape without permuting elements.
7489 //
7490 // Examples of what to fold:
7491 // %0 = vector.transpose %arg, [0, 1] : vector<1x1xi8> to vector<1x1xi8>
7492 // %0 = vector.transpose %arg, [0, 1] : vector<2x2xi8> to vector<2x2xi8>
7493 // %0 = vector.transpose %arg, [1, 0] : vector<1x1xi8> to vector<1x1xi8>
7494 //
7495 // Example of what NOT to fold:
7496 // %0 = vector.transpose %arg, [1, 0] : vector<2x2xi8> to vector<2x2xi8>
7497 //
7498 if (getSourceVectorType() == getResultVectorType() &&
7499 isOrderPreserving(*this))
7500 return getVector();
7501
7502 return {};
7503}
7504
7505LogicalResult vector::TransposeOp::verify() {
7506 VectorType vectorType = getSourceVectorType();
7507 VectorType resultType = getResultVectorType();
7508 int64_t rank = resultType.getRank();
7509 if (vectorType.getRank() != rank)
7510 return emitOpError("vector result rank mismatch: ") << rank;
7511 // Verify transposition array.
7512 ArrayRef<int64_t> perm = getPermutation();
7513 int64_t size = perm.size();
7514 if (rank != size)
7515 return emitOpError("transposition length mismatch: ") << size;
7516 SmallVector<bool, 8> seen(rank, false);
7517 for (const auto &ta : llvm::enumerate(perm)) {
7518 if (ta.value() < 0 || ta.value() >= rank)
7519 return emitOpError("transposition index out of range: ") << ta.value();
7520 if (seen[ta.value()])
7521 return emitOpError("duplicate position index: ") << ta.value();
7522 seen[ta.value()] = true;
7523 if (resultType.getDimSize(ta.index()) != vectorType.getDimSize(ta.value()))
7524 return emitOpError("dimension size mismatch at: ") << ta.value();
7525 }
7526 return success();
7527}
7528
7529std::optional<SmallVector<int64_t, 4>>
7530TransposeOp::getIterationShapeToUnroll() {
7531 return llvm::to_vector<4>(getResultVectorType().getShape());
7532}
7533
7534void TransposeOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
7535 SetIntRangeFn setResultRanges) {
7536 setResultRanges(getResult(), argRanges.front());
7537}
7538
7539namespace {
7540
7541// Rewrites two back-to-back TransposeOp operations into a single TransposeOp.
7542class TransposeFolder final : public OpRewritePattern<vector::TransposeOp> {
7543public:
7544 using Base::Base;
7545
7546 LogicalResult matchAndRewrite(vector::TransposeOp transposeOp,
7547 PatternRewriter &rewriter) const override {
7548 // Composes two permutations: result[i] = permutation1[permutation2[i]].
7549 auto composePermutations = [](ArrayRef<int64_t> permutation1,
7550 ArrayRef<int64_t> permutation2) {
7551 SmallVector<int64_t, 4> result;
7552 for (auto index : permutation2)
7553 result.push_back(permutation1[index]);
7554 return result;
7555 };
7556
7557 // Return if the input of 'transposeOp' is not defined by another transpose.
7558 vector::TransposeOp parentTransposeOp =
7559 transposeOp.getVector().getDefiningOp<vector::TransposeOp>();
7560 if (!parentTransposeOp)
7561 return failure();
7562
7563 SmallVector<int64_t, 4> permutation = composePermutations(
7564 parentTransposeOp.getPermutation(), transposeOp.getPermutation());
7565 // Replace 'transposeOp' with a new transpose operation.
7566 rewriter.replaceOpWithNewOp<vector::TransposeOp>(
7567 transposeOp, transposeOp.getResult().getType(),
7568 parentTransposeOp.getVector(), permutation);
7569 return success();
7570 }
7571};
7572
7573/// Replace transpose(splat-like(v)) with broadcast(v)
7574class FoldTransposeSplat final : public OpRewritePattern<TransposeOp> {
7575public:
7576 using Base::Base;
7577
7578 LogicalResult matchAndRewrite(TransposeOp transposeOp,
7579 PatternRewriter &rewriter) const override {
7580 Value splat = getScalarSplatSource(transposeOp.getVector());
7581 if (!splat)
7582 return failure();
7583
7584 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(
7585 transposeOp, transposeOp.getResultVectorType(), splat);
7586 return success();
7587 }
7588};
7589
7590/// Folds transpose(create_mask) into a new transposed create_mask.
7591class FoldTransposeCreateMask final : public OpRewritePattern<TransposeOp> {
7592public:
7593 using Base::Base;
7594
7595 LogicalResult matchAndRewrite(TransposeOp transpOp,
7596 PatternRewriter &rewriter) const override {
7597 Value transposeSrc = transpOp.getVector();
7598 auto createMaskOp = transposeSrc.getDefiningOp<vector::CreateMaskOp>();
7599 auto constantMaskOp = transposeSrc.getDefiningOp<vector::ConstantMaskOp>();
7600 if (!createMaskOp && !constantMaskOp)
7601 return failure();
7602
7603 // Get the transpose permutation and apply it to the vector.create_mask or
7604 // vector.constant_mask operands.
7605 ArrayRef<int64_t> permutation = transpOp.getPermutation();
7606
7607 if (createMaskOp) {
7608 auto maskOperands = createMaskOp.getOperands();
7609 SmallVector<Value> newOperands(maskOperands.begin(), maskOperands.end());
7610 applyPermutationToVector(newOperands, permutation);
7611
7612 rewriter.replaceOpWithNewOp<vector::CreateMaskOp>(
7613 transpOp, transpOp.getResultVectorType(), newOperands);
7614 return success();
7615 }
7616
7617 // ConstantMaskOp case.
7618 auto maskDimSizes = constantMaskOp.getMaskDimSizes();
7619 auto newMaskDimSizes = applyPermutation(maskDimSizes, permutation);
7620
7621 rewriter.replaceOpWithNewOp<vector::ConstantMaskOp>(
7622 transpOp, transpOp.getResultVectorType(), newMaskDimSizes);
7623 return success();
7624 }
7625};
7626
7627/// Folds transpose(shape_cast) into a new shape_cast.
7628class FoldTransposeShapeCast final : public OpRewritePattern<TransposeOp> {
7629public:
7630 using Base::Base;
7631
7632 LogicalResult matchAndRewrite(TransposeOp transposeOp,
7633 PatternRewriter &rewriter) const override {
7634 auto shapeCastOp =
7635 transposeOp.getVector().getDefiningOp<vector::ShapeCastOp>();
7636 if (!shapeCastOp)
7637 return failure();
7638 if (!isOrderPreserving(transposeOp))
7639 return failure();
7640
7641 VectorType resultType = transposeOp.getType();
7642
7643 // We don't need to check isValidShapeCast at this point, because it is
7644 // guaranteed that merging the transpose into the the shape_cast is a valid
7645 // shape_cast, because the transpose just inserts/removes ones.
7646
7647 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(transposeOp, resultType,
7648 shapeCastOp.getSource());
7649 return success();
7650 }
7651};
7652
7653/// Folds transpose(from_elements(...)) into a new from_elements with permuted
7654/// operands matching the transposed shape.
7655///
7656/// Example:
7657///
7658/// %v = vector.from_elements %a00, %a01, %a02, %a10, %a11, %a12 :
7659/// vector<2x3xi32> %t = vector.transpose %v, [1, 0] : vector<2x3xi32> to
7660/// vector<3x2xi32>
7661///
7662/// becomes ->
7663///
7664/// %r = vector.from_elements %a00, %a10, %a01, %a11, %a02, %a12 :
7665/// vector<3x2xi32>
7666///
7667class FoldTransposeFromElements final : public OpRewritePattern<TransposeOp> {
7668public:
7669 using Base::Base;
7670 LogicalResult matchAndRewrite(vector::TransposeOp transposeOp,
7671 PatternRewriter &rewriter) const override {
7672 auto fromElementsOp =
7673 transposeOp.getVector().getDefiningOp<vector::FromElementsOp>();
7674 if (!fromElementsOp)
7675 return failure();
7676
7677 VectorType srcTy = fromElementsOp.getDest().getType();
7678 VectorType dstTy = transposeOp.getType();
7679
7680 ArrayRef<int64_t> permutation = transposeOp.getPermutation();
7681 int64_t rank = srcTy.getRank();
7682
7683 // Build inverse permutation to map destination indices back to source.
7684 SmallVector<int64_t> inversePerm(rank, 0);
7685 for (int64_t i = 0; i < rank; ++i)
7686 inversePerm[permutation[i]] = i;
7687
7688 ArrayRef<int64_t> srcShape = srcTy.getShape();
7689 ArrayRef<int64_t> dstShape = dstTy.getShape();
7690 SmallVector<int64_t> srcIdx(rank, 0);
7691 SmallVector<int64_t> dstIdx(rank, 0);
7692 SmallVector<int64_t> srcStrides = computeStrides(srcShape);
7693 SmallVector<int64_t> dstStrides = computeStrides(dstShape);
7694
7695 auto elementsOld = fromElementsOp.getElements();
7696 SmallVector<Value> elementsNew;
7697 int64_t dstNumElements = dstTy.getNumElements();
7698 elementsNew.reserve(dstNumElements);
7699
7700 // For each element in destination row-major order, pick the corresponding
7701 // source element.
7702 for (int64_t linearIdx = 0; linearIdx < dstNumElements; ++linearIdx) {
7703 // Pick the destination element index.
7704 dstIdx = delinearize(linearIdx, dstStrides);
7705 // Map the destination element index to the source element index.
7706 for (int64_t j = 0; j < rank; ++j)
7707 srcIdx[j] = dstIdx[inversePerm[j]];
7708 // Linearize the source element index.
7709 int64_t srcLin = linearize(srcIdx, srcStrides);
7710 // Add the source element to the new elements.
7711 elementsNew.push_back(elementsOld[srcLin]);
7712 }
7713
7714 rewriter.replaceOpWithNewOp<FromElementsOp>(transposeOp, dstTy,
7715 elementsNew);
7716 return success();
7717 }
7718};
7719
7720/// Folds transpose(broadcast(x)) to broadcast(x) if the transpose is
7721/// 'order preserving', where 'order preserving' means the flattened
7722/// inputs and outputs of the transpose have identical (numerical) values.
7723///
7724/// Example:
7725/// ```
7726/// %0 = vector.broadcast %input : vector<1x1xi32> to vector<1x8xi32>
7727/// %1 = vector.transpose %0, [1, 0] : vector<1x8xi32>
7728/// to vector<8x1xi32>
7729/// ```
7730/// can be rewritten as the equivalent
7731/// ```
7732/// %0 = vector.broadcast %input : vector<1x1xi32> to vector<8x1xi32>.
7733/// ```
7734/// The algorithm works by partitioning dimensions into groups that can be
7735/// locally permuted while preserving order, and checks that the transpose
7736/// only permutes within these groups.
7737///
7738/// Groups are either contiguous sequences of 1s, or non-1s (1-element groups).
7739/// Consider broadcasting 4x1x1x7 to 2x3x4x5x6x7. This is equivalent to
7740/// broadcasting from 1x1x4x1x1x7.
7741/// ^^^ ^ ^^^ ^
7742/// groups: 0 1 2 3
7743/// Order preserving permutations for this example are ones that only permute
7744/// within the groups [0,1] and [3,4], like (1 0 2 4 3 5 6).
7745class FoldTransposeBroadcast : public OpRewritePattern<vector::TransposeOp> {
7746public:
7747 using Base::Base;
7748 FoldTransposeBroadcast(MLIRContext *context, PatternBenefit benefit = 1)
7749 : OpRewritePattern<vector::TransposeOp>(context, benefit) {}
7750
7751 LogicalResult matchAndRewrite(vector::TransposeOp transpose,
7752 PatternRewriter &rewriter) const override {
7753
7754 vector::BroadcastOp broadcast =
7755 transpose.getVector().getDefiningOp<vector::BroadcastOp>();
7756 if (!broadcast) {
7757 return rewriter.notifyMatchFailure(transpose,
7758 "not preceded by a broadcast");
7759 }
7760
7761 auto inputType = dyn_cast<VectorType>(broadcast.getSourceType());
7762 VectorType outputType = transpose.getResultVectorType();
7763
7764 // transpose(broadcast(scalar)) -> broadcast(scalar) is always valid
7765 bool inputIsScalar = !inputType;
7766 if (inputIsScalar) {
7767 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(transpose, outputType,
7768 broadcast.getSource());
7769 return success();
7770 }
7771
7772 ArrayRef<int64_t> permutation = transpose.getPermutation();
7773 ArrayRef<int64_t> inputShape = inputType.getShape();
7774 int64_t inputRank = inputType.getRank();
7775 int64_t outputRank = transpose.getType().getRank();
7776 int64_t deltaRank = outputRank - inputRank;
7777
7778 int low = 0;
7779 for (int inputIndex = 0; inputIndex < inputRank; ++inputIndex) {
7780 bool notOne = inputShape[inputIndex] != 1;
7781 bool prevNotOne = (inputIndex != 0 && inputShape[inputIndex - 1] != 1);
7782 bool groupEndFound = notOne || prevNotOne;
7783 if (groupEndFound) {
7784 int high = inputIndex + deltaRank;
7785 // Return failure if not all permutation destinations for indices in
7786 // [low, high) are in [low, high), i.e. the permutation is not local to
7787 // the group.
7788 for (int i = low; i < high; ++i) {
7789 if (permutation[i] < low || permutation[i] >= high) {
7790 return rewriter.notifyMatchFailure(
7791 transpose, "permutation not local to group");
7792 }
7793 }
7794 low = high;
7795 }
7796 }
7797
7798 // We don't need to check the final group [low, outputRank) because if it is
7799 // not locally bound, there must be a preceding group that already failed
7800 // the check (impossible to have just 1 non-locally bound group).
7801
7802 // The preceding logic also ensures that at this point, the output of the
7803 // transpose is definitely broadcastable from the input shape, assert so:
7804 assert(vector::isBroadcastableTo(inputType, outputType) ==
7805 vector::BroadcastableToResult::Success &&
7806 "not broadcastable directly to transpose output");
7807
7808 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(transpose, outputType,
7809 broadcast.getSource());
7810
7811 return success();
7812 }
7813};
7814
7815} // namespace
7816
7817void vector::TransposeOp::getCanonicalizationPatterns(
7818 RewritePatternSet &results, MLIRContext *context) {
7819 results.add<FoldTransposeCreateMask, FoldTransposeShapeCast, TransposeFolder,
7820 FoldTransposeSplat, FoldTransposeFromElements,
7821 FoldTransposeBroadcast>(context);
7822}
7823
7824//===----------------------------------------------------------------------===//
7825// ConstantMaskOp
7826//===----------------------------------------------------------------------===//
7827
7828void ConstantMaskOp::build(OpBuilder &builder, OperationState &result,
7829 VectorType type, ConstantMaskKind kind) {
7830 assert(kind == ConstantMaskKind::AllTrue ||
7831 kind == ConstantMaskKind::AllFalse);
7832 build(builder, result, type,
7833 kind == ConstantMaskKind::AllTrue
7834 ? type.getShape()
7835 : SmallVector<int64_t>(type.getRank(), 0));
7836}
7837
7838LogicalResult ConstantMaskOp::verify() {
7839 auto resultType = llvm::cast<VectorType>(getResult().getType());
7840 // Check the corner case of 0-D vectors first.
7841 if (resultType.getRank() == 0) {
7842 if (getMaskDimSizes().size() != 1)
7843 return emitError("array attr must have length 1 for 0-D vectors");
7844 auto dim = getMaskDimSizes()[0];
7845 if (dim != 0 && dim != 1)
7846 return emitError("mask dim size must be either 0 or 1 for 0-D vectors");
7847 return success();
7848 }
7849
7850 // Verify that array attr size matches the rank of the vector result.
7851 if (static_cast<int64_t>(getMaskDimSizes().size()) != resultType.getRank())
7852 return emitOpError(
7853 "must specify array attr of size equal vector result rank");
7854 // Verify that each array attr element is in bounds of corresponding vector
7855 // result dimension size.
7856 auto resultShape = resultType.getShape();
7857 auto resultScalableDims = resultType.getScalableDims();
7858 ArrayRef<int64_t> maskDimSizes = getMaskDimSizes();
7859 for (const auto [index, maskDimSize] : llvm::enumerate(maskDimSizes)) {
7860 if (maskDimSize < 0 || maskDimSize > resultShape[index])
7861 return emitOpError(
7862 "array attr of size out of bounds of vector result dimension size");
7863 if (resultScalableDims[index] && maskDimSize != 0 &&
7864 maskDimSize != resultShape[index])
7865 return emitOpError(
7866 "only supports 'none set' or 'all set' scalable dimensions");
7867 }
7868 // Verify that if one mask dim size is zero, they all should be zero (because
7869 // the mask region is a conjunction of each mask dimension interval).
7870 bool anyZeros = llvm::is_contained(maskDimSizes, 0);
7871 bool allZeros = llvm::all_of(maskDimSizes, [](int64_t s) { return s == 0; });
7872 if (anyZeros && !allZeros)
7873 return emitOpError("expected all mask dim sizes to be zeros, "
7874 "as a result of conjunction with zero mask dim");
7875 return success();
7876}
7877
7878bool ConstantMaskOp::isAllOnesMask() {
7879 auto resultType = getVectorType();
7880 // Check the corner case of 0-D vectors first.
7881 if (resultType.getRank() == 0) {
7882 assert(getMaskDimSizes().size() == 1 && "invalid sizes for zero rank mask");
7883 return getMaskDimSizes()[0] == 1;
7884 }
7885 for (const auto [resultSize, maskDimSize] :
7886 llvm::zip_equal(resultType.getShape(), getMaskDimSizes())) {
7887 if (maskDimSize < resultSize)
7888 return false;
7889 }
7890 return true;
7891}
7892
7893OpFoldResult ConstantMaskOp::fold(FoldAdaptor adaptor) {
7894 ArrayRef<int64_t> bounds = getMaskDimSizes();
7895 ArrayRef<int64_t> vectorSizes = getVectorType().getShape();
7896
7897 auto createBoolSplat = [&](bool x) {
7898 return SplatElementsAttr::get(getVectorType(),
7900 };
7901
7902 // Check the corner case of 0-D vectors first.
7903 if (vectorSizes.empty()) {
7904 assert(bounds.size() == 1 && "invalid sizes for zero rank mask");
7905 return createBoolSplat(bounds[0] == 1);
7906 }
7907 // Fold vector.constant_mask to splat if possible.
7908 if (bounds == vectorSizes)
7909 return createBoolSplat(true);
7910 if (llvm::all_of(bounds, [](int64_t x) { return x == 0; }))
7911 return createBoolSplat(false);
7912 return OpFoldResult();
7913}
7914
7915//===----------------------------------------------------------------------===//
7916// CreateMaskOp
7917//===----------------------------------------------------------------------===//
7918
7919void CreateMaskOp::build(OpBuilder &builder, OperationState &result,
7920 VectorType type,
7921 ArrayRef<OpFoldResult> mixedOperands) {
7922 SmallVector<Value> operands =
7923 getValueOrCreateConstantIndexOp(builder, result.location, mixedOperands);
7924 build(builder, result, type, operands);
7925}
7926
7927LogicalResult CreateMaskOp::verify() {
7928 auto vectorType = llvm::cast<VectorType>(getResult().getType());
7929 // Verify that an operand was specified for each result vector each dimension.
7930 if (vectorType.getRank() == 0) {
7931 if (getNumOperands() != 1)
7932 return emitOpError(
7933 "must specify exactly one operand for 0-D create_mask");
7934 } else if (getNumOperands() !=
7935 llvm::cast<VectorType>(getResult().getType()).getRank()) {
7936 return emitOpError(
7937 "must specify an operand for each result vector dimension");
7938 }
7939 return success();
7940}
7941
7942namespace {
7943
7944/// Pattern to rewrite a CreateMaskOp with a ConstantMaskOp.
7945///
7946/// Ex 1:
7947/// %c2 = arith.constant 2 : index
7948/// %c3 = arith.constant 3 : index
7949/// %0 = vector.create_mask %c3, %c2 : vector<4x3xi1>
7950/// Becomes:
7951/// vector.constant_mask [3, 2] : vector<4x3xi1>
7952///
7953/// Ex 2:
7954/// %c_neg_1 = arith.constant -1 : index
7955/// %0 = vector.create_mask %c_neg_1 : vector<[8]xi1>
7956/// becomes:
7957/// vector.constant_mask [0] : vector<[8]xi1>
7958///
7959/// Ex 3:
7960/// %c8 = arith.constant 8 : index
7961/// %c16 = arith.constant 16 : index
7962/// %0 = vector.vscale
7963/// %1 = arith.muli %0, %c16 : index
7964/// %10 = vector.create_mask %c8, %1 : vector<8x[16]xi1>
7965/// becomes:
7966/// %0 = vector.constant_mask [8, 16] : vector<8x[16]xi1>
7967class CreateMaskFolder final : public OpRewritePattern<CreateMaskOp> {
7968public:
7969 using Base::Base;
7970
7971 LogicalResult matchAndRewrite(CreateMaskOp createMaskOp,
7972 PatternRewriter &rewriter) const override {
7973 VectorType maskType = createMaskOp.getVectorType();
7974 ArrayRef<int64_t> maskTypeDimSizes = maskType.getShape();
7975 ArrayRef<bool> maskTypeDimScalableFlags = maskType.getScalableDims();
7976
7977 // Special case: Rank zero shape.
7978 constexpr std::array<int64_t, 1> rankZeroShape{1};
7979 constexpr std::array<bool, 1> rankZeroScalableDims{false};
7980 if (maskType.getRank() == 0) {
7981 maskTypeDimSizes = rankZeroShape;
7982 maskTypeDimScalableFlags = rankZeroScalableDims;
7983 }
7984
7985 // Determine if this CreateMaskOp can be folded to a ConstantMaskOp and
7986 // collect the `constantDims` (for the ConstantMaskOp).
7987 SmallVector<int64_t, 4> constantDims;
7988 for (auto [i, dimSize] : llvm::enumerate(createMaskOp.getOperands())) {
7989 if (auto intSize = getConstantIntValue(dimSize)) {
7990 // Constant value.
7991 // If the mask dim is non-scalable this can be any value.
7992 // If the mask dim is scalable only zero (all-false) is supported.
7993 if (maskTypeDimScalableFlags[i] && intSize >= 0)
7994 return failure();
7995 constantDims.push_back(*intSize);
7996 } else if (auto vscaleMultiplier = getConstantVscaleMultiplier(dimSize)) {
7997 // Constant vscale multiple (e.g. 4 x vscale).
7998 // Must be all-true to fold to a ConstantMask.
7999 if (vscaleMultiplier < maskTypeDimSizes[i])
8000 return failure();
8001 constantDims.push_back(*vscaleMultiplier);
8002 } else {
8003 return failure();
8004 }
8005 }
8006
8007 // Clamp values to constant_mask bounds.
8008 for (auto [value, maskDimSize] : llvm::zip(constantDims, maskTypeDimSizes))
8009 value = std::clamp<int64_t>(value, 0, maskDimSize);
8010
8011 // If one of dim sizes is zero, set all dims to zero.
8012 if (llvm::is_contained(constantDims, 0))
8013 constantDims.assign(constantDims.size(), 0);
8014
8015 // Replace 'createMaskOp' with ConstantMaskOp.
8016 rewriter.replaceOpWithNewOp<ConstantMaskOp>(createMaskOp, maskType,
8017 constantDims);
8018 return success();
8019 }
8020};
8021
8022} // namespace
8023
8024void CreateMaskOp::getCanonicalizationPatterns(RewritePatternSet &results,
8025 MLIRContext *context) {
8026 results.add<CreateMaskFolder>(context);
8027}
8028
8029//===----------------------------------------------------------------------===//
8030// MaskOp
8031//===----------------------------------------------------------------------===//
8032
8033void MaskOp::build(
8034 OpBuilder &builder, OperationState &result, Value mask,
8035 Operation *maskableOp,
8036 function_ref<void(OpBuilder &, Operation *)> maskRegionBuilder) {
8037 assert(maskRegionBuilder &&
8038 "builder callback for 'maskRegion' must be present");
8039
8040 result.addOperands(mask);
8041 OpBuilder::InsertionGuard guard(builder);
8042 Region *maskRegion = result.addRegion();
8043 builder.createBlock(maskRegion);
8044 maskRegionBuilder(builder, maskableOp);
8045}
8046
8047void MaskOp::build(
8048 OpBuilder &builder, OperationState &result, TypeRange resultTypes,
8049 Value mask, Operation *maskableOp,
8050 function_ref<void(OpBuilder &, Operation *)> maskRegionBuilder) {
8051 build(builder, result, resultTypes, mask, /*passthru=*/Value(), maskableOp,
8052 maskRegionBuilder);
8053}
8054
8055void MaskOp::build(
8056 OpBuilder &builder, OperationState &result, TypeRange resultTypes,
8057 Value mask, Value passthru, Operation *maskableOp,
8058 function_ref<void(OpBuilder &, Operation *)> maskRegionBuilder) {
8059 build(builder, result, mask, maskableOp, maskRegionBuilder);
8060 if (passthru)
8061 result.addOperands(passthru);
8062 result.addTypes(resultTypes);
8063}
8064
8065ParseResult MaskOp::parse(OpAsmParser &parser, OperationState &result) {
8066 // Create the op region.
8067 result.regions.reserve(1);
8068 Region &maskRegion = *result.addRegion();
8069
8070 auto &builder = parser.getBuilder();
8071
8072 // Parse all the operands.
8073 OpAsmParser::UnresolvedOperand mask;
8074 if (parser.parseOperand(mask))
8075 return failure();
8076
8077 // Optional passthru operand.
8078 OpAsmParser::UnresolvedOperand passthru;
8079 ParseResult parsePassthru = parser.parseOptionalComma();
8080 if (parsePassthru.succeeded() && parser.parseOperand(passthru))
8081 return failure();
8082
8083 // Parse op region.
8084 if (parser.parseRegion(maskRegion, /*arguments=*/{}, /*argTypes=*/{}))
8085 return failure();
8086
8087 MaskOp::ensureTerminator(maskRegion, builder, result.location);
8088
8089 // Parse the optional attribute list.
8090 if (parser.parseOptionalAttrDict(result.attributes))
8091 return failure();
8092
8093 // Parse all the types.
8094 Type maskType;
8095 if (parser.parseColonType(maskType))
8096 return failure();
8097
8098 SmallVector<Type> resultTypes;
8099 if (parser.parseOptionalArrowTypeList(resultTypes))
8100 return failure();
8101 result.types.append(resultTypes);
8102
8103 // Resolve operands.
8104 if (parser.resolveOperand(mask, maskType, result.operands))
8105 return failure();
8106
8107 if (parsePassthru.succeeded()) {
8108 if (resultTypes.empty())
8109 return parser.emitError(
8110 parser.getNameLoc(),
8111 "expects a result if passthru operand is provided");
8112
8113 if (parser.resolveOperand(passthru, resultTypes[0], result.operands))
8114 return failure();
8115 }
8116
8117 return success();
8118}
8119
8120void mlir::vector::MaskOp::print(OpAsmPrinter &p) {
8121 p << " " << getMask();
8122 if (getPassthru())
8123 p << ", " << getPassthru();
8124
8125 // Print single masked operation and skip terminator.
8126 p << " { ";
8127 Block *singleBlock = &getMaskRegion().getBlocks().front();
8128 if (singleBlock && !singleBlock->getOperations().empty())
8129 p.printCustomOrGenericOp(&singleBlock->front());
8130 p << " }";
8131
8133 getOperation()->getDiscardableAttrDictionary().getValue());
8134
8135 p << " : " << getMask().getType();
8136 if (getNumResults() > 0)
8137 p << " -> " << getResultTypes();
8138}
8139
8140void MaskOp::ensureTerminator(Region &region, Builder &builder, Location loc) {
8141 // 1. For an empty `vector.mask`, create a default terminator.
8142 if (region.empty() || region.front().empty()) {
8143 OpTrait::SingleBlockImplicitTerminator<vector::YieldOp>::Impl<
8144 MaskOp>::ensureTerminator(region, builder, loc);
8145 return;
8146 }
8147
8148 // 2. For a non-empty `vector.mask` with an explicit terminator, do nothing.
8149 Block &block = region.front();
8150 if (isa<vector::YieldOp>(block.back()))
8151 return;
8152
8153 // 3. For a non-empty `vector.mask` without an explicit terminator:
8154
8155 // Create default terminator if the number of masked operations is not
8156 // one. This case will trigger a verification failure.
8157 if (block.getOperations().size() != 1) {
8158 OpTrait::SingleBlockImplicitTerminator<vector::YieldOp>::Impl<
8159 MaskOp>::ensureTerminator(region, builder, loc);
8160 return;
8161 }
8162
8163 // Create a terminator that yields the results from the masked operation.
8164 OpBuilder opBuilder(builder.getContext());
8165 Operation *maskedOp = &block.front();
8166 opBuilder.setInsertionPointToEnd(&block);
8167 vector::YieldOp::create(opBuilder, loc, maskedOp->getResults());
8168}
8169
8170LogicalResult MaskOp::verify() {
8171 // Structural checks.
8172 Block &block = getMaskRegion().getBlocks().front();
8173 if (block.getOperations().empty())
8174 return emitOpError("expects a terminator within the mask region");
8175
8176 unsigned numMaskRegionOps = block.getOperations().size();
8177 if (numMaskRegionOps > 2)
8178 return emitOpError("expects only one operation to mask");
8179
8180 // Terminator checks.
8181 auto terminator = dyn_cast<vector::YieldOp>(block.back());
8182 if (!terminator)
8183 return emitOpError("expects a terminator within the mask region");
8184
8185 if (terminator->getNumOperands() != getNumResults())
8186 return emitOpError(
8187 "expects number of results to match mask region yielded values");
8188
8189 // Empty vector.mask. Nothing else to check.
8190 if (numMaskRegionOps == 1)
8191 return success();
8192
8193 auto maskableOp = dyn_cast<MaskableOpInterface>(block.front());
8194 if (!maskableOp)
8195 return emitOpError("expects a MaskableOpInterface within the mask region");
8196
8197 // Result checks.
8198 if (maskableOp->getNumResults() != getNumResults())
8199 return emitOpError("expects number of results to match maskable operation "
8200 "number of results");
8201
8202 if (!llvm::equal(maskableOp->getResults(), terminator.getOperands()))
8203 return emitOpError("expects all the results from the MaskableOpInterface "
8204 "to match all the values returned by the terminator");
8205
8206 if (!llvm::equal(maskableOp->getResultTypes(), getResultTypes()))
8207 return emitOpError(
8208 "expects result type to match maskable operation result type");
8209
8210 if (llvm::count_if(maskableOp->getResultTypes(),
8211 [](Type t) { return llvm::isa<VectorType>(t); }) > 1)
8212 return emitOpError("multiple vector results not supported");
8213
8214 // Mask checks.
8215 Type expectedMaskType = maskableOp.getExpectedMaskType();
8216 if (getMask().getType() != expectedMaskType)
8217 return emitOpError("expects a ")
8218 << expectedMaskType << " mask for the maskable operation";
8219
8220 // Passthru checks.
8221 Value passthru = getPassthru();
8222 if (passthru) {
8223 if (!maskableOp.supportsPassthru())
8224 return emitOpError(
8225 "doesn't expect a passthru argument for this maskable operation");
8226
8227 if (maskableOp->getNumResults() != 1)
8228 return emitOpError("expects result when passthru argument is provided");
8229
8230 if (passthru.getType() != maskableOp->getResultTypes()[0])
8231 return emitOpError("expects passthru type to match result type");
8232 }
8233
8234 return success();
8235}
8236
8237/// Folds empty `vector.mask` with no passthru operand and with or without
8238/// return values. For example:
8239///
8240/// %0 = vector.mask %mask { vector.yield %a : vector<8xf32> } :
8241/// vector<8xi1> -> vector<8xf32>
8242/// %1 = user_op %0 : vector<8xf32>
8243///
8244/// becomes:
8245///
8246/// %0 = user_op %a : vector<8xf32>
8247///
8248/// Empty `vector.mask` with passthru operand are handled by the canonicalizer
8249/// as it requires creating new operations.
8250
8251static LogicalResult foldEmptyMaskOp(MaskOp maskOp, MaskOp::FoldAdaptor adaptor,
8252 SmallVectorImpl<OpFoldResult> &results) {
8253 if (!maskOp.isEmpty() || maskOp.hasPassthru())
8254 return failure();
8255
8256 Block *block = maskOp.getMaskBlock();
8257 auto terminator = cast<vector::YieldOp>(block->front());
8258 if (terminator.getNumOperands() == 0)
8259 return failure();
8260
8261 // `vector.mask` has results, propagate the results.
8262 llvm::append_range(results, terminator.getOperands());
8263 return success();
8264}
8265
8266LogicalResult MaskOp::fold(FoldAdaptor adaptor,
8267 SmallVectorImpl<OpFoldResult> &results) {
8268 if (succeeded(foldEmptyMaskOp(*this, adaptor, results)))
8269 return success();
8270
8271 MaskFormat maskFormat = getMaskFormat(getMask());
8272 if (maskFormat != MaskFormat::AllTrue)
8273 return failure();
8274
8275 // Move maskable operation outside of the `vector.mask` region.
8276 // If there is no maskable op (empty body), the fold cannot proceed; the
8277 // canonicalizer handles this case instead.
8278 Operation *maskableOp = getMaskableOp();
8279 if (!maskableOp)
8280 return failure();
8281 maskableOp->dropAllUses();
8282 maskableOp->moveBefore(getOperation());
8283
8284 llvm::append_range(results, maskableOp->getResults());
8285 return success();
8286}
8287
8288/// Canonialize empty `vector.mask` operations that can't be handled in
8289/// `VectorMask::fold` as they require creating new operations.
8290///
8291/// Example 1: Empty `vector.mask` with passthru operand.
8292///
8293/// %0 = vector.mask %mask, %passthru { vector.yield %a : vector<8xf32> } :
8294/// vector<8xi1> -> vector<8xf32>
8295///
8296/// becomes:
8297///
8298/// %0 = arith.select %mask, %a, %passthru : vector<8xf32>
8299///
8300class CanonializeEmptyMaskOp : public OpRewritePattern<MaskOp> {
8301 using Base::Base;
8302
8303 LogicalResult matchAndRewrite(MaskOp maskOp,
8304 PatternRewriter &rewriter) const override {
8305 if (!maskOp.isEmpty())
8306 return failure();
8307
8308 if (!maskOp.hasPassthru())
8309 return failure();
8310
8311 // arith.select with a vector condition requires the value types to be
8312 // vectors of the same shape. Since vector.mask always has a vector mask
8313 // type, bail out when any result type doesn't match the mask shape to
8314 // avoid creating invalid IR.
8315 VectorType maskType = maskOp.getMask().getType();
8316 for (Type resultType : maskOp.getResultTypes()) {
8317 auto vecResultType = dyn_cast<VectorType>(resultType);
8318 if (!vecResultType || vecResultType.getShape() != maskType.getShape())
8319 return failure();
8320 }
8321
8322 Block *block = maskOp.getMaskBlock();
8323 auto terminator = cast<vector::YieldOp>(block->front());
8324 assert(terminator.getNumOperands() == 1 &&
8325 "expected one result when passthru is provided");
8326
8327 rewriter.replaceOpWithNewOp<arith::SelectOp>(
8328 maskOp, maskOp.getResultTypes(), maskOp.getMask(),
8329 terminator.getOperand(0), maskOp.getPassthru());
8330
8331 return success();
8332 }
8333};
8334
8335void MaskOp::getCanonicalizationPatterns(RewritePatternSet &results,
8336 MLIRContext *context) {
8337 results.add<CanonializeEmptyMaskOp>(context);
8338}
8339
8340// MaskingOpInterface definitions.
8341
8342/// Returns the operation masked by this 'vector.mask'.
8343Operation *MaskOp::getMaskableOp() {
8344 Block *block = getMaskBlock();
8345 if (block->getOperations().size() < 2)
8346 return nullptr;
8347
8348 return &block->front();
8349}
8350
8351/// Returns true if 'vector.mask' has a passthru value.
8352bool MaskOp::hasPassthru() { return getPassthru() != Value(); }
8353
8354//===----------------------------------------------------------------------===//
8355// ScanOp
8356//===----------------------------------------------------------------------===//
8357
8358LogicalResult ScanOp::verify() {
8359 VectorType srcType = getSourceType();
8360 VectorType initialType = getInitialValueType();
8361 // Check reduction dimension < rank.
8362 int64_t srcRank = srcType.getRank();
8363 int64_t reductionDim = getReductionDim();
8364 if (reductionDim >= srcRank)
8365 return emitOpError("reduction dimension ")
8366 << reductionDim << " has to be less than " << srcRank;
8367
8368 // Check that rank(initial_value) = rank(src) - 1.
8369 int64_t initialValueRank = initialType.getRank();
8370 if (initialValueRank != srcRank - 1)
8371 return emitOpError("initial value rank ")
8372 << initialValueRank << " has to be equal to " << srcRank - 1;
8373
8374 // Check shapes of initial value and src.
8375 ArrayRef<int64_t> srcShape = srcType.getShape();
8376 ArrayRef<int64_t> initialValueShapes = initialType.getShape();
8377 SmallVector<int64_t> expectedShape;
8378 for (int i = 0; i < srcRank; i++) {
8379 if (i != reductionDim)
8380 expectedShape.push_back(srcShape[i]);
8381 }
8382 if (!llvm::equal(initialValueShapes, expectedShape)) {
8383 return emitOpError("incompatible input/initial value shapes");
8384 }
8385
8386 // Verify supported reduction kind.
8387 Type eltType = getDestType().getElementType();
8388 if (!isSupportedCombiningKind(getKind(), eltType))
8389 return emitOpError("unsupported reduction type ")
8390 << eltType << " for kind '" << stringifyCombiningKind(getKind())
8391 << "'";
8392
8393 return success();
8394}
8395
8397 RewritePatternSet &patterns, PatternBenefit benefit) {
8398 patterns
8399 .add<CreateMaskFolder, MaskedLoadFolder, MaskedStoreFolder, GatherFolder,
8400 ScatterFolder, ExpandLoadFolder, CompressStoreFolder,
8401 StridedSliceConstantMaskFolder, TransposeFolder>(
8402 patterns.getContext(), benefit);
8403}
8404
8405Value mlir::vector::makeArithReduction(OpBuilder &b, Location loc,
8406 CombiningKind kind, Value v1, Value acc,
8407 arith::FastMathFlagsAttr fastmath,
8408 Value mask) {
8409 Type t1 = getElementTypeOrSelf(v1.getType());
8410 Type tAcc = getElementTypeOrSelf(acc.getType());
8411 Value result;
8412
8413 switch (kind) {
8414 case CombiningKind::ADD:
8415 if (t1.isIntOrIndex() && tAcc.isIntOrIndex())
8416 result = b.createOrFold<arith::AddIOp>(loc, v1, acc);
8417 else if (llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc))
8418 result = b.createOrFold<arith::AddFOp>(loc, v1, acc, fastmath);
8419 else
8420 llvm_unreachable("invalid value types for ADD reduction");
8421 break;
8422 case CombiningKind::AND:
8423 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8424 result = b.createOrFold<arith::AndIOp>(loc, v1, acc);
8425 break;
8426 case CombiningKind::MAXNUMF:
8427 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8428 "expected float values");
8429 result = b.createOrFold<arith::MaxNumFOp>(loc, v1, acc, fastmath);
8430 break;
8431 case CombiningKind::MAXIMUMNUMF:
8432 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8433 "expected float values");
8434 result = b.createOrFold<arith::MaximumNumFOp>(loc, v1, acc, fastmath);
8435 break;
8436 case CombiningKind::MAXIMUMF:
8437 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8438 "expected float values");
8439 result = b.createOrFold<arith::MaximumFOp>(loc, v1, acc, fastmath);
8440 break;
8441 case CombiningKind::MINNUMF:
8442 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8443 "expected float values");
8444 result = b.createOrFold<arith::MinNumFOp>(loc, v1, acc, fastmath);
8445 break;
8446 case CombiningKind::MINIMUMNUMF:
8447 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8448 "expected float values");
8449 result = b.createOrFold<arith::MinimumNumFOp>(loc, v1, acc, fastmath);
8450 break;
8451 case CombiningKind::MINIMUMF:
8452 assert(llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc) &&
8453 "expected float values");
8454 result = b.createOrFold<arith::MinimumFOp>(loc, v1, acc, fastmath);
8455 break;
8456 case CombiningKind::MAXSI:
8457 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8458 result = b.createOrFold<arith::MaxSIOp>(loc, v1, acc);
8459 break;
8460 case CombiningKind::MINSI:
8461 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8462 result = b.createOrFold<arith::MinSIOp>(loc, v1, acc);
8463 break;
8464 case CombiningKind::MAXUI:
8465 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8466 result = b.createOrFold<arith::MaxUIOp>(loc, v1, acc);
8467 break;
8468 case CombiningKind::MINUI:
8469 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8470 result = b.createOrFold<arith::MinUIOp>(loc, v1, acc);
8471 break;
8472 case CombiningKind::MUL:
8473 if (t1.isIntOrIndex() && tAcc.isIntOrIndex())
8474 result = b.createOrFold<arith::MulIOp>(loc, v1, acc);
8475 else if (llvm::isa<FloatType>(t1) && llvm::isa<FloatType>(tAcc))
8476 result = b.createOrFold<arith::MulFOp>(loc, v1, acc, fastmath);
8477 else
8478 llvm_unreachable("invalid value types for MUL reduction");
8479 break;
8480 case CombiningKind::OR:
8481 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8482 result = b.createOrFold<arith::OrIOp>(loc, v1, acc);
8483 break;
8484 case CombiningKind::XOR:
8485 assert(t1.isIntOrIndex() && tAcc.isIntOrIndex() && "expected int values");
8486 result = b.createOrFold<arith::XOrIOp>(loc, v1, acc);
8487 break;
8488 };
8489
8490 assert(result && "unknown CombiningKind");
8491 return selectPassthru(b, mask, result, acc);
8492}
8493
8494//===----------------------------------------------------------------------===//
8495// StepOp
8496//===----------------------------------------------------------------------===//
8497
8498void StepOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
8499 SetIntRangeFn setResultRanges) {
8500 auto resultType = cast<VectorType>(getType());
8501 if (resultType.isScalable()) {
8502 return;
8503 }
8504 unsigned bitwidth = ConstantIntRanges::getStorageBitwidth(resultType);
8505 // The result holds the sequence [0, 1, ..., N-1], with each value truncated
8506 // to the result element type.
8507 uint64_t maxIndex = resultType.getDimSize(0) - 1;
8508 APInt umin = APInt::getZero(bitwidth);
8509 APInt umax = APInt::getMaxValue(bitwidth).ugt(maxIndex)
8510 ? APInt(bitwidth, maxIndex)
8511 : APInt::getMaxValue(bitwidth);
8512 setResultRanges(getResult(), ConstantIntRanges::fromUnsigned(umin, umax));
8513}
8514
8515namespace {
8516
8517/// Fold `vector.step -> arith.cmpi` when the step value is compared to a
8518/// constant large enough such that the result is the same at all indices.
8519///
8520/// For example, rewrite the 'greater than' comparison below,
8521///
8522/// ```mlir
8523/// %cst = arith.constant dense<7> : vector<3xindex>
8524/// %stp = vector.step : vector<3xindex>
8525/// %out = arith.cmpi ugt, %stp, %cst : vector<3xindex>
8526/// ```
8527///
8528/// as,
8529///
8530/// ```mlir
8531/// %out = arith.constant dense<false> : vector<3xi1>.
8532/// ```
8533///
8534/// Above `[0, 1, 2] > [7, 7, 7]` => `[false, false, false]`. Because the result
8535/// is false at ALL indices we fold. If the constant was 1, then
8536/// `[0, 1, 2] > [1, 1, 1]` => `[false, false, true]` and we do fold,
8537/// conservatively preferring the 'compact' vector.step representation.
8538///
8539/// Note: this folder only works for the case where the constant (`%cst` above)
8540/// is the second operand of the comparison. The arith.cmpi canonicalizer will
8541/// ensure that constants are always second (on the right).
8542struct StepCompareFolder : public OpRewritePattern<StepOp> {
8543 using Base::Base;
8544
8545 LogicalResult matchAndRewrite(StepOp stepOp,
8546 PatternRewriter &rewriter) const override {
8547 const int64_t stepSize = stepOp.getResult().getType().getNumElements();
8548
8549 for (OpOperand &use : stepOp.getResult().getUses()) {
8550 auto cmpiOp = dyn_cast<arith::CmpIOp>(use.getOwner());
8551 if (!cmpiOp)
8552 continue;
8553
8554 // arith.cmpi canonicalizer makes constants final operands.
8555 const unsigned stepOperandNumber = use.getOperandNumber();
8556 if (stepOperandNumber != 0)
8557 continue;
8558
8559 // Check that operand 1 is a constant.
8560 unsigned constOperandNumber = 1;
8561 Value otherOperand = cmpiOp.getOperand(constOperandNumber);
8562 std::optional<int64_t> maybeConstValue =
8563 getConstantIntValue(otherOperand);
8564 if (!maybeConstValue.has_value())
8565 continue;
8566
8567 int64_t constValue = maybeConstValue.value();
8568 arith::CmpIPredicate pred = cmpiOp.getPredicate();
8569
8570 auto maybeSplat = [&]() -> std::optional<bool> {
8571 // Handle ult (unsigned less than) and uge (unsigned greater equal).
8572 if ((pred == arith::CmpIPredicate::ult ||
8573 pred == arith::CmpIPredicate::uge) &&
8574 stepSize <= constValue)
8575 return pred == arith::CmpIPredicate::ult;
8576
8577 // Handle ule and ugt.
8578 if ((pred == arith::CmpIPredicate::ule ||
8579 pred == arith::CmpIPredicate::ugt) &&
8580 stepSize - 1 <= constValue) {
8581 return pred == arith::CmpIPredicate::ule;
8582 }
8583
8584 // Handle eq and ne.
8585 if ((pred == arith::CmpIPredicate::eq ||
8586 pred == arith::CmpIPredicate::ne) &&
8587 stepSize <= constValue)
8588 return pred == arith::CmpIPredicate::ne;
8589
8590 return std::nullopt;
8591 }();
8592
8593 if (!maybeSplat.has_value())
8594 continue;
8595
8596 rewriter.setInsertionPointAfter(cmpiOp);
8597
8598 auto type = dyn_cast<VectorType>(cmpiOp.getResult().getType());
8599 if (!type)
8600 continue;
8601
8602 auto boolAttr = DenseElementsAttr::get(type, maybeSplat.value());
8603 Value splat = mlir::arith::ConstantOp::create(rewriter, cmpiOp.getLoc(),
8604 type, boolAttr);
8605
8606 rewriter.replaceOp(cmpiOp, splat);
8607 return success();
8608 }
8609
8610 return failure();
8611 }
8612};
8613} // namespace
8614
8615void StepOp::getCanonicalizationPatterns(RewritePatternSet &results,
8616 MLIRContext *context) {
8617 results.add<StepCompareFolder>(context);
8618}
8619
8620//===----------------------------------------------------------------------===//
8621// Vector Masking Utilities
8622//===----------------------------------------------------------------------===//
8623
8624/// Create the vector.yield-ended region of a vector.mask op with `maskableOp`
8625/// as masked operation.
8626void mlir::vector::createMaskOpRegion(OpBuilder &builder,
8627 Operation *maskableOp) {
8628 assert(maskableOp->getBlock() && "MaskableOp must be inserted into a block");
8629 Block *insBlock = builder.getInsertionBlock();
8630 // Create a block and move the op to that block.
8631 insBlock->getOperations().splice(
8632 insBlock->begin(), maskableOp->getBlock()->getOperations(), maskableOp);
8633 YieldOp::create(builder, maskableOp->getLoc(), maskableOp->getResults());
8634}
8635
8636/// Creates a vector.mask operation around a maskable operation. Returns the
8637/// vector.mask operation if the mask provided is valid. Otherwise, returns
8638/// the maskable operation itself.
8639Operation *mlir::vector::maskOperation(OpBuilder &builder,
8640 Operation *maskableOp, Value mask,
8641 Value passthru) {
8642 if (!mask)
8643 return maskableOp;
8644 if (passthru)
8645 return MaskOp::create(builder, maskableOp->getLoc(),
8646 maskableOp->getResultTypes(), mask, passthru,
8647 maskableOp, createMaskOpRegion);
8648 return MaskOp::create(builder, maskableOp->getLoc(),
8649 maskableOp->getResultTypes(), mask, maskableOp,
8651}
8652
8653/// Creates a vector select operation that picks values from `newValue` or
8654/// `passthru` for each result vector lane based on `mask`. This utility is used
8655/// to propagate the pass-thru value of vector.mask or for cases where only the
8656/// pass-thru value propagation is needed. VP intrinsics do not support
8657/// pass-thru values and every mask-out lane is set to poison. LLVM backends are
8658/// usually able to match op + select patterns and fold them into a native
8659/// target instructions.
8660Value mlir::vector::selectPassthru(OpBuilder &builder, Value mask,
8661 Value newValue, Value passthru) {
8662 if (!mask)
8663 return newValue;
8664
8665 return arith::SelectOp::create(builder, newValue.getLoc(), newValue.getType(),
8666 mask, newValue, passthru);
8667}
8668
8669//===----------------------------------------------------------------------===//
8670// InterleaveOp
8671//===----------------------------------------------------------------------===//
8672
8673namespace {
8674
8675/// This folder works on the following round-trip identity:
8676/// interleave(deinterleave(x).even, deinterleave(x).odd) -> x
8677struct InterleaveDeinterleaveFolder : public OpRewritePattern<InterleaveOp> {
8678 using Base::Base;
8679
8680 LogicalResult matchAndRewrite(InterleaveOp interleaveOp,
8681 PatternRewriter &rewriter) const override {
8682 auto lhsDefOp = interleaveOp.getLhs().getDefiningOp<DeinterleaveOp>();
8683 auto rhsDefOp = interleaveOp.getRhs().getDefiningOp<DeinterleaveOp>();
8684 if (!lhsDefOp || !rhsDefOp || lhsDefOp != rhsDefOp)
8685 return failure();
8686 for (auto [idx, operand] : llvm::enumerate(interleaveOp.getOperands())) {
8687 if (cast<OpResult>(operand).getResultNumber() != idx)
8688 return failure();
8689 }
8690 rewriter.replaceOp(interleaveOp, lhsDefOp.getSource());
8691 return success();
8692 }
8693};
8694} // namespace
8695
8696void InterleaveOp::getCanonicalizationPatterns(RewritePatternSet &results,
8697 MLIRContext *context) {
8698 results.add<InterleaveDeinterleaveFolder>(context);
8699}
8700
8701OpFoldResult InterleaveOp::fold(FoldAdaptor adaptor) {
8702 // interleave(splat(x), splat(x)) -> widened splat(x)
8703 auto splat = dyn_cast_if_present<SplatElementsAttr>(adaptor.getLhs());
8704 if (!splat || adaptor.getLhs() != adaptor.getRhs())
8705 return {};
8706 return SplatElementsAttr::get(getResultVectorType(),
8707 splat.getSplatValue<Attribute>());
8708}
8709
8710std::optional<SmallVector<int64_t, 4>>
8711InterleaveOp::getIterationShapeToUnroll() {
8712 return llvm::to_vector<4>(getResultVectorType().getShape());
8713}
8714
8715//===----------------------------------------------------------------------===//
8716// DeinterleaveOp
8717//===----------------------------------------------------------------------===//
8718
8719std::optional<SmallVector<int64_t, 4>>
8720DeinterleaveOp::getIterationShapeToUnroll() {
8721 return llvm::to_vector<4>(getResultVectorType().getShape());
8722}
8723
8724//===----------------------------------------------------------------------===//
8725// VectorDialect canonicalization
8726//===----------------------------------------------------------------------===//
8727
8728namespace {
8729/// A broadcast of a scalar condition selects the same arm in every lane.
8730struct SelectBroadcastCondition : OpRewritePattern<arith::SelectOp> {
8731 using Base::Base;
8732
8733 LogicalResult matchAndRewrite(arith::SelectOp select,
8734 PatternRewriter &rewriter) const override {
8735 auto broadcast = select.getCondition().getDefiningOp<BroadcastOp>();
8736 if (!broadcast || !broadcast.getSourceType().isSignlessInteger(1))
8737 return failure();
8738 rewriter.modifyOpInPlace(select, [&] {
8739 select.getConditionMutable().assign(broadcast.getSource());
8740 });
8741 return success();
8742 }
8743};
8744} // namespace
8745
8746void VectorDialect::getCanonicalizationPatterns(
8747 RewritePatternSet &results) const {
8748 results.add<SelectBroadcastCondition>(getContext());
8749}
8750
8751//===----------------------------------------------------------------------===//
8752// TableGen'd op method definitions
8753//===----------------------------------------------------------------------===//
8754
8755#define GET_ATTRDEF_CLASSES
8756#include "mlir/Dialect/Vector/IR/VectorAttributes.cpp.inc"
8757
8758#define GET_OP_CLASSES
8759#include "mlir/Dialect/Vector/IR/VectorOps.cpp.inc"
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
getNumOperands() - 1))) return failure()
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static LogicalResult extractStrides(AffineExpr e, AffineExpr multiplicativeFactor, MutableArrayRef< AffineExpr > strides, AffineExpr &offset)
Takes a single AffineExpr e and populates the strides array with the strides expressions for each dim...
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
static Value getBase(Value v)
Looks through known "view-like" ops to find the base memref.
lhs
static bool isLegalToInline(InlinerInterface &interface, Region *src, Region *insertRegion, bool shouldCloneInlinedRegion, IRMapping &valueMapping)
Utility to check that all of the operations within 'src' can be inlined.
static SmallVector< unsigned > extractPosition(ArrayRef< int64_t > indices)
Convert the value of a DenseI64ArrayAttr to a vector of unsigned indices.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
b getContext())
auto load
*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 inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
static ParseResult parseBoolAttr(OpAsmParser &parser, BoolAttr &result)
static void printBoolAttr(OpAsmPrinter &printer, Operation *, BoolAttr attr)
static std::optional< VectorShape > vectorShape(Type type)
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static void contract(RootOrderingGraph &graph, ArrayRef< Value > cycle, const DenseMap< Value, unsigned > &parentDepths, DenseMap< Value, Value > &actualSource, DenseMap< Value, Value > &actualTarget)
Contracts the specified cycle in the given graph in-place.
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 Value broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
static VectorType getVectorType(Type scalarTy, const VectorizationStrategy *strategy)
Returns the vector type resulting from applying the provided vectorization strategy on the scalar typ...
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
static VectorType trimLeadingUnitDims(VectorType oldType, bool trimOnlyOneDim=false, bool allowRank0=false)
static MaskFormat getMaskFormat(Value mask)
Helper method to classify a mask value.
Definition VectorOps.cpp:73
static OpFoldResult foldShuffleIdentityMask(ShuffleOp op)
Fold shuffle V1, V2, [0, 1, 2, 3] : <4xi32>, <2xi32> -> V1.
static LogicalResult foldExtractOpFromExtractChain(ExtractOp extractOp)
Fold the result of chains of ExtractOp in place by simply concatenating the positions.
static OpFoldResult foldFromElementsToElements(FromElementsOp fromElementsOp)
Folds vector.from_elements(vector.to_elements(vector)) into vector.
static bool hasZeroDimVectors(Operation *op)
Returns true if the operation has a 0-D vector type operand or result.
static void printTransferAttrs(OpAsmPrinter &p, VectorTransferOpInterface op)
static Value foldScalarExtractFromFromElements(ExtractOp extractOp)
Try to fold the extraction of a scalar from a vector defined by vector.from_elements.
static std::vector< std::pair< int64_t, int64_t > > getDimPairForIterTy(ContractionOp *contract, IteratorType targetIteratorType)
static Attribute convertNumericAttr(Attribute attr, Type expectedType)
Converts numeric attributes to the expected type.
static Value foldExtractFromExtractStrided(ExtractOp extractOp)
Fold an ExtractOp from ExtractStridedSliceOp.
static llvm::SetVector< int64_t > computeBroadcastedUnitDims(ArrayRef< int64_t > srcShape, ArrayRef< int64_t > dstShape)
Return the dimensions of the result vector that were formerly ones in the source tensor and thus corr...
static Value foldExtractFromBroadcast(ExtractOp extractOp)
Fold extract(broadcast(X)) to either extract(X) or just X.
static LogicalResult foldToElementsFromElements(ToElementsOp toElementsOp, SmallVectorImpl< OpFoldResult > &results)
Folds vector.to_elements(vector.from_elements(e0, e1, ...)) into (e0, e1, ...).
static Attribute foldPoisonSrcExtractOp(Attribute srcAttr)
Fold a vector extract from is a poison source.
static std::optional< int64_t > getOneSidedReductionDim(vector::ContractionOp *contract)
static LogicalResult foldBroadcastOfShapeCast(BroadcastOp broadcastOp)
static OpFoldResult foldShufflePoisonInputs(MLIRContext *context, Attribute v1Attr, Attribute v2Attr)
Fold shuffle poison, poison -> poison.
static bool isSupportedCombiningKind(CombiningKind combiningKind, Type elementType)
static Attribute foldPoisonIndexInsertExtractOp(MLIRContext *context, ArrayRef< int64_t > staticPos, int64_t poisonVal)
Fold an insert or extract operation into an poison value when a poison index is found at any dimensio...
MaskFormat
Helper enum to classify mask value.
Definition VectorOps.cpp:63
static ArrayAttr makeI64ArrayAttr(ArrayRef< int64_t > values, MLIRContext *context)
static unsigned getEffectiveVectorRankForXferOp(ShapedType shapedType, VectorType vectorType)
Returns the effective rank of the vector to read/write for Xfer Ops.
static OpFoldResult foldFromElementsToConstant(FromElementsOp fromElementsOp, ArrayRef< Attribute > elements)
Fold vector.from_elements to a constant when all operands are constants.
static LogicalResult incSlicePosition(MutableArrayRef< int64_t > position, ArrayRef< int64_t > shape, ArrayRef< int64_t > offsets)
static Value extractInsertFoldConstantOp(OpType op, AdaptorType adaptor, SmallVectorImpl< Value > &operands)
If the dynamic indices of extractOp or insertOp are in fact constants, then fold it.
static LogicalResult foldToElementsOfBroadcast(ToElementsOp toElementsOp, SmallVectorImpl< OpFoldResult > &results)
Folds vector.to_elements(vector.broadcast(x)) for the scalar case only.
static bool isStepIndexArray(ArrayRef< T > idxArr, uint64_t begin, size_t width)
static LogicalResult isIntegerArrayAttrConfinedToRange(OpType op, ArrayAttr arrayAttr, int64_t min, int64_t max, StringRef attrName, bool halfOpen=true)
static bool haveSameDefiningOp(OperandRange operands, Operation *defOp)
Returns true if all the operands are defined by defOp.
static int64_t getResultIndex(AffineMap map, AffineExpr targetExpr)
static LogicalResult isIntegerArrayAttrConfinedToShape(OpType op, ArrayAttr arrayAttr, ArrayRef< int64_t > shape, StringRef attrName, bool halfOpen=true, int64_t min=0)
static bool isSplatWriteConsistentWithMaskedRead(vector::TransferWriteOp write, vector::TransferReadOp read)
Check if write is of a constant splat and the masked read is padded with the same splat value – meani...
static LogicalResult isSumOfIntegerArrayAttrConfinedToShape(OpType op, ArrayAttr arrayAttr1, ArrayAttr arrayAttr2, ArrayRef< int64_t > shape, StringRef attrName1, StringRef attrName2, bool halfOpen=true, int64_t min=1)
static Attribute foldDenseElementsAttrDestInsertOp(InsertOp insertOp, Attribute srcAttr, Attribute dstAttr, int64_t maxVectorSizeFoldThreshold)
static LogicalResult foldTransferFullMask(TransferOp op)
static SmallVector< IntType > extractVector(ArrayAttr arrayAttr)
static bool isValidPositiveIndexOrPoison(int64_t index, int64_t poisonValue, int64_t maxIndex)
static OpFoldResult foldShuffleConstantInputs(ShuffleOp op, Attribute v1Attr, Attribute v2Attr)
Fold a shuffle of constant 1-D inputs by evaluating the mask.
static OpFoldResult foldExtractStridedSliceNonSplatConstant(ExtractStridedSliceOp op, Attribute foldInput)
static LogicalResult verifyPermutationMap(AffineMap permutationMap, EmitFun emitOpError)
static LogicalResult rewriteFromElementsAsBroadcast(FromElementsOp fromElementsOp, PatternRewriter &rewriter)
Rewrite vector.from_elements as vector.broadcast if the elements are the same.
static Value foldInsertUseChain(InsertOp insertOp)
Folder to replace the dest operand of the insert op with the root dest of the insert op use chain.
static bool isBroadcastLike(Operation *op)
All BroadcastOps, as well as ShapeCastOps that only prepend 1s, are considered to be 'broadcastlike'.
static LogicalResult isIntegerArrayAttrSmallerThanShape(OpType op, ArrayAttr arrayAttr, ArrayRef< int64_t > shape, StringRef attrName)
static Value foldExtractFromShapeCast(ExtractOp extractOp)
static LogicalResult verifyTransferOp(VectorTransferOpInterface op, ShapedType shapedType, VectorType vectorType, VectorType maskType, VectorType inferredMaskType, AffineMap permutationMap, ArrayAttr inBounds)
static bool isInBounds(TransferOp op, int64_t resultIdx, int64_t indicesIdx)
static LogicalResult verifyOutputShape(ContractionOp op, VectorType lhsType, VectorType rhsType, Type accType, Type resType, const std::vector< std::pair< int64_t, int64_t > > &contractingDimMap, const std::vector< std::pair< int64_t, int64_t > > &batchDimMap)
static bool verifyDimMap(VectorType lhsType, VectorType rhsType, const std::vector< std::pair< int64_t, int64_t > > &map)
static Type inferStridedSliceOpResultType(VectorType vectorType, ArrayAttr offsets, ArrayAttr sizes, ArrayAttr strides)
static OpFoldResult foldShufflePoisonOperandToMask(ShuffleOp op)
If a shuffle operand is poison, replace all mask indices that reference it with kPoisonIndex.
static LogicalResult foldSize1TransferPermutationMap(TransferOp op)
When the vector type is vector<1xT>, the permutation map is irrelevant: the single vector lane always...
static Value foldExtractFromShuffle(ExtractOp extractOp)
Fold extractOp coming from ShuffleOp.
static LogicalResult foldTransferInBoundsAttribute(TransferOp op)
static Value foldExtractStridedOpFromInsertChain(ExtractOp extractOp)
Fold extract_op fed from a chain of insertStridedSlice ops.
static int64_t calculateInsertPosition(VectorType destTy, ArrayRef< int64_t > positions)
static Attribute foldDenseElementsAttrSrcExtractOp(ExtractOp extractOp, Attribute srcAttr)
Fold a vector extract extracting from a DenseElementsAttr.
static void populateFromInt64AttrArray(ArrayAttr arrayAttr, SmallVectorImpl< int64_t > &results)
#define mul(a, b)
Rewrite from_elements on multiple scalar extracts as a shape_cast on a single extract.
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap getMinorIdentityMap(unsigned dims, unsigned results, MLIRContext *context)
Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most minor dimensions.
MLIRContext * getContext() const
bool isFunctionOfDim(unsigned position) const
Return true if any affine expression involves AffineDimExpr position.
Definition AffineMap.h:223
bool isMinorIdentity() const
Returns true if this affine map is a minor identity, i.e.
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
unsigned getNumInputs() const
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
SmallVector< unsigned > getBroadcastDims() const
Returns the list of broadcast dimensions (i.e.
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
@ Square
Square brackets surrounding zero or more operands.
virtual ParseResult parseColonTypeList(SmallVectorImpl< Type > &result)=0
Parse a colon followed by a type list, which must have at least one type.
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
MLIRContext * getContext() const
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
ParseResult addTypeToList(Type type, SmallVectorImpl< Type > &result)
Add the specified type to the end of the specified type list and return success.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual ParseResult parseOptionalComma()=0
Parse a , token if present.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseComma()=0
Parse a , token.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
ParseResult parseKeywordType(const char *keyword, Type &result)
Parse a keyword followed by a type.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
virtual void printAttribute(Attribute attr)
Base storage class appearing in an attribute.
Attributes are known-constant values of operations.
Definition Attributes.h:25
Dialect & getDialect() const
Get the dialect this attribute is registered to.
Definition Attributes.h:58
bool empty()
Definition Block.h:173
OpListType & getOperations()
Definition Block.h:162
Operation & front()
Definition Block.h:178
Operation & back()
Definition Block.h:177
iterator begin()
Definition Block.h:168
static BoolAttr get(MLIRContext *context, bool value)
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
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:171
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:175
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
IntegerType getI1Type()
Definition Builders.cpp:61
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
MLIRContext * getContext() const
Definition Builders.h:56
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
IndexType getIndexType()
Definition Builders.cpp:59
ArrayAttr getBoolArrayAttr(ArrayRef< bool > values)
Definition Builders.cpp:279
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:327
static ConstantIntRanges fromUnsigned(const APInt &umin, const APInt &umax)
Create an ConstantIntRanges with the unsigned minimum and maximum equal to umin and umax and the sign...
static unsigned getStorageBitwidth(Type type)
Return the bitwidth that should be used for integer ranges describing type.
The main mechanism for performing data layout queries.
static DataLayout closest(Operation *op)
Returns the layout of the closest parent operation carrying layout info.
llvm::TypeSize getTypeSizeInBits(Type t) const
Returns the size in bits of the given type in the current scope.
An attribute that represents a reference to a dense vector or tensor object.
std::enable_if_t<!std::is_base_of< Attribute, T >::value||std::is_same< Attribute, T >::value, T > getSplatValue() const
Return the splat value for this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
virtual Operation * materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc)
Registered hook to materialize a single constant operation from a given attribute value with the desi...
Definition Dialect.h:83
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
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:71
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
ParseResult parseTrailingOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None)
Parse zero or more trailing SSA comma-separated trailing operand references with a specified surround...
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
virtual void printCustomOrGenericOp(Operation *op)=0
Prints the entire operation with the custom assembly form, if available, or the generic assembly form...
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
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Block * getInsertionBlock() const
Return the block the current insertion point belongs to.
Definition Builders.h:445
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 implements the operand iterators for the Operation class.
Definition ValueRange.h:44
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:377
void dropAllUses()
Drop all uses of results of this operation.
Definition Operation.h:888
void setOperand(unsigned idx, Value value)
Definition Operation.h:378
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
operand_type_range getOperandTypes()
Definition Operation.h:424
result_type_range getResultTypes()
Definition Operation.h:455
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
result_range getResults()
Definition Operation.h:442
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:431
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
bool empty()
Definition Region.h:60
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...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
T * allocate()
Allocate an instance of the provided type.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrIndexOrFloat() const
Return true if this is an integer (of any signedness), index, or float type.
Definition Types.cpp:122
bool isF32() const
Definition Types.cpp:40
bool isIntOrIndex() const
Return true if this is an integer (of any signedness) or an index type.
Definition Types.cpp:114
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
static FailureOr< bool > areEqual(const Variable &var1, const Variable &var2)
Compute whether the given variables are equal.
static FailureOr< int64_t > computeConstantDelta(Value value1, Value value2, std::optional< int64_t > dim1=std::nullopt, std::optional< int64_t > dim2=std::nullopt)
Compute a constant delta between the given two values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
This is a builder type that keeps local references to arguments.
Builder & setElementType(Type newElementType)
Specialization of arith.constant op that returns an integer of index type.
Definition Arith.h:92
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto Speculatable
constexpr auto NotSpeculatable
FailureOr< int64_t > fullyComposeAndComputeConstantDelta(Value value1, Value value2)
Compute a constant delta of the given two values.
AttrTypeReplacer.
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
FailureOr< std::optional< SmallVector< Value > > > bubbleDownInPlaceMemorySpaceCastImpl(OpOperand &operand, ValueRange results)
Tries to bubble-down inplace a MemorySpaceCastOpInterface operation referenced by operand.
bool hasNegativeStaticStride(MemRefType memRefTy)
Returns true if any stride of memRefTy is statically known to be negative.
LogicalResult foldMemRefCast(Operation *op, Value inner=nullptr)
This is a common utility used for patterns of the form "someop(memref.cast) -> someop".
Definition MemRefOps.cpp:48
Operation::operand_range getIndices(Operation *op)
Get the indices that the given load/store operation is operating on.
Definition Utils.cpp:18
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:744
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
LogicalResult foldTensorCast(Operation *op)
Performs folding of any operand of op if it comes from a tensor::CastOp that can be folded.
detail::poison_attr_matcher m_Poison()
Matches a poison constant (any attribute implementing PoisonAttrInterface).
Definition UBMatchers.h:46
Value makeArithReduction(OpBuilder &b, Location loc, CombiningKind kind, Value v1, Value acc, arith::FastMathFlagsAttr fastmath=nullptr, Value mask=nullptr)
Returns the result value of reducing two scalar/vector values with the corresponding arith operation.
ArrayAttr getVectorSubscriptAttr(Builder &b, ArrayRef< int64_t > values)
Returns an integer array attribute containing the given values using the integer type required for su...
Operation * maskOperation(OpBuilder &builder, Operation *maskableOp, Value mask, Value passthru=Value())
Creates a vector.mask operation around a maskable operation.
void buildTerminatedBody(OpBuilder &builder, Location loc)
Default callback to build a region with a 'vector.yield' terminator with no arguments.
std::optional< int64_t > getConstantVscaleMultiplier(Value value)
If value is a constant multiple of vector.vscale (e.g.
AffineMap getTransferMinorIdentityMap(ShapedType shapedType, VectorType vectorType)
Build the default minor identity map suitable for a vector transfer.
bool checkSameValueRAW(TransferWriteOp defWrite, TransferReadOp read)
Return true if the transfer_write fully writes the data accessed by the transfer_read.
ConstantMaskKind
Predefined constant_mask kinds.
Definition VectorOps.h:63
BroadcastableToResult isBroadcastableTo(Type srcType, VectorType dstVectorType, std::pair< VectorDim, VectorDim > *mismatchingDims=nullptr)
Return whether srcType can be broadcast to dstVectorType under the semantics of the vector....
VectorType inferTransferOpMaskType(VectorType vecType, AffineMap permMap)
Infers the mask type for a transfer op given its vector type and permutation map.
Value selectPassthru(OpBuilder &builder, Value mask, Value newValue, Value passthru)
Creates a vector select operation that picks values from newValue or passthru for each result vector ...
bool isDisjointTransferIndices(VectorTransferOpInterface transferA, VectorTransferOpInterface transferB, bool testDynamicValueUsingBounds=false)
Return true if we can prove that the transfer operations access disjoint memory, without requring the...
bool isDisjointTransferSet(VectorTransferOpInterface transferA, VectorTransferOpInterface transferB, bool testDynamicValueUsingBounds=false)
Return true if we can prove that the transfer operations access disjoint memory, requiring the operat...
bool checkSameValueWAW(TransferWriteOp write, TransferWriteOp priorWrite)
Return true if the write op fully over-write the priorWrite transfer_write op.
SmallVector< int64_t > getAsIntegers(ArrayRef< Value > values)
Returns the integer numbers in values.
void populateVectorToVectorCanonicalizationPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Collect a set of vector-to-vector canonicalization patterns.
void createMaskOpRegion(OpBuilder &builder, Operation *maskableOp)
Create the vector.yield-ended region of a vector.mask op with maskableOp as masked operation.
SmallVector< Value > getAsValues(OpBuilder &builder, Location loc, ArrayRef< OpFoldResult > foldResults)
Convert foldResults into Values.
Value getVectorReductionOp(arith::AtomicRMWKind op, OpBuilder &builder, Location loc, Value vector)
Returns the value obtained by reducing the vector into a scalar using the operation kind associated w...
BroadcastableToResult
Models whether srcType can be broadcast to dstVectorType under the semantics of the vector....
Definition VectorOps.h:71
IntegerType getVectorSubscriptType(Builder &builder)
Returns the integer type required for subscripts in the vector dialect.
Include the generated interface declarations.
AffineMap simplifyAffineMap(AffineMap map)
Simplifies an affine map by simplifying its underlying AffineExpr results.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::function_ref< void(Value, const ConstantIntRanges &)> SetIntRangeFn
The type of the setResultRanges callback provided to ops implementing InferIntRangeInterface.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2)
Return true if ofr1 and ofr2 are the same integer constant attribute values or the same SSA value.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
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...
LogicalResult emitOptionalError(std::optional< Location > loc, Args &&...args)
Overloads of the above emission functions that take an optionally null location.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:125
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
StorageUniquer::StorageAllocator AttributeStorageAllocator
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
SmallVector< int64_t > getI64SubArray(ArrayAttr arrayAttr, unsigned dropFront=0, unsigned dropBack=0)
Helper to return a subset of arrayAttr as a vector of int64_t.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:496
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
AffineMap compressUnusedDims(AffineMap map)
Drop the dims that are not used.
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
LogicalResult verifyElementTypesMatch(Operation *op, ShapedType lhs, ShapedType rhs, StringRef lhsName, StringRef rhsName)
Verify that two shaped types have matching element types.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
SmallVector< T > applyPermutationMap(AffineMap map, llvm::ArrayRef< T > source)
Apply a permutation from map to source and return the result.
Definition AffineMap.h:677
llvm::SmallBitVector getUnusedDimsBitVector(ArrayRef< AffineMap > maps)
int64_t linearize(ArrayRef< int64_t > offsets, ArrayRef< int64_t > basis)
Return the linearized index of 'offsets' w.r.t.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
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.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
Return a fused vector::ContractionOp which represents a patterns such as:
LogicalResult matchAndRewrite(AddOpType addOp, PatternRewriter &rewriter) const override
Canonicalize vector.to_elements(vector.broadcast(v)) where v is a vector.
LogicalResult matchAndRewrite(ToElementsOp toElementsOp, PatternRewriter &rewriter) const override
This is the representation of an operand reference.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern Base
Type alias to allow derived classes to inherit constructors with using Base::Base;.
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.
static BitmaskEnumStorage * construct(AttributeStorageAllocator &allocator, const KeyTy &key)
bool operator==(const KeyTy &key) const