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