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