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