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