MLIR 24.0.0git
VectorTransferSplitRewritePatterns.cpp
Go to the documentation of this file.
1//===- VectorTransferSplitRewritePatterns.cpp - Transfer Split Rewrites ---===//
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 target-independent patterns to rewrite a vector.transfer
10// op into a fully in-bounds part and a partial part.
11//
12//===----------------------------------------------------------------------===//
13
14#include <optional>
15
21#include "llvm/ADT/SmallVectorExtras.h"
22
26
27#include "llvm/ADT/STLExtras.h"
28#include "llvm/ADT/SmallVectorExtras.h"
29
30#define DEBUG_TYPE "vector-transfer-split"
31
32using namespace mlir;
33using namespace mlir::vector;
34
35/// Build the condition to ensure that a particular VectorTransferOpInterface
36/// is in-bounds.
38 VectorTransferOpInterface xferOp) {
39 assert(xferOp.getPermutationMap().isMinorIdentity() &&
40 "Expected minor identity map");
41 Value inBoundsCond;
42 xferOp.zipResultAndIndexing([&](int64_t resultIdx, int64_t indicesIdx) {
43 // Zip over the resulting vector shape and memref indices.
44 // If the dimension is known to be in-bounds, it does not participate in
45 // the construction of `inBoundsCond`.
46 if (xferOp.isDimInBounds(resultIdx))
47 return;
48 // Fold or create the check that `index + vector_size` <= `memref_size`.
49 Location loc = xferOp.getLoc();
50 int64_t vectorSize = xferOp.getVectorType().getDimSize(resultIdx);
52 b, loc, b.getAffineDimExpr(0) + b.getAffineConstantExpr(vectorSize),
53 {xferOp.getIndices()[indicesIdx]});
54 OpFoldResult dimSz =
55 memref::getMixedSize(b, loc, xferOp.getBase(), indicesIdx);
56 auto maybeCstSum = getConstantIntValue(sum);
57 auto maybeCstDimSz = getConstantIntValue(dimSz);
58 if (maybeCstSum && maybeCstDimSz && *maybeCstSum <= *maybeCstDimSz)
59 return;
60 Value cond =
61 arith::CmpIOp::create(b, loc, arith::CmpIPredicate::sle,
64 // Conjunction over all dims for which we are in-bounds.
65 if (inBoundsCond)
66 inBoundsCond = arith::AndIOp::create(b, loc, inBoundsCond, cond);
67 else
68 inBoundsCond = cond;
69 });
70 return inBoundsCond;
71}
72
73/// Split a vector.transfer operation into an in-bounds (i.e., no out-of-bounds
74/// masking) fast path and a slow path.
75/// If `ifOp` is not null and the result is `success, the `ifOp` points to the
76/// newly created conditional upon function return.
77/// To accommodate for the fact that the original vector.transfer indexing may
78/// be arbitrary and the slow path indexes @[0...0] in the temporary buffer, the
79/// scf.if op returns a view and values of type index.
80/// At this time, only vector.transfer_read case is implemented.
81///
82/// Example (a 2-D vector.transfer_read):
83/// ```
84/// %1 = vector.transfer_read %0[...], %pad : memref<A...>, vector<...>
85/// ```
86/// is transformed into:
87/// ```
88/// %1:3 = scf.if (%inBounds) {
89/// // fast path, direct cast
90/// memref.cast %A: memref<A...> to compatibleMemRefType
91/// scf.yield %view : compatibleMemRefType, index, index
92/// } else {
93/// // slow path, not in-bounds vector.transfer or memref.copy.
94/// memref.cast %alloc: memref<B...> to compatibleMemRefType
95/// scf.yield %4 : compatibleMemRefType, index, index
96// }
97/// %0 = vector.transfer_read %1#0[%1#1, %1#2] {in_bounds = [true ... true]}
98/// ```
99/// where `alloc` is a top of the function alloca'ed buffer of one vector.
100///
101/// Preconditions:
102/// 1. `xferOp.getPermutationMap()` must be a minor identity map
103/// 2. the rank of the `xferOp.memref()` and the rank of the
104/// `xferOp.getVector()` must be equal. This will be relaxed in the future
105/// but requires rank-reducing subviews.
106static LogicalResult
107splitFullAndPartialTransferPrecondition(VectorTransferOpInterface xferOp) {
108 // TODO: support 0-d corner case.
109 if (xferOp.getTransferRank() == 0)
110 return failure();
111
112 // TODO: expand support to these 2 cases.
113 if (!xferOp.getPermutationMap().isMinorIdentity())
114 return failure();
115 // Must have some out-of-bounds dimension to be a candidate for splitting.
116 if (!xferOp.hasOutOfBoundsDim())
117 return failure();
118 // Don't split transfer operations directly under IfOp, this avoids applying
119 // the pattern recursively.
120 // TODO: improve the filtering condition to make it more applicable.
121 if (isa<scf::IfOp>(xferOp->getParentOp()))
122 return failure();
123 return success();
124}
125
126/// Given two MemRefTypes `aT` and `bT`, return a MemRefType to which both can
127/// be cast. If the MemRefTypes don't have the same rank or are not strided,
128/// return null; otherwise:
129/// 1. if `aT` and `bT` are cast-compatible, return `aT`.
130/// 2. else return a new MemRefType obtained by iterating over the shape and
131/// strides and:
132/// a. keeping the ones that are static and equal across `aT` and `bT`.
133/// b. using a dynamic shape and/or stride for the dimensions that don't
134/// agree.
135static MemRefType getCastCompatibleMemRefType(MemRefType aT, MemRefType bT) {
136 if (memref::CastOp::areCastCompatible(aT, bT))
137 return aT;
138 if (aT.getRank() != bT.getRank())
139 return MemRefType();
140 int64_t aOffset, bOffset;
141 SmallVector<int64_t, 4> aStrides, bStrides;
142 if (failed(aT.getStridesAndOffset(aStrides, aOffset)) ||
143 failed(bT.getStridesAndOffset(bStrides, bOffset)) ||
144 aStrides.size() != bStrides.size())
145 return MemRefType();
146
147 ArrayRef<int64_t> aShape = aT.getShape(), bShape = bT.getShape();
148 int64_t resOffset;
149 SmallVector<int64_t, 4> resShape(aT.getRank(), 0),
150 resStrides(bT.getRank(), 0);
151 for (int64_t idx = 0, e = aT.getRank(); idx < e; ++idx) {
152 resShape[idx] =
153 (aShape[idx] == bShape[idx]) ? aShape[idx] : ShapedType::kDynamic;
154 resStrides[idx] =
155 (aStrides[idx] == bStrides[idx]) ? aStrides[idx] : ShapedType::kDynamic;
156 }
157 resOffset = (aOffset == bOffset) ? aOffset : ShapedType::kDynamic;
158 return MemRefType::get(
159 resShape, aT.getElementType(),
160 StridedLayoutAttr::get(aT.getContext(), resOffset, resStrides));
161}
162
163/// Casts the given memref to a compatible memref type. If the source memref has
164/// a different address space than the target type, a `memref.memory_space_cast`
165/// is first inserted, followed by a `memref.cast`.
166static Value castToCompatibleMemRefType(OpBuilder &b, Value memref,
167 MemRefType compatibleMemRefType) {
168 MemRefType sourceType = cast<MemRefType>(memref.getType());
169 Value res = memref;
170 if (sourceType.getMemorySpace() != compatibleMemRefType.getMemorySpace()) {
171 sourceType = MemRefType::get(
172 sourceType.getShape(), sourceType.getElementType(),
173 sourceType.getLayout(), compatibleMemRefType.getMemorySpace());
174 res =
175 memref::MemorySpaceCastOp::create(b, memref.getLoc(), sourceType, res);
176 }
177 if (sourceType == compatibleMemRefType)
178 return res;
179 return memref::CastOp::create(b, memref.getLoc(), compatibleMemRefType, res);
180}
181
182/// Operates under a scoped context to build the intersection between the
183/// view `xferOp.getbase()` @ `xferOp.getIndices()` and the view `alloc`.
184// TODO: view intersection/union/differences should be a proper std op.
185static std::pair<Value, Value>
186createSubViewIntersection(RewriterBase &b, VectorTransferOpInterface xferOp,
187 Value alloc) {
188 Location loc = xferOp.getLoc();
189 int64_t memrefRank = xferOp.getShapedType().getRank();
190 // TODO: relax this precondition, will require rank-reducing subviews.
191 assert(memrefRank == cast<MemRefType>(alloc.getType()).getRank() &&
192 "Expected memref rank to match the alloc rank");
193 ValueRange leadingIndices =
194 xferOp.getIndices().take_front(xferOp.getLeadingShapedRank());
196 sizes.append(leadingIndices.begin(), leadingIndices.end());
197 auto isaWrite = isa<vector::TransferWriteOp>(xferOp);
198 xferOp.zipResultAndIndexing([&](int64_t resultIdx, int64_t indicesIdx) {
199 using MapList = ArrayRef<ArrayRef<AffineExpr>>;
200 Value dimMemRef =
201 memref::DimOp::create(b, xferOp.getLoc(), xferOp.getBase(), indicesIdx);
202 Value dimAlloc = memref::DimOp::create(b, loc, alloc, resultIdx);
203 Value index = xferOp.getIndices()[indicesIdx];
204 AffineExpr i, j, k;
205 bindDims(xferOp.getContext(), i, j, k);
207 AffineMap::inferFromExprList(MapList{{i - j, k}}, b.getContext());
208 // affine_min(%dimMemRef - %index, %dimAlloc)
209 Value affineMin =
210 affine::AffineMinOp::create(b, loc, index.getType(), maps[0],
211 ValueRange{dimMemRef, index, dimAlloc});
212 sizes.push_back(affineMin);
213 });
214
215 SmallVector<OpFoldResult> srcIndices = llvm::map_to_vector<4>(
216 xferOp.getIndices(), [](Value idx) -> OpFoldResult { return idx; });
217 SmallVector<OpFoldResult> destIndices(memrefRank, b.getIndexAttr(0));
218 SmallVector<OpFoldResult> strides(memrefRank, b.getIndexAttr(1));
219 auto copySrc = memref::SubViewOp::create(
220 b, loc, isaWrite ? alloc : xferOp.getBase(), srcIndices, sizes, strides);
221 auto copyDest = memref::SubViewOp::create(
222 b, loc, isaWrite ? xferOp.getBase() : alloc, destIndices, sizes, strides);
223 return std::make_pair(copySrc, copyDest);
224}
225
226/// Given an `xferOp` for which:
227/// 1. `inBoundsCond` and a `compatibleMemRefType` have been computed.
228/// 2. a memref of single vector `alloc` has been allocated.
229/// Produce IR resembling:
230/// ```
231/// %1:3 = scf.if (%inBounds) {
232/// (memref.memory_space_cast %A: memref<A..., addr_space> to memref<A...>)
233/// %view = memref.cast %A: memref<A...> to compatibleMemRefType
234/// scf.yield %view, ... : compatibleMemRefType, index, index
235/// } else {
236/// %2 = vector.broadcast %pad : elt to vector<...>
237/// %3 = vector.type_cast %alloc : memref<B...> to memref<vector<...>>
238/// memref.store %2, %3[] : memref<vector<...>>
239/// %4 = subview %view [...][...][...]
240/// %5 = subview %alloc [0, 0] [...] [...]
241/// memref.copy %4, %5
242/// %6 = memref.cast %alloc: memref<B...> to compatibleMemRefType
243/// scf.yield %6, ... : compatibleMemRefType, index, index
244/// }
245/// ```
246/// Return the produced scf::IfOp.
247static scf::IfOp
248createFullPartialMemRefCopy(RewriterBase &b, vector::TransferReadOp xferOp,
249 TypeRange returnTypes, Value inBoundsCond,
250 MemRefType compatibleMemRefType, Value alloc) {
251 Location loc = xferOp.getLoc();
252 Value zero = arith::ConstantIndexOp::create(b, loc, 0);
253 Value memref = xferOp.getBase();
254 return scf::IfOp::create(
255 b, loc, inBoundsCond,
256 [&](OpBuilder &b, Location loc) {
257 Value res = castToCompatibleMemRefType(b, memref, compatibleMemRefType);
258 scf::ValueVector viewAndIndices{res};
259 llvm::append_range(viewAndIndices, xferOp.getIndices());
260 scf::YieldOp::create(b, loc, viewAndIndices);
261 },
262 [&](OpBuilder &b, Location loc) {
263 // Fill the whole buffer with the padding value. The buffer holds
264 // exactly one vector, so a single vector store covers it.
265 Value padding = vector::BroadcastOp::create(
266 b, loc, xferOp.getVectorType(), xferOp.getPadding());
267 memref::StoreOp::create(
268 b, loc, padding,
269 vector::TypeCastOp::create(
270 b, loc, MemRefType::get({}, padding.getType()), alloc));
271 // Take partial subview of memref which guarantees no dimension
272 // overflows.
273 IRRewriter rewriter(b);
274 std::pair<Value, Value> copyArgs = createSubViewIntersection(
275 rewriter, cast<VectorTransferOpInterface>(xferOp.getOperation()),
276 alloc);
277 memref::CopyOp::create(b, loc, copyArgs.first, copyArgs.second);
278 Value casted =
279 castToCompatibleMemRefType(b, alloc, compatibleMemRefType);
280 scf::ValueVector viewAndIndices{casted};
281 viewAndIndices.insert(viewAndIndices.end(), xferOp.getTransferRank(),
282 zero);
283 scf::YieldOp::create(b, loc, viewAndIndices);
284 });
285}
286
287/// Given an `xferOp` for which:
288/// 1. `inBoundsCond` and a `compatibleMemRefType` have been computed.
289/// 2. a memref of single vector `alloc` has been allocated.
290/// Produce IR resembling:
291/// ```
292/// %1:3 = scf.if (%inBounds) {
293/// (memref.memory_space_cast %A: memref<A..., addr_space> to memref<A...>)
294/// memref.cast %A: memref<A...> to compatibleMemRefType
295/// scf.yield %view, ... : compatibleMemRefType, index, index
296/// } else {
297/// %2 = vector.transfer_read %view[...], %pad : memref<A...>, vector<...>
298/// %3 = vector.type_cast %extra_alloc :
299/// memref<...> to memref<vector<...>>
300/// store %2, %3[] : memref<vector<...>>
301/// %4 = memref.cast %alloc: memref<B...> to compatibleMemRefType
302/// scf.yield %4, ... : compatibleMemRefType, index, index
303/// }
304/// ```
305/// Return the produced scf::IfOp.
306static scf::IfOp createFullPartialVectorTransferRead(
307 RewriterBase &b, vector::TransferReadOp xferOp, TypeRange returnTypes,
308 Value inBoundsCond, MemRefType compatibleMemRefType, Value alloc) {
309 Location loc = xferOp.getLoc();
310 scf::IfOp fullPartialIfOp;
311 Value zero = arith::ConstantIndexOp::create(b, loc, 0);
312 Value memref = xferOp.getBase();
313 return scf::IfOp::create(
314 b, loc, inBoundsCond,
315 [&](OpBuilder &b, Location loc) {
316 Value res = castToCompatibleMemRefType(b, memref, compatibleMemRefType);
317 scf::ValueVector viewAndIndices{res};
318 llvm::append_range(viewAndIndices, xferOp.getIndices());
319 scf::YieldOp::create(b, loc, viewAndIndices);
320 },
321 [&](OpBuilder &b, Location loc) {
322 Operation *newXfer = b.clone(*xferOp.getOperation());
323 Value vector = cast<VectorTransferOpInterface>(newXfer).getVector();
324 memref::StoreOp::create(
325 b, loc, vector,
326 vector::TypeCastOp::create(
327 b, loc, MemRefType::get({}, vector.getType()), alloc));
328
329 Value casted =
330 castToCompatibleMemRefType(b, alloc, compatibleMemRefType);
331 scf::ValueVector viewAndIndices{casted};
332 viewAndIndices.insert(viewAndIndices.end(), xferOp.getTransferRank(),
333 zero);
334 scf::YieldOp::create(b, loc, viewAndIndices);
335 });
336}
337
338/// Given an `xferOp` for which:
339/// 1. `inBoundsCond` and a `compatibleMemRefType` have been computed.
340/// 2. a memref of single vector `alloc` has been allocated.
341/// Produce IR resembling:
342/// ```
343/// %1:3 = scf.if (%inBounds) {
344/// memref.cast %A: memref<A...> to compatibleMemRefType
345/// scf.yield %view, ... : compatibleMemRefType, index, index
346/// } else {
347/// %3 = vector.type_cast %extra_alloc :
348/// memref<...> to memref<vector<...>>
349/// %4 = memref.cast %alloc: memref<B...> to compatibleMemRefType
350/// scf.yield %4, ... : compatibleMemRefType, index, index
351/// }
352/// ```
353static ValueRange
354getLocationToWriteFullVec(RewriterBase &b, vector::TransferWriteOp xferOp,
355 TypeRange returnTypes, Value inBoundsCond,
356 MemRefType compatibleMemRefType, Value alloc) {
357 Location loc = xferOp.getLoc();
358 Value zero = arith::ConstantIndexOp::create(b, loc, 0);
359 Value memref = xferOp.getBase();
360 return scf::IfOp::create(
361 b, loc, inBoundsCond,
362 [&](OpBuilder &b, Location loc) {
363 Value res =
364 castToCompatibleMemRefType(b, memref, compatibleMemRefType);
365 scf::ValueVector viewAndIndices{res};
366 llvm::append_range(viewAndIndices, xferOp.getIndices());
367 scf::YieldOp::create(b, loc, viewAndIndices);
368 },
369 [&](OpBuilder &b, Location loc) {
370 Value casted =
371 castToCompatibleMemRefType(b, alloc, compatibleMemRefType);
372 scf::ValueVector viewAndIndices{casted};
373 viewAndIndices.insert(viewAndIndices.end(),
374 xferOp.getTransferRank(), zero);
375 scf::YieldOp::create(b, loc, viewAndIndices);
376 })
377 ->getResults();
378}
379
380/// Given an `xferOp` for which:
381/// 1. `inBoundsCond` has been computed.
382/// 2. a memref of single vector `alloc` has been allocated.
383/// 3. it originally wrote to %view
384/// Produce IR resembling:
385/// ```
386/// %notInBounds = arith.xori %inBounds, %true
387/// scf.if (%notInBounds) {
388/// %3 = subview %alloc [...][...][...]
389/// %4 = subview %view [0, 0][...][...]
390/// memref.copy %3, %4
391/// }
392/// ```
393static void createFullPartialMemRefCopy(RewriterBase &b,
394 vector::TransferWriteOp xferOp,
395 Value inBoundsCond, Value alloc) {
396 Location loc = xferOp.getLoc();
397 auto notInBounds = arith::XOrIOp::create(
398 b, loc, inBoundsCond, arith::ConstantIntOp::create(b, loc, true, 1));
399 scf::IfOp::create(b, loc, notInBounds, [&](OpBuilder &b, Location loc) {
400 IRRewriter rewriter(b);
401 std::pair<Value, Value> copyArgs = createSubViewIntersection(
402 rewriter, cast<VectorTransferOpInterface>(xferOp.getOperation()),
403 alloc);
404 memref::CopyOp::create(b, loc, copyArgs.first, copyArgs.second);
405 scf::YieldOp::create(b, loc, ValueRange{});
406 });
407}
408
409/// Given an `xferOp` for which:
410/// 1. `inBoundsCond` has been computed.
411/// 2. a memref of single vector `alloc` has been allocated.
412/// 3. it originally wrote to %view
413/// Produce IR resembling:
414/// ```
415/// %notInBounds = arith.xori %inBounds, %true
416/// scf.if (%notInBounds) {
417/// %2 = load %alloc : memref<vector<...>>
418/// vector.transfer_write %2, %view[...] : memref<A...>, vector<...>
419/// }
420/// ```
421static void createFullPartialVectorTransferWrite(RewriterBase &b,
422 vector::TransferWriteOp xferOp,
423 Value inBoundsCond,
424 Value alloc) {
425 Location loc = xferOp.getLoc();
426 auto notInBounds = arith::XOrIOp::create(
427 b, loc, inBoundsCond, arith::ConstantIntOp::create(b, loc, true, 1));
428 scf::IfOp::create(b, loc, notInBounds, [&](OpBuilder &b, Location loc) {
429 IRMapping mapping;
430 Value load = memref::LoadOp::create(
431 b, loc,
432 vector::TypeCastOp::create(
433 b, loc, MemRefType::get({}, xferOp.getVector().getType()), alloc),
434 ValueRange());
435 mapping.map(xferOp.getVector(), load);
436 b.clone(*xferOp.getOperation(), mapping);
437 scf::YieldOp::create(b, loc, ValueRange{});
438 });
439}
440
441// TODO: Parallelism and threadlocal considerations with a ParallelScope trait.
442static Operation *getAutomaticAllocationScope(Operation *op) {
443 // Find the closest surrounding allocation scope that is not a known looping
444 // construct (putting alloca's in loops doesn't always lower to deallocation
445 // until the end of the loop).
446 Operation *scope = nullptr;
447 for (Operation *parent = op->getParentOp(); parent != nullptr;
448 parent = parent->getParentOp()) {
449 if (parent->hasTrait<OpTrait::AutomaticAllocationScope>())
450 scope = parent;
451 if (!isa<scf::ForOp, affine::AffineForOp>(parent))
452 break;
453 }
454 assert(scope && "Expected op to be inside automatic allocation scope");
455 return scope;
456}
457
458/// Split a vector.transfer operation into an in-bounds (i.e., no out-of-bounds
459/// masking) fastpath and a slowpath.
460///
461/// For vector.transfer_read:
462/// If `ifOp` is not null and the result is `success, the `ifOp` points to the
463/// newly created conditional upon function return.
464/// To accomodate for the fact that the original vector.transfer indexing may be
465/// arbitrary and the slow path indexes @[0...0] in the temporary buffer, the
466/// scf.if op returns a view and values of type index.
467///
468/// Example (a 2-D vector.transfer_read):
469/// ```
470/// %1 = vector.transfer_read %0[...], %pad : memref<A...>, vector<...>
471/// ```
472/// is transformed into:
473/// ```
474/// %1:3 = scf.if (%inBounds) {
475/// // fastpath, direct cast
476/// memref.cast %A: memref<A...> to compatibleMemRefType
477/// scf.yield %view : compatibleMemRefType, index, index
478/// } else {
479/// // slowpath, not in-bounds vector.transfer or memref.copy.
480/// memref.cast %alloc: memref<B...> to compatibleMemRefType
481/// scf.yield %4 : compatibleMemRefType, index, index
482// }
483/// %0 = vector.transfer_read %1#0[%1#1, %1#2] {in_bounds = [true ... true]}
484/// ```
485/// where `alloc` is a top of the function alloca'ed buffer of one vector.
486///
487/// For vector.transfer_write:
488/// There are 2 conditional blocks. First a block to decide which memref and
489/// indices to use for an unmasked, inbounds write. Then a conditional block to
490/// further copy a partial buffer into the final result in the slow path case.
491///
492/// Example (a 2-D vector.transfer_write):
493/// ```
494/// vector.transfer_write %arg, %0[...], %pad : memref<A...>, vector<...>
495/// ```
496/// is transformed into:
497/// ```
498/// %1:3 = scf.if (%inBounds) {
499/// memref.cast %A: memref<A...> to compatibleMemRefType
500/// scf.yield %view : compatibleMemRefType, index, index
501/// } else {
502/// memref.cast %alloc: memref<B...> to compatibleMemRefType
503/// scf.yield %4 : compatibleMemRefType, index, index
504/// }
505/// %0 = vector.transfer_write %arg, %1#0[%1#1, %1#2] {in_bounds = [true ...
506/// true]}
507/// scf.if (%notInBounds) {
508/// // slowpath: not in-bounds vector.transfer or memref.copy.
509/// }
510/// ```
511/// where `alloc` is a top of the function alloca'ed buffer of one vector.
512///
513/// Preconditions:
514/// 1. `xferOp.getPermutationMap()` must be a minor identity map
515/// 2. the rank of the `xferOp.getBase()` and the rank of the
516/// `xferOp.getVector()` must be equal. This will be relaxed in the future
517/// but requires rank-reducing subviews.
518LogicalResult mlir::vector::splitFullAndPartialTransfer(
519 RewriterBase &b, VectorTransferOpInterface xferOp,
520 VectorTransformsOptions options, scf::IfOp *ifOp) {
521 if (options.vectorTransferSplit == VectorTransferSplit::None)
522 return failure();
523
524 SmallVector<bool, 4> bools(xferOp.getTransferRank(), true);
525 auto inBoundsAttr = b.getBoolArrayAttr(bools);
526 if (options.vectorTransferSplit == VectorTransferSplit::ForceInBounds) {
527 b.modifyOpInPlace(xferOp, [&]() {
528 xferOp->setInherentAttr(b.getStringAttr(xferOp.getInBoundsAttrName()),
529 inBoundsAttr);
530 });
531 return success();
532 }
533
534 // Assert preconditions. Additionally, keep the variables in an inner scope to
535 // ensure they aren't used in the wrong scopes further down.
536 {
537 assert(succeeded(splitFullAndPartialTransferPrecondition(xferOp)) &&
538 "Expected splitFullAndPartialTransferPrecondition to hold");
539
540 auto xferReadOp = dyn_cast<vector::TransferReadOp>(xferOp.getOperation());
541 auto xferWriteOp = dyn_cast<vector::TransferWriteOp>(xferOp.getOperation());
542
543 if (!(xferReadOp || xferWriteOp))
544 return failure();
545 if (xferWriteOp && xferWriteOp.getMask())
546 return failure();
547 if (xferReadOp && xferReadOp.getMask())
548 return failure();
549 }
550
552 b.setInsertionPoint(xferOp);
553 Value inBoundsCond = createInBoundsCond(
554 b, cast<VectorTransferOpInterface>(xferOp.getOperation()));
555 if (!inBoundsCond)
556 return failure();
557
558 // Top of the function `alloc` for transient storage.
559 Value alloc;
560 {
562 Operation *scope = getAutomaticAllocationScope(xferOp);
563 assert(scope->getNumRegions() == 1 &&
564 "AutomaticAllocationScope with >1 regions");
565 b.setInsertionPointToStart(&scope->getRegion(0).front());
566 auto shape = xferOp.getVectorType().getShape();
567 Type elementType = xferOp.getVectorType().getElementType();
568 alloc = memref::AllocaOp::create(b, scope->getLoc(),
569 MemRefType::get(shape, elementType),
570 ValueRange{}, b.getI64IntegerAttr(32));
571 }
572
573 MemRefType compatibleMemRefType =
574 getCastCompatibleMemRefType(cast<MemRefType>(xferOp.getShapedType()),
575 cast<MemRefType>(alloc.getType()));
576 if (!compatibleMemRefType)
577 return failure();
578
579 SmallVector<Type, 4> returnTypes(1 + xferOp.getTransferRank(),
580 b.getIndexType());
581 returnTypes[0] = compatibleMemRefType;
582
583 if (auto xferReadOp =
584 dyn_cast<vector::TransferReadOp>(xferOp.getOperation())) {
585 // Read case: full fill + partial copy -> in-bounds vector.xfer_read.
586 scf::IfOp fullPartialIfOp =
587 options.vectorTransferSplit == VectorTransferSplit::VectorTransfer
588 ? createFullPartialVectorTransferRead(b, xferReadOp, returnTypes,
589 inBoundsCond,
590 compatibleMemRefType, alloc)
591 : createFullPartialMemRefCopy(b, xferReadOp, returnTypes,
592 inBoundsCond, compatibleMemRefType,
593 alloc);
594 if (ifOp)
595 *ifOp = fullPartialIfOp;
596
597 // Set existing read op to in-bounds, it always reads from a full buffer.
598 for (unsigned i = 0, e = returnTypes.size(); i != e; ++i)
599 xferReadOp.setOperand(i, fullPartialIfOp.getResult(i));
600
601 b.modifyOpInPlace(xferOp, [&]() {
602 xferOp->setInherentAttr(b.getStringAttr(xferOp.getInBoundsAttrName()),
603 inBoundsAttr);
604 });
605
606 return success();
607 }
608
609 auto xferWriteOp = cast<vector::TransferWriteOp>(xferOp.getOperation());
610
611 // Decide which location to write the entire vector to.
612 auto memrefAndIndices = getLocationToWriteFullVec(
613 b, xferWriteOp, returnTypes, inBoundsCond, compatibleMemRefType, alloc);
614
615 // Do an in bounds write to either the output or the extra allocated buffer.
616 // The operation is cloned to prevent deleting information needed for the
617 // later IR creation.
618 IRMapping mapping;
619 mapping.map(xferWriteOp.getBase(), memrefAndIndices.front());
620 mapping.map(xferWriteOp.getIndices(), memrefAndIndices.drop_front());
621 auto *clone = b.clone(*xferWriteOp, mapping);
622 clone->setInherentAttr(xferWriteOp.getInBoundsAttrName(), inBoundsAttr);
623
624 // Create a potential copy from the allocated buffer to the final output in
625 // the slow path case.
626 if (options.vectorTransferSplit == VectorTransferSplit::VectorTransfer)
627 createFullPartialVectorTransferWrite(b, xferWriteOp, inBoundsCond, alloc);
628 else
629 createFullPartialMemRefCopy(b, xferWriteOp, inBoundsCond, alloc);
630
631 b.eraseOp(xferOp);
632
633 return success();
634}
635
636namespace {
637/// Apply `splitFullAndPartialTransfer` selectively via a pattern. This pattern
638/// may take an extra filter to perform selection at a finer granularity.
639struct VectorTransferFullPartialRewriter : public RewritePattern {
640 using FilterConstraintType =
641 std::function<LogicalResult(VectorTransferOpInterface op)>;
642
643 explicit VectorTransferFullPartialRewriter(
644 MLIRContext *context,
645 VectorTransformsOptions options = VectorTransformsOptions(),
646 FilterConstraintType filter =
647 [](VectorTransferOpInterface op) { return success(); },
648 PatternBenefit benefit = 1)
649 : RewritePattern(MatchAnyOpTypeTag(), benefit, context), options(options),
650 filter(std::move(filter)) {}
651
652 /// Performs the rewrite.
653 LogicalResult matchAndRewrite(Operation *op,
654 PatternRewriter &rewriter) const override;
655
656private:
657 VectorTransformsOptions options;
658 FilterConstraintType filter;
659};
660
661} // namespace
662
663LogicalResult VectorTransferFullPartialRewriter::matchAndRewrite(
664 Operation *op, PatternRewriter &rewriter) const {
665 auto xferOp = dyn_cast<VectorTransferOpInterface>(op);
666 if (!xferOp || failed(splitFullAndPartialTransferPrecondition(xferOp)) ||
667 failed(filter(xferOp)))
668 return failure();
669 return splitFullAndPartialTransfer(rewriter, xferOp, options);
670}
671
674 patterns.add<VectorTransferFullPartialRewriter>(patterns.getContext(),
675 options);
676}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
auto load
static llvm::ManagedStatic< PassManagerOptions > options
static Value createInBoundsCond(RewriterBase &b, VectorTransferOpInterface xferOp)
Build the condition to ensure that a particular VectorTransferOpInterface is in-bounds.
Base type for affine expression.
Definition AffineExpr.h:68
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
This class represents a single result from folding an operation.
A trait of region holding operations that define a new scope for automatic allocations,...
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:740
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:728
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Block & front()
Definition Region.h:65
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
RewritePattern is the common base class for all DAG to DAG replacements.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
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
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:333
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given memref value.
Definition MemRefOps.cpp:71
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:744
SmallVector< Value > ValueVector
An owning vector of values, handy to return from functions.
Definition SCF.h:66
void populateVectorTransferFullPartialPatterns(RewritePatternSet &patterns, const VectorTransformsOptions &options)
Populate patterns with the following patterns.
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:313
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
Structure to control the behavior of vector transform patterns.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.