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