MLIR 24.0.0git
VectorUtils.cpp
Go to the documentation of this file.
1//===- VectorUtils.cpp - MLIR Utilities for VectorOps ------------------===//
2//
3// Part of the MLIR 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 utility methods for working with the Vector dialect.
10//
11//===----------------------------------------------------------------------===//
12
14
24#include "mlir/IR/Builders.h"
25#include "mlir/IR/IntegerSet.h"
26#include "mlir/IR/Matchers.h"
27#include "mlir/IR/Operation.h"
29#include "mlir/Support/LLVM.h"
30
31#include "llvm/ADT/DenseSet.h"
32#include "llvm/Support/DebugLog.h"
33#include "llvm/Support/InterleavedRange.h"
34
35#define DEBUG_TYPE "vector-utils"
36
37using namespace mlir;
38
39/// Helper function that creates a memref::DimOp or tensor::DimOp depending on
40/// the type of `source`.
42 int64_t dim) {
43 if (isa<UnrankedMemRefType, MemRefType>(source.getType()))
44 return b.createOrFold<memref::DimOp>(loc, source, dim);
45 if (isa<UnrankedTensorType, RankedTensorType>(source.getType()))
46 return b.createOrFold<tensor::DimOp>(loc, source, dim);
47 llvm_unreachable("Expected MemRefType or TensorType");
48}
49
50/// Given the n-D transpose pattern 'transp', return true if 'dim0' and 'dim1'
51/// should be transposed with each other within the context of their 2D
52/// transposition slice.
53///
54/// Example 1: dim0 = 0, dim1 = 2, transp = [2, 1, 0]
55/// Return true: dim0 and dim1 are transposed within the context of their 2D
56/// transposition slice ([1, 0]).
57///
58/// Example 2: dim0 = 0, dim1 = 1, transp = [2, 1, 0]
59/// Return true: dim0 and dim1 are transposed within the context of their 2D
60/// transposition slice ([1, 0]). Paradoxically, note how dim1 (1) is *not*
61/// transposed within the full context of the transposition.
62///
63/// Example 3: dim0 = 0, dim1 = 1, transp = [2, 0, 1]
64/// Return false: dim0 and dim1 are *not* transposed within the context of
65/// their 2D transposition slice ([0, 1]). Paradoxically, note how dim0 (0)
66/// and dim1 (1) are transposed within the full context of the of the
67/// transposition.
69 ArrayRef<int64_t> transp) {
70 // Perform a linear scan along the dimensions of the transposed pattern. If
71 // dim0 is found first, dim0 and dim1 are not transposed within the context of
72 // their 2D slice. Otherwise, 'dim1' is found first and they are transposed.
73 for (int64_t permDim : transp) {
74 if (permDim == dim0)
75 return false;
76 if (permDim == dim1)
77 return true;
78 }
79
80 llvm_unreachable("Ill-formed transpose pattern");
81}
82
83FailureOr<std::pair<int, int>>
84mlir::vector::isTranspose2DSlice(vector::TransposeOp op) {
85 VectorType srcType = op.getSourceVectorType();
86 SmallVector<int64_t> srcGtOneDims;
87 for (auto [index, size] : llvm::enumerate(srcType.getShape()))
88 if (size > 1)
89 srcGtOneDims.push_back(index);
90
91 if (srcGtOneDims.size() != 2)
92 return failure();
93
94 // Check whether the two source vector dimensions that are greater than one
95 // must be transposed with each other so that we can apply one of the 2-D
96 // transpose patterns. Otherwise, these patterns are not applicable.
97 if (!areDimsTransposedIn2DSlice(srcGtOneDims[0], srcGtOneDims[1],
98 op.getPermutation()))
99 return failure();
100
101 return std::pair<int, int>(srcGtOneDims[0], srcGtOneDims[1]);
102}
103
104/// Constructs a permutation map from memref indices to vector dimension.
105///
106/// The implementation uses the knowledge of the mapping of enclosing loop to
107/// vector dimension. `enclosingLoopToVectorDim` carries this information as a
108/// map with:
109/// - keys representing "vectorized enclosing loops";
110/// - values representing the corresponding vector dimension.
111/// The algorithm traverses "vectorized enclosing loops" and extracts the
112/// at-most-one MemRef index that is invariant along said loop. This index is
113/// guaranteed to be at most one by construction: otherwise the MemRef is not
114/// vectorizable.
115/// If this invariant index is found, it is added to the permutation_map at the
116/// proper vector dimension.
117/// If no index is found to be invariant, 0 is added to the permutation_map and
118/// corresponds to a vector broadcast along that dimension.
119///
120/// Returns an empty AffineMap if `enclosingLoopToVectorDim` is empty,
121/// signalling that no permutation map can be constructed given
122/// `enclosingLoopToVectorDim`.
123///
124/// Examples can be found in the documentation of `makePermutationMap`, in the
125/// header file.
128 const DenseMap<Operation *, unsigned> &enclosingLoopToVectorDim) {
129 if (enclosingLoopToVectorDim.empty())
130 return AffineMap();
131 MLIRContext *context =
132 enclosingLoopToVectorDim.begin()->getFirst()->getContext();
133 SmallVector<AffineExpr> perm(enclosingLoopToVectorDim.size(),
134 getAffineConstantExpr(0, context));
135
136 for (auto kvp : enclosingLoopToVectorDim) {
137 assert(kvp.second < perm.size());
138 auto invariants = affine::getInvariantAccesses(
139 cast<affine::AffineForOp>(kvp.first).getInductionVar(), indices);
140 unsigned numIndices = indices.size();
141 unsigned countInvariantIndices = 0;
142 for (unsigned dim = 0; dim < numIndices; ++dim) {
143 if (!invariants.count(indices[dim])) {
144 assert(perm[kvp.second] == getAffineConstantExpr(0, context) &&
145 "permutationMap already has an entry along dim");
146 perm[kvp.second] = getAffineDimExpr(dim, context);
147 } else {
148 ++countInvariantIndices;
149 }
150 }
151 assert((countInvariantIndices == numIndices ||
152 countInvariantIndices == numIndices - 1) &&
153 "Vectorization prerequisite violated: at most 1 index may be "
154 "invariant wrt a vectorized loop");
155 (void)countInvariantIndices;
156 }
157 return AffineMap::get(indices.size(), 0, perm, context);
158}
159
160/// Implementation detail that walks up the parents and records the ones with
161/// the specified type.
162/// TODO: could also be implemented as a collect parents followed by a
163/// filter and made available outside this file.
164template <typename T>
167 auto *current = block->getParentOp();
168 while (current) {
169 if ([[maybe_unused]] auto typedParent = dyn_cast<T>(current)) {
170 assert(res.count(current) == 0 && "Already inserted");
171 res.insert(current);
172 }
173 current = current->getParentOp();
174 }
175 return res;
176}
177
178/// Returns the enclosing AffineForOp, from closest to farthest.
182
183AffineMap mlir::makePermutationMap(
184 Block *insertPoint, ArrayRef<Value> indices,
185 const DenseMap<Operation *, unsigned> &loopToVectorDim) {
186 DenseMap<Operation *, unsigned> enclosingLoopToVectorDim;
187 auto enclosingLoops = getEnclosingforOps(insertPoint);
188 for (auto *forInst : enclosingLoops) {
189 auto it = loopToVectorDim.find(forInst);
190 if (it != loopToVectorDim.end()) {
191 enclosingLoopToVectorDim.insert(*it);
192 }
193 }
194 return ::makePermutationMap(indices, enclosingLoopToVectorDim);
195}
196
197AffineMap mlir::makePermutationMap(
199 const DenseMap<Operation *, unsigned> &loopToVectorDim) {
200 return makePermutationMap(op->getBlock(), indices, loopToVectorDim);
201}
202
203bool matcher::operatesOnSuperVectorsOf(Operation &op,
204 VectorType subVectorType) {
205 // First, extract the vector type and distinguish between:
206 // a. ops that *must* lower a super-vector (i.e. vector.transfer_read,
207 // vector.transfer_write); and
208 // b. ops that *may* lower a super-vector (all other ops).
209 // The ops that *may* lower a super-vector only do so if the super-vector to
210 // sub-vector ratio exists. The ops that *must* lower a super-vector are
211 // explicitly checked for this property.
212 /// TODO: there should be a single function for all ops to do this so we
213 /// do not have to special case. Maybe a trait, or just a method, unclear atm.
214 VectorType superVectorType;
215 if (auto transfer = dyn_cast<VectorTransferOpInterface>(op)) {
216 superVectorType = transfer.getVectorType();
217 } else if (op.getNumResults() == 0) {
218 if (!isa<func::ReturnOp>(op)) {
219 op.emitError("NYI: assuming only return operations can have 0 "
220 " results at this point");
221 }
222 return false;
223 } else if (op.getNumResults() == 1) {
224 if (auto v = dyn_cast<VectorType>(op.getResult(0).getType())) {
225 superVectorType = v;
226 } else {
227 // Not a vector type.
228 return false;
229 }
230 } else {
231 // Not a vector.transfer and has more than 1 result, fail hard for now to
232 // wake us up when something changes.
233 op.emitError("NYI: operation has more than 1 result");
234 return false;
235 }
236
237 // Get the ratio. If the shapes are incompatible (e.g., different ranks or
238 // non-integer divisibility), the operation does not operate on a super-vector
239 // of the given sub-vector type.
240 auto ratio =
241 computeShapeRatio(superVectorType.getShape(), subVectorType.getShape());
242 return ratio.has_value();
243}
244
245bool vector::isContiguousSlice(MemRefType memrefType, VectorType vectorType) {
246 if (vectorType.isScalable())
247 return false;
248
249 // Ignore a leading sequence of adjacent unit dimensions in the vector.
251 vectorType.getShape().drop_while([](auto v) { return v == 1; });
252 auto vecRank = vectorShape.size();
253
254 // A single element is always contiguous.
255 if (vecRank == 0)
256 return true;
257
258 if (!memrefType.areTrailingDimsContiguous(vecRank))
259 return false;
260
261 // Extract the trailing dims of the input memref
262 auto memrefShape = memrefType.getShape().take_back(vecRank);
263
264 // Compare the dims of `vectorType` against `memrefType`.
265 // All of the dimensions, except the first must match.
266 return llvm::equal(vectorShape.drop_front(), memrefShape.drop_front());
267}
268
269std::optional<StaticTileOffsetRange>
270vector::createUnrollIterator(VectorType vType, int64_t targetRank) {
271 if (vType.getRank() <= targetRank)
272 return {};
273 // Attempt to unroll until targetRank or the first scalable dimension (which
274 // cannot be unrolled).
275 auto shapeToUnroll = vType.getShape().drop_back(targetRank);
276 auto inputScalableVecDimsToUnroll =
277 vType.getScalableDims().drop_back(targetRank);
278 const auto *it = llvm::find(inputScalableVecDimsToUnroll, true);
279 auto firstScalableDim = it - inputScalableVecDimsToUnroll.begin();
280 if (firstScalableDim == 0)
281 return {};
282 // All scalable dimensions should be removed now.
283 inputScalableVecDimsToUnroll =
284 inputScalableVecDimsToUnroll.slice(0, firstScalableDim);
285 assert(!llvm::is_contained(inputScalableVecDimsToUnroll, true) &&
286 "unexpected leading scalable dimension");
287 // Create an unroll iterator for leading dimensions.
288 shapeToUnroll = shapeToUnroll.slice(0, firstScalableDim);
289 return StaticTileOffsetRange(shapeToUnroll, /*unrollStep=*/1);
290}
291
293 Operation *xfer,
294 RewriterBase &rewriter) {
295 auto loc = xfer->getLoc();
296
297 Value base =
299 .Case([&](vector::TransferReadOp readOp) { return readOp.getBase(); })
300 .Case([&](vector::TransferWriteOp writeOp) {
301 return writeOp.getOperand(1);
302 });
303
304 SmallVector<OpFoldResult> mixedSourceDims =
305 hasTensorSemantics ? tensor::getMixedSizes(rewriter, loc, base)
306 : memref::getMixedSizes(rewriter, loc, base);
307 return mixedSourceDims;
308}
309
310bool vector::isLinearizableVector(VectorType type) {
311 return (type.getRank() > 1) && (type.getNumScalableDims() <= 1);
312}
313
314/// Determines whether a mask for xfer_read/write is trivially "all true"
315///
316/// Given all the inputs required to generate a mask (mask sizes and shapes),
317/// and an xfer_read/write operation (indices and the source/destination tensor
318/// shape), determines whether the corresponding mask would be trivially
319/// foldable (i.e., trivially "all true").
320///
321/// Use this method to avoid generating spurious masks and relying on
322/// vectorization post-processing to remove them.
323///
324/// Pre-conditions for a mask to be trivially foldable:
325/// * All involved shapes (mask + destination tensor) are static.
326/// * All indices are constant.
327/// * All mask sizes are constant (including `arith.constant`).
328///
329/// If the pre-conditions are met, the method checks for each destination
330/// dimension `d`:
331/// (1) maskShape[d] <= destDimSize[rankDiff + d]
332/// (2) index[d] + maskSize[d] <= destDimSize[rankDiff + d]
333/// (3) maskShape[d] <= maskSize[d]
334///
335/// rankDiff = rank(dest) - rank(mask).
336///
337/// This method takes a conservative view: it may return false even if the mask
338/// is technically foldable.
339///
340/// EXAMPLE 1 (trivially foldable, all shapes match, mask sizes match the shape
341/// of the dest tensor):
342/// %c0 = arith.constant 0 : index
343/// %mask = vector.create_mask 5, 1
344/// vector.mask %mask {
345/// vector.transfer_write %vecToStore_1, %dest{[%c0, %c0]
346/// {in_bounds = [true, true]}
347/// : vector<5x1xi32>, tensor<5x1xi32>
348/// }
349///
350/// EXAMPLE 2 (not trivially foldable - vector shape exceeds the tensor shape,
351/// mask is required to avoid out-of-bounds write):
352/// %c0 = arith.constant 0 : index
353/// %mask = vector.create_mask 5, 1
354/// vector.mask %mask {
355/// vector.transfer_write %vecToStore_2, %dest[%c0, %c0]
356/// {in_bounds = [true, true]}
357/// : vector<8x1xi32>, tensor<5x1xi32>
358/// }
361 ArrayRef<int64_t> baseShape,
362 ArrayRef<int64_t> maskShape) {
363 // Masking is unavoidable in the case of dynamic tensors.
364 if (ShapedType::isDynamicShape(baseShape))
365 return false;
366
367 // Collect all constant mask sizes.
368 SmallVector<int64_t, 4> cstMaskSizes;
369 for (auto [i, dimSize] : llvm::enumerate(maskSizes)) {
370 if (auto intSize = getConstantIntValue(dimSize)) {
371 cstMaskSizes.push_back(*intSize);
372 }
373 }
374
375 // If any of the mask sizes is non-constant, bail out.
376 if (cstMaskSizes.size() != maskShape.size())
377 return false;
378
379 // Collect all constant indices.
380 SmallVector<int64_t, 4> cstIndices;
381 for (auto [i, idx] : llvm::enumerate(indices)) {
382 APSInt intVal;
383 if (matchPattern(idx, m_ConstantInt(&intVal))) {
384 cstIndices.push_back(intVal.getSExtValue());
385 }
386 }
387
388 // If any of the indices is non-constant, bail out.
389 if (cstIndices.size() != baseShape.size())
390 return false;
391
392 // Go over all destination dims and check (1), (2) and (3). Take into account
393 // that:
394 // * The number of mask sizes will match the rank of the vector to
395 // load/store. This could be lower than the rank of the destination tensor.
396 // * Mask sizes could be larger than the corresponding mask shape (hence
397 // `clamp`).
398 // TODO: The 2nd item should be rejected by the verifier.
399 int64_t rankDiff = baseShape.size() - cstMaskSizes.size();
400 for (auto [i, idx] : llvm::enumerate(cstMaskSizes)) {
401 if (/*(1)*/ maskShape[i] > baseShape[rankDiff + i] ||
402 /*(2)*/ baseShape[rankDiff + i] <
403 (std::clamp(cstMaskSizes[i], int64_t(0), maskShape[i]) +
404 cstIndices[i]) ||
405 /*(3)*/ maskShape[i] > cstMaskSizes[i])
406 return false;
407 }
408
409 return true;
410}
411
413 Value source,
414 ArrayRef<int64_t> inputVectorSizes,
415 std::optional<Value> padValue,
416 bool useInBoundsInsteadOfMasking,
417 ArrayRef<bool> inputScalableVecDims) {
418 VectorType vecToReadTy = VectorType::get(
419 inputVectorSizes, cast<ShapedType>(source.getType()).getElementType(),
420 inputScalableVecDims);
421
422 return createReadOrMaskedRead(builder, loc, source, vecToReadTy, padValue,
423 useInBoundsInsteadOfMasking);
424}
425
426/// Compute the in_bounds attribute for a transfer op given its permutation map
427/// and the source being accessed. Dimension i is in-bounds when the map result
428/// is an AffineDimExpr pointing to a static source dimension divisible by the
429/// vector size, or an AffineConstantExpr (broadcast).
431 AffineMap permutationMap, VectorType vectorType, ShapedType sourceType) {
432 SmallVector<bool> inBounds(vectorType.getRank(), false);
433 for (unsigned i = 0; i < (unsigned)vectorType.getRank(); ++i) {
434 AffineExpr expr = permutationMap.getResult(i);
435 if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
436 unsigned memDim = dimExpr.getPosition();
437 if (!sourceType.isDynamicDim(memDim) &&
438 sourceType.getDimSize(memDim) % vectorType.getDimSize(i) == 0)
439 inBounds[i] = true;
440 } else if (isa<AffineConstantExpr>(expr)) {
441 inBounds[i] = true;
442 }
443 }
444 return inBounds;
445}
446
448 Value source,
449 const VectorType &vecToReadTy,
450 std::optional<Value> padValue,
451 bool useInBoundsInsteadOfMasking,
452 ArrayRef<Value> customIndices,
453 AffineMap permutationMap) {
454 assert(!llvm::is_contained(vecToReadTy.getScalableDims(),
455 ShapedType::kDynamic) &&
456 "invalid input vector sizes");
457 auto sourceShapedType = cast<ShapedType>(source.getType());
458 auto sourceShape = sourceShapedType.getShape();
459
460 int64_t vecToReadRank = vecToReadTy.getRank();
461 auto vecToReadShape = vecToReadTy.getShape();
462
463 // The permutation map maps the source's index space to the vector's, so its
464 // dims must match the source rank and its results the vector rank. Without a
465 // map, a minor identity is implied, requiring the two ranks to match.
466 assert(sourceShape.size() == (permutationMap
467 ? permutationMap.getNumDims()
468 : static_cast<size_t>(vecToReadRank)) &&
469 "expected source rank to match permutation map dims or vector rank.");
470 assert((!permutationMap || permutationMap.getNumResults() ==
471 static_cast<size_t>(vecToReadRank)) &&
472 "expected permutation map results to match vector rank.");
473 assert((!padValue.has_value() ||
474 padValue.value().getType() == sourceShapedType.getElementType()) &&
475 "expected same pad element type to match source element type");
476
477 SmallVector<bool> inBoundsVal(vecToReadRank, true);
478
479 if (useInBoundsInsteadOfMasking) {
480 if (permutationMap) {
481 // Update the inBounds attribute.
482 // FIXME: This computation is too weak - it ignores the read indices.
484 permutationMap, vecToReadTy, cast<ShapedType>(source.getType()));
485 } else {
486 // Update the inBounds attribute.
487 // FIXME: This computation is too weak - it ignores the read indices.
488 for (unsigned i = 0; i < vecToReadRank; i++)
489 inBoundsVal[i] = (sourceShape[i] == vecToReadShape[i]) &&
490 ShapedType::isStatic(sourceShape[i]);
491 }
492 }
493 // The transfer op expects one index per source dimension.
494 assert(
495 (customIndices.empty() || customIndices.size() == sourceShape.size()) &&
496 "expected as many custom indices as source dims.");
498 customIndices.empty()
499 ? indices.assign(sourceShape.size(),
500 arith::ConstantIndexOp::create(builder, loc, 0))
501 : indices.assign(customIndices.begin(), customIndices.end());
502
503 // A null permutation map means the builder defaults to a minor identity map.
504 auto transferReadOp =
505 vector::TransferReadOp::create(builder, loc, /*vectorType=*/vecToReadTy,
506 /*source=*/source,
507 /*indices=*/indices,
508 /*padding=*/padValue,
509 /*permutationMap=*/permutationMap,
510 /*inBounds=*/inBoundsVal);
511
512 if (useInBoundsInsteadOfMasking)
513 return transferReadOp;
514
515 SmallVector<OpFoldResult> mixedSourceDims =
516 isa<MemRefType>(source.getType())
517 ? memref::getMixedSizes(builder, loc, source)
518 : tensor::getMixedSizes(builder, loc, source);
519
520 if (isMaskTriviallyFoldable(mixedSourceDims, indices, sourceShape,
521 vecToReadShape))
522 return transferReadOp;
523
524 auto maskType = vecToReadTy.cloneWith(/*shape=*/{}, builder.getI1Type());
525 Value mask =
526 vector::CreateMaskOp::create(builder, loc, maskType, mixedSourceDims);
527 return mlir::vector::maskOperation(builder, transferReadOp, mask)
528 ->getResult(0);
529}
530
532 Value vecToStore, Value dest,
533 SmallVector<Value> writeIndices,
534 bool useInBoundsInsteadOfMasking,
535 AffineMap permutationMap) {
536
537 ShapedType destType = cast<ShapedType>(dest.getType());
538 int64_t destRank = destType.getRank();
539 auto destShape = destType.getShape();
540
541 VectorType vecToStoreType = cast<VectorType>(vecToStore.getType());
542 int64_t vecToStoreRank = vecToStoreType.getRank();
543 auto vecToStoreShape = vecToStoreType.getShape();
544
545 // Compute the in_bounds attribute
546 SmallVector<bool> inBoundsVal(vecToStoreRank, true);
547 if (useInBoundsInsteadOfMasking) {
548 if (permutationMap) {
549 // Update the inBounds attribute.
550 // FIXME: This computation is too weak - it ignores the write indices.
552 permutationMap, vecToStoreType, cast<ShapedType>(dest.getType()));
553 } else {
554 // Update the inBounds attribute.
555 // FIXME: This computation is too weak - it ignores the write indices.
556 for (unsigned i = 0; i < vecToStoreRank; i++)
557 inBoundsVal[i] =
558 (destShape[destRank - vecToStoreRank + i] >= vecToStoreShape[i]) &&
559 ShapedType::isStatic(destShape[destRank - vecToStoreRank + i]);
560 }
561 }
562
563 // If missing, initialize the write indices to 0.
564 bool useDefaultWriteIdxs = writeIndices.empty();
565 assert((useDefaultWriteIdxs ||
566 writeIndices.size() == static_cast<size_t>(destRank)) &&
567 "Invalid number of write indices!");
568 if (useDefaultWriteIdxs) {
569 auto zero = arith::ConstantIndexOp::create(builder, loc, 0);
570 writeIndices.assign(destRank, zero);
571 }
572
573 // Generate the xfer_write Op. A null permutation map means the builder
574 // defaults to a minor identity map.
575 Operation *write =
576 vector::TransferWriteOp::create(builder, loc,
577 /*vector=*/vecToStore,
578 /*dest=*/dest,
579 /*indices=*/writeIndices,
580 /*permutationMap=*/permutationMap,
581 /*inBounds=*/inBoundsVal);
582
583 // If masking is disabled, exit.
584 if (useInBoundsInsteadOfMasking)
585 return write;
586
587 // The dest dims written to are those the permutation map selects. A null map
588 // means a minor identity: the trailing dims.
589 AffineMap map = permutationMap
590 ? permutationMap
591 : AffineMap::getMinorIdentityMap(destRank, vecToStoreRank,
592 builder.getContext());
593
594 // Check if masking is needed. If not, exit.
595 if (llvm::equal(vecToStoreShape, applyPermutationMap(map, destShape)))
596 return write;
597
598 // Compute the mask and mask the write Op.
599 VectorType writeMaskType = inferTransferOpMaskType(vecToStoreType, map);
600
601 SmallVector<OpFoldResult> destSizes =
602 isa<MemRefType>(dest.getType())
603 ? memref::getMixedSizes(builder, loc, dest)
604 : tensor::getMixedSizes(builder, loc, dest);
605
606 // Compute sizes for write-mask: `dim - index` for each dest dim written to,
607 // in increasing order, as the mask dims are.
608 SmallVector<int64_t> writtenDims;
609 for (AffineExpr expr : map.getResults())
610 writtenDims.push_back(cast<AffineDimExpr>(expr).getPosition());
611 llvm::sort(writtenDims);
613 for (int64_t dim : writtenDims) {
614 Value size = getValueOrCreateConstantIndexOp(builder, loc, destSizes[dim]);
615 maskSizes.push_back(
616 builder.createOrFold<arith::SubIOp>(loc, size, writeIndices[dim]));
617 }
618
619 // TODO: isMaskTriviallyFoldable checks the trailing dims against the leading
620 // indices. Make it use the dims that the map selects and their indices.
621 if (!permutationMap && isMaskTriviallyFoldable(maskSizes, writeIndices,
622 destShape, vecToStoreShape))
623 return write;
624
625 Value maskForWrite =
626 builder.createOrFold<vector::CreateMaskOp>(loc, writeMaskType, maskSizes);
627 return mlir::vector::maskOperation(builder, write, maskForWrite);
628}
629
630LogicalResult
632 ArrayRef<int64_t> inputVectorSizes) {
633 LDBG() << "Iteration space static sizes:" << llvm::interleaved(shape);
634
635 if (inputVectorSizes.size() != shape.size()) {
636 LDBG() << "Input vector sizes don't match the number of loops";
637 return failure();
638 }
639 if (ShapedType::isDynamicShape(inputVectorSizes)) {
640 LDBG() << "Input vector sizes can't have dynamic dimensions";
641 return failure();
642 }
643 if (!llvm::all_of(llvm::zip(shape, inputVectorSizes),
644 [](std::tuple<int64_t, int64_t> sizePair) {
645 int64_t staticSize = std::get<0>(sizePair);
646 int64_t inputSize = std::get<1>(sizePair);
647 return ShapedType::isDynamic(staticSize) ||
648 staticSize <= inputSize;
649 })) {
650 LDBG() << "Input vector sizes must be greater than or equal to iteration "
651 "space static sizes";
652 return failure();
653 }
654 return success();
655}
656
657/// Takes a 2+ dimensional vector as an input
658/// returns n vector values produced by n vector.extract operations.
659/// I.e. calling unrollVectorValue([[%v]], rewriter) such that
660///
661/// %v : vector<nxaxb...>
662///
663/// will produce the following IR changes
664///
665/// %v0 = vector.extract %v[0] : vector<axbx...> from vector<nxaxb...>
666/// %v1 = vector.extract %v[1] : vector<axbx...> from vector<nxaxb...>
667/// ...
668/// %vnminusone = vector.extract %v[n-1] : vector<axbx...> from ...
669///
670/// and returns SmallVector<Value> r = {[[%v0]], [[%v1]], ..., [[%vnminusone]]}
671FailureOr<SmallVector<Value>>
673 RewriterBase &rewriter) {
674 SmallVector<Value> subvectors;
675 VectorType ty = cast<VectorType>(vector.getType());
676 Location loc = vector.getLoc();
677 if (ty.getRank() < 2)
678 return rewriter.notifyMatchFailure(loc, "already 1-D");
679
680 // Unrolling doesn't take vscale into account. Pattern is disabled for
681 // vectors with leading scalable dim(s).
682 if (ty.getScalableDims().front())
683 return rewriter.notifyMatchFailure(loc, "cannot unroll scalable dim");
684
685 for (int64_t i = 0, e = ty.getShape().front(); i < e; ++i) {
686 subvectors.push_back(vector::ExtractOp::create(rewriter, loc, vector, i));
687 }
688
689 return subvectors;
690}
691
693 vector::UnrollVectorOpFn unrollFn) {
694 assert(op->getNumResults() == 1 && "expected single result");
695 assert(isa<VectorType>(op->getResult(0).getType()) && "expected vector type");
696 VectorType resultTy = cast<VectorType>(op->getResult(0).getType());
697 if (resultTy.getRank() < 2)
698 return rewriter.notifyMatchFailure(op, "already 1-D");
699
700 // Unrolling doesn't take vscale into account. Pattern is disabled for
701 // vectors with leading scalable dim(s).
702 if (resultTy.getScalableDims().front())
703 return rewriter.notifyMatchFailure(op, "cannot unroll scalable dim");
704
705 Location loc = op->getLoc();
706 Value result = ub::PoisonOp::create(rewriter, loc, resultTy);
707 VectorType subTy = VectorType::Builder(resultTy).dropDim(0);
708
709 for (int64_t i = 0, e = resultTy.getShape().front(); i < e; ++i) {
710 Value subVector = unrollFn(rewriter, loc, subTy, i);
711 result = vector::InsertOp::create(rewriter, loc, subVector, result, i);
712 }
713
714 rewriter.replaceOp(op, result);
715 return success();
716}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static std::optional< VectorShape > vectorShape(Type type)
static SetVector< Operation * > getParentsOfType(Block *block)
Implementation detail that walks up the parents and records the ones with the specified type.
static bool areDimsTransposedIn2DSlice(int64_t dim0, int64_t dim1, ArrayRef< int64_t > transp)
Given the n-D transpose pattern 'transp', return true if 'dim0' and 'dim1' should be transposed with ...
static SetVector< Operation * > getEnclosingforOps(Block *block)
Returns the enclosing AffineForOp, from closest to farthest.
static AffineMap makePermutationMap(ArrayRef< Value > indices, const DenseMap< Operation *, unsigned > &enclosingLoopToVectorDim)
Constructs a permutation map from memref indices to vector dimension.
static bool isMaskTriviallyFoldable(SmallVector< OpFoldResult > &maskSizes, SmallVector< Value > &indices, ArrayRef< int64_t > baseShape, ArrayRef< int64_t > maskShape)
Determines whether a mask for xfer_read/write is trivially "all true".
static SmallVector< bool > computeInBoundsFromPermutationMap(AffineMap permutationMap, VectorType vectorType, ShapedType sourceType)
Compute the in_bounds attribute for a transfer op given its permutation map and the source being acce...
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.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
Block represents an ordered list of Operations.
Definition Block.h:34
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
IntegerType getI1Type()
Definition Builders.cpp:61
MLIRContext * getContext() const
Definition Builders.h:56
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
This class helps build Operations.
Definition Builders.h:210
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
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
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 coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
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,...
A range-style iterator that allows for iterating over the offsets of all potential tiles of size tile...
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
This is a builder type that keeps local references to arguments.
Builder & dropDim(unsigned pos)
Erase a dim from shape @pos.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
DenseSet< Value, DenseMapInfo< Value > > getInvariantAccesses(Value iv, ArrayRef< Value > indices)
Given an induction variable iv of type AffineForOp and indices of type IndexType, returns the set of ...
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given memref value.
Definition MemRefOps.cpp:80
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
bool isContiguousSlice(MemRefType memrefType, VectorType vectorType)
Return true if vectorType is a contiguous slice of memrefType, in the sense that it can be read/writt...
Operation * maskOperation(OpBuilder &builder, Operation *maskableOp, Value mask, Value passthru=Value())
Creates a vector.mask operation around a maskable operation.
LogicalResult isValidMaskedInputVector(ArrayRef< int64_t > shape, ArrayRef< int64_t > inputVectorSizes)
Returns success if inputVectorSizes is a valid masking configuraion for given shape,...
VectorType inferTransferOpMaskType(VectorType vecType, AffineMap permMap)
Infers the mask type for a transfer op given its vector type and permutation map.
Operation * createWriteOrMaskedWrite(OpBuilder &builder, Location loc, Value vecToStore, Value dest, SmallVector< Value > writeIndices={}, bool useInBoundsInsteadOfMasking=false, AffineMap permutationMap=AffineMap())
Create a TransferWriteOp of vecToStore into dest.
Value createReadOrMaskedRead(OpBuilder &builder, Location loc, Value source, const VectorType &vecToReadTy, std::optional< Value > padValue=std::nullopt, bool useInBoundsInsteadOfMasking=false, ArrayRef< Value > indices={}, AffineMap permutationMap=AffineMap())
Creates a TransferReadOp from source.
FailureOr< std::pair< int, int > > isTranspose2DSlice(vector::TransposeOp op)
Returns two dims that are greater than one if the transposition is applied on a 2D slice.
FailureOr< SmallVector< Value > > unrollVectorValue(TypedValue< VectorType >, RewriterBase &)
Generic utility for unrolling values of type vector<NxAxBx...> to N values of type vector<AxBx....
std::optional< StaticTileOffsetRange > createUnrollIterator(VectorType vType, int64_t targetRank=1)
Returns an iterator for all positions in the leading dimensions of vType up to the targetRank.
Value createOrFoldDimOp(OpBuilder &b, Location loc, Value source, int64_t dim)
Helper function that creates a memref::DimOp or tensor::DimOp depending on the type of source.
bool isLinearizableVector(VectorType type)
Returns true if the input Vector type can be linearized.
function_ref< Value(PatternRewriter &, Location, VectorType, int64_t)> UnrollVectorOpFn
Generic utility for unrolling n-D vector operations to (n-1)-D operations.
SmallVector< OpFoldResult > getMixedSizesXfer(bool hasTensorSemantics, Operation *xfer, RewriterBase &rewriter)
A wrapper for getMixedSizes for vector.transfer_read and vector.transfer_write Ops (for source and de...
LogicalResult unrollVectorOp(Operation *op, PatternRewriter &rewriter, UnrollVectorOpFn unrollFn)
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
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
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:147
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)
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
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.