MLIR 24.0.0git
VectorDistribute.cpp
Go to the documentation of this file.
1//===- VectorDistribute.cpp - patterns to do vector distribution ----------===//
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
18#include "mlir/IR/AffineExpr.h"
19#include "mlir/IR/Attributes.h"
23#include "llvm/ADT/SetVector.h"
24#include "llvm/ADT/SmallBitVector.h"
25#include "llvm/ADT/SmallVectorExtras.h"
26#include "llvm/Support/FormatVariadic.h"
27#include <utility>
28
29using namespace mlir;
30using namespace mlir::vector;
31using namespace mlir::gpu;
32
33/// Currently the distribution map is implicit based on the vector shape. In the
34/// future it will be part of the op.
35/// Example:
36/// ```
37/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1x16x2xf32>) {
38/// ...
39/// gpu.yield %3 : vector<32x16x64xf32>
40/// }
41/// ```
42/// Would have an implicit map of:
43/// `(d0, d1, d2) -> (d0, d2)`
44static AffineMap calculateImplicitMap(VectorType sequentialType,
45 VectorType distributedType) {
47 perm.reserve(1);
48 // Check which dimensions of the sequential type are different than the
49 // dimensions of the distributed type to know the distributed dimensions. Then
50 // associate each distributed dimension to an ID in order.
51 for (unsigned i = 0, e = sequentialType.getRank(); i < e; i++) {
52 if (sequentialType.getDimSize(i) != distributedType.getDimSize(i))
53 perm.push_back(getAffineDimExpr(i, distributedType.getContext()));
54 }
55 auto map = AffineMap::get(sequentialType.getRank(), 0, perm,
56 distributedType.getContext());
57 return map;
58}
59
60/// Given a sequential and distributed vector type, returns the distributed
61/// dimension. This function expects that only a single dimension is
62/// distributed.
63static int getDistributedDim(VectorType sequentialType,
64 VectorType distributedType) {
65 assert(sequentialType.getRank() == distributedType.getRank() &&
66 "sequential and distributed vector types must have the same rank");
67 int64_t distributedDim = -1;
68 for (int64_t i = 0; i < sequentialType.getRank(); ++i) {
69 if (distributedType.getDimSize(i) != sequentialType.getDimSize(i)) {
70 // Keep this assert here in case WarpExecuteOnLane0Op gets extended to
71 // support distributing multiple dimensions in the future.
72 assert(distributedDim == -1 && "found multiple distributed dims");
73 distributedDim = i;
74 }
75 }
76 return distributedDim;
77}
78
79namespace {
80
81/// Helper struct to create the load / store operations that permit transit
82/// through the parallel / sequential and the sequential / parallel boundaries
83/// when performing `rewriteWarpOpToScfFor`.
84///
85/// The vector distribution dimension is inferred from the vector types.
86struct DistributedLoadStoreHelper {
87 DistributedLoadStoreHelper(Value sequentialVal, Value distributedVal,
88 Value laneId, Value zero)
89 : sequentialVal(sequentialVal), distributedVal(distributedVal),
90 laneId(laneId), zero(zero) {
91 sequentialVectorType = dyn_cast<VectorType>(sequentialVal.getType());
92 distributedVectorType = dyn_cast<VectorType>(distributedVal.getType());
93 if (sequentialVectorType && distributedVectorType)
94 distributionMap =
95 calculateImplicitMap(sequentialVectorType, distributedVectorType);
96 }
97
98 Value buildDistributedOffset(RewriterBase &b, Location loc, int64_t index) {
99 int64_t distributedSize = distributedVectorType.getDimSize(index);
100 AffineExpr tid = getAffineSymbolExpr(0, b.getContext());
101 return b.createOrFold<affine::AffineApplyOp>(loc, tid * distributedSize,
102 ArrayRef<Value>{laneId});
103 }
104
105 /// Create a store during the process of distributing the
106 /// `vector.warp_execute_on_thread_0` op.
107 /// Vector distribution assumes the following convention regarding the
108 /// temporary buffers that are created to transition values. This **must**
109 /// be properly specified in the `options.warpAllocationFn`:
110 /// 1. scalars of type T transit through a memref<1xT>.
111 /// 2. vectors of type V<shapexT> transit through a memref<shapexT>
112 Operation *buildStore(RewriterBase &b, Location loc, Value val,
113 Value buffer) {
114 assert((val == distributedVal || val == sequentialVal) &&
115 "Must store either the preregistered distributed or the "
116 "preregistered sequential value.");
117 // Scalar case can directly use memref.store.
118 if (!isa<VectorType>(val.getType()))
119 return memref::StoreOp::create(b, loc, val, buffer, zero);
120
121 // Vector case must use vector::TransferWriteOp which will later lower to
122 // vector.store of memref.store depending on further lowerings.
123 int64_t rank = sequentialVectorType.getRank();
124 SmallVector<Value> indices(rank, zero);
125 if (val == distributedVal) {
126 for (auto dimExpr : distributionMap.getResults()) {
127 int64_t index = cast<AffineDimExpr>(dimExpr).getPosition();
128 indices[index] = buildDistributedOffset(b, loc, index);
129 }
130 }
131 SmallVector<bool> inBounds(indices.size(), true);
132 return vector::TransferWriteOp::create(
133 b, loc, val, buffer, indices,
134 ArrayRef<bool>(inBounds.begin(), inBounds.end()));
135 }
136
137 /// Create a load during the process of distributing the
138 /// `vector.warp_execute_on_thread_0` op.
139 /// Vector distribution assumes the following convention regarding the
140 /// temporary buffers that are created to transition values. This **must**
141 /// be properly specified in the `options.warpAllocationFn`:
142 /// 1. scalars of type T transit through a memref<1xT>.
143 /// 2. vectors of type V<shapexT> transit through a memref<shapexT>
144 ///
145 /// When broadcastMode is true, the load is not distributed to account for
146 /// the broadcast semantics of the `gpu.warp_execute_on_lane_0` op.
147 ///
148 /// Example:
149 ///
150 /// ```
151 /// %r = gpu.warp_execute_on_lane_0(...) -> (f32) {
152 /// gpu.yield %cst : f32
153 /// }
154 /// // Both types are f32. The constant %cst is broadcasted to all lanes.
155 /// ```
156 /// This behavior described in more detail in the documentation of the op.
157 Value buildLoad(RewriterBase &b, Location loc, Type type, Value buffer) {
158
159 // Scalar case can directly use memref.store.
160 if (!isa<VectorType>(type))
161 return memref::LoadOp::create(b, loc, buffer, zero);
162
163 // Other cases must be vector atm.
164 // Vector case must use vector::TransferReadOp which will later lower to
165 // vector.read of memref.read depending on further lowerings.
166 assert((type == distributedVectorType || type == sequentialVectorType) &&
167 "Must store either the preregistered distributed or the "
168 "preregistered sequential type.");
169 SmallVector<Value> indices(sequentialVectorType.getRank(), zero);
170 if (type == distributedVectorType) {
171 for (auto dimExpr : distributionMap.getResults()) {
172 int64_t index = cast<AffineDimExpr>(dimExpr).getPosition();
173 indices[index] = buildDistributedOffset(b, loc, index);
174 }
175 }
176 SmallVector<bool> inBounds(indices.size(), true);
177 return vector::TransferReadOp::create(
178 b, loc, cast<VectorType>(type), buffer, indices,
179 /*padding=*/std::nullopt,
180 ArrayRef<bool>(inBounds.begin(), inBounds.end()));
181 }
182
183 Value sequentialVal, distributedVal, laneId, zero;
184 VectorType sequentialVectorType, distributedVectorType;
185 AffineMap distributionMap;
186};
187
188} // namespace
189
190// Clones `op` into a new operation that takes `operands` and returns
191// `resultTypes`.
193 Location loc, Operation *op,
194 ArrayRef<Value> operands,
195 ArrayRef<Type> resultTypes) {
196 OperationState res(loc, op->getName().getStringRef(), operands, resultTypes,
197 op->getDiscardableAttrDictionary().getValue());
198 res.propertiesAttr = op->getPropertiesAsAttribute();
199 return rewriter.create(res);
200}
201
202namespace {
203
204/// Rewrite a WarpExecuteOnLane0Op into a predicated scf.if op where the single
205/// thread `laneId` executes the entirety of the computation.
206///
207/// After the transformation:
208/// - the IR within the scf.if op can be thought of as executing sequentially
209/// (from the point of view of threads along `laneId`).
210/// - the IR outside of the scf.if op can be thought of as executing in
211/// parallel (from the point of view of threads along `laneId`).
212///
213/// Values that need to transit through the parallel / sequential and the
214/// sequential / parallel boundaries do so via reads and writes to a temporary
215/// memory location.
216///
217/// The transformation proceeds in multiple steps:
218/// 1. Create the scf.if op.
219/// 2. Insert appropriate (alloc, write)-pairs before the scf.if and reads
220/// within the scf.if to transit the values captured from above.
221/// 3. Synchronize before the scf.if to ensure all writes inserted in 2. are
222/// consistent within the scf.if.
223/// 4. Move the body of the WarpExecuteOnLane0Op inside the scf.if.
224/// 5. Insert appropriate writes within scf.if and reads after the scf.if to
225/// transit the values returned by the op.
226/// 6. Synchronize after the scf.if to ensure all writes inserted in 5. are
227/// consistent after the scf.if.
228/// 7. Perform late cleanups.
229///
230/// All this assumes the vector distribution occurs along the most minor
231/// distributed vector dimension.
232struct WarpOpToScfIfPattern : public WarpDistributionPattern {
233 WarpOpToScfIfPattern(MLIRContext *context,
234 const WarpExecuteOnLane0LoweringOptions &options,
235 PatternBenefit benefit = 1)
236 : WarpDistributionPattern(context, benefit), options(options) {}
237
238 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
239 PatternRewriter &rewriter) const override {
240 assert(warpOp.getBodyRegion().hasOneBlock() &&
241 "expected WarpOp with single block");
242 Block *warpOpBody = &warpOp.getBodyRegion().front();
243 Location loc = warpOp.getLoc();
244
245 // Passed all checks. Start rewriting.
246 OpBuilder::InsertionGuard g(rewriter);
247 rewriter.setInsertionPoint(warpOp);
248
249 // Step 1: Create scf.if op.
250 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
251 Value isLane0 = arith::CmpIOp::create(
252 rewriter, loc, arith::CmpIPredicate::eq, warpOp.getLaneid(), c0);
253 auto ifOp = scf::IfOp::create(rewriter, loc, isLane0,
254 /*withElseRegion=*/false);
255 rewriter.eraseOp(ifOp.thenBlock()->getTerminator());
256
257 // Step 2: insert appropriate (alloc, write)-pairs before the scf.if and
258 // reads within the scf.if to transit the values captured from above.
259 SmallVector<Value> bbArgReplacements;
260 for (const auto &it : llvm::enumerate(warpOp.getArgs())) {
261 Value sequentialVal = warpOpBody->getArgument(it.index());
262 Value distributedVal = it.value();
263 DistributedLoadStoreHelper helper(sequentialVal, distributedVal,
264 warpOp.getLaneid(), c0);
265
266 // Create buffer before the ifOp.
267 rewriter.setInsertionPoint(ifOp);
268 Value buffer = options.warpAllocationFn(loc, rewriter, warpOp,
269 sequentialVal.getType());
270 // Store distributed vector into buffer, before the ifOp.
271 helper.buildStore(rewriter, loc, distributedVal, buffer);
272 // Load sequential vector from buffer, inside the ifOp.
273 rewriter.setInsertionPointToStart(ifOp.thenBlock());
274 bbArgReplacements.push_back(
275 helper.buildLoad(rewriter, loc, sequentialVal.getType(), buffer));
276 }
277
278 // Step 3. Insert sync after all the stores and before all the loads.
279 if (!warpOp.getArgs().empty()) {
280 rewriter.setInsertionPoint(ifOp);
281 options.warpSynchronizationFn(loc, rewriter, warpOp);
282 }
283
284 // Step 4. Move body of warpOp to ifOp.
285 rewriter.mergeBlocks(warpOpBody, ifOp.thenBlock(), bbArgReplacements);
286
287 // Step 5. Insert appropriate writes within scf.if and reads after the
288 // scf.if to transit the values returned by the op.
289 // TODO: at this point, we can reuse the shared memory from previous
290 // buffers.
291 SmallVector<Value> replacements;
292 auto yieldOp = cast<gpu::YieldOp>(ifOp.thenBlock()->getTerminator());
293 Location yieldLoc = yieldOp.getLoc();
294 for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {
295 Value sequentialVal = it.value();
296 Value distributedVal = warpOp->getResult(it.index());
297 DistributedLoadStoreHelper helper(sequentialVal, distributedVal,
298 warpOp.getLaneid(), c0);
299
300 // Create buffer before the ifOp.
301 rewriter.setInsertionPoint(ifOp);
302 Value buffer = options.warpAllocationFn(loc, rewriter, warpOp,
303 sequentialVal.getType());
304
305 // Store yielded value into buffer, inside the ifOp, before the
306 // terminator.
307 rewriter.setInsertionPoint(yieldOp);
308 helper.buildStore(rewriter, loc, sequentialVal, buffer);
309
310 // Load distributed value from buffer, after the warpOp.
311 rewriter.setInsertionPointAfter(ifOp);
312 // Result type and yielded value type are the same. This is a broadcast.
313 // E.g.:
314 // %r = gpu.warp_execute_on_lane_0(...) -> (f32) {
315 // gpu.yield %cst : f32
316 // }
317 // Both types are f32. The constant %cst is broadcasted to all lanes.
318 // This is described in more detail in the documentation of the op.
319 replacements.push_back(
320 helper.buildLoad(rewriter, loc, distributedVal.getType(), buffer));
321 }
322
323 // Step 6. Insert sync after all the stores and before all the loads.
324 if (!yieldOp.getOperands().empty()) {
325 rewriter.setInsertionPointAfter(ifOp);
326 options.warpSynchronizationFn(loc, rewriter, warpOp);
327 }
328
329 // Step 7. Delete terminator and add empty scf.yield.
330 rewriter.eraseOp(yieldOp);
331 rewriter.setInsertionPointToEnd(ifOp.thenBlock());
332 scf::YieldOp::create(rewriter, yieldLoc);
333
334 // Compute replacements for WarpOp results.
335 rewriter.replaceOp(warpOp, replacements);
336
337 return success();
338 }
339
340private:
341 const WarpExecuteOnLane0LoweringOptions &options;
342};
343
344/// Return the distributed vector type based on the original type and the
345/// distribution map. The map is expected to have a dimension equal to the
346/// original type rank and should be a projection where the results are the
347/// distributed dimensions. If the number of results is zero there is no
348/// distribution (i.e. original type is returned).
349/// Otherwise, The number of results should be equal to the number
350/// of warp sizes which is currently limited to 1.
351/// Example: For a vector<16x32x64> distributed with a map(d0, d1, d2) -> (d1)
352/// and a warp size of 16 would distribute the second dimension (associated to
353/// d1) and return vector<16x2x64>
354static VectorType getDistributedType(VectorType originalType, AffineMap map,
355 int64_t warpSize) {
356 // If the map has zero results, return the original type.
357 if (map.getNumResults() == 0)
358 return originalType;
359 SmallVector<int64_t> targetShape(originalType.getShape());
360 for (unsigned i = 0, e = map.getNumResults(); i < e; i++) {
361 unsigned position = map.getDimPosition(i);
362 if (targetShape[position] % warpSize != 0) {
363 if (warpSize % targetShape[position] != 0) {
364 return VectorType();
365 }
366 warpSize /= targetShape[position];
367 targetShape[position] = 1;
368 continue;
369 }
370 targetShape[position] = targetShape[position] / warpSize;
371 warpSize = 1;
372 break;
373 }
374 if (warpSize != 1) {
375 return VectorType();
376 }
377 VectorType targetType =
378 VectorType::get(targetShape, originalType.getElementType());
379 return targetType;
380}
381
382/// Given a warpOp that contains ops with regions, the corresponding op's
383/// "inner" region and the distributionMapFn, get all values used by the op's
384/// region that are defined within the warpOp, but outside the inner region.
385/// Return the set of values, their types and their distributed types.
386std::tuple<llvm::SmallSetVector<Value, 32>, SmallVector<Type>,
388getInnerRegionEscapingValues(WarpExecuteOnLane0Op warpOp, Region &innerRegion,
389 DistributionMapFn distributionMapFn) {
390 llvm::SmallSetVector<Value, 32> escapingValues;
391 SmallVector<Type> escapingValueTypes;
392 SmallVector<Type> escapingValueDistTypes; // to yield from the new warpOp
393 if (innerRegion.empty())
394 return {std::move(escapingValues), std::move(escapingValueTypes),
395 std::move(escapingValueDistTypes)};
396 mlir::visitUsedValuesDefinedAbove(innerRegion, [&](OpOperand *operand) {
397 Operation *parent = operand->get().getParentRegion()->getParentOp();
398 if (warpOp->isAncestor(parent)) {
399 if (!escapingValues.insert(operand->get()))
400 return;
401 Type distType = operand->get().getType();
402 if (auto vecType = dyn_cast<VectorType>(distType)) {
403 AffineMap map = distributionMapFn(operand->get());
404 distType = getDistributedType(vecType, map,
405 map.isEmpty() ? 1 : warpOp.getWarpSize());
406 }
407 escapingValueTypes.push_back(operand->get().getType());
408 escapingValueDistTypes.push_back(distType);
409 }
410 });
411 return {std::move(escapingValues), std::move(escapingValueTypes),
412 std::move(escapingValueDistTypes)};
413}
414
415/// Distribute transfer_write ops based on the affine map returned by
416/// `distributionMapFn`. Writes of size more than `maxNumElementToExtract`
417/// will not be distributed (it should be less than the warp size).
418///
419/// Example:
420/// ```
421/// %0 = gpu.warp_execute_on_lane_0(%id){
422/// ...
423/// vector.transfer_write %v, %A[%c0] : vector<32xf32>, memref<128xf32>
424/// gpu.yield
425/// }
426/// ```
427/// To
428/// ```
429/// %r:3 = gpu.warp_execute_on_lane_0(%id) -> (vector<1xf32>) {
430/// ...
431/// gpu.yield %v : vector<32xf32>
432/// }
433/// vector.transfer_write %v, %A[%id] : vector<1xf32>, memref<128xf32>
434struct WarpOpTransferWrite : public WarpDistributionPattern {
435 WarpOpTransferWrite(MLIRContext *ctx, DistributionMapFn fn,
436 unsigned maxNumElementsToExtract, PatternBenefit b = 1)
437 : WarpDistributionPattern(ctx, b), distributionMapFn(std::move(fn)),
438 maxNumElementsToExtract(maxNumElementsToExtract) {}
439
440 /// Distribute the TransferWriteOp. Only 1D distributions and vector dims that
441 /// are multiples of the distribution ratio are supported at the moment.
442 LogicalResult tryDistributeOp(RewriterBase &rewriter,
443 vector::TransferWriteOp writeOp,
444 WarpExecuteOnLane0Op warpOp) const {
445 VectorType writtenVectorType = writeOp.getVectorType();
446
447 // 1. If the write is 0-D, we just clone it into a new WarpExecuteOnLane0Op
448 // to separate it from the rest.
449 if (writtenVectorType.getRank() == 0)
450 return failure();
451
452 // 2. Compute the distributed type.
453 AffineMap map = distributionMapFn(writeOp.getVector());
454 VectorType targetType =
455 getDistributedType(writtenVectorType, map, warpOp.getWarpSize());
456 if (!targetType)
457 return failure();
458
459 // 2.5 Compute the distributed type for the new mask;
460 VectorType maskType;
461 if (writeOp.getMask()) {
462 // TODO: Distribution of masked writes with non-trivial permutation maps
463 // requires the distribution of the mask to elementwise match the
464 // distribution of the permuted written vector. Currently the details
465 // of which lane is responsible for which element is captured strictly
466 // by shape information on the warp op, and thus requires materializing
467 // the permutation in IR.
468 if (!writeOp.getPermutationMap().isMinorIdentity())
469 return failure();
470 maskType =
471 getDistributedType(writeOp.getMaskType(), map, warpOp.getWarpSize());
472 }
473
474 // 3. clone the write into a new WarpExecuteOnLane0Op to separate it from
475 // the rest.
476 vector::TransferWriteOp newWriteOp =
477 cloneWriteOp(rewriter, warpOp, writeOp, targetType, maskType);
478
479 // 4. Reindex the write using the distribution map.
480 auto newWarpOp =
481 newWriteOp.getVector().getDefiningOp<WarpExecuteOnLane0Op>();
482
483 // Delinearize the lane id based on the way threads are divided across the
484 // vector. To get the number of threads per vector dimension, divide the
485 // sequential size by the distributed size along each dim.
486 rewriter.setInsertionPoint(newWriteOp);
487 SmallVector<OpFoldResult> delinearizedIdSizes;
488 for (auto [seqSize, distSize] :
489 llvm::zip_equal(writtenVectorType.getShape(), targetType.getShape())) {
490 assert(seqSize % distSize == 0 && "Invalid distributed vector shape");
491 delinearizedIdSizes.push_back(rewriter.getIndexAttr(seqSize / distSize));
492 }
493 SmallVector<Value> delinearized;
494 if (map.getNumResults() > 1) {
495 delinearized = mlir::affine::AffineDelinearizeIndexOp::create(
496 rewriter, newWarpOp.getLoc(), newWarpOp.getLaneid(),
497 delinearizedIdSizes)
498 .getResults();
499 } else {
500 // If there is only one map result, we can elide the delinearization
501 // op and use the lane id directly.
502 delinearized.append(targetType.getRank(), newWarpOp.getLaneid());
503 }
504
505 AffineMap indexMap = map.compose(newWriteOp.getPermutationMap());
506 Location loc = newWriteOp.getLoc();
507 SmallVector<Value> indices(newWriteOp.getIndices().begin(),
508 newWriteOp.getIndices().end());
509 for (auto it : llvm::zip(indexMap.getResults(), map.getResults())) {
510 AffineExpr d0, d1;
511 bindDims(newWarpOp.getContext(), d0, d1);
512 auto indexExpr = dyn_cast<AffineDimExpr>(std::get<0>(it));
513 if (!indexExpr)
514 continue;
515 unsigned indexPos = indexExpr.getPosition();
516 unsigned vectorPos = cast<AffineDimExpr>(std::get<1>(it)).getPosition();
517 Value laneId = delinearized[vectorPos];
518 auto scale =
519 rewriter.getAffineConstantExpr(targetType.getDimSize(vectorPos));
521 rewriter, loc, d0 + scale * d1, {indices[indexPos], laneId});
522 }
523 newWriteOp.getIndicesMutable().assign(indices);
524
525 return success();
526 }
527
528 /// Extract TransferWriteOps of vector<1x> into a separate warp op.
529 LogicalResult tryExtractOp(RewriterBase &rewriter,
530 vector::TransferWriteOp writeOp,
531 WarpExecuteOnLane0Op warpOp) const {
532 Location loc = writeOp.getLoc();
533 VectorType vecType = writeOp.getVectorType();
534
535 if (vecType.getNumElements() > maxNumElementsToExtract) {
536 return rewriter.notifyMatchFailure(
537 warpOp,
538 llvm::formatv(
539 "writes more elements ({0}) than allowed to extract ({1})",
540 vecType.getNumElements(), maxNumElementsToExtract));
541 }
542
543 // Do not process warp ops that contain only TransferWriteOps.
544 if (llvm::all_of(warpOp.getOps(),
545 llvm::IsaPred<vector::TransferWriteOp, gpu::YieldOp>))
546 return failure();
547
548 SmallVector<Value> yieldValues = {writeOp.getVector()};
549 SmallVector<Type> retTypes = {vecType};
550 SmallVector<size_t> newRetIndices;
551 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
552 rewriter, warpOp, yieldValues, retTypes, newRetIndices);
553 rewriter.setInsertionPointAfter(newWarpOp);
554
555 // Create a second warp op that contains only writeOp.
556 auto secondWarpOp = WarpExecuteOnLane0Op::create(rewriter, loc, TypeRange(),
557 newWarpOp.getLaneid(),
558 newWarpOp.getWarpSize());
559 Block &body = secondWarpOp.getBodyRegion().front();
560 rewriter.setInsertionPointToStart(&body);
561 auto newWriteOp =
562 cast<vector::TransferWriteOp>(rewriter.clone(*writeOp.getOperation()));
563 newWriteOp.getValueToStoreMutable().assign(
564 newWarpOp.getResult(newRetIndices[0]));
565 rewriter.eraseOp(writeOp);
566 gpu::YieldOp::create(rewriter, newWarpOp.getLoc());
567 return success();
568 }
569
570 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
571 PatternRewriter &rewriter) const override {
572 gpu::YieldOp yield = warpOp.getTerminator();
573 Operation *lastNode = yield->getPrevNode();
574 auto writeOp = dyn_cast_or_null<vector::TransferWriteOp>(lastNode);
575 if (!writeOp)
576 return failure();
577
578 Value maybeMask = writeOp.getMask();
579 if (!llvm::all_of(writeOp->getOperands(), [&](Value value) {
580 return writeOp.getVector() == value ||
581 (maybeMask && maybeMask == value) ||
582 warpOp.isDefinedOutsideOfRegion(value);
583 }))
584 return failure();
585
586 if (succeeded(tryDistributeOp(rewriter, writeOp, warpOp)))
587 return success();
588
589 // Masked writes not supported for extraction.
590 if (writeOp.getMask())
591 return failure();
592
593 if (succeeded(tryExtractOp(rewriter, writeOp, warpOp)))
594 return success();
595
596 return failure();
597 }
598
599private:
600 /// Clone `writeOp` assumed to be nested under `warpOp` into a new warp
601 /// execute op with the proper return type. The new write op is updated to
602 /// write the result of the new warp execute op. The old `writeOp` is deleted.
603 vector::TransferWriteOp cloneWriteOp(RewriterBase &rewriter,
604 WarpExecuteOnLane0Op warpOp,
605 vector::TransferWriteOp writeOp,
606 VectorType targetType,
607 VectorType maybeMaskType) const {
608 assert(writeOp->getParentOp() == warpOp &&
609 "write must be nested immediately under warp");
610 OpBuilder::InsertionGuard g(rewriter);
611 SmallVector<size_t> newRetIndices;
612 WarpExecuteOnLane0Op newWarpOp;
613 if (maybeMaskType) {
615 rewriter, warpOp, ValueRange{writeOp.getVector(), writeOp.getMask()},
616 TypeRange{targetType, maybeMaskType}, newRetIndices);
617 } else {
619 rewriter, warpOp, ValueRange{{writeOp.getVector()}},
620 TypeRange{targetType}, newRetIndices);
621 }
622 rewriter.setInsertionPointAfter(newWarpOp);
623 auto newWriteOp =
624 cast<vector::TransferWriteOp>(rewriter.clone(*writeOp.getOperation()));
625 rewriter.eraseOp(writeOp);
626 newWriteOp.getValueToStoreMutable().assign(
627 newWarpOp.getResult(newRetIndices[0]));
628 if (maybeMaskType)
629 newWriteOp.getMaskMutable().assign(newWarpOp.getResult(newRetIndices[1]));
630 return newWriteOp;
631 }
632
633 DistributionMapFn distributionMapFn;
634 unsigned maxNumElementsToExtract = 1;
635};
636
637/// Sink out elementwise op feeding into a warp op yield.
638/// ```
639/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>) {
640/// ...
641/// %3 = arith.addf %1, %2 : vector<32xf32>
642/// gpu.yield %3 : vector<32xf32>
643/// }
644/// ```
645/// To
646/// ```
647/// %r:3 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>,
648/// vector<1xf32>, vector<1xf32>) {
649/// ...
650/// %4 = arith.addf %2, %3 : vector<32xf32>
651/// gpu.yield %4, %2, %3 : vector<32xf32>, vector<32xf32>,
652/// vector<32xf32>
653/// }
654/// %0 = arith.addf %r#1, %r#2 : vector<1xf32>
655struct WarpOpElementwise : public WarpDistributionPattern {
656 using Base::Base;
657 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
658 PatternRewriter &rewriter) const override {
659 OpOperand *yieldOperand = getWarpResult(warpOp, [](Operation *op) {
661 });
662 if (!yieldOperand)
663 return failure();
664
665 Operation *elementWise = yieldOperand->get().getDefiningOp();
666 unsigned operandIndex = yieldOperand->getOperandNumber();
667 Value distributedVal = warpOp.getResult(operandIndex);
668 SmallVector<Value> yieldValues;
669 SmallVector<Type> retTypes;
670 Location loc = warpOp.getLoc();
671 for (OpOperand &operand : elementWise->getOpOperands()) {
672 Type targetType;
673 if (auto vecType = dyn_cast<VectorType>(distributedVal.getType())) {
674 // If the result type is a vector, the operands must also be vectors.
675 auto operandType = cast<VectorType>(operand.get().getType());
676 targetType =
677 VectorType::get(vecType.getShape(), operandType.getElementType());
678 } else {
679 auto operandType = operand.get().getType();
680 assert(!isa<VectorType>(operandType) &&
681 "unexpected yield of vector from op with scalar result type");
682 targetType = operandType;
683 }
684 retTypes.push_back(targetType);
685 yieldValues.push_back(operand.get());
686 }
687 SmallVector<size_t> newRetIndices;
688 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
689 rewriter, warpOp, yieldValues, retTypes, newRetIndices);
690 rewriter.setInsertionPointAfter(newWarpOp);
691 SmallVector<Value> newOperands(elementWise->getOperands().begin(),
692 elementWise->getOperands().end());
693 for (unsigned i : llvm::seq(unsigned(0), elementWise->getNumOperands())) {
694 newOperands[i] = newWarpOp.getResult(newRetIndices[i]);
695 }
696 OpBuilder::InsertionGuard g(rewriter);
697 rewriter.setInsertionPointAfter(newWarpOp);
698 Operation *newOp = cloneOpWithOperandsAndTypes(
699 rewriter, loc, elementWise, newOperands,
700 {newWarpOp.getResult(operandIndex).getType()});
701 rewriter.replaceAllUsesWith(newWarpOp.getResult(operandIndex),
702 newOp->getResult(0));
703 return success();
704 }
705};
706
707/// Sink out splat constant op feeding into a warp op yield.
708/// ```
709/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>) {
710/// ...
711/// %cst = arith.constant dense<2.0> : vector<32xf32>
712/// gpu.yield %cst : vector<32xf32>
713/// }
714/// ```
715/// To
716/// ```
717/// gpu.warp_execute_on_lane_0(%arg0 {
718/// ...
719/// }
720/// %0 = arith.constant dense<2.0> : vector<1xf32>
721struct WarpOpConstant : public WarpDistributionPattern {
722 using Base::Base;
723 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
724 PatternRewriter &rewriter) const override {
725 OpOperand *yieldOperand =
726 getWarpResult(warpOp, llvm::IsaPred<arith::ConstantOp>);
727 if (!yieldOperand)
728 return failure();
729 auto constantOp = yieldOperand->get().getDefiningOp<arith::ConstantOp>();
730 auto dense = dyn_cast<SplatElementsAttr>(constantOp.getValue());
731 if (!dense)
732 return failure();
733 // Notify the rewriter that the warp op is changing (see the comment on
734 // the WarpOpTransferRead pattern).
735 rewriter.startOpModification(warpOp);
736 unsigned operandIndex = yieldOperand->getOperandNumber();
737 Attribute scalarAttr = dense.getSplatValue<Attribute>();
738 auto newAttr = DenseElementsAttr::get(
739 cast<ShapedType>(warpOp.getResult(operandIndex).getType()), scalarAttr);
740 Location loc = warpOp.getLoc();
741 rewriter.setInsertionPointAfter(warpOp);
742 Value distConstant = arith::ConstantOp::create(rewriter, loc, newAttr);
743 rewriter.replaceAllUsesWith(warpOp.getResult(operandIndex), distConstant);
744 rewriter.finalizeOpModification(warpOp);
745 return success();
746 }
747};
748
749/// Sink out step op feeding into a warp op yield.
750/// Vector step op is treated similar to arith.constant, apart from
751/// the result that represents a sequence [0, vec_size).
752/// Due to the to vec_size == warp_size limitation,
753/// we can simply wrap the lane id into a vector (i.e., broadcast).
754/// Supporting vec_size != warp_size may involve preserving the step
755/// result and using additional arith ops (the exact details are TBD).
756/// ```
757/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xindex>) {
758/// ...
759/// %cst = vector.step : vector<32xindex>
760/// gpu.yield %cst : vector<1xindex>
761/// }
762/// ```
763/// To
764/// ```
765/// gpu.warp_execute_on_lane_0(%arg0) {
766/// ...
767/// }
768/// %lane_id_vec = vector.broadcast %arg0 : index to vector<1xindex>
769struct WarpOpStep final : public WarpDistributionPattern {
770 using Base::Base;
771 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
772 PatternRewriter &rewriter) const override {
773 OpOperand *yieldOperand =
774 getWarpResult(warpOp, llvm::IsaPred<vector::StepOp>);
775 if (!yieldOperand)
776 return failure();
777 const unsigned operandIdx = yieldOperand->getOperandNumber();
778 auto stepOp = yieldOperand->get().getDefiningOp<vector::StepOp>();
779 VectorType resTy = stepOp.getResult().getType();
780 if (resTy.getNumElements() != static_cast<int64_t>(warpOp.getWarpSize()))
781 return rewriter.notifyMatchFailure(
782 warpOp,
783 llvm::formatv("Expected result size ({0}) to be of warp size ({1})",
784 resTy.getNumElements(), warpOp.getWarpSize()));
785 VectorType newVecTy =
786 cast<VectorType>(warpOp.getResult(operandIdx).getType());
787 rewriter.setInsertionPointAfter(warpOp);
788 Value laneIdVec = vector::BroadcastOp::create(rewriter, warpOp.getLoc(),
789 newVecTy, warpOp.getLaneid());
790 rewriter.replaceAllUsesWith(warpOp.getResult(operandIdx), laneIdVec);
791 return success();
792 }
793};
794
795/// Sink out transfer_read op feeding into a warp op yield.
796/// ```
797/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>) {
798/// ...
799// %2 = vector.transfer_read %src[%c0], %cst : memref<1024xf32>,
800// vector<32xf32>
801/// gpu.yield %2 : vector<32xf32>
802/// }
803/// ```
804/// To
805/// ```
806/// %dead = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>,
807/// vector<1xf32>, vector<1xf32>) {
808/// ...
809/// %2 = vector.transfer_read %src[%c0], %cst : memref<1024xf32>,
810/// vector<32xf32> gpu.yield %2 : vector<32xf32>
811/// }
812/// %0 = vector.transfer_read %src[%c0], %cst : memref<1024xf32>, vector<1xf32>
813struct WarpOpTransferRead : public WarpDistributionPattern {
814 using Base::Base;
815 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
816 PatternRewriter &rewriter) const override {
817 // Try to find a distributable yielded read. Note that this pattern can
818 // still fail at the end after distribution, in which case this might have
819 // missed another distributable read.
820 OpOperand *operand = getWarpResult(warpOp, [](Operation *op) {
821 // Don't duplicate transfer_read ops when distributing.
822 return isa<vector::TransferReadOp>(op) && op->hasOneUse();
823 });
824 if (!operand)
825 return rewriter.notifyMatchFailure(
826 warpOp, "warp result is not a vector.transfer_read op");
827 auto read = operand->get().getDefiningOp<vector::TransferReadOp>();
828
829 // Source must be defined outside of the region.
830 if (!warpOp.isDefinedOutsideOfRegion(read.getBase()))
831 return rewriter.notifyMatchFailure(
832 read, "source must be defined outside of the region");
833
834 unsigned operandIndex = operand->getOperandNumber();
835 Value distributedVal = warpOp.getResult(operandIndex);
836
837 SmallVector<Value, 4> indices(read.getIndices().begin(),
838 read.getIndices().end());
839 auto sequentialType = cast<VectorType>(read.getResult().getType());
840 auto distributedType = cast<VectorType>(distributedVal.getType());
841 AffineMap map = calculateImplicitMap(sequentialType, distributedType);
842 AffineMap indexMap = map.compose(read.getPermutationMap());
843
844 // Try to delinearize the lane ID to match the rank expected for
845 // distribution.
846 SmallVector<Value> delinearizedIds;
847 if (!delinearizeLaneId(rewriter, read.getLoc(), sequentialType.getShape(),
848 distributedType.getShape(), warpOp.getWarpSize(),
849 warpOp.getLaneid(), delinearizedIds)) {
850 return rewriter.notifyMatchFailure(
851 read, "cannot delinearize lane ID for distribution");
852 }
853 assert(!delinearizedIds.empty() || map.getNumResults() == 0);
854
855 // Distribute indices and the mask (if present).
856 OpBuilder::InsertionGuard g(rewriter);
857 SmallVector<Value> additionalResults(indices.begin(), indices.end());
858 SmallVector<Type> additionalResultTypes(indices.size(),
859 rewriter.getIndexType());
860 additionalResults.push_back(read.getPadding());
861 additionalResultTypes.push_back(read.getPadding().getType());
862
863 bool hasMask = false;
864 if (read.getMask()) {
865 hasMask = true;
866 // TODO: Distribution of masked reads with non-trivial permutation maps
867 // requires the distribution of the mask to elementwise match the
868 // distribution of the permuted written vector. Currently the details
869 // of which lane is responsible for which element is captured strictly
870 // by shape information on the warp op, and thus requires materializing
871 // the permutation in IR.
872 if (!mlir::compressUnusedDims(read.getPermutationMap()).isIdentity())
873 return rewriter.notifyMatchFailure(
874 read, "non-trivial permutation maps not supported");
875 VectorType maskType =
876 getDistributedType(read.getMaskType(), map, warpOp.getWarpSize());
877 additionalResults.push_back(read.getMask());
878 additionalResultTypes.push_back(maskType);
879 }
880
881 SmallVector<size_t> newRetIndices;
882 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
883 rewriter, warpOp, additionalResults, additionalResultTypes,
884 newRetIndices);
885 distributedVal = newWarpOp.getResult(operandIndex);
886
887 // Distributed indices were appended first.
888 SmallVector<Value> newIndices;
889 for (int64_t i = 0, e = indices.size(); i < e; ++i)
890 newIndices.push_back(newWarpOp.getResult(newRetIndices[i]));
891
892 rewriter.setInsertionPointAfter(newWarpOp);
893 for (auto it : llvm::zip_equal(indexMap.getResults(), map.getResults())) {
894 AffineExpr d0, d1;
895 bindDims(read.getContext(), d0, d1);
896 auto indexExpr = dyn_cast<AffineDimExpr>(std::get<0>(it));
897 if (!indexExpr)
898 continue;
899 unsigned indexPos = indexExpr.getPosition();
900 unsigned vectorPos = cast<AffineDimExpr>(std::get<1>(it)).getPosition();
901 int64_t scale = distributedType.getDimSize(vectorPos);
902 newIndices[indexPos] = affine::makeComposedAffineApply(
903 rewriter, read.getLoc(), d0 + scale * d1,
904 {newIndices[indexPos], delinearizedIds[vectorPos]});
905 }
906
907 // Distributed padding value was appended right after the indices.
908 Value newPadding = newWarpOp.getResult(newRetIndices[indices.size()]);
909 // Distributed mask value was added at the end (if the op has a mask).
910 Value newMask =
911 hasMask ? newWarpOp.getResult(newRetIndices[newRetIndices.size() - 1])
912 : Value();
913 auto newRead = vector::TransferReadOp::create(
914 rewriter, read.getLoc(), distributedVal.getType(), read.getBase(),
915 newIndices, read.getPermutationMapAttr(), newPadding, newMask,
916 read.getInBoundsAttr());
917
918 rewriter.replaceAllUsesWith(distributedVal, newRead);
919 return success();
920 }
921};
922
923/// Remove any result that has no use along with the matching yieldOp operand.
924// TODO: Move this in WarpExecuteOnLane0Op canonicalization.
925struct WarpOpDeadResult : public WarpDistributionPattern {
926 using Base::Base;
927 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
928 PatternRewriter &rewriter) const override {
929 SmallVector<Type> newResultTypes;
930 newResultTypes.reserve(warpOp->getNumResults());
931 SmallVector<Value> newYieldValues;
932 newYieldValues.reserve(warpOp->getNumResults());
933 DenseMap<Value, int64_t> dedupYieldOperandPositionMap;
934 DenseMap<OpResult, int64_t> dedupResultPositionMap;
935 gpu::YieldOp yield = warpOp.getTerminator();
936
937 // Some values may be yielded multiple times and correspond to multiple
938 // results. Deduplicating occurs by taking each result with its matching
939 // yielded value, and:
940 // 1. recording the unique first position at which the value with uses is
941 // yielded.
942 // 2. recording for the result, the first position at which the dedup'ed
943 // value is yielded.
944 // 3. skipping from the new result types / new yielded values any result
945 // that has no use or whose yielded value has already been seen.
946 for (OpResult result : warpOp.getResults()) {
947 if (result.use_empty())
948 continue;
949 Value yieldOperand = yield.getOperand(result.getResultNumber());
950 auto it = dedupYieldOperandPositionMap.insert(
951 std::make_pair(yieldOperand, newResultTypes.size()));
952 dedupResultPositionMap.insert(std::make_pair(result, it.first->second));
953 if (!it.second)
954 continue;
955 newResultTypes.push_back(result.getType());
956 newYieldValues.push_back(yieldOperand);
957 }
958 // No modification, exit early.
959 if (yield.getNumOperands() == newYieldValues.size())
960 return failure();
961 // Move the body of the old warpOp to a new warpOp.
962 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndReplaceReturns(
963 rewriter, warpOp, newYieldValues, newResultTypes);
964
965 // Simplify the new warp op after dropping dead results.
966 newWarpOp.getBody()->walk([&](Operation *op) {
967 if (isOpTriviallyDead(op))
968 rewriter.eraseOp(op);
969 });
970
971 // Replace results of the old warpOp by the new, deduplicated results.
972 SmallVector<Value> newValues;
973 newValues.reserve(warpOp->getNumResults());
974 for (OpResult result : warpOp.getResults()) {
975 if (result.use_empty())
976 newValues.push_back(Value());
977 else
978 newValues.push_back(
979 newWarpOp.getResult(dedupResultPositionMap.lookup(result)));
980 }
981 rewriter.replaceOp(warpOp, newValues);
982 return success();
983 }
984};
985
986// If an operand is directly yielded out of the region we can forward it
987// directly and it doesn't need to go through the region.
988struct WarpOpForwardOperand : public WarpDistributionPattern {
989 using Base::Base;
990 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
991 PatternRewriter &rewriter) const override {
992 gpu::YieldOp yield = warpOp.getTerminator();
993 Value valForwarded;
994 unsigned resultIndex;
995 for (OpOperand &operand : yield->getOpOperands()) {
996 Value result = warpOp.getResult(operand.getOperandNumber());
997 if (result.use_empty())
998 continue;
999
1000 // Assume all the values coming from above are uniform.
1001 if (!warpOp.getBodyRegion().isAncestor(operand.get().getParentRegion())) {
1002 if (result.getType() != operand.get().getType())
1003 continue;
1004 valForwarded = operand.get();
1005 resultIndex = operand.getOperandNumber();
1006 break;
1007 }
1008 auto arg = dyn_cast<BlockArgument>(operand.get());
1009 if (!arg || arg.getOwner()->getParentOp() != warpOp.getOperation())
1010 continue;
1011 Value warpOperand = warpOp.getArgs()[arg.getArgNumber()];
1012 if (result.getType() != warpOperand.getType())
1013 continue;
1014 valForwarded = warpOperand;
1015 resultIndex = operand.getOperandNumber();
1016 break;
1017 }
1018 if (!valForwarded)
1019 return failure();
1020 // Notify the rewriter that the warp op is changing (see the comment on
1021 // the WarpOpTransferRead pattern).
1022 rewriter.startOpModification(warpOp);
1023 rewriter.replaceAllUsesWith(warpOp.getResult(resultIndex), valForwarded);
1024 rewriter.finalizeOpModification(warpOp);
1025 return success();
1026 }
1027};
1028
1029struct WarpOpBroadcast : public WarpDistributionPattern {
1030 using Base::Base;
1031 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1032 PatternRewriter &rewriter) const override {
1033 OpOperand *operand =
1034 getWarpResult(warpOp, llvm::IsaPred<vector::BroadcastOp>);
1035 if (!operand)
1036 return failure();
1037 unsigned int operandNumber = operand->getOperandNumber();
1038 auto broadcastOp = operand->get().getDefiningOp<vector::BroadcastOp>();
1039 Location loc = broadcastOp.getLoc();
1040 auto destVecType =
1041 cast<VectorType>(warpOp->getResultTypes()[operandNumber]);
1042 Value broadcastSrc = broadcastOp.getSource();
1043 Type broadcastSrcType = broadcastSrc.getType();
1044
1045 // Check that the broadcast actually spans a set of values uniformly across
1046 // all threads. In other words, check that each thread can reconstruct
1047 // their own broadcast.
1048 // For that we simply check that the broadcast we want to build makes sense.
1049 if (vector::isBroadcastableTo(broadcastSrcType, destVecType) !=
1050 vector::BroadcastableToResult::Success)
1051 return failure();
1052 SmallVector<size_t> newRetIndices;
1053 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1054 rewriter, warpOp, {broadcastSrc}, {broadcastSrcType}, newRetIndices);
1055 rewriter.setInsertionPointAfter(newWarpOp);
1056 Value broadcasted = vector::BroadcastOp::create(
1057 rewriter, loc, destVecType, newWarpOp->getResult(newRetIndices[0]));
1058 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1059 broadcasted);
1060 return success();
1061 }
1062};
1063
1064/// Pattern to move shape cast out of the warp op. shape cast is basically a
1065/// no-op for warp distribution; we need to handle the shape though.
1066struct WarpOpShapeCast : public WarpDistributionPattern {
1067 using Base::Base;
1068 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1069 PatternRewriter &rewriter) const override {
1070 OpOperand *operand =
1071 getWarpResult(warpOp, llvm::IsaPred<vector::ShapeCastOp>);
1072 if (!operand)
1073 return failure();
1074
1075 auto oldCastOp = operand->get().getDefiningOp<vector::ShapeCastOp>();
1076
1077 unsigned int operandNumber = operand->getOperandNumber();
1078 auto castDistributedType =
1079 cast<VectorType>(warpOp->getResultTypes()[operandNumber]);
1080 VectorType castOriginalType = oldCastOp.getSourceVectorType();
1081 VectorType castResultType = castDistributedType;
1082
1083 FailureOr<VectorType> maybeSrcType =
1084 inferDistributedSrcType(castDistributedType, castOriginalType);
1085 if (failed(maybeSrcType))
1086 return failure();
1087 castDistributedType = *maybeSrcType;
1088
1089 SmallVector<size_t> newRetIndices;
1090 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1091 rewriter, warpOp, {oldCastOp.getSource()}, {castDistributedType},
1092 newRetIndices);
1093 rewriter.setInsertionPointAfter(newWarpOp);
1094 Value newCast = vector::ShapeCastOp::create(
1095 rewriter, oldCastOp.getLoc(), castResultType,
1096 newWarpOp->getResult(newRetIndices[0]));
1097 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber), newCast);
1098 return success();
1099 }
1100
1101private:
1102 static FailureOr<VectorType>
1103 inferDistributedSrcType(VectorType distributedType, VectorType srcType) {
1104 unsigned distributedRank = distributedType.getRank();
1105 unsigned srcRank = srcType.getRank();
1106 if (distributedRank == srcRank)
1107 // Nothing to do.
1108 return distributedType;
1109 if (distributedRank < srcRank) {
1110 // If the distributed type has a smaller rank than the original type,
1111 // prepend with unit dimensions to make the types the same length.
1112 SmallVector<int64_t> shape(srcRank - distributedRank, 1);
1113 llvm::append_range(shape, distributedType.getShape());
1114 return VectorType::get(shape, distributedType.getElementType());
1115 }
1116 // Handle the expanding shape_cast's.
1117 //
1118 // If the casted-from type has one rank, we can assert that the element
1119 // count in that rank will match the full thread-level element count of
1120 // the yielded type.
1121 // Note that getNumElements() will correctly "flatten" the shape of the
1122 // specific shape_cast's distributed type (its distribution may be
1123 // different from the overall warp size, e.g. if the cast is applied to
1124 // a result of a gather).
1125 if (srcRank == 1)
1126 return VectorType::get(distributedType.getNumElements(),
1127 srcType.getElementType());
1128 // Try to strip leading unit dimensions to match the ranks. We bail out
1129 // for more complex tile sizes, because those would require us to
1130 // determine the specific distribution parameters to threads, which is
1131 // unfeasible within this pattern.
1132 unsigned excessDims = distributedRank - srcRank;
1133 ArrayRef<int64_t> shape = distributedType.getShape();
1134 if (!llvm::all_of(shape.take_front(excessDims),
1135 [](int64_t d) { return d == 1; }))
1136 return failure();
1137 return VectorType::get(shape.drop_front(excessDims),
1138 distributedType.getElementType());
1139 }
1140};
1141
1142/// Sink out vector.create_mask / vector.constant_mask op feeding into a warp op
1143/// yield.
1144/// ```
1145/// %0 = ...
1146/// %1 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<1xf32>) {
1147/// ...
1148/// %mask = vector.create_mask %0 : vector<32xi1>
1149/// // or %mask = vector.constant_mask[2] : vector<32xi1>
1150/// gpu.yield %mask : vector<32xi1>
1151/// }
1152/// ```
1153/// To
1154/// ```
1155/// %0 = ...
1156/// gpu.warp_execute_on_lane_0(%arg0) {
1157/// ...
1158/// }
1159/// %cmp = arith.cmpi ult, %laneid, %0
1160/// %ub = arith.select %cmp, %c0, %c1
1161/// %1 = vector.create_mask %ub : vector<1xi1>
1162template <typename OpType,
1163 typename = std::enable_if_t<llvm::is_one_of<
1164 OpType, vector::CreateMaskOp, vector::ConstantMaskOp>::value>>
1165struct WarpOpCreateMask : public WarpDistributionPattern {
1166 using Base::Base;
1167 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1168 PatternRewriter &rewriter) const override {
1169 OpOperand *yieldOperand = getWarpResult(warpOp, (llvm::IsaPred<OpType>));
1170 if (!yieldOperand)
1171 return failure();
1172
1173 Operation *mask = yieldOperand->get().getDefiningOp<OpType>();
1174
1175 // Early exit if any values needed for calculating the new mask indices
1176 // are defined inside the warp op.
1177 if (mask->getOperands().size() &&
1178 !llvm::all_of(mask->getOperands(), [&](Value value) {
1179 return warpOp.isDefinedOutsideOfRegion(value);
1180 }))
1181 return failure();
1182
1183 Location loc = mask->getLoc();
1184 unsigned operandIndex = yieldOperand->getOperandNumber();
1185
1186 auto distType = cast<VectorType>(warpOp.getResult(operandIndex).getType());
1187 VectorType seqType = cast<VectorType>(mask->getResult(0).getType());
1188 ArrayRef<int64_t> seqShape = seqType.getShape();
1189 ArrayRef<int64_t> distShape = distType.getShape();
1190 SmallVector<Value> materializedOperands;
1191 if constexpr (std::is_same_v<OpType, vector::CreateMaskOp>) {
1192 materializedOperands.append(mask->getOperands().begin(),
1193 mask->getOperands().end());
1194 } else {
1195 auto constantMaskOp = cast<vector::ConstantMaskOp>(mask);
1196 auto dimSizes = constantMaskOp.getMaskDimSizesAttr().asArrayRef();
1197 for (auto dimSize : dimSizes)
1198 materializedOperands.push_back(
1199 arith::ConstantIndexOp::create(rewriter, loc, dimSize).getResult());
1200 }
1201
1202 rewriter.setInsertionPointAfter(warpOp);
1203
1204 // Delinearize the lane ID for constructing the distributed mask sizes.
1205 SmallVector<Value> delinearizedIds;
1206 if (!delinearizeLaneId(rewriter, loc, seqShape, distShape,
1207 warpOp.getWarpSize(), warpOp.getLaneid(),
1208 delinearizedIds))
1209 return rewriter.notifyMatchFailure(
1210 mask, "cannot delinearize lane ID for distribution");
1211 assert(!delinearizedIds.empty());
1212
1213 // Notify the rewriter that the warp op is changing (see the comment on
1214 // the WarpOpTransferRead pattern).
1215 rewriter.startOpModification(warpOp);
1216
1217 AffineExpr s0, s1;
1218 bindSymbols(rewriter.getContext(), s0, s1);
1219 SmallVector<Value> newOperands;
1220 for (int i = 0, e = distShape.size(); i < e; ++i) {
1221 // Get `mask_dim_range_upper_limit[i] - lane_id[i] * dist_sizes[i]` to
1222 // find the distance from the largest mask index owned by this lane to the
1223 // original mask size. `vector.create_mask` implicitly clamps mask
1224 // operands to the range [0, mask_vector_size[i]], or in other words, the
1225 // mask sizes are always in the range [0, mask_vector_size[i]).
1226 Value maskDimIdx = affine::makeComposedAffineApply(
1227 rewriter, loc, s1 - s0 * distShape[i],
1228 {delinearizedIds[i], materializedOperands[i]});
1229 newOperands.push_back(maskDimIdx);
1230 }
1231
1232 auto newMask =
1233 vector::CreateMaskOp::create(rewriter, loc, distType, newOperands);
1234 rewriter.replaceAllUsesWith(warpOp.getResult(operandIndex), newMask);
1235 rewriter.finalizeOpModification(warpOp);
1236 return success();
1237 }
1238};
1239
1240/// Sink out insert_strided_slice op feeding into a warp op yield.
1241/// ```
1242/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<8x1xf32>) {
1243/// ...
1244/// %src = ... : vector<4x32xf32>
1245/// %dest = ... : vector<8x32xf32>
1246/// %insert = vector.insert_strided_slice %src, %dest, offsets = [0, 0],
1247/// strides = [1, 1] : vector<4x32xf32> into vector<8x32xf32>
1248/// gpu.yield %insert : vector<8x32xf32>
1249/// }
1250/// ```
1251/// To
1252/// ```
1253/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<4x1xf32>,
1254/// vector<8x1xf32>) {
1255/// ...
1256/// %src = ... : vector<4x32xf32>
1257/// %dest = ... : vector<8x32xf32>
1258/// gpu.yield %src, %dest : vector<4x16xf32>, vector<8x16xf32>
1259/// }
1260/// %insert = vector.insert_strided_slice %0#0, %0#1,
1261/// offsets = [0, 0], strides = [1, 1] : vector<4x1xf32> into vector<8x1xf32>
1262/// ```
1263/// NOTE: Current support assumes that both src and dest vectors are distributed
1264/// to lanes and sinking the insert op does not require any cross lane
1265/// communication.
1266struct WarpOpInsertStridedSlice : public WarpDistributionPattern {
1267 using Base::Base;
1268 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1269 PatternRewriter &rewriter) const override {
1270 OpOperand *operand =
1271 getWarpResult(warpOp, llvm::IsaPred<vector::InsertStridedSliceOp>);
1272 if (!operand)
1273 return failure();
1274 unsigned int operandNumber = operand->getOperandNumber();
1275 auto insertOp =
1276 operand->get().getDefiningOp<vector::InsertStridedSliceOp>();
1277 auto distributedType =
1278 cast<VectorType>(warpOp.getResult(operandNumber).getType());
1279 // Distributed type must be 2D or higher.
1280 // TODO: Support 1D distributed types.
1281 if (distributedType.getRank() < 2)
1282 return rewriter.notifyMatchFailure(
1283 insertOp, "result vector type must be 2D or higher");
1284 // Find the distributed dimension of the dest vector. There should be
1285 // exactly one.
1286 auto yieldedType = cast<VectorType>(operand->get().getType());
1287 int64_t destDistributedDim =
1288 getDistributedDim(yieldedType, distributedType);
1289 assert(destDistributedDim != -1 && "could not find distributed dimension");
1290
1291 VectorType srcType = insertOp.getSourceVectorType();
1292 VectorType destType = insertOp.getDestVectorType();
1293 // Currently we require that both source (kD) and dest (nD) vectors are
1294 // distributed. This requires that distributedDim (d) is contained in the
1295 // last k dims of the dest vector (d >= n - k).
1296 // TODO: Add support for case where source vector is not distributed.
1297 int64_t sourceDistributedDim =
1298 destDistributedDim - (destType.getRank() - srcType.getRank());
1299 if (sourceDistributedDim < 0)
1300 return rewriter.notifyMatchFailure(
1301 insertOp,
1302 "distributed dimension must be in the last k dims of dest vector");
1303 // Distributed dimension must be fully inserted.
1304 if (srcType.getDimSize(sourceDistributedDim) !=
1305 destType.getDimSize(destDistributedDim))
1306 return rewriter.notifyMatchFailure(
1307 insertOp, "distributed dimension must be fully inserted");
1308 SmallVector<int64_t> newSourceDistShape(
1309 insertOp.getSourceVectorType().getShape());
1310 newSourceDistShape[sourceDistributedDim] =
1311 distributedType.getDimSize(destDistributedDim);
1312 auto newSourceTy =
1313 VectorType::get(newSourceDistShape, distributedType.getElementType());
1314 VectorType newDestTy = distributedType;
1315 SmallVector<size_t> newRetIndices;
1316 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1317 rewriter, warpOp, {insertOp.getValueToStore(), insertOp.getDest()},
1318 {newSourceTy, newDestTy}, newRetIndices);
1319 rewriter.setInsertionPointAfter(newWarpOp);
1320 Value distributedSource = newWarpOp->getResult(newRetIndices[0]);
1321 Value distributedDest = newWarpOp->getResult(newRetIndices[1]);
1322 // Create a new insert strided slice op that inserts distributed source into
1323 // distributed dest.
1324 Value newInsert = vector::InsertStridedSliceOp::create(
1325 rewriter, insertOp.getLoc(), distributedDest.getType(),
1326 distributedSource, distributedDest, insertOp.getOffsets(),
1327 insertOp.getStrides());
1328 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber), newInsert);
1329 return success();
1330 }
1331};
1332
1333/// Sink out extract_strided_slice op feeding into a warp op yield.
1334/// ```
1335/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<16x1xf32>) {
1336/// ...
1337/// %src = ... : vector<64x32xf32>
1338/// %extract = vector.extract_strided_slice %src, offsets = [0], sizes = [16],
1339/// strides = [1] : vector<64x32xf32> to vector<16x32xf32>
1340/// gpu.yield %extract : vector<16x32xf32>
1341/// }
1342/// ```
1343/// To
1344/// ```
1345/// %0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<64x1xf32>) {
1346/// ...
1347/// %src = ... : vector<64x32xf32>
1348/// gpu.yield %src : vector<64x32xf32>
1349/// }
1350/// %extract = vector.extract_strided_slice %0, offsets = [0], sizes = [16],
1351/// strides = [1] : vector<64x1xf32> to vector<16x1xf32>
1352/// ```
1353/// NOTE: Current support assumes that the extraction happens only on non
1354/// distributed dimensions (does not require cross lane communication).
1355struct WarpOpExtractStridedSlice : public WarpDistributionPattern {
1356 using Base::Base;
1357 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1358 PatternRewriter &rewriter) const override {
1359 OpOperand *operand =
1360 getWarpResult(warpOp, llvm::IsaPred<vector::ExtractStridedSliceOp>);
1361 if (!operand)
1362 return failure();
1363 unsigned int operandNumber = operand->getOperandNumber();
1364 auto extractOp =
1365 operand->get().getDefiningOp<vector::ExtractStridedSliceOp>();
1366 auto distributedType =
1367 cast<VectorType>(warpOp.getResult(operandNumber).getType());
1368 // Distributed type must be 2D or higher.
1369 // TODO: Support 1D distributed types.
1370 if (distributedType.getRank() < 2)
1371 return rewriter.notifyMatchFailure(
1372 extractOp, "result vector type must be 2D or higher");
1373
1374 // Find the distributed dimension. There should be exactly one.
1375 auto yieldedType = cast<VectorType>(operand->get().getType());
1376 int64_t distributedDim = getDistributedDim(yieldedType, distributedType);
1377 assert(distributedDim != -1 && "could not find distributed dimension");
1378
1379 int64_t numOfExtractedDims =
1380 static_cast<int64_t>(extractOp.getSizes().size());
1381 // If the distributed dim is included in the extracted dims, then we make
1382 // sure distributed dim is fully extracted. If distributed dim is not
1383 // included in extracted dims, it is guaranteed to be fully extracted (i.e.
1384 // distributed dim comes after all the extracted dims)
1385 // TODO: Partial extraction from distributed dimension require cross lane
1386 // communication.
1387 if (distributedDim < numOfExtractedDims) {
1388 int64_t distributedDimOffset =
1389 llvm::cast<IntegerAttr>(extractOp.getOffsets()[distributedDim])
1390 .getInt();
1391 int64_t distributedDimSize =
1392 llvm::cast<IntegerAttr>(extractOp.getSizes()[distributedDim])
1393 .getInt();
1394 if (distributedDimOffset != 0 ||
1395 distributedDimSize != yieldedType.getDimSize(distributedDim))
1396 return rewriter.notifyMatchFailure(
1397 extractOp, "distributed dimension must be fully extracted");
1398 }
1399 SmallVector<int64_t> newDistributedShape(
1400 extractOp.getSourceVectorType().getShape());
1401 newDistributedShape[distributedDim] =
1402 distributedType.getDimSize(distributedDim);
1403 auto newDistributedType =
1404 VectorType::get(newDistributedShape, distributedType.getElementType());
1405 SmallVector<size_t> newRetIndices;
1406 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1407 rewriter, warpOp, {extractOp.getSource()}, {newDistributedType},
1408 newRetIndices);
1409 rewriter.setInsertionPointAfter(newWarpOp);
1410 SmallVector<Attribute> distributedSizes = llvm::map_to_vector(
1411 extractOp.getSizes(), [](Attribute attr) { return attr; });
1412 // Update the distributed sizes to match the distributed type.
1413 if (distributedDim < static_cast<int64_t>(distributedSizes.size()))
1414 distributedSizes[distributedDim] = rewriter.getI64IntegerAttr(
1415 distributedType.getDimSize(distributedDim));
1416
1417 // Create a new extract strided slice op that extracts from the
1418 // distributed vector.
1419 Value distributedVec = newWarpOp->getResult(newRetIndices[0]);
1420 Value newExtract = vector::ExtractStridedSliceOp::create(
1421 rewriter, extractOp.getLoc(), distributedType, distributedVec,
1422 extractOp.getOffsets(),
1423 ArrayAttr::get(rewriter.getContext(), distributedSizes),
1424 extractOp.getStrides());
1425 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1426 newExtract);
1427 return success();
1428 }
1429};
1430
1431/// Pattern to move out vector.extract of single element vector. Those don't
1432/// need to be distributed and can just be propagated outside of the region.
1433struct WarpOpExtract : public WarpDistributionPattern {
1434 using Base::Base;
1435 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1436 PatternRewriter &rewriter) const override {
1437 OpOperand *operand =
1438 getWarpResult(warpOp, llvm::IsaPred<vector::ExtractOp>);
1439 if (!operand)
1440 return failure();
1441 unsigned int operandNumber = operand->getOperandNumber();
1442 auto extractOp = operand->get().getDefiningOp<vector::ExtractOp>();
1443 VectorType extractSrcType = extractOp.getSourceVectorType();
1444 Location loc = extractOp.getLoc();
1445
1446 // For 1-d or 0-d source cases, we rely on WarpOpExtractScalar pattern.
1447 if (extractSrcType.getRank() <= 1) {
1448 return failure();
1449 }
1450
1451 // All following cases are 2d or higher dimensional source vectors.
1452
1453 if (warpOp.getResult(operandNumber).getType() == operand->get().getType()) {
1454 // There is no distribution, this is a broadcast. Simply move the extract
1455 // out of the warp op.
1456 // TODO: This could be optimized. E.g., in case of a scalar result, let
1457 // one lane extract and shuffle the result to all other lanes (same as
1458 // the 1d case).
1459 SmallVector<size_t> newRetIndices;
1460 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1461 rewriter, warpOp, {extractOp.getSource()},
1462 {extractOp.getSourceVectorType()}, newRetIndices);
1463 rewriter.setInsertionPointAfter(newWarpOp);
1464 Value distributedVec = newWarpOp->getResult(newRetIndices[0]);
1465 // Extract from distributed vector.
1466 Value newExtract = vector::ExtractOp::create(
1467 rewriter, loc, distributedVec, extractOp.getMixedPosition());
1468 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1469 newExtract);
1470 return success();
1471 }
1472
1473 // Find the distributed dimension. There should be exactly one.
1474 auto distributedType =
1475 cast<VectorType>(warpOp.getResult(operandNumber).getType());
1476 auto yieldedType = cast<VectorType>(operand->get().getType());
1477 int64_t distributedDim = getDistributedDim(yieldedType, distributedType);
1478 assert(distributedDim != -1 && "could not find distributed dimension");
1479 (void)distributedDim;
1480
1481 // Yield source vector from warp op.
1482 SmallVector<int64_t> newDistributedShape(extractSrcType.getShape());
1483 for (int i = 0; i < distributedType.getRank(); ++i)
1484 newDistributedShape[i + extractOp.getNumIndices()] =
1485 distributedType.getDimSize(i);
1486 auto newDistributedType =
1487 VectorType::get(newDistributedShape, distributedType.getElementType());
1488 SmallVector<size_t> newRetIndices;
1489 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1490 rewriter, warpOp, {extractOp.getSource()}, {newDistributedType},
1491 newRetIndices);
1492 rewriter.setInsertionPointAfter(newWarpOp);
1493 Value distributedVec = newWarpOp->getResult(newRetIndices[0]);
1494 // Extract from distributed vector.
1495 Value newExtract = vector::ExtractOp::create(rewriter, loc, distributedVec,
1496 extractOp.getMixedPosition());
1497 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1498 newExtract);
1499 return success();
1500 }
1501};
1502
1503/// Pattern to move out vector.extract with a scalar result.
1504/// Only supports 1-D and 0-D sources for now.
1505struct WarpOpExtractScalar : public WarpDistributionPattern {
1506 WarpOpExtractScalar(MLIRContext *ctx, WarpShuffleFromIdxFn fn,
1507 PatternBenefit b = 1)
1508 : WarpDistributionPattern(ctx, b), warpShuffleFromIdxFn(std::move(fn)) {}
1509 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1510 PatternRewriter &rewriter) const override {
1511 OpOperand *operand =
1512 getWarpResult(warpOp, llvm::IsaPred<vector::ExtractOp>);
1513 if (!operand)
1514 return failure();
1515 unsigned int operandNumber = operand->getOperandNumber();
1516 auto extractOp = operand->get().getDefiningOp<vector::ExtractOp>();
1517 VectorType extractSrcType = extractOp.getSourceVectorType();
1518 // Only supports 1-D or 0-D sources for now.
1519 if (extractSrcType.getRank() > 1) {
1520 return rewriter.notifyMatchFailure(
1521 extractOp, "only 0-D or 1-D source supported for now");
1522 }
1523 // TODO: Supported shuffle types should be parameterizable, similar to
1524 // `WarpShuffleFromIdxFn`.
1525 if (!extractSrcType.getElementType().isF32() &&
1526 !extractSrcType.getElementType().isInteger(32))
1527 return rewriter.notifyMatchFailure(
1528 extractOp, "only f32/i32 element types are supported");
1529 bool is0dOrVec1Extract = extractSrcType.getNumElements() == 1;
1530 Type elType = extractSrcType.getElementType();
1531 VectorType distributedVecType;
1532 if (!is0dOrVec1Extract) {
1533 assert(extractSrcType.getRank() == 1 &&
1534 "expected that extract src rank is 0 or 1");
1535 if (extractSrcType.getShape()[0] % warpOp.getWarpSize() != 0)
1536 return failure();
1537 int64_t elementsPerLane =
1538 extractSrcType.getShape()[0] / warpOp.getWarpSize();
1539 distributedVecType = VectorType::get({elementsPerLane}, elType);
1540 } else {
1541 distributedVecType = extractSrcType;
1542 }
1543 // Yield source vector and position (if present) from warp op.
1544 SmallVector<Value> additionalResults{extractOp.getSource()};
1545 SmallVector<Type> additionalResultTypes{distributedVecType};
1546 additionalResults.append(
1547 SmallVector<Value>(extractOp.getDynamicPosition()));
1548 additionalResultTypes.append(
1549 SmallVector<Type>(extractOp.getDynamicPosition().getTypes()));
1550
1551 Location loc = extractOp.getLoc();
1552 SmallVector<size_t> newRetIndices;
1553 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1554 rewriter, warpOp, additionalResults, additionalResultTypes,
1555 newRetIndices);
1556 rewriter.setInsertionPointAfter(newWarpOp);
1557 Value distributedVec = newWarpOp->getResult(newRetIndices[0]);
1558
1559 // 0d extract: The new warp op broadcasts the source vector to all lanes.
1560 // All lanes extract the scalar.
1561 if (is0dOrVec1Extract) {
1562 Value newExtract;
1563 SmallVector<int64_t> indices(extractSrcType.getRank(), 0);
1564 newExtract =
1565 vector::ExtractOp::create(rewriter, loc, distributedVec, indices);
1566 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1567 newExtract);
1568 return success();
1569 }
1570
1571 int64_t staticPos = extractOp.getStaticPosition()[0];
1572 OpFoldResult pos = ShapedType::isDynamic(staticPos)
1573 ? (newWarpOp->getResult(newRetIndices[1]))
1574 : OpFoldResult(rewriter.getIndexAttr(staticPos));
1575 // 1d extract: Distribute the source vector. One lane extracts and shuffles
1576 // the value to all other lanes.
1577 int64_t elementsPerLane = distributedVecType.getShape()[0];
1578 AffineExpr sym0 = getAffineSymbolExpr(0, rewriter.getContext());
1579 // tid of extracting thread: pos / elementsPerLane
1580 Value broadcastFromTid = affine::makeComposedAffineApply(
1581 rewriter, loc, sym0.ceilDiv(elementsPerLane), pos);
1582 // Extract at position: pos % elementsPerLane
1583 Value newPos =
1584 elementsPerLane == 1
1585 ? arith::ConstantIndexOp::create(rewriter, loc, 0).getResult()
1586 : affine::makeComposedAffineApply(rewriter, loc,
1587 sym0 % elementsPerLane, pos);
1588 Value extracted =
1589 vector::ExtractOp::create(rewriter, loc, distributedVec, newPos);
1590
1591 // Shuffle the extracted value to all lanes.
1592 Value shuffled = warpShuffleFromIdxFn(
1593 loc, rewriter, extracted, broadcastFromTid, newWarpOp.getWarpSize());
1594 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber), shuffled);
1595 return success();
1596 }
1597
1598private:
1599 WarpShuffleFromIdxFn warpShuffleFromIdxFn;
1600};
1601
1602/// Pattern to move out vector.insert with a scalar input.
1603/// Only supports 1-D and 0-D destinations for now.
1604struct WarpOpInsertScalar : public WarpDistributionPattern {
1605 using Base::Base;
1606 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1607 PatternRewriter &rewriter) const override {
1608 OpOperand *operand = getWarpResult(warpOp, llvm::IsaPred<vector::InsertOp>);
1609 if (!operand)
1610 return failure();
1611 unsigned int operandNumber = operand->getOperandNumber();
1612 auto insertOp = operand->get().getDefiningOp<vector::InsertOp>();
1613 VectorType vecType = insertOp.getDestVectorType();
1614 VectorType distrType =
1615 cast<VectorType>(warpOp.getResult(operandNumber).getType());
1616
1617 // Only supports 1-D or 0-D destinations for now.
1618 if (vecType.getRank() > 1) {
1619 return rewriter.notifyMatchFailure(
1620 insertOp, "only 0-D or 1-D source supported for now");
1621 }
1622
1623 // Yield destination vector, source scalar and position from warp op.
1624 SmallVector<Value> additionalResults{insertOp.getDest(),
1625 insertOp.getValueToStore()};
1626 SmallVector<Type> additionalResultTypes{
1627 distrType, insertOp.getValueToStore().getType()};
1628 additionalResults.append(SmallVector<Value>(insertOp.getDynamicPosition()));
1629 additionalResultTypes.append(
1630 SmallVector<Type>(insertOp.getDynamicPosition().getTypes()));
1631
1632 Location loc = insertOp.getLoc();
1633 SmallVector<size_t> newRetIndices;
1634 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1635 rewriter, warpOp, additionalResults, additionalResultTypes,
1636 newRetIndices);
1637 rewriter.setInsertionPointAfter(newWarpOp);
1638 Value distributedVec = newWarpOp->getResult(newRetIndices[0]);
1639 Value newSource = newWarpOp->getResult(newRetIndices[1]);
1640 rewriter.setInsertionPointAfter(newWarpOp);
1641
1642 OpFoldResult pos;
1643 if (vecType.getRank() != 0) {
1644 int64_t staticPos = insertOp.getStaticPosition()[0];
1645 pos = ShapedType::isDynamic(staticPos)
1646 ? (newWarpOp->getResult(newRetIndices[2]))
1647 : OpFoldResult(rewriter.getIndexAttr(staticPos));
1648 }
1649
1650 // This condition is always true for 0-d vectors.
1651 if (vecType == distrType) {
1652 Value newInsert;
1653 SmallVector<OpFoldResult> indices;
1654 if (pos) {
1655 indices.push_back(pos);
1656 }
1657 newInsert = vector::InsertOp::create(rewriter, loc, newSource,
1658 distributedVec, indices);
1659 // Broadcast: Simply move the vector.insert op out.
1660 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1661 newInsert);
1662 return success();
1663 }
1664
1665 // This is a distribution. Only one lane should insert.
1666 int64_t elementsPerLane = distrType.getShape()[0];
1667 AffineExpr sym0 = getAffineSymbolExpr(0, rewriter.getContext());
1668 // tid of extracting thread: pos / elementsPerLane
1669 Value insertingLane = affine::makeComposedAffineApply(
1670 rewriter, loc, sym0.ceilDiv(elementsPerLane), pos);
1671 // Insert position: pos % elementsPerLane
1672 OpFoldResult newPos = affine::makeComposedFoldedAffineApply(
1673 rewriter, loc, sym0 % elementsPerLane, pos);
1674 Value isInsertingLane =
1675 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1676 newWarpOp.getLaneid(), insertingLane);
1677 Value newResult =
1678 scf::IfOp::create(
1679 rewriter, loc, isInsertingLane,
1680 /*thenBuilder=*/
1681 [&](OpBuilder &builder, Location loc) {
1682 Value newInsert = vector::InsertOp::create(
1683 builder, loc, newSource, distributedVec, newPos);
1684 scf::YieldOp::create(builder, loc, newInsert);
1685 },
1686 /*elseBuilder=*/
1687 [&](OpBuilder &builder, Location loc) {
1688 scf::YieldOp::create(builder, loc, distributedVec);
1689 })
1690 .getResult(0);
1691 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber), newResult);
1692 return success();
1693 }
1694};
1695
1696struct WarpOpInsert : public WarpDistributionPattern {
1697 using Base::Base;
1698 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1699 PatternRewriter &rewriter) const override {
1700 OpOperand *operand = getWarpResult(warpOp, llvm::IsaPred<vector::InsertOp>);
1701 if (!operand)
1702 return failure();
1703 unsigned int operandNumber = operand->getOperandNumber();
1704 auto insertOp = operand->get().getDefiningOp<vector::InsertOp>();
1705 Location loc = insertOp.getLoc();
1706
1707 // For 1-d or 0-d destination cases, we rely on WarpOpInsertScalar pattern.
1708 if (insertOp.getDestVectorType().getRank() <= 1) {
1709 return failure();
1710 }
1711
1712 // All following cases are 2d or higher dimensional source vectors.
1713
1714 if (warpOp.getResult(operandNumber).getType() == operand->get().getType()) {
1715 // There is no distribution, this is a broadcast. Simply move the insert
1716 // out of the warp op.
1717 SmallVector<size_t> newRetIndices;
1718 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1719 rewriter, warpOp, {insertOp.getValueToStore(), insertOp.getDest()},
1720 {insertOp.getValueToStoreType(), insertOp.getDestVectorType()},
1721 newRetIndices);
1722 rewriter.setInsertionPointAfter(newWarpOp);
1723 Value distributedSrc = newWarpOp->getResult(newRetIndices[0]);
1724 Value distributedDest = newWarpOp->getResult(newRetIndices[1]);
1725 Value newResult = vector::InsertOp::create(rewriter, loc, distributedSrc,
1726 distributedDest,
1727 insertOp.getMixedPosition());
1728 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber),
1729 newResult);
1730 return success();
1731 }
1732
1733 // Find the distributed dimension. There should be exactly one.
1734 auto distrDestType =
1735 cast<VectorType>(warpOp.getResult(operandNumber).getType());
1736 auto yieldedType = cast<VectorType>(operand->get().getType());
1737 int64_t distrDestDim = -1;
1738 for (int64_t i = 0; i < yieldedType.getRank(); ++i) {
1739 if (distrDestType.getDimSize(i) != yieldedType.getDimSize(i)) {
1740 // Keep this assert here in case WarpExecuteOnLane0Op gets extended to
1741 // support distributing multiple dimensions in the future.
1742 assert(distrDestDim == -1 && "found multiple distributed dims");
1743 distrDestDim = i;
1744 }
1745 }
1746 assert(distrDestDim != -1 && "could not find distributed dimension");
1747
1748 // Compute the distributed source vector type.
1749 VectorType srcVecType = cast<VectorType>(insertOp.getValueToStoreType());
1750 SmallVector<int64_t> distrSrcShape(srcVecType.getShape());
1751 // E.g.: vector.insert %s, %d [2] : vector<96xf32> into vector<128x96xf32>
1752 // Case 1: distrDestDim = 1 (dim of size 96). In that case, each lane will
1753 // insert a smaller vector<3xf32>.
1754 // Case 2: distrDestDim = 0 (dim of size 128) => distrSrcDim = -1. In that
1755 // case, one lane will insert the source vector<96xf32>. The other
1756 // lanes will not do anything.
1757 int64_t distrSrcDim = distrDestDim - insertOp.getNumIndices();
1758 if (distrSrcDim >= 0)
1759 distrSrcShape[distrSrcDim] = distrDestType.getDimSize(distrDestDim);
1760 auto distrSrcType =
1761 VectorType::get(distrSrcShape, distrDestType.getElementType());
1762
1763 // Yield source and dest vectors from warp op.
1764 SmallVector<size_t> newRetIndices;
1765 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1766 rewriter, warpOp, {insertOp.getValueToStore(), insertOp.getDest()},
1767 {distrSrcType, distrDestType}, newRetIndices);
1768 rewriter.setInsertionPointAfter(newWarpOp);
1769 Value distributedSrc = newWarpOp->getResult(newRetIndices[0]);
1770 Value distributedDest = newWarpOp->getResult(newRetIndices[1]);
1771
1772 // Insert into the distributed vector.
1773 Value newResult;
1774 if (distrSrcDim >= 0) {
1775 // Every lane inserts a small piece.
1776 newResult = vector::InsertOp::create(rewriter, loc, distributedSrc,
1777 distributedDest,
1778 insertOp.getMixedPosition());
1779 } else {
1780 // One lane inserts the entire source vector.
1781 int64_t elementsPerLane = distrDestType.getDimSize(distrDestDim);
1782 SmallVector<OpFoldResult> pos = insertOp.getMixedPosition();
1783 SmallVector<int64_t> newPos = getAsIntegers(pos);
1784 // tid of inserting lane: pos / elementsPerLane
1785 Value insertingLane = arith::ConstantIndexOp::create(
1786 rewriter, loc, newPos[distrDestDim] / elementsPerLane);
1787 Value isInsertingLane =
1788 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1789 newWarpOp.getLaneid(), insertingLane);
1790 // Insert position: pos % elementsPerLane
1791 newPos[distrDestDim] %= elementsPerLane;
1792 auto insertingBuilder = [&](OpBuilder &builder, Location loc) {
1793 Value newInsert = vector::InsertOp::create(builder, loc, distributedSrc,
1794 distributedDest, newPos);
1795 scf::YieldOp::create(builder, loc, newInsert);
1796 };
1797 auto nonInsertingBuilder = [&](OpBuilder &builder, Location loc) {
1798 scf::YieldOp::create(builder, loc, distributedDest);
1799 };
1800 newResult = scf::IfOp::create(rewriter, loc, isInsertingLane,
1801 /*thenBuilder=*/insertingBuilder,
1802 /*elseBuilder=*/nonInsertingBuilder)
1803 .getResult(0);
1804 }
1805
1806 rewriter.replaceAllUsesWith(newWarpOp->getResult(operandNumber), newResult);
1807 return success();
1808 }
1809};
1810
1811/// Sink scf.if out of WarpExecuteOnLane0Op. This can be done only if
1812/// the scf.if is the last operation in the region so that it doesn't
1813/// change the order of execution. This creates a new scf.if after the
1814/// WarpExecuteOnLane0Op. Each branch of the new scf.if is enclosed in
1815/// the "inner" WarpExecuteOnLane0Op. Example:
1816/// ```
1817/// gpu.warp_execute_on_lane_0(%laneid)[32] {
1818/// %payload = ... : vector<32xindex>
1819/// scf.if %pred {
1820/// vector.store %payload, %buffer[%idx] : memref<128xindex>,
1821/// vector<32xindex>
1822/// }
1823/// gpu.yield
1824/// }
1825/// ```
1826/// %r = gpu.warp_execute_on_lane_0(%laneid)[32] {
1827/// %payload = ... : vector<32xindex>
1828/// gpu.yield %payload : vector<32xindex>
1829/// }
1830/// scf.if %pred {
1831/// gpu.warp_execute_on_lane_0(%laneid)[32] args(%r : vector<1xindex>) {
1832/// ^bb0(%arg1: vector<32xindex>):
1833/// vector.store %arg1, %buffer[%idx] : memref<128xindex>, vector<32xindex>
1834/// }
1835/// }
1836/// ```
1837struct WarpOpScfIfOp : public WarpDistributionPattern {
1838 WarpOpScfIfOp(MLIRContext *ctx, DistributionMapFn fn, PatternBenefit b = 1)
1839 : WarpDistributionPattern(ctx, b), distributionMapFn(std::move(fn)) {}
1840 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
1841 PatternRewriter &rewriter) const override {
1842 gpu::YieldOp warpOpYield = warpOp.getTerminator();
1843 // Only pick up `IfOp` if it is the last op in the region.
1844 Operation *lastNode = warpOpYield->getPrevNode();
1845 auto ifOp = dyn_cast_or_null<scf::IfOp>(lastNode);
1846 if (!ifOp)
1847 return failure();
1848
1849 // The current `WarpOp` can yield two types of values:
1850 // 1. Not results of `IfOp`:
1851 // Preserve them in the new `WarpOp`.
1852 // Collect their yield index to remap the usages.
1853 // 2. Results of `IfOp`:
1854 // They are not part of the new `WarpOp` results.
1855 // Map current warp's yield operand index to `IfOp` result idx.
1856 SmallVector<Value> nonIfYieldValues;
1857 SmallVector<unsigned> nonIfYieldIndices;
1858 llvm::SmallDenseMap<unsigned, unsigned> ifResultMapping;
1859 llvm::SmallDenseMap<unsigned, VectorType> ifResultDistTypes;
1860 for (OpOperand &yieldOperand : warpOpYield->getOpOperands()) {
1861 const unsigned yieldOperandIdx = yieldOperand.getOperandNumber();
1862 if (yieldOperand.get().getDefiningOp() != ifOp.getOperation()) {
1863 nonIfYieldValues.push_back(yieldOperand.get());
1864 nonIfYieldIndices.push_back(yieldOperandIdx);
1865 continue;
1866 }
1867 OpResult ifResult = cast<OpResult>(yieldOperand.get());
1868 const unsigned ifResultIdx = ifResult.getResultNumber();
1869 ifResultMapping[yieldOperandIdx] = ifResultIdx;
1870 // If this `ifOp` result is vector type and it is yielded by the
1871 // `WarpOp`, we keep track the distributed type for this result.
1872 if (!isa<VectorType>(ifResult.getType()))
1873 continue;
1874 VectorType distType =
1875 cast<VectorType>(warpOp.getResult(yieldOperandIdx).getType());
1876 ifResultDistTypes[ifResultIdx] = distType;
1877 }
1878
1879 // Collect `WarpOp`-defined values used in `ifOp`, the new warp op returns
1880 // them
1881 auto [escapingValuesThen, escapingValueInputTypesThen,
1882 escapingValueDistTypesThen] =
1883 getInnerRegionEscapingValues(warpOp, ifOp.getThenRegion(),
1884 distributionMapFn);
1885 auto [escapingValuesElse, escapingValueInputTypesElse,
1886 escapingValueDistTypesElse] =
1887 getInnerRegionEscapingValues(warpOp, ifOp.getElseRegion(),
1888 distributionMapFn);
1889 if (llvm::is_contained(escapingValueDistTypesThen, Type{}) ||
1890 llvm::is_contained(escapingValueDistTypesElse, Type{}))
1891 return failure();
1892
1893 // The new `WarpOp` groups yields values in following order:
1894 // 1. Branch condition
1895 // 2. Escaping values then branch
1896 // 3. Escaping values else branch
1897 // 4. All non-`ifOp` yielded values.
1898 SmallVector<Value> newWarpOpYieldValues{ifOp.getCondition()};
1899 newWarpOpYieldValues.append(escapingValuesThen.begin(),
1900 escapingValuesThen.end());
1901 newWarpOpYieldValues.append(escapingValuesElse.begin(),
1902 escapingValuesElse.end());
1903 SmallVector<Type> newWarpOpDistTypes{ifOp.getCondition().getType()};
1904 newWarpOpDistTypes.append(escapingValueDistTypesThen.begin(),
1905 escapingValueDistTypesThen.end());
1906 newWarpOpDistTypes.append(escapingValueDistTypesElse.begin(),
1907 escapingValueDistTypesElse.end());
1908
1909 for (auto [idx, val] :
1910 llvm::zip_equal(nonIfYieldIndices, nonIfYieldValues)) {
1911 newWarpOpYieldValues.push_back(val);
1912 newWarpOpDistTypes.push_back(warpOp.getResult(idx).getType());
1913 }
1914 // Replace the old `WarpOp` with the new one that has additional yield
1915 // values and types.
1916 SmallVector<size_t> newIndices;
1917 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
1918 rewriter, warpOp, newWarpOpYieldValues, newWarpOpDistTypes, newIndices);
1919 // `ifOp` returns the result of the inner warp op.
1920 SmallVector<Type> newIfOpDistResTypes;
1921 for (auto [i, res] : llvm::enumerate(ifOp.getResults())) {
1922 Type distType = cast<Value>(res).getType();
1923 if (auto vecType = dyn_cast<VectorType>(distType)) {
1924 AffineMap map = distributionMapFn(cast<Value>(res));
1925 // Fallback to affine map if the dist result was not previously recorded
1926 distType = ifResultDistTypes.count(i)
1927 ? ifResultDistTypes[i]
1928 : getDistributedType(
1929 vecType, map,
1930 map.isEmpty() ? 1 : newWarpOp.getWarpSize());
1931 }
1932 newIfOpDistResTypes.push_back(distType);
1933 }
1934 // Create a new `IfOp` outside the new `WarpOp` region.
1935 OpBuilder::InsertionGuard g(rewriter);
1936 rewriter.setInsertionPointAfter(newWarpOp);
1937 auto newIfOp = scf::IfOp::create(
1938 rewriter, ifOp.getLoc(), newIfOpDistResTypes,
1939 newWarpOp.getResult(newIndices[0]), static_cast<bool>(ifOp.thenBlock()),
1940 static_cast<bool>(ifOp.elseBlock()));
1941 auto encloseRegionInWarpOp =
1942 [&](Block *oldIfBranch, Block *newIfBranch,
1943 llvm::SmallSetVector<Value, 32> &escapingValues,
1944 SmallVector<Type> &escapingValueInputTypes,
1945 size_t warpResRangeStart) {
1946 OpBuilder::InsertionGuard g(rewriter);
1947 if (!newIfBranch)
1948 return;
1949 rewriter.setInsertionPointToStart(newIfBranch);
1950 llvm::SmallDenseMap<Value, int64_t> escapeValToBlockArgIndex;
1951 SmallVector<Value> innerWarpInputVals;
1952 SmallVector<Type> innerWarpInputTypes;
1953 for (size_t i = 0; i < escapingValues.size();
1954 ++i, ++warpResRangeStart) {
1955 innerWarpInputVals.push_back(
1956 newWarpOp.getResult(newIndices[warpResRangeStart]));
1957 escapeValToBlockArgIndex[escapingValues[i]] =
1958 innerWarpInputTypes.size();
1959 innerWarpInputTypes.push_back(escapingValueInputTypes[i]);
1960 }
1961 auto innerWarp = WarpExecuteOnLane0Op::create(
1962 rewriter, newWarpOp.getLoc(), newIfOp.getResultTypes(),
1963 newWarpOp.getLaneid(), newWarpOp.getWarpSize(),
1964 innerWarpInputVals, innerWarpInputTypes);
1965
1966 innerWarp.getWarpRegion().takeBody(*oldIfBranch->getParent());
1967 innerWarp.getWarpRegion().addArguments(
1968 innerWarpInputTypes,
1969 SmallVector<Location>(innerWarpInputTypes.size(), ifOp.getLoc()));
1970
1971 SmallVector<Value> yieldOperands;
1972 for (Value operand : oldIfBranch->getTerminator()->getOperands())
1973 yieldOperands.push_back(operand);
1974 rewriter.eraseOp(oldIfBranch->getTerminator());
1975
1976 rewriter.setInsertionPointToEnd(innerWarp.getBody());
1977 gpu::YieldOp::create(rewriter, innerWarp.getLoc(), yieldOperands);
1978 rewriter.setInsertionPointAfter(innerWarp);
1979 scf::YieldOp::create(rewriter, ifOp.getLoc(), innerWarp.getResults());
1980
1981 // Update any users of escaping values that were forwarded to the
1982 // inner `WarpOp`. These values are arguments of the inner `WarpOp`.
1983 innerWarp.walk([&](Operation *op) {
1984 SmallVector<std::pair<unsigned, Value>> replacements;
1985 for (OpOperand &operand : op->getOpOperands()) {
1986 auto it = escapeValToBlockArgIndex.find(operand.get());
1987 if (it == escapeValToBlockArgIndex.end())
1988 continue;
1989 replacements.emplace_back(
1990 operand.getOperandNumber(),
1991 innerWarp.getBodyRegion().getArgument(it->second));
1992 }
1993 if (!replacements.empty()) {
1994 rewriter.modifyOpInPlace(op, [&]() {
1995 for (auto [idx, newVal] : replacements)
1996 op->setOperand(idx, newVal);
1997 });
1998 }
1999 });
2000 mlir::vector::moveScalarUniformCode(innerWarp);
2001 };
2002 encloseRegionInWarpOp(&ifOp.getThenRegion().front(),
2003 &newIfOp.getThenRegion().front(), escapingValuesThen,
2004 escapingValueInputTypesThen, 1);
2005 if (!ifOp.getElseRegion().empty())
2006 encloseRegionInWarpOp(&ifOp.getElseRegion().front(),
2007 &newIfOp.getElseRegion().front(),
2008 escapingValuesElse, escapingValueInputTypesElse,
2009 1 + escapingValuesThen.size());
2010 // Update the users of `<- WarpOp.yield <- IfOp.yield` to use the new `IfOp`
2011 // result.
2012 for (auto [origIdx, newIdx] : ifResultMapping)
2013 rewriter.replaceAllUsesExcept(newWarpOp.getResult(origIdx),
2014 newIfOp.getResult(newIdx), newIfOp);
2015
2016 // The original `ifOp` was left inside `newWarpOp` with empty then/else
2017 // regions (their blocks were moved into the inner WarpOps by takeBody).
2018 // Clear remaining uses and erase it to restore IR validity. Directly
2019 // update newWarpOp's yield operands instead of using replaceAllUsesWith,
2020 // to avoid triggering notifyOperandReplaced on the now-invalid ifOp.
2021 {
2022 OpBuilder::InsertionGuard guard(rewriter);
2023 rewriter.setInsertionPoint(ifOp);
2024 Operation *yield = newWarpOp.getTerminator();
2025 rewriter.modifyOpInPlace(yield, [&]() {
2026 for (auto [origIdx, ifResultIdx] : ifResultMapping) {
2027 Value poison = ub::PoisonOp::create(
2028 rewriter, ifOp.getLoc(), ifOp.getResult(ifResultIdx).getType());
2029 yield->setOperand(origIdx, poison);
2030 }
2031 });
2032 rewriter.eraseOp(ifOp);
2033 }
2034
2035 return success();
2036 }
2037
2038private:
2039 DistributionMapFn distributionMapFn;
2040};
2041
2042/// Sink scf.for region out of WarpExecuteOnLane0Op. This can be done only if
2043/// the scf.ForOp is the last operation in the region so that it doesn't
2044/// change the order of execution. This creates a new scf.for region after the
2045/// WarpExecuteOnLane0Op. The new scf.for region will contain a new
2046/// WarpExecuteOnLane0Op region. Example:
2047/// ```
2048/// %w = gpu.warp_execute_on_lane_0(%laneid) -> (vector<4xf32>) {
2049/// ...
2050/// %v1 = scf.for %arg3 = %c0 to %c128 step %c1 iter_args(%arg4 = %v)
2051/// -> (vector<128xf32>) {
2052/// ...
2053/// scf.yield %r : vector<128xf32>
2054/// }
2055/// gpu.yield %v1 : vector<128xf32>
2056/// }
2057/// ```
2058/// To:
2059/// %w0 = gpu.warp_execute_on_lane_0(%arg0) -> (vector<4xf32>) {
2060/// ...
2061/// gpu.yield %v : vector<128xf32>
2062/// }
2063/// %w = scf.for %arg3 = %c0 to %c128 step %c1 iter_args(%varg = %q0)
2064/// -> (vector<4xf32>) {
2065/// %iw = gpu.warp_execute_on_lane_0(%laneid)
2066/// args(%varg : vector<4xf32>) -> (vector<4xf32>) {
2067/// ^bb0(%arg: vector<128xf32>):
2068/// ...
2069/// gpu.yield %ir : vector<128xf32>
2070/// }
2071/// scf.yield %iw : vector<4xf32>
2072/// }
2073/// ```
2074struct WarpOpScfForOp : public WarpDistributionPattern {
2075
2076 WarpOpScfForOp(MLIRContext *ctx, DistributionMapFn fn, PatternBenefit b = 1)
2077 : WarpDistributionPattern(ctx, b), distributionMapFn(std::move(fn)) {}
2078 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
2079 PatternRewriter &rewriter) const override {
2080 gpu::YieldOp warpOpYield = warpOp.getTerminator();
2081 // Only pick up `ForOp` if it is the last op in the region.
2082 Operation *lastNode = warpOpYield->getPrevNode();
2083 auto forOp = dyn_cast_or_null<scf::ForOp>(lastNode);
2084 if (!forOp)
2085 return failure();
2086 // Collect Values that come from the `WarpOp` but are outside the `ForOp`.
2087 // Those Values need to be returned by the new warp op.
2088 auto [escapingValues, escapingValueInputTypes, escapingValueDistTypes] =
2089 getInnerRegionEscapingValues(warpOp, forOp.getBodyRegion(),
2090 distributionMapFn);
2091 if (llvm::is_contained(escapingValueDistTypes, Type{}))
2092 return failure();
2093 // `WarpOp` can yield two types of values:
2094 // 1. Values that are not results of the `ForOp`:
2095 // These values must also be yielded by the new `WarpOp`. Also, we need
2096 // to record the index mapping for these values to replace them later.
2097 // 2. Values that are results of the `ForOp`:
2098 // In this case, we record the index mapping between the `WarpOp` result
2099 // index and matching `ForOp` result index.
2100 // Additionally, we keep track of the distributed types for all `ForOp`
2101 // vector results.
2102 SmallVector<Value> nonForYieldedValues;
2103 SmallVector<unsigned> nonForResultIndices;
2104 llvm::SmallDenseMap<unsigned, unsigned> forResultMapping;
2105 llvm::SmallDenseMap<unsigned, VectorType> forResultDistTypes;
2106 llvm::SmallBitVector forResultsMapped(forOp.getNumResults());
2107 for (OpOperand &yieldOperand : warpOpYield->getOpOperands()) {
2108 // Yielded value is not a result of the forOp.
2109 if (yieldOperand.get().getDefiningOp() != forOp.getOperation()) {
2110 nonForYieldedValues.push_back(yieldOperand.get());
2111 nonForResultIndices.push_back(yieldOperand.getOperandNumber());
2112 continue;
2113 }
2114 OpResult forResult = cast<OpResult>(yieldOperand.get());
2115 unsigned int forResultNumber = forResult.getResultNumber();
2116 forResultMapping[yieldOperand.getOperandNumber()] = forResultNumber;
2117 forResultsMapped.set(forResultNumber);
2118 // If this `ForOp` result is vector type and it is yielded by the
2119 // `WarpOp`, we keep track the distributed type for this result.
2120 if (!isa<VectorType>(forResult.getType()))
2121 continue;
2122 VectorType distType = cast<VectorType>(
2123 warpOp.getResult(yieldOperand.getOperandNumber()).getType());
2124 forResultDistTypes[forResultNumber] = distType;
2125 }
2126
2127 // Newly created `WarpOp` will yield values in following order:
2128 // 1. Loop bounds.
2129 // 2. All init args of the `ForOp`.
2130 // 3. All escaping values.
2131 // 4. All non-`ForOp` yielded values.
2132 SmallVector<Value> newWarpOpYieldValues;
2133 SmallVector<Type> newWarpOpDistTypes;
2134 newWarpOpYieldValues.insert(
2135 newWarpOpYieldValues.end(),
2136 {forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep()});
2137 newWarpOpDistTypes.insert(newWarpOpDistTypes.end(),
2138 {forOp.getLowerBound().getType(),
2139 forOp.getUpperBound().getType(),
2140 forOp.getStep().getType()});
2141 for (auto [i, initArg] : llvm::enumerate(forOp.getInitArgs())) {
2142 newWarpOpYieldValues.push_back(initArg);
2143 // Compute the distributed type for this init arg.
2144 Type distType = initArg.getType();
2145 if (auto vecType = dyn_cast<VectorType>(distType)) {
2146 // If the `ForOp` result corresponds to this init arg is already yielded
2147 // we can get the distributed type from `forResultDistTypes` map.
2148 // Otherwise, we compute it using distributionMapFn.
2149 AffineMap map = distributionMapFn(initArg);
2150 distType =
2151 forResultDistTypes.count(i)
2152 ? forResultDistTypes[i]
2153 : getDistributedType(vecType, map,
2154 map.isEmpty() ? 1 : warpOp.getWarpSize());
2155 }
2156 newWarpOpDistTypes.push_back(distType);
2157 }
2158 // Insert escaping values and their distributed types.
2159 newWarpOpYieldValues.insert(newWarpOpYieldValues.end(),
2160 escapingValues.begin(), escapingValues.end());
2161 newWarpOpDistTypes.insert(newWarpOpDistTypes.end(),
2162 escapingValueDistTypes.begin(),
2163 escapingValueDistTypes.end());
2164 // Next, we insert all non-`ForOp` yielded values and their distributed
2165 // types.
2166 for (auto [i, v] :
2167 llvm::zip_equal(nonForResultIndices, nonForYieldedValues)) {
2168 newWarpOpYieldValues.push_back(v);
2169 newWarpOpDistTypes.push_back(warpOp.getResult(i).getType());
2170 }
2171 // Create the new `WarpOp` with the updated yield values and types.
2172 SmallVector<size_t> newIndices;
2173 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
2174 rewriter, warpOp, newWarpOpYieldValues, newWarpOpDistTypes, newIndices);
2175
2176 // Next, we create a new `ForOp` with the init args yielded by the new
2177 // `WarpOp`.
2178 const unsigned initArgsStartIdx = 3; // After loop bounds.
2179 const unsigned escapingValuesStartIdx =
2180 initArgsStartIdx +
2181 forOp.getInitArgs().size(); // `ForOp` init args are positioned before
2182 // escaping values in the new `WarpOp`.
2183 SmallVector<Value> newForOpOperands;
2184 for (size_t i = initArgsStartIdx; i < escapingValuesStartIdx; ++i)
2185 newForOpOperands.push_back(newWarpOp.getResult(newIndices[i]));
2186
2187 // Create a new `ForOp` outside the new `WarpOp` region.
2188 OpBuilder::InsertionGuard g(rewriter);
2189 rewriter.setInsertionPointAfter(newWarpOp);
2190 auto newForOp = scf::ForOp::create(
2191 rewriter, forOp.getLoc(),
2192 /**LowerBound=**/ newWarpOp.getResult(newIndices[0]),
2193 /**UpperBound=**/ newWarpOp.getResult(newIndices[1]),
2194 /**Step=**/ newWarpOp.getResult(newIndices[2]), newForOpOperands,
2195 /*bodyBuilder=*/nullptr, forOp.getUnsignedCmp());
2196 // Next, we insert a new `WarpOp` (called inner `WarpOp`) inside the
2197 // newly created `ForOp`. This `WarpOp` will contain all ops that were
2198 // contained within the original `ForOp` body.
2199 rewriter.setInsertionPointToStart(newForOp.getBody());
2200
2201 SmallVector<Value> innerWarpInput(newForOp.getRegionIterArgs().begin(),
2202 newForOp.getRegionIterArgs().end());
2203 SmallVector<Type> innerWarpInputType(forOp.getResultTypes().begin(),
2204 forOp.getResultTypes().end());
2205 // Escaping values are forwarded to the inner `WarpOp` as its (additional)
2206 // arguments. We keep track of the mapping between these values and their
2207 // argument index in the inner `WarpOp` (to replace users later).
2208 llvm::SmallDenseMap<Value, int64_t> argIndexMapping;
2209 for (size_t i = escapingValuesStartIdx;
2210 i < escapingValuesStartIdx + escapingValues.size(); ++i) {
2211 innerWarpInput.push_back(newWarpOp.getResult(newIndices[i]));
2212 argIndexMapping[escapingValues[i - escapingValuesStartIdx]] =
2213 innerWarpInputType.size();
2214 innerWarpInputType.push_back(
2215 escapingValueInputTypes[i - escapingValuesStartIdx]);
2216 }
2217 // Create the inner `WarpOp` with the new input values and types.
2218 auto innerWarp = WarpExecuteOnLane0Op::create(
2219 rewriter, newWarpOp.getLoc(), newForOp.getResultTypes(),
2220 newWarpOp.getLaneid(), newWarpOp.getWarpSize(), innerWarpInput,
2221 innerWarpInputType);
2222
2223 // Inline the `ForOp` body into the inner `WarpOp` body.
2224 SmallVector<Value> argMapping;
2225 argMapping.push_back(newForOp.getInductionVar());
2226 for (Value args : innerWarp.getBody()->getArguments())
2227 argMapping.push_back(args);
2228
2229 argMapping.resize(forOp.getBody()->getNumArguments());
2230 SmallVector<Value> yieldOperands;
2231 for (Value operand : forOp.getBody()->getTerminator()->getOperands()) {
2232 if (BlockArgument blockArg = dyn_cast<BlockArgument>(operand);
2233 blockArg && blockArg.getOwner() == forOp.getBody()) {
2234 yieldOperands.push_back(argMapping[blockArg.getArgNumber()]);
2235 continue;
2236 }
2237 yieldOperands.push_back(operand);
2238 }
2239
2240 rewriter.eraseOp(forOp.getBody()->getTerminator());
2241 rewriter.mergeBlocks(forOp.getBody(), innerWarp.getBody(), argMapping);
2242
2243 // Insert a gpu `YieldOp` at the end of the inner `WarpOp` body that yields
2244 // original `ForOp` results.
2245 rewriter.setInsertionPointToEnd(innerWarp.getBody());
2246 gpu::YieldOp::create(rewriter, innerWarp.getLoc(), yieldOperands);
2247 rewriter.setInsertionPointAfter(innerWarp);
2248 // Insert a scf.yield op at the end of the new `ForOp` body that yields
2249 // the inner `WarpOp` results.
2250 if (!innerWarp.getResults().empty())
2251 scf::YieldOp::create(rewriter, forOp.getLoc(), innerWarp.getResults());
2252
2253 // Update the users of the new `WarpOp` results that were coming from the
2254 // original `ForOp` to the corresponding new `ForOp` result.
2255 for (auto [origIdx, newIdx] : forResultMapping)
2256 rewriter.replaceAllUsesExcept(newWarpOp.getResult(origIdx),
2257 newForOp.getResult(newIdx), newForOp);
2258
2259 // The original `ForOp` was left inside `newWarpOp` with an empty body
2260 // region (its body block was moved into `innerWarp` by `mergeBlocks`).
2261 // Clear remaining uses and erase it to restore IR validity.
2262 for (OpResult result : forOp.getResults()) {
2263 if (forResultsMapped.test(result.getResultNumber()))
2264 rewriter.replaceAllUsesWith(
2265 result, forOp.getInitArgs()[result.getResultNumber()]);
2266 }
2267 rewriter.eraseOp(forOp);
2268
2269 // Update any users of escaping values that were forwarded to the
2270 // inner `WarpOp`. These values are now arguments of the inner `WarpOp`.
2271 newForOp.walk([&](Operation *op) {
2272 SmallVector<std::pair<unsigned, Value>> replacements;
2273 for (OpOperand &operand : op->getOpOperands()) {
2274 auto it = argIndexMapping.find(operand.get());
2275 if (it == argIndexMapping.end())
2276 continue;
2277 replacements.emplace_back(
2278 operand.getOperandNumber(),
2279 innerWarp.getBodyRegion().getArgument(it->second));
2280 }
2281 if (!replacements.empty()) {
2282 rewriter.modifyOpInPlace(op, [&]() {
2283 for (auto [idx, newVal] : replacements)
2284 op->setOperand(idx, newVal);
2285 });
2286 }
2287 });
2288
2289 // Finally, hoist out any now uniform code from the inner `WarpOp`.
2290 mlir::vector::moveScalarUniformCode(innerWarp);
2291 return success();
2292 }
2293
2294private:
2295 DistributionMapFn distributionMapFn;
2296};
2297
2298/// A pattern that extracts vector.reduction ops from a WarpExecuteOnLane0Op.
2299/// The vector is reduced in parallel. Currently limited to vector size
2300/// matching the warpOp size. E.g.:
2301/// ```
2302/// %r = gpu.warp_execute_on_lane_0(%laneid)[32] -> (f32) {
2303/// %0 = "some_def"() : () -> (vector<32xf32>)
2304/// %1 = vector.reduction "add", %0 : vector<32xf32> into f32
2305/// gpu.yield %1 : f32
2306/// }
2307/// ```
2308/// is lowered to:
2309/// ```
2310/// %0 = gpu.warp_execute_on_lane_0(%laneid)[32] -> (vector<1xf32>) {
2311/// %1 = "some_def"() : () -> (vector<32xf32>)
2312/// gpu.yield %1 : vector<32xf32>
2313/// }
2314/// %a = vector.extract %0[0] : f32 from vector<1xf32>
2315/// %r = ("warp.reduction %a")
2316/// ```
2317struct WarpOpReduction : public WarpDistributionPattern {
2318 WarpOpReduction(MLIRContext *context,
2319 DistributedReductionFn distributedReductionFn,
2320 PatternBenefit benefit = 1)
2321 : WarpDistributionPattern(context, benefit),
2322 distributedReductionFn(std::move(distributedReductionFn)) {}
2323
2324 LogicalResult matchAndRewrite(WarpExecuteOnLane0Op warpOp,
2325 PatternRewriter &rewriter) const override {
2326 OpOperand *yieldOperand =
2327 getWarpResult(warpOp, llvm::IsaPred<vector::ReductionOp>);
2328 if (!yieldOperand)
2329 return failure();
2330
2331 auto reductionOp =
2332 cast<vector::ReductionOp>(yieldOperand->get().getDefiningOp());
2333 auto vectorType = cast<VectorType>(reductionOp.getVector().getType());
2334 // Only rank 1 vectors supported.
2335 if (vectorType.getRank() != 1)
2336 return rewriter.notifyMatchFailure(
2337 warpOp, "Only rank 1 reductions can be distributed.");
2338 // Only warp_size-sized vectors supported.
2339 if (vectorType.getShape()[0] % warpOp.getWarpSize() != 0)
2340 return rewriter.notifyMatchFailure(
2341 warpOp, "Reduction vector dimension must match was size.");
2342 if (!reductionOp.getType().isIntOrFloat())
2343 return rewriter.notifyMatchFailure(
2344 warpOp, "Reduction distribution currently only supports floats and "
2345 "integer types.");
2346
2347 int64_t numElements = vectorType.getShape()[0] / warpOp.getWarpSize();
2348 // Return vector that will be reduced from the WarpExecuteOnLane0Op.
2349 unsigned operandIndex = yieldOperand->getOperandNumber();
2350 SmallVector<Value> yieldValues = {reductionOp.getVector()};
2351 SmallVector<Type> retTypes = {
2352 VectorType::get({numElements}, reductionOp.getType())};
2353 if (reductionOp.getAcc()) {
2354 yieldValues.push_back(reductionOp.getAcc());
2355 retTypes.push_back(reductionOp.getAcc().getType());
2356 }
2357 SmallVector<size_t> newRetIndices;
2358 WarpExecuteOnLane0Op newWarpOp = moveRegionToNewWarpOpAndAppendReturns(
2359 rewriter, warpOp, yieldValues, retTypes, newRetIndices);
2360 rewriter.setInsertionPointAfter(newWarpOp);
2361
2362 // Obtain data to reduce for a single lane.
2363 Value laneValVec = newWarpOp.getResult(newRetIndices[0]);
2364 // Distribute and reduce across threads.
2365 Value fullReduce =
2366 distributedReductionFn(reductionOp.getLoc(), rewriter, laneValVec,
2367 reductionOp.getKind(), newWarpOp.getWarpSize());
2368 if (reductionOp.getAcc()) {
2369 fullReduce = vector::makeArithReduction(
2370 rewriter, reductionOp.getLoc(), reductionOp.getKind(), fullReduce,
2371 newWarpOp.getResult(newRetIndices[1]));
2372 }
2373 rewriter.replaceAllUsesWith(newWarpOp.getResult(operandIndex), fullReduce);
2374 return success();
2375 }
2376
2377private:
2378 DistributedReductionFn distributedReductionFn;
2379};
2380
2381} // namespace
2382
2384 RewritePatternSet &patterns,
2386 patterns.add<WarpOpToScfIfPattern>(patterns.getContext(), options, benefit);
2387}
2388
2389void mlir::vector::populateDistributeTransferWriteOpPatterns(
2390 RewritePatternSet &patterns, const DistributionMapFn &distributionMapFn,
2391 unsigned maxNumElementsToExtract, PatternBenefit benefit) {
2392 patterns.add<WarpOpTransferWrite>(patterns.getContext(), distributionMapFn,
2393 maxNumElementsToExtract, benefit);
2394}
2395
2396void mlir::vector::populatePropagateWarpVectorDistributionPatterns(
2397 RewritePatternSet &patterns, const DistributionMapFn &distributionMapFn,
2398 const WarpShuffleFromIdxFn &warpShuffleFromIdxFn, PatternBenefit benefit,
2399 PatternBenefit readBenefit) {
2400 patterns.add<WarpOpTransferRead>(patterns.getContext(), readBenefit);
2401 patterns.add<WarpOpElementwise, WarpOpDeadResult, WarpOpBroadcast,
2402 WarpOpShapeCast, WarpOpExtract, WarpOpForwardOperand,
2403 WarpOpConstant, WarpOpInsertScalar, WarpOpInsert,
2404 WarpOpCreateMask<vector::CreateMaskOp>,
2405 WarpOpCreateMask<vector::ConstantMaskOp>,
2406 WarpOpExtractStridedSlice, WarpOpInsertStridedSlice, WarpOpStep>(
2407 patterns.getContext(), benefit);
2408 patterns.add<WarpOpExtractScalar>(patterns.getContext(), warpShuffleFromIdxFn,
2409 benefit);
2410 patterns.add<WarpOpScfForOp>(patterns.getContext(), distributionMapFn,
2411 benefit);
2412 patterns.add<WarpOpScfIfOp>(patterns.getContext(), distributionMapFn,
2413 benefit);
2414}
2415
2416void mlir::vector::populateDistributeReduction(
2417 RewritePatternSet &patterns,
2418 const DistributedReductionFn &distributedReductionFn,
2419 PatternBenefit benefit) {
2420 patterns.add<WarpOpReduction>(patterns.getContext(), distributedReductionFn,
2421 benefit);
2422}
2423
2424/// Helper to know if an op can be hoisted out of the region.
2425static bool canBeHoisted(Operation *op,
2426 function_ref<bool(Value)> definedOutside) {
2427 return llvm::all_of(op->getOperands(), definedOutside) &&
2428 isMemoryEffectFree(op) && op->getNumRegions() == 0;
2429}
2430
2431void mlir::vector::moveScalarUniformCode(WarpExecuteOnLane0Op warpOp) {
2432 Block *body = warpOp.getBody();
2433
2434 // Keep track of the ops we want to hoist.
2435 llvm::SmallSetVector<Operation *, 8> opsToMove;
2436
2437 // Helper to check if a value is or will be defined outside of the region.
2438 auto isDefinedOutsideOfBody = [&](Value value) {
2439 auto *definingOp = value.getDefiningOp();
2440 return (definingOp && opsToMove.count(definingOp)) ||
2441 warpOp.isDefinedOutsideOfRegion(value);
2442 };
2443
2444 // Do not use walk here, as we do not want to go into nested regions and hoist
2445 // operations from there.
2446 for (auto &op : body->without_terminator()) {
2447 bool hasVectorResult = llvm::any_of(op.getResults(), [](Value result) {
2448 return isa<VectorType>(result.getType());
2449 });
2450 if (!hasVectorResult && canBeHoisted(&op, isDefinedOutsideOfBody))
2451 opsToMove.insert(&op);
2452 }
2453
2454 // Move all the ops marked as uniform outside of the region.
2455 for (Operation *op : opsToMove)
2456 op->moveBefore(warpOp);
2457}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static llvm::ManagedStatic< PassManagerOptions > options
static AffineMap calculateImplicitMap(VectorType sequentialType, VectorType distributedType)
Currently the distribution map is implicit based on the vector shape.
static Operation * cloneOpWithOperandsAndTypes(RewriterBase &rewriter, Location loc, Operation *op, ArrayRef< Value > operands, ArrayRef< Type > resultTypes)
static int getDistributedDim(VectorType sequentialType, VectorType distributedType)
Given a sequential and distributed vector type, returns the distributed dimension.
static bool canBeHoisted(Operation *op, function_ref< bool(Value)> definedOutside)
Helper to know if an op can be hoisted out of the region.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isEmpty() const
Returns true if this affine map is an empty map, i.e., () -> ().
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
bool isIdentity() const
Returns true if this affine map is an identity affine map.
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:236
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
AffineExpr getAffineConstantExpr(int64_t constant)
Definition Builders.cpp:381
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
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
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 setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:466
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
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
unsigned getResultNumber() const
Returns the number of this result.
Definition Value.h:466
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
bool hasOneUse()
Returns true if this operation has exactly one use.
Definition Operation.h:901
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
unsigned getNumOperands()
Definition Operation.h:371
Attribute getPropertiesAsAttribute()
Return the properties converted to an attribute.
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
result_range getResults()
Definition Operation.h:440
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
bool empty()
Definition Region.h:60
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
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 finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
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.
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
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.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
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
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Returns a composed AffineApplyOp by composing map and operands with other AffineApplyOps supplying th...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Value makeArithReduction(OpBuilder &b, Location loc, CombiningKind kind, Value v1, Value acc, arith::FastMathFlagsAttr fastmath=nullptr, Value mask=nullptr)
Returns the result value of reducing two scalar/vector values with the corresponding arith operation.
std::function< AffineMap(Value)> DistributionMapFn
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....
void populateWarpExecuteOnLane0OpToScfForPattern(RewritePatternSet &patterns, const WarpExecuteOnLane0LoweringOptions &options, PatternBenefit benefit=1)
SmallVector< int64_t > getAsIntegers(ArrayRef< Value > values)
Returns the integer numbers in values.
Include the generated interface declarations.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
bool isOpTriviallyDead(Operation *op)
Return true if the given operation is unused, and has no side effects on memory that prevent erasing.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
AffineMap compressUnusedDims(AffineMap map)
Drop the dims that are not used.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
void visitUsedValuesDefinedAbove(Region &region, Region &limit, function_ref< void(OpOperand *)> callback)
Calls callback for each use of a value within region or its descendants that was defined at the ances...
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
AffineExpr getAffineSymbolExpr(unsigned position, MLIRContext *context)
This represents an operation in an abstracted form, suitable for use with the builder APIs.
WarpExecuteOnLane0Op moveRegionToNewWarpOpAndAppendReturns(RewriterBase &rewriter, WarpExecuteOnLane0Op warpOp, ValueRange newYieldedValues, TypeRange newReturnTypes, SmallVector< size_t > &indices) const
Helper to create a new WarpExecuteOnLane0Op region with extra outputs.
bool delinearizeLaneId(OpBuilder &builder, Location loc, ArrayRef< int64_t > originalShape, ArrayRef< int64_t > distributedShape, int64_t warpSize, Value laneId, SmallVectorImpl< Value > &delinearizedIds) const
Delinearize the given laneId into multiple dimensions, where each dimension's size is determined by o...
WarpExecuteOnLane0Op moveRegionToNewWarpOpAndReplaceReturns(RewriterBase &rewriter, WarpExecuteOnLane0Op warpOp, ValueRange newYieldedValues, TypeRange newReturnTypes) const
Helper to create a new WarpExecuteOnLane0Op with different signature.
virtual LogicalResult matchAndRewrite(WarpExecuteOnLane0Op op, PatternRewriter &rewriter) const override=0
OpOperand * getWarpResult(WarpExecuteOnLane0Op warpOp, llvm::function_ref< bool(Operation *)> fn) const
Return a value yielded by warpOp which statifies the filter lamdba condition and is not dead.