MLIR 23.0.0git
Vectorization.cpp
Go to the documentation of this file.
1//===- Vectorization.cpp - Implementation of linalg Vectorization ---------===//
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 the linalg dialect Vectorization transformations.
10//
11//===----------------------------------------------------------------------===//
13
28#include "mlir/IR/AffineExpr.h"
29#include "mlir/IR/AffineMap.h"
30#include "mlir/IR/Builders.h"
35#include "mlir/IR/Value.h"
36#include "mlir/Support/LLVM.h"
38#include "llvm/ADT/STLExtras.h"
39#include "llvm/ADT/Sequence.h"
40#include "llvm/ADT/SmallVector.h"
41#include "llvm/ADT/SmallVectorExtras.h"
42#include "llvm/ADT/TypeSwitch.h"
43#include "llvm/Support/DebugLog.h"
44#include "llvm/Support/InterleavedRange.h"
45#include "llvm/Support/MathExtras.h"
46#include "llvm/Support/raw_ostream.h"
47#include <optional>
48
49using namespace mlir;
50using namespace mlir::linalg;
51
52#define DEBUG_TYPE "linalg-vectorization"
53
54/// Try to vectorize `convOp` as a convolution.
55static FailureOr<Operation *>
56vectorizeConvolution(RewriterBase &rewriter, LinalgOp convOp,
57 ArrayRef<int64_t> inputVecSizes = {},
58 ArrayRef<bool> inputVecScalableFlags = {},
59 bool flatten1DDepthwiseConv = false);
60
61/// Vectorize tensor::InsertSliceOp with:
62/// * vector::TransferReadOp + vector::TransferWriteOp
63/// The vector sizes are either:
64/// * user-provided in `inputVectorSizes`, or
65/// * inferred from the static dims in the input and output tensors.
66/// Bails out if:
67/// * vector sizes are not user-provided, and
68/// * at least one dim is dynamic (in both the input and output tensors).
69///
70/// Before:
71/// !t_in_type = tensor<1x2x3xf32>
72/// !t_out_type = tensor<9x8x7x1x2x3xf32>
73/// !v_type = vector<1x2x3xf32>
74/// %inserted_slice = tensor.insert_slice %src into %dest ... : !t_in_type
75/// into !t_out_type
76/// After:
77/// %read = vector.transfer_read %src[...], %pad ... : !t_in_type, !v_type
78/// %write = vector.transfer_write %read, %dest ... : !v_type, !t_out_type
79static LogicalResult
80vectorizeAsInsertSliceOp(RewriterBase &rewriter, tensor::InsertSliceOp sliceOp,
81 ArrayRef<int64_t> inputVectorSizes,
82 SmallVectorImpl<Value> &newResults);
83
84/// Returns the effective Pad value for the input op, provided it's a scalar.
85///
86/// Many Ops exhibit pad-like behaviour, but this isn't always explicit. If
87/// this Op performs padding, retrieve the padding value provided that it's
88/// a scalar and static/fixed for all the padded values. Returns an empty value
89/// otherwise.
91
92/// Return the unique instance of OpType in `block` if it is indeed unique.
93/// Return null if none or more than 1 instances exist.
94template <typename OpType>
95static OpType getSingleOpOfType(Block &block) {
96 OpType res;
97 block.walk([&](OpType op) {
98 if (res) {
99 res = nullptr;
100 return WalkResult::interrupt();
101 }
102 res = op;
103 return WalkResult::advance();
104 });
105 return res;
106}
107
108/// Helper function to extract the input slices after filter is unrolled along
109/// kw.
112 int64_t nSize, int64_t wSize, int64_t cSize,
113 int64_t kwSize, int strideW, int dilationW,
114 int64_t wSizeStep, bool isSingleChanneled) {
116 if (isSingleChanneled) {
117 // Extract input slice of size {wSizeStep} @ [w + kw] for non-channeled
118 // convolution.
119 SmallVector<int64_t> sizes = {wSizeStep};
120 SmallVector<int64_t> strides = {1};
121 for (int64_t kw = 0; kw < kwSize; ++kw) {
122 for (int64_t w = 0; w < wSize; w += wSizeStep) {
123 result.push_back(vector::ExtractStridedSliceOp::create(
124 rewriter, loc, input, /*offsets=*/ArrayRef<int64_t>{w + kw}, sizes,
125 strides));
126 }
127 }
128 } else {
129 // Extract lhs slice of size {n, wSizeStep, c} @ [0, sw * w + dw * kw, 0]
130 // for channeled convolution.
131 SmallVector<int64_t> sizes = {nSize, wSizeStep, cSize};
132 SmallVector<int64_t> strides = {1, 1, 1};
133 for (int64_t kw = 0; kw < kwSize; ++kw) {
134 for (int64_t w = 0; w < wSize; w += wSizeStep) {
135 result.push_back(vector::ExtractStridedSliceOp::create(
136 rewriter, loc, input,
137 /*offsets=*/ArrayRef<int64_t>{0, w * strideW + kw * dilationW, 0},
138 sizes, strides));
139 }
140 }
141 }
142 return result;
143}
144
145/// Helper function to extract the filter slices after filter is unrolled along
146/// kw.
148 Location loc, Value filter,
149 int64_t kwSize) {
151 // Extract rhs slice of size [{c, f} for channeled convolutions and {1} for
152 // non-chanelled convolution] @ [kw].
153 for (int64_t kw = 0; kw < kwSize; ++kw) {
154 result.push_back(vector::ExtractOp::create(
155 rewriter, loc, filter, /*offsets=*/ArrayRef<int64_t>{kw}));
156 }
157 return result;
158}
159
160/// Helper function to extract the result slices after filter is unrolled along
161/// kw.
164 int64_t nSize, int64_t wSize, int64_t fSize,
165 int64_t wSizeStep, bool isSingleChanneled) {
167 if (isSingleChanneled) {
168 // Extract res slice: {wSizeStep} @ [w] for non-channeled convolution.
169 SmallVector<int64_t> sizes = {wSizeStep};
170 SmallVector<int64_t> strides = {1};
171 for (int64_t w = 0; w < wSize; w += wSizeStep) {
172 result.push_back(vector::ExtractStridedSliceOp::create(
173 rewriter, loc, res, /*offsets=*/ArrayRef<int64_t>{w}, sizes,
174 strides));
175 }
176 } else {
177 // Extract res slice: {n, wSizeStep, f} @ [0, w, 0] for channeled
178 // convolution.
179 SmallVector<int64_t> sizes = {nSize, wSizeStep, fSize};
180 SmallVector<int64_t> strides = {1, 1, 1};
181 for (int64_t w = 0; w < wSize; w += wSizeStep) {
182 result.push_back(vector::ExtractStridedSliceOp::create(
183 rewriter, loc, res, /*offsets=*/ArrayRef<int64_t>{0, w, 0}, sizes,
184 strides));
185 }
186 }
187 return result;
188}
189
190/// Helper function to insert the computed result slices.
192 Value res, int64_t wSize, int64_t wSizeStep,
193 SmallVectorImpl<Value> &resVals,
194 bool isSingleChanneled) {
195
196 if (isSingleChanneled) {
197 // Write back res slice: {wSizeStep} @ [w] for non-channeled convolution.
198 // This does not depend on kw.
199 SmallVector<int64_t> strides = {1};
200 for (int64_t w = 0; w < wSize; w += wSizeStep) {
201 res = vector::InsertStridedSliceOp::create(
202 rewriter, loc, resVals[w], res, /*offsets=*/ArrayRef<int64_t>{w},
203 strides);
204 }
205 } else {
206 // Write back res slice: {n, wSizeStep, f} @ [0, w, 0] for channeled
207 // convolution. This does not depend on kw.
208 SmallVector<int64_t> strides = {1, 1, 1};
209 for (int64_t w = 0; w < wSize; w += wSizeStep) {
210 res = vector::InsertStridedSliceOp::create(
211 rewriter, loc, resVals[w], res,
212 /*offsets=*/ArrayRef<int64_t>{0, w, 0}, strides);
213 }
214 }
215 return res;
216}
217
218/// Contains the vectorization state and related methods used across the
219/// vectorization process of a given operation.
221 VectorizationState(RewriterBase &rewriter) : rewriterGuard(rewriter) {}
222
223 /// Initializes the vectorization state, including the computation of the
224 /// canonical vector shape for vectorization.
225 LogicalResult initState(RewriterBase &rewriter, LinalgOp linalgOp,
226 ArrayRef<int64_t> inputVectorSizes,
227 ArrayRef<bool> inputScalableVecDims,
228 bool assumeDynamicDimsMatchVecSizes = false);
229
230 /// Returns the canonical vector shape used to vectorize the iteration space.
231 ArrayRef<int64_t> getCanonicalVecShape() const { return canonicalVecShape; }
232
233 /// Returns the vector dimensions that are scalable in the canonical vector
234 /// shape.
235 ArrayRef<bool> getScalableVecDims() const { return scalableVecDims; }
236
237 /// Returns a vector type of the provided `elementType` with the canonical
238 /// vector shape and the corresponding fixed/scalable dimensions bit. If
239 /// `dimPermutation` is provided, the canonical vector dimensions are permuted
240 /// accordingly.
242 Type elementType,
243 std::optional<AffineMap> dimPermutation = std::nullopt) const {
245 SmallVector<bool> scalableDims;
246 if (dimPermutation.has_value()) {
248 applyPermutationMap<int64_t>(*dimPermutation, canonicalVecShape);
249 scalableDims =
250 applyPermutationMap<bool>(*dimPermutation, scalableVecDims);
251 } else {
252 vectorShape.append(canonicalVecShape.begin(), canonicalVecShape.end());
253 scalableDims.append(scalableVecDims.begin(), scalableVecDims.end());
254 }
255
256 return VectorType::get(vectorShape, elementType, scalableDims);
257 }
258
259 /// Masks an operation with the canonical vector mask if the operation needs
260 /// masking. Returns the masked operation or the original operation if masking
261 /// is not needed. If provided, the canonical mask for this operation is
262 /// permuted using `maybeIndexingMap`.
263 Operation *
264 maskOperation(RewriterBase &rewriter, Operation *opToMask, LinalgOp linalgOp,
265 std::optional<AffineMap> maybeIndexingMap = std::nullopt);
266
267private:
268 /// Initializes the iteration space static sizes using the Linalg op
269 /// information. This may become more complicated in the future.
270 void initIterSpaceStaticSizes(LinalgOp linalgOp) {
271 iterSpaceStaticSizes.append(linalgOp.getStaticLoopRanges());
272 }
273
274 /// Generates 'arith.constant' and 'tensor/memref.dim' operations for
275 /// all the static and dynamic dimensions of the iteration space to be
276 /// vectorized and store them in `iterSpaceValueSizes`.
277 LogicalResult precomputeIterSpaceValueSizes(RewriterBase &rewriter,
278 LinalgOp linalgOp);
279
280 /// Create or retrieve an existing mask value to mask `opToMask` in the
281 /// canonical vector iteration space. If `maybeMaskingMap` the mask is
282 /// permuted using that permutation map. If a new mask is created, it will be
283 /// cached for future users.
284 Value getOrCreateMaskFor(RewriterBase &rewriter, Operation *opToMask,
285 LinalgOp linalgOp,
286 std::optional<AffineMap> maybeMaskingMap);
287
288 /// Check whether this permutation map can be used for masking. At the
289 /// moment we only make sure that there are no broadcast dimensions, but this
290 /// might change if indexing maps evolve.
291 bool isValidMaskingMap(AffineMap maskingMap) {
292 return maskingMap.getBroadcastDims().empty();
293 }
294
295 /// Turn the input indexing map into a valid masking map.
296 ///
297 /// The input indexing map may contain "zero" results, e.g.:
298 /// (d0, d1, d2, d3) -> (d2, d1, d0, 0)
299 /// Applying such maps to canonical vector shapes like this one:
300 /// (1, 16, 16, 4)
301 /// would yield an invalid vector shape like this:
302 /// (16, 16, 1, 0)
303 /// Instead, drop the broadcasting dims that make no sense for masking perm.
304 /// maps:
305 /// (d0, d1, d2, d3) -> (d2, d1, d0)
306 /// This way, the corresponding vector/mask type will be:
307 /// vector<16x16x1xty>
308 /// rather than this invalid Vector type:
309 /// vector<16x16x1x0xty>
310 AffineMap getMaskingMapFromIndexingMap(AffineMap &indexingMap) {
311 return indexingMap.dropZeroResults();
312 }
313
314 // Holds the compile-time static sizes of the iteration space to vectorize.
315 // Dynamic dimensions are represented using ShapedType::kDynamic.
316 SmallVector<int64_t> iterSpaceStaticSizes;
317
318 /// Holds the value sizes of the iteration space to vectorize. Static
319 /// dimensions are represented by 'arith.constant' and dynamic
320 /// dimensions by 'tensor/memref.dim'.
321 SmallVector<Value> iterSpaceValueSizes;
322
323 /// Holds the canonical vector shape used to vectorize the iteration space.
324 SmallVector<int64_t> canonicalVecShape;
325
326 /// Holds the vector dimensions that are scalable in the canonical vector
327 /// shape.
328 SmallVector<bool> scalableVecDims;
329
330 /// Holds the active masks for permutations of the canonical vector iteration
331 /// space.
332 DenseMap<AffineMap, Value> activeMaskCache;
333
334 /// Global vectorization guard for the incoming rewriter. It's initialized
335 /// when the vectorization state is initialized.
336 OpBuilder::InsertionGuard rewriterGuard;
337
338 /// Do all dynamic dims match the corresponding vector sizes?
339 ///
340 /// When a dynamic tensor/memref dimension matches the corresponding vector
341 /// dimension, masking can be safely skipped, despite the presence of dynamic
342 /// shapes. Use this flag with care and only for cases where you are
343 /// confident the assumption holds.
344 bool assumeDynamicDimsMatchVecSizes = false;
345};
346
347LogicalResult
348VectorizationState::precomputeIterSpaceValueSizes(RewriterBase &rewriter,
349 LinalgOp linalgOp) {
350 // TODO: Support 0-d vectors.
351 for (int vecDim = 0, end = canonicalVecShape.size(); vecDim < end; ++vecDim) {
352 if (ShapedType::isStatic(iterSpaceStaticSizes[vecDim])) {
353 // Create constant index op for static dimensions.
354 iterSpaceValueSizes.push_back(arith::ConstantIndexOp::create(
355 rewriter, linalgOp.getLoc(), iterSpaceStaticSizes[vecDim]));
356 continue;
357 }
358
359 // Find an operand defined on this dimension of the iteration space to
360 // extract the runtime dimension size.
361 Value operand;
362 unsigned operandDimPos;
363 if (failed(linalgOp.mapIterationSpaceDimToOperandDim(vecDim, operand,
364 operandDimPos)))
365 return failure();
366
367 Value dynamicDim =
368 linalgOp.hasPureTensorSemantics()
369 ? (Value)tensor::DimOp::create(rewriter, linalgOp.getLoc(), operand,
370 operandDimPos)
371 : (Value)memref::DimOp::create(rewriter, linalgOp.getLoc(), operand,
372 operandDimPos);
373 iterSpaceValueSizes.push_back(dynamicDim);
374 }
375
376 return success();
377}
378
379/// Initializes the vectorization state, including the computation of the
380/// canonical vector shape for vectorization.
381// TODO: Move this to the constructor when we can remove the failure cases.
383 LinalgOp linalgOp,
384 ArrayRef<int64_t> inputVectorSizes,
385 ArrayRef<bool> inputScalableVecDims,
386 bool assumeDimsMatchVec) {
387 assumeDynamicDimsMatchVecSizes = assumeDimsMatchVec;
388 // Initialize the insertion point.
389 rewriter.setInsertionPoint(linalgOp);
390
391 if (!inputVectorSizes.empty()) {
392 // Get the canonical vector shape from the input vector sizes provided. This
393 // path should be taken to vectorize code with dynamic shapes and when using
394 // vector sizes greater than the iteration space sizes.
395 canonicalVecShape.append(inputVectorSizes.begin(), inputVectorSizes.end());
396 scalableVecDims.append(inputScalableVecDims.begin(),
397 inputScalableVecDims.end());
398 } else {
399 // Compute the canonical vector shape from the operation shape. If there are
400 // dynamic shapes, the operation won't be vectorized. We assume all the
401 // vector dimensions are fixed.
402 canonicalVecShape = linalgOp.getStaticLoopRanges();
403 scalableVecDims.append(linalgOp.getNumLoops(), false);
404 }
405
406 LDBG() << "Canonical vector shape: " << llvm::interleaved(canonicalVecShape);
407 LDBG() << "Scalable vector dims: " << llvm::interleaved(scalableVecDims);
408
409 if (ShapedType::isDynamicShape(canonicalVecShape))
410 return failure();
411
412 // Initialize iteration space static sizes.
413 initIterSpaceStaticSizes(linalgOp);
414
415 // Generate 'arith.constant' and 'tensor/memref.dim' operations for
416 // all the static and dynamic dimensions of the iteration space, needed to
417 // compute a mask during vectorization.
418 if (failed(precomputeIterSpaceValueSizes(rewriter, linalgOp)))
419 return failure();
420
421 return success();
422}
423
424/// Create or retrieve an existing mask value to mask `opToMask` in the
425/// canonical vector iteration space. If `maybeMaskingMap` the mask is permuted
426/// using that permutation map. If a new mask is created, it will be cached for
427/// future users.
428Value VectorizationState::getOrCreateMaskFor(
429 RewriterBase &rewriter, Operation *opToMask, LinalgOp linalgOp,
430 std::optional<AffineMap> maybeMaskingMap) {
431
432 assert((!maybeMaskingMap || isValidMaskingMap(*maybeMaskingMap)) &&
433 "Ill-formed masking map.");
434
435 // No mask is needed if the operation is not maskable.
436 auto maskableOp = dyn_cast<vector::MaskableOpInterface>(opToMask);
437 if (!maskableOp)
438 return Value();
439
440 assert(!maskableOp.isMasked() &&
441 "Masking an operation that is already masked");
442
443 // If no masking map was provided, use an identity map with the loop dims.
444 assert((!maybeMaskingMap || *maybeMaskingMap) &&
445 "Unexpected null mask permutation map");
446 AffineMap maskingMap =
447 maybeMaskingMap ? *maybeMaskingMap
449 linalgOp.getNumLoops(), rewriter.getContext());
450
451 LDBG() << "Masking map: " << maskingMap;
452
453 // Return the active mask for the masking map of this operation if it was
454 // already created.
455 auto activeMaskIt = activeMaskCache.find(maskingMap);
456 if (activeMaskIt != activeMaskCache.end()) {
457 Value mask = activeMaskIt->second;
458 LDBG() << "Reusing mask: " << mask;
459 return mask;
460 }
461
462 // Compute permuted projection of the iteration space to be masked and the
463 // corresponding mask shape. If the resulting iteration space dimensions are
464 // static and identical to the mask shape, masking is not needed for this
465 // operation.
466 // TODO: Improve this check. Only projected permutation indexing maps are
467 // supported.
468 SmallVector<int64_t> permutedStaticSizes =
469 applyPermutationMap<int64_t>(maskingMap, iterSpaceStaticSizes);
470 auto maskType = getCanonicalVecType(rewriter.getI1Type(), maskingMap);
471 auto maskShape = maskType.getShape();
472
473 LDBG() << "Mask shape: " << llvm::interleaved(maskShape);
474
475 if (permutedStaticSizes == maskShape) {
476 LDBG() << "Masking is not needed for masking map: " << maskingMap;
477 activeMaskCache[maskingMap] = Value();
478 return Value();
479 }
480
481 if (assumeDynamicDimsMatchVecSizes) {
482 // While for _dynamic_ dim sizes we can _assume_ that the corresponding
483 // vector sizes match, we still need to check the _static_ dim sizes. Only
484 // then we can be 100% sure that masking is not required.
485 if (llvm::all_of(llvm::zip(permutedStaticSizes, maskType.getShape()),
486 [](auto it) {
487 return std::get<0>(it) == ShapedType::kDynamic
488 ? true
489 : std::get<0>(it) == std::get<1>(it);
490 })) {
491 LDBG()
492 << "Dynamic + static dimensions match vector sizes, masking is not "
493 "required.";
494 activeMaskCache[maskingMap] = Value();
495 return Value();
496 }
497 }
498
499 // Permute the iteration space value sizes to compute the mask upper bounds.
500 SmallVector<Value> upperBounds =
501 applyPermutationMap(maskingMap, ArrayRef<Value>(iterSpaceValueSizes));
502 assert(!maskShape.empty() && !upperBounds.empty() &&
503 "Masked 0-d vectors are not supported yet");
504
505 // Create the mask based on the dimension values.
506 Value mask = vector::CreateMaskOp::create(rewriter, linalgOp.getLoc(),
507 maskType, upperBounds);
508 LDBG() << "Creating new mask: " << mask;
509 activeMaskCache[maskingMap] = mask;
510 return mask;
511}
512
513Operation *
515 LinalgOp linalgOp,
516 std::optional<AffineMap> maybeIndexingMap) {
517 LDBG() << "Trying to mask: " << *opToMask;
518
519 std::optional<AffineMap> maybeMaskingMap = std::nullopt;
520 if (maybeIndexingMap)
521 maybeMaskingMap = getMaskingMapFromIndexingMap(*maybeIndexingMap);
522
523 // Create or retrieve mask for this operation.
524 Value mask =
525 getOrCreateMaskFor(rewriter, opToMask, linalgOp, maybeMaskingMap);
526
527 if (!mask) {
528 LDBG() << "No mask required";
529 if (assumeDynamicDimsMatchVecSizes) {
531 .Case<vector::TransferReadOp, vector::TransferWriteOp>(
532 [&](auto xferOp) {
533 // For vector.transfer_read and vector.transfer_write, there is
534 // also the `in-bounds` attribute that has to be set explicitly
535 // to true. Otherwise, "out-of-bounds" access will be assumed
536 // and masks will be generated while lowering these.
537 LDBG() << "Assuming dynamic dimensions match vector sizes and "
538 "setting their in-bounds to true!";
539 SmallVector<bool> inBoundsMap = xferOp.getInBoundsValues();
540 ShapedType xferType = xferOp.getShapedType();
541 AffineMap permMap = xferOp.getPermutationMap();
542 // Only set the in-bounds values to true for dynamic dims.
543 // Different mechanisms will set these accordingly for the
544 // static dims.
545 for (unsigned i = 0; i < xferOp.getTransferRank(); i++) {
546 auto dimExpr = dyn_cast<AffineDimExpr>(permMap.getResult(i));
547 // Skip broadcast dimensions.
548 if (!dimExpr)
549 continue;
550 unsigned pos = dimExpr.getPosition();
551 if (xferType.isDynamicDim(pos))
552 inBoundsMap[i] = true;
553 }
554 rewriter.modifyOpInPlace(xferOp, [&]() {
555 xferOp.setInBoundsAttr(
556 rewriter.getBoolArrayAttr(inBoundsMap));
557 });
558 })
559 .Default([](Operation *op) {
560 // No-op if the operation is not an xfer read or write.
561 });
562 }
563 return opToMask;
564 }
565
566 // Wrap the operation with a new `vector.mask` and update D-U chain.
567 assert(opToMask && "Expected a valid operation to mask");
568 auto maskOp = cast<vector::MaskOp>(
569 mlir::vector::maskOperation(rewriter, opToMask, mask));
570 Operation *maskOpTerminator = &maskOp.getMaskRegion().front().back();
571
572 for (auto [resIdx, resVal] : llvm::enumerate(opToMask->getResults()))
573 rewriter.replaceAllUsesExcept(resVal, maskOp.getResult(resIdx),
574 maskOpTerminator);
575
576 LDBG() << "Masked operation: " << *maskOp;
577 return maskOp;
578}
579
580/// Given an indexing `map` coming from a LinalgOp indexing, restricted to a
581/// projectedPermutation, compress the unused dimensions to serve as a
582/// permutation_map for a vector transfer operation.
583/// For example, given a linalg op such as:
584///
585/// ```
586/// %0 = linalg.generic {
587/// indexing_maps = affine_map<(d0, d1, d2, d3, d4) -> (d4, d0, d2)>,
588/// indexing_maps = affine_map<(d0, d1, d2, d3, d4) -> (d1, d3)>
589/// }
590/// ins(%0 : tensor<2x3x4xf32>)
591/// outs(%1 : tensor<5x6xf32>)
592/// ```
593///
594/// the iteration domain size of the linalg op is 3x5x4x6x2. The first affine
595/// map is reindexed to `affine_map<(d0, d1, d2) -> (d2, d0, d1)>`, the second
596/// affine map is reindexed to `affine_map<(d0, d1) -> (d0, d1)>`.
598 assert(map.isProjectedPermutation(/*allowZeroInResults=*/true) &&
599 "expected projected permutation");
600 auto res = compressUnusedDims(map);
601 assert(res.getNumDims() ==
602 (res.getNumResults() - res.getNumOfZeroResults()) &&
603 "expected reindexed map with same number of dims and results");
604 return res;
605}
606
607/// Helper enum to represent conv1d input traversal order.
608enum class Conv1DOpOrder {
609 W, // Corresponds to non-channeled 1D convolution operation.
610 Ncw, // Corresponds to operation that traverses the input in (n, c, w) order.
611 Nwc // Corresponds to operation that traverses the input in (n, w, c) order.
612};
613
614/// Helper data structure to represent the result of vectorization for a single
615/// operation. In certain specific cases, like terminators, we do not want to
616/// propagate.
618 /// Op failed to vectorize.
620 /// Op vectorized and custom function took care of replacement logic
622 /// Op vectorized into a new Op whose results will replace original Op's
623 /// results.
625 // TODO: support values if Op vectorized to Many-Ops whose results we need to
626 // aggregate for replacement.
627};
628/// VectorizationHookResult contains the vectorized op returned from a
629/// CustomVectorizationHook. This is an internal implementation detail of
630/// linalg vectorization, not to be confused with VectorizationResult.
632 /// Return status from vectorizing the current op.
634 /// New vectorized operation to replace the current op.
635 /// Replacement behavior is specified by `status`.
637};
638
639std::optional<vector::CombiningKind>
641 using ::mlir::vector::CombiningKind;
642
643 if (!combinerOp)
644 return std::nullopt;
646 .Case<arith::AddIOp, arith::AddFOp>(
647 [&](auto op) { return CombiningKind::ADD; })
648 .Case([&](arith::AndIOp op) { return CombiningKind::AND; })
649 .Case([&](arith::MaxSIOp op) { return CombiningKind::MAXSI; })
650 .Case([&](arith::MaxUIOp op) { return CombiningKind::MAXUI; })
651 .Case([&](arith::MaximumFOp op) { return CombiningKind::MAXIMUMF; })
652 .Case([&](arith::MaxNumFOp op) { return CombiningKind::MAXNUMF; })
653 .Case([&](arith::MinSIOp op) { return CombiningKind::MINSI; })
654 .Case([&](arith::MinUIOp op) { return CombiningKind::MINUI; })
655 .Case([&](arith::MinimumFOp op) { return CombiningKind::MINIMUMF; })
656 .Case([&](arith::MinNumFOp op) { return CombiningKind::MINNUMF; })
657 .Case<arith::MulIOp, arith::MulFOp>(
658 [&](auto op) { return CombiningKind::MUL; })
659 .Case([&](arith::OrIOp op) { return CombiningKind::OR; })
660 .Case([&](arith::XOrIOp op) { return CombiningKind::XOR; })
661 .Default(std::nullopt);
662}
663
664/// Check whether `outputOperand` is a reduction with a single combiner
665/// operation. Return the combiner operation of the reduction. Return
666/// nullptr otherwise. Multiple reduction operations would impose an
667/// ordering between reduction dimensions and is currently unsupported in
668/// Linalg. This limitation is motivated by the fact that e.g. min(max(X)) !=
669/// max(min(X))
670// TODO: use in LinalgOp verification, there is a circular dependency atm.
671static Operation *matchLinalgReduction(OpOperand *outputOperand) {
672 auto linalgOp = cast<LinalgOp>(outputOperand->getOwner());
673 unsigned outputPos =
674 outputOperand->getOperandNumber() - linalgOp.getNumDpsInputs();
675 // Only single combiner operations are supported for now.
676 SmallVector<Operation *, 4> combinerOps;
677 if (!matchReduction(linalgOp.getRegionOutputArgs(), outputPos, combinerOps) ||
678 combinerOps.size() != 1)
679 return nullptr;
680
681 // Return the combiner operation.
682 return combinerOps[0];
683}
684
685/// Broadcast `value` to a vector of `shape` if possible. Return value
686/// otherwise.
687static Value broadcastIfNeeded(OpBuilder &b, Value value, Type dstType) {
688 auto dstVecType = dyn_cast<VectorType>(dstType);
689 // If no shape to broadcast to, just return `value`.
690 if (dstVecType.getRank() == 0)
691 return value;
692 if (vector::isBroadcastableTo(value.getType(), dstVecType) !=
694 return value;
695 Location loc = b.getInsertionPoint()->getLoc();
696 return b.createOrFold<vector::BroadcastOp>(loc, dstVecType, value);
697}
698
699/// Create MultiDimReductionOp to compute the reduction for `reductionOp`. This
700/// assumes that `reductionOp` has two operands and one of them is the reduction
701/// initial value.buildMultiDimReduce
702// Note: this is a true builder that notifies the OpBuilder listener.
703// TODO: Consider moving as a static helper on the ReduceOp.
705 Value valueToReduce, Value acc,
706 ArrayRef<bool> dimsToMask) {
707 auto maybeKind = getCombinerOpKind(reduceOp);
708 assert(maybeKind && "Failed precondition: could not get reduction kind");
709 return vector::MultiDimReductionOp::create(
710 b, reduceOp->getLoc(), valueToReduce, acc, dimsToMask, *maybeKind);
711}
712
713static SmallVector<bool> getDimsToReduce(LinalgOp linalgOp) {
714 return llvm::map_to_vector(linalgOp.getIteratorTypesArray(),
716}
717
718/// Check if `op` is a linalg.reduce or a linalg.generic that has at least one
719/// reduction iterator.
720static bool hasReductionIterator(LinalgOp &op) {
721 return isa<linalg::ReduceOp>(op) ||
722 (isa<linalg::GenericOp>(op) &&
723 llvm::any_of(op.getIteratorTypesArray(), isReductionIterator));
724}
725
726/// Build a vector.transfer_write of `value` into `outputOperand` at indices set
727/// to all `0`; where `outputOperand` is an output operand of the LinalgOp
728/// currently being vectorized. If `dest` has null rank, build an memref.store.
729/// Return the produced value or null if no value is produced.
730// Note: this is a true builder that notifies the OpBuilder listener.
731// TODO: Consider moving as a static helper on the ReduceOp.
732static Value buildVectorWrite(RewriterBase &rewriter, Value value,
733 OpOperand *outputOperand,
734 VectorizationState &state) {
735 Location loc = value.getLoc();
736 auto linalgOp = cast<LinalgOp>(outputOperand->getOwner());
737 AffineMap opOperandMap = linalgOp.getMatchingIndexingMap(outputOperand);
738
739 // Compute the vector type of the value to store. This type should be an
740 // identity or projection of the canonical vector type without any permutation
741 // applied, given that any permutation in a transfer write happens as part of
742 // the write itself.
744 opOperandMap.getContext(), opOperandMap.getNumInputs(),
745 [&](AffineDimExpr dimExpr) -> bool {
746 return llvm::is_contained(opOperandMap.getResults(), dimExpr);
747 });
748 auto vectorType = state.getCanonicalVecType(
749 getElementTypeOrSelf(outputOperand->get().getType()), vectorTypeMap);
750
751 SmallVector<Value> indices(linalgOp.getRank(outputOperand),
752 arith::ConstantIndexOp::create(rewriter, loc, 0));
753
754 Operation *write;
755 if (vectorType.getRank() > 0) {
756 AffineMap writeMap = inversePermutation(reindexIndexingMap(opOperandMap));
757 value = broadcastIfNeeded(rewriter, value, vectorType);
758 assert(value.getType() == vectorType && "Incorrect type");
759 write = vector::TransferWriteOp::create(
760 rewriter, loc, value, outputOperand->get(), indices, writeMap);
761 } else {
762 // 0-d case is still special: do not invert the reindexing writeMap.
763 if (!isa<VectorType>(value.getType()))
764 value = vector::BroadcastOp::create(rewriter, loc, vectorType, value);
765 assert(value.getType() == vectorType && "Incorrect type");
766 write = vector::TransferWriteOp::create(rewriter, loc, value,
767 outputOperand->get(), indices);
768 }
769
770 write = state.maskOperation(rewriter, write, linalgOp, opOperandMap);
771
772 // If masked, set in-bounds to true. Masking guarantees that the access will
773 // be in-bounds.
774 if (auto maskOp = dyn_cast<vector::MaskingOpInterface>(write)) {
775 auto maskedWriteOp = cast<vector::TransferWriteOp>(maskOp.getMaskableOp());
776 SmallVector<bool> inBounds(maskedWriteOp.getVectorType().getRank(), true);
777 maskedWriteOp.setInBoundsAttr(rewriter.getBoolArrayAttr(inBounds));
778 }
779
780 LDBG() << "vectorized op: " << *write;
781 if (!write->getResults().empty())
782 return write->getResult(0);
783 return Value();
784}
785
786// Custom vectorization precondition function type. This is intented to be used
787// with CustomVectorizationHook. Returns success if the corresponding custom
788// hook can vectorize the op.
790 std::function<LogicalResult(Operation *, bool)>;
791
792// Custom vectorization function type. Produce a vector form of Operation*
793// assuming all its vectorized operands are already in the IRMapping.
794// Return nullptr if the Operation cannot be vectorized.
796 std::function<VectorizationHookResult(Operation *, const IRMapping &)>;
797
798/// Helper function to vectorize the terminator of a `linalgOp`. New result
799/// vector values are appended to `newResults`. Return
800/// VectorizationHookStatus::NoReplace to signal the vectorization algorithm
801/// that it should not try to map produced operations and instead return the
802/// results using the `newResults` vector making them available to the
803/// vectorization algorithm for RAUW. This function is meant to be used as a
804/// CustomVectorizationHook.
807 const IRMapping &bvm, VectorizationState &state,
808 LinalgOp linalgOp, SmallVectorImpl<Value> &newResults) {
809 auto yieldOp = dyn_cast<linalg::YieldOp>(op);
810 if (!yieldOp)
812 for (const auto &output : llvm::enumerate(yieldOp.getValues())) {
813 // TODO: Scan for an opportunity for reuse.
814 // TODO: use a map.
815 Value vectorValue = bvm.lookup(output.value());
816 Value newResult =
817 buildVectorWrite(rewriter, vectorValue,
818 linalgOp.getDpsInitOperand(output.index()), state);
819 if (newResult)
820 newResults.push_back(newResult);
821 }
822
824}
825
826/// Helper function to vectorize the index operations of a `linalgOp`. Return
827/// VectorizationHookStatus::NewOp to signal the vectorization algorithm that it
828/// should map the produced operations. This function is meant to be used as a
829/// CustomVectorizationHook.
831 VectorizationState &state,
832 Operation *op,
833 LinalgOp linalgOp) {
834 IndexOp indexOp = dyn_cast<linalg::IndexOp>(op);
835 if (!indexOp)
837 auto loc = indexOp.getLoc();
838 // Compute the static loop sizes of the index op.
839 ArrayRef<int64_t> targetShape = state.getCanonicalVecShape();
840 auto dim = indexOp.getDim();
841 // Compute a one-dimensional index vector for the index op dimension.
842 auto indexVectorType =
843 VectorType::get({targetShape[dim]}, rewriter.getIndexType(),
844 state.getScalableVecDims()[dim]);
845 auto indexSteps = vector::StepOp::create(rewriter, loc, indexVectorType);
846 // Return the one-dimensional index vector if it lives in the trailing
847 // dimension of the iteration space since the vectorization algorithm in this
848 // case can handle the broadcast.
849 if (dim == targetShape.size() - 1)
851 // Otherwise permute the targetShape to move the index dimension last,
852 // broadcast the one-dimensional index vector to the permuted shape, and
853 // finally transpose the broadcasted index vector to undo the permutation.
854 auto permPattern =
855 llvm::to_vector(llvm::seq<unsigned>(0, targetShape.size()));
856 std::swap(permPattern[dim], permPattern.back());
857 auto permMap =
858 AffineMap::getPermutationMap(permPattern, linalgOp.getContext());
859
860 auto broadCastOp = vector::BroadcastOp::create(
861 rewriter, loc,
862 state.getCanonicalVecType(rewriter.getIndexType(), permMap), indexSteps);
863 SmallVector<int64_t> transposition =
864 llvm::to_vector<16>(llvm::seq<int64_t>(0, linalgOp.getNumLoops()));
865 std::swap(transposition.back(), transposition[dim]);
866 auto transposeOp =
867 vector::TransposeOp::create(rewriter, loc, broadCastOp, transposition);
869}
870
871/// Helper function to check if the tensor.extract can be vectorized by the
872/// custom hook vectorizeTensorExtract.
873static LogicalResult
875 tensor::ExtractOp extractOp = dyn_cast<tensor::ExtractOp>(op);
876 if (!extractOp)
877 return failure();
878
879 if (extractOp.getIndices().size() != 1 && !vectorizeNDExtract)
880 return failure();
881
882 // Check the index type, but only for non 0-d tensors (for which we do need
883 // access indices).
884 if (not extractOp.getIndices().empty()) {
885 if (!VectorType::isValidElementType(extractOp.getIndices()[0].getType()))
886 return failure();
887 }
888
889 if (!llvm::all_of(extractOp->getResultTypes(),
890 VectorType::isValidElementType)) {
891 return failure();
892 }
893
894 return success();
895}
896
897/// Calculates the offsets (`$index_vec`) for `vector.gather` operations
898/// generated from `tensor.extract`. The offset is calculated as follows
899/// (example using scalar values):
900///
901/// offset = extractOp.indices[0]
902/// for (i = 1; i < numIndices; i++)
903/// offset = extractOp.dimSize[i] * offset + extractOp.indices[i];
904///
905/// For tensor<45 x 80 x 15 x f32> and index [1, 2, 3], this leads to:
906/// offset = ( ( 1 ) * 80 + 2 ) * 15 + 3
908 VectorizationState &state,
909 tensor::ExtractOp extractOp,
910 const IRMapping &bvm) {
911 // The vector of indices for GatherOp should be shaped as the output vector.
912 auto indexVecType = state.getCanonicalVecType(rewriter.getIndexType());
913 auto loc = extractOp.getLoc();
914
915 Value offset = broadcastIfNeeded(
916 rewriter, bvm.lookup(extractOp.getIndices()[0]), indexVecType);
917
918 const size_t numIndices = extractOp.getIndices().size();
919 for (size_t i = 1; i < numIndices; i++) {
920 Value dimIdx = arith::ConstantIndexOp::create(rewriter, loc, i);
921
922 auto dimSize = broadcastIfNeeded(
923 rewriter,
924 tensor::DimOp::create(rewriter, loc, extractOp.getTensor(), dimIdx),
925 indexVecType);
926
927 offset = arith::MulIOp::create(rewriter, loc, offset, dimSize);
928
929 auto extractOpIndex = broadcastIfNeeded(
930 rewriter, bvm.lookup(extractOp.getIndices()[i]), indexVecType);
931
932 offset = arith::AddIOp::create(rewriter, loc, extractOpIndex, offset);
933 }
934
935 return offset;
936}
937
939
940/// Find the index of the trailing non-unit dim in linalgOp. This hook is used
941/// when checking whether `tensor.extract` Op (within a `linalg.generic` Op)
942/// represents a contiguous load operation.
943///
944/// Note that when calling this hook, it is assumed that the output vector is
945/// effectively 1D. Other cases (i.e. reading n-D vectors) should've been
946/// labelled as a gather load before entering this method.
947///
948/// Following on from the above, it is assumed that:
949/// * for statically shaped loops, when no masks are used, only one dim is !=
950/// 1 (that's what the shape of the output vector is based on).
951/// * for dynamically shaped loops, there might be more non-unit dims
952/// as the output vector type is user-specified.
953///
954/// TODO: Statically shaped loops + vector masking
955static uint64_t getTrailingNonUnitLoopDimIdx(LinalgOp linalgOp) {
956 SmallVector<int64_t> loopRanges = linalgOp.getStaticLoopRanges();
957 assert(
958 (linalgOp.hasDynamicShape() ||
959 llvm::count_if(loopRanges, [](int64_t dim) { return dim != 1; }) == 1) &&
960 "For statically shaped Linalg Ops, only one "
961 "non-unit loop dim is expected");
962 assert(!loopRanges.empty() && "Empty loops, nothing to analyse.");
963
964 size_t idx = loopRanges.size() - 1;
965 for (; idx != 0; idx--)
966 if (loopRanges[idx] != 1)
967 break;
968
969 return idx;
970}
971
972/// Checks whether `val` can be used for calculating a loop invariant index.
973static bool isLoopInvariantIdx(LinalgOp &linalgOp, Value &val,
974 VectorType resType) {
975
976 assert(((llvm::count_if(resType.getShape(),
977 [](int64_t dimSize) { return dimSize > 1; }) == 1)) &&
978 "n-D vectors are not yet supported");
979
980 // Blocks outside _this_ linalg.generic are effectively loop invariant.
981 // However, analysing block arguments for _this_ linalg.generic Op is a bit
982 // tricky. Just bail out in the latter case.
983 // TODO: We could try analysing the corresponding affine map here.
984 auto *block = linalgOp.getBlock();
985 if (isa<BlockArgument>(val))
986 return !llvm::is_contained(block->getArguments(), val);
987
988 Operation *defOp = val.getDefiningOp();
989 assert(defOp && "This is neither a block argument nor an operation result");
990
991 // IndexOp is loop invariant as long as its result remains constant across
992 // iterations. Note that for dynamic shapes, the corresponding dim will also
993 // be conservatively treated as != 1.
994 if (auto indexOp = dyn_cast<linalg::IndexOp>(defOp)) {
995 return linalgOp.getStaticLoopRanges()[indexOp.getDim()] == 1;
996 }
997
998 auto *ancestor = block->findAncestorOpInBlock(*defOp);
999
1000 // Values define outside `linalgOp` are loop invariant.
1001 if (!ancestor)
1002 return true;
1003
1004 // Values defined inside `linalgOp`, which are constant, are loop invariant.
1005 if (isa<arith::ConstantOp>(ancestor))
1006 return true;
1007
1008 bool result = true;
1009 for (auto op : ancestor->getOperands())
1010 result &= isLoopInvariantIdx(linalgOp, op, resType);
1011
1012 return result;
1013}
1014
1015/// Check whether `val` could be used for calculating the trailing index for a
1016/// contiguous load operation.
1017///
1018/// There are currently 3 types of values that are allowed here:
1019/// 1. loop-invariant values,
1020/// 2. values that increment by 1 with every loop iteration,
1021/// 3. results of basic arithmetic operations (linear and continuous)
1022/// involving 1., 2. and 3.
1023/// This method returns True if indeed only such values are used in calculating
1024/// `val.`
1025///
1026/// Additionally, the trailing index for a contiguous load operation should
1027/// increment by 1 with every loop iteration, i.e. be based on:
1028/// * `linalg.index <dim>` ,
1029/// where <dim> is the trailing non-unit dim of the iteration space (this way,
1030/// `linalg.index <dim>` increments by 1 with every loop iteration).
1031/// `foundIndexOp` is updated to `true` when such Op is found.
1032static bool isContiguousLoadIdx(LinalgOp &linalgOp, Value &val,
1033 bool &foundIndexOp, VectorType resType) {
1034
1035 assert(((llvm::count_if(resType.getShape(),
1036 [](int64_t dimSize) { return dimSize > 1; }) == 1)) &&
1037 "n-D vectors are not yet supported");
1038
1039 // Blocks outside _this_ linalg.generic are effectively loop invariant.
1040 // However, analysing block arguments for _this_ linalg.generic Op is a bit
1041 // tricky. Just bail out in the latter case.
1042 // TODO: We could try analysing the corresponding affine map here.
1043 auto *block = linalgOp.getBlock();
1044 if (isa<BlockArgument>(val))
1045 return !llvm::is_contained(block->getArguments(), val);
1046
1047 Operation *defOp = val.getDefiningOp();
1048 assert(defOp && "This is neither a block argument nor an operation result");
1049
1050 if (auto indexOp = dyn_cast<linalg::IndexOp>(defOp)) {
1051 auto loopDimThatIncrementsByOne = getTrailingNonUnitLoopDimIdx(linalgOp);
1052
1053 foundIndexOp = (indexOp.getDim() == loopDimThatIncrementsByOne);
1054 return true;
1055 }
1056
1057 auto *ancestor = block->findAncestorOpInBlock(*defOp);
1058
1059 if (!ancestor)
1060 return false;
1061
1062 // Conservatively reject Ops that could lead to indices with stride other
1063 // than 1.
1064 if (!isa<arith::AddIOp, arith::ConstantOp, linalg::IndexOp>(ancestor))
1065 return false;
1066
1067 bool result = false;
1068 for (auto op : ancestor->getOperands())
1069 result |= isContiguousLoadIdx(linalgOp, op, foundIndexOp, resType);
1070
1071 return result;
1072}
1073
1074/// Infer the memory access pattern for the input ExtractOp
1075///
1076/// Based on the ExtratOp result shape and the access indices, decides whether
1077/// this Op corresponds to a contiguous load (including a broadcast of a scalar)
1078/// or a gather load. When analysing the ExtractOp indices (to identify
1079/// contiguous laods), this method looks for "loop" invariant indices (e.g.
1080/// block arguments) and indices that change linearly (e.g. via `linalg.index`
1081/// Op).
1082///
1083/// Note that it is always safe to use gather load operations for contiguous
1084/// loads (albeit slow), but not vice-versa. When in doubt, bail out and assume
1085/// that `extractOp` is a gather load.
1087getTensorExtractMemoryAccessPattern(tensor::ExtractOp extractOp,
1088 LinalgOp &linalgOp, VectorType resType) {
1089
1090 auto inputShape = cast<ShapedType>(extractOp.getTensor().getType());
1091
1092 // 0. Is this a 0-D vector? If yes then this is a scalar broadcast.
1093 if (inputShape.getShape().empty())
1095
1096 // 0a. Is the result a 0-D vector? If yes, there are no iteration dimensions
1097 // so the tensor.extract is a single scalar load regardless of the index.
1098 if (resType.getRank() == 0)
1100
1101 // True for vectors that are effectively 1D, e.g. `vector<1x4x1xi32>`, false
1102 // otherwise.
1103 bool isOutput1DVector =
1104 (llvm::count_if(resType.getShape(),
1105 [](int64_t dimSize) { return dimSize > 1; }) == 1);
1106 // 1. Assume that it's a gather load when reading non-1D vector.
1107 if (!isOutput1DVector)
1109
1110 bool leadingIdxsLoopInvariant = true;
1111
1112 // 2. Analyze the leading indices of `extractOp`.
1113 // Look at the way each index is calculated and decide whether it is suitable
1114 // for a contiguous load, i.e. whether it's loop invariant. If not, it's a
1115 // gather load.
1116 auto indices = extractOp.getIndices();
1117 auto leadIndices = indices.drop_back(1);
1118
1119 for (auto [i, indexVal] : llvm::enumerate(leadIndices)) {
1120 if (inputShape.getShape()[i] == 1)
1121 continue;
1122
1123 leadingIdxsLoopInvariant &= isLoopInvariantIdx(linalgOp, indexVal, resType);
1124 }
1125
1126 if (!leadingIdxsLoopInvariant) {
1127 LDBG() << "Found gather load: " << extractOp;
1129 }
1130
1131 // 3. Analyze the trailing index for `extractOp`.
1132 // At this point we know that the leading indices are loop invariant. This
1133 // means that is potentially a scalar or a contiguous load. We can decide
1134 // based on the trailing idx.
1135 auto extractOpTrailingIdx = indices.back();
1136
1137 // 3a. Scalar broadcast load
1138 // If the trailing index is loop invariant then this is a scalar load.
1139 if (leadingIdxsLoopInvariant &&
1140 isLoopInvariantIdx(linalgOp, extractOpTrailingIdx, resType)) {
1141 LDBG() << "Found scalar broadcast load: " << extractOp;
1142
1144 }
1145
1146 // 3b. Contiguous loads
1147 // The trailing `extractOp` index should increment with every loop iteration.
1148 // This effectively means that it must be based on the trailing loop index.
1149 // This is what the following bool captures.
1150 bool foundIndexOp = false;
1151 bool isContiguousLoad = isContiguousLoadIdx(linalgOp, extractOpTrailingIdx,
1152 foundIndexOp, resType);
1153 // TODO: Support generating contiguous loads for column vectors - that will
1154 // require adding a permutation map to tranfer_read Ops.
1155 bool isRowVector = resType.getShape().back() != 1;
1156 isContiguousLoad &= (foundIndexOp && isRowVector);
1157
1158 if (isContiguousLoad) {
1159 LDBG() << "Found contigous load: " << extractOp;
1161 }
1162
1163 // 4. Fallback case - gather load.
1164 LDBG() << "Found gather load: " << extractOp;
1166}
1167
1168/// Helper function to vectorize the tensor.extract operations. Returns
1169/// VectorizationHookStatus::NewOp to signal the vectorization algorithm that it
1170/// should map the produced operations. This function is meant to be used as a
1171/// CustomVectorizationHook.
1172static VectorizationHookResult
1173vectorizeTensorExtract(RewriterBase &rewriter, VectorizationState &state,
1174 Operation *op, LinalgOp linalgOp, const IRMapping &bvm) {
1175 tensor::ExtractOp extractOp = dyn_cast<tensor::ExtractOp>(op);
1176 if (!extractOp)
1178 auto loc = extractOp.getLoc();
1179
1180 // Compute the static loop sizes of the extract op.
1181 auto resultType = state.getCanonicalVecType(extractOp.getResult().getType());
1182 auto maskConstantOp = arith::ConstantOp::create(
1183 rewriter, loc,
1184 DenseIntElementsAttr::get(state.getCanonicalVecType(rewriter.getI1Type()),
1185 /*value=*/true));
1186 auto passThruConstantOp = arith::ConstantOp::create(
1187 rewriter, loc, rewriter.getZeroAttr(resultType));
1188
1189 // Base indices are currently set to 0. We will need to re-visit if more
1190 // generic scenarios are to be supported.
1191 SmallVector<Value> baseIndices(
1192 extractOp.getIndices().size(),
1193 arith::ConstantIndexOp::create(rewriter, loc, 0));
1194
1195 VectorMemoryAccessKind memAccessKind =
1196 getTensorExtractMemoryAccessPattern(extractOp, linalgOp, resultType);
1197
1198 // 1. Handle gather access
1199 if (memAccessKind == VectorMemoryAccessKind::Gather) {
1200 Value offset = calculateGatherOffset(rewriter, state, extractOp, bvm);
1201
1202 // Generate the gather load
1203 Operation *gatherOp = vector::GatherOp::create(
1204 rewriter, loc, resultType, extractOp.getTensor(), baseIndices, offset,
1205 maskConstantOp, passThruConstantOp);
1206 gatherOp = state.maskOperation(rewriter, gatherOp, linalgOp);
1207
1208 LDBG() << "Vectorised as gather load: " << extractOp;
1210 }
1211
1212 // 2. Handle:
1213 // a. scalar loads + broadcast,
1214 // b. contiguous loads.
1215 // Both cases use vector.transfer_read.
1216
1217 // Collect indices for `vector.transfer_read`. At this point, the indices will
1218 // either be scalars or would have been broadcast to vectors matching the
1219 // result type. For indices that are vectors, there are two options:
1220 // * for non-trailing indices, all elements are identical (contiguous
1221 // loads are identified by looking for non-trailing indices that are
1222 // invariant with respect to the corresponding linalg.generic), or
1223 // * for trailing indices, the index vector will contain values with stride
1224 // one, but for `vector.transfer_read` only the first (i.e. 0th) index is
1225 // needed.
1226 // This means that
1227 // * for scalar indices - just re-use it,
1228 // * for vector indices (e.g. `vector<1x1x4xindex>`) - extract the bottom
1229 // (0th) element and use that.
1230 SmallVector<Value> transferReadIdxs;
1231 for (size_t i = 0; i < extractOp.getIndices().size(); i++) {
1232 Value idx = bvm.lookup(extractOp.getIndices()[i]);
1233 if (idx.getType().isIndex()) {
1234 transferReadIdxs.push_back(idx);
1235 continue;
1236 }
1237
1238 auto indexAs1dVector = vector::ShapeCastOp::create(
1239 rewriter, loc,
1240 VectorType::get(resultType.getShape().back(), rewriter.getIndexType(),
1241 resultType.getScalableDims().back()),
1242 idx);
1243 transferReadIdxs.push_back(
1244 vector::ExtractOp::create(rewriter, loc, indexAs1dVector, 0));
1245 }
1246
1247 // `tensor.extract_element` is always in-bounds, hence the following holds.
1248 auto dstRank = resultType.getRank();
1249 auto srcRank = extractOp.getTensor().getType().getRank();
1250 SmallVector<bool> inBounds(dstRank, true);
1251
1252 // 2a. Handle scalar broadcast access.
1253 if (memAccessKind == VectorMemoryAccessKind::ScalarBroadcast) {
1254 MLIRContext *ctx = rewriter.getContext();
1255 SmallVector<AffineExpr> exprs(dstRank, getAffineConstantExpr(0, ctx));
1256 auto permutationMap = AffineMap::get(srcRank, 0, exprs, ctx);
1257
1258 auto transferReadOp = vector::TransferReadOp::create(
1259 rewriter, loc, resultType, extractOp.getTensor(), transferReadIdxs,
1260 /*padding=*/std::nullopt, permutationMap, inBounds);
1261
1262 Operation *readOrMaskedReadOp = transferReadOp;
1263 if (dstRank > 0) {
1264 // Mask this broadcasting xfer_read here rather than relying on the
1265 // generic path (the generic path assumes identity masking map, which
1266 // wouldn't be valid here).
1267 SmallVector<int64_t> readMaskShape = {1};
1268 auto readMaskType = VectorType::get(readMaskShape, rewriter.getI1Type());
1269 auto allTrue = vector::ConstantMaskOp::create(
1270 rewriter, loc, readMaskType, vector::ConstantMaskKind::AllTrue);
1271 readOrMaskedReadOp =
1272 mlir::vector::maskOperation(rewriter, transferReadOp, allTrue);
1273 }
1274
1275 LDBG() << "Vectorised as scalar broadcast load: " << extractOp;
1277 readOrMaskedReadOp};
1278 }
1279
1280 // 2b. Handle contiguous access.
1281 auto permutationMap = AffineMap::getMinorIdentityMap(
1282 srcRank, std::min(dstRank, srcRank), rewriter.getContext());
1283
1284 int32_t rankDiff = dstRank - srcRank;
1285 // When dstRank > srcRank, broadcast the source tensor to the unitary leading
1286 // dims so that the ranks match. This is done by extending the map with 0s.
1287 // For example, for dstRank = 3, srcRank = 2, the following map created
1288 // above:
1289 // (d0, d1) --> (d0, d1)
1290 // is extended as:
1291 // (d0, d1) --> (0, d0, d1)
1292 while (rankDiff > 0) {
1293 permutationMap = permutationMap.insertResult(
1294 mlir::getAffineConstantExpr(0, rewriter.getContext()), 0);
1295 rankDiff--;
1296 }
1297
1298 auto transferReadOp = vector::TransferReadOp::create(
1299 rewriter, loc, resultType, extractOp.getTensor(), transferReadIdxs,
1300 /*padding=*/std::nullopt, permutationMap, inBounds);
1301
1302 // Mask this contiguous xfer_read here rather than relying on the generic
1303 // path (the generic path assumes an identity masking map over all the loop
1304 // dims, which wouldn't be valid here). A contiguous load only reads the
1305 // trailing `min(dstRank, srcRank)` dims of the iteration space - the leading
1306 // dims are broadcast via `permutationMap` above - so its inferred mask is
1307 // rank-reduced. Build a masking map that projects the iteration space onto
1308 // exactly those trailing dims so the created mask matches the xfer_read.
1309 int64_t numReadDims = std::min(dstRank, srcRank);
1310 auto maskingMap = AffineMap::getMinorIdentityMap(
1311 linalgOp.getNumLoops(), numReadDims, rewriter.getContext());
1312 Operation *maskedReadOp =
1313 state.maskOperation(rewriter, transferReadOp, linalgOp, maskingMap);
1314
1315 LDBG() << "Vectorised as contiguous load: " << extractOp;
1317}
1318
1319/// Emit reduction operations if the shapes of the value to reduce is different
1320/// that the result shape.
1321// Note: this is a true builder that notifies the OpBuilder listener.
1322// TODO: Consider moving as a static helper on the ReduceOp.
1323static Operation *reduceIfNeeded(OpBuilder &b, LinalgOp linalgOp, Operation *op,
1324 Value reduceValue, Value initialValue,
1325 const IRMapping &bvm) {
1326 Value reduceVec = bvm.lookup(reduceValue);
1327 Value outputVec = bvm.lookup(initialValue);
1328 auto reduceType = dyn_cast<VectorType>(reduceVec.getType());
1329 auto outputType = dyn_cast<VectorType>(outputVec.getType());
1330 // Reduce only if needed as the value may already have been reduce for
1331 // contraction vectorization.
1332 if (!reduceType ||
1333 (outputType && reduceType.getShape() == outputType.getShape()))
1334 return nullptr;
1335 SmallVector<bool> dimsToMask = getDimsToReduce(linalgOp);
1336 return buildMultiDimReduce(b, op, reduceVec, outputVec, dimsToMask);
1337}
1338
1339/// Generic vectorization for a single operation `op`, given already vectorized
1340/// operands carried by `bvm`. Vectorization occurs as follows:
1341/// 1. Try to apply any of the `customVectorizationHooks` and return its
1342/// result on success.
1343/// 2. Clone any constant in the current scope without vectorization: each
1344/// consumer of the constant will later determine the shape to which the
1345/// constant needs to be broadcast to.
1346/// 3. Fail on any remaining non `ElementwiseMappable` op. It is the purpose
1347/// of the `customVectorizationHooks` to cover such cases.
1348/// 4. Clone `op` in vector form to a vector of shape prescribed by the first
1349/// operand of maximal rank. Other operands have smaller rank and are
1350/// broadcast accordingly. It is assumed this broadcast is always legal,
1351/// otherwise, it means one of the `customVectorizationHooks` is incorrect.
1352///
1353/// This function assumes all operands of `op` have been vectorized and are in
1354/// the `bvm` mapping. As a consequence, this function is meant to be called on
1355/// a topologically-sorted list of ops.
1356/// This function does not update `bvm` but returns a VectorizationHookStatus
1357/// that instructs the caller what `bvm` update needs to occur.
1358static VectorizationHookResult
1359vectorizeOneOp(RewriterBase &rewriter, VectorizationState &state,
1360 LinalgOp linalgOp, Operation *op, const IRMapping &bvm,
1361 ArrayRef<CustomVectorizationHook> customVectorizationHooks) {
1362 LDBG() << "vectorize op " << *op;
1363
1364 // 1. Try to apply any CustomVectorizationHook.
1365 if (!customVectorizationHooks.empty()) {
1366 for (auto &customFunc : customVectorizationHooks) {
1367 VectorizationHookResult result = customFunc(op, bvm);
1369 continue;
1370 return result;
1371 }
1372 }
1373
1374 // 2. Constant ops don't get vectorized but rather broadcasted at their users.
1375 // Clone so that the constant is not confined to the linalgOp block .
1376 if (isa<arith::ConstantOp, func::ConstantOp>(op))
1378 rewriter.clone(*op)};
1379
1380 // 3. Only ElementwiseMappable are allowed in the generic vectorization.
1383
1384 // 4 . Check if the operation is a reduction.
1385 SmallVector<std::pair<Value, Value>> reductionOperands;
1386 for (Value operand : op->getOperands()) {
1387 auto blockArg = dyn_cast<BlockArgument>(operand);
1388 if (!blockArg || blockArg.getOwner() != linalgOp.getBlock() ||
1389 blockArg.getArgNumber() < linalgOp.getNumDpsInputs())
1390 continue;
1391 SmallVector<Operation *> reductionOps;
1392 Value reduceValue = matchReduction(
1393 linalgOp.getRegionOutputArgs(),
1394 blockArg.getArgNumber() - linalgOp.getNumDpsInputs(), reductionOps);
1395 if (!reduceValue)
1396 continue;
1397 reductionOperands.push_back(std::make_pair(reduceValue, operand));
1398 }
1399 if (!reductionOperands.empty()) {
1400 assert(reductionOperands.size() == 1);
1401 Operation *reduceOp =
1402 reduceIfNeeded(rewriter, linalgOp, op, reductionOperands[0].first,
1403 reductionOperands[0].second, bvm);
1404 if (reduceOp)
1406 }
1407
1408 // 5. Generic vectorization path for ElementwiseMappable ops.
1409 // a. Get the first max ranked shape.
1410 VectorType firstMaxRankedType;
1411 for (Value operand : op->getOperands()) {
1412 auto vecOperand = bvm.lookup(operand);
1413 assert(vecOperand && "Vector operand couldn't be found");
1414
1415 auto vecType = dyn_cast<VectorType>(vecOperand.getType());
1416 if (vecType && (!firstMaxRankedType ||
1417 firstMaxRankedType.getRank() < vecType.getRank()))
1418 firstMaxRankedType = vecType;
1419 }
1420 // b. Broadcast each op if needed.
1421 SmallVector<Value> vecOperands;
1422 for (Value scalarOperand : op->getOperands()) {
1423 Value vecOperand = bvm.lookup(scalarOperand);
1424 assert(vecOperand && "Vector operand couldn't be found");
1425
1426 if (firstMaxRankedType) {
1427 auto vecType = VectorType::get(firstMaxRankedType.getShape(),
1428 getElementTypeOrSelf(vecOperand.getType()),
1429 firstMaxRankedType.getScalableDims());
1430 vecOperands.push_back(broadcastIfNeeded(rewriter, vecOperand, vecType));
1431 } else {
1432 vecOperands.push_back(vecOperand);
1433 }
1434 }
1435 // c. for elementwise, the result is the vector with the firstMaxRankedShape
1436 SmallVector<Type> resultTypes;
1437 for (Type resultType : op->getResultTypes()) {
1438 resultTypes.push_back(
1439 firstMaxRankedType
1440 ? VectorType::get(firstMaxRankedType.getShape(), resultType,
1441 firstMaxRankedType.getScalableDims())
1442 : resultType);
1443 }
1444 // d. Build and return the new op.
1447 rewriter.create(op->getLoc(), op->getName().getIdentifier(), vecOperands,
1448 resultTypes, op->getAttrs())};
1449}
1450
1451/// Generic vectorization function that rewrites the body of a `linalgOp` into
1452/// vector form. Generic vectorization proceeds as follows:
1453/// 1. Verify the `linalgOp` has one non-empty region.
1454/// 2. Values defined above the region are mapped to themselves and will be
1455/// broadcasted on a per-need basis by their consumers.
1456/// 3. Each region argument is vectorized into a vector.transfer_read (or 0-d
1457/// load).
1458/// TODO: Reuse opportunities for RAR dependencies.
1459/// 4a. Register CustomVectorizationHook for YieldOp to capture the results.
1460/// 4rewriter. Register CustomVectorizationHook for IndexOp to access the
1461/// iteration indices.
1462/// 5. Iteratively call vectorizeOneOp on the region operations.
1463///
1464/// When `broadcastToMaximalCommonShape` is set to true, eager broadcasting is
1465/// performed to the maximal common vector size implied by the `linalgOp`
1466/// iteration space. This eager broadcasting is introduced in the
1467/// permutation_map of the vector.transfer_read operations. The eager
1468/// broadcasting makes it trivial to determine where broadcast, transposes and
1469/// reductions should occur, without any bookkeeping. The tradeoff is that, in
1470/// the absence of good canonicalizations, the amount of work increases.
1471/// This is not deemed a problem as we expect canonicalizations and foldings to
1472/// aggressively clean up the useless work.
1473static LogicalResult
1474vectorizeAsLinalgGeneric(RewriterBase &rewriter, VectorizationState &state,
1475 LinalgOp linalgOp,
1476 SmallVectorImpl<Value> &newResults) {
1477 LDBG() << "Vectorizing operation as linalg generic/n";
1478 Block *block = linalgOp.getBlock();
1479
1480 // 2. Values defined above the region can only be broadcast for now. Make them
1481 // map to themselves.
1482 IRMapping bvm;
1483 SetVector<Value> valuesSet;
1484 mlir::getUsedValuesDefinedAbove(linalgOp->getRegion(0), valuesSet);
1485 bvm.map(valuesSet.getArrayRef(), valuesSet.getArrayRef());
1486
1487 if (linalgOp.getNumDpsInits() == 0)
1488 return failure();
1489
1490 // 3. Turn all BBArgs into vector.transfer_read / load.
1491 Location loc = linalgOp.getLoc();
1492 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1493 for (OpOperand *opOperand : linalgOp.getOpOperandsMatchingBBargs()) {
1494 BlockArgument bbarg = linalgOp.getMatchingBlockArgument(opOperand);
1495 if (linalgOp.isScalar(opOperand)) {
1496 bvm.map(bbarg, opOperand->get());
1497 continue;
1498 }
1499
1500 // 3.a. Convert the indexing map for this input/output to a transfer read
1501 // permutation map and masking map.
1502 AffineMap indexingMap = linalgOp.getMatchingIndexingMap(opOperand);
1503
1504 AffineMap readMap;
1505 VectorType readType;
1506 Type elemType = getElementTypeOrSelf(opOperand->get());
1507 if (linalgOp.isDpsInput(opOperand)) {
1508 // 3.a.i. For input reads we use the canonical vector shape.
1509 readMap = inverseAndBroadcastProjectedPermutation(indexingMap);
1510 readType = state.getCanonicalVecType(elemType);
1511 } else {
1512 // 3.a.ii. For output reads (iteration-carried dependence, e.g.,
1513 // reductions), the vector shape is computed by mapping the canonical
1514 // vector shape to the output domain and back to the canonical domain.
1515 readMap = inversePermutation(reindexIndexingMap(indexingMap));
1516 readType =
1517 state.getCanonicalVecType(elemType, readMap.compose(indexingMap));
1518 }
1519
1520 SmallVector<Value> indices(linalgOp.getShape(opOperand).size(), zero);
1521
1522 Operation *read = vector::TransferReadOp::create(
1523 rewriter, loc, readType, opOperand->get(), indices,
1524 /*padding=*/std::nullopt, readMap);
1525 read = state.maskOperation(rewriter, read, linalgOp, indexingMap);
1526 Value readValue = read->getResult(0);
1527
1528 // 3.b. If masked, set in-bounds to true. Masking guarantees that the access
1529 // will be in-bounds.
1530 if (auto maskOp = dyn_cast<vector::MaskingOpInterface>(read)) {
1531 SmallVector<bool> inBounds(readType.getRank(), true);
1532 cast<vector::TransferReadOp>(maskOp.getMaskableOp())
1533 .setInBoundsAttr(rewriter.getBoolArrayAttr(inBounds));
1534 }
1535
1536 // 3.c. Not all ops support 0-d vectors, extract the scalar for now.
1537 // TODO: remove this.
1538 if (readType.getRank() == 0)
1539 readValue = vector::ExtractOp::create(rewriter, loc, readValue,
1541
1542 LDBG() << "New vectorized bbarg(" << bbarg.getArgNumber()
1543 << "): " << readValue;
1544 bvm.map(bbarg, readValue);
1545 bvm.map(opOperand->get(), readValue);
1546 }
1547
1549 // 4a. Register CustomVectorizationHook for yieldOp.
1550 CustomVectorizationHook vectorizeYield =
1551 [&](Operation *op, const IRMapping &bvm) -> VectorizationHookResult {
1552 return vectorizeLinalgYield(rewriter, op, bvm, state, linalgOp, newResults);
1553 };
1554 hooks.push_back(vectorizeYield);
1555
1556 // 4b. Register CustomVectorizationHook for indexOp.
1557 CustomVectorizationHook vectorizeIndex =
1558 [&](Operation *op, const IRMapping &bvm) -> VectorizationHookResult {
1559 return vectorizeLinalgIndex(rewriter, state, op, linalgOp);
1560 };
1561 hooks.push_back(vectorizeIndex);
1562
1563 // 4c. Register CustomVectorizationHook for extractOp.
1564 CustomVectorizationHook vectorizeExtract =
1565 [&](Operation *op, const IRMapping &bvm) -> VectorizationHookResult {
1566 return vectorizeTensorExtract(rewriter, state, op, linalgOp, bvm);
1567 };
1568 hooks.push_back(vectorizeExtract);
1569
1570 // 5. Iteratively call `vectorizeOneOp` to each op in the slice.
1571 for (Operation &op : block->getOperations()) {
1573 vectorizeOneOp(rewriter, state, linalgOp, &op, bvm, hooks);
1575 LDBG() << "failed to vectorize: " << op;
1576 return failure();
1577 }
1578 if (result.status == VectorizationHookStatus::NewOp) {
1579 Operation *maybeMaskedOp =
1580 state.maskOperation(rewriter, result.newOp, linalgOp);
1581 LDBG() << "New vector op: " << *maybeMaskedOp;
1582 bvm.map(op.getResults(), maybeMaskedOp->getResults());
1583 }
1584 }
1585
1586 return success();
1587}
1588
1589/// Given the re-associations, "collapses" the input Vector type
1590///
1591/// This is similar to CollapseShapeOp::inferCollapsedType with two notable
1592/// differences:
1593/// * We can safely assume that there are no dynamic sizes.
1594/// * Scalable flags are updated alongside regular dims.
1595///
1596/// When collapsing scalable flags, conservatively avoids cases with two
1597/// scalable dims. We could re-visit this in the future.
1598///
1599/// EXAMPLE:
1600/// type = vector<4x16x[8]x16xf32>
1601/// reassociation = [(d0, d1, d2, d3) -> (d0, d1),
1602/// (d0, d1, d2, d3) -> (d2, d3)]
1603/// Result:
1604/// vector<64x[128]xf32>
1605static VectorType getCollapsedVecType(VectorType type,
1606 ArrayRef<AffineMap> reassociation) {
1607 assert(type.getNumScalableDims() < 2 &&
1608 "Collapsing more than 1 scalable dim is not supported ATM");
1609
1610 // Use the fact that reassociation is valid to simplify the logic: only use
1611 // each map's rank.
1612 assert(isReassociationValid(reassociation) && "invalid reassociation");
1613
1614 auto shape = type.getShape();
1615 auto scalableFlags = type.getScalableDims();
1616 SmallVector<int64_t> newShape;
1617 SmallVector<bool> newScalableFlags;
1618
1619 unsigned currentDim = 0;
1620 for (AffineMap m : reassociation) {
1621 unsigned dim = m.getNumResults();
1622 int64_t size = 1;
1623 bool flag = false;
1624 for (unsigned d = 0; d < dim; ++d) {
1625 size *= shape[currentDim + d];
1626 flag |= scalableFlags[currentDim + d];
1627 }
1628 newShape.push_back(size);
1629 newScalableFlags.push_back(flag);
1630 currentDim += dim;
1631 }
1632
1633 return VectorType::get(newShape, type.getElementType(), newScalableFlags);
1634}
1635
1636/// Vectorize `linalg.pack` as:
1637/// * xfer_read -> shape_cast -> transpose -> xfer_write
1638///
1639/// The input-vector-sizes specify the _write_ vector sizes (i.e. the vector
1640/// sizes for the xfer_write operation). This is sufficient to infer the other
1641/// vector sizes required here.
1642///
1643/// If the vector sizes are not provided:
1644/// * the vector sizes are determined from the destination tensor static shape.
1645/// * the inBounds attribute is used instead of masking.
1646///
1647/// EXAMPLE (no vector sizes):
1648/// ```
1649/// %pack = tensor.pack %src
1650/// inner_dims_pos = [2, 1]
1651/// inner_tiles = [16, 2]
1652/// into %dst : tensor<32x8x16xf32> -> tensor<32x4x1x16x2xf32>
1653/// ``
1654/// is vectorizes as:
1655/// ```
1656/// %read = vector.transfer_read %src
1657/// : tensor<32x7x16xf32>, vector<32x8x16xf32>
1658/// %sc = vector.shape_cast %read
1659/// : vector<32x8x16xf32> to vector<32x4x2x1x16xf32>
1660/// %tr = vector.transpose %sc, [0, 1, 3, 4, 2]
1661/// : vector<32x4x2x1x16xf32> to vector<32x4x1x16x2xf32>
1662/// %write = vector.transfer_write %tr into %dest
1663/// : vector<32x4x1x16x2xf32>, tensor<32x4x1x16x2xf32>
1664/// ```
1665static LogicalResult
1666vectorizeAsTensorPackOp(RewriterBase &rewriter, linalg::PackOp packOp,
1667 ArrayRef<int64_t> inputVectorSizes,
1668 SmallVectorImpl<Value> &newResults) {
1669 if (!inputVectorSizes.empty()) {
1670 assert(inputVectorSizes.size() == packOp.getDestRank() &&
1671 "Invalid number of input vector sizes!");
1672 }
1673
1674 // TODO: Introduce a parent class that will handle the insertion point update.
1675 OpBuilder::InsertionGuard g(rewriter);
1676 rewriter.setInsertionPoint(packOp);
1677
1678 Location loc = packOp.getLoc();
1679 std::optional<Value> padValue = packOp.getPaddingValue()
1680 ? std::optional(packOp.getPaddingValue())
1681 : std::nullopt;
1682
1683 SmallVector<int64_t> destShape =
1684 SmallVector<int64_t>(packOp.getDestType().getShape());
1685
1686 // This is just a convenience alias to clearly communicate that the input
1687 // vector sizes determine the _write_ sizes.
1688 ArrayRef<int64_t> &writeVectorSizes = inputVectorSizes;
1689
1690 // In the absence of input-vector-sizes, use the _static_ input tensor shape.
1691 // In addition, use the inBounds attribute instead of masking.
1692 bool useInBoundsInsteadOfMasking = false;
1693 if (writeVectorSizes.empty()) {
1694 if (ShapedType::isDynamicShape(destShape))
1695 return rewriter.notifyMatchFailure(packOp,
1696 "unable to infer vector sizes");
1697
1698 writeVectorSizes = destShape;
1699 useInBoundsInsteadOfMasking = true;
1700 }
1701
1702 // Compute pre-transpose-write-vector-type, i.e. the write vector type
1703 // _before_ the transposition (i.e. before dimension permutation). This is
1704 // done by inverting the permutation/transposition that's part of the Pack
1705 // operation. This type is required to:
1706 // 1) compute the read vector type for masked-read below, and
1707 // 2) generate shape-cast Op below that expands the read vector type.
1708 PackingMetadata packMetadata;
1709 SmallVector<int64_t> preTransposeWriteVecSizses(writeVectorSizes);
1710 auto destInvPermutation = getPackInverseDestPerm(packOp, packMetadata);
1711 applyPermutationToVector(preTransposeWriteVecSizses, destInvPermutation);
1712 auto preTransposeWriteVecType =
1713 VectorType::get(preTransposeWriteVecSizses,
1714 packOp.getResult().getType().getElementType());
1715
1716 // Compute vector type for the _read_ opeartion. This is simply
1717 // pre-transpose-write-vector-type with the dimensions collapsed
1718 // as per the Pack operation.
1719 VectorType readVecType = getCollapsedVecType(
1720 preTransposeWriteVecType,
1722 rewriter.getContext(), packMetadata.reassociations)));
1723
1724 // Create masked TransferReadOp.
1725 auto maskedRead = vector::createReadOrMaskedRead(
1726 rewriter, loc, packOp.getSource(), readVecType, padValue,
1727 useInBoundsInsteadOfMasking);
1728
1729 // Create ShapeCastOp.
1730 auto shapeCastOp = vector::ShapeCastOp::create(
1731 rewriter, loc, preTransposeWriteVecType, maskedRead);
1732
1733 // Create TransposeOp.
1734 auto destPermutation = invertPermutationVector(destInvPermutation);
1735 auto transposeOp = vector::TransposeOp::create(
1736 rewriter, loc, shapeCastOp.getResult(), destPermutation);
1737
1738 // Create TransferWriteOp.
1739 Operation *write = vector::createWriteOrMaskedWrite(
1740 rewriter, loc, transposeOp.getResult(), packOp.getDest());
1741 newResults.push_back(write->getResult(0));
1742 return success();
1743}
1744
1745/// Vectorize `linalg.unpack` as:
1746/// * xfer_read -> vector.transpose -> vector.shape_cast -> xfer_write
1747///
1748/// The input-vector-sizes specify the _read_ vector sizes (i.e. the vector
1749/// sizes for the xfer_read operation). This is sufficient to infer the other
1750/// vector sizes required here.
1751///
1752/// If the vector sizes are not provided:
1753/// * the vector sizes are determined from the input tensor static shape.
1754/// * the inBounds attribute is used instead of masking.
1755///
1756/// EXAMPLE (no vector sizes):
1757/// ```
1758/// %unpack = linalg.unpack %src
1759/// inner_dims_pos = [0, 1]
1760/// inner_tiles = [8, 8]
1761/// into %dest : tensor<1x1x8x8xf32> -> tensor<8x8xf32>
1762/// ```
1763/// is vectorized as:
1764/// ```
1765/// %read = vector.transfer_read %src
1766/// : tensor<1x1x8x8xf32>, vector<1x1x8x8xf32>
1767/// %tr = vector.transpose %read, [0, 2, 1, 3]
1768/// : vector<1x1x8x8xf32> to vector<1x8x1x8xf32>
1769/// %sc = vector.shape_cast %tr
1770/// : vector<1x8x1x8xf32> to vector<8x8xf32>
1771/// %vector = vector.transfer_write %sc into %dest
1772/// : vector<8x8xf32>, tensor<8x8xf32>
1773/// ```
1774static LogicalResult
1775vectorizeAsTensorUnpackOp(RewriterBase &rewriter, linalg::UnPackOp unpackOp,
1776 ArrayRef<int64_t> inputVectorSizes,
1777 ArrayRef<bool> inputScalableVecDims,
1778 SmallVectorImpl<Value> &newResults) {
1779 if (!inputVectorSizes.empty()) {
1780 assert(inputVectorSizes.size() == unpackOp.getSourceRank() &&
1781 "Invalid number of input vector sizes!");
1782 assert(inputVectorSizes.size() == inputScalableVecDims.size() &&
1783 "Incompatible number of vector sizes and vector scalable flags!");
1784 }
1785
1786 // TODO: Introduce a parent class that will handle the insertion point update.
1787 OpBuilder::InsertionGuard g(rewriter);
1788 rewriter.setInsertionPoint(unpackOp);
1789
1790 ShapedType unpackTensorType = unpackOp.getSourceType();
1791
1792 ArrayRef<int64_t> sourceShape = unpackTensorType.getShape();
1793 bool useInBoundsInsteadOfMasking = false;
1794
1795 Location loc = unpackOp->getLoc();
1796
1797 // Obtain vector sizes for the read operation.
1798 SmallVector<int64_t> readVectorSizes(inputVectorSizes);
1799 SmallVector<bool> readScalableVectorFlags(inputScalableVecDims);
1800
1801 // In the absence of input-vector-sizes, use the _static_ input tensor shape.
1802 if (inputVectorSizes.empty()) {
1803 if (ShapedType::isDynamicShape(sourceShape))
1804 return rewriter.notifyMatchFailure(unpackOp,
1805 "Unable to infer vector sizes!");
1806
1807 readVectorSizes.assign(sourceShape.begin(), sourceShape.end());
1808 useInBoundsInsteadOfMasking = true;
1809 }
1810
1811 // -- Generate the read operation --
1812 VectorType readVecType =
1813 VectorType::get(readVectorSizes, unpackTensorType.getElementType(),
1814 readScalableVectorFlags);
1815 Value readResult = vector::createReadOrMaskedRead(
1816 rewriter, loc, unpackOp.getSource(), readVecType, std::nullopt,
1817 useInBoundsInsteadOfMasking);
1818
1819 // -- Generate the transpose operation --
1820 PackingMetadata packMetadata;
1821 SmallVector<int64_t> lastDimToInsertPosPerm =
1822 getUnPackInverseSrcPerm(unpackOp, packMetadata);
1823 vector::TransposeOp transposeOp = vector::TransposeOp::create(
1824 rewriter, loc, readResult, lastDimToInsertPosPerm);
1825
1826 // -- Generate the shape_cast operation --
1827 VectorType collapsedVecType = getCollapsedVecType(
1828 transposeOp.getType(),
1830 rewriter.getContext(), packMetadata.reassociations)));
1831 vector::ShapeCastOp shapeCastOp = vector::ShapeCastOp::create(
1832 rewriter, loc, collapsedVecType, transposeOp->getResult(0));
1833
1834 // -- Generate the write operation --
1835 Operation *write = vector::createWriteOrMaskedWrite(
1836 rewriter, loc, shapeCastOp.getResult(), unpackOp.getDest(),
1837 /*writeIndices=*/{}, useInBoundsInsteadOfMasking);
1838
1839 newResults.push_back(write->getResult(0));
1840 return success();
1841}
1842
1843/// Vectorize a `padOp` with (1) static result type, (2) constant padding value
1844/// and (3) all-zero lowPad to
1845/// `transfer_write_in_bounds(transfer_read_masked(pad_source, pad_value))`.
1846static LogicalResult
1847vectorizeAsTensorPadOp(RewriterBase &rewriter, tensor::PadOp padOp,
1848 ArrayRef<int64_t> inputVectorSizes,
1849 SmallVectorImpl<Value> &newResults) {
1850 auto padValue = padOp.getConstantPaddingValue();
1851 Location loc = padOp.getLoc();
1852
1853 // TODO: Introduce a parent class that will handle the insertion point update.
1854 OpBuilder::InsertionGuard g(rewriter);
1855 rewriter.setInsertionPoint(padOp);
1856
1857 ReifiedRankedShapedTypeDims reifiedReturnShapes;
1858 LogicalResult status =
1859 cast<ReifyRankedShapedTypeOpInterface>(padOp.getOperation())
1860 .reifyResultShapes(rewriter, reifiedReturnShapes);
1861 (void)status; // prevent unused variable warning on non-assert builds
1862 assert(succeeded(status) && "failed to reify result shapes");
1863 auto readType = VectorType::get(inputVectorSizes, padValue.getType());
1864 auto maskedRead = vector::createReadOrMaskedRead(
1865 rewriter, loc, padOp.getSource(), readType, padValue,
1866 /*useInBoundsInsteadOfMasking=*/false);
1867
1868 // Create Xfer write Op
1869 Value dest = tensor::EmptyOp::create(rewriter, loc, reifiedReturnShapes[0],
1870 padOp.getResultType().getElementType());
1871 Operation *write =
1872 vector::createWriteOrMaskedWrite(rewriter, loc, maskedRead, dest);
1873 newResults.push_back(write->getResult(0));
1874 return success();
1875}
1876
1877// TODO: probably need some extra checks for reduction followed by consumer
1878// ops that may not commute (e.g. linear reduction + non-linear instructions).
1879static LogicalResult reductionPreconditions(LinalgOp op) {
1880 if (llvm::none_of(op.getIteratorTypesArray(), isReductionIterator)) {
1881 LDBG() << "reduction precondition failed: no reduction iterator";
1882 return failure();
1883 }
1884 for (OpOperand &opOperand : op.getDpsInitsMutable()) {
1885 AffineMap indexingMap = op.getMatchingIndexingMap(&opOperand);
1886 if (indexingMap.isPermutation())
1887 continue;
1888
1889 Operation *reduceOp = matchLinalgReduction(&opOperand);
1890 if (!reduceOp || !getCombinerOpKind(reduceOp)) {
1891 LDBG() << "reduction precondition failed: reduction detection failed";
1892 return failure();
1893 }
1894 }
1895 return success();
1896}
1897
1898static LogicalResult
1899vectorizeDynamicConvOpPrecondition(linalg::LinalgOp conv,
1900 bool flatten1DDepthwiseConv) {
1901 if (flatten1DDepthwiseConv) {
1902 LDBG() << "Vectorization of flattened convs with dynamic shapes is not "
1903 "supported";
1904 return failure();
1905 }
1906
1908 LDBG() << "Not a 1D depth-wise WC conv, dynamic shapes are not supported";
1909 return failure();
1910 }
1911
1912 // Support dynamic shapes in 1D depthwise convolution, but only in the
1913 // _channel_ dimension.
1914 Value lhs = conv.getDpsInputOperand(0)->get();
1915 ArrayRef<int64_t> lhsShape = cast<ShapedType>(lhs.getType()).getShape();
1916 auto shapeWithoutCh = lhsShape.drop_back(1);
1917 if (ShapedType::isDynamicShape(shapeWithoutCh)) {
1918 LDBG() << "Dynamically-shaped op vectorization precondition failed: only "
1919 "channel dim can be dynamic";
1920 return failure();
1921 }
1922
1923 return success();
1924}
1925
1926static LogicalResult
1927vectorizeDynamicLinalgOpPrecondition(linalg::LinalgOp op,
1928 bool flatten1DDepthwiseConv) {
1930 return vectorizeDynamicConvOpPrecondition(op, flatten1DDepthwiseConv);
1931
1932 if (hasReductionIterator(op))
1933 return reductionPreconditions(op);
1934
1935 // TODO: Masking only supports dynamic element-wise ops, linalg.generic ops,
1936 // linalg.copy ops and ops that implement ContractionOpInterface for now.
1937 if (!isElementwise(op) &&
1938 !isa<linalg::GenericOp, linalg::CopyOp, linalg::ContractionOpInterface>(
1939 op.getOperation()))
1940 return failure();
1941
1942 LDBG() << "Dynamically-shaped op meets vectorization pre-conditions";
1943 return success();
1944}
1945
1946//// This hook considers two cases:
1947/// (1) If the input-vector-sizes are empty, then the vector sizes will be
1948/// infered. This is only possible when all shapes are static.
1949/// (2) If the input-vector-sizes are non-empty (i.e. user provided), then
1950/// carry out basic sanity-checking.
1951static LogicalResult
1952vectorizeUnPackOpPrecondition(linalg::UnPackOp unpackOp,
1953 ArrayRef<int64_t> inputVectorSizes) {
1954 // TODO: Support Memref UnPackOp. Temporarily return failure.
1955 if (!unpackOp.hasPureTensorSemantics())
1956 return failure();
1957
1958 // If there are no input vector sizes and all shapes are static, there is
1959 // nothing left to check.
1960 if (inputVectorSizes.empty() && unpackOp.getDestType().hasStaticShape() &&
1961 unpackOp.getSourceType().hasStaticShape())
1962 return success();
1963
1964 // The number of input vector sizes must be equal to:
1965 // * read-vector-rank
1966 if (!inputVectorSizes.empty() &&
1967 (inputVectorSizes.size() != unpackOp.getSourceRank())) {
1968 LDBG() << "Incorrect number of input vector sizes";
1969 return failure();
1970 }
1971
1972 // Check the vector sizes for the read operation.
1974 unpackOp.getSourceType().getShape(), inputVectorSizes))) {
1975 LDBG() << "Invalid vector sizes for the read operation";
1976 return failure();
1977 }
1978
1979 return success();
1980}
1981
1982static LogicalResult
1983vectorizeInsertSliceOpPrecondition(tensor::InsertSliceOp sliceOp,
1984 ArrayRef<int64_t> inputVectorSizes) {
1985
1986 TypedValue<RankedTensorType> source = sliceOp.getSource();
1987 auto sourceType = source.getType();
1988 if (!VectorType::isValidElementType(sourceType.getElementType()))
1989 return failure();
1990
1991 // Get the pad value.
1992 // TransferReadOp (which is used to vectorize InsertSliceOp), requires a
1993 // scalar padding value. Note that:
1994 // * for in-bounds accesses,
1995 // the value is actually irrelevant. There are 2 cases in which xfer.read
1996 // accesses are known to be in-bounds:
1997 // 1. The source shape is static (output vector sizes would be based on
1998 // the source shape and hence all memory accesses would be in-bounds),
1999 // 2. Masking is used, i.e. the output vector sizes are user-provided. In
2000 // this case it is safe to assume that all memory accesses are in-bounds.
2001 //
2002 // When the value is not known and not needed, use 0. Otherwise, bail out.
2003 Value padValue = getStaticPadVal(sliceOp);
2004 bool isOutOfBoundsRead =
2005 !sourceType.hasStaticShape() && inputVectorSizes.empty();
2006
2007 if (!padValue && isOutOfBoundsRead) {
2008 LDBG() << "Failed to get a pad value for out-of-bounds read access";
2009 return failure();
2010 }
2011 return success();
2012}
2013
2014/// Vectorize a named linalg contraction op into:
2015/// vector::TransferReadOp - Reads vectors from the operands
2016/// vector::ContractionOp - Performs contraction
2017/// vector::TransferWriteOp - Write the result vector back to the
2018/// destination
2019/// The operands shapes are preserved and loaded directly into vectors.
2020/// Any further permutations or numerical casting remain within contraction op.
2021static LogicalResult
2022vectorizeAsLinalgContraction(RewriterBase &rewriter, VectorizationState &state,
2023 LinalgOp linalgOp,
2024 SmallVectorImpl<Value> &newResults) {
2025 Location loc = linalgOp.getLoc();
2026 MLIRContext *ctx = linalgOp.getContext();
2027
2028 // For simplicity, contraction vectorization is limited to linalg named ops.
2029 // Generic op is ignored as not every arbitrary contraction body can be
2030 // expressed by a vector.contract.
2031 if (!isa<ContractionOpInterface>(linalgOp.getOperation()))
2032 return failure();
2033
2034 OpOperand *outOperand = linalgOp.getDpsInitOperand(0);
2035 Operation *reduceOp = matchLinalgReduction(outOperand);
2036 auto maybeKind = getCombinerOpKind(reduceOp);
2037 if (!maybeKind) {
2038 LDBG() << "Failed to determine contraction combining kind.";
2039 return failure();
2040 }
2041
2042 // Check that all dimensions are present in the input operands.
2043 // Arbitrary broadcasts are not supported by the vector contraction.
2044 // Broadcasts are expected to be decomposed before vectorization.
2045 AffineMap lhsMap = linalgOp.getIndexingMapsArray()[0];
2046 AffineMap rhsMap = linalgOp.getIndexingMapsArray()[1];
2047 if (getUnusedDimsBitVector({lhsMap, rhsMap}).any()) {
2048 LDBG() << "Contractions with broadcasts are not supported.";
2049 return failure();
2050 }
2051
2052 // Load operands.
2053 SmallVector<Value> vecOperands;
2054 for (OpOperand &opOperand : linalgOp->getOpOperands()) {
2055 // The operand vector shape is computed by mapping the canonical vector
2056 // shape to the operand's domain. Further permutations are left as a part of
2057 // the contraction.
2058 AffineMap indexingMap = linalgOp.getMatchingIndexingMap(&opOperand);
2059 AffineMap readMap = AffineMap::getMultiDimIdentityMap(
2060 indexingMap.getNumResults(), rewriter.getContext());
2061 Type elemType = getElementTypeOrSelf(opOperand.get());
2062 VectorType readType =
2063 state.getCanonicalVecType(elemType, readMap.compose(indexingMap));
2064
2066 rewriter, loc, opOperand.get(), readType,
2067 /*padding=*/arith::getZeroConstant(rewriter, loc, elemType),
2068 /*useInBoundsInsteadOfMasking=*/false);
2069 vecOperands.push_back(read);
2070 }
2071
2072 // Remap iterators from linalg to vector.
2073 SmallVector<Attribute> iterAttrs;
2074 auto iterators = linalgOp.getIteratorTypesArray();
2075 for (utils::IteratorType iter : iterators) {
2076 auto vecIter = iter == utils::IteratorType::parallel
2077 ? vector::IteratorType::parallel
2078 : vector::IteratorType::reduction;
2079 iterAttrs.push_back(vector::IteratorTypeAttr::get(ctx, vecIter));
2080 }
2081
2082 // Create contraction.
2083 Operation *contractOp = vector::ContractionOp::create(
2084 rewriter, loc, /*lhs=*/vecOperands[0],
2085 /*rhs=*/vecOperands[1], /*acc=*/vecOperands[2],
2086 linalgOp.getIndexingMaps(), rewriter.getArrayAttr(iterAttrs), *maybeKind);
2087 contractOp = state.maskOperation(rewriter, contractOp, linalgOp);
2088
2089 // Store result.
2090 Operation *write = vector::createWriteOrMaskedWrite(
2091 rewriter, loc, contractOp->getResult(0), outOperand->get());
2092
2093 // Finalize.
2094 if (!write->getResults().empty())
2095 newResults.push_back(write->getResult(0));
2096
2097 return success();
2098}
2099
2100namespace {
2101enum class ConvOperationKind { Conv, Pool };
2102} // namespace
2103
2104static bool isCastOfBlockArgument(Operation *op) {
2105 return isa<CastOpInterface>(op) && op->getNumOperands() == 1 &&
2106 isa<BlockArgument>(op->getOperand(0));
2107}
2108
2109// Returns the ConvOperationKind of the op using reduceOp of the generic
2110// payload. If it is neither a convolution nor a pooling, it returns
2111// std::nullopt.
2112//
2113// If (region has 2 ops (reduction + yield) or 3 ops (extension + reduction
2114// + yield) and rhs is not used) then it is the body of a pooling
2115// If conv, check for single `mul` predecessor. The `mul` operands must be
2116// block arguments or extension of block arguments.
2117// Otherwise, check for one or zero `ext` predecessor. The `ext` operands
2118// must be block arguments or extension of block arguments.
2119static std::optional<ConvOperationKind>
2120getConvOperationKind(Operation *reduceOp) {
2121 int numBlockArguments =
2122 llvm::count_if(reduceOp->getOperands(), llvm::IsaPred<BlockArgument>);
2123
2124 switch (numBlockArguments) {
2125 case 1: {
2126 // Will be convolution if feeder is a MulOp.
2127 // A strength reduced version of MulOp for i1 type is AndOp which is also
2128 // supported. Otherwise, it can be pooling. This strength reduction logic
2129 // is in `buildBinaryFn` helper in the Linalg dialect.
2130 auto feedValIt = llvm::find_if_not(reduceOp->getOperands(),
2131 llvm::IsaPred<BlockArgument>);
2132 assert(feedValIt != reduceOp->operand_end() &&
2133 "Expected a non-block argument operand");
2134 Operation *feedOp = (*feedValIt).getDefiningOp();
2135 if (isCastOfBlockArgument(feedOp)) {
2136 return ConvOperationKind::Pool;
2137 }
2138
2139 if (!((isa<arith::MulIOp, arith::MulFOp>(feedOp) ||
2140 (isa<arith::AndIOp>(feedOp) &&
2141 feedOp->getResultTypes()[0].isInteger(1))) &&
2142 llvm::all_of(feedOp->getOperands(), [](Value v) {
2143 if (isa<BlockArgument>(v))
2144 return true;
2145 if (Operation *op = v.getDefiningOp())
2146 return isCastOfBlockArgument(op);
2147 return false;
2148 }))) {
2149 return std::nullopt;
2150 }
2151
2152 return ConvOperationKind::Conv;
2153 }
2154 case 2:
2155 // Must be pooling
2156 return ConvOperationKind::Pool;
2157 default:
2158 return std::nullopt;
2159 }
2160}
2161
2162static bool isSupportedPoolKind(vector::CombiningKind kind) {
2163 switch (kind) {
2164 case vector::CombiningKind::ADD:
2165 case vector::CombiningKind::MAXNUMF:
2166 case vector::CombiningKind::MAXIMUMF:
2167 case vector::CombiningKind::MAXSI:
2168 case vector::CombiningKind::MAXUI:
2169 case vector::CombiningKind::MINNUMF:
2170 case vector::CombiningKind::MINIMUMF:
2171 case vector::CombiningKind::MINSI:
2172 case vector::CombiningKind::MINUI:
2173 return true;
2174 default:
2175 return false;
2176 }
2177}
2178
2179static LogicalResult vectorizeConvOpPrecondition(linalg::LinalgOp convOp) {
2180 auto getOperandType = [&](auto operand) {
2181 return dyn_cast<ShapedType>((operand->get()).getType());
2182 };
2183 ShapedType lhsShapedType = getOperandType(convOp.getDpsInputOperand(0));
2184 ShapedType rhsShapedType = getOperandType(convOp.getDpsInputOperand(1));
2185 ShapedType resShapedType = getOperandType(convOp.getDpsInitOperand(0));
2186 // (LHS has dimension NCW/NWC and RES has dimension NFW/NCW/NWF/NWC) OR
2187 // (non-channeled convolution -> LHS and RHS both have single dimensions).
2188 // Note that this also ensures 2D and 3D convolutions are rejected.
2189 if ((lhsShapedType.getRank() != 3 || resShapedType.getRank() != 3) &&
2190 (lhsShapedType.getRank() != 1 || resShapedType.getRank() != 1))
2191 return failure();
2192
2193 Operation *reduceOp = matchLinalgReduction(convOp.getDpsInitOperand(0));
2194 if (!reduceOp)
2195 return failure();
2196
2197 auto maybeOper = getConvOperationKind(reduceOp);
2198 if (!maybeOper.has_value())
2199 return failure();
2200
2201 auto maybeKind = getCombinerOpKind(reduceOp);
2202 // Typically convolution will have a `Add` CombiningKind but for i1 type it
2203 // can get strength reduced to `OR` which is also supported. This strength
2204 // reduction logic is in `buildBinaryFn` helper in the Linalg dialect.
2205 if (!maybeKind || ((*maybeKind != vector::CombiningKind::ADD &&
2206 *maybeKind != vector::CombiningKind::OR) &&
2207 (*maybeOper != ConvOperationKind::Pool ||
2208 !isSupportedPoolKind(*maybeKind)))) {
2209 return failure();
2210 }
2211
2212 auto rhsRank = rhsShapedType.getRank();
2213 if (*maybeOper == ConvOperationKind::Pool) {
2214 if (rhsRank != 1)
2215 return failure();
2216 } else {
2217 if (rhsRank != 1 && rhsRank != 2 && rhsRank != 3)
2218 return failure();
2219 }
2220
2221 return success();
2222}
2223
2224static LogicalResult vectorizeLinalgOpPrecondition(
2225 LinalgOp linalgOp, ArrayRef<int64_t> inputVectorSizes,
2226 bool vectorizeNDExtract, bool flatten1DDepthwiseConv) {
2227 // tensor with dimension of 0 cannot be vectorized.
2228 if (llvm::any_of(linalgOp->getOpOperands(), [&](OpOperand &operand) {
2229 return llvm::is_contained(linalgOp.getShape(&operand), 0);
2230 }))
2231 return failure();
2232 // Check API contract for input vector sizes.
2233 if (!inputVectorSizes.empty() &&
2234 failed(vector::isValidMaskedInputVector(linalgOp.getStaticLoopRanges(),
2235 inputVectorSizes)))
2236 return failure();
2237
2238 if (linalgOp.hasDynamicShape() && failed(vectorizeDynamicLinalgOpPrecondition(
2239 linalgOp, flatten1DDepthwiseConv))) {
2240 LDBG() << "Dynamically-shaped op failed vectorization pre-conditions";
2241 return failure();
2242 }
2243
2244 SmallVector<CustomVectorizationPrecondition> customPreconditions;
2245
2246 // Register CustomVectorizationPrecondition for extractOp.
2247 customPreconditions.push_back(tensorExtractVectorizationPrecondition);
2248
2249 // All types in the body should be a supported element type for VectorType.
2250 for (Operation &innerOp : linalgOp->getRegion(0).front()) {
2251 // Check if any custom hook can vectorize the inner op.
2252 if (llvm::any_of(
2253 customPreconditions,
2254 [&](const CustomVectorizationPrecondition &customPrecondition) {
2255 return succeeded(
2256 customPrecondition(&innerOp, vectorizeNDExtract));
2257 })) {
2258 continue;
2259 }
2260 if (!llvm::all_of(innerOp.getOperandTypes(),
2261 VectorType::isValidElementType)) {
2262 return failure();
2263 }
2264 if (!llvm::all_of(innerOp.getResultTypes(),
2265 VectorType::isValidElementType)) {
2266 return failure();
2267 }
2268 }
2269 if (isElementwise(linalgOp))
2270 return success();
2271
2272 // Check for both named as well as generic convolution ops.
2273 if (isaConvolutionOpInterface(linalgOp))
2274 return vectorizeConvOpPrecondition(linalgOp);
2275
2276 // TODO: the common vector shape is equal to the static loop sizes only when
2277 // all indexing maps are projected permutations. For convs and stencils the
2278 // logic will need to evolve.
2279 if (!allIndexingsAreProjectedPermutation(linalgOp)) {
2280 LDBG() << "precondition failed: not projected permutations";
2281 return failure();
2282 }
2283 if (failed(reductionPreconditions(linalgOp))) {
2284 LDBG() << "precondition failed: reduction preconditions";
2285 return failure();
2286 }
2287 return success();
2288}
2289
2290static LogicalResult
2291vectorizePackOpPrecondition(linalg::PackOp packOp,
2292 ArrayRef<int64_t> inputVectorSizes) {
2293 // TODO: Support Memref PackOp. Temporarily return failure.
2294 if (!packOp.hasPureTensorSemantics())
2295 return failure();
2296
2297 auto padValue = packOp.getPaddingValue();
2298 Attribute cstAttr;
2299 // TODO: Relax this condiiton
2300 if (padValue && !matchPattern(padValue, m_Constant(&cstAttr))) {
2301 LDBG() << "pad value is not constant: " << packOp;
2302 return failure();
2303 }
2304
2305 ArrayRef<int64_t> resultTensorShape = packOp.getDestType().getShape();
2306 bool satisfyEmptyCond = true;
2307 if (inputVectorSizes.empty()) {
2308 if (!packOp.getDestType().hasStaticShape() ||
2309 !packOp.getSourceType().hasStaticShape())
2310 satisfyEmptyCond = false;
2311 }
2312
2313 if (!satisfyEmptyCond &&
2315 resultTensorShape.take_front(packOp.getSourceRank()),
2316 inputVectorSizes)))
2317 return failure();
2318
2319 if (llvm::any_of(packOp.getInnerTiles(), [](OpFoldResult v) {
2320 return !getConstantIntValue(v).has_value();
2321 })) {
2322 LDBG() << "inner_tiles must be constant: " << packOp;
2323 return failure();
2324 }
2325
2326 return success();
2327}
2328
2329static LogicalResult
2330vectorizePadOpPrecondition(tensor::PadOp padOp,
2331 ArrayRef<int64_t> inputVectorSizes) {
2332 auto padValue = padOp.getConstantPaddingValue();
2333 if (!padValue) {
2334 LDBG() << "pad value is not constant: " << padOp;
2335 return failure();
2336 }
2337
2338 ArrayRef<int64_t> resultTensorShape = padOp.getResultType().getShape();
2339 if (failed(vector::isValidMaskedInputVector(resultTensorShape,
2340 inputVectorSizes)))
2341 return failure();
2342
2343 // Padding with non-zero low pad values is not supported, unless the
2344 // corresponding result dim is 1 as this would require shifting the results to
2345 // the right for the low padded dims by the required amount of low padding.
2346 // However, we do support low padding if the dims being low padded have result
2347 // sizes of 1. The reason is when we have a low pad on a unit result dim, the
2348 // input size of that dimension will be dynamically zero (as the sum of the
2349 // low pad and input dim size has to be one) and hence we will create a zero
2350 // mask as the lowering logic just makes the mask one for the input dim size -
2351 // which is zero here. Hence we will load the pad value which is what we want
2352 // in this case. If the low pad is dynamically zero then the lowering is
2353 // correct as well as no shifts are necessary.
2354 if (llvm::any_of(llvm::enumerate(padOp.getMixedLowPad()),
2355 [&](const auto &en) {
2356 OpFoldResult padValue = en.value();
2357 unsigned pos = en.index();
2358 std::optional<int64_t> pad = getConstantIntValue(padValue);
2359 return (!pad.has_value() || pad.value() != 0) &&
2360 resultTensorShape[pos] != 1;
2361 })) {
2362 LDBG() << "low pad must all be zero for all non unit dims: " << padOp;
2363 return failure();
2364 }
2365
2366 return success();
2367}
2368
2369/// Preconditions for scalable vectors.
2370///
2371/// For Ops implementing the LinalgOp interface, this is quite restrictive - it
2372/// models the fact that in practice we would only make selected dimensions
2373/// scalable. For other Ops (e.g. `linalg.unpack`), this will succeed
2374/// unconditionally - we are yet to identify meaningful conditions.
2375static LogicalResult
2376vectorizeScalableVectorPrecondition(Operation *op,
2377 ArrayRef<int64_t> inputVectorSizes,
2378 ArrayRef<bool> inputScalableVecDims) {
2379 assert(inputVectorSizes.size() == inputScalableVecDims.size() &&
2380 "Number of input vector sizes and scalable dims doesn't match");
2381
2382 size_t numOfScalableDims =
2383 llvm::count_if(inputScalableVecDims, [](bool flag) { return flag; });
2384
2385 if (numOfScalableDims == 0)
2386 return success();
2387
2388 auto linalgOp = dyn_cast<LinalgOp>(op);
2389
2390 // Cond 1: Reject Ops that don't implement the LinalgOp interface, with the
2391 // exception of UnpackOp for which there is a dedicated hook.
2392 if (!linalgOp) {
2393 return success(isa<linalg::UnPackOp>(op));
2394 }
2395
2396 // Cond 2: There's been no need for more than 2 scalable dims so far
2397 if (numOfScalableDims > 2)
2398 return failure();
2399
2400 // Cond 3: Look at the configuration in `inputScalableVecDims` and verify that
2401 // it matches one of the supported cases:
2402 // 1. Exactly 1 dim is scalable and that's the _last_ non-unit parallel dim
2403 // (*).
2404 // 2. Exactly 2 dims are scalable and those are the _last two adjacent_
2405 // parallel dims.
2406 // 3. Exactly 1 reduction dim is scalable and that's the last (innermost)
2407 // dim.
2408 // The 2nd restriction above means that only Matmul-like Ops are supported
2409 // when 2 dims are scalable, e.g. :
2410 // * iterators = [parallel, parallel, reduction]
2411 // * scalable flags = [true, true, false]
2412 //
2413 // (*) Non-unit dims get folded away in practice.
2414 // TODO: Relax these conditions as good motivating examples are identified.
2415
2416 // Find the first scalable flag.
2417 bool seenNonUnitParallel = false;
2418 auto iterators = linalgOp.getIteratorTypesArray();
2419 SmallVector<bool> scalableFlags(inputScalableVecDims);
2420 int64_t idx = scalableFlags.size() - 1;
2421 while (!scalableFlags[idx]) {
2422 bool isNonUnitDim = (inputVectorSizes[idx] != 1);
2423 seenNonUnitParallel |=
2424 (iterators[idx] == utils::IteratorType::parallel && isNonUnitDim);
2425
2426 iterators.pop_back();
2427 scalableFlags.pop_back();
2428 --idx;
2429 }
2430
2431 // Analyze the iterator corresponding to the first scalable dim.
2432 switch (iterators.back()) {
2433 case utils::IteratorType::reduction: {
2434 // Check 3. above is met.
2435 if (iterators.size() != inputVectorSizes.size()) {
2436 LDBG() << "Non-trailing reduction dim requested for scalable "
2437 "vectorization";
2438 return failure();
2439 }
2440 if (isa<linalg::MatmulOp>(op)) {
2441 LDBG()
2442 << "Scalable vectorization of the reduction dim in Matmul-like ops "
2443 "is not supported";
2444 return failure();
2445 }
2446 break;
2447 }
2448 case utils::IteratorType::parallel: {
2449 // Check 1. and 2. above are met.
2450 if (seenNonUnitParallel) {
2451 LDBG() << "Inner parallel dim not requested for scalable "
2452 "vectorization";
2453 return failure();
2454 }
2455 break;
2456 }
2457 }
2458
2459 // If present, check the 2nd scalable dim. ATM, only Matmul-like Ops are
2460 // supported for which expect the folowing config:
2461 // * iterators = [parallel, parallel, reduction]
2462 // * scalable flags = [true, true, false]
2463 if (numOfScalableDims == 2) {
2464 // Disallow below case which breaks 3. above:
2465 // * iterators = [..., parallel, reduction]
2466 // * scalable flags = [..., true, true]
2467 if (iterators.back() == utils::IteratorType::reduction) {
2468 LDBG() << "Higher dim than the trailing reduction dim requested for "
2469 "scalable "
2470 "vectorizatio";
2471 return failure();
2472 }
2473 scalableFlags.pop_back();
2474 iterators.pop_back();
2475
2476 if (!scalableFlags.back() ||
2477 (iterators.back() != utils::IteratorType::parallel))
2478 return failure();
2479 }
2480
2481 // Cond 4: Only the following ops are supported in the
2482 // presence of scalable vectors
2483 return success(
2484 isElementwise(linalgOp) || isa<linalg::MatmulOp>(op) ||
2485 isa<linalg::BatchMatmulOp>(op) ||
2487 isa<linalg::MatvecOp>(op) || isa<linalg::Mmt4DOp>(op) ||
2488 isa<linalg::BatchMmt4DOp>(op) || hasReductionIterator(linalgOp));
2489}
2490
2492 Operation *op, ArrayRef<int64_t> inputVectorSizes,
2493 ArrayRef<bool> inputScalableVecDims, bool vectorizeNDExtract,
2494 bool flatten1DDepthwiseConv) {
2495
2496 if (!hasVectorizationImpl(op))
2497 return failure();
2498
2499 if (failed(vectorizeScalableVectorPrecondition(op, inputVectorSizes,
2500 inputScalableVecDims)))
2501 return failure();
2502
2504 .Case([&](linalg::LinalgOp linalgOp) {
2505 return vectorizeLinalgOpPrecondition(linalgOp, inputVectorSizes,
2506 vectorizeNDExtract,
2507 flatten1DDepthwiseConv);
2508 })
2509 .Case([&](tensor::PadOp padOp) {
2510 return vectorizePadOpPrecondition(padOp, inputVectorSizes);
2511 })
2512 .Case([&](linalg::PackOp packOp) {
2513 return vectorizePackOpPrecondition(packOp, inputVectorSizes);
2514 })
2515 .Case([&](linalg::UnPackOp unpackOp) {
2516 return vectorizeUnPackOpPrecondition(unpackOp, inputVectorSizes);
2517 })
2518 .Case([&](tensor::InsertSliceOp sliceOp) {
2519 return vectorizeInsertSliceOpPrecondition(sliceOp, inputVectorSizes);
2520 })
2521 .Default(failure());
2522}
2523
2524/// Converts affine.apply Ops to arithmetic operations.
2525static void convertAffineApply(RewriterBase &rewriter, LinalgOp linalgOp) {
2526 OpBuilder::InsertionGuard g(rewriter);
2527 auto toReplace = linalgOp.getBlock()->getOps<affine::AffineApplyOp>();
2528
2529 for (auto op : make_early_inc_range(toReplace)) {
2530 rewriter.setInsertionPoint(op);
2531 auto expanded = affine::expandAffineExpr(
2532 rewriter, op->getLoc(), op.getAffineMap().getResult(0),
2533 op.getOperands().take_front(op.getAffineMap().getNumDims()),
2534 op.getOperands().take_back(op.getAffineMap().getNumSymbols()));
2535 rewriter.replaceOp(op, expanded);
2536 }
2537}
2538
2539bool mlir::linalg::hasVectorizationImpl(Operation *op) {
2540 return isa<linalg::LinalgOp, tensor::PadOp, linalg::PackOp, linalg::UnPackOp,
2541 tensor::InsertSliceOp>(op);
2542}
2543
2544FailureOr<VectorizationResult> mlir::linalg::vectorize(
2545 RewriterBase &rewriter, Operation *op, ArrayRef<int64_t> inputVectorSizes,
2546 ArrayRef<bool> inputScalableVecDims, bool vectorizeNDExtract,
2547 bool flatten1DDepthwiseConv, bool assumeDynamicDimsMatchVecSizes,
2548 bool createNamedContraction) {
2549 LDBG() << "Attempting to vectorize: " << *op;
2550 LDBG() << "Input vector sizes: " << llvm::interleaved(inputVectorSizes);
2551 LDBG() << "Input scalable vector dims: "
2552 << llvm::interleaved(inputScalableVecDims);
2553
2554 if (failed(vectorizeOpPrecondition(op, inputVectorSizes, inputScalableVecDims,
2555 vectorizeNDExtract,
2556 flatten1DDepthwiseConv))) {
2557 LDBG() << "Vectorization pre-conditions failed";
2558 return failure();
2559 }
2560
2561 // Initialize vectorization state.
2562 VectorizationState state(rewriter);
2563 if (auto linalgOp = dyn_cast<linalg::LinalgOp>(op)) {
2564 if (failed(state.initState(rewriter, linalgOp, inputVectorSizes,
2565 inputScalableVecDims,
2566 assumeDynamicDimsMatchVecSizes))) {
2567 LDBG() << "Vectorization state couldn't be initialized";
2568 return failure();
2569 }
2570 }
2571
2572 SmallVector<Value> results;
2573 auto vectorizeResult =
2575 .Case([&](linalg::LinalgOp linalgOp) {
2576 // Check for both named as well as generic convolution ops.
2577 if (isaConvolutionOpInterface(linalgOp)) {
2578 FailureOr<Operation *> convOr = vectorizeConvolution(
2579 rewriter, linalgOp, inputVectorSizes, inputScalableVecDims,
2580 flatten1DDepthwiseConv);
2581 if (succeeded(convOr)) {
2582 llvm::append_range(results, (*convOr)->getResults());
2583 return success();
2584 }
2585
2586 LDBG() << "Unsupported convolution can't be vectorized.";
2587 return failure();
2588 }
2589
2590 if (createNamedContraction &&
2591 isa<ContractionOpInterface>(linalgOp.getOperation()))
2592 return vectorizeAsLinalgContraction(rewriter, state, linalgOp,
2593 results);
2594
2595 LDBG()
2596 << "Vectorize generic by broadcasting to the canonical vector "
2597 "shape";
2598
2599 // Pre-process before proceeding.
2600 convertAffineApply(rewriter, linalgOp);
2601
2602 // TODO: 'vectorize' takes in a 'RewriterBase' which is up-casted
2603 // to 'OpBuilder' when it is passed over to some methods like
2604 // 'vectorizeAsLinalgGeneric'. This is highly problematic: if we
2605 // erase an op within these methods, the actual rewriter won't be
2606 // notified and we will end up with read-after-free issues!
2607 return vectorizeAsLinalgGeneric(rewriter, state, linalgOp, results);
2608 })
2609 .Case([&](tensor::PadOp padOp) {
2610 return vectorizeAsTensorPadOp(rewriter, padOp, inputVectorSizes,
2611 results);
2612 })
2613 .Case([&](linalg::PackOp packOp) {
2614 return vectorizeAsTensorPackOp(rewriter, packOp, inputVectorSizes,
2615 results);
2616 })
2617 .Case([&](linalg::UnPackOp unpackOp) {
2618 return vectorizeAsTensorUnpackOp(rewriter, unpackOp,
2619 inputVectorSizes,
2620 inputScalableVecDims, results);
2621 })
2622 .Case([&](tensor::InsertSliceOp sliceOp) {
2623 return vectorizeAsInsertSliceOp(rewriter, sliceOp, inputVectorSizes,
2624 results);
2625 })
2626 .Default(failure());
2627
2628 if (failed(vectorizeResult)) {
2629 LDBG() << "Vectorization failed";
2630 return failure();
2631 }
2632
2633 return VectorizationResult{results};
2634}
2635
2636LogicalResult mlir::linalg::vectorizeCopy(RewriterBase &rewriter,
2637 memref::CopyOp copyOp) {
2638 auto srcType = cast<MemRefType>(copyOp.getSource().getType());
2639 auto dstType = cast<MemRefType>(copyOp.getTarget().getType());
2640 if (!srcType.hasStaticShape() || !dstType.hasStaticShape())
2641 return failure();
2642
2643 auto srcElementType = getElementTypeOrSelf(srcType);
2644 auto dstElementType = getElementTypeOrSelf(dstType);
2645 if (!VectorType::isValidElementType(srcElementType) ||
2646 !VectorType::isValidElementType(dstElementType))
2647 return failure();
2648
2649 auto readType = VectorType::get(srcType.getShape(), srcElementType);
2650 auto writeType = VectorType::get(dstType.getShape(), dstElementType);
2651
2652 Location loc = copyOp->getLoc();
2653 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
2654 SmallVector<Value> indices(srcType.getRank(), zero);
2655
2656 Value readValue = vector::TransferReadOp::create(
2657 rewriter, loc, readType, copyOp.getSource(), indices,
2658 /*padding=*/std::nullopt,
2659 rewriter.getMultiDimIdentityMap(srcType.getRank()));
2660 if (cast<VectorType>(readValue.getType()).getRank() == 0) {
2661 readValue = vector::ExtractOp::create(rewriter, loc, readValue,
2662 ArrayRef<int64_t>());
2663 readValue =
2664 vector::BroadcastOp::create(rewriter, loc, writeType, readValue);
2665 }
2666 Operation *writeValue = vector::TransferWriteOp::create(
2667 rewriter, loc, readValue, copyOp.getTarget(), indices,
2668 rewriter.getMultiDimIdentityMap(srcType.getRank()));
2669 rewriter.replaceOp(copyOp, writeValue->getResults());
2670 return success();
2671}
2672
2673//----------------------------------------------------------------------------//
2674// Misc. vectorization patterns.
2675//----------------------------------------------------------------------------//
2676/// Base pattern for rewriting tensor::PadOps whose result is consumed by a
2677/// given operation type OpTy.
2678template <typename OpTy>
2679struct VectorizePadOpUserPattern : public OpRewritePattern<tensor::PadOp> {
2680 using OpRewritePattern<tensor::PadOp>::OpRewritePattern;
2681
2682 LogicalResult matchAndRewrite(tensor::PadOp padOp,
2683 PatternRewriter &rewriter) const final {
2684 bool changed = false;
2685 // Insert users in vector, because some users may be replaced/removed.
2686 for (auto *user : llvm::to_vector<4>(padOp->getUsers()))
2687 if (auto op = dyn_cast<OpTy>(user))
2688 changed |= rewriteUser(rewriter, padOp, op).succeeded();
2689 return success(changed);
2690 }
2691
2692protected:
2693 virtual LogicalResult rewriteUser(PatternRewriter &rewriter,
2694 tensor::PadOp padOp, OpTy op) const = 0;
2695};
2696
2697/// Rewrite use of tensor::PadOp result in TransferReadOp. E.g.:
2698/// ```
2699/// %0 = tensor.pad %src ... : tensor<?x?xf32> to tensor<17x5xf32>
2700/// %r = vector.transfer_read %0[%c0, %c0], %cst
2701/// {in_bounds = [true, true]} : tensor<17x5xf32>, vector<17x5xf32>
2702/// ```
2703/// is rewritten to:
2704/// ```
2705/// %r = vector.transfer_read %src[%c0, %c0], %padding
2706/// {in_bounds = [true, true]}
2707/// : tensor<?x?xf32>, vector<17x5xf32>
2708/// ```
2709/// Note: By restricting this pattern to in-bounds TransferReadOps, we can be
2710/// sure that the original padding value %cst was never used.
2711///
2712/// This rewrite is possible if:
2713/// - `xferOp` has no out-of-bounds dims or mask.
2714/// - Low padding is static 0.
2715/// - Single, scalar padding value.
2716struct PadOpVectorizationWithTransferReadPattern
2717 : public VectorizePadOpUserPattern<vector::TransferReadOp> {
2718 using VectorizePadOpUserPattern<
2719 vector::TransferReadOp>::VectorizePadOpUserPattern;
2720
2721 LogicalResult rewriteUser(PatternRewriter &rewriter, tensor::PadOp padOp,
2722 vector::TransferReadOp xferOp) const override {
2723 // Low padding must be static 0.
2724 if (!padOp.hasZeroLowPad())
2725 return failure();
2726 // Pad value must be a constant.
2727 auto padValue = padOp.getConstantPaddingValue();
2728 if (!padValue)
2729 return failure();
2730 // Padding value of existing `xferOp` is unused.
2731 if (xferOp.hasOutOfBoundsDim() || xferOp.getMask())
2732 return failure();
2733
2734 rewriter.modifyOpInPlace(xferOp, [&]() {
2735 SmallVector<bool> inBounds(xferOp.getVectorType().getRank(), false);
2736 xferOp->setAttr(xferOp.getInBoundsAttrName(),
2737 rewriter.getBoolArrayAttr(inBounds));
2738 xferOp.getBaseMutable().assign(padOp.getSource());
2739 xferOp.getPaddingMutable().assign(padValue);
2740 });
2741
2742 return success();
2743 }
2744};
2745
2746/// Rewrite use of tensor::PadOp result in TransferWriteOp.
2747/// This pattern rewrites TransferWriteOps that write to a padded tensor
2748/// value, where the same amount of padding is immediately removed again after
2749/// the write. In such cases, the TransferWriteOp can write to the non-padded
2750/// tensor value and apply out-of-bounds masking. E.g.:
2751/// ```
2752/// %0 = tensor.extract_slice ...[...] [%s0, %s1] [1, 1]
2753/// : tensor<...> to tensor<?x?xf32>
2754/// %1 = tensor.pad %0 ... : tensor<?x?xf32> to tensor<17x5xf32>
2755/// %2 = vector.transfer_write %vec, %1[...]
2756/// : vector<17x5xf32>, tensor<17x5xf32>
2757/// %r = tensor.extract_slice %2[0, 0] [%s0, %s1] [1, 1]
2758/// : tensor<17x5xf32> to tensor<?x?xf32>
2759/// ```
2760/// is rewritten to:
2761/// ```
2762/// %0 = tensor.extract_slice ...[...] [%s0, %s1] [1, 1]
2763/// : tensor<...> to tensor<?x?xf32>
2764/// %r = vector.transfer_write %vec, %0[...] : vector<17x5xf32>,
2765/// tensor<?x?xf32>
2766/// ```
2767/// Note: It is important that the ExtractSliceOp %r resizes the result of the
2768/// TransferWriteOp to the same size as the input of the TensorPadOp (or an
2769/// even smaller size). Otherwise, %r's new (dynamic) dimensions would differ
2770/// from %r's old dimensions.
2771///
2772/// This rewrite is possible if:
2773/// - Low padding is static 0.
2774/// - `xferOp` has exactly one use, which is an ExtractSliceOp. This
2775/// ExtractSliceOp trims the same amount of padding that was added
2776/// beforehand.
2777/// - Single, scalar padding value.
2778struct PadOpVectorizationWithTransferWritePattern
2779 : public VectorizePadOpUserPattern<vector::TransferWriteOp> {
2780 using VectorizePadOpUserPattern<
2781 vector::TransferWriteOp>::VectorizePadOpUserPattern;
2782
2783 LogicalResult rewriteUser(PatternRewriter &rewriter, tensor::PadOp padOp,
2784 vector::TransferWriteOp xferOp) const override {
2785 // TODO: support 0-d corner case.
2786 if (xferOp.getTransferRank() == 0)
2787 return failure();
2788
2789 // Low padding must be static 0.
2790 if (!padOp.hasZeroLowPad())
2791 return failure();
2792 // Pad value must be a constant.
2793 auto padValue = padOp.getConstantPaddingValue();
2794 if (!padValue)
2795 return failure();
2796 // TransferWriteOp result must be directly consumed by an ExtractSliceOp.
2797 if (!xferOp->hasOneUse())
2798 return failure();
2799 auto trimPadding = dyn_cast<tensor::ExtractSliceOp>(*xferOp->user_begin());
2800 if (!trimPadding)
2801 return failure();
2802 // Only static zero offsets supported when trimming padding.
2803 if (!trimPadding.hasZeroOffset())
2804 return failure();
2805 // trimPadding must remove the amount of padding that was added earlier.
2806 if (!hasSameTensorSize(padOp.getSource(), trimPadding))
2807 return failure();
2808
2809 // Insert the new TransferWriteOp at position of the old TransferWriteOp.
2810 rewriter.setInsertionPoint(xferOp);
2811
2812 SmallVector<bool> inBounds(xferOp.getVectorType().getRank(), false);
2813 auto newXferOp = rewriter.replaceOpWithNewOp<vector::TransferWriteOp>(
2814 xferOp, padOp.getSource().getType(), xferOp.getVector(),
2815 padOp.getSource(), xferOp.getIndices(), xferOp.getPermutationMapAttr(),
2816 xferOp.getMask(), rewriter.getBoolArrayAttr(inBounds));
2817 rewriter.replaceOp(trimPadding, newXferOp->getResult(0));
2818
2819 return success();
2820 }
2821
2822 /// Check if `beforePadding` and `afterTrimming` have the same tensor size,
2823 /// i.e., same dimensions.
2824 ///
2825 /// Dimensions may be static, dynamic or mix of both. In case of dynamic
2826 /// dimensions, this function tries to infer the (static) tensor size by
2827 /// looking at the defining op and utilizing op-specific knowledge.
2828 ///
2829 /// This is a conservative analysis. In case equal tensor sizes cannot be
2830 /// proven statically, this analysis returns `false` even though the tensor
2831 /// sizes may turn out to be equal at runtime.
2832 bool hasSameTensorSize(Value beforePadding,
2833 tensor::ExtractSliceOp afterTrimming) const {
2834 // If the input to tensor::PadOp is a CastOp, try with both CastOp
2835 // result and CastOp operand.
2836 if (auto castOp = beforePadding.getDefiningOp<tensor::CastOp>())
2837 if (hasSameTensorSize(castOp.getSource(), afterTrimming))
2838 return true;
2839
2840 auto t1 = dyn_cast<RankedTensorType>(beforePadding.getType());
2841 auto t2 = dyn_cast<RankedTensorType>(afterTrimming.getType());
2842 // Only RankedTensorType supported.
2843 if (!t1 || !t2)
2844 return false;
2845 // Rank of both values must be the same.
2846 if (t1.getRank() != t2.getRank())
2847 return false;
2848
2849 // All static dimensions must be the same. Mixed cases (e.g., dimension
2850 // static in `t1` but dynamic in `t2`) are not supported.
2851 for (unsigned i = 0; i < t1.getRank(); ++i) {
2852 if (t1.isDynamicDim(i) != t2.isDynamicDim(i))
2853 return false;
2854 if (!t1.isDynamicDim(i) && t1.getDimSize(i) != t2.getDimSize(i))
2855 return false;
2856 }
2857
2858 // Nothing more to check if all dimensions are static.
2859 if (t1.getNumDynamicDims() == 0)
2860 return true;
2861
2862 // All dynamic sizes must be the same. The only supported case at the
2863 // moment is when `beforePadding` is an ExtractSliceOp (or a cast
2864 // thereof).
2865
2866 // Apart from CastOp, only ExtractSliceOp is supported.
2867 auto beforeSlice = beforePadding.getDefiningOp<tensor::ExtractSliceOp>();
2868 if (!beforeSlice)
2869 return false;
2870
2871 assert(static_cast<size_t>(t1.getRank()) ==
2872 beforeSlice.getMixedSizes().size());
2873 assert(static_cast<size_t>(t2.getRank()) ==
2874 afterTrimming.getMixedSizes().size());
2875
2876 for (unsigned i = 0; i < t1.getRank(); ++i) {
2877 // Skip static dimensions.
2878 if (!t1.isDynamicDim(i))
2879 continue;
2880 auto size1 = beforeSlice.getMixedSizes()[i];
2881 auto size2 = afterTrimming.getMixedSizes()[i];
2882
2883 // Case 1: Same value or same constant int.
2884 if (isEqualConstantIntOrValue(size1, size2))
2885 continue;
2886
2887 // Other cases: Take a deeper look at defining ops of values.
2888 auto v1 = llvm::dyn_cast_if_present<Value>(size1);
2889 auto v2 = llvm::dyn_cast_if_present<Value>(size2);
2890 if (!v1 || !v2)
2891 return false;
2892
2893 // Case 2: Both values are identical AffineMinOps. (Should not happen if
2894 // CSE is run.)
2895 auto minOp1 = v1.getDefiningOp<affine::AffineMinOp>();
2896 auto minOp2 = v2.getDefiningOp<affine::AffineMinOp>();
2897 if (minOp1 && minOp2 && minOp1.getAffineMap() == minOp2.getAffineMap() &&
2898 minOp1.getOperands() == minOp2.getOperands())
2899 continue;
2900
2901 // Add additional cases as needed.
2902 }
2903
2904 // All tests passed.
2905 return true;
2906 }
2907};
2908
2909/// Returns the effective Pad value for the input op, provided it's a scalar.
2910///
2911/// Many Ops exhibit pad-like behaviour, but this isn't always explicit. If
2912/// this Op performs padding, retrieve the padding value provided that it's
2913/// a scalar and static/fixed for all the padded values. Returns an empty value
2914/// otherwise.
2915///
2916/// TODO: This is used twice (when checking vectorization pre-conditions and
2917/// when vectorizing). Cache results instead of re-running.
2918static Value getStaticPadVal(Operation *op) {
2919 if (!op)
2920 return {};
2921
2922 // 1. vector.broadcast (f32 -> vector <...xf32>) - return the value that's
2923 // being broadcast, provided that it's a scalar.
2924 if (auto bcast = llvm::dyn_cast<vector::BroadcastOp>(op)) {
2925 auto source = bcast.getSource();
2926 if (llvm::dyn_cast<VectorType>(source.getType()))
2927 return {};
2928
2929 return source;
2930 }
2931
2932 // 2. linalg.fill - use the scalar input value that used to fill the output
2933 // tensor.
2934 if (auto fill = llvm::dyn_cast<linalg::FillOp>(op)) {
2935 return fill.getInputs()[0];
2936 }
2937
2938 // 3. tensor.generateOp - can't guarantee the value is fixed without
2939 // analysing, bail out.
2940 if (auto generate = llvm::dyn_cast<tensor::GenerateOp>(op)) {
2941 return {};
2942 }
2943
2944 // 4. vector.transfer_write - inspect the input vector that's written from. If
2945 // if contains a single value that has been broadcast (e.g. via
2946 // vector.broadcast), extract it, fail otherwise.
2947 if (auto xferWrite = llvm::dyn_cast<vector::TransferWriteOp>(op))
2948 return getStaticPadVal(xferWrite.getVector().getDefiningOp());
2949
2950 // 5. tensor.insert_slice - inspect the destination tensor. If it's larger
2951 // than the input tensor, then, provided it's constant, we'll extract the
2952 // value that was used to generate it (via e.g. linalg.fill), fail otherwise.
2953 // TODO: Clarify the semantics when the input tensor is larger than the
2954 // destination.
2955 if (auto slice = llvm::dyn_cast<tensor::InsertSliceOp>(op))
2956 return getStaticPadVal(slice.getDest().getDefiningOp());
2957
2958 return {};
2959}
2960
2961static LogicalResult
2962vectorizeAsInsertSliceOp(RewriterBase &rewriter, tensor::InsertSliceOp sliceOp,
2963 ArrayRef<int64_t> inputVectorSizes,
2964 SmallVectorImpl<Value> &newResults) {
2965 // TODO: Introduce a parent class that will handle the insertion point update.
2966 OpBuilder::InsertionGuard g(rewriter);
2967 rewriter.setInsertionPoint(sliceOp);
2968
2969 TypedValue<RankedTensorType> source = sliceOp.getSource();
2970 auto sourceType = source.getType();
2971 auto resultType = sliceOp.getResultType();
2972
2973 Value padValue = getStaticPadVal(sliceOp);
2974
2975 if (!padValue) {
2976 auto elemType = sourceType.getElementType();
2977 padValue = arith::ConstantOp::create(rewriter, sliceOp.getLoc(), elemType,
2978 rewriter.getZeroAttr(elemType));
2979 }
2980
2981 // 2. Get the vector shape
2982 SmallVector<int64_t> vecShape;
2983 size_t rankDiff = resultType.getRank() - sourceType.getRank();
2984 for (int64_t i = 0, end = sourceType.getRank(); i < end; ++i) {
2985 if (!inputVectorSizes.empty()) {
2986 vecShape.push_back(inputVectorSizes[i]);
2987 } else if (!sourceType.isDynamicDim(i)) {
2988 vecShape.push_back(sourceType.getDimSize(i));
2989 } else if (!resultType.isDynamicDim(i)) {
2990 // Source shape is not statically known, but result shape is.
2991 // Vectorize with size of result shape. This may be larger than the
2992 // source size.
2993 // FIXME: Using rankDiff implies that the source tensor is inserted at
2994 // the end of the destination tensor. However, that's not required.
2995 vecShape.push_back(resultType.getDimSize(rankDiff + i));
2996 } else {
2997 // Neither source nor result dim of padOp is static. Cannot vectorize
2998 // the copy.
2999 return failure();
3000 }
3001 }
3002 auto vecType = VectorType::get(vecShape, sourceType.getElementType());
3003
3004 // 3. Generate TransferReadOp + TransferWriteOp
3005 auto loc = sliceOp.getLoc();
3006
3007 // Create read
3008 SmallVector<Value> readIndices(
3009 vecType.getRank(), arith::ConstantIndexOp::create(rewriter, loc, 0));
3011 rewriter, loc, source, vecType, padValue,
3012 /*useInBoundsInsteadOfMasking=*/inputVectorSizes.empty());
3013
3014 // Create write
3015 auto writeIndices =
3016 getValueOrCreateConstantIndexOp(rewriter, loc, sliceOp.getMixedOffsets());
3017 Operation *write =
3018 vector::createWriteOrMaskedWrite(rewriter, loc, read, sliceOp.getDest(),
3019 writeIndices, inputVectorSizes.empty());
3020
3021 // 4. Finalize
3022 newResults.push_back(write->getResult(0));
3023
3024 return success();
3025}
3026
3027/// Rewrite use of tensor::PadOp result in InsertSliceOp. E.g.:
3028/// ```
3029/// %0 = tensor.pad %src ... : tensor<?x?xf32> to tensor<17x5xf32>
3030/// %r = tensor.insert_slice %0
3031/// into %dest[%a, %b, 0, 0] [1, 1, 17, 5] [1, 1, 1, 1]
3032/// : tensor<17x5xf32> into tensor<?x?x17x5xf32>
3033/// ```
3034/// is rewritten to:
3035/// ```
3036/// %0 = vector.transfer_read %src[%c0, %c0], %padding
3037/// : tensor<?x?xf32>, vector<17x5xf32>
3038/// %r = vector.transfer_write %0, %dest[%a, %b, %c0, %c0]
3039/// {in_bounds = [true, true]} : vector<17x5xf32>, tensor<?x?x17x5xf32>
3040/// ```
3041///
3042/// This rewrite is possible if:
3043/// - Low padding is static 0.
3044/// - `padOp` result shape is static.
3045/// - The entire padded tensor is inserted.
3046/// (Implies that sizes of `insertOp` are all static.)
3047/// - Only unit strides in `insertOp`.
3048/// - Single, scalar padding value.
3049/// - `padOp` result not used as destination.
3050struct PadOpVectorizationWithInsertSlicePattern
3051 : public VectorizePadOpUserPattern<tensor::InsertSliceOp> {
3052 using VectorizePadOpUserPattern<
3053 tensor::InsertSliceOp>::VectorizePadOpUserPattern;
3054
3055 LogicalResult rewriteUser(PatternRewriter &rewriter, tensor::PadOp padOp,
3056 tensor::InsertSliceOp insertOp) const override {
3057 // Low padding must be static 0.
3058 if (!padOp.hasZeroLowPad())
3059 return failure();
3060 // Only unit stride supported.
3061 if (!insertOp.hasUnitStride())
3062 return failure();
3063 // Pad value must be a constant.
3064 auto padValue = padOp.getConstantPaddingValue();
3065 if (!padValue)
3066 return failure();
3067 // Dynamic shapes not supported.
3068 if (!cast<ShapedType>(padOp.getResult().getType()).hasStaticShape())
3069 return failure();
3070 // Pad result not used as destination.
3071 if (insertOp.getDest() == padOp.getResult())
3072 return failure();
3073
3074 auto vecType = VectorType::get(padOp.getType().getShape(),
3075 padOp.getType().getElementType());
3076 unsigned vecRank = vecType.getRank();
3077 unsigned tensorRank = insertOp.getType().getRank();
3078
3079 // Check if sizes match: Insert the entire tensor into most minor dims.
3080 // (No permutations allowed.)
3081 SmallVector<int64_t> expectedSizes(tensorRank - vecRank, 1);
3082 expectedSizes.append(vecType.getShape().begin(), vecType.getShape().end());
3083 if (!llvm::all_of(
3084 llvm::zip(insertOp.getMixedSizes(), expectedSizes), [](auto it) {
3085 return getConstantIntValue(std::get<0>(it)) == std::get<1>(it);
3086 }))
3087 return failure();
3088
3089 // Insert the TransferReadOp and TransferWriteOp at the position of the
3090 // InsertSliceOp.
3091 rewriter.setInsertionPoint(insertOp);
3092
3093 // Generate TransferReadOp: Read entire source tensor and add high
3094 // padding.
3095 SmallVector<Value> readIndices(
3096 vecRank, arith::ConstantIndexOp::create(rewriter, padOp.getLoc(), 0));
3097 auto read = vector::TransferReadOp::create(rewriter, padOp.getLoc(),
3098 vecType, padOp.getSource(),
3099 readIndices, padValue);
3100
3101 // Generate TransferWriteOp: Write to InsertSliceOp's dest tensor at
3102 // specified offsets. Write is fully in-bounds because a InsertSliceOp's
3103 // source must fit into the destination at the specified offsets.
3104 auto writeIndices = getValueOrCreateConstantIndexOp(
3105 rewriter, padOp.getLoc(), insertOp.getMixedOffsets());
3106 SmallVector<bool> inBounds(vecRank, true);
3107 rewriter.replaceOpWithNewOp<vector::TransferWriteOp>(
3108 insertOp, read, insertOp.getDest(), writeIndices,
3109 ArrayRef<bool>{inBounds});
3110
3111 return success();
3112 }
3113};
3114
3116 RewritePatternSet &patterns, PatternBenefit baseBenefit) {
3117 patterns.add<PadOpVectorizationWithTransferReadPattern,
3118 PadOpVectorizationWithTransferWritePattern,
3119 PadOpVectorizationWithInsertSlicePattern>(
3120 patterns.getContext(), baseBenefit.getBenefit() + 1);
3121}
3122
3123//----------------------------------------------------------------------------//
3124// Forwarding patterns
3125//----------------------------------------------------------------------------//
3126
3127/// Check whether there is any interleaved use of any `values` between
3128/// `firstOp` and `secondOp`. Conservatively return `true` if any op or value
3129/// is in a different block.
3130static bool mayExistInterleavedUses(Operation *firstOp, Operation *secondOp,
3131 ValueRange values) {
3132 if (firstOp->getBlock() != secondOp->getBlock() ||
3133 !firstOp->isBeforeInBlock(secondOp)) {
3134 LDBG() << "interleavedUses precondition failed, firstOp: " << *firstOp
3135 << ", second op: " << *secondOp;
3136 return true;
3137 }
3138 for (auto v : values) {
3139 for (auto &u : v.getUses()) {
3140 Operation *owner = u.getOwner();
3141 if (owner == firstOp || owner == secondOp)
3142 continue;
3143 // TODO: this is too conservative, use dominance info in the future.
3144 if (owner->getBlock() == firstOp->getBlock() &&
3145 (owner->isBeforeInBlock(firstOp) || secondOp->isBeforeInBlock(owner)))
3146 continue;
3147 LDBG() << " found interleaved op " << *owner << ", firstOp: " << *firstOp
3148 << ", second op: " << *secondOp;
3149 return true;
3150 }
3151 }
3152 return false;
3153}
3154
3155/// Return the unique subview use of `v` if it is indeed unique, null
3156/// otherwise.
3157static memref::SubViewOp getSubViewUseIfUnique(Value v) {
3158 memref::SubViewOp subViewOp;
3159 for (auto &u : v.getUses()) {
3160 if (auto newSubViewOp = dyn_cast<memref::SubViewOp>(u.getOwner())) {
3161 if (subViewOp)
3162 return memref::SubViewOp();
3163 subViewOp = newSubViewOp;
3164 }
3165 }
3166 return subViewOp;
3167}
3168
3169/// TODO: use interfaces, side-effects and aliasing analysis as appropriate,
3170/// when available.
3172 vector::TransferReadOp xferOp, PatternRewriter &rewriter) const {
3173
3174 // TODO: support mask.
3175 if (xferOp.getMask())
3176 return rewriter.notifyMatchFailure(xferOp, "unsupported mask");
3177
3178 // Transfer into `view`.
3179 Value viewOrAlloc = xferOp.getBase();
3180 if (!viewOrAlloc.getDefiningOp<memref::ViewOp>() &&
3181 !viewOrAlloc.getDefiningOp<memref::AllocOp>())
3182 return rewriter.notifyMatchFailure(xferOp, "source not a view or alloc");
3183
3184 // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`.
3185 memref::SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc);
3186 if (!subViewOp)
3187 return rewriter.notifyMatchFailure(xferOp, "no subview found");
3188 Value subView = subViewOp.getResult();
3189
3190 // Find the copy into `subView` without interleaved uses.
3191 memref::CopyOp copyOp;
3192 for (auto &u : subView.getUses()) {
3193 if (auto newCopyOp = dyn_cast<memref::CopyOp>(u.getOwner())) {
3194 assert(isa<MemRefType>(newCopyOp.getTarget().getType()));
3195 if (newCopyOp.getTarget() != subView)
3196 continue;
3197 if (mayExistInterleavedUses(newCopyOp, xferOp, {viewOrAlloc, subView}))
3198 continue;
3199 copyOp = newCopyOp;
3200 break;
3201 }
3202 }
3203 if (!copyOp)
3204 return rewriter.notifyMatchFailure(xferOp, "no copy found");
3205
3206 // Find the fill into `viewOrAlloc` without interleaved uses before the
3207 // copy.
3208 FillOp maybeFillOp;
3209 for (auto &u : viewOrAlloc.getUses()) {
3210 if (auto newFillOp = dyn_cast<FillOp>(u.getOwner())) {
3211 assert(isa<MemRefType>(newFillOp.output().getType()));
3212 if (newFillOp.output() != viewOrAlloc)
3213 continue;
3214 if (mayExistInterleavedUses(newFillOp, copyOp, {viewOrAlloc, subView}))
3215 continue;
3216 maybeFillOp = newFillOp;
3217 break;
3218 }
3219 }
3220 // Ensure padding matches.
3221 if (maybeFillOp && xferOp.getPadding() != maybeFillOp.value())
3222 return rewriter.notifyMatchFailure(xferOp,
3223 "padding value does not match fill");
3224
3225 // `in` is the subview that memref.copy reads. Replace it.
3226 Value in = copyOp.getSource();
3227
3228 // memref.copy + linalg.fill can be used to create a padded local buffer.
3229 // The `masked` attribute is only valid on this padded buffer.
3230 // When forwarding to vector.transfer_read, the attribute must be reset
3231 // conservatively.
3232 auto vectorType = xferOp.getVectorType();
3233 Value res = vector::TransferReadOp::create(
3234 rewriter, xferOp.getLoc(), vectorType, in, xferOp.getIndices(),
3235 xferOp.getPermutationMapAttr(), xferOp.getPadding(), xferOp.getMask(),
3236 rewriter.getBoolArrayAttr(
3237 SmallVector<bool>(vectorType.getRank(), false)));
3238
3239 if (maybeFillOp)
3240 rewriter.eraseOp(maybeFillOp);
3241 rewriter.eraseOp(copyOp);
3242 rewriter.replaceOp(xferOp, res);
3243
3244 return success();
3245}
3246
3247/// TODO: use interfaces, side-effects and aliasing analysis as appropriate,
3248/// when available.
3250 vector::TransferWriteOp xferOp, PatternRewriter &rewriter) const {
3251 // TODO: support mask.
3252 if (xferOp.getMask())
3253 return rewriter.notifyMatchFailure(xferOp, "unsupported mask");
3254
3255 // Transfer into `viewOrAlloc`.
3256 Value viewOrAlloc = xferOp.getBase();
3257 if (!viewOrAlloc.getDefiningOp<memref::ViewOp>() &&
3258 !viewOrAlloc.getDefiningOp<memref::AllocOp>())
3259 return rewriter.notifyMatchFailure(xferOp, "source not a view or alloc");
3260
3261 // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`.
3262 memref::SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc);
3263 if (!subViewOp)
3264 return rewriter.notifyMatchFailure(xferOp, "no subview found");
3265 Value subView = subViewOp.getResult();
3266
3267 // Find the copy from `subView` without interleaved uses.
3268 memref::CopyOp copyOp;
3269 for (auto &u : subViewOp.getResult().getUses()) {
3270 if (auto newCopyOp = dyn_cast<memref::CopyOp>(u.getOwner())) {
3271 if (newCopyOp.getSource() != subView)
3272 continue;
3273 if (mayExistInterleavedUses(xferOp, newCopyOp, {viewOrAlloc, subView}))
3274 continue;
3275 copyOp = newCopyOp;
3276 break;
3277 }
3278 }
3279 if (!copyOp)
3280 return rewriter.notifyMatchFailure(xferOp, "no copy found");
3281
3282 // `out` is the subview copied into that we replace.
3283 assert(isa<MemRefType>(copyOp.getTarget().getType()));
3284 Value out = copyOp.getTarget();
3285
3286 // Forward vector.transfer into copy.
3287 // memref.copy + linalg.fill can be used to create a padded local buffer.
3288 // The `masked` attribute is only valid on this padded buffer.
3289 // When forwarding to vector.transfer_write, the attribute must be reset
3290 // conservatively.
3291 auto vector = xferOp.getVector();
3292 vector::TransferWriteOp::create(
3293 rewriter, xferOp.getLoc(), vector, out, xferOp.getIndices(),
3294 xferOp.getPermutationMapAttr(), xferOp.getMask(),
3295 rewriter.getBoolArrayAttr(SmallVector<bool>(
3296 dyn_cast<VectorType>(vector.getType()).getRank(), false)));
3297
3298 rewriter.eraseOp(copyOp);
3299 rewriter.eraseOp(xferOp);
3300
3301 return success();
3302}
3303
3304//===----------------------------------------------------------------------===//
3305// Convolution vectorization patterns
3306//===----------------------------------------------------------------------===//
3307
3308template <int N>
3309static void bindShapeDims(ShapedType shapedType) {}
3310
3311template <int N, typename IntTy, typename... IntTy2>
3312static void bindShapeDims(ShapedType shapedType, IntTy &val, IntTy2 &...vals) {
3313 val = shapedType.getShape()[N];
3314 bindShapeDims<N + 1, IntTy2 &...>(shapedType, vals...);
3315}
3316
3317/// Bind a pack of int& to the leading dimensions of shapedType.getShape().
3318template <typename... IntTy>
3319static void bindShapeDims(ShapedType shapedType, IntTy &...vals) {
3320 bindShapeDims<0>(shapedType, vals...);
3321}
3322
3323/// Match 1D convolution or pooling operations and return their dilations and
3324/// strides. Returns std::nullopt for unrecognized ops.
3325static std::optional<DilationsAndStrides> match1DConvPoolOp(LinalgOp op) {
3326#define MATCH_1D_CONV_POOL_OP(ConvOpTy) \
3327 if (auto convParams = matchConvolutionOpOfType<ConvOpTy>(op)) \
3328 return convParams;
3329
3330 // 1D Convolution ops.
3331 MATCH_1D_CONV_POOL_OP(linalg::Conv1DOp);
3332 MATCH_1D_CONV_POOL_OP(linalg::Conv1DNwcWcfOp);
3333 MATCH_1D_CONV_POOL_OP(linalg::Conv1DNcwFcwOp);
3334 // Depthwise 1D Convolution ops.
3335 // Note: Only NWC layout without channel multiplier is supported.
3336 // DepthwiseConv1DNcwCwOp (NCW) and DepthwiseConv1DNwcWcmOp (with multiplier)
3337 // are not supported.
3338 MATCH_1D_CONV_POOL_OP(linalg::DepthwiseConv1DNwcWcOp);
3339 // 1D Pooling ops (NWC layout).
3340 MATCH_1D_CONV_POOL_OP(linalg::PoolingNwcSumOp);
3341 MATCH_1D_CONV_POOL_OP(linalg::PoolingNwcMaxOp);
3342 MATCH_1D_CONV_POOL_OP(linalg::PoolingNwcMaxUnsignedOp);
3343 MATCH_1D_CONV_POOL_OP(linalg::PoolingNwcMinOp);
3344 MATCH_1D_CONV_POOL_OP(linalg::PoolingNwcMinUnsignedOp);
3345 // 1D Pooling ops (NCW layout).
3346 MATCH_1D_CONV_POOL_OP(linalg::PoolingNcwSumOp);
3347 MATCH_1D_CONV_POOL_OP(linalg::PoolingNcwMaxOp);
3348
3349#undef MATCH_1D_CONV_POOL_OP
3350
3351 return std::nullopt;
3352}
3353
3354namespace {
3355/// Generate a vector implementation for either:
3356/// ```
3357/// Op def: ( w, kw )
3358/// Iters: ({Par(), Red()})
3359/// Layout: {{w + kw}, {kw}, {w}}
3360/// ```
3361/// kw is unrolled.
3362///
3363/// or
3364///
3365/// ```
3366/// Op def: ( n, w, c, kw, f )
3367/// Iters: ({Par(), Par(), Par(), Red(), Red()})
3368/// Layout: {{n, strideW * w + dilationW * kw, c}, {kw, c, f}, {n, w, f}}
3369/// ```
3370/// kw is unrolled, w is unrolled iff dilationW > 1.
3371///
3372/// or
3373///
3374/// ```
3375/// Op def: ( n, c, w, f, kw )
3376/// Iters: ({Par(), Par(), Par(), Red(), Red()})
3377/// Layout: {{n, c, strideW * w + dilationW * kw}, {f, c, kw}, {n, f, w}}
3378/// ```
3379/// kw is unrolled, w is unrolled iff dilationW > 1.
3380///
3381/// or
3382///
3383/// ```
3384/// Op def: ( n, w, c, kw )
3385/// Iters: ({Par(), Par(), Par(), Red()})
3386/// Layout: {{n, strideW * w + dilationW * kw, c}, {kw, c}, {n, w, c}}
3387/// ```
3388/// kw is unrolled, w is unrolled iff dilationW > 1.
3389struct Conv1DGenerator
3390 : public StructuredGenerator<LinalgOp, utils::IteratorType> {
3391 /// Factory method to create a Conv1DGenerator. Returns failure if the
3392 /// operation doesn't have valid strides/dilations.
3393 static FailureOr<Conv1DGenerator> create(RewriterBase &rewriter,
3394 LinalgOp linalgOp) {
3395 // Try to match a 1D conv/pool op using matchConvolutionOpOfType. This
3396 // works for both named ops and generic ops that match their semantics.
3397 std::optional<DilationsAndStrides> convParams = match1DConvPoolOp(linalgOp);
3398 if (!convParams)
3399 return failure();
3400
3401 int strideW = static_cast<int>(convParams->strides.front());
3402 int dilationW = static_cast<int>(convParams->dilations.front());
3403 return Conv1DGenerator(rewriter, linalgOp, strideW, dilationW);
3404 }
3405
3406private:
3407 Conv1DGenerator(RewriterBase &rewriter, LinalgOp linalgOp, int strideW,
3408 int dilationW)
3409 : StructuredGenerator<LinalgOp, utils::IteratorType>(rewriter, linalgOp),
3410 strideW(strideW), dilationW(dilationW) {
3411
3412 lhsShaped = linalgOp.getDpsInputOperand(0)->get();
3413 rhsShaped = linalgOp.getDpsInputOperand(1)->get();
3414 resShaped = linalgOp.getDpsInitOperand(0)->get();
3415 lhsShapedType = dyn_cast<ShapedType>(lhsShaped.getType());
3416 rhsShapedType = dyn_cast<ShapedType>(rhsShaped.getType());
3417 resShapedType = dyn_cast<ShapedType>(resShaped.getType());
3418
3419 Operation *reduceOp = matchLinalgReduction(linalgOp.getDpsInitOperand(0));
3420 redOp = reduceOp->getName().getIdentifier();
3421
3422 setConvOperationKind(reduceOp);
3423
3424 auto maybeKind = getCombinerOpKind(reduceOp);
3425 reductionKind = maybeKind.value();
3426 }
3427
3428public:
3429 /// Generate a vector implementation for:
3430 /// ```
3431 /// Op def: ( w, kw )
3432 /// Iters: ({Par(), Red()})
3433 /// Layout: {{w + kw}, {kw}, {w}}
3434 /// ```
3435 /// kw is always unrolled.
3436 ///
3437 /// or
3438 ///
3439 /// ```
3440 /// Op def: ( n, w, c, kw, f )
3441 /// Iters: ({Par(), Par(), Par(), Red(), Red()})
3442 /// Layout: {{n, strideW * w + dilationW * kw, c}, {kw, c, f}, {n, w, f}}
3443 /// ```
3444 /// kw is always unrolled.
3445 /// TODO: w (resp. kw) is unrolled when the strideW ( resp. dilationW) is
3446 /// > 1.
3447 FailureOr<Operation *> conv(Conv1DOpOrder conv1DOpOrder) {
3448 int64_t nSize, wSize, cSize, kwSize, fSize;
3449 SmallVector<int64_t, 3> lhsShape, rhsShape, resShape;
3450 bool isSingleChanneled = (conv1DOpOrder == Conv1DOpOrder::W);
3451 switch (conv1DOpOrder) {
3452 case Conv1DOpOrder::W:
3453 // Initialize unused dimensions
3454 nSize = fSize = cSize = 0;
3455 // out{W}
3456 bindShapeDims(resShapedType, wSize);
3457 // kernel{kw}
3458 bindShapeDims(rhsShapedType, kwSize);
3459 lhsShape = {// iw = ow + kw - 1
3460 // (i.e. 16 convolved with 3 -> 14)
3461 (wSize + kwSize - 1)};
3462 rhsShape = {kwSize};
3463 resShape = {wSize};
3464 break;
3465 case Conv1DOpOrder::Nwc:
3466 // out{n, w, f}
3467 bindShapeDims(resShapedType, nSize, wSize, fSize);
3468 switch (oper) {
3469 case ConvOperationKind::Conv:
3470 // kernel{kw, c, f}
3471 bindShapeDims(rhsShapedType, kwSize, cSize);
3472 break;
3473 case ConvOperationKind::Pool:
3474 // kernel{kw}
3475 bindShapeDims(rhsShapedType, kwSize);
3476 cSize = fSize;
3477 break;
3478 }
3479 lhsShape = {nSize,
3480 // iw = ow * sw + kw * dw - 1
3481 // (i.e. 16 convolved with 3 (@stride 1 dilation 1) -> 14)
3482 // Perform the proper inclusive -> exclusive -> inclusive.
3483 ((wSize - 1) * strideW + 1) + ((kwSize - 1) * dilationW + 1) -
3484 1,
3485 cSize};
3486 switch (oper) {
3487 case ConvOperationKind::Conv:
3488 rhsShape = {kwSize, cSize, fSize};
3489 break;
3490 case ConvOperationKind::Pool:
3491 rhsShape = {kwSize};
3492 break;
3493 }
3494 resShape = {nSize, wSize, fSize};
3495 break;
3496 case Conv1DOpOrder::Ncw:
3497 // out{n, f, w}
3498 bindShapeDims(resShapedType, nSize, fSize, wSize);
3499 switch (oper) {
3500 case ConvOperationKind::Conv:
3501 // kernel{f, c, kw}
3502 bindShapeDims(rhsShapedType, fSize, cSize, kwSize);
3503 break;
3504 case ConvOperationKind::Pool:
3505 // kernel{kw}
3506 bindShapeDims(rhsShapedType, kwSize);
3507 cSize = fSize;
3508 break;
3509 }
3510 lhsShape = {nSize, cSize,
3511 // iw = ow * sw + kw * dw - 1
3512 // (i.e. 16 convolved with 3 (@stride 1 dilation 1) -> 14)
3513 // Perform the proper inclusive -> exclusive -> inclusive.
3514 ((wSize - 1) * strideW + 1) + ((kwSize - 1) * dilationW + 1) -
3515 1};
3516 switch (oper) {
3517 case ConvOperationKind::Conv:
3518 rhsShape = {fSize, cSize, kwSize};
3519 break;
3520 case ConvOperationKind::Pool:
3521 rhsShape = {kwSize};
3522 break;
3523 }
3524 resShape = {nSize, fSize, wSize};
3525 break;
3526 }
3527
3528 vector::TransferWriteOp write;
3529 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
3530
3531 // w is unrolled (i.e. wSizeStep == 1) iff strideW > 1.
3532 // When strideW == 1, we can batch the contiguous loads and avoid
3533 // unrolling
3534 int64_t wSizeStep = strideW == 1 ? wSize : 1;
3535
3536 Type lhsEltType = lhsShapedType.getElementType();
3537 Type rhsEltType = rhsShapedType.getElementType();
3538 Type resEltType = resShapedType.getElementType();
3539 auto lhsType = VectorType::get(lhsShape, lhsEltType);
3540 auto rhsType = VectorType::get(rhsShape, rhsEltType);
3541 auto resType = VectorType::get(resShape, resEltType);
3542 // Zero padding with the corresponding dimensions for lhs, rhs and res.
3543 SmallVector<Value> lhsPadding(lhsShape.size(), zero);
3544 SmallVector<Value> rhsPadding(rhsShape.size(), zero);
3545 SmallVector<Value> resPadding(resShape.size(), zero);
3546
3547 // Read the whole lhs, rhs and res in one shot (with zero padding).
3548 Value lhs = vector::TransferReadOp::create(
3549 rewriter, loc, lhsType, lhsShaped, lhsPadding,
3550 /*padding=*/arith::getZeroConstant(rewriter, loc, lhsEltType));
3551 // This is needed only for Conv.
3552 Value rhs = nullptr;
3553 if (oper == ConvOperationKind::Conv)
3554 rhs = vector::TransferReadOp::create(
3555 rewriter, loc, rhsType, rhsShaped, rhsPadding,
3556 /*padding=*/arith::getZeroConstant(rewriter, loc, rhsEltType));
3557 Value res = vector::TransferReadOp::create(
3558 rewriter, loc, resType, resShaped, resPadding,
3559 /*padding=*/arith::getZeroConstant(rewriter, loc, resEltType));
3560
3561 // The base vectorization case for channeled convolution is input:
3562 // {n,w,c}, weight: {kw,c,f}, output: {n,w,f}. To reuse the base pattern
3563 // vectorization case, we do pre transpose on input, weight, and output.
3564 switch (conv1DOpOrder) {
3565 case Conv1DOpOrder::W:
3566 case Conv1DOpOrder::Nwc:
3567 // Base case, so no transposes necessary.
3568 break;
3569 case Conv1DOpOrder::Ncw: {
3570 // To match base vectorization case, we pre-transpose current case.
3571 // ncw -> nwc
3572 static constexpr std::array<int64_t, 3> permLhs = {0, 2, 1};
3573 lhs = vector::TransposeOp::create(rewriter, loc, lhs, permLhs);
3574 // fcw -> wcf
3575 static constexpr std::array<int64_t, 3> permRhs = {2, 1, 0};
3576
3577 // This is needed only for Conv.
3578 if (oper == ConvOperationKind::Conv)
3579 rhs = vector::TransposeOp::create(rewriter, loc, rhs, permRhs);
3580 // nfw -> nwf
3581 static constexpr std::array<int64_t, 3> permRes = {0, 2, 1};
3582 res = vector::TransposeOp::create(rewriter, loc, res, permRes);
3583 break;
3584 }
3585 }
3586
3587 //===------------------------------------------------------------------===//
3588 // Begin vector-only rewrite part
3589 //===------------------------------------------------------------------===//
3590 // Unroll along kw and read slices of lhs and rhs.
3591 SmallVector<Value> lhsVals, rhsVals, resVals;
3592 lhsVals = extractConvInputSlices(rewriter, loc, lhs, nSize, wSize, cSize,
3593 kwSize, strideW, dilationW, wSizeStep,
3594 isSingleChanneled);
3595 // Do not do for pooling.
3596 if (oper == ConvOperationKind::Conv)
3597 rhsVals = extractConvFilterSlices(rewriter, loc, rhs, kwSize);
3598 resVals = extractConvResultSlices(rewriter, loc, res, nSize, wSize, fSize,
3599 wSizeStep, isSingleChanneled);
3600
3601 auto linearIndex = [&](int64_t kw, int64_t w) {
3602 return kw * (wSize / wSizeStep) + w;
3603 };
3604
3605 // Compute contraction: O{n, w, f} += I{n, sw * w + dw * kw, c} * F{c, f}
3606 // or perform outerproduct for non-channeled convolution or perform simple
3607 // arith operation for pooling
3608 for (int64_t kw = 0; kw < kwSize; ++kw) {
3609 for (int64_t w = 0; w < wSize; w += wSizeStep) {
3610 switch (oper) {
3611 case ConvOperationKind::Conv:
3612 if (isSingleChanneled) {
3613 resVals[w] = conv1dSliceAsOuterProduct(rewriter, loc,
3614 lhsVals[linearIndex(kw, w)],
3615 rhsVals[kw], resVals[w]);
3616 } else {
3617 resVals[w] = conv1dSliceAsContraction(rewriter, loc,
3618 lhsVals[linearIndex(kw, w)],
3619 rhsVals[kw], resVals[w]);
3620 }
3621 break;
3622 case ConvOperationKind::Pool:
3623 resVals[w] = pool1dSlice(rewriter, loc, lhsVals[linearIndex(kw, w)],
3624 resVals[w]);
3625 break;
3626 }
3627 }
3628 }
3629
3630 res = insertConvResultSlices(rewriter, loc, res, wSize, wSizeStep, resVals,
3631 isSingleChanneled);
3632 //===------------------------------------------------------------------===//
3633 // End vector-only rewrite part
3634 //===------------------------------------------------------------------===//
3635
3636 // The base vectorization case for channeled convolution is output:
3637 // {n,w,f} To reuse the result from base pattern vectorization case, we
3638 // post transpose the base case result.
3639 switch (conv1DOpOrder) {
3640 case Conv1DOpOrder::W:
3641 case Conv1DOpOrder::Nwc:
3642 // Base case, so no transposes necessary.
3643 break;
3644 case Conv1DOpOrder::Ncw: {
3645 // nwf -> nfw
3646 static constexpr std::array<int64_t, 3> perm = {0, 2, 1};
3647 res = vector::TransposeOp::create(rewriter, loc, res, perm);
3648 break;
3649 }
3650 }
3651
3652 return vector::TransferWriteOp::create(rewriter, loc, res, resShaped,
3653 resPadding)
3654 .getOperation();
3655 }
3656
3657 // Take a value and widen to have the same element type as `ty`.
3658 Value promote(RewriterBase &rewriter, Location loc, Value val, Type ty) {
3659 const Type srcElementType = getElementTypeOrSelf(val.getType());
3660 const Type dstElementType = getElementTypeOrSelf(ty);
3661 assert(isa<IntegerType>(dstElementType) || isa<FloatType>(dstElementType));
3662 if (srcElementType == dstElementType)
3663 return val;
3664
3665 const int64_t srcWidth = srcElementType.getIntOrFloatBitWidth();
3666 const int64_t dstWidth = dstElementType.getIntOrFloatBitWidth();
3667 // Handle both shaped as well as scalar types.
3668 Type dstType;
3669 if (auto shapedType = dyn_cast<ShapedType>(val.getType()))
3670 dstType = shapedType.cloneWith(std::nullopt, dstElementType);
3671 else
3672 dstType = dstElementType;
3673
3674 if (isa<IntegerType>(srcElementType) && isa<FloatType>(dstElementType)) {
3675 return arith::SIToFPOp::create(rewriter, loc, dstType, val);
3676 }
3677
3678 if (isa<FloatType>(srcElementType) && isa<FloatType>(dstElementType) &&
3679 srcWidth < dstWidth)
3680 return arith::ExtFOp::create(rewriter, loc, dstType, val);
3681
3682 if (isa<IntegerType>(srcElementType) && isa<IntegerType>(dstElementType) &&
3683 srcWidth < dstWidth)
3684 return arith::ExtSIOp::create(rewriter, loc, dstType, val);
3685
3686 assert(false && "unhandled promotion case");
3687 return nullptr;
3688 }
3689
3690 // Create a contraction: lhs{n, w, c} * rhs{c, f} -> res{n, w, f}
3691 Value conv1dSliceAsContraction(RewriterBase &rewriter, Location loc,
3692 Value lhs, Value rhs, Value res) {
3693 vector::IteratorType par = vector::IteratorType::parallel;
3694 vector::IteratorType red = vector::IteratorType::reduction;
3695 AffineExpr n, w, f, c;
3696 bindDims(ctx, n, w, f, c);
3697 lhs = promote(rewriter, loc, lhs, res.getType());
3698 rhs = promote(rewriter, loc, rhs, res.getType());
3699 auto contrationOp = vector::ContractionOp::create(
3700 rewriter, loc, lhs, rhs, res,
3701 /*indexingMaps=*/MapList{{n, w, c}, {c, f}, {n, w, f}},
3702 /*iteratorTypes=*/ArrayRef<vector::IteratorType>{par, par, par, red});
3703 contrationOp.setKind(reductionKind);
3704 return contrationOp;
3705 }
3706
3707 // Create an outerproduct: lhs{w} * rhs{1} -> res{w} for single channel
3708 // convolution.
3709 Value conv1dSliceAsOuterProduct(RewriterBase &rewriter, Location loc,
3710 Value lhs, Value rhs, Value res) {
3711 lhs = promote(rewriter, loc, lhs, res.getType());
3712 rhs = promote(rewriter, loc, rhs, res.getType());
3713 return vector::OuterProductOp::create(rewriter, loc, res.getType(), lhs,
3714 rhs, res, vector::CombiningKind::ADD);
3715 }
3716
3717 // Create a reduction: lhs{n, w, c} -> res{n, w, c}
3718 Value pool1dSlice(RewriterBase &rewriter, Location loc, Value lhs,
3719 Value res) {
3720 if (isPoolExt)
3721 lhs = rewriter.create(loc, poolExtOp, lhs, res.getType())->getResult(0);
3722 return rewriter
3723 .create(loc, redOp, ArrayRef<Value>{lhs, res}, res.getType())
3724 ->getResult(0);
3725 }
3726
3727 /// Generate a vector implementation for:
3728 /// ```
3729 /// Op def: ( n, w, c, kw)
3730 /// Iters: ({Par(), Par(), Par(), Red()})
3731 /// Layout: {{n, strideW * w + dilationW * kw, c}, {kw, c}, {n, w, c}}
3732 /// ```
3733 /// kw is always unrolled.
3734 /// TODO: w (resp. kw) is unrolled when the strideW ( resp. dilationW) is
3735 /// > 1.
3736 FailureOr<Operation *> depthwiseConv(uint64_t channelDimVecSize,
3737 bool channelDimScalableFlag,
3738 bool flatten) {
3739 bool scalableChDim = false;
3740 bool useMasking = false;
3741 int64_t nSize, wSize, cSize, kwSize;
3742 // kernel{kw, c}
3743 bindShapeDims(rhsShapedType, kwSize, cSize);
3744 if (ShapedType::isDynamic(cSize)) {
3745 assert(channelDimVecSize != 0 && "Channel dim vec size must be > 0");
3746 cSize = channelDimVecSize;
3747 // Scalable vectors are only used when both conditions are met:
3748 // 1. channel dim is dynamic
3749 // 2. channelDimScalableFlag is set
3750 scalableChDim = channelDimScalableFlag;
3751 useMasking = true;
3752 }
3753
3754 assert(!(useMasking && flatten) &&
3755 "Unsupported flattened conv with dynamic shapes");
3756
3757 // out{n, w, c}
3758 bindShapeDims(resShapedType, nSize, wSize);
3759
3760 vector::TransferWriteOp write;
3761 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
3762
3763 // w is unrolled (i.e. wSizeStep == 1) iff strideW > 1.
3764 // When strideW == 1, we can batch the contiguous loads and avoid
3765 // unrolling
3766 int64_t wSizeStep = strideW == 1 ? wSize : 1;
3767
3768 Type lhsEltType = lhsShapedType.getElementType();
3769 Type rhsEltType = rhsShapedType.getElementType();
3770 Type resEltType = resShapedType.getElementType();
3771 VectorType lhsType = VectorType::get(
3772 {nSize,
3773 // iw = ow * sw + kw * dw - 1
3774 // (i.e. 16 convolved with 3 (@stride 1 dilation 1) -> 14)
3775 ((wSize - 1) * strideW + 1) + ((kwSize - 1) * dilationW + 1) - 1,
3776 cSize},
3777 lhsEltType, /*scalableDims=*/{false, false, scalableChDim});
3778 VectorType rhsType =
3779 VectorType::get({kwSize, cSize}, rhsEltType,
3780 /*scalableDims=*/{false, scalableChDim});
3781 VectorType resType =
3782 VectorType::get({nSize, wSize, cSize}, resEltType,
3783 /*scalableDims=*/{false, false, scalableChDim});
3784
3785 // Masks the input xfer Op along the channel dim, iff the corresponding
3786 // scalable flag is set.
3787 auto maybeMaskXferOp = [&](ArrayRef<int64_t> maskShape,
3788 ArrayRef<bool> scalableDims,
3789 Operation *opToMask) {
3790 if (!useMasking)
3791 return opToMask;
3792 auto maskType =
3793 VectorType::get(maskShape, rewriter.getI1Type(), scalableDims);
3794
3795 SmallVector<bool> inBounds(maskShape.size(), true);
3796 auto xferOp = cast<VectorTransferOpInterface>(opToMask);
3797 xferOp->setAttr(xferOp.getInBoundsAttrName(),
3798 rewriter.getBoolArrayAttr(inBounds));
3799
3800 SmallVector<OpFoldResult> mixedDims = vector::getMixedSizesXfer(
3801 cast<LinalgOp>(op).hasPureTensorSemantics(), opToMask, rewriter);
3802
3803 Value maskOp =
3804 vector::CreateMaskOp::create(rewriter, loc, maskType, mixedDims);
3805
3806 return mlir::vector::maskOperation(rewriter, opToMask, maskOp);
3807 };
3808
3809 // Read lhs slice of size {n, w * strideW + kw * dilationW, c} @ [0, 0,
3810 // 0].
3811 Value lhs = vector::TransferReadOp::create(
3812 rewriter, loc, lhsType, lhsShaped, ValueRange{zero, zero, zero},
3813 /*padding=*/arith::getZeroConstant(rewriter, loc, lhsEltType));
3814 auto *maybeMaskedLhs = maybeMaskXferOp(
3815 lhsType.getShape(), lhsType.getScalableDims(), lhs.getDefiningOp());
3816
3817 // Read rhs slice of size {kw, c} @ [0, 0].
3818 Value rhs = vector::TransferReadOp::create(
3819 rewriter, loc, rhsType, rhsShaped, ValueRange{zero, zero},
3820 /*padding=*/arith::getZeroConstant(rewriter, loc, rhsEltType));
3821 auto *maybeMaskedRhs = maybeMaskXferOp(
3822 rhsType.getShape(), rhsType.getScalableDims(), rhs.getDefiningOp());
3823
3824 // Read res slice of size {n, w, c} @ [0, 0, 0].
3825 Value res = vector::TransferReadOp::create(
3826 rewriter, loc, resType, resShaped, ValueRange{zero, zero, zero},
3827 /*padding=*/arith::getZeroConstant(rewriter, loc, resEltType));
3828 auto *maybeMaskedRes = maybeMaskXferOp(
3829 resType.getShape(), resType.getScalableDims(), res.getDefiningOp());
3830
3831 //===------------------------------------------------------------------===//
3832 // Begin vector-only rewrite part
3833 //===------------------------------------------------------------------===//
3834 // Unroll along kw and read slices of lhs and rhs.
3835 SmallVector<Value> lhsVals, rhsVals, resVals;
3836 SmallVector<int64_t> inOutSliceSizes = {nSize, wSizeStep, cSize};
3837 SmallVector<int64_t> inOutStrides = {1, 1, 1};
3838
3839 // Extract lhs slice of size {n, wSizeStep, c}
3840 // @ [0, sw * w + dw * kw, 0].
3841 for (int64_t kw = 0; kw < kwSize; ++kw) {
3842 for (int64_t w = 0; w < wSize; w += wSizeStep) {
3843 lhsVals.push_back(vector::ExtractStridedSliceOp::create(
3844 rewriter, loc, maybeMaskedLhs->getResult(0),
3845 /*offsets=*/ArrayRef<int64_t>{0, w * strideW + kw * dilationW, 0},
3846 inOutSliceSizes, inOutStrides));
3847 }
3848 }
3849 // Extract rhs slice of size {c} @ [kw].
3850 for (int64_t kw = 0; kw < kwSize; ++kw) {
3851 rhsVals.push_back(
3852 vector::ExtractOp::create(rewriter, loc, maybeMaskedRhs->getResult(0),
3853 /*offsets=*/ArrayRef<int64_t>{kw}));
3854 }
3855 // Extract res slice: {n, wSizeStep, c} @ [0, w, 0].
3856 for (int64_t w = 0; w < wSize; w += wSizeStep) {
3857 resVals.push_back(vector::ExtractStridedSliceOp::create(
3858 rewriter, loc, maybeMaskedRes->getResult(0),
3859 /*offsets=*/ArrayRef<int64_t>{0, w, 0}, inOutSliceSizes,
3860 inOutStrides));
3861 }
3862
3863 auto linearIndex = [&](int64_t kw, int64_t w) {
3864 return kw * (wSize / wSizeStep) + w;
3865 };
3866
3867 // Note - the scalable flags are ignored as flattening combined with
3868 // scalable vectorization is not supported.
3869 SmallVector<int64_t> inOutFlattenSliceSizes = {nSize, wSizeStep * cSize};
3870 auto lhsTypeAfterFlattening =
3871 VectorType::get(inOutFlattenSliceSizes, lhsEltType);
3872 auto resTypeAfterFlattening =
3873 VectorType::get(inOutFlattenSliceSizes, resEltType);
3874
3875 // Compute contraction: O{n, w, c} += I{n, sw * w + dw * kw, c} * F{c}
3876 for (int64_t kw = 0; kw < kwSize; ++kw) {
3877 for (int64_t w = 0; w < wSize; w += wSizeStep) {
3878 Value lhsVal = lhsVals[linearIndex(kw, w)];
3879 Value resVal = resVals[w];
3880 if (flatten) {
3881 // Flatten the input and output vectors (collapse the channel
3882 // dimension)
3883 lhsVal =
3884 vector::ShapeCastOp::create(rewriter, loc, lhsTypeAfterFlattening,
3885 lhsVals[linearIndex(kw, w)]);
3886 resVal = vector::ShapeCastOp::create(
3887 rewriter, loc, resTypeAfterFlattening, resVals[w]);
3888 }
3889 resVals[w] = depthwiseConv1dSliceAsMulAcc(rewriter, loc, lhsVal,
3890 rhsVals[kw], resVal, flatten);
3891 if (flatten) {
3892 // Un-flatten the output vector (restore the channel dimension)
3893 resVals[w] = vector::ShapeCastOp::create(
3894 rewriter, loc, VectorType::get(inOutSliceSizes, resEltType),
3895 resVals[w]);
3896 }
3897 }
3898 }
3899
3900 // Its possible we failed to create the Fma.
3901 if (!llvm::all_of(resVals, [](Value v) { return v; })) {
3902 // Manually revert (in reverse order) to avoid leaving a bad IR state.
3903 for (auto &collection :
3904 {resVals, rhsVals, lhsVals, {res, rhs, lhs, zero}})
3905 for (Value v : collection)
3906 rewriter.eraseOp(v.getDefiningOp());
3907 return rewriter.notifyMatchFailure(op, "failed to create FMA");
3908 }
3909
3910 // Write back res slice: {n, wSizeStep, c} @ [0, w, 0].
3911 // This does not depend on kw.
3912 for (int64_t w = 0; w < wSize; w += wSizeStep) {
3913 maybeMaskedRes = vector::InsertStridedSliceOp::create(
3914 rewriter, loc, resVals[w], maybeMaskedRes->getResult(0),
3915 /*offsets=*/ArrayRef<int64_t>{0, w, 0},
3916 /*strides=*/ArrayRef<int64_t>{1, 1, 1});
3917 }
3918 //===------------------------------------------------------------------===//
3919 // End vector-only rewrite part
3920 //===------------------------------------------------------------------===//
3921
3922 // Write back res slice of size {n, w, c} @ [0, 0, 0].
3923 Operation *resOut = vector::TransferWriteOp::create(
3924 rewriter, loc, maybeMaskedRes->getResult(0), resShaped,
3925 ValueRange{zero, zero, zero});
3926 return maybeMaskXferOp(resType.getShape(), resType.getScalableDims(),
3927 resOut);
3928 }
3929
3930 /// Lower:
3931 /// * lhs{n, w, c} * rhs{c} -> res{n, w, c} (flatten = false)
3932 /// * lhs{n, w * c} * rhs{c} -> res{n, w * c} (flatten = true)
3933 /// to MulAcc.
3934 Value depthwiseConv1dSliceAsMulAcc(RewriterBase &rewriter, Location loc,
3935 Value lhs, Value rhs, Value res,
3936 bool flatten) {
3937 auto rhsTy = cast<ShapedType>(rhs.getType());
3938 auto resTy = cast<ShapedType>(res.getType());
3939
3940 // TODO(suderman): Change this to use a vector.ima intrinsic.
3941 lhs = promote(rewriter, loc, lhs, resTy);
3942
3943 if (flatten) {
3944 // NOTE: This following logic won't work for scalable vectors. For this
3945 // reason, "flattening" is not supported when shapes are dynamic (this
3946 // should be captured by one of the pre-conditions).
3947
3948 // There are two options for handling the filter:
3949 // * shape_cast(broadcast(filter))
3950 // * broadcast(shuffle(filter))
3951 // Opt for the option without shape_cast to simplify the codegen.
3952 auto rhsSize = cast<VectorType>(rhs.getType()).getShape()[0];
3953 auto resSize = cast<VectorType>(res.getType()).getShape()[1];
3954
3955 SmallVector<int64_t, 16> indices;
3956 for (int i = 0; i < resSize / rhsSize; ++i) {
3957 for (int j = 0; j < rhsSize; ++j)
3958 indices.push_back(j);
3959 }
3960
3961 rhs = vector::ShuffleOp::create(rewriter, loc, rhs, rhs, indices);
3962 }
3963 // Broadcast the filter to match the output vector
3964 rhs = vector::BroadcastOp::create(rewriter, loc,
3965 resTy.clone(rhsTy.getElementType()), rhs);
3966
3967 rhs = promote(rewriter, loc, rhs, resTy);
3968
3969 if (!lhs || !rhs)
3970 return nullptr;
3971
3972 if (isa<FloatType>(resTy.getElementType()))
3973 return vector::FMAOp::create(rewriter, loc, lhs, rhs, res);
3974
3975 auto mul = arith::MulIOp::create(rewriter, loc, lhs, rhs);
3976 return arith::AddIOp::create(rewriter, loc, mul, res);
3977 }
3978
3979 /// Entry point for non-channeled convolution:
3980 /// {{w + kw}, {kw}, {w}}
3981 FailureOr<Operation *> generateNonChanneledConv() {
3982 AffineExpr w, kw;
3983 bindDims(ctx, w, kw);
3984 if (!iters({Par(), Red()}))
3985 return rewriter.notifyMatchFailure(op,
3986 "failed to match conv::W 1-par 1-red");
3987
3988 // No transposition needed.
3989 if (layout({/*lhsIndex*/ {w + kw},
3990 /*rhsIndex*/ {kw},
3991 /*resIndex*/ {w}}))
3992 return conv(Conv1DOpOrder::W);
3993
3994 return rewriter.notifyMatchFailure(op, "not a conv::W layout");
3995 }
3996
3997 /// Entry point that transposes into the common form:
3998 /// {{n, strideW * w + dilationW * kw, c}, {kw, c, f}, {n, w, f}}
3999 FailureOr<Operation *> generateNwcConv() {
4000 AffineExpr n, w, f, kw, c;
4001 bindDims(ctx, n, w, f, kw, c);
4002 if (!iters({Par(), Par(), Par(), Red(), Red()}))
4003 return rewriter.notifyMatchFailure(
4004 op, "failed to match conv::Nwc 3-par 2-red");
4005
4006 // No transposition needed.
4007 if (layout({/*lhsIndex*/ {n, strideW * w + dilationW * kw, c},
4008 /*rhsIndex*/ {kw, c, f},
4009 /*resIndex*/ {n, w, f}}))
4010 return conv(Conv1DOpOrder::Nwc);
4011
4012 return rewriter.notifyMatchFailure(op, "not a conv::Nwc layout");
4013 }
4014
4015 /// Entry point that transposes into the common form:
4016 /// {{n, c, strideW * w + dilationW * kw}, {f, c, kw}, {n, f, w}}
4017 FailureOr<Operation *> generateNcwConv() {
4018 AffineExpr n, w, f, kw, c;
4019 bindDims(ctx, n, f, w, c, kw);
4020 if (!iters({Par(), Par(), Par(), Red(), Red()}))
4021 return rewriter.notifyMatchFailure(
4022 op, "failed to match conv::Ncw 3-par 2-red");
4023
4024 if (layout({/*lhsIndex*/ {n, c, strideW * w + dilationW * kw},
4025 /*rhsIndex*/ {f, c, kw},
4026 /*resIndex*/ {n, f, w}}))
4027 return conv(Conv1DOpOrder::Ncw);
4028
4029 return rewriter.notifyMatchFailure(op, "not a conv::Ncw layout");
4030 }
4031
4032 /// Entry point that transposes into the common form:
4033 /// {{n, strideW * w + dilationW * kw, c}, {kw}, {n, w, c}} for pooling
4034 FailureOr<Operation *> generateNwcPooling() {
4035 AffineExpr n, w, c, kw;
4036 bindDims(ctx, n, w, c, kw);
4037 if (!iters({Par(), Par(), Par(), Red()}))
4038 return rewriter.notifyMatchFailure(op,
4039 "failed to match pooling 3-par 1-red");
4040
4041 // No transposition needed.
4042 if (layout({/*lhsIndex*/ {n, strideW * w + dilationW * kw, c},
4043 /*rhsIndex*/ {kw},
4044 /*resIndex*/ {n, w, c}}))
4045 return conv(Conv1DOpOrder::Nwc);
4046
4047 return rewriter.notifyMatchFailure(op, "not a pooling::Nwc layout");
4048 }
4049
4050 /// Entry point that transposes into the common form:
4051 /// {{n, c, strideW * w + dilationW * kw}, {kw}, {n, c, w}} for pooling
4052 FailureOr<Operation *> generateNcwPooling() {
4053 AffineExpr n, w, c, kw;
4054 bindDims(ctx, n, c, w, kw);
4055 if (!iters({Par(), Par(), Par(), Red()}))
4056 return rewriter.notifyMatchFailure(op,
4057 "failed to match pooling 3-par 1-red");
4058
4059 if (layout({/*lhsIndex*/ {n, c, strideW * w + dilationW * kw},
4060 /*rhsIndex*/ {kw},
4061 /*resIndex*/ {n, c, w}}))
4062 return conv(Conv1DOpOrder::Ncw);
4063
4064 return rewriter.notifyMatchFailure(op, "not a pooling::Ncw layout");
4065 }
4066
4067 /// Entry point that transposes into the common form:
4068 /// {{n, strideW * w + dilationW * kw, c}, {kw, c}, {n, w, c}}
4069 FailureOr<Operation *> generateDilatedConv(uint64_t vecChDimSize = 0,
4070 bool vecChDimScalableFlag = false,
4071 bool flatten = false) {
4072 AffineExpr n, w, c, kw;
4073 bindDims(ctx, n, w, c, kw);
4074 if (!iters({Par(), Par(), Par(), Red()}))
4075 return rewriter.notifyMatchFailure(
4076 op, "failed to match depthwise::Nwc conv 3-par 1-red");
4077
4078 // No transposition needed.
4079 if (layout({/*lhsIndex*/ {n, strideW * w + dilationW * kw, c},
4080 /*rhsIndex*/ {kw, c},
4081 /*resIndex*/ {n, w, c}}))
4082 return depthwiseConv(vecChDimSize, vecChDimScalableFlag, flatten);
4083
4084 return rewriter.notifyMatchFailure(op, "not a depthwise::Nwc layout");
4085 }
4086
4087private:
4088 ConvOperationKind oper = ConvOperationKind::Conv;
4089 StringAttr redOp;
4090 StringAttr poolExtOp;
4091 bool isPoolExt = false;
4092 int strideW, dilationW;
4093 Value lhsShaped, rhsShaped, resShaped;
4094 ShapedType lhsShapedType, rhsShapedType, resShapedType;
4095 vector::CombiningKind reductionKind;
4096
4097 // Sets oper, poolExtOp and isPoolExt for valid conv/pooling ops.
4098 void setConvOperationKind(Operation *reduceOp) {
4099 int numBlockArguments =
4100 llvm::count_if(reduceOp->getOperands(), llvm::IsaPred<BlockArgument>);
4101 if (numBlockArguments == 1) {
4102 // Will be convolution if feeder is a MulOp.
4103 // A strength reduced version of MulOp for i1 type is AndOp which is also
4104 // supported. Otherwise, it can be pooling. This strength reduction logic
4105 // is in `buildBinaryFn` helper in the Linalg dialect.
4106 auto feedValIt = llvm::find_if_not(reduceOp->getOperands(),
4107 llvm::IsaPred<BlockArgument>);
4108 Operation *feedOp = (*feedValIt).getDefiningOp();
4109 if (isCastOfBlockArgument(feedOp)) {
4110 oper = ConvOperationKind::Pool;
4111 isPoolExt = true;
4112 poolExtOp = feedOp->getName().getIdentifier();
4113 return;
4114 }
4115 oper = ConvOperationKind::Conv;
4116 return;
4117 }
4118 // numBlockArugments == 2 and this is a pooling op.
4119 oper = ConvOperationKind::Pool;
4120 isPoolExt = false;
4121 }
4122};
4123} // namespace
4124
4125/// Helper function to vectorize a LinalgOp with convolution semantics.
4126// TODO: extend the generic vectorization to support windows and drop this.
4127static FailureOr<Operation *> vectorizeConvolution(
4128 RewriterBase &rewriter, LinalgOp op, ArrayRef<int64_t> inputVecSizes,
4129 ArrayRef<bool> inputScalableVecDims, bool flatten1DDepthwiseConv) {
4130 FailureOr<Conv1DGenerator> conv1dGen = Conv1DGenerator::create(rewriter, op);
4131 if (failed(conv1dGen))
4132 return failure();
4133 auto res = conv1dGen->generateNonChanneledConv();
4134 if (succeeded(res))
4135 return res;
4136 res = conv1dGen->generateNwcConv();
4137 if (succeeded(res))
4138 return res;
4139 res = conv1dGen->generateNcwConv();
4140 if (succeeded(res))
4141 return res;
4142 res = conv1dGen->generateNwcPooling();
4143 if (succeeded(res))
4144 return res;
4145 res = conv1dGen->generateNcwPooling();
4146 if (succeeded(res))
4147 return res;
4148
4149 // Only depthwise 1D NWC convs are left - these can be vectorized using masks
4150 // and scalable vectors. Note that ATM the only dim that can be dynamic (i.e.
4151 // masked/scalable) is the channel dim (i.e. the trailing dim).
4152 uint64_t vecChDimSize = ShapedType::kDynamic;
4153 bool vecChDimScalableFlag = false;
4154 if (!inputVecSizes.empty()) {
4155 // Only use the input vector size corresponding to the channel dim. Other
4156 // vector dims will be inferred from the Ops.
4159 "Not a 1D depthwise conv!");
4160 size_t chDimIdx = 0;
4162 chDimIdx = 2;
4164 chDimIdx = 1;
4165
4166 vecChDimSize = inputVecSizes[chDimIdx];
4167 vecChDimScalableFlag = inputScalableVecDims[chDimIdx];
4168 }
4169 return conv1dGen->generateDilatedConv(vecChDimSize, vecChDimScalableFlag,
4170 flatten1DDepthwiseConv);
4171}
4172
4173struct VectorizeConvolution : public OpInterfaceRewritePattern<LinalgOp> {
4175
4176 LogicalResult matchAndRewrite(LinalgOp op,
4177 PatternRewriter &rewriter) const override {
4178 FailureOr<Operation *> resultOrFail = vectorizeConvolution(rewriter, op);
4179 if (failed(resultOrFail))
4180 return failure();
4181 Operation *newOp = *resultOrFail;
4182 if (newOp->getNumResults() == 0) {
4183 rewriter.eraseOp(op.getOperation());
4184 return success();
4185 }
4186 assert(newOp->getNumResults() == 1 && "expected single result");
4187 rewriter.replaceOp(op.getOperation(), newOp->getResult(0));
4188 return success();
4189 }
4190};
4191
4193 RewritePatternSet &patterns, PatternBenefit benefit) {
4194 patterns.add<VectorizeConvolution>(patterns.getContext(), benefit);
4195}
return success()
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static std::optional< VectorShape > vectorShape(Type type)
static bool isLoopInvariantIdx(LinalgOp &linalgOp, Value &val, VectorType resType)
Checks whether val can be used for calculating a loop invariant index.
static Value insertConvResultSlices(RewriterBase &rewriter, Location loc, Value res, int64_t wSize, int64_t wSizeStep, SmallVectorImpl< Value > &resVals, bool isSingleChanneled)
Helper function to insert the computed result slices.
static SmallVector< bool > getDimsToReduce(LinalgOp linalgOp)
static VectorMemoryAccessKind getTensorExtractMemoryAccessPattern(tensor::ExtractOp extractOp, LinalgOp &linalgOp, VectorType resType)
Infer the memory access pattern for the input ExtractOp.
static SmallVector< Value > extractConvInputSlices(RewriterBase &rewriter, Location loc, Value input, int64_t nSize, int64_t wSize, int64_t cSize, int64_t kwSize, int strideW, int dilationW, int64_t wSizeStep, bool isSingleChanneled)
Helper function to extract the input slices after filter is unrolled along kw.
VectorMemoryAccessKind
@ Contiguous
@ Gather
@ ScalarBroadcast
static VectorizationHookResult vectorizeTensorExtract(RewriterBase &rewriter, VectorizationState &state, Operation *op, LinalgOp linalgOp, const IRMapping &bvm)
Helper function to vectorize the tensor.extract operations.
static VectorizationHookResult vectorizeLinalgIndex(RewriterBase &rewriter, VectorizationState &state, Operation *op, LinalgOp linalgOp)
Helper function to vectorize the index operations of a linalgOp.
static LogicalResult vectorizeAsInsertSliceOp(RewriterBase &rewriter, tensor::InsertSliceOp sliceOp, ArrayRef< int64_t > inputVectorSizes, SmallVectorImpl< Value > &newResults)
Vectorize tensor::InsertSliceOp with:
static FailureOr< Operation * > vectorizeConvolution(RewriterBase &rewriter, LinalgOp convOp, ArrayRef< int64_t > inputVecSizes={}, ArrayRef< bool > inputVecScalableFlags={}, bool flatten1DDepthwiseConv=false)
Try to vectorize convOp as a convolution.
static LogicalResult vectorizeAsLinalgGeneric(RewriterBase &rewriter, VectorizationState &state, LinalgOp linalgOp, SmallVectorImpl< Value > &newResults)
Generic vectorization function that rewrites the body of a linalgOp into vector form.
#define MATCH_1D_CONV_POOL_OP(ConvOpTy)
static VectorizationHookResult vectorizeOneOp(RewriterBase &rewriter, VectorizationState &state, LinalgOp linalgOp, Operation *op, const IRMapping &bvm, ArrayRef< CustomVectorizationHook > customVectorizationHooks)
Generic vectorization for a single operation op, given already vectorized operands carried by bvm.
static Operation * matchLinalgReduction(OpOperand *outputOperand)
Check whether outputOperand is a reduction with a single combiner operation.
static Value buildVectorWrite(RewriterBase &rewriter, Value value, OpOperand *outputOperand, VectorizationState &state)
Build a vector.transfer_write of value into outputOperand at indices set to all 0; where outputOperan...
static Value getStaticPadVal(Operation *op)
Returns the effective Pad value for the input op, provided it's a scalar.
static SmallVector< Value > extractConvFilterSlices(RewriterBase &rewriter, Location loc, Value filter, int64_t kwSize)
Helper function to extract the filter slices after filter is unrolled along kw.
static bool hasReductionIterator(LinalgOp &op)
Check if op is a linalg.reduce or a linalg.generic that has at least one reduction iterator.
std::function< LogicalResult(Operation *, bool)> CustomVectorizationPrecondition
static uint64_t getTrailingNonUnitLoopDimIdx(LinalgOp linalgOp)
Find the index of the trailing non-unit dim in linalgOp.
static VectorType getCollapsedVecType(VectorType type, ArrayRef< AffineMap > reassociation)
Given the re-associations, "collapses" the input Vector type.
Conv1DOpOrder
Helper enum to represent conv1d input traversal order.
VectorizationHookStatus
Helper data structure to represent the result of vectorization for a single operation.
@ Failure
Op failed to vectorize.
@ NewOp
Op vectorized into a new Op whose results will replace original Op's results.
@ NoReplace
Op vectorized and custom function took care of replacement logic.
static Operation * reduceIfNeeded(OpBuilder &b, LinalgOp linalgOp, Operation *op, Value reduceValue, Value initialValue, const IRMapping &bvm)
Emit reduction operations if the shapes of the value to reduce is different that the result shape.
static OpType getSingleOpOfType(Block &block)
Return the unique instance of OpType in block if it is indeed unique.
std::function< VectorizationHookResult(Operation *, const IRMapping &)> CustomVectorizationHook
static AffineMap reindexIndexingMap(AffineMap map)
Given an indexing map coming from a LinalgOp indexing, restricted to a projectedPermutation,...
static LogicalResult tensorExtractVectorizationPrecondition(Operation *op, bool vectorizeNDExtract)
Helper function to check if the tensor.extract can be vectorized by the custom hook vectorizeTensorEx...
static Value broadcastIfNeeded(OpBuilder &b, Value value, Type dstType)
Broadcast value to a vector of shape if possible.
static Value calculateGatherOffset(RewriterBase &rewriter, VectorizationState &state, tensor::ExtractOp extractOp, const IRMapping &bvm)
Calculates the offsets ($index_vec) for vector.gather operations generated from tensor....
static SmallVector< Value > extractConvResultSlices(RewriterBase &rewriter, Location loc, Value res, int64_t nSize, int64_t wSize, int64_t fSize, int64_t wSizeStep, bool isSingleChanneled)
Helper function to extract the result slices after filter is unrolled along kw.
static bool isContiguousLoadIdx(LinalgOp &linalgOp, Value &val, bool &foundIndexOp, VectorType resType)
Check whether val could be used for calculating the trailing index for a contiguous load operation.
static VectorizationHookResult vectorizeLinalgYield(RewriterBase &rewriter, Operation *op, const IRMapping &bvm, VectorizationState &state, LinalgOp linalgOp, SmallVectorImpl< Value > &newResults)
Helper function to vectorize the terminator of a linalgOp.
static Operation * buildMultiDimReduce(OpBuilder &b, Operation *reduceOp, Value valueToReduce, Value acc, ArrayRef< bool > dimsToMask)
Create MultiDimReductionOp to compute the reduction for reductionOp.
#define mul(a, b)
A dimensional identifier appearing in an affine expression.
Definition AffineExpr.h:223
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap getMinorIdentityMap(unsigned dims, unsigned results, MLIRContext *context)
Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most minor dimensions.
MLIRContext * getContext() const
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumResults() const
unsigned getNumInputs() const
AffineExpr getResult(unsigned idx) const
static AffineMap getFilteredIdentityMap(MLIRContext *ctx, unsigned numDims, llvm::function_ref< bool(AffineDimExpr)> keepDimFilter)
Returns an identity affine map with numDims input dimensions and filtered results using keepDimFilter...
AffineMap dropZeroResults()
Returns the AffineMap resulting from removing "zero" results (constant values == 0) from this map.
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
SmallVector< unsigned > getBroadcastDims() const
Returns the list of broadcast dimensions (i.e.
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
bool isPermutation() const
Returns true if the AffineMap represents a symbol-less permutation map.
This class represents an argument of a Block.
Definition Value.h:306
unsigned getArgNumber() const
Returns the number of this argument.
Definition Value.h:318
Block represents an ordered list of Operations.
Definition Block.h:33
OpListType & getOperations()
Definition Block.h:161
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Definition Block.h:332
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:392
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:329
IntegerType getI1Type()
Definition Builders.cpp:57
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:271
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:55
ArrayAttr getBoolArrayAttr(ArrayRef< bool > values)
Definition Builders.cpp:275
static DenseIntElementsAttr get(const ShapedType &type, Arg &&arg)
Get an instance of a DenseIntElementsAttr with the given arguments.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookup(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:72
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
IRValueT get() const
Return the current value being used by this operand.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class helps build Operations.
Definition Builders.h:209
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:567
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:462
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
StringAttr getIdentifier() const
Return the name of this operation as a StringAttr.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
Definition Operation.h:537
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
unsigned getNumOperands()
Definition Operation.h:371
operand_iterator operand_end()
Definition Operation.h:400
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
unsigned short getBenefit() const
If the corresponding pattern can match, return its benefit. If the.
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.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIndex() const
Definition Types.cpp:56
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:384
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
bool hasVectorizationImpl(Operation *)
Return true if there's dedicated logic in the Linalg Vectorizer to vectorize this Op,...
SmallVector< int64_t > getUnPackInverseSrcPerm(linalg::UnPackOp, PackingMetadata &metadata)
Compute inverse permutation for the source tensor (i.e.
bool allIndexingsAreProjectedPermutation(LinalgOp op)
Check if all indexing maps are projected permutations.
Definition Utils.cpp:197
FailureOr< VectorizationResult > vectorize(RewriterBase &rewriter, Operation *op, ArrayRef< int64_t > inputVectorSizes={}, ArrayRef< bool > inputScalableVecDims={}, bool vectorizeNDExtract=false, bool flatten1DDepthwiseConv=false, bool assumeDynamicDimsMatchVecSizes=false, bool createNamedContraction=false)
Returns a VectorizationResult containing the results of the vectorized op, or failure if the transfor...
void populatePadOpVectorizationPatterns(RewritePatternSet &patterns, PatternBenefit baseBenefit=1)
Populates patterns with patterns that vectorize tensor.pad.
bool isReductionIterator(utils::IteratorType iteratorType)
Check if iterator type has "reduction" semantics.
Definition Utils.cpp:236
bool isaConvolutionOpInterface(LinalgOp linalgOp, bool allowEmptyConvolvedDims=false)
Checks whether linalgOp conforms to ConvolutionOpInterface.
void populateConvolutionVectorizationPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Populate patterns for vectorizing low-D convolution ops.
bool isElementwise(LinalgOp op)
Check if a LinalgOp is an element-wise operation.
Definition Utils.cpp:217
LogicalResult vectorizeCopy(RewriterBase &builder, memref::CopyOp copyOp)
Emit a suitable vector form for a Copy op with fully static shape.
LogicalResult vectorizeOpPrecondition(Operation *op, ArrayRef< int64_t > inputVectorSizes={}, ArrayRef< bool > inputScalableVecDims={}, bool vectorizeNDExtract=false, bool flatten1DDepthwiseConv=false)
Return success if the operation can be vectorized.
SmallVector< int64_t > getPackInverseDestPerm(linalg::PackOp packOp, PackingMetadata &metadata)
Compute inverse permutation for the destination tensor (i.e.
bool isaConvolutionOpOfType(LinalgOp op)
Returns true if the linalg op is a convolution op of type ConvOpTy.
Definition Utils.h:126
std::optional< vector::CombiningKind > getCombinerOpKind(Operation *combinerOp)
Return vector::CombiningKind for the given op.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
void promote(RewriterBase &rewriter, scf::ForallOp forallOp)
Promotes the loop body of a scf::ForallOp to its containing block.
Definition SCF.cpp:748
std::enable_if_t<!is_complex< V >::value, V > readValue(char **linePtr)
Returns an element-value of non-complex type.
Definition File.h:47
Operation * maskOperation(OpBuilder &builder, Operation *maskableOp, Value mask, Value passthru=Value())
Creates a vector.mask operation around a maskable operation.
LogicalResult isValidMaskedInputVector(ArrayRef< int64_t > shape, ArrayRef< int64_t > inputVectorSizes)
Returns success if inputVectorSizes is a valid masking configuraion for given shape,...
BroadcastableToResult isBroadcastableTo(Type srcType, VectorType dstVectorType, std::pair< VectorDim, VectorDim > *mismatchingDims=nullptr)
Operation * createWriteOrMaskedWrite(OpBuilder &builder, Location loc, Value vecToStore, Value dest, SmallVector< Value > writeIndices={}, bool useInBoundsInsteadOfMasking=false, AffineMap permutationMap=AffineMap())
Create a TransferWriteOp of vecToStore into dest.
Value createReadOrMaskedRead(OpBuilder &builder, Location loc, Value source, const VectorType &vecToReadTy, std::optional< Value > padValue=std::nullopt, bool useInBoundsInsteadOfMasking=false, ArrayRef< Value > indices={}, AffineMap permutationMap=AffineMap())
Creates a TransferReadOp from source.
SmallVector< OpFoldResult > getMixedSizesXfer(bool hasTensorSemantics, Operation *xfer, RewriterBase &rewriter)
A wrapper for getMixedSizes for vector.transfer_read and vector.transfer_write Ops (for source and de...
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2)
Return true if ofr1 and ofr2 are the same integer constant attribute values or the same SSA value.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
AffineMap inverseAndBroadcastProjectedPermutation(AffineMap map)
Return the reverse map of a projected permutation where the projected dimensions are transformed into...
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
SmallVector< AffineMap, 4 > getSymbolLessAffineMaps(ArrayRef< ReassociationExprs > reassociation)
Constructs affine maps out of Array<Array<AffineExpr>>.
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
Value matchReduction(ArrayRef< BlockArgument > iterCarriedArgs, unsigned redPos, SmallVectorImpl< Operation * > &combinerOps)
Utility to match a generic reduction given a list of iteration-carried arguments, iterCarriedArgs and...
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:494
void getUsedValuesDefinedAbove(Region &region, Region &limit, SetVector< Value > &values)
Fill values with a list of values defined at the ancestors of the limit region and used within region...
AffineMap compressUnusedDims(AffineMap map)
Drop the dims that are not used.
SmallVector< SmallVector< AffineExpr, 2 >, 2 > convertReassociationIndicesToExprs(MLIRContext *context, ArrayRef< ReassociationIndices > reassociationIndices)
Convert reassociation indices to affine expressions.
bool isReassociationValid(ArrayRef< AffineMap > reassociation, int *invalidIndex=nullptr)
Return true if the reassociation specification is valid, false otherwise.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
SmallVector< T > applyPermutationMap(AffineMap map, llvm::ArrayRef< T > source)
Apply a permutation from map to source and return the result.
Definition AffineMap.h:675
llvm::SmallBitVector getUnusedDimsBitVector(ArrayRef< AffineMap > maps)
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
VectorizationHookResult contains the vectorized op returned from a CustomVectorizationHook.
enum VectorizationHookStatus status
Return status from vectorizing the current op.
Operation * newOp
New vectorized operation to replace the current op.
ArrayRef< int64_t > getCanonicalVecShape() const
Returns the canonical vector shape used to vectorize the iteration space.
LogicalResult initState(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< int64_t > inputVectorSizes, ArrayRef< bool > inputScalableVecDims, bool assumeDynamicDimsMatchVecSizes=false)
Initializes the vectorization state, including the computation of the canonical vector shape for vect...
Operation * maskOperation(RewriterBase &rewriter, Operation *opToMask, LinalgOp linalgOp, std::optional< AffineMap > maybeIndexingMap=std::nullopt)
Masks an operation with the canonical vector mask if the operation needs masking.
VectorType getCanonicalVecType(Type elementType, std::optional< AffineMap > dimPermutation=std::nullopt) const
Returns a vector type of the provided elementType with the canonical vector shape and the correspondi...
ArrayRef< bool > getScalableVecDims() const
Returns the vector dimensions that are scalable in the canonical vector shape.
VectorizationState(RewriterBase &rewriter)
OpInterfaceRewritePattern(MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(vector::TransferReadOp xferOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::TransferWriteOp xferOp, PatternRewriter &rewriter) const override