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