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