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 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1095 rewriter.getZeroAttr(resultType));
1096 VectorType targetVectorType =
1097 VectorType::get(*targetShape, rewriter.getI1Type());
1098 SmallVector<int64_t> strides(targetShape->size(), 1);
1099
1100 // In each dimension (d), each unrolled vector computes its mask size as:
1101 // min(max(originalMaskOperands[d] - offset[d], 0), unrolledDimSize[d]).
1102 for (SmallVector<int64_t> offsets :
1103 StaticTileOffsetRange(originalSize, *targetShape)) {
1104 SmallVector<Value> unrolledOperands;
1105
1106 for (auto [i, originalMaskOperand] :
1107 llvm::enumerate(createMaskOp.getOperands())) {
1108 Value offsetVal =
1109 arith::ConstantIndexOp::create(rewriter, loc, offsets[i]);
1110 Value adjustedMaskSize = rewriter.createOrFold<arith::SubIOp>(
1111 loc, originalMaskOperand, offsetVal);
1112 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1113 Value unrolledDimSize =
1114 arith::ConstantIndexOp::create(rewriter, loc, (*targetShape)[i]);
1115 Value nonNegative =
1116 rewriter.createOrFold<arith::MaxSIOp>(loc, adjustedMaskSize, zero);
1117 Value unrolledOperand = rewriter.createOrFold<arith::MinSIOp>(
1118 loc, nonNegative, unrolledDimSize);
1119 unrolledOperands.push_back(unrolledOperand);
1120 }
1121
1122 auto unrolledMask = rewriter.createOrFold<vector::CreateMaskOp>(
1123 loc, targetVectorType, unrolledOperands);
1124 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1125 loc, unrolledMask, result, offsets, strides);
1126 }
1127 rewriter.replaceOp(createMaskOp, result);
1128 return success();
1129 }
1130
1131private:
1132 vector::UnrollVectorOptions options;
1133};
1134
1135/// This pattern unrolls `vector.constant_mask` operations into smaller mask
1136/// operations based on the target unroll shape. Each unrolled slice computes
1137/// whether its elements should be masked based on the original mask dimensions
1138/// and the slice's offset position.
1139///
1140/// Example:
1141/// Given a constant_mask operation:
1142/// %0 = vector.constant_mask [6, 10] : vector<8x16xi1>
1143///
1144/// and a target unroll shape of <4x8>, the pattern produces:
1145///
1146/// %false = arith.constant dense<false> : vector<8x16xi1>
1147///
1148/// Slice [0,0]: elements [0:4, 0:8] - fully within [6, 10] bounds
1149/// %mask00 = vector.constant_mask [4, 8] : vector<4x8xi1>
1150/// %r0 = vector.insert_strided_slice %mask00, %false [0, 0], [1, 1]
1151/// : vector<4x8xi1> into vector<8x16xi1>
1152///
1153/// Slice [0,8]: elements [0:4, 8:16] - partially within bounds
1154/// %mask01 = vector.constant_mask [4, 2] : vector<4x8xi1>
1155/// %r1 = vector.insert_strided_slice %mask01, %r0 [0, 8], [1, 1]
1156/// : vector<4x8xi1> into vector<8x16xi1>
1157///
1158/// Slice [4,0]: elements [4:8, 0:8] - partially within bounds
1159/// %mask10 = vector.constant_mask [2, 8] : vector<4x8xi1>
1160/// %r2 = vector.insert_strided_slice %mask10, %r1 [4, 0], [1, 1]
1161/// : vector<4x8xi1> into vector<8x16xi1>
1162///
1163/// Slice [4,8]: elements [4:8, 8:16] - partially within bounds
1164/// %mask11 = vector.constant_mask [2, 2] : vector<4x8xi1>
1165/// %result = vector.insert_strided_slice %mask11, %r2 [4, 8], [1, 1]
1166/// : vector<4x8xi1> into vector<8x16xi1>
1167struct UnrollConstantMaskPattern
1168 : public OpRewritePattern<vector::ConstantMaskOp> {
1169 UnrollConstantMaskPattern(MLIRContext *context,
1170 const vector::UnrollVectorOptions &options,
1171 PatternBenefit benefit = 1)
1172 : OpRewritePattern<vector::ConstantMaskOp>(context, benefit),
1173 options(options) {}
1174
1175 LogicalResult matchAndRewrite(vector::ConstantMaskOp constantMaskOp,
1176 PatternRewriter &rewriter) const override {
1177 std::optional<SmallVector<int64_t>> targetShape =
1178 getTargetShape(options, constantMaskOp);
1179 if (!targetShape)
1180 return failure();
1181
1182 VectorType resultType = constantMaskOp.getVectorType();
1183 SmallVector<int64_t> originalSize =
1184 *constantMaskOp.getIterationShapeToUnroll();
1185 Location loc = constantMaskOp.getLoc();
1186
1187 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1188 rewriter.getZeroAttr(resultType));
1189 VectorType targetVectorType =
1190 VectorType::get(*targetShape, rewriter.getI1Type());
1191 SmallVector<int64_t> strides(targetShape->size(), 1);
1192
1193 // In each dimension (d), each unrolled vector computes its mask size as:
1194 // min(max(originalMaskDim[d] - offset[d], 0), unrolledDimSize[d]).
1195 for (const SmallVector<int64_t> &offsets :
1196 StaticTileOffsetRange(originalSize, *targetShape)) {
1197 SmallVector<int64_t> unrolledMaskDims;
1198
1199 for (auto [i, originalMaskDim] :
1200 llvm::enumerate(constantMaskOp.getMaskDimSizes())) {
1201 // Calculate how many elements in this dimension should be masked
1202 // for this particular slice
1203 int64_t adjustedMaskSize =
1204 std::max(originalMaskDim - offsets[i], static_cast<int64_t>(0));
1205 int64_t unrolledMaskDim =
1206 std::min(adjustedMaskSize, static_cast<int64_t>((*targetShape)[i]));
1207 unrolledMaskDims.push_back(unrolledMaskDim);
1208 }
1209
1210 auto unrolledMask = rewriter.createOrFold<vector::ConstantMaskOp>(
1211 loc, targetVectorType, unrolledMaskDims);
1212 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1213 loc, unrolledMask, result, offsets, strides);
1214 }
1215 rewriter.replaceOp(constantMaskOp, result);
1216 return success();
1217 }
1218
1219private:
1220 vector::UnrollVectorOptions options;
1221};
1222
1223/// Checks whether extractShape is a contiguous slice of shape.
1224/// For extractShape to be contiguous in shape:
1225/// 1) All but the leading dimension of extractShape and shape must match
1226/// exactly. 2) The total number of elements in shape must be evenly divisible
1227/// by
1228/// the total number of elements in extractShape.
1229/// Examples:
1230/// isContiguous([4, 4], [8, 4]) == true
1231/// isContiguous([2, 4], [8, 4]) == true
1232/// isContiguous([2, 2], [8, 4]) == false
1233/// Removes leading unit dimensions to handle cases like:
1234/// isContiguous([1, 16], [1, 32]) == true
1235static bool isContiguous(ArrayRef<int64_t> extractShape,
1237
1238 if (extractShape.empty() || shape.empty() ||
1239 extractShape.size() > shape.size())
1240 return false;
1241
1242 while (extractShape.size() > 1 && extractShape.front() == 1)
1243 extractShape = extractShape.drop_front();
1244
1245 while (shape.size() > 1 && shape.front() == 1) {
1246 shape = shape.drop_front();
1247 }
1248
1249 size_t rankDiff = shape.size() - extractShape.size();
1250 if (!llvm::equal(extractShape.drop_front(), shape.drop_front(rankDiff + 1)))
1251 return false;
1252
1253 int64_t extractElements = ShapedType::getNumElements(extractShape);
1254 int64_t shapeElements = ShapedType::getNumElements(shape);
1255 return shapeElements % extractElements == 0;
1256}
1257
1258/// Determines what shape to use with `vector.extract_strided_slice` to extract
1259/// a contiguous memory region from a source vector. The extraction must be
1260/// contiguous and contain exactly the specified number of elements. If such an
1261/// extraction shape cannot be determined, returns std::nullopt.
1262/// EXAMPLE 1:
1263/// sourceShape = [16], targetElements = 8
1264/// Working right-to-left:
1265/// - Take min(8, 16) = 8 from only dim → extractShape = [8],
1266/// remaining = 8/8 = 1
1267/// Result: [8]
1268///
1269/// EXAMPLE 2:
1270/// sourceShape = [4, 4], targetElements = 8
1271/// Working right-to-left:
1272/// - Take min(8, 4) = 4 from last dim → extractShape = [4],
1273/// remaining = 8/4 = 2
1274/// - Take min(2, 4) = 2 from first dim → extractShape = [2, 4],
1275/// remaining = 2/2 = 1
1276/// Result: [2, 4]
1277static std::optional<SmallVector<int64_t>>
1278calculateSourceExtractShape(ArrayRef<int64_t> sourceShape,
1279 int64_t targetElements) {
1280 SmallVector<int64_t> extractShape;
1281 int64_t remainingElements = targetElements;
1282
1283 // Build extract shape from innermost dimension outward to ensure contiguity.
1284 for (int i = sourceShape.size() - 1; i >= 0 && remainingElements > 1; --i) {
1285 int64_t takeFromDim = std::min(remainingElements, sourceShape[i]);
1286 extractShape.insert(extractShape.begin(), takeFromDim);
1287
1288 if (remainingElements % takeFromDim != 0)
1289 return std::nullopt; // Not evenly divisible.
1290 remainingElements /= takeFromDim;
1291 }
1292
1293 // Fill remaining dimensions with 1.
1294 while (extractShape.size() < sourceShape.size())
1295 extractShape.insert(extractShape.begin(), 1);
1296
1297 if (ShapedType::getNumElements(extractShape) != targetElements)
1298 return std::nullopt;
1299
1300 return extractShape;
1301}
1302
1303// Convert result offsets to source offsets via linear position.
1305calculateSourceOffsets(ArrayRef<int64_t> resultOffsets,
1306 ArrayRef<int64_t> sourceShape,
1307 ArrayRef<int64_t> resultShape) {
1308 // Convert result offsets to linear position.
1309 int64_t linearIndex = linearize(resultOffsets, computeStrides(resultShape));
1310 // Convert linear position to source offsets.
1311 return delinearize(linearIndex, computeStrides(sourceShape));
1312}
1313
1314/// A maximal aligned range of source dims [srcBegin, srcEnd) and result dims
1315/// [resBegin, resEnd) of a `vector.shape_cast` that hold equal element counts.
1316struct ShapeCastReassociationGroup {
1317 int64_t srcBegin, srcEnd;
1318 int64_t resBegin, resEnd;
1319};
1320
1321/// Splits a shape_cast from `sourceShape` to `resultShape` into reassociation
1322/// groups (trailing unit dims absorbed). Returns nullopt if shapes misalign.
1323/// E.g. [8, 32, 32] -> [256, 32] ==> {[0,2)->[0,1)}, {[2,3)->[1,2)}
1324static std::optional<SmallVector<ShapeCastReassociationGroup>>
1325computeShapeCastGroups(ArrayRef<int64_t> sourceShape,
1326 ArrayRef<int64_t> resultShape) {
1328 int64_t si = 0, ri = 0;
1329 int64_t srcRank = sourceShape.size(), resRank = resultShape.size();
1330 while (si < srcRank && ri < resRank) {
1331 int64_t srcBegin = si, resBegin = ri;
1332 int64_t srcProd = sourceShape[si++];
1333 int64_t resProd = resultShape[ri++];
1334 // Grow the smaller side until both groups span the same element count.
1335 while (srcProd != resProd) {
1336 if (srcProd < resProd) {
1337 if (si >= srcRank)
1338 return std::nullopt;
1339 srcProd *= sourceShape[si++];
1340 } else {
1341 if (ri >= resRank)
1342 return std::nullopt;
1343 resProd *= resultShape[ri++];
1344 }
1345 }
1346 // Absorb trailing unit dimensions into the current group.
1347 while (si < srcRank && sourceShape[si] == 1)
1348 ++si;
1349 while (ri < resRank && resultShape[ri] == 1)
1350 ++ri;
1351 groups.push_back({srcBegin, si, resBegin, ri});
1352 }
1353 if (si != srcRank || ri != resRank)
1354 return std::nullopt;
1355 return groups;
1356}
1357
1358/// This pattern unrolls `vector.shape_cast` operations according to the
1359/// provided target unroll shape. It unrolls a large shape cast into smaller
1360/// shape casts by extracting contiguous slices from the source vector, casting
1361/// each slice to the target shape, and assembling the result by inserting each
1362/// computed segment into the appropriate offset of the result vector.
1363///
1364/// The target tile need only be contiguous within each reassociation group of
1365/// the cast (not in the whole result vector), so that each extracted slice
1366/// remains a valid vector. The unrolling proceeds as:
1367/// vector.extract_strided_slice -> vector.shape_cast (on the slice) ->
1368/// vector.insert_strided_slice.
1369///
1370/// NOTE: This replaces a NOP `vector.shape_cast` with strided slices. Per-group
1371/// contiguity keeps those slices contiguous, so they are expected to lower to a
1372/// NOP too. Targets where strided slices do not lower to a NOP should not use
1373/// this pattern, or should pick a tile that avoids introducing such slices.
1374///
1375/// Example (single group):
1376/// Given a shape cast operation:
1377/// %0 = vector.shape_cast %src : vector<8x2xf32> to vector<4x4xf32>
1378///
1379/// and a target unroll shape of <2x4>, the pattern produces:
1380///
1381/// %zero = arith.constant dense<0.0> : vector<4x4xf32>
1382/// %s0 = vector.extract_strided_slice %src [0, 0], [4, 2], [1, 1]
1383/// : vector<8x2xf32> to vector<4x2xf32>
1384/// %sc0 = vector.shape_cast %s0 : vector<4x2xf32> to vector<2x4xf32>
1385/// %i0 = vector.insert_strided_slice %sc0, %zero [0, 0], [1, 1]
1386/// : vector<2x4xf32> into vector<4x4xf32>
1387/// %s1 = vector.extract_strided_slice %src [4, 0], [4, 2], [1, 1]
1388/// : vector<8x2xf32> to vector<4x2xf32>
1389/// %sc1 = vector.shape_cast %s1 : vector<4x2xf32> to vector<2x4xf32>
1390/// %i1 = vector.insert_strided_slice %sc1, %i0 [2, 0], [1, 1]
1391/// : vector<2x4xf32> into vector<4x4xf32>
1392///
1393/// Example (multiple groups): with target tile <8x1x4>, the tile is strided in
1394/// the result <8x1x32> but contiguous per group (8|32 -> 8x1|32), so the
1395/// matching strided box <8x4> is extracted from the source:
1396/// %0 = vector.shape_cast %src : vector<8x32xf32> to vector<8x1x32xf32>
1397///
1398/// %s0 = vector.extract_strided_slice %src [0, 0], [8, 4], [1, 1]
1399/// : vector<8x32xf32> to vector<8x4xf32>
1400/// %sc0 = vector.shape_cast %s0 : vector<8x4xf32> to vector<8x1x4xf32>
1401/// %i0 = vector.insert_strided_slice %sc0, %zero [0, 0, 0], [1, 1, 1]
1402/// : vector<8x1x4xf32> into vector<8x1x32xf32>
1403/// // ... repeat for the remaining slices.
1404///
1405struct UnrollShapeCastPattern : public OpRewritePattern<vector::ShapeCastOp> {
1406 UnrollShapeCastPattern(MLIRContext *context,
1407 const vector::UnrollVectorOptions &options,
1408 PatternBenefit benefit = 1)
1409 : OpRewritePattern<vector::ShapeCastOp>(context, benefit),
1410 options(options) {}
1411
1412 LogicalResult matchAndRewrite(vector::ShapeCastOp shapeCastOp,
1413 PatternRewriter &rewriter) const override {
1414 std::optional<SmallVector<int64_t>> targetShape =
1415 getTargetShape(options, shapeCastOp);
1416 if (!targetShape)
1417 return failure();
1418
1419 VectorType sourceType = shapeCastOp.getSourceVectorType();
1420 VectorType resultType = shapeCastOp.getResultVectorType();
1421 ArrayRef<int64_t> sourceShape = sourceType.getShape();
1422 ArrayRef<int64_t> resultShape = resultType.getShape();
1423
1424 // The cast factors into reassociation groups; the target tile only needs to
1425 // be contiguous within each group, not in the whole result vector.
1426 std::optional<SmallVector<ShapeCastReassociationGroup>> groups =
1427 computeShapeCastGroups(sourceShape, resultShape);
1428 if (!groups)
1429 return rewriter.notifyMatchFailure(
1430 shapeCastOp, "cannot align source and result reassociation groups");
1431
1432 // The tile is right-aligned against the result; left-pad with 1s so it can
1433 // be indexed per group.
1434 SmallVector<int64_t> paddedTarget(resultShape.size(), 1);
1435 llvm::copy(*targetShape,
1436 paddedTarget.end() - static_cast<int64_t>(targetShape->size()));
1437
1438 // Validate per-group contiguity and build the source extract shape.
1439 SmallVector<int64_t> extractShapeStorage;
1440 for (const ShapeCastReassociationGroup &g : *groups) {
1441 ArrayRef<int64_t> resSub =
1442 resultShape.slice(g.resBegin, g.resEnd - g.resBegin);
1443 ArrayRef<int64_t> tgtSub = ArrayRef<int64_t>(paddedTarget)
1444 .slice(g.resBegin, g.resEnd - g.resBegin);
1445 if (!isContiguous(tgtSub, resSub))
1446 return rewriter.notifyMatchFailure(
1447 shapeCastOp, "target shape is not contiguous within a "
1448 "reassociation group of the result vector shape");
1449
1450 ArrayRef<int64_t> srcSub =
1451 sourceShape.slice(g.srcBegin, g.srcEnd - g.srcBegin);
1452 int64_t groupTargetElements = ShapedType::getNumElements(tgtSub);
1453 std::optional<SmallVector<int64_t>> groupExtract =
1454 calculateSourceExtractShape(srcSub, groupTargetElements);
1455 if (!groupExtract)
1456 return rewriter.notifyMatchFailure(
1457 shapeCastOp, "cannot extract the target number of elements "
1458 "contiguously from a source reassociation group");
1459 extractShapeStorage.append(groupExtract->begin(), groupExtract->end());
1460 }
1461 ArrayRef<int64_t> extractShape = extractShapeStorage;
1462
1463 Location loc = shapeCastOp.getLoc();
1464
1465 // Create result vector initialized to zero.
1466 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1467 rewriter.getZeroAttr(resultType));
1468
1469 VectorType targetType =
1470 VectorType::get(*targetShape, sourceType.getElementType());
1471
1472 SmallVector<int64_t> extractStrides(extractShape.size(), 1);
1473 SmallVector<int64_t> insertStrides(targetShape->size(), 1);
1474
1475 for (SmallVector<int64_t> resultOffsets :
1476 StaticTileOffsetRange(resultShape, *targetShape)) {
1477 SmallVector<int64_t> sourceOffsets =
1478 calculateSourceOffsets(resultOffsets, sourceShape, resultShape);
1479 Value sourceChunk = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1480 loc, shapeCastOp.getSource(), sourceOffsets, extractShape,
1482 Value targetChunk = rewriter.createOrFold<vector::ShapeCastOp>(
1483 loc, targetType, sourceChunk);
1484 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1485 loc, targetChunk, result, resultOffsets, insertStrides);
1486 }
1487
1488 rewriter.replaceOp(shapeCastOp, result);
1489 return success();
1490 }
1491
1492private:
1493 vector::UnrollVectorOptions options;
1494};
1495
1496// Unroll vector::BitCastOp into smaller slice-based bitcast operations.
1497// Decomposes the result vector into target shape chunks and bitcasts
1498// corresponding source slices, accounting for element bitwidth ratios.
1499/// Example:
1500/// Given a bitcast Op:
1501///
1502/// vector.bitcast %src : vector<4x8xf32>
1503///
1504/// and a target unroll shape of <2x4>, the pattern produces:
1505///
1506/// %slice_0 = vector.extract_strided_slice %lhs[0, 0] : vector<2x4xf32>
1507/// %slice_0 = vector.bitcast %slice_0 : vector<2x4xf32>
1508/// %result = vector.insert_strided_slice %slice_0, %init[0, 0]
1509/// // ... repeat for remaining slices
1510struct UnrollBitCastPattern : public OpRewritePattern<vector::BitCastOp> {
1511 UnrollBitCastPattern(MLIRContext *context,
1512 const vector::UnrollVectorOptions &options,
1513 PatternBenefit benefit = 1)
1514 : OpRewritePattern<vector::BitCastOp>(context, benefit),
1515 options(options) {}
1516
1517 LogicalResult matchAndRewrite(vector::BitCastOp bitCastOp,
1518 PatternRewriter &rewriter) const override {
1519 auto targetShape = getTargetShape(options, bitCastOp);
1520 if (!targetShape)
1521 return rewriter.notifyMatchFailure(bitCastOp,
1522 "failed to get target shape");
1523
1524 VectorType sourceType = bitCastOp.getSourceVectorType();
1525 VectorType resultType = bitCastOp.getResultVectorType();
1526 ArrayRef<int64_t> resultShape = resultType.getShape();
1527 Location loc = bitCastOp.getLoc();
1528
1529 if (targetShape->size() != resultShape.size())
1530 return rewriter.notifyMatchFailure(
1531 bitCastOp, "target shape rank must match result rank");
1532
1533 unsigned sourceElementBits = sourceType.getElementTypeBitWidth();
1534 unsigned resultElementBits = resultType.getElementTypeBitWidth();
1535
1536 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1537 targetShape->end());
1538 int64_t lastDim = sourceSliceShape.size() - 1;
1539
1540 sourceSliceShape[lastDim] =
1541 ((*targetShape)[lastDim] * resultElementBits) / sourceElementBits;
1542
1543 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1544 rewriter.getZeroAttr(resultType));
1545 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1546 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1547
1548 VectorType targetType =
1549 VectorType::get(*targetShape, resultType.getElementType());
1550
1551 for (SmallVector<int64_t> resultOffsets :
1552 StaticTileOffsetRange(resultShape, *targetShape)) {
1553 SmallVector<int64_t> sourceOffsets = resultOffsets;
1554 sourceOffsets[lastDim] =
1555 (resultOffsets[lastDim] * resultElementBits) / sourceElementBits;
1556
1557 Value sourceSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1558 loc, bitCastOp.getSource(), sourceOffsets, sourceSliceShape,
1559 sourceStrides);
1560 Value bitcastSlice = rewriter.createOrFold<vector::BitCastOp>(
1561 loc, targetType, sourceSlice);
1562 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1563 loc, bitcastSlice, result, resultOffsets, resultStrides);
1564 }
1565
1566 rewriter.replaceOp(bitCastOp, result);
1567 return success();
1568 }
1569
1570private:
1571 vector::UnrollVectorOptions options;
1572};
1573
1574/// Pattern to unroll vector.interleave into smaller slice-sized operations.
1575/// Decomposes a large interleave into slices by extracting slices from both
1576/// input vectors, interleaving them, and inserting back into the result.
1577///
1578/// Example:
1579/// Given an interleave Op:
1580///
1581/// vector.interleave %lhs, %rhs : vector<4x8xf32>
1582///
1583/// and a target unroll shape of <2x4>, the pattern produces:
1584///
1585/// %slice_lhs_0 = vector.extract_strided_slice %lhs[0, 0] : vector<2x2xf32>
1586/// %slice_rhs_0 = vector.extract_strided_slice %rhs[0, 0] : vector<2x2xf32>
1587/// %slice_0 = vector.interleave %slice_lhs_0, %slice_rhs_0
1588/// : vector<2x4xf32>
1589/// %result = vector.insert_strided_slice %slice_0, %init[0, 0]
1590/// // ... repeat for remaining slices
1591struct UnrollInterleavePattern : public OpRewritePattern<vector::InterleaveOp> {
1592 UnrollInterleavePattern(MLIRContext *context,
1593 const vector::UnrollVectorOptions &options,
1594 PatternBenefit benefit = 1)
1595 : OpRewritePattern<vector::InterleaveOp>(context, benefit),
1596 options(options) {}
1597
1598 LogicalResult matchAndRewrite(vector::InterleaveOp interleaveOp,
1599 PatternRewriter &rewriter) const override {
1600 auto targetShape = getTargetShape(options, interleaveOp);
1601 if (!targetShape)
1602 return rewriter.notifyMatchFailure(interleaveOp,
1603 "failed to get target shape");
1604
1605 VectorType resultType = interleaveOp.getResultVectorType();
1606 ArrayRef<int64_t> resultShape = resultType.getShape();
1607 Location loc = interleaveOp.getLoc();
1608
1609 if (targetShape->size() != resultShape.size())
1610 return rewriter.notifyMatchFailure(
1611 interleaveOp, "target shape rank must match result rank");
1612
1613 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1614 targetShape->end());
1615 int64_t lastDim = sourceSliceShape.size() - 1;
1616 sourceSliceShape[lastDim] = (*targetShape)[lastDim] / 2;
1617
1618 Value result = arith::ConstantOp::create(rewriter, loc, resultType,
1619 rewriter.getZeroAttr(resultType));
1620 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1621 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1622
1623 VectorType targetType =
1624 VectorType::get(*targetShape, resultType.getElementType());
1625
1626 for (SmallVector<int64_t> resultOffsets :
1627 StaticTileOffsetRange(resultShape, *targetShape)) {
1628 SmallVector<int64_t> sourceOffsets = resultOffsets;
1629 sourceOffsets[lastDim] = resultOffsets[lastDim] / 2;
1630
1631 Value lhsSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1632 loc, interleaveOp.getLhs(), sourceOffsets, sourceSliceShape,
1633 sourceStrides);
1634 Value rhsSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1635 loc, interleaveOp.getRhs(), sourceOffsets, sourceSliceShape,
1636 sourceStrides);
1637 Value interleaveSlice = rewriter.createOrFold<vector::InterleaveOp>(
1638 loc, targetType, lhsSlice, rhsSlice);
1639 result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1640 loc, interleaveSlice, result, resultOffsets, resultStrides);
1641 }
1642
1643 rewriter.replaceOp(interleaveOp, result);
1644 return success();
1645 }
1646
1647private:
1648 vector::UnrollVectorOptions options;
1649};
1650
1651/// Pattern to unroll vector.deinterleave into smaller slice-sized operations.
1652/// Decomposes a large deinterleave (which splits a vector into even/odd halves)
1653/// by extracting source slices, deinterleaving them, and inserting into two
1654/// result vectors.
1655///
1656/// Example:
1657/// Given a deinterleave Op:
1658///
1659/// vector.deinterleave %src : vector<4x8xf32>
1660///
1661/// and a target unroll shape of <2x4>, the pattern produces:
1662///
1663/// %slice_0 = vector.extract_strided_slice %src[0, 0] : vector<2x4xf32>
1664/// %slice_lhs_0, %slice_rhs_0 = vector.deinterleave %slice_0 :
1665/// vector<2x4xf32> %result1 = vector.insert_strided_slice %slice_lhs_0,
1666/// %init1[0, 0] %result2 = vector.insert_strided_slice %slice_rhs_0,
1667/// %init2[0, 0]
1668/// // ... repeat for remaining slices
1669struct UnrollDeinterleavePattern
1670 : public OpRewritePattern<vector::DeinterleaveOp> {
1671 UnrollDeinterleavePattern(MLIRContext *context,
1672 const vector::UnrollVectorOptions &options,
1673 PatternBenefit benefit = 1)
1674 : OpRewritePattern<vector::DeinterleaveOp>(context, benefit),
1675 options(options) {}
1676
1677 LogicalResult matchAndRewrite(vector::DeinterleaveOp deinterleaveOp,
1678 PatternRewriter &rewriter) const override {
1679 auto targetShape = getTargetShape(options, deinterleaveOp);
1680 if (!targetShape)
1681 return rewriter.notifyMatchFailure(deinterleaveOp,
1682 "failed to get target shape");
1683
1684 VectorType resultType = deinterleaveOp.getResultVectorType();
1685 ArrayRef<int64_t> resultShape = resultType.getShape();
1686 Location loc = deinterleaveOp.getLoc();
1687
1688 if (targetShape->size() != resultShape.size())
1689 return rewriter.notifyMatchFailure(
1690 deinterleaveOp, "target shape rank must match result rank");
1691
1692 SmallVector<int64_t> sourceSliceShape(targetShape->begin(),
1693 targetShape->end());
1694 int64_t lastDim = sourceSliceShape.size() - 1;
1695 sourceSliceShape[lastDim] = (*targetShape)[lastDim] * 2;
1696
1697 Value resultOdd = arith::ConstantOp::create(
1698 rewriter, loc, resultType, rewriter.getZeroAttr(resultType));
1699 Value resultEven = arith::ConstantOp::create(
1700 rewriter, loc, resultType, rewriter.getZeroAttr(resultType));
1701 SmallVector<int64_t> resultStrides(targetShape->size(), 1);
1702 SmallVector<int64_t> sourceStrides(sourceSliceShape.size(), 1);
1703
1704 for (SmallVector<int64_t> resultOffsets :
1705 StaticTileOffsetRange(resultShape, *targetShape)) {
1706 SmallVector<int64_t> sourceOffsets = resultOffsets;
1707 sourceOffsets[lastDim] = resultOffsets[lastDim] * 2;
1708
1709 Value sourceSlice = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
1710 loc, deinterleaveOp.getSource(), sourceOffsets, sourceSliceShape,
1711 sourceStrides);
1712
1713 auto deinterleaveSlice =
1714 vector::DeinterleaveOp::create(rewriter, loc, sourceSlice);
1715
1716 resultOdd = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1717 loc, deinterleaveSlice.getRes1(), resultOdd, resultOffsets,
1718 resultStrides);
1719 resultEven = rewriter.createOrFold<vector::InsertStridedSliceOp>(
1720 loc, deinterleaveSlice.getRes2(), resultEven, resultOffsets,
1721 resultStrides);
1722 }
1723
1724 rewriter.replaceOp(deinterleaveOp, ValueRange{resultOdd, resultEven});
1725 return success();
1726 }
1727
1728private:
1729 vector::UnrollVectorOptions options;
1730};
1731
1732} // namespace
1733
1734void mlir::vector::populateVectorUnrollPatterns(
1736 PatternBenefit benefit) {
1737 patterns.add<UnrollTransferReadPattern, UnrollTransferWritePattern,
1738 UnrollContractionPattern, UnrollElementwisePattern,
1739 UnrollReductionPattern, UnrollMultiReductionPattern,
1740 UnrollTransposePattern, UnrollGatherPattern, UnrollLoadPattern,
1741 UnrollStorePattern, UnrollBroadcastPattern, UnrollFromElements,
1742 UnrollToElements, UnrollStepPattern, UnrollShapeCastPattern,
1743 UnrollCreateMaskPattern, UnrollConstantMaskPattern,
1744 UnrollBitCastPattern, UnrollInterleavePattern,
1745 UnrollDeinterleavePattern>(patterns.getContext(), options,
1746 benefit);
1747}
1748
1749void mlir::vector::populateVectorContractUnrollPatterns(
1751 PatternBenefit benefit) {
1752 patterns.add<UnrollContractionPattern>(patterns.getContext(), options,
1753 benefit);
1754}
1755
1756void mlir::vector::populateVectorToElementsUnrollPatterns(
1757 RewritePatternSet &patterns, PatternBenefit benefit) {
1758 patterns.add<UnrollToElements>(patterns.getContext(), UnrollVectorOptions(),
1759 benefit);
1760}
1761
1762void mlir::vector::populateVectorFromElementsUnrollPatterns(
1763 RewritePatternSet &patterns, PatternBenefit benefit) {
1764 patterns.add<UnrollFromElements>(patterns.getContext(), UnrollVectorOptions(),
1765 benefit);
1766}
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:64
This class helps build Operations.
Definition Builders.h:210
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
Operation * 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: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:734
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.