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