MLIR 24.0.0git
VectorUnroll.cpp
Go to the documentation of this file.
1//===- VectorUnrollDistribute.cpp - patterns to do vector unrolling -------===//
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 patterns to do vector unrolling and vector distribution.
10//
11//===----------------------------------------------------------------------===//
12
18#include "llvm/ADT/MapVector.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/Support/DebugLog.h"
21#include "llvm/Support/InterleavedRange.h"
22#include <optional>
23
24#define DEBUG_TYPE "vector-unroll"
25
26using namespace mlir;
27using namespace mlir::vector;
28
29SmallVector<Value> mlir::vector::sliceTransferIndices(
31 AffineMap permutationMap, Location loc, OpBuilder &builder) {
32 MLIRContext *ctx = builder.getContext();
33 auto isBroadcast = [](AffineExpr expr) {
34 if (auto constExpr = dyn_cast<AffineConstantExpr>(expr))
35 return constExpr.getValue() == 0;
36 return false;
37 };
38 // Compute 'sliceIndices' by adding 'sliceOffsets[i]' to 'indices[i]'.
39 SmallVector<Value> slicedIndices(indices);
40 for (const auto &dim : llvm::enumerate(permutationMap.getResults())) {
41 int64_t elementOffset = elementOffsets[dim.index()];
42 if (isBroadcast(dim.value()) || elementOffset == 0)
43 continue;
44 unsigned pos = cast<AffineDimExpr>(dim.value()).getPosition();
45 auto expr = getAffineDimExpr(0, builder.getContext()) +
46 getAffineConstantExpr(elementOffset, ctx);
47 auto map = AffineMap::get(/*dimCount=*/1, /*symbolCount=*/0, expr);
48 slicedIndices[pos] =
49 affine::AffineApplyOp::create(builder, loc, map, indices[pos]);
50 }
51 return slicedIndices;
52}
53
54// Compute the new indices by adding `offsets` to `originalIndices`.
55// If m < n (m = offsets.size(), n = originalIndices.size()),
56// then only the trailing m values in `originalIndices` are updated.
58 Location loc,
59 OperandRange originalIndices,
60 ArrayRef<int64_t> offsets) {
61 assert(offsets.size() <= originalIndices.size() &&
62 "Offsets should not exceed the number of original indices");
63 SmallVector<Value> indices(originalIndices);
64
65 auto start = indices.size() - offsets.size();
66 for (auto [i, offset] : llvm::enumerate(offsets)) {
67 if (offset != 0) {
68 indices[start + i] = arith::AddIOp::create(
69 rewriter, loc, originalIndices[start + i],
70 arith::ConstantIndexOp::create(rewriter, loc, offset));
71 }
72 }
73 return indices;
74}
75
76// Clones `op` into a new operations that takes `operands` and returns
77// `resultTypes`.
79 Operation *op,
80 ArrayRef<Value> operands,
81 ArrayRef<Type> resultTypes) {
82 OperationState state(loc, op->getName(), operands, resultTypes,
83 op->getDiscardableAttrDictionary().getValue());
85 return builder.create(state);
86}
87
88/// Return the target shape for unrolling for the given `op`. Return
89/// std::nullopt if the op shouldn't be or cannot be unrolled.
90static std::optional<SmallVector<int64_t>>
92 LDBG() << "Get unroll shape for op " << op->getName().getStringRef();
93 if (options.filterConstraint && failed(options.filterConstraint(op))) {
94 LDBG() << "--no filter constraint -> BAIL";
95 return std::nullopt;
96 }
97 assert(options.nativeShape &&
98 "vector unrolling expects the native shape or native"
99 "shape call back function to be set");
100 auto unrollableVectorOp = dyn_cast<VectorUnrollOpInterface>(op);
101 if (!unrollableVectorOp) {
102 LDBG() << "--not an unrollable op -> BAIL";
103 return std::nullopt;
104 }
105 auto maybeUnrollShape = unrollableVectorOp.getShapeForUnroll();
106 if (!maybeUnrollShape) {
107 LDBG() << "--could not get shape of op " << *op << " -> BAIL";
108 return std::nullopt;
109 }
110 LDBG() << "--vector op shape: " << llvm::interleaved(*maybeUnrollShape);
111
112 std::optional<SmallVector<int64_t>> targetShape = options.nativeShape(op);
113 if (!targetShape) {
114 LDBG() << "--no unrolling target shape defined " << *op << "-> SKIP";
115 return std::nullopt;
116 }
117 LDBG() << "--target shape: " << llvm::interleaved(*targetShape);
118
119 auto maybeShapeRatio = computeShapeRatio(*maybeUnrollShape, *targetShape);
120 if (!maybeShapeRatio) {
121 LDBG() << "--could not compute integral shape ratio -> BAIL";
122 return std::nullopt;
123 }
124 if (llvm::all_of(*maybeShapeRatio, [](int64_t v) { return v == 1; })) {
125 LDBG() << "--no unrolling needed -> SKIP";
126 return std::nullopt;
127 }
128 LDBG() << "--found an integral shape ratio to unroll to -> SUCCESS";
129 return targetShape;
130}
131
133getUnrollOrder(unsigned numLoops, Operation *op,
135 SmallVector<int64_t> loopOrder =
136 llvm::to_vector(llvm::seq<int64_t>(0, static_cast<int64_t>(numLoops)));
137 if (options.traversalOrderCallback != nullptr) {
138 std::optional<SmallVector<int64_t>> order =
139 options.traversalOrderCallback(op);
140 if (order) {
141 loopOrder = std::move(*order);
142 }
143 }
144 return loopOrder;
145}
146
147namespace {
148
149struct UnrollTransferReadPattern
150 : public OpRewritePattern<vector::TransferReadOp> {
151 UnrollTransferReadPattern(MLIRContext *context,
152 const vector::UnrollVectorOptions &options,
153 PatternBenefit benefit = 1)
154 : OpRewritePattern<vector::TransferReadOp>(context, benefit),
155 options(options) {}
156
157 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
158 PatternRewriter &rewriter) const override {
159 // TODO: support 0-d corner case.
160 if (readOp.getTransferRank() == 0)
161 return failure();
162 if (readOp.getMask())
163 return failure();
164 auto targetShape = getTargetShape(options, readOp);
165 if (!targetShape)
166 return failure();
167 auto sourceVectorType = readOp.getVectorType();
168 SmallVector<int64_t> strides(targetShape->size(), 1);
169 Location loc = readOp.getLoc();
170 ArrayRef<int64_t> originalSize = sourceVectorType.getShape();
171
172 // Prepare the result vector;
173 Value result =
174 arith::ConstantOp::create(rewriter, loc, sourceVectorType,
175 rewriter.getZeroAttr(sourceVectorType));
176 auto targetType =
177 VectorType::get(*targetShape, sourceVectorType.getElementType());
178 SmallVector<Value> originalIndices(readOp.getIndices().begin(),
179 readOp.getIndices().end());
180 SmallVector<int64_t> loopOrder =
181 getUnrollOrder(originalSize.size(), readOp, options);
182 for (SmallVector<int64_t> elementOffsets :
183 StaticTileOffsetRange(originalSize, *targetShape, loopOrder)) {
184 SmallVector<Value> indices =
185 sliceTransferIndices(elementOffsets, originalIndices,
186 readOp.getPermutationMap(), loc, rewriter);
187 auto slicedRead = vector::TransferReadOp::create(
188 rewriter, loc, targetType, readOp.getBase(), indices,
189 readOp.getPermutationMapAttr(), readOp.getPadding(), readOp.getMask(),
190 readOp.getInBoundsAttr());
191
192 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
193 loc, slicedRead, result, elementOffsets, strides);
194 }
195 rewriter.replaceOp(readOp, result);
196 return success();
197 }
198
199private:
200 vector::UnrollVectorOptions options;
201};
202
203struct UnrollTransferWritePattern
204 : public OpRewritePattern<vector::TransferWriteOp> {
205 UnrollTransferWritePattern(MLIRContext *context,
206 const vector::UnrollVectorOptions &options,
207 PatternBenefit benefit = 1)
208 : OpRewritePattern<vector::TransferWriteOp>(context, benefit),
209 options(options) {}
210
211 LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp,
212 PatternRewriter &rewriter) const override {
213 // TODO: support 0-d corner case.
214 if (writeOp.getTransferRank() == 0)
215 return failure();
216
217 if (writeOp.getMask())
218 return failure();
219 auto targetShape = getTargetShape(options, writeOp);
220 if (!targetShape)
221 return failure();
222 auto sourceVectorType = writeOp.getVectorType();
223 SmallVector<int64_t> strides(targetShape->size(), 1);
224 Location loc = writeOp.getLoc();
225 ArrayRef<int64_t> originalSize = sourceVectorType.getShape();
226 // Bail-out if rank(source) != rank(target). The main limitation here is the
227 // fact that `ExtractStridedSlice` requires the rank for the input and
228 // output to match. If needed, we can relax this later.
229 if (originalSize.size() != targetShape->size())
230 return rewriter.notifyMatchFailure(
231 writeOp,
232 "expected source input vector rank to match target shape rank");
233
234 SmallVector<Value> originalIndices(writeOp.getIndices().begin(),
235 writeOp.getIndices().end());
236 SmallVector<int64_t> loopOrder =
237 getUnrollOrder(originalSize.size(), writeOp, options);
238 Value resultTensor;
239 for (SmallVector<int64_t> elementOffsets :
240 StaticTileOffsetRange(originalSize, *targetShape, loopOrder)) {
241 Value slicedVector = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
242 loc, writeOp.getVector(), elementOffsets, *targetShape, strides);
243 SmallVector<Value> indices =
244 sliceTransferIndices(elementOffsets, originalIndices,
245 writeOp.getPermutationMap(), loc, rewriter);
246 Operation *slicedWrite = vector::TransferWriteOp::create(
247 rewriter, loc, slicedVector,
248 resultTensor ? resultTensor : writeOp.getBase(), indices,
249 writeOp.getPermutationMapAttr(), writeOp.getInBoundsAttr());
250 // For the tensor case update the destination for the next transfer write.
251 if (!slicedWrite->getResults().empty())
252 resultTensor = slicedWrite->getResult(0);
253 }
254 if (resultTensor)
255 rewriter.replaceOp(writeOp, resultTensor);
256 else
257 rewriter.eraseOp(writeOp);
258 return success();
259 }
260
261private:
262 vector::UnrollVectorOptions options;
263};
264
265struct OffsetMapInfo {
266 static unsigned getHashValue(const SmallVector<int64_t> &v) {
267 return static_cast<unsigned>(llvm::hash_combine_range(v));
268 }
269
270 static bool isEqual(const SmallVector<int64_t> &lhs,
271 const SmallVector<int64_t> &rhs) {
272 return lhs == rhs;
273 }
274};
275
276struct UnrollContractionPattern
277 : public OpRewritePattern<vector::ContractionOp> {
278 UnrollContractionPattern(MLIRContext *context,
279 const vector::UnrollVectorOptions &options,
280 PatternBenefit benefit = 1)
281 : OpRewritePattern<vector::ContractionOp>(context, benefit),
282 options(options) {}
283
284 LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
285 PatternRewriter &rewriter) const override {
286 auto targetShape = getTargetShape(options, contractOp);
287 if (!targetShape)
288 return failure();
289 auto dstVecType = cast<VectorType>(contractOp.getResultType());
290 SmallVector<int64_t> originalSize = *contractOp.getShapeForUnroll();
291
292 Location loc = contractOp.getLoc();
293 unsigned accIndex = vector::ContractionOp::getAccOperandIndex();
294 AffineMap dstAffineMap = contractOp.getIndexingMapsArray()[accIndex];
295 llvm::MapVector<
296 SmallVector<int64_t>, Value,
297 llvm::DenseMap<SmallVector<int64_t>, unsigned, OffsetMapInfo>>
298 accCache;
299
300 SmallVector<int64_t> loopOrder = getUnrollOrder(
301 contractOp.getIteratorTypes().size(), contractOp, options);
302
303 for (SmallVector<int64_t> offsets :
304 StaticTileOffsetRange(originalSize, *targetShape, loopOrder)) {
305 SmallVector<Value> slicesOperands(contractOp.getNumOperands());
306
307 // Helper to compute the new shape of each operand and extract the slice.
308 auto extractOperand = [&](unsigned index, Value operand,
309 AffineMap permutationMap,
310 ArrayRef<int64_t> operandOffets) {
311 SmallVector<int64_t> operandShape = applyPermutationMap(
312 permutationMap, ArrayRef<int64_t>(*targetShape));
313 SmallVector<int64_t> operandStrides(operandOffets.size(), 1);
314 slicesOperands[index] =
315 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
316 loc, operand, operandOffets, operandShape, operandStrides);
317 };
318
319 // Extract the new lhs operand.
320 AffineMap lhsPermutationMap = contractOp.getIndexingMapsArray()[0];
321 SmallVector<int64_t> lhsOffets =
322 applyPermutationMap(lhsPermutationMap, ArrayRef<int64_t>(offsets));
323 extractOperand(0, contractOp.getLhs(), lhsPermutationMap, lhsOffets);
324
325 // Extract the new rhs operand.
326 AffineMap rhsPermutationMap = contractOp.getIndexingMapsArray()[1];
327 SmallVector<int64_t> rhsOffets =
328 applyPermutationMap(rhsPermutationMap, ArrayRef<int64_t>(offsets));
329 extractOperand(1, contractOp.getRhs(), rhsPermutationMap, rhsOffets);
330
331 AffineMap accPermutationMap = contractOp.getIndexingMapsArray()[2];
332 SmallVector<int64_t> accOffets =
333 applyPermutationMap(accPermutationMap, ArrayRef<int64_t>(offsets));
334 // If a version of the accumulator has already been computed, use it
335 // otherwise extract the first version from the original operand.
336 auto *accIt = accCache.find(accOffets);
337 if (accIt != accCache.end())
338 slicesOperands[2] = accIt->second;
339 else
340 extractOperand(2, contractOp.getAcc(), accPermutationMap, accOffets);
341
342 SmallVector<int64_t> dstShape =
343 applyPermutationMap(dstAffineMap, ArrayRef<int64_t>(*targetShape));
344 auto targetType = VectorType::get(dstShape, dstVecType.getElementType());
345 Operation *newOp = cloneOpWithOperandsAndTypes(
346 rewriter, loc, contractOp, slicesOperands, targetType);
347
348 SmallVector<int64_t> dstOffets =
349 applyPermutationMap(dstAffineMap, ArrayRef<int64_t>(offsets));
350 // Save the accumulated value untill all the loops are unrolled since
351 // reduction loop keep updating the accumulator.
352 accCache[dstOffets] = newOp->getResult(0);
353 }
354 // Assemble back the accumulator into a single vector.
355 Value result = arith::ConstantOp::create(rewriter, loc, dstVecType,
356 rewriter.getZeroAttr(dstVecType));
357 for (const auto &it : accCache) {
358 SmallVector<int64_t> dstStrides(it.first.size(), 1);
359 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
360 loc, it.second, result, it.first, dstStrides);
361 }
362 rewriter.replaceOp(contractOp, result);
363 return success();
364 }
365
366private:
367 vector::UnrollVectorOptions options;
368};
369
370struct UnrollMultiReductionPattern
371 : public OpRewritePattern<vector::MultiDimReductionOp> {
372 UnrollMultiReductionPattern(MLIRContext *context,
373 const vector::UnrollVectorOptions &options,
374 PatternBenefit benefit = 1)
375 : OpRewritePattern<vector::MultiDimReductionOp>(context, benefit),
376 options(options) {}
377
378 LogicalResult matchAndRewrite(vector::MultiDimReductionOp reductionOp,
379 PatternRewriter &rewriter) const override {
380 std::optional<SmallVector<int64_t>> targetShape =
381 getTargetShape(options, reductionOp);
382 if (!targetShape)
383 return failure();
384 SmallVector<int64_t> originalSize = *reductionOp.getShapeForUnroll();
385 Location loc = reductionOp.getLoc();
386 auto resultType = reductionOp->getResult(0).getType();
387
388 // A target shape with fewer dimensions than the source vector applies to
389 // its trailing dimensions. Add leading unit dimensions so that it can be
390 // used to slice the source and to index its dimensions.
391 SmallVector<int64_t> adjustedTargetShape(originalSize.size(), 1);
392 llvm::copy(*targetShape, adjustedTargetShape.end() - targetShape->size());
393
394 // Handle scalar result case: all dimensions are reduced.
395 // Each source tile is reduced to a scalar, and partial results are
396 // chained through the accumulator operand.
397 if (resultType.isIntOrFloat()) {
398 Value accumulator = reductionOp.getAcc();
399 for (SmallVector<int64_t> offsets :
400 StaticTileOffsetRange(originalSize, adjustedTargetShape)) {
401 SmallVector<int64_t> operandStrides(offsets.size(), 1);
402 Value slicedOperand =
403 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
404 loc, reductionOp.getSource(), offsets, adjustedTargetShape,
405 operandStrides);
406 Operation *newOp = cloneOpWithOperandsAndTypes(
407 rewriter, loc, reductionOp, {slicedOperand, accumulator},
408 resultType);
409 accumulator = newOp->getResult(0);
410 }
411 rewriter.replaceOp(reductionOp, accumulator);
412 return success();
413 }
414
415 // Vector result case.
416 llvm::MapVector<
417 SmallVector<int64_t>, Value,
418 llvm::DenseMap<SmallVector<int64_t>, unsigned, OffsetMapInfo>>
419 accCache;
420
421 // Stride of the ratios, this gives us the offsets of sliceCount in a basis
422 // of multiples of the targetShape.
423 for (SmallVector<int64_t> offsets :
424 StaticTileOffsetRange(originalSize, adjustedTargetShape)) {
425 SmallVector<Value> operands;
426 SmallVector<int64_t> operandStrides(offsets.size(), 1);
427 Value slicedOperand =
428 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
429 loc, reductionOp.getSource(), offsets, adjustedTargetShape,
430 operandStrides);
431 operands.push_back(slicedOperand);
432 SmallVector<int64_t> dstShape;
433 SmallVector<int64_t> destOffset;
434 for (size_t i : llvm::seq(size_t(0), adjustedTargetShape.size())) {
435 if (!reductionOp.isReducedDim(i)) {
436 destOffset.push_back(offsets[i]);
437 dstShape.push_back(adjustedTargetShape[i]);
438 }
439 }
440 Value acc;
441 SmallVector<int64_t> accStrides(destOffset.size(), 1);
442 // If a version of the accumulator has already been computed, use it
443 // otherwise extract the first version from the original operand.
444 auto *accIt = accCache.find(destOffset);
445 if (accIt != accCache.end())
446 acc = accIt->second;
447 else
448 acc = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
449 loc, reductionOp.getAcc(), destOffset, dstShape, accStrides);
450 operands.push_back(acc);
451 auto targetType = VectorType::get(
452 dstShape, reductionOp.getSourceVectorType().getElementType());
453 Operation *newOp = cloneOpWithOperandsAndTypes(rewriter, loc, reductionOp,
454 operands, targetType);
455 Value result = newOp->getResult(0);
456 accCache[destOffset] = result;
457 }
458 // Assemble back the accumulator into a single vector.
459 Value result = arith::ConstantOp::create(
460 rewriter, loc, reductionOp.getDestType(),
461 rewriter.getZeroAttr(reductionOp.getDestType()));
462 for (const auto &it : accCache) {
463 SmallVector<int64_t> dstStrides(it.first.size(), 1);
464 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
465 loc, it.second, result, it.first, dstStrides);
466 }
467 rewriter.replaceOp(reductionOp, result);
468 return success();
469 }
470
471private:
472 vector::UnrollVectorOptions options;
473};
474
475struct UnrollElementwisePattern : public RewritePattern {
476 UnrollElementwisePattern(MLIRContext *context,
477 const vector::UnrollVectorOptions &options,
478 PatternBenefit benefit = 1)
479 : RewritePattern(MatchAnyOpTypeTag(), benefit, context),
480 options(options) {}
481
482 LogicalResult matchAndRewrite(Operation *op,
483 PatternRewriter &rewriter) const override {
485 return failure();
486 auto targetShape = getTargetShape(options, op);
487 if (!targetShape)
488 return failure();
489 int64_t targetShapeRank = targetShape->size();
490 auto dstVecType = cast<VectorType>(op->getResult(0).getType());
491 SmallVector<int64_t> originalSize =
492 *cast<VectorUnrollOpInterface>(op).getShapeForUnroll();
493 int64_t originalShapeRank = originalSize.size();
494
495 Location loc = op->getLoc();
496
497 // Handle rank mismatch by adding leading unit dimensions to targetShape
498 SmallVector<int64_t> adjustedTargetShape(originalShapeRank);
499 int64_t rankDiff = originalShapeRank - targetShapeRank;
500 std::fill(adjustedTargetShape.begin(),
501 adjustedTargetShape.begin() + rankDiff, 1);
502 std::copy(targetShape->begin(), targetShape->end(),
503 adjustedTargetShape.begin() + rankDiff);
504
505 int64_t adjustedTargetShapeRank = adjustedTargetShape.size();
506 // Prepare the result vector.
507 Value result = arith::ConstantOp::create(rewriter, loc, dstVecType,
508 rewriter.getZeroAttr(dstVecType));
509 SmallVector<int64_t> strides(adjustedTargetShapeRank, 1);
510 VectorType unrolledVecType =
511 VectorType::get(*targetShape, dstVecType.getElementType());
512
513 // Create the unrolled computation.
514 for (SmallVector<int64_t> offsets :
515 StaticTileOffsetRange(originalSize, adjustedTargetShape)) {
516 SmallVector<Value> extractOperands;
517 for (OpOperand &operand : op->getOpOperands()) {
518 auto vecType = dyn_cast<VectorType>(operand.get().getType());
519 if (!vecType) {
520 extractOperands.push_back(operand.get());
521 continue;
522 }
523 Value extracted = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
524 loc, operand.get(), offsets, adjustedTargetShape, strides);
525
526 // Reshape to remove leading unit dims if needed
527 if (adjustedTargetShapeRank > targetShapeRank) {
528 extracted = rewriter.createOrFold<vector::ShapeCastOp>(
529 loc, VectorType::get(*targetShape, vecType.getElementType()),
530 extracted);
531 }
532 extractOperands.push_back(extracted);
533 }
534
535 Operation *newOp = cloneOpWithOperandsAndTypes(
536 rewriter, loc, op, extractOperands, unrolledVecType);
537
538 Value computeResult = newOp->getResult(0);
539
540 // Use strides sized to targetShape for proper insertion
541 SmallVector<int64_t> insertStrides =
542 (adjustedTargetShapeRank > targetShapeRank)
543 ? SmallVector<int64_t>(targetShapeRank, 1)
544 : strides;
545
546 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
547 loc, computeResult, result, offsets, insertStrides);
548 }
549 rewriter.replaceOp(op, result);
550 return success();
551 }
552
553private:
554 vector::UnrollVectorOptions options;
555};
556
557struct UnrollReductionPattern : public OpRewritePattern<vector::ReductionOp> {
558 UnrollReductionPattern(MLIRContext *context,
559 const vector::UnrollVectorOptions &options,
560 PatternBenefit benefit = 1)
561 : OpRewritePattern<vector::ReductionOp>(context, benefit),
562 options(options) {}
563
564 LogicalResult matchAndRewrite(vector::ReductionOp reductionOp,
565 PatternRewriter &rewriter) const override {
566 std::optional<SmallVector<int64_t>> targetShape =
567 getTargetShape(options, reductionOp);
568 if (!targetShape)
569 return failure();
570 SmallVector<int64_t> originalSize = *reductionOp.getShapeForUnroll();
571
572 // Create unrolled vector reduction.
573 Location loc = reductionOp.getLoc();
574 Value accumulator = nullptr;
575 for (SmallVector<int64_t> offsets :
576 StaticTileOffsetRange(originalSize, *targetShape)) {
577 SmallVector<int64_t> strides(offsets.size(), 1);
578 Value slicedOperand =
579 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
580 loc, reductionOp.getVector(), offsets, *targetShape, strides);
581 Operation *newOp = cloneOpWithOperandsAndTypes(
582 rewriter, loc, reductionOp, slicedOperand, reductionOp.getType());
583 Value result = newOp->getResult(0);
584
585 if (!accumulator) {
586 // This is the first reduction.
587 accumulator = result;
588 } else {
589 // On subsequent reduction, combine with the accumulator.
590 accumulator = makeArithReduction(rewriter, loc, reductionOp.getKind(),
591 accumulator, result);
592 }
593 }
594
595 rewriter.replaceOp(reductionOp, accumulator);
596 return success();
597 }
598
599private:
600 const vector::UnrollVectorOptions options;
601};
602
603struct UnrollTransposePattern : public OpRewritePattern<vector::TransposeOp> {
604 UnrollTransposePattern(MLIRContext *context,
605 const vector::UnrollVectorOptions &options,
606 PatternBenefit benefit = 1)
607 : OpRewritePattern<vector::TransposeOp>(context, benefit),
608 options(options) {}
609
610 LogicalResult matchAndRewrite(vector::TransposeOp transposeOp,
611 PatternRewriter &rewriter) const override {
612 if (transposeOp.getResultVectorType().getRank() == 0)
613 return failure();
614 auto targetShape = getTargetShape(options, transposeOp);
615 if (!targetShape)
616 return failure();
617 auto originalVectorType = transposeOp.getResultVectorType();
618 SmallVector<int64_t> strides(targetShape->size(), 1);
619 Location loc = transposeOp.getLoc();
620 ArrayRef<int64_t> originalSize = originalVectorType.getShape();
621
622 // Prepare the result vector;
623 Value result =
624 arith::ConstantOp::create(rewriter, loc, originalVectorType,
625 rewriter.getZeroAttr(originalVectorType));
626 ArrayRef<int64_t> permutation = transposeOp.getPermutation();
627
628 // Unroll the computation.
629 for (SmallVector<int64_t> elementOffsets :
630 StaticTileOffsetRange(originalSize, *targetShape)) {
631 SmallVector<int64_t> permutedOffsets(elementOffsets.size());
632 SmallVector<int64_t> permutedShape(elementOffsets.size());
633 // Compute the source offsets and shape.
634 for (auto indices : llvm::enumerate(permutation)) {
635 permutedOffsets[indices.value()] = elementOffsets[indices.index()];
636 permutedShape[indices.value()] = (*targetShape)[indices.index()];
637 }
638 Value slicedOperand =
639 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
640 loc, transposeOp.getVector(), permutedOffsets, permutedShape,
641 strides);
642 Value transposedSlice = rewriter.createOrFold<vector::TransposeOp>(
643 loc, slicedOperand, permutation);
644 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
645 loc, transposedSlice, result, elementOffsets, strides);
646 }
647 rewriter.replaceOp(transposeOp, result);
648 return success();
649 }
650
651private:
652 vector::UnrollVectorOptions options;
653};
654
655struct UnrollGatherPattern : public OpRewritePattern<vector::GatherOp> {
656 UnrollGatherPattern(MLIRContext *context,
657 const vector::UnrollVectorOptions &options,
658 PatternBenefit benefit = 1)
659 : OpRewritePattern<vector::GatherOp>(context, benefit), options(options) {
660 }
661
662 LogicalResult matchAndRewrite(vector::GatherOp gatherOp,
663 PatternRewriter &rewriter) const override {
664 VectorType sourceVectorType = gatherOp.getVectorType();
665 if (sourceVectorType.getRank() == 0)
666 return failure();
667 auto targetShape = getTargetShape(options, gatherOp);
668 if (!targetShape)
669 return failure();
670 SmallVector<int64_t> strides(targetShape->size(), 1);
671 Location loc = gatherOp.getLoc();
672 ArrayRef<int64_t> originalSize = gatherOp.getVectorType().getShape();
673
674 // Prepare the result vector;
675 Value result =
676 arith::ConstantOp::create(rewriter, loc, sourceVectorType,
677 rewriter.getZeroAttr(sourceVectorType));
678 auto targetType =
679 VectorType::get(*targetShape, sourceVectorType.getElementType());
680
681 SmallVector<int64_t> loopOrder =
682 getUnrollOrder(originalSize.size(), gatherOp, options);
683 for (SmallVector<int64_t> elementOffsets :
684 StaticTileOffsetRange(originalSize, *targetShape, loopOrder)) {
685 // To get the unrolled gather, extract the same slice based on the
686 // decomposed shape from each of the index, mask, and pass-through
687 // vectors.
688 Value indexSubVec = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
689 loc, gatherOp.getIndices(), elementOffsets, *targetShape, strides);
690 Value maskSubVec = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
691 loc, gatherOp.getMask(), elementOffsets, *targetShape, strides);
692 Value passThruSubVec =
693 rewriter.createOrFold<vector::ExtractStridedSliceOp>(
694 loc, gatherOp.getPassThru(), elementOffsets, *targetShape,
695 strides);
696 auto slicedGather = vector::GatherOp::create(
697 rewriter, loc, targetType, gatherOp.getBase(), gatherOp.getOffsets(),
698 indexSubVec, maskSubVec, passThruSubVec);
699
700 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
701 loc, slicedGather, result, elementOffsets, strides);
702 }
703 rewriter.replaceOp(gatherOp, result);
704 return success();
705 }
706
707private:
708 vector::UnrollVectorOptions options;
709};
710
711struct UnrollLoadPattern : public OpRewritePattern<vector::LoadOp> {
712 UnrollLoadPattern(MLIRContext *context,
713 const vector::UnrollVectorOptions &options,
714 PatternBenefit benefit = 1)
715 : OpRewritePattern<vector::LoadOp>(context, benefit), options(options) {}
716
717 LogicalResult matchAndRewrite(vector::LoadOp loadOp,
718 PatternRewriter &rewriter) const override {
719 VectorType vecType = loadOp.getVectorType();
720
721 auto targetShape = getTargetShape(options, loadOp);
722 if (!targetShape)
723 return failure();
724
725 Location loc = loadOp.getLoc();
726 ArrayRef<int64_t> originalShape = vecType.getShape();
727 SmallVector<int64_t> strides(targetShape->size(), 1);
728
729 Value result = arith::ConstantOp::create(rewriter, loc, vecType,
730 rewriter.getZeroAttr(vecType));
731
732 SmallVector<int64_t> loopOrder =
733 getUnrollOrder(originalShape.size(), loadOp, options);
734
735 auto targetVecType =
736 VectorType::get(*targetShape, vecType.getElementType());
737
738 for (SmallVector<int64_t> offsets :
739 StaticTileOffsetRange(originalShape, *targetShape, loopOrder)) {
740 SmallVector<Value> indices =
741 sliceLoadStoreIndices(rewriter, loc, loadOp.getIndices(), offsets);
742 Value slicedLoad = vector::LoadOp::create(rewriter, loc, targetVecType,
743 loadOp.getBase(), indices);
744 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
745 loc, slicedLoad, result, offsets, strides);
746 }
747 rewriter.replaceOp(loadOp, result);
748 return success();
749 }
750
751private:
752 vector::UnrollVectorOptions options;
753};
754
755struct UnrollStorePattern : public OpRewritePattern<vector::StoreOp> {
756 UnrollStorePattern(MLIRContext *context,
757 const vector::UnrollVectorOptions &options,
758 PatternBenefit benefit = 1)
759 : OpRewritePattern<vector::StoreOp>(context, benefit), options(options) {}
760
761 LogicalResult matchAndRewrite(vector::StoreOp storeOp,
762 PatternRewriter &rewriter) const override {
763 VectorType vecType = storeOp.getVectorType();
764
765 auto targetShape = getTargetShape(options, storeOp);
766 if (!targetShape)
767 return failure();
768
769 Location loc = storeOp.getLoc();
770 ArrayRef<int64_t> originalShape = vecType.getShape();
771 SmallVector<int64_t> strides(targetShape->size(), 1);
772
773 Value base = storeOp.getBase();
774 Value vector = storeOp.getValueToStore();
775
776 SmallVector<int64_t> loopOrder =
777 getUnrollOrder(originalShape.size(), storeOp, options);
778
779 for (SmallVector<int64_t> offsets :
780 StaticTileOffsetRange(originalShape, *targetShape, loopOrder)) {
781 SmallVector<Value> indices =
782 sliceLoadStoreIndices(rewriter, loc, storeOp.getIndices(), offsets);
783 Value slice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
784 loc, vector, offsets, *targetShape, strides);
785 vector::StoreOp::create(rewriter, loc, slice, base, indices);
786 }
787 rewriter.eraseOp(storeOp);
788 return success();
789 }
790
791private:
792 vector::UnrollVectorOptions options;
793};
794
795struct UnrollBroadcastPattern : public OpRewritePattern<vector::BroadcastOp> {
796 UnrollBroadcastPattern(MLIRContext *context,
797 const vector::UnrollVectorOptions &options,
798 PatternBenefit benefit = 1)
799 : OpRewritePattern<vector::BroadcastOp>(context, benefit),
800 options(options) {}
801
802 LogicalResult matchAndRewrite(vector::BroadcastOp broadcastOp,
803 PatternRewriter &rewriter) const override {
804 auto targetShape = getTargetShape(options, broadcastOp);
805 if (!targetShape)
806 return failure();
807
808 Location loc = broadcastOp.getLoc();
809 VectorType srcType = dyn_cast<VectorType>(broadcastOp.getSourceType());
810 VectorType resType = broadcastOp.getResultVectorType();
811 VectorType targetType =
812 resType.cloneWith(*targetShape, resType.getElementType());
813 Value result = arith::ConstantOp::create(rewriter, loc, resType,
814 rewriter.getZeroAttr(resType));
815
816 SmallVector<int64_t> originalShape = *broadcastOp.getShapeForUnroll();
817 SmallVector<int64_t> strides(originalShape.size(), 1);
818
819 for (SmallVector<int64_t> offsets :
820 StaticTileOffsetRange(originalShape, *targetShape)) {
821 Value newSrc;
822 if (!srcType) {
823 // Scalar to vector broadcast.
824 newSrc = broadcastOp.getSource();
825 } else {
826 // Vector to vector broadcast.
827 int64_t rank = srcType.getRank();
828 SmallVector<int64_t> srcOffsets(offsets.end() - rank, offsets.end());
829 SmallVector<int64_t> srcShape(targetShape->end() - rank,
830 targetShape->end());
831 SmallVector<int64_t> srcStrides(strides.end() - rank, strides.end());
832 // adjust the offset and shape for src if the corresponding dim is 1.
833 for (int64_t i = 0; i < rank; ++i) {
834 if (srcType.getDimSize(i) == 1) {
835 srcOffsets[i] = 0;
836 srcShape[i] = 1;
837 }
838 }
839 newSrc = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
840 loc, broadcastOp.getSource(), srcOffsets, srcShape, srcStrides);
841 }
842
843 Operation *newOp = cloneOpWithOperandsAndTypes(rewriter, loc, broadcastOp,
844 newSrc, targetType);
845
846 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
847 loc, newOp->getResult(0), result, offsets, strides);
848 }
849
850 rewriter.replaceOp(broadcastOp, result);
851 return success();
852 }
853
854private:
855 vector::UnrollVectorOptions options;
856};
857
858/// Unrolls 2 or more dimensional `vector.to_elements` ops by unrolling the
859/// outermost dimension of the operand. For example:
860///
861/// ```
862/// %0:4 = vector.to_elements %v : vector<2x2xf32>
863///
864/// ==>
865///
866/// %v0 = vector.extract %v[0] : vector<2x2xf32> from vector<2x2x2xf32>
867/// %v1 = vector.extract %v[1] : vector<2x2xf32> from vector<2x2x2xf32>
868/// %0:4 = vector.to_elements %v0 : vector<2x2xf32>
869/// %1:4 = vector.to_elements %v1 : vector<2x2xf32>
870/// ```
871///
872/// When this pattern is applied until a fixed-point is reached,
873/// this will produce a sequence of 1-d from_elements
874/// ops.
875struct UnrollToElements final : public OpRewritePattern<vector::ToElementsOp> {
876 UnrollToElements(MLIRContext *context,
877 const vector::UnrollVectorOptions &options,
878 PatternBenefit benefit = 1)
879 : OpRewritePattern<vector::ToElementsOp>(context, benefit),
880 options(options) {}
881
882 LogicalResult matchAndRewrite(vector::ToElementsOp op,
883 PatternRewriter &rewriter) const override {
884
885 TypedValue<VectorType> source = op.getSource();
886 FailureOr<SmallVector<Value>> result =
887 vector::unrollVectorValue(source, rewriter);
888 if (failed(result)) {
889 return failure();
890 }
891 SmallVector<Value> vectors = *result;
892
893 SmallVector<Value> results;
894 for (Value vector : vectors) {
895 auto subElements =
896 vector::ToElementsOp::create(rewriter, op.getLoc(), vector);
897 llvm::append_range(results, subElements.getResults());
898 }
899 rewriter.replaceOp(op, results);
900 return success();
901 }
902
903private:
904 vector::UnrollVectorOptions options;
905};
906
907/// This pattern unrolls `vector.step` operations according to the provided
908/// target unroll shape. It decomposes a large step vector into smaller step
909/// vectors (segments) and assembles the result by inserting each computed
910/// segment into the appropriate offset of the original vector.
911///
912/// The pattern does not support scalable vectors and will fail to match them.
913///
914/// For each segment, it adds the base step vector and the segment's offset,
915/// then inserts the result into the output vector at the corresponding
916/// position.
917///
918/// Example:
919/// Given a step operation:
920/// %0 = vector.step : vector<8xindex>
921///
922/// and a target unroll shape of <4>, the pattern produces:
923///
924/// %base = vector.step : vector<4xindex>
925/// %zero = arith.constant dense<0> : vector<8xindex>
926/// %result0 = vector.insert_strided_slice %base, %zero
927/// {offsets = [0], strides = [1]} : vector<4xindex> into vector<8xindex>
928/// %offset = arith.constant dense<4> : vector<4xindex>
929/// %segment1 = arith.addi %base, %offset : vector<4xindex>
930/// %result1 = vector.insert_strided_slice %segment1, %result0
931/// {offsets = [4], strides = [1]} : vector<4xindex> into vector<8xindex>
932///
933struct UnrollStepPattern : public OpRewritePattern<vector::StepOp> {
934 UnrollStepPattern(MLIRContext *context,
935 const vector::UnrollVectorOptions &options,
936 PatternBenefit benefit = 1)
937 : OpRewritePattern<vector::StepOp>(context, benefit), options(options) {}
938
939 LogicalResult matchAndRewrite(vector::StepOp stepOp,
940 PatternRewriter &rewriter) const override {
941 std::optional<SmallVector<int64_t>> targetShape =
942 getTargetShape(options, stepOp);
943 if (!targetShape)
944 return failure();
945
946 VectorType vecType = stepOp.getType();
947 if (vecType.isScalable()) {
948 // Scalable vectors are not supported by this pattern.
949 return failure();
950 }
951 int64_t originalSize = vecType.getShape()[0];
952 Location loc = stepOp.getLoc();
953 SmallVector<int64_t> strides(1, 1);
954
955 Value result = arith::ConstantOp::create(rewriter, loc, vecType,
956 rewriter.getZeroAttr(vecType));
957
958 auto targetVecType =
959 VectorType::get(*targetShape, vecType.getElementType());
960 Value baseStep = vector::StepOp::create(rewriter, loc, targetVecType);
961 for (const SmallVector<int64_t> &offsets :
962 StaticTileOffsetRange({originalSize}, *targetShape)) {
963 Value bcastOffset = arith::ConstantOp::create(
964 rewriter, loc, targetVecType,
966 targetVecType,
967 IntegerAttr::get(targetVecType.getElementType(), offsets[0])));
968 Value tileStep =
969 arith::AddIOp::create(rewriter, loc, baseStep, bcastOffset);
970
971 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
972 loc, tileStep, result, offsets, strides);
973 }
974 rewriter.replaceOp(stepOp, result);
975 return success();
976 }
977
978private:
979 vector::UnrollVectorOptions options;
980};
981
982/// Unrolls 2 or more dimensional `vector.from_elements` ops by unrolling the
983/// outermost dimension. For example:
984/// ```
985/// %v = vector.from_elements %e0, %e1, %e2, %e3, %e4, %e5 : vector<2x3xf32>
986///
987/// ==>
988///
989/// %0 = ub.poison : vector<2x3xf32>
990/// %v0 = vector.from_elements %e0, %e1, %e2 : vector<3xf32>
991/// %1 = vector.insert %v0, %0 [0] : vector<3xf32> into vector<2x3xf32>
992/// %v1 = vector.from_elements %e3, %e4, %e5 : vector<3xf32>
993/// %v = vector.insert %v1, %1 [1] : vector<3xf32> into vector<2x3xf32>
994/// ```
995///
996/// When this pattern is applied until a fixed-point is reached,
997/// this will produce a sequence of 1-d from_elements
998/// ops.
999struct UnrollFromElements : OpRewritePattern<vector::FromElementsOp> {
1000 UnrollFromElements(MLIRContext *context,
1001 const vector::UnrollVectorOptions &options,
1002 PatternBenefit benefit = 1)
1003 : OpRewritePattern<vector::FromElementsOp>(context, benefit),
1004 options(options) {}
1005
1006 LogicalResult matchAndRewrite(vector::FromElementsOp op,
1007 PatternRewriter &rewriter) const override {
1008 ValueRange allElements = op.getElements();
1009
1010 auto unrollFromElementsFn = [&](PatternRewriter &rewriter, Location loc,
1011 VectorType subTy, int64_t index) {
1012 size_t subTyNumElements = subTy.getNumElements();
1013 assert((index + 1) * subTyNumElements <= allElements.size() &&
1014 "out of bounds");
1015 ValueRange subElements =
1016 allElements.slice(index * subTyNumElements, subTyNumElements);
1017 return vector::FromElementsOp::create(rewriter, loc, subTy, subElements);
1018 };
1019
1020 return unrollVectorOp(op, rewriter, unrollFromElementsFn);
1021 }
1022
1023private:
1024 vector::UnrollVectorOptions options;
1025};
1026
1027/// This pattern unrolls `vector.create_mask` operations into smaller mask
1028/// operations based on the target unroll shape. Each unrolled slice computes
1029/// its local mask size in each dimension (d) as:
1030/// min(max(originalMaskSize[d] - offset[d], 0), unrolledDimSize[d]).
1031/// Example:
1032/// Given a create_mask operation:
1033/// %0 = vector.create_mask %c6, %c10 : vector<8x16xi1> // mask first 6x10
1034/// elements
1035///
1036/// and a target unroll shape of <4x8>, the pattern produces:
1037///
1038/// %false = arith.constant dense<false> : vector<8x16xi1>
1039///
1040/// Slice [0,0]:
1041/// mask size = min(max(6-0, 0), 4) x min(max(10-0, 0), 8) = 4x8
1042/// %mask00 = vector.create_mask %c4, %c8 : vector<4x8xi1>
1043/// %r0 = vector.insert_strided_slice %mask00, %false [0, 0], [1, 1]
1044/// : vector<4x8xi1> into vector<8x16xi1>
1045/// Slice [0,8]:
1046/// mask size = min(max(6-0, 0), 4) x min(max(10-8, 0), 8) = 4x2
1047/// %mask01 = vector.create_mask %c4, %c2 : vector<4x8xi1>
1048/// %r1 = vector.insert_strided_slice %mask01, %r0 [0, 8], [1, 1]
1049/// : vector<4x8xi1> into vector<8x16xi1>
1050/// Slice [4,0]:
1051/// mask size = min(max(6-4, 0), 4) x min(max(10-0, 0), 8) = 2x8
1052/// %mask10 = vector.create_mask %c2, %c8 : vector<4x8xi1>
1053/// %r2 = vector.insert_strided_slice %mask10, %r1 [4, 0], [1, 1]
1054/// : vector<4x8xi1> into vector<8x16xi1>
1055/// Slice [4,8]:
1056/// mask size = min(max(6-4, 0), 4) x min(max(10-8, 0), 8) = 2x2
1057/// %mask11 = vector.create_mask %c2, %c2 : vector<4x8xi1>
1058/// %result = vector.insert_strided_slice %mask11, %r2 [4, 8], [1, 1]
1059/// : vector<4x8xi1> into vector<8x16xi1>
1060struct UnrollCreateMaskPattern : public OpRewritePattern<vector::CreateMaskOp> {
1061 UnrollCreateMaskPattern(MLIRContext *context,
1062 const vector::UnrollVectorOptions &options,
1063 PatternBenefit benefit = 1)
1064 : OpRewritePattern<vector::CreateMaskOp>(context, benefit),
1065 options(options) {}
1066
1067 LogicalResult matchAndRewrite(vector::CreateMaskOp createMaskOp,
1068 PatternRewriter &rewriter) const override {
1069 auto targetShape = getTargetShape(options, createMaskOp);
1070 if (!targetShape)
1071 return failure();
1072
1073 VectorType resultType = createMaskOp.getVectorType();
1074 SmallVector<int64_t> originalSize = *createMaskOp.getShapeForUnroll();
1075 Location loc = createMaskOp.getLoc();
1076
1077 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1078 rewriter.getZeroAttr(resultType));
1079 VectorType targetVectorType =
1080 VectorType::get(*targetShape, rewriter.getI1Type());
1081 SmallVector<int64_t> strides(targetShape->size(), 1);
1082
1083 // In each dimension (d), each unrolled vector computes its mask size as:
1084 // min(max(originalMaskOperands[d] - offset[d], 0), unrolledDimSize[d]).
1085 for (SmallVector<int64_t> offsets :
1086 StaticTileOffsetRange(originalSize, *targetShape)) {
1087 SmallVector<Value> unrolledOperands;
1088
1089 for (auto [i, originalMaskOperand] :
1090 llvm::enumerate(createMaskOp.getOperands())) {
1091 Value offsetVal =
1092 arith::ConstantIndexOp::create(rewriter, loc, offsets[i]);
1093 Value adjustedMaskSize = rewriter.createOrFold<arith::SubIOp>(
1094 loc, originalMaskOperand, offsetVal);
1095 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1096 Value unrolledDimSize =
1097 arith::ConstantIndexOp::create(rewriter, loc, (*targetShape)[i]);
1098 Value nonNegative =
1099 rewriter.createOrFold<arith::MaxSIOp>(loc, adjustedMaskSize, zero);
1100 Value unrolledOperand = rewriter.createOrFold<arith::MinSIOp>(
1101 loc, nonNegative, unrolledDimSize);
1102 unrolledOperands.push_back(unrolledOperand);
1103 }
1104
1105 auto unrolledMask = rewriter.createOrFold<vector::CreateMaskOp>(
1106 loc, targetVectorType, unrolledOperands);
1107 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1108 loc, unrolledMask, result, offsets, strides);
1109 }
1110 rewriter.replaceOp(createMaskOp, result);
1111 return success();
1112 }
1113
1114private:
1115 vector::UnrollVectorOptions options;
1116};
1117
1118/// This pattern unrolls `vector.constant_mask` operations into smaller mask
1119/// operations based on the target unroll shape. Each unrolled slice computes
1120/// whether its elements should be masked based on the original mask dimensions
1121/// and the slice's offset position.
1122///
1123/// Example:
1124/// Given a constant_mask operation:
1125/// %0 = vector.constant_mask [6, 10] : vector<8x16xi1>
1126///
1127/// and a target unroll shape of <4x8>, the pattern produces:
1128///
1129/// %false = arith.constant dense<false> : vector<8x16xi1>
1130///
1131/// Slice [0,0]: elements [0:4, 0:8] - fully within [6, 10] bounds
1132/// %mask00 = vector.constant_mask [4, 8] : vector<4x8xi1>
1133/// %r0 = vector.insert_strided_slice %mask00, %false [0, 0], [1, 1]
1134/// : vector<4x8xi1> into vector<8x16xi1>
1135///
1136/// Slice [0,8]: elements [0:4, 8:16] - partially within bounds
1137/// %mask01 = vector.constant_mask [4, 2] : vector<4x8xi1>
1138/// %r1 = vector.insert_strided_slice %mask01, %r0 [0, 8], [1, 1]
1139/// : vector<4x8xi1> into vector<8x16xi1>
1140///
1141/// Slice [4,0]: elements [4:8, 0:8] - partially within bounds
1142/// %mask10 = vector.constant_mask [2, 8] : vector<4x8xi1>
1143/// %r2 = vector.insert_strided_slice %mask10, %r1 [4, 0], [1, 1]
1144/// : vector<4x8xi1> into vector<8x16xi1>
1145///
1146/// Slice [4,8]: elements [4:8, 8:16] - partially within bounds
1147/// %mask11 = vector.constant_mask [2, 2] : vector<4x8xi1>
1148/// %result = vector.insert_strided_slice %mask11, %r2 [4, 8], [1, 1]
1149/// : vector<4x8xi1> into vector<8x16xi1>
1150struct UnrollConstantMaskPattern
1151 : public OpRewritePattern<vector::ConstantMaskOp> {
1152 UnrollConstantMaskPattern(MLIRContext *context,
1153 const vector::UnrollVectorOptions &options,
1154 PatternBenefit benefit = 1)
1155 : OpRewritePattern<vector::ConstantMaskOp>(context, benefit),
1156 options(options) {}
1157
1158 LogicalResult matchAndRewrite(vector::ConstantMaskOp constantMaskOp,
1159 PatternRewriter &rewriter) const override {
1160 std::optional<SmallVector<int64_t>> targetShape =
1161 getTargetShape(options, constantMaskOp);
1162 if (!targetShape)
1163 return failure();
1164
1165 VectorType resultType = constantMaskOp.getVectorType();
1166 SmallVector<int64_t> originalSize = *constantMaskOp.getShapeForUnroll();
1167 Location loc = constantMaskOp.getLoc();
1168
1169 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1170 rewriter.getZeroAttr(resultType));
1171 VectorType targetVectorType =
1172 VectorType::get(*targetShape, rewriter.getI1Type());
1173 SmallVector<int64_t> strides(targetShape->size(), 1);
1174
1175 // In each dimension (d), each unrolled vector computes its mask size as:
1176 // min(max(originalMaskDim[d] - offset[d], 0), unrolledDimSize[d]).
1177 for (const SmallVector<int64_t> &offsets :
1178 StaticTileOffsetRange(originalSize, *targetShape)) {
1179 SmallVector<int64_t> unrolledMaskDims;
1180
1181 for (auto [i, originalMaskDim] :
1182 llvm::enumerate(constantMaskOp.getMaskDimSizes())) {
1183 // Calculate how many elements in this dimension should be masked
1184 // for this particular slice
1185 int64_t adjustedMaskSize =
1186 std::max(originalMaskDim - offsets[i], static_cast<int64_t>(0));
1187 int64_t unrolledMaskDim =
1188 std::min(adjustedMaskSize, static_cast<int64_t>((*targetShape)[i]));
1189 unrolledMaskDims.push_back(unrolledMaskDim);
1190 }
1191
1192 auto unrolledMask = rewriter.createOrFold<vector::ConstantMaskOp>(
1193 loc, targetVectorType, unrolledMaskDims);
1194 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1195 loc, unrolledMask, result, offsets, strides);
1196 }
1197 rewriter.replaceOp(constantMaskOp, result);
1198 return success();
1199 }
1200
1201private:
1202 vector::UnrollVectorOptions options;
1203};
1204
1205/// Checks whether extractShape is a contiguous slice of shape.
1206/// For extractShape to be contiguous in shape:
1207/// 1) All but the leading dimension of extractShape and shape must match
1208/// exactly. 2) The total number of elements in shape must be evenly divisible
1209/// by
1210/// the total number of elements in extractShape.
1211/// Examples:
1212/// isContiguous([4, 4], [8, 4]) == true
1213/// isContiguous([2, 4], [8, 4]) == true
1214/// isContiguous([2, 2], [8, 4]) == false
1215/// Removes leading unit dimensions to handle cases like:
1216/// isContiguous([1, 16], [1, 32]) == true
1217static bool isContiguous(ArrayRef<int64_t> extractShape,
1219
1220 if (extractShape.empty() || shape.empty() ||
1221 extractShape.size() > shape.size())
1222 return false;
1223
1224 while (extractShape.size() > 1 && extractShape.front() == 1)
1225 extractShape = extractShape.drop_front();
1226
1227 while (shape.size() > 1 && shape.front() == 1) {
1228 shape = shape.drop_front();
1229 }
1230
1231 size_t rankDiff = shape.size() - extractShape.size();
1232 if (!llvm::equal(extractShape.drop_front(), shape.drop_front(rankDiff + 1)))
1233 return false;
1234
1235 int64_t extractElements = ShapedType::getNumElements(extractShape);
1236 int64_t shapeElements = ShapedType::getNumElements(shape);
1237 return shapeElements % extractElements == 0;
1238}
1239
1240/// Determines what shape to use with `vector.extract_strided_slice` to extract
1241/// a contiguous memory region from a source vector. The extraction must be
1242/// contiguous and contain exactly the specified number of elements. If such an
1243/// extraction shape cannot be determined, returns std::nullopt.
1244/// EXAMPLE 1:
1245/// sourceShape = [16], targetElements = 8
1246/// Working right-to-left:
1247/// - Take min(8, 16) = 8 from only dim → extractShape = [8],
1248/// remaining = 8/8 = 1
1249/// Result: [8]
1250///
1251/// EXAMPLE 2:
1252/// sourceShape = [4, 4], targetElements = 8
1253/// Working right-to-left:
1254/// - Take min(8, 4) = 4 from last dim → extractShape = [4],
1255/// remaining = 8/4 = 2
1256/// - Take min(2, 4) = 2 from first dim → extractShape = [2, 4],
1257/// remaining = 2/2 = 1
1258/// Result: [2, 4]
1259static std::optional<SmallVector<int64_t>>
1260calculateSourceExtractShape(ArrayRef<int64_t> sourceShape,
1261 int64_t targetElements) {
1262 SmallVector<int64_t> extractShape;
1263 int64_t remainingElements = targetElements;
1264
1265 // Build extract shape from innermost dimension outward to ensure contiguity.
1266 for (int i = sourceShape.size() - 1; i >= 0 && remainingElements > 1; --i) {
1267 int64_t takeFromDim = std::min(remainingElements, sourceShape[i]);
1268 extractShape.insert(extractShape.begin(), takeFromDim);
1269
1270 if (remainingElements % takeFromDim != 0)
1271 return std::nullopt; // Not evenly divisible.
1272 remainingElements /= takeFromDim;
1273 }
1274
1275 // Fill remaining dimensions with 1.
1276 while (extractShape.size() < sourceShape.size())
1277 extractShape.insert(extractShape.begin(), 1);
1278
1279 if (ShapedType::getNumElements(extractShape) != targetElements)
1280 return std::nullopt;
1281
1282 return extractShape;
1283}
1284
1285// Convert result offsets to source offsets via linear position.
1287calculateSourceOffsets(ArrayRef<int64_t> resultOffsets,
1288 ArrayRef<int64_t> sourceShape,
1289 ArrayRef<int64_t> resultShape) {
1290 // Convert result offsets to linear position.
1291 int64_t linearIndex = linearize(resultOffsets, computeStrides(resultShape));
1292 // Convert linear position to source offsets.
1293 return delinearize(linearIndex, computeStrides(sourceShape));
1294}
1295
1296/// A maximal aligned range of source dims [srcBegin, srcEnd) and result dims
1297/// [resBegin, resEnd) of a `vector.shape_cast` that hold equal element counts.
1298struct ShapeCastReassociationGroup {
1299 int64_t srcBegin, srcEnd;
1300 int64_t resBegin, resEnd;
1301};
1302
1303/// Splits a shape_cast from `sourceShape` to `resultShape` into reassociation
1304/// groups (trailing unit dims absorbed). Returns nullopt if shapes misalign.
1305/// E.g. [8, 32, 32] -> [256, 32] ==> {[0,2)->[0,1)}, {[2,3)->[1,2)}
1306static std::optional<SmallVector<ShapeCastReassociationGroup>>
1307computeShapeCastGroups(ArrayRef<int64_t> sourceShape,
1308 ArrayRef<int64_t> resultShape) {
1310 int64_t si = 0, ri = 0;
1311 int64_t srcRank = sourceShape.size(), resRank = resultShape.size();
1312 while (si < srcRank && ri < resRank) {
1313 int64_t srcBegin = si, resBegin = ri;
1314 int64_t srcProd = sourceShape[si++];
1315 int64_t resProd = resultShape[ri++];
1316 // Grow the smaller side until both groups span the same element count.
1317 while (srcProd != resProd) {
1318 if (srcProd < resProd) {
1319 if (si >= srcRank)
1320 return std::nullopt;
1321 srcProd *= sourceShape[si++];
1322 } else {
1323 if (ri >= resRank)
1324 return std::nullopt;
1325 resProd *= resultShape[ri++];
1326 }
1327 }
1328 // Absorb trailing unit dimensions into the current group.
1329 while (si < srcRank && sourceShape[si] == 1)
1330 ++si;
1331 while (ri < resRank && resultShape[ri] == 1)
1332 ++ri;
1333 groups.push_back({srcBegin, si, resBegin, ri});
1334 }
1335 if (si != srcRank || ri != resRank)
1336 return std::nullopt;
1337 return groups;
1338}
1339
1340/// This pattern unrolls `vector.shape_cast` operations according to the
1341/// provided target unroll shape. It unrolls a large shape cast into smaller
1342/// shape casts by extracting contiguous slices from the source vector, casting
1343/// each slice to the target shape, and assembling the result by inserting each
1344/// computed segment into the appropriate offset of the result vector.
1345///
1346/// The target tile need only be contiguous within each reassociation group of
1347/// the cast (not in the whole result vector), so that each extracted slice
1348/// remains a valid vector. The unrolling proceeds as:
1349/// vector.extract_strided_slice -> vector.shape_cast (on the slice) ->
1350/// vector.insert_strided_slice.
1351///
1352/// NOTE: This replaces a NOP `vector.shape_cast` with strided slices. Per-group
1353/// contiguity keeps those slices contiguous, so they are expected to lower to a
1354/// NOP too. Targets where strided slices do not lower to a NOP should not use
1355/// this pattern, or should pick a tile that avoids introducing such slices.
1356///
1357/// Example (single group):
1358/// Given a shape cast operation:
1359/// %0 = vector.shape_cast %src : vector<8x2xf32> to vector<4x4xf32>
1360///
1361/// and a target unroll shape of <2x4>, the pattern produces:
1362///
1363/// %zero = arith.constant dense<0.0> : vector<4x4xf32>
1364/// %s0 = vector.extract_strided_slice %src [0, 0], [4, 2], [1, 1]
1365/// : vector<8x2xf32> to vector<4x2xf32>
1366/// %sc0 = vector.shape_cast %s0 : vector<4x2xf32> to vector<2x4xf32>
1367/// %i0 = vector.insert_strided_slice %sc0, %zero [0, 0], [1, 1]
1368/// : vector<2x4xf32> into vector<4x4xf32>
1369/// %s1 = vector.extract_strided_slice %src [4, 0], [4, 2], [1, 1]
1370/// : vector<8x2xf32> to vector<4x2xf32>
1371/// %sc1 = vector.shape_cast %s1 : vector<4x2xf32> to vector<2x4xf32>
1372/// %i1 = vector.insert_strided_slice %sc1, %i0 [2, 0], [1, 1]
1373/// : vector<2x4xf32> into vector<4x4xf32>
1374///
1375/// Example (multiple groups): with target tile <8x1x4>, the tile is strided in
1376/// the result <8x1x32> but contiguous per group (8|32 -> 8x1|32), so the
1377/// matching strided box <8x4> is extracted from the source:
1378/// %0 = vector.shape_cast %src : vector<8x32xf32> to vector<8x1x32xf32>
1379///
1380/// %s0 = vector.extract_strided_slice %src [0, 0], [8, 4], [1, 1]
1381/// : vector<8x32xf32> to vector<8x4xf32>
1382/// %sc0 = vector.shape_cast %s0 : vector<8x4xf32> to vector<8x1x4xf32>
1383/// %i0 = vector.insert_strided_slice %sc0, %zero [0, 0, 0], [1, 1, 1]
1384/// : vector<8x1x4xf32> into vector<8x1x32xf32>
1385/// // ... repeat for the remaining slices.
1386///
1387struct UnrollShapeCastPattern : public OpRewritePattern<vector::ShapeCastOp> {
1388 UnrollShapeCastPattern(MLIRContext *context,
1389 const vector::UnrollVectorOptions &options,
1390 PatternBenefit benefit = 1)
1391 : OpRewritePattern<vector::ShapeCastOp>(context, benefit),
1392 options(options) {}
1393
1394 LogicalResult matchAndRewrite(vector::ShapeCastOp shapeCastOp,
1395 PatternRewriter &rewriter) const override {
1396 std::optional<SmallVector<int64_t>> targetShape =
1397 getTargetShape(options, shapeCastOp);
1398 if (!targetShape)
1399 return failure();
1400
1401 VectorType sourceType = shapeCastOp.getSourceVectorType();
1402 VectorType resultType = shapeCastOp.getResultVectorType();
1403 ArrayRef<int64_t> sourceShape = sourceType.getShape();
1404 ArrayRef<int64_t> resultShape = resultType.getShape();
1405
1406 // The cast factors into reassociation groups; the target tile only needs to
1407 // be contiguous within each group, not in the whole result vector.
1408 std::optional<SmallVector<ShapeCastReassociationGroup>> groups =
1409 computeShapeCastGroups(sourceShape, resultShape);
1410 if (!groups)
1411 return rewriter.notifyMatchFailure(
1412 shapeCastOp, "cannot align source and result reassociation groups");
1413
1414 // The tile is right-aligned against the result; left-pad with 1s so it can
1415 // be indexed per group.
1416 SmallVector<int64_t> paddedTarget(resultShape.size(), 1);
1417 llvm::copy(*targetShape,
1418 paddedTarget.end() - static_cast<int64_t>(targetShape->size()));
1419
1420 // Validate per-group contiguity and build the source extract shape.
1421 SmallVector<int64_t> extractShapeStorage;
1422 for (const ShapeCastReassociationGroup &g : *groups) {
1423 ArrayRef<int64_t> resSub =
1424 resultShape.slice(g.resBegin, g.resEnd - g.resBegin);
1425 ArrayRef<int64_t> tgtSub = ArrayRef<int64_t>(paddedTarget)
1426 .slice(g.resBegin, g.resEnd - g.resBegin);
1427 if (!isContiguous(tgtSub, resSub))
1428 return rewriter.notifyMatchFailure(
1429 shapeCastOp, "target shape is not contiguous within a "
1430 "reassociation group of the result vector shape");
1431
1432 ArrayRef<int64_t> srcSub =
1433 sourceShape.slice(g.srcBegin, g.srcEnd - g.srcBegin);
1434 int64_t groupTargetElements = ShapedType::getNumElements(tgtSub);
1435 std::optional<SmallVector<int64_t>> groupExtract =
1436 calculateSourceExtractShape(srcSub, groupTargetElements);
1437 if (!groupExtract)
1438 return rewriter.notifyMatchFailure(
1439 shapeCastOp, "cannot extract the target number of elements "
1440 "contiguously from a source reassociation group");
1441 extractShapeStorage.append(groupExtract->begin(), groupExtract->end());
1442 }
1443 ArrayRef<int64_t> extractShape = extractShapeStorage;
1444
1445 Location loc = shapeCastOp.getLoc();
1446
1447 // Create result vector initialized to zero.
1448 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1449 rewriter.getZeroAttr(resultType));
1450
1451 VectorType targetType =
1452 VectorType::get(*targetShape, sourceType.getElementType());
1453
1454 SmallVector<int64_t> extractStrides(extractShape.size(), 1);
1455 SmallVector<int64_t> insertStrides(targetShape->size(), 1);
1456
1457 for (SmallVector<int64_t> resultOffsets :
1458 StaticTileOffsetRange(resultShape, *targetShape)) {
1459 SmallVector<int64_t> sourceOffsets =
1460 calculateSourceOffsets(resultOffsets, sourceShape, resultShape);
1461 Value sourceChunk = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1462 loc, shapeCastOp.getSource(), sourceOffsets, extractShape,
1464 Value targetChunk = rewriter.createOrFold<vector::ShapeCastOp>(
1465 loc, targetType, sourceChunk);
1466 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1467 loc, targetChunk, result, resultOffsets, insertStrides);
1468 }
1469
1470 rewriter.replaceOp(shapeCastOp, result);
1471 return success();
1472 }
1473
1474private:
1475 vector::UnrollVectorOptions options;
1476};
1477
1478// Unroll vector::BitCastOp into smaller slice-based bitcast operations.
1479// Decomposes the result vector into target shape chunks and bitcasts
1480// corresponding source slices, accounting for element bitwidth ratios.
1481/// Example:
1482/// Given a bitcast Op:
1483///
1484/// vector.bitcast %src : vector<4x8xf32>
1485///
1486/// and a target unroll shape of <2x4>, the pattern produces:
1487///
1488/// %slice_0 = vector.extract_strided_slice %lhs[0, 0] : vector<2x4xf32>
1489/// %slice_0 = vector.bitcast %slice_0 : vector<2x4xf32>
1490/// %result = vector.insert_strided_slice %slice_0, %init[0, 0]
1491/// // ... repeat for remaining slices
1492struct UnrollBitCastPattern : public OpRewritePattern<vector::BitCastOp> {
1493 UnrollBitCastPattern(MLIRContext *context,
1494 const vector::UnrollVectorOptions &options,
1495 PatternBenefit benefit = 1)
1496 : OpRewritePattern<vector::BitCastOp>(context, benefit),
1497 options(options) {}
1498
1499 LogicalResult matchAndRewrite(vector::BitCastOp bitCastOp,
1500 PatternRewriter &rewriter) const override {
1501 auto targetShape = getTargetShape(options, bitCastOp);
1502 if (!targetShape)
1503 return rewriter.notifyMatchFailure(bitCastOp,
1504 "failed to get target shape");
1505
1506 VectorType sourceType = bitCastOp.getSourceVectorType();
1507 VectorType resultType = bitCastOp.getResultVectorType();
1508 ArrayRef<int64_t> resultShape = resultType.getShape();
1509 Location loc = bitCastOp.getLoc();
1510
1511 if (targetShape->size() != resultShape.size())
1512 return rewriter.notifyMatchFailure(
1513 bitCastOp, "target shape rank must match result rank");
1514
1515 unsigned sourceElementBits = sourceType.getElementTypeBitWidth();
1516 unsigned resultElementBits = resultType.getElementTypeBitWidth();
1517
1518 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1519 targetShape->end());
1520 int64_t lastDim = sourceSliceShape.size() - 1;
1521
1522 sourceSliceShape[lastDim] =
1523 ((*targetShape)[lastDim] * resultElementBits) / sourceElementBits;
1524
1525 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1526 rewriter.getZeroAttr(resultType));
1527 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1528 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1529
1530 VectorType targetType =
1531 VectorType::get(*targetShape, resultType.getElementType());
1532
1533 for (SmallVector<int64_t> resultOffsets :
1534 StaticTileOffsetRange(resultShape, *targetShape)) {
1535 SmallVector<int64_t> sourceOffsets = resultOffsets;
1536 sourceOffsets[lastDim] =
1537 (resultOffsets[lastDim] * resultElementBits) / sourceElementBits;
1538
1539 Value sourceSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1540 loc, bitCastOp.getSource(), sourceOffsets, sourceSliceShape,
1541 sourceStrides);
1542 Value bitcastSlice = rewriter.createOrFold<vector::BitCastOp>(
1543 loc, targetType, sourceSlice);
1544 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1545 loc, bitcastSlice, result, resultOffsets, resultStrides);
1546 }
1547
1548 rewriter.replaceOp(bitCastOp, result);
1549 return success();
1550 }
1551
1552private:
1553 vector::UnrollVectorOptions options;
1554};
1555
1556/// Pattern to unroll vector.interleave into smaller slice-sized operations.
1557/// Decomposes a large interleave into slices by extracting slices from both
1558/// input vectors, interleaving them, and inserting back into the result.
1559///
1560/// Example:
1561/// Given an interleave Op:
1562///
1563/// vector.interleave %lhs, %rhs : vector<4x8xf32>
1564///
1565/// and a target unroll shape of <2x4>, the pattern produces:
1566///
1567/// %slice_lhs_0 = vector.extract_strided_slice %lhs[0, 0] : vector<2x2xf32>
1568/// %slice_rhs_0 = vector.extract_strided_slice %rhs[0, 0] : vector<2x2xf32>
1569/// %slice_0 = vector.interleave %slice_lhs_0, %slice_rhs_0
1570/// : vector<2x4xf32>
1571/// %result = vector.insert_strided_slice %slice_0, %init[0, 0]
1572/// // ... repeat for remaining slices
1573struct UnrollInterleavePattern : public OpRewritePattern<vector::InterleaveOp> {
1574 UnrollInterleavePattern(MLIRContext *context,
1575 const vector::UnrollVectorOptions &options,
1576 PatternBenefit benefit = 1)
1577 : OpRewritePattern<vector::InterleaveOp>(context, benefit),
1578 options(options) {}
1579
1580 LogicalResult matchAndRewrite(vector::InterleaveOp interleaveOp,
1581 PatternRewriter &rewriter) const override {
1582 auto targetShape = getTargetShape(options, interleaveOp);
1583 if (!targetShape)
1584 return rewriter.notifyMatchFailure(interleaveOp,
1585 "failed to get target shape");
1586
1587 VectorType resultType = interleaveOp.getResultVectorType();
1588 ArrayRef<int64_t> resultShape = resultType.getShape();
1589 Location loc = interleaveOp.getLoc();
1590
1591 if (targetShape->size() != resultShape.size())
1592 return rewriter.notifyMatchFailure(
1593 interleaveOp, "target shape rank must match result rank");
1594
1595 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1596 targetShape->end());
1597 int64_t lastDim = sourceSliceShape.size() - 1;
1598 sourceSliceShape[lastDim] = (*targetShape)[lastDim] / 2;
1599
1600 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1601 rewriter.getZeroAttr(resultType));
1602 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1603 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1604
1605 VectorType targetType =
1606 VectorType::get(*targetShape, resultType.getElementType());
1607
1608 for (SmallVector<int64_t> resultOffsets :
1609 StaticTileOffsetRange(resultShape, *targetShape)) {
1610 SmallVector<int64_t> sourceOffsets = resultOffsets;
1611 sourceOffsets[lastDim] = resultOffsets[lastDim] / 2;
1612
1613 Value lhsSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1614 loc, interleaveOp.getLhs(), sourceOffsets, sourceSliceShape,
1615 sourceStrides);
1616 Value rhsSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1617 loc, interleaveOp.getRhs(), sourceOffsets, sourceSliceShape,
1618 sourceStrides);
1619 Value interleaveSlice = rewriter.createOrFold<vector::InterleaveOp>(
1620 loc, targetType, lhsSlice, rhsSlice);
1621 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1622 loc, interleaveSlice, result, resultOffsets, resultStrides);
1623 }
1624
1625 rewriter.replaceOp(interleaveOp, result);
1626 return success();
1627 }
1628
1629private:
1630 vector::UnrollVectorOptions options;
1631};
1632
1633/// Pattern to unroll vector.deinterleave into smaller slice-sized operations.
1634/// Decomposes a large deinterleave (which splits a vector into even/odd halves)
1635/// by extracting source slices, deinterleaving them, and inserting into two
1636/// result vectors.
1637///
1638/// Example:
1639/// Given a deinterleave Op:
1640///
1641/// vector.deinterleave %src : vector<4x8xf32>
1642///
1643/// and a target unroll shape of <2x4>, the pattern produces:
1644///
1645/// %slice_0 = vector.extract_strided_slice %src[0, 0] : vector<2x4xf32>
1646/// %slice_lhs_0, %slice_rhs_0 = vector.deinterleave %slice_0 :
1647/// vector<2x4xf32> %result1 = vector.insert_strided_slice %slice_lhs_0,
1648/// %init1[0, 0] %result2 = vector.insert_strided_slice %slice_rhs_0,
1649/// %init2[0, 0]
1650/// // ... repeat for remaining slices
1651struct UnrollDeinterleavePattern
1652 : public OpRewritePattern<vector::DeinterleaveOp> {
1653 UnrollDeinterleavePattern(MLIRContext *context,
1654 const vector::UnrollVectorOptions &options,
1655 PatternBenefit benefit = 1)
1656 : OpRewritePattern<vector::DeinterleaveOp>(context, benefit),
1657 options(options) {}
1658
1659 LogicalResult matchAndRewrite(vector::DeinterleaveOp deinterleaveOp,
1660 PatternRewriter &rewriter) const override {
1661 auto targetShape = getTargetShape(options, deinterleaveOp);
1662 if (!targetShape)
1663 return rewriter.notifyMatchFailure(deinterleaveOp,
1664 "failed to get target shape");
1665
1666 VectorType resultType = deinterleaveOp.getResultVectorType();
1667 ArrayRef<int64_t> resultShape = resultType.getShape();
1668 Location loc = deinterleaveOp.getLoc();
1669
1670 if (targetShape->size() != resultShape.size())
1671 return rewriter.notifyMatchFailure(
1672 deinterleaveOp, "target shape rank must match result rank");
1673
1674 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1675 targetShape->end());
1676 int64_t lastDim = sourceSliceShape.size() - 1;
1677 sourceSliceShape[lastDim] = (*targetShape)[lastDim] * 2;
1678
1679 Value resultOdd = arith::ConstantOp::create(
1680 rewriter, loc, resultType, rewriter.getZeroAttr(resultType));
1681 Value resultEven = arith::ConstantOp::create(
1682 rewriter, loc, resultType, rewriter.getZeroAttr(resultType));
1683 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1684 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1685
1686 for (SmallVector<int64_t> resultOffsets :
1687 StaticTileOffsetRange(resultShape, *targetShape)) {
1688 SmallVector<int64_t> sourceOffsets = resultOffsets;
1689 sourceOffsets[lastDim] = resultOffsets[lastDim] * 2;
1690
1691 Value sourceSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1692 loc, deinterleaveOp.getSource(), sourceOffsets, sourceSliceShape,
1693 sourceStrides);
1694
1695 auto deinterleaveSlice =
1696 vector::DeinterleaveOp::create(rewriter, loc, sourceSlice);
1697
1698 resultOdd = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1699 loc, deinterleaveSlice.getRes1(), resultOdd, resultOffsets,
1700 resultStrides);
1701 resultEven = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1702 loc, deinterleaveSlice.getRes2(), resultEven, resultOffsets,
1703 resultStrides);
1704 }
1705
1706 rewriter.replaceOp(deinterleaveOp, ValueRange{resultOdd, resultEven});
1707 return success();
1708 }
1709
1710private:
1711 vector::UnrollVectorOptions options;
1712};
1713
1714} // namespace
1715
1716void mlir::vector::populateVectorUnrollPatterns(
1718 PatternBenefit benefit) {
1719 patterns.add<UnrollTransferReadPattern, UnrollTransferWritePattern,
1720 UnrollContractionPattern, UnrollElementwisePattern,
1721 UnrollReductionPattern, UnrollMultiReductionPattern,
1722 UnrollTransposePattern, UnrollGatherPattern, UnrollLoadPattern,
1723 UnrollStorePattern, UnrollBroadcastPattern, UnrollFromElements,
1724 UnrollToElements, UnrollStepPattern, UnrollShapeCastPattern,
1725 UnrollCreateMaskPattern, UnrollConstantMaskPattern,
1726 UnrollBitCastPattern, UnrollInterleavePattern,
1727 UnrollDeinterleavePattern>(patterns.getContext(), options,
1728 benefit);
1729}
1730
1731void mlir::vector::populateVectorToElementsUnrollPatterns(
1732 RewritePatternSet &patterns, PatternBenefit benefit) {
1733 patterns.add<UnrollToElements>(patterns.getContext(), UnrollVectorOptions(),
1734 benefit);
1735}
1736
1737void mlir::vector::populateVectorFromElementsUnrollPatterns(
1738 RewritePatternSet &patterns, PatternBenefit benefit) {
1739 patterns.add<UnrollFromElements>(patterns.getContext(), UnrollVectorOptions(),
1740 benefit);
1741}
return success()
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...
lhs
static llvm::ManagedStatic< PassManagerOptions > options
static SmallVector< Value > sliceLoadStoreIndices(PatternRewriter &rewriter, Location loc, OperandRange originalIndices, ArrayRef< int64_t > offsets)
static std::optional< SmallVector< int64_t > > getTargetShape(const vector::UnrollVectorOptions &options, Operation *op)
Return the target shape for unrolling for the given op.
static SmallVector< int64_t > getUnrollOrder(unsigned numLoops, Operation *op, const vector::UnrollVectorOptions &options)
static Operation * cloneOpWithOperandsAndTypes(OpBuilder &builder, Location loc, Operation *op, ArrayRef< Value > operands, ArrayRef< Type > resultTypes)
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 get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
ArrayRef< AffineExpr > getResults() const
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
IntegerType getI1Type()
Definition Builders.cpp:61
MLIRContext * getContext() const
Definition Builders.h:56
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
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 * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:466
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
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
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
Attribute getPropertiesAsAttribute()
Return the properties converted to an attribute.
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
result_range getResults()
Definition Operation.h:440
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
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.
RewritePattern is the common base class for all DAG to DAG replacements.
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,...
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
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.
FailureOr< SmallVector< Value > > unrollVectorValue(TypedValue< VectorType >, RewriterBase &)
Generic utility for unrolling values of type vector<NxAxBx...> to N values of type vector<AxBx....
LogicalResult unrollVectorOp(Operation *op, PatternRewriter &rewriter, UnrollVectorOpFn unrollFn)
Include the generated interface declarations.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
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...
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
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
SmallVector< T > applyPermutationMap(AffineMap map, llvm::ArrayRef< T > source)
Apply a permutation from map to source and return the result.
Definition AffineMap.h:675
int64_t linearize(ArrayRef< int64_t > offsets, ArrayRef< int64_t > basis)
Return the linearized index of 'offsets' w.r.t.
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.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
This represents an operation in an abstracted form, suitable for use with the builder APIs.
Attribute propertiesAttr
This Attribute is used to opaquely construct the properties of the operation.
Options that control the vector unrolling.