1*4ead2cf7SAlex Zinenko //===- SCFToGPU.cpp - Convert an affine loop nest to a GPU kernel -------===// 2*4ead2cf7SAlex Zinenko // 3*4ead2cf7SAlex Zinenko // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*4ead2cf7SAlex Zinenko // See https://llvm.org/LICENSE.txt for license information. 5*4ead2cf7SAlex Zinenko // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*4ead2cf7SAlex Zinenko // 7*4ead2cf7SAlex Zinenko //===----------------------------------------------------------------------===// 8*4ead2cf7SAlex Zinenko // 9*4ead2cf7SAlex Zinenko // This implements a straightforward conversion of an loop nest into a GPU 10*4ead2cf7SAlex Zinenko // kernel. The caller is expected to guarantee that the conversion is correct 11*4ead2cf7SAlex Zinenko // or to further transform the kernel to ensure correctness. 12*4ead2cf7SAlex Zinenko // 13*4ead2cf7SAlex Zinenko //===----------------------------------------------------------------------===// 14*4ead2cf7SAlex Zinenko 15*4ead2cf7SAlex Zinenko #include "mlir/Conversion/SCFToGPU/SCFToGPU.h" 16*4ead2cf7SAlex Zinenko 17*4ead2cf7SAlex Zinenko #include "mlir/Conversion/AffineToStandard/AffineToStandard.h" 18*4ead2cf7SAlex Zinenko #include "mlir/Dialect/Affine/IR/AffineOps.h" 19*4ead2cf7SAlex Zinenko #include "mlir/Dialect/GPU/GPUDialect.h" 20*4ead2cf7SAlex Zinenko #include "mlir/Dialect/GPU/ParallelLoopMapper.h" 21*4ead2cf7SAlex Zinenko #include "mlir/Dialect/SCF/SCF.h" 22*4ead2cf7SAlex Zinenko #include "mlir/Dialect/StandardOps/IR/Ops.h" 23*4ead2cf7SAlex Zinenko #include "mlir/IR/AffineExpr.h" 24*4ead2cf7SAlex Zinenko #include "mlir/IR/BlockAndValueMapping.h" 25*4ead2cf7SAlex Zinenko #include "mlir/IR/Builders.h" 26*4ead2cf7SAlex Zinenko #include "mlir/Pass/Pass.h" 27*4ead2cf7SAlex Zinenko #include "mlir/Transforms/DialectConversion.h" 28*4ead2cf7SAlex Zinenko #include "mlir/Transforms/LoopUtils.h" 29*4ead2cf7SAlex Zinenko #include "mlir/Transforms/Passes.h" 30*4ead2cf7SAlex Zinenko #include "mlir/Transforms/RegionUtils.h" 31*4ead2cf7SAlex Zinenko #include "llvm/ADT/Sequence.h" 32*4ead2cf7SAlex Zinenko #include "llvm/Support/Debug.h" 33*4ead2cf7SAlex Zinenko 34*4ead2cf7SAlex Zinenko #define DEBUG_TYPE "loops-to-gpu" 35*4ead2cf7SAlex Zinenko 36*4ead2cf7SAlex Zinenko using namespace mlir; 37*4ead2cf7SAlex Zinenko using namespace mlir::scf; 38*4ead2cf7SAlex Zinenko 39*4ead2cf7SAlex Zinenko using llvm::seq; 40*4ead2cf7SAlex Zinenko 41*4ead2cf7SAlex Zinenko // Extract an indexed value from KernelDim3. 42*4ead2cf7SAlex Zinenko static Value getDim3Value(const gpu::KernelDim3 &dim3, unsigned pos) { 43*4ead2cf7SAlex Zinenko switch (pos) { 44*4ead2cf7SAlex Zinenko case 0: 45*4ead2cf7SAlex Zinenko return dim3.x; 46*4ead2cf7SAlex Zinenko case 1: 47*4ead2cf7SAlex Zinenko return dim3.y; 48*4ead2cf7SAlex Zinenko case 2: 49*4ead2cf7SAlex Zinenko return dim3.z; 50*4ead2cf7SAlex Zinenko default: 51*4ead2cf7SAlex Zinenko llvm_unreachable("dim3 position out of bounds"); 52*4ead2cf7SAlex Zinenko } 53*4ead2cf7SAlex Zinenko return nullptr; 54*4ead2cf7SAlex Zinenko } 55*4ead2cf7SAlex Zinenko 56*4ead2cf7SAlex Zinenko // Get the lower bound-related operands of a loop operation. 57*4ead2cf7SAlex Zinenko static Operation::operand_range getLowerBoundOperands(AffineForOp forOp) { 58*4ead2cf7SAlex Zinenko return forOp.getLowerBoundOperands(); 59*4ead2cf7SAlex Zinenko } 60*4ead2cf7SAlex Zinenko static SmallVector<Value, 1> getLowerBoundOperands(ForOp forOp) { 61*4ead2cf7SAlex Zinenko SmallVector<Value, 1> bounds(1, forOp.lowerBound()); 62*4ead2cf7SAlex Zinenko return bounds; 63*4ead2cf7SAlex Zinenko } 64*4ead2cf7SAlex Zinenko 65*4ead2cf7SAlex Zinenko // Get the upper bound-related operands of a loop operation. 66*4ead2cf7SAlex Zinenko static Operation::operand_range getUpperBoundOperands(AffineForOp forOp) { 67*4ead2cf7SAlex Zinenko return forOp.getUpperBoundOperands(); 68*4ead2cf7SAlex Zinenko } 69*4ead2cf7SAlex Zinenko static SmallVector<Value, 1> getUpperBoundOperands(ForOp forOp) { 70*4ead2cf7SAlex Zinenko SmallVector<Value, 1> bounds(1, forOp.upperBound()); 71*4ead2cf7SAlex Zinenko return bounds; 72*4ead2cf7SAlex Zinenko } 73*4ead2cf7SAlex Zinenko 74*4ead2cf7SAlex Zinenko // Get a Value that corresponds to the loop step. If the step is an attribute, 75*4ead2cf7SAlex Zinenko // materialize a corresponding constant using builder. 76*4ead2cf7SAlex Zinenko static Value getOrCreateStep(AffineForOp forOp, OpBuilder &builder) { 77*4ead2cf7SAlex Zinenko return builder.create<ConstantIndexOp>(forOp.getLoc(), forOp.getStep()); 78*4ead2cf7SAlex Zinenko } 79*4ead2cf7SAlex Zinenko static Value getOrCreateStep(ForOp forOp, OpBuilder &) { return forOp.step(); } 80*4ead2cf7SAlex Zinenko 81*4ead2cf7SAlex Zinenko // Get a Value for the loop lower bound. If the value requires computation, 82*4ead2cf7SAlex Zinenko // materialize the instructions using builder. 83*4ead2cf7SAlex Zinenko static Value getOrEmitLowerBound(AffineForOp forOp, OpBuilder &builder) { 84*4ead2cf7SAlex Zinenko return lowerAffineLowerBound(forOp, builder); 85*4ead2cf7SAlex Zinenko } 86*4ead2cf7SAlex Zinenko static Value getOrEmitLowerBound(ForOp forOp, OpBuilder &) { 87*4ead2cf7SAlex Zinenko return forOp.lowerBound(); 88*4ead2cf7SAlex Zinenko } 89*4ead2cf7SAlex Zinenko 90*4ead2cf7SAlex Zinenko // Get a Value for the loop upper bound. If the value requires computation, 91*4ead2cf7SAlex Zinenko // materialize the instructions using builder. 92*4ead2cf7SAlex Zinenko static Value getOrEmitUpperBound(AffineForOp forOp, OpBuilder &builder) { 93*4ead2cf7SAlex Zinenko return lowerAffineUpperBound(forOp, builder); 94*4ead2cf7SAlex Zinenko } 95*4ead2cf7SAlex Zinenko static Value getOrEmitUpperBound(ForOp forOp, OpBuilder &) { 96*4ead2cf7SAlex Zinenko return forOp.upperBound(); 97*4ead2cf7SAlex Zinenko } 98*4ead2cf7SAlex Zinenko 99*4ead2cf7SAlex Zinenko // Check the structure of the loop nest: 100*4ead2cf7SAlex Zinenko // - there are enough loops to map to numDims; 101*4ead2cf7SAlex Zinenko // - the loops are perfectly nested; 102*4ead2cf7SAlex Zinenko // - the loop bounds can be computed above the outermost loop. 103*4ead2cf7SAlex Zinenko // This roughly corresponds to the "matcher" part of the pattern-based 104*4ead2cf7SAlex Zinenko // rewriting infrastructure. 105*4ead2cf7SAlex Zinenko template <typename OpTy> 106*4ead2cf7SAlex Zinenko static LogicalResult checkLoopNestMappableImpl(OpTy forOp, unsigned numDims) { 107*4ead2cf7SAlex Zinenko Region &limit = forOp.region(); 108*4ead2cf7SAlex Zinenko for (unsigned i = 0, e = numDims; i < e; ++i) { 109*4ead2cf7SAlex Zinenko Operation *nested = &forOp.getBody()->front(); 110*4ead2cf7SAlex Zinenko if (!areValuesDefinedAbove(getLowerBoundOperands(forOp), limit) || 111*4ead2cf7SAlex Zinenko !areValuesDefinedAbove(getUpperBoundOperands(forOp), limit)) 112*4ead2cf7SAlex Zinenko return forOp.emitError( 113*4ead2cf7SAlex Zinenko "loops with bounds depending on other mapped loops " 114*4ead2cf7SAlex Zinenko "are not supported"); 115*4ead2cf7SAlex Zinenko 116*4ead2cf7SAlex Zinenko // The innermost loop can have an arbitrary body, skip the perfect nesting 117*4ead2cf7SAlex Zinenko // check for it. 118*4ead2cf7SAlex Zinenko if (i == e - 1) 119*4ead2cf7SAlex Zinenko break; 120*4ead2cf7SAlex Zinenko 121*4ead2cf7SAlex Zinenko auto begin = forOp.getBody()->begin(), end = forOp.getBody()->end(); 122*4ead2cf7SAlex Zinenko if (forOp.getBody()->empty() || std::next(begin, 2) != end) 123*4ead2cf7SAlex Zinenko return forOp.emitError("expected perfectly nested loops in the body"); 124*4ead2cf7SAlex Zinenko 125*4ead2cf7SAlex Zinenko if (!(forOp = dyn_cast<OpTy>(nested))) 126*4ead2cf7SAlex Zinenko return nested->emitError("expected a nested loop"); 127*4ead2cf7SAlex Zinenko } 128*4ead2cf7SAlex Zinenko return success(); 129*4ead2cf7SAlex Zinenko } 130*4ead2cf7SAlex Zinenko 131*4ead2cf7SAlex Zinenko template <typename OpTy> 132*4ead2cf7SAlex Zinenko static LogicalResult checkLoopNestMappable(OpTy forOp, unsigned numBlockDims, 133*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 134*4ead2cf7SAlex Zinenko if (numBlockDims < 1 || numThreadDims < 1) { 135*4ead2cf7SAlex Zinenko LLVM_DEBUG(llvm::dbgs() << "nothing to map"); 136*4ead2cf7SAlex Zinenko return success(); 137*4ead2cf7SAlex Zinenko } 138*4ead2cf7SAlex Zinenko 139*4ead2cf7SAlex Zinenko if (numBlockDims > 3) { 140*4ead2cf7SAlex Zinenko return forOp.emitError("cannot map to more than 3 block dimensions"); 141*4ead2cf7SAlex Zinenko } 142*4ead2cf7SAlex Zinenko if (numThreadDims > 3) { 143*4ead2cf7SAlex Zinenko return forOp.emitError("cannot map to more than 3 thread dimensions"); 144*4ead2cf7SAlex Zinenko } 145*4ead2cf7SAlex Zinenko return checkLoopNestMappableImpl(forOp, numBlockDims + numThreadDims); 146*4ead2cf7SAlex Zinenko } 147*4ead2cf7SAlex Zinenko 148*4ead2cf7SAlex Zinenko template <typename OpTy> 149*4ead2cf7SAlex Zinenko static LogicalResult checkLoopOpMappable(OpTy forOp, unsigned numBlockDims, 150*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 151*4ead2cf7SAlex Zinenko if (numBlockDims < 1 || numThreadDims < 1) { 152*4ead2cf7SAlex Zinenko LLVM_DEBUG(llvm::dbgs() << "nothing to map"); 153*4ead2cf7SAlex Zinenko return success(); 154*4ead2cf7SAlex Zinenko } 155*4ead2cf7SAlex Zinenko 156*4ead2cf7SAlex Zinenko if (numBlockDims > 3) { 157*4ead2cf7SAlex Zinenko return forOp.emitError("cannot map to more than 3 block dimensions"); 158*4ead2cf7SAlex Zinenko } 159*4ead2cf7SAlex Zinenko if (numThreadDims > 3) { 160*4ead2cf7SAlex Zinenko return forOp.emitError("cannot map to more than 3 thread dimensions"); 161*4ead2cf7SAlex Zinenko } 162*4ead2cf7SAlex Zinenko if (numBlockDims != numThreadDims) { 163*4ead2cf7SAlex Zinenko // TODO(ravishankarm) : This can probably be relaxed by having a one-trip 164*4ead2cf7SAlex Zinenko // loop for the missing dimension, but there is not reason to handle this 165*4ead2cf7SAlex Zinenko // case for now. 166*4ead2cf7SAlex Zinenko return forOp.emitError( 167*4ead2cf7SAlex Zinenko "mismatch in block dimensions and thread dimensions"); 168*4ead2cf7SAlex Zinenko } 169*4ead2cf7SAlex Zinenko 170*4ead2cf7SAlex Zinenko // Check that the forOp contains perfectly nested loops for numBlockDims 171*4ead2cf7SAlex Zinenko if (failed(checkLoopNestMappableImpl(forOp, numBlockDims))) { 172*4ead2cf7SAlex Zinenko return failure(); 173*4ead2cf7SAlex Zinenko } 174*4ead2cf7SAlex Zinenko 175*4ead2cf7SAlex Zinenko // Get to the innermost loop. 176*4ead2cf7SAlex Zinenko for (auto i : seq<unsigned>(0, numBlockDims - 1)) { 177*4ead2cf7SAlex Zinenko forOp = cast<OpTy>(&forOp.getBody()->front()); 178*4ead2cf7SAlex Zinenko (void)i; 179*4ead2cf7SAlex Zinenko } 180*4ead2cf7SAlex Zinenko 181*4ead2cf7SAlex Zinenko // The forOp now points to the body of the innermost loop mapped to blocks. 182*4ead2cf7SAlex Zinenko for (Operation &op : *forOp.getBody()) { 183*4ead2cf7SAlex Zinenko // If the operation is a loop, check that it is mappable to workItems. 184*4ead2cf7SAlex Zinenko if (auto innerLoop = dyn_cast<OpTy>(&op)) { 185*4ead2cf7SAlex Zinenko if (failed(checkLoopNestMappableImpl(innerLoop, numThreadDims))) { 186*4ead2cf7SAlex Zinenko return failure(); 187*4ead2cf7SAlex Zinenko } 188*4ead2cf7SAlex Zinenko continue; 189*4ead2cf7SAlex Zinenko } 190*4ead2cf7SAlex Zinenko // TODO(ravishankarm) : If it is not a loop op, it is assumed that the 191*4ead2cf7SAlex Zinenko // statement is executed by all threads. It might be a collective operation, 192*4ead2cf7SAlex Zinenko // or some non-side effect instruction. Have to decide on "allowable" 193*4ead2cf7SAlex Zinenko // statements and check for those here. 194*4ead2cf7SAlex Zinenko } 195*4ead2cf7SAlex Zinenko return success(); 196*4ead2cf7SAlex Zinenko } 197*4ead2cf7SAlex Zinenko 198*4ead2cf7SAlex Zinenko namespace { 199*4ead2cf7SAlex Zinenko // Helper structure that holds common state of the loop to GPU kernel 200*4ead2cf7SAlex Zinenko // conversion. 201*4ead2cf7SAlex Zinenko struct LoopToGpuConverter { 202*4ead2cf7SAlex Zinenko template <typename OpTy> 203*4ead2cf7SAlex Zinenko Optional<OpTy> collectBounds(OpTy forOp, unsigned numLoops); 204*4ead2cf7SAlex Zinenko 205*4ead2cf7SAlex Zinenko template <typename OpTy> 206*4ead2cf7SAlex Zinenko void createLaunch(OpTy rootForOp, OpTy innermostForOp, unsigned numBlockDims, 207*4ead2cf7SAlex Zinenko unsigned numThreadDims); 208*4ead2cf7SAlex Zinenko 209*4ead2cf7SAlex Zinenko // Ranges of the loops mapped to blocks or threads. 210*4ead2cf7SAlex Zinenko SmallVector<Value, 6> dims; 211*4ead2cf7SAlex Zinenko // Lower bounds of the loops mapped to blocks or threads. 212*4ead2cf7SAlex Zinenko SmallVector<Value, 6> lbs; 213*4ead2cf7SAlex Zinenko // Induction variables of the loops mapped to blocks or threads. 214*4ead2cf7SAlex Zinenko SmallVector<Value, 6> ivs; 215*4ead2cf7SAlex Zinenko // Steps of the loops mapped to blocks or threads. 216*4ead2cf7SAlex Zinenko SmallVector<Value, 6> steps; 217*4ead2cf7SAlex Zinenko }; 218*4ead2cf7SAlex Zinenko } // namespace 219*4ead2cf7SAlex Zinenko 220*4ead2cf7SAlex Zinenko // Return true if the value is obviously a constant "one". 221*4ead2cf7SAlex Zinenko static bool isConstantOne(Value value) { 222*4ead2cf7SAlex Zinenko if (auto def = value.getDefiningOp<ConstantIndexOp>()) 223*4ead2cf7SAlex Zinenko return def.getValue() == 1; 224*4ead2cf7SAlex Zinenko return false; 225*4ead2cf7SAlex Zinenko } 226*4ead2cf7SAlex Zinenko 227*4ead2cf7SAlex Zinenko // Collect ranges, bounds, steps and induction variables in preparation for 228*4ead2cf7SAlex Zinenko // mapping a loop nest of depth "numLoops" rooted at "forOp" to a GPU kernel. 229*4ead2cf7SAlex Zinenko // This may fail if the IR for computing loop bounds cannot be constructed, for 230*4ead2cf7SAlex Zinenko // example if an affine loop uses semi-affine maps. Return the last loop to be 231*4ead2cf7SAlex Zinenko // mapped on success, llvm::None on failure. 232*4ead2cf7SAlex Zinenko template <typename OpTy> 233*4ead2cf7SAlex Zinenko Optional<OpTy> LoopToGpuConverter::collectBounds(OpTy forOp, 234*4ead2cf7SAlex Zinenko unsigned numLoops) { 235*4ead2cf7SAlex Zinenko OpBuilder builder(forOp.getOperation()); 236*4ead2cf7SAlex Zinenko dims.reserve(numLoops); 237*4ead2cf7SAlex Zinenko lbs.reserve(numLoops); 238*4ead2cf7SAlex Zinenko ivs.reserve(numLoops); 239*4ead2cf7SAlex Zinenko steps.reserve(numLoops); 240*4ead2cf7SAlex Zinenko OpTy currentLoop = forOp; 241*4ead2cf7SAlex Zinenko for (unsigned i = 0; i < numLoops; ++i) { 242*4ead2cf7SAlex Zinenko Value lowerBound = getOrEmitLowerBound(currentLoop, builder); 243*4ead2cf7SAlex Zinenko Value upperBound = getOrEmitUpperBound(currentLoop, builder); 244*4ead2cf7SAlex Zinenko if (!lowerBound || !upperBound) { 245*4ead2cf7SAlex Zinenko return llvm::None; 246*4ead2cf7SAlex Zinenko } 247*4ead2cf7SAlex Zinenko 248*4ead2cf7SAlex Zinenko Value range = 249*4ead2cf7SAlex Zinenko builder.create<SubIOp>(currentLoop.getLoc(), upperBound, lowerBound); 250*4ead2cf7SAlex Zinenko Value step = getOrCreateStep(currentLoop, builder); 251*4ead2cf7SAlex Zinenko if (!isConstantOne(step)) 252*4ead2cf7SAlex Zinenko range = builder.create<SignedDivIOp>(currentLoop.getLoc(), range, step); 253*4ead2cf7SAlex Zinenko dims.push_back(range); 254*4ead2cf7SAlex Zinenko 255*4ead2cf7SAlex Zinenko lbs.push_back(lowerBound); 256*4ead2cf7SAlex Zinenko ivs.push_back(currentLoop.getInductionVar()); 257*4ead2cf7SAlex Zinenko steps.push_back(step); 258*4ead2cf7SAlex Zinenko 259*4ead2cf7SAlex Zinenko if (i != numLoops - 1) 260*4ead2cf7SAlex Zinenko currentLoop = cast<OpTy>(¤tLoop.getBody()->front()); 261*4ead2cf7SAlex Zinenko } 262*4ead2cf7SAlex Zinenko return currentLoop; 263*4ead2cf7SAlex Zinenko } 264*4ead2cf7SAlex Zinenko 265*4ead2cf7SAlex Zinenko /// Given `nDims` perfectly nested loops rooted as `rootForOp`, convert them o 266*4ead2cf7SAlex Zinenko /// be partitioned across workgroups or workitems. The values for the 267*4ead2cf7SAlex Zinenko /// workgroup/workitem id along each dimension is passed in with `ids`. The 268*4ead2cf7SAlex Zinenko /// number of workgroups/workitems along each dimension are passed in with 269*4ead2cf7SAlex Zinenko /// `nids`. The innermost loop is mapped to the x-dimension, followed by the 270*4ead2cf7SAlex Zinenko /// next innermost loop to y-dimension, followed by z-dimension. 271*4ead2cf7SAlex Zinenko template <typename OpTy> 272*4ead2cf7SAlex Zinenko static OpTy createGPULaunchLoops(OpTy rootForOp, ArrayRef<Value> ids, 273*4ead2cf7SAlex Zinenko ArrayRef<Value> nids) { 274*4ead2cf7SAlex Zinenko auto nDims = ids.size(); 275*4ead2cf7SAlex Zinenko assert(nDims == nids.size()); 276*4ead2cf7SAlex Zinenko for (auto dim : llvm::seq<unsigned>(0, nDims)) { 277*4ead2cf7SAlex Zinenko // TODO(ravishankarm): Don't always need to generate a loop here. If nids >= 278*4ead2cf7SAlex Zinenko // number of iterations of the original loop, this becomes a if 279*4ead2cf7SAlex Zinenko // condition. Though that does rely on how the workgroup/workitem sizes are 280*4ead2cf7SAlex Zinenko // specified to begin with. 281*4ead2cf7SAlex Zinenko mapLoopToProcessorIds(rootForOp, ids[dim], nids[dim]); 282*4ead2cf7SAlex Zinenko if (dim != nDims - 1) { 283*4ead2cf7SAlex Zinenko rootForOp = cast<OpTy>(rootForOp.getBody()->front()); 284*4ead2cf7SAlex Zinenko } 285*4ead2cf7SAlex Zinenko } 286*4ead2cf7SAlex Zinenko return rootForOp; 287*4ead2cf7SAlex Zinenko } 288*4ead2cf7SAlex Zinenko 289*4ead2cf7SAlex Zinenko /// Utility method to convert the gpu::KernelDim3 object for representing id of 290*4ead2cf7SAlex Zinenko /// each workgroup/workitem and number of workgroup/workitems along a dimension 291*4ead2cf7SAlex Zinenko /// of the launch into a container. 292*4ead2cf7SAlex Zinenko static void packIdAndNumId(gpu::KernelDim3 kernelIds, 293*4ead2cf7SAlex Zinenko gpu::KernelDim3 kernelNids, unsigned nDims, 294*4ead2cf7SAlex Zinenko SmallVectorImpl<Value> &ids, 295*4ead2cf7SAlex Zinenko SmallVectorImpl<Value> &nids) { 296*4ead2cf7SAlex Zinenko assert(nDims <= 3 && "invalid number of launch dimensions"); 297*4ead2cf7SAlex Zinenko std::array<Value, 3> allIds = {kernelIds.z, kernelIds.y, kernelIds.x}; 298*4ead2cf7SAlex Zinenko std::array<Value, 3> allNids = {kernelNids.z, kernelNids.y, kernelNids.x}; 299*4ead2cf7SAlex Zinenko ids.clear(); 300*4ead2cf7SAlex Zinenko ids.append(std::next(allIds.begin(), allIds.size() - nDims), allIds.end()); 301*4ead2cf7SAlex Zinenko nids.clear(); 302*4ead2cf7SAlex Zinenko nids.append(std::next(allNids.begin(), allNids.size() - nDims), 303*4ead2cf7SAlex Zinenko allNids.end()); 304*4ead2cf7SAlex Zinenko } 305*4ead2cf7SAlex Zinenko 306*4ead2cf7SAlex Zinenko /// Generate the body of the launch operation. 307*4ead2cf7SAlex Zinenko template <typename OpTy> 308*4ead2cf7SAlex Zinenko static LogicalResult 309*4ead2cf7SAlex Zinenko createLaunchBody(OpBuilder &builder, OpTy rootForOp, gpu::LaunchOp launchOp, 310*4ead2cf7SAlex Zinenko unsigned numBlockDims, unsigned numThreadDims) { 311*4ead2cf7SAlex Zinenko OpBuilder::InsertionGuard bodyInsertionGuard(builder); 312*4ead2cf7SAlex Zinenko builder.setInsertionPointToEnd(&launchOp.body().front()); 313*4ead2cf7SAlex Zinenko auto terminatorOp = builder.create<gpu::TerminatorOp>(launchOp.getLoc()); 314*4ead2cf7SAlex Zinenko 315*4ead2cf7SAlex Zinenko rootForOp.getOperation()->moveBefore(terminatorOp); 316*4ead2cf7SAlex Zinenko SmallVector<Value, 3> workgroupID, numWorkGroups; 317*4ead2cf7SAlex Zinenko packIdAndNumId(launchOp.getBlockIds(), launchOp.getGridSize(), numBlockDims, 318*4ead2cf7SAlex Zinenko workgroupID, numWorkGroups); 319*4ead2cf7SAlex Zinenko 320*4ead2cf7SAlex Zinenko // Partition the loop for mapping to workgroups. 321*4ead2cf7SAlex Zinenko auto loopOp = createGPULaunchLoops(rootForOp, workgroupID, numWorkGroups); 322*4ead2cf7SAlex Zinenko 323*4ead2cf7SAlex Zinenko // Iterate over the body of the loopOp and get the loops to partition for 324*4ead2cf7SAlex Zinenko // thread blocks. 325*4ead2cf7SAlex Zinenko SmallVector<OpTy, 1> threadRootForOps; 326*4ead2cf7SAlex Zinenko for (Operation &op : *loopOp.getBody()) { 327*4ead2cf7SAlex Zinenko if (auto threadRootForOp = dyn_cast<OpTy>(&op)) { 328*4ead2cf7SAlex Zinenko threadRootForOps.push_back(threadRootForOp); 329*4ead2cf7SAlex Zinenko } 330*4ead2cf7SAlex Zinenko } 331*4ead2cf7SAlex Zinenko 332*4ead2cf7SAlex Zinenko SmallVector<Value, 3> workItemID, workGroupSize; 333*4ead2cf7SAlex Zinenko packIdAndNumId(launchOp.getThreadIds(), launchOp.getBlockSize(), 334*4ead2cf7SAlex Zinenko numThreadDims, workItemID, workGroupSize); 335*4ead2cf7SAlex Zinenko for (auto &loopOp : threadRootForOps) { 336*4ead2cf7SAlex Zinenko builder.setInsertionPoint(loopOp); 337*4ead2cf7SAlex Zinenko createGPULaunchLoops(loopOp, workItemID, workGroupSize); 338*4ead2cf7SAlex Zinenko } 339*4ead2cf7SAlex Zinenko return success(); 340*4ead2cf7SAlex Zinenko } 341*4ead2cf7SAlex Zinenko 342*4ead2cf7SAlex Zinenko // Convert the computation rooted at the `rootForOp`, into a GPU kernel with the 343*4ead2cf7SAlex Zinenko // given workgroup size and number of workgroups. 344*4ead2cf7SAlex Zinenko template <typename OpTy> 345*4ead2cf7SAlex Zinenko static LogicalResult createLaunchFromOp(OpTy rootForOp, 346*4ead2cf7SAlex Zinenko ArrayRef<Value> numWorkGroups, 347*4ead2cf7SAlex Zinenko ArrayRef<Value> workGroupSizes) { 348*4ead2cf7SAlex Zinenko OpBuilder builder(rootForOp.getOperation()); 349*4ead2cf7SAlex Zinenko if (numWorkGroups.size() > 3) { 350*4ead2cf7SAlex Zinenko return rootForOp.emitError("invalid ") 351*4ead2cf7SAlex Zinenko << numWorkGroups.size() << "-D workgroup specification"; 352*4ead2cf7SAlex Zinenko } 353*4ead2cf7SAlex Zinenko auto loc = rootForOp.getLoc(); 354*4ead2cf7SAlex Zinenko Value one = builder.create<ConstantOp>( 355*4ead2cf7SAlex Zinenko loc, builder.getIntegerAttr(builder.getIndexType(), 1)); 356*4ead2cf7SAlex Zinenko SmallVector<Value, 3> numWorkGroups3D(3, one), workGroupSize3D(3, one); 357*4ead2cf7SAlex Zinenko for (auto numWorkGroup : enumerate(numWorkGroups)) { 358*4ead2cf7SAlex Zinenko numWorkGroups3D[numWorkGroup.index()] = numWorkGroup.value(); 359*4ead2cf7SAlex Zinenko } 360*4ead2cf7SAlex Zinenko for (auto workGroupSize : enumerate(workGroupSizes)) { 361*4ead2cf7SAlex Zinenko workGroupSize3D[workGroupSize.index()] = workGroupSize.value(); 362*4ead2cf7SAlex Zinenko } 363*4ead2cf7SAlex Zinenko 364*4ead2cf7SAlex Zinenko auto launchOp = builder.create<gpu::LaunchOp>( 365*4ead2cf7SAlex Zinenko rootForOp.getLoc(), numWorkGroups3D[0], numWorkGroups3D[1], 366*4ead2cf7SAlex Zinenko numWorkGroups3D[2], workGroupSize3D[0], workGroupSize3D[1], 367*4ead2cf7SAlex Zinenko workGroupSize3D[2]); 368*4ead2cf7SAlex Zinenko if (failed(createLaunchBody(builder, rootForOp, launchOp, 369*4ead2cf7SAlex Zinenko numWorkGroups.size(), workGroupSizes.size()))) { 370*4ead2cf7SAlex Zinenko return failure(); 371*4ead2cf7SAlex Zinenko } 372*4ead2cf7SAlex Zinenko 373*4ead2cf7SAlex Zinenko return success(); 374*4ead2cf7SAlex Zinenko } 375*4ead2cf7SAlex Zinenko 376*4ead2cf7SAlex Zinenko // Replace the rooted at "rootForOp" with a GPU launch operation. This expects 377*4ead2cf7SAlex Zinenko // "innermostForOp" to point to the last loop to be transformed to the kernel, 378*4ead2cf7SAlex Zinenko // and to have (numBlockDims + numThreadDims) perfectly nested loops between 379*4ead2cf7SAlex Zinenko // "rootForOp" and "innermostForOp". 380*4ead2cf7SAlex Zinenko // TODO(ravishankarm) : This method can be modified to use the 381*4ead2cf7SAlex Zinenko // createLaunchFromOp method, since that is a strict generalization of this 382*4ead2cf7SAlex Zinenko // method. 383*4ead2cf7SAlex Zinenko template <typename OpTy> 384*4ead2cf7SAlex Zinenko void LoopToGpuConverter::createLaunch(OpTy rootForOp, OpTy innermostForOp, 385*4ead2cf7SAlex Zinenko unsigned numBlockDims, 386*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 387*4ead2cf7SAlex Zinenko OpBuilder builder(rootForOp.getOperation()); 388*4ead2cf7SAlex Zinenko // Prepare the grid and block sizes for the launch operation. If there is 389*4ead2cf7SAlex Zinenko // no loop mapped to a specific dimension, use constant "1" as its size. 390*4ead2cf7SAlex Zinenko Value constOne = (numBlockDims < 3 || numThreadDims < 3) 391*4ead2cf7SAlex Zinenko ? builder.create<ConstantIndexOp>(rootForOp.getLoc(), 1) 392*4ead2cf7SAlex Zinenko : nullptr; 393*4ead2cf7SAlex Zinenko Value gridSizeX = numBlockDims > 0 ? dims[0] : constOne; 394*4ead2cf7SAlex Zinenko Value gridSizeY = numBlockDims > 1 ? dims[1] : constOne; 395*4ead2cf7SAlex Zinenko Value gridSizeZ = numBlockDims > 2 ? dims[2] : constOne; 396*4ead2cf7SAlex Zinenko Value blockSizeX = numThreadDims > 0 ? dims[numBlockDims] : constOne; 397*4ead2cf7SAlex Zinenko Value blockSizeY = numThreadDims > 1 ? dims[numBlockDims + 1] : constOne; 398*4ead2cf7SAlex Zinenko Value blockSizeZ = numThreadDims > 2 ? dims[numBlockDims + 2] : constOne; 399*4ead2cf7SAlex Zinenko 400*4ead2cf7SAlex Zinenko // Create a launch op and move the body region of the innermost loop to the 401*4ead2cf7SAlex Zinenko // launch op. 402*4ead2cf7SAlex Zinenko auto launchOp = builder.create<gpu::LaunchOp>( 403*4ead2cf7SAlex Zinenko rootForOp.getLoc(), gridSizeX, gridSizeY, gridSizeZ, blockSizeX, 404*4ead2cf7SAlex Zinenko blockSizeY, blockSizeZ); 405*4ead2cf7SAlex Zinenko 406*4ead2cf7SAlex Zinenko // Replace the loop terminator (loops contain only a single block) with the 407*4ead2cf7SAlex Zinenko // gpu terminator and move the operations from the loop body block to the gpu 408*4ead2cf7SAlex Zinenko // launch body block. Do not move the entire block because of the difference 409*4ead2cf7SAlex Zinenko // in block arguments. 410*4ead2cf7SAlex Zinenko Operation &terminator = innermostForOp.getBody()->back(); 411*4ead2cf7SAlex Zinenko Location terminatorLoc = terminator.getLoc(); 412*4ead2cf7SAlex Zinenko terminator.erase(); 413*4ead2cf7SAlex Zinenko builder.setInsertionPointToEnd(innermostForOp.getBody()); 414*4ead2cf7SAlex Zinenko builder.create<gpu::TerminatorOp>(terminatorLoc, llvm::None); 415*4ead2cf7SAlex Zinenko launchOp.body().front().getOperations().splice( 416*4ead2cf7SAlex Zinenko launchOp.body().front().begin(), 417*4ead2cf7SAlex Zinenko innermostForOp.getBody()->getOperations()); 418*4ead2cf7SAlex Zinenko 419*4ead2cf7SAlex Zinenko // Remap the loop iterators to use block/thread identifiers instead. Loops 420*4ead2cf7SAlex Zinenko // may iterate from LB with step S whereas GPU thread/block ids always iterate 421*4ead2cf7SAlex Zinenko // from 0 to N with step 1. Therefore, loop induction variables are replaced 422*4ead2cf7SAlex Zinenko // with (gpu-thread/block-id * S) + LB. 423*4ead2cf7SAlex Zinenko builder.setInsertionPointToStart(&launchOp.body().front()); 424*4ead2cf7SAlex Zinenko auto lbArgumentIt = lbs.begin(); 425*4ead2cf7SAlex Zinenko auto stepArgumentIt = steps.begin(); 426*4ead2cf7SAlex Zinenko for (auto en : llvm::enumerate(ivs)) { 427*4ead2cf7SAlex Zinenko Value id = 428*4ead2cf7SAlex Zinenko en.index() < numBlockDims 429*4ead2cf7SAlex Zinenko ? getDim3Value(launchOp.getBlockIds(), en.index()) 430*4ead2cf7SAlex Zinenko : getDim3Value(launchOp.getThreadIds(), en.index() - numBlockDims); 431*4ead2cf7SAlex Zinenko Value step = steps[en.index()]; 432*4ead2cf7SAlex Zinenko if (!isConstantOne(step)) 433*4ead2cf7SAlex Zinenko id = builder.create<MulIOp>(rootForOp.getLoc(), step, id); 434*4ead2cf7SAlex Zinenko 435*4ead2cf7SAlex Zinenko Value ivReplacement = 436*4ead2cf7SAlex Zinenko builder.create<AddIOp>(rootForOp.getLoc(), *lbArgumentIt, id); 437*4ead2cf7SAlex Zinenko en.value().replaceAllUsesWith(ivReplacement); 438*4ead2cf7SAlex Zinenko std::advance(lbArgumentIt, 1); 439*4ead2cf7SAlex Zinenko std::advance(stepArgumentIt, 1); 440*4ead2cf7SAlex Zinenko } 441*4ead2cf7SAlex Zinenko 442*4ead2cf7SAlex Zinenko // We are done and can erase the original outermost loop. 443*4ead2cf7SAlex Zinenko rootForOp.erase(); 444*4ead2cf7SAlex Zinenko } 445*4ead2cf7SAlex Zinenko 446*4ead2cf7SAlex Zinenko // Generic loop to GPU kernel conversion function. 447*4ead2cf7SAlex Zinenko template <typename OpTy> 448*4ead2cf7SAlex Zinenko static LogicalResult convertLoopNestToGPULaunch(OpTy forOp, 449*4ead2cf7SAlex Zinenko unsigned numBlockDims, 450*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 451*4ead2cf7SAlex Zinenko if (failed(checkLoopNestMappable(forOp, numBlockDims, numThreadDims))) 452*4ead2cf7SAlex Zinenko return failure(); 453*4ead2cf7SAlex Zinenko 454*4ead2cf7SAlex Zinenko LoopToGpuConverter converter; 455*4ead2cf7SAlex Zinenko auto maybeInnerLoop = 456*4ead2cf7SAlex Zinenko converter.collectBounds(forOp, numBlockDims + numThreadDims); 457*4ead2cf7SAlex Zinenko if (!maybeInnerLoop) 458*4ead2cf7SAlex Zinenko return failure(); 459*4ead2cf7SAlex Zinenko converter.createLaunch(forOp, *maybeInnerLoop, numBlockDims, numThreadDims); 460*4ead2cf7SAlex Zinenko 461*4ead2cf7SAlex Zinenko return success(); 462*4ead2cf7SAlex Zinenko } 463*4ead2cf7SAlex Zinenko 464*4ead2cf7SAlex Zinenko // Generic loop to GPU kernel conversion function when loop is imperfectly 465*4ead2cf7SAlex Zinenko // nested. The workgroup size and num workgroups is provided as input 466*4ead2cf7SAlex Zinenko template <typename OpTy> 467*4ead2cf7SAlex Zinenko static LogicalResult convertLoopToGPULaunch(OpTy forOp, 468*4ead2cf7SAlex Zinenko ArrayRef<Value> numWorkGroups, 469*4ead2cf7SAlex Zinenko ArrayRef<Value> workGroupSize) { 470*4ead2cf7SAlex Zinenko if (failed(checkLoopOpMappable(forOp, numWorkGroups.size(), 471*4ead2cf7SAlex Zinenko workGroupSize.size()))) { 472*4ead2cf7SAlex Zinenko return failure(); 473*4ead2cf7SAlex Zinenko } 474*4ead2cf7SAlex Zinenko return createLaunchFromOp(forOp, numWorkGroups, workGroupSize); 475*4ead2cf7SAlex Zinenko } 476*4ead2cf7SAlex Zinenko 477*4ead2cf7SAlex Zinenko LogicalResult mlir::convertAffineLoopNestToGPULaunch(AffineForOp forOp, 478*4ead2cf7SAlex Zinenko unsigned numBlockDims, 479*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 480*4ead2cf7SAlex Zinenko return ::convertLoopNestToGPULaunch(forOp, numBlockDims, numThreadDims); 481*4ead2cf7SAlex Zinenko } 482*4ead2cf7SAlex Zinenko 483*4ead2cf7SAlex Zinenko LogicalResult mlir::convertLoopNestToGPULaunch(ForOp forOp, 484*4ead2cf7SAlex Zinenko unsigned numBlockDims, 485*4ead2cf7SAlex Zinenko unsigned numThreadDims) { 486*4ead2cf7SAlex Zinenko return ::convertLoopNestToGPULaunch(forOp, numBlockDims, numThreadDims); 487*4ead2cf7SAlex Zinenko } 488*4ead2cf7SAlex Zinenko 489*4ead2cf7SAlex Zinenko LogicalResult mlir::convertLoopToGPULaunch(scf::ForOp forOp, 490*4ead2cf7SAlex Zinenko ArrayRef<Value> numWorkGroups, 491*4ead2cf7SAlex Zinenko ArrayRef<Value> workGroupSizes) { 492*4ead2cf7SAlex Zinenko return ::convertLoopToGPULaunch(forOp, numWorkGroups, workGroupSizes); 493*4ead2cf7SAlex Zinenko } 494*4ead2cf7SAlex Zinenko 495*4ead2cf7SAlex Zinenko namespace { 496*4ead2cf7SAlex Zinenko struct ParallelToGpuLaunchLowering : public OpRewritePattern<ParallelOp> { 497*4ead2cf7SAlex Zinenko using OpRewritePattern<ParallelOp>::OpRewritePattern; 498*4ead2cf7SAlex Zinenko 499*4ead2cf7SAlex Zinenko LogicalResult matchAndRewrite(ParallelOp parallelOp, 500*4ead2cf7SAlex Zinenko PatternRewriter &rewriter) const override; 501*4ead2cf7SAlex Zinenko }; 502*4ead2cf7SAlex Zinenko } // namespace 503*4ead2cf7SAlex Zinenko 504*4ead2cf7SAlex Zinenko /// Tries to derive a static upper bound from the defining operation of 505*4ead2cf7SAlex Zinenko /// `upperBound`. 506*4ead2cf7SAlex Zinenko static Value deriveStaticUpperBound(Value upperBound, 507*4ead2cf7SAlex Zinenko PatternRewriter &rewriter) { 508*4ead2cf7SAlex Zinenko if (auto op = upperBound.getDefiningOp<ConstantIndexOp>()) { 509*4ead2cf7SAlex Zinenko return op; 510*4ead2cf7SAlex Zinenko } 511*4ead2cf7SAlex Zinenko 512*4ead2cf7SAlex Zinenko if (auto minOp = upperBound.getDefiningOp<AffineMinOp>()) { 513*4ead2cf7SAlex Zinenko for (const AffineExpr &result : minOp.map().getResults()) { 514*4ead2cf7SAlex Zinenko if (auto constExpr = result.dyn_cast<AffineConstantExpr>()) { 515*4ead2cf7SAlex Zinenko return rewriter.create<ConstantIndexOp>(minOp.getLoc(), 516*4ead2cf7SAlex Zinenko constExpr.getValue()); 517*4ead2cf7SAlex Zinenko } 518*4ead2cf7SAlex Zinenko } 519*4ead2cf7SAlex Zinenko } 520*4ead2cf7SAlex Zinenko 521*4ead2cf7SAlex Zinenko if (auto multiplyOp = upperBound.getDefiningOp<MulIOp>()) { 522*4ead2cf7SAlex Zinenko if (auto lhs = dyn_cast_or_null<ConstantIndexOp>( 523*4ead2cf7SAlex Zinenko deriveStaticUpperBound(multiplyOp.getOperand(0), rewriter) 524*4ead2cf7SAlex Zinenko .getDefiningOp())) 525*4ead2cf7SAlex Zinenko if (auto rhs = dyn_cast_or_null<ConstantIndexOp>( 526*4ead2cf7SAlex Zinenko deriveStaticUpperBound(multiplyOp.getOperand(1), rewriter) 527*4ead2cf7SAlex Zinenko .getDefiningOp())) { 528*4ead2cf7SAlex Zinenko // Assumptions about the upper bound of minimum computations no longer 529*4ead2cf7SAlex Zinenko // work if multiplied by a negative value, so abort in this case. 530*4ead2cf7SAlex Zinenko if (lhs.getValue() < 0 || rhs.getValue() < 0) 531*4ead2cf7SAlex Zinenko return {}; 532*4ead2cf7SAlex Zinenko 533*4ead2cf7SAlex Zinenko return rewriter.create<ConstantIndexOp>( 534*4ead2cf7SAlex Zinenko multiplyOp.getLoc(), lhs.getValue() * rhs.getValue()); 535*4ead2cf7SAlex Zinenko } 536*4ead2cf7SAlex Zinenko } 537*4ead2cf7SAlex Zinenko 538*4ead2cf7SAlex Zinenko return {}; 539*4ead2cf7SAlex Zinenko } 540*4ead2cf7SAlex Zinenko 541*4ead2cf7SAlex Zinenko static bool isMappedToProcessor(gpu::Processor processor) { 542*4ead2cf7SAlex Zinenko return processor != gpu::Processor::Sequential; 543*4ead2cf7SAlex Zinenko } 544*4ead2cf7SAlex Zinenko 545*4ead2cf7SAlex Zinenko static unsigned getLaunchOpArgumentNum(gpu::Processor processor) { 546*4ead2cf7SAlex Zinenko switch (processor) { 547*4ead2cf7SAlex Zinenko case gpu::Processor::BlockX: 548*4ead2cf7SAlex Zinenko return 0; 549*4ead2cf7SAlex Zinenko case gpu::Processor::BlockY: 550*4ead2cf7SAlex Zinenko return 1; 551*4ead2cf7SAlex Zinenko case gpu::Processor::BlockZ: 552*4ead2cf7SAlex Zinenko return 2; 553*4ead2cf7SAlex Zinenko case gpu::Processor::ThreadX: 554*4ead2cf7SAlex Zinenko return 3; 555*4ead2cf7SAlex Zinenko case gpu::Processor::ThreadY: 556*4ead2cf7SAlex Zinenko return 4; 557*4ead2cf7SAlex Zinenko case gpu::Processor::ThreadZ: 558*4ead2cf7SAlex Zinenko return 5; 559*4ead2cf7SAlex Zinenko default:; 560*4ead2cf7SAlex Zinenko } 561*4ead2cf7SAlex Zinenko llvm_unreachable( 562*4ead2cf7SAlex Zinenko "invalid processor type while retrieving launch op argument number"); 563*4ead2cf7SAlex Zinenko } 564*4ead2cf7SAlex Zinenko 565*4ead2cf7SAlex Zinenko /// Modifies the current transformation state to capture the effect of the given 566*4ead2cf7SAlex Zinenko /// `scf.parallel` operation on index substitutions and the operations to be 567*4ead2cf7SAlex Zinenko /// inserted. 568*4ead2cf7SAlex Zinenko /// Specifically, if a dimension of a parallel loop is mapped to a hardware id, 569*4ead2cf7SAlex Zinenko /// this function will 570*4ead2cf7SAlex Zinenko /// - compute the loop index based on the hardware id and affine map from the 571*4ead2cf7SAlex Zinenko /// mapping and update `cloningMap` to substitute all uses. 572*4ead2cf7SAlex Zinenko /// - derive a new upper bound for the hardware id and augment the provided 573*4ead2cf7SAlex Zinenko /// `gpu.launch operation` accordingly. 574*4ead2cf7SAlex Zinenko /// - if the upper bound is imprecise, insert a conditional in the `gpu.launch` 575*4ead2cf7SAlex Zinenko /// and update the rewriter to insert into the conditional's body. 576*4ead2cf7SAlex Zinenko /// If the dimension is mapped to sequential, 577*4ead2cf7SAlex Zinenko /// - insert a for loop into the body and update the rewriter to insert into 578*4ead2cf7SAlex Zinenko /// the for loop's body. 579*4ead2cf7SAlex Zinenko /// - update the `cloningMap` to replace uses of the index with the index of 580*4ead2cf7SAlex Zinenko /// the new for loop. 581*4ead2cf7SAlex Zinenko /// In either case, 582*4ead2cf7SAlex Zinenko /// - append the instructions from the loops body to worklist, in reverse order. 583*4ead2cf7SAlex Zinenko /// To note the end of the current scope in case a loop or conditional was 584*4ead2cf7SAlex Zinenko /// inserted, a sentinel (the `gpu.launch` operation) is inserted into the 585*4ead2cf7SAlex Zinenko /// worklist. This signals the processor of the worklist to pop the rewriter 586*4ead2cf7SAlex Zinenko /// one scope-level up. 587*4ead2cf7SAlex Zinenko static LogicalResult processParallelLoop( 588*4ead2cf7SAlex Zinenko ParallelOp parallelOp, gpu::LaunchOp launchOp, 589*4ead2cf7SAlex Zinenko BlockAndValueMapping &cloningMap, SmallVectorImpl<Operation *> &worklist, 590*4ead2cf7SAlex Zinenko DenseMap<gpu::Processor, Value> &bounds, PatternRewriter &rewriter) { 591*4ead2cf7SAlex Zinenko // TODO(herhut): Verify that this is a valid GPU mapping. 592*4ead2cf7SAlex Zinenko // processor ids: 0-2 block [x/y/z], 3-5 -> thread [x/y/z], 6-> sequential 593*4ead2cf7SAlex Zinenko ArrayAttr mapping = 594*4ead2cf7SAlex Zinenko parallelOp.getAttrOfType<ArrayAttr>(gpu::getMappingAttrName()); 595*4ead2cf7SAlex Zinenko 596*4ead2cf7SAlex Zinenko // TODO(herhut): Support reductions. 597*4ead2cf7SAlex Zinenko if (!mapping || parallelOp.getNumResults() != 0) 598*4ead2cf7SAlex Zinenko return failure(); 599*4ead2cf7SAlex Zinenko 600*4ead2cf7SAlex Zinenko Location loc = parallelOp.getLoc(); 601*4ead2cf7SAlex Zinenko 602*4ead2cf7SAlex Zinenko auto launchIndependent = [&launchOp](Value val) { 603*4ead2cf7SAlex Zinenko return val.getParentRegion()->isAncestor(launchOp.getParentRegion()); 604*4ead2cf7SAlex Zinenko }; 605*4ead2cf7SAlex Zinenko 606*4ead2cf7SAlex Zinenko auto ensureLaunchIndependent = [&rewriter, 607*4ead2cf7SAlex Zinenko launchIndependent](Value val) -> Value { 608*4ead2cf7SAlex Zinenko if (launchIndependent(val)) 609*4ead2cf7SAlex Zinenko return val; 610*4ead2cf7SAlex Zinenko if (ConstantOp constOp = val.getDefiningOp<ConstantOp>()) 611*4ead2cf7SAlex Zinenko return rewriter.create<ConstantOp>(constOp.getLoc(), constOp.getValue()); 612*4ead2cf7SAlex Zinenko return {}; 613*4ead2cf7SAlex Zinenko }; 614*4ead2cf7SAlex Zinenko 615*4ead2cf7SAlex Zinenko for (auto config : llvm::zip(mapping, parallelOp.getInductionVars(), 616*4ead2cf7SAlex Zinenko parallelOp.lowerBound(), parallelOp.upperBound(), 617*4ead2cf7SAlex Zinenko parallelOp.step())) { 618*4ead2cf7SAlex Zinenko Attribute mappingAttribute; 619*4ead2cf7SAlex Zinenko Value iv, lowerBound, upperBound, step; 620*4ead2cf7SAlex Zinenko std::tie(mappingAttribute, iv, lowerBound, upperBound, step) = config; 621*4ead2cf7SAlex Zinenko auto annotation = mappingAttribute.dyn_cast<gpu::ParallelLoopDimMapping>(); 622*4ead2cf7SAlex Zinenko if (!annotation) 623*4ead2cf7SAlex Zinenko return parallelOp.emitOpError() 624*4ead2cf7SAlex Zinenko << "expected mapping attribute for lowering to GPU"; 625*4ead2cf7SAlex Zinenko Value newIndex; 626*4ead2cf7SAlex Zinenko gpu::Processor processor = gpu::getProcessor(annotation); 627*4ead2cf7SAlex Zinenko 628*4ead2cf7SAlex Zinenko if (isMappedToProcessor(processor)) { 629*4ead2cf7SAlex Zinenko // Use the corresponding thread/grid index as replacement for the loop iv. 630*4ead2cf7SAlex Zinenko Value operand = launchOp.body().front().getArgument( 631*4ead2cf7SAlex Zinenko getLaunchOpArgumentNum(processor)); 632*4ead2cf7SAlex Zinenko // Take the indexmap and add the lower bound and step computations in. 633*4ead2cf7SAlex Zinenko // This computes operand * step + lowerBound. 634*4ead2cf7SAlex Zinenko // Use an affine map here so that it composes nicely with the provided 635*4ead2cf7SAlex Zinenko // annotation. 636*4ead2cf7SAlex Zinenko AffineMap lowerAndStep = AffineMap::get( 637*4ead2cf7SAlex Zinenko 1, 2, 638*4ead2cf7SAlex Zinenko rewriter.getAffineDimExpr(0) * rewriter.getAffineSymbolExpr(0) + 639*4ead2cf7SAlex Zinenko rewriter.getAffineSymbolExpr(1)); 640*4ead2cf7SAlex Zinenko newIndex = rewriter.create<AffineApplyOp>( 641*4ead2cf7SAlex Zinenko loc, annotation.map().getValue().compose(lowerAndStep), 642*4ead2cf7SAlex Zinenko ValueRange{operand, step, lowerBound}); 643*4ead2cf7SAlex Zinenko // If there was also a bound, insert that, too. 644*4ead2cf7SAlex Zinenko // TODO(herhut): Check that we do not assign bounds twice. 645*4ead2cf7SAlex Zinenko if (annotation.bound().getValue()) { 646*4ead2cf7SAlex Zinenko // We pass as the single operand to the bound-map the number of 647*4ead2cf7SAlex Zinenko // iterations, which is (upperBound - lowerBound) ceilDiv step. To 648*4ead2cf7SAlex Zinenko // support inner loops with dynamic upper bounds (as generated by e.g. 649*4ead2cf7SAlex Zinenko // tiling), try to derive a max for the bounds. If the used bound for 650*4ead2cf7SAlex Zinenko // the hardware id is imprecise, wrap the contained code into a 651*4ead2cf7SAlex Zinenko // conditional. If the lower-bound is constant or defined before the 652*4ead2cf7SAlex Zinenko // launch, we can use it in the launch bounds. Otherwise fail. 653*4ead2cf7SAlex Zinenko if (!launchIndependent(lowerBound) && 654*4ead2cf7SAlex Zinenko !isa_and_nonnull<ConstantOp>(lowerBound.getDefiningOp())) 655*4ead2cf7SAlex Zinenko return failure(); 656*4ead2cf7SAlex Zinenko // The step must also be constant or defined outside of the loop nest. 657*4ead2cf7SAlex Zinenko if (!launchIndependent(step) && 658*4ead2cf7SAlex Zinenko !isa_and_nonnull<ConstantOp>(step.getDefiningOp())) 659*4ead2cf7SAlex Zinenko return failure(); 660*4ead2cf7SAlex Zinenko // If the upper-bound is constant or defined before the launch, we can 661*4ead2cf7SAlex Zinenko // use it in the launch bounds directly. Otherwise try derive a bound. 662*4ead2cf7SAlex Zinenko bool boundIsPrecise = 663*4ead2cf7SAlex Zinenko launchIndependent(upperBound) || 664*4ead2cf7SAlex Zinenko isa_and_nonnull<ConstantOp>(upperBound.getDefiningOp()); 665*4ead2cf7SAlex Zinenko { 666*4ead2cf7SAlex Zinenko PatternRewriter::InsertionGuard guard(rewriter); 667*4ead2cf7SAlex Zinenko rewriter.setInsertionPoint(launchOp); 668*4ead2cf7SAlex Zinenko if (!boundIsPrecise) { 669*4ead2cf7SAlex Zinenko upperBound = deriveStaticUpperBound(upperBound, rewriter); 670*4ead2cf7SAlex Zinenko if (!upperBound) { 671*4ead2cf7SAlex Zinenko return parallelOp.emitOpError() 672*4ead2cf7SAlex Zinenko << "cannot derive loop-invariant upper bound for number " 673*4ead2cf7SAlex Zinenko "of iterations"; 674*4ead2cf7SAlex Zinenko } 675*4ead2cf7SAlex Zinenko } 676*4ead2cf7SAlex Zinenko // Compute the number of iterations needed. We compute this as an 677*4ead2cf7SAlex Zinenko // affine expression ceilDiv (upperBound - lowerBound) step. We use 678*4ead2cf7SAlex Zinenko // affine.apply here so that it composes nicely with the provided map. 679*4ead2cf7SAlex Zinenko AffineMap stepMap = 680*4ead2cf7SAlex Zinenko AffineMap::get(0, 3, 681*4ead2cf7SAlex Zinenko ((rewriter.getAffineSymbolExpr(0) - 682*4ead2cf7SAlex Zinenko rewriter.getAffineSymbolExpr(1)) 683*4ead2cf7SAlex Zinenko .ceilDiv(rewriter.getAffineSymbolExpr(2)))); 684*4ead2cf7SAlex Zinenko Value launchBound = rewriter.create<AffineApplyOp>( 685*4ead2cf7SAlex Zinenko loc, annotation.bound().getValue().compose(stepMap), 686*4ead2cf7SAlex Zinenko ValueRange{ 687*4ead2cf7SAlex Zinenko ensureLaunchIndependent( 688*4ead2cf7SAlex Zinenko cloningMap.lookupOrDefault(upperBound)), 689*4ead2cf7SAlex Zinenko ensureLaunchIndependent( 690*4ead2cf7SAlex Zinenko cloningMap.lookupOrDefault(lowerBound)), 691*4ead2cf7SAlex Zinenko ensureLaunchIndependent(cloningMap.lookupOrDefault(step))}); 692*4ead2cf7SAlex Zinenko // todo(herhut,ravishankarm): Update the behavior of setMappingAttr 693*4ead2cf7SAlex Zinenko // when this condition is relaxed. 694*4ead2cf7SAlex Zinenko if (bounds.find(processor) != bounds.end()) { 695*4ead2cf7SAlex Zinenko return parallelOp.emitOpError() 696*4ead2cf7SAlex Zinenko << "cannot redefine the bound for processor " 697*4ead2cf7SAlex Zinenko << static_cast<int64_t>(processor); 698*4ead2cf7SAlex Zinenko } 699*4ead2cf7SAlex Zinenko bounds[processor] = launchBound; 700*4ead2cf7SAlex Zinenko } 701*4ead2cf7SAlex Zinenko if (!boundIsPrecise) { 702*4ead2cf7SAlex Zinenko // We are using an approximation, create a surrounding conditional. 703*4ead2cf7SAlex Zinenko Value originalBound = std::get<3>(config); 704*4ead2cf7SAlex Zinenko CmpIOp pred = rewriter.create<CmpIOp>( 705*4ead2cf7SAlex Zinenko loc, CmpIPredicate::slt, newIndex, 706*4ead2cf7SAlex Zinenko cloningMap.lookupOrDefault(originalBound)); 707*4ead2cf7SAlex Zinenko scf::IfOp ifOp = rewriter.create<scf::IfOp>(loc, pred, false); 708*4ead2cf7SAlex Zinenko rewriter.setInsertionPointToStart(&ifOp.thenRegion().front()); 709*4ead2cf7SAlex Zinenko // Put a sentinel into the worklist so we know when to pop out of the 710*4ead2cf7SAlex Zinenko // if body again. We use the launchOp here, as that cannot be part of 711*4ead2cf7SAlex Zinenko // the bodies instruction. 712*4ead2cf7SAlex Zinenko worklist.push_back(launchOp.getOperation()); 713*4ead2cf7SAlex Zinenko } 714*4ead2cf7SAlex Zinenko } 715*4ead2cf7SAlex Zinenko } else { 716*4ead2cf7SAlex Zinenko // Create a sequential for loop. 717*4ead2cf7SAlex Zinenko auto loopOp = rewriter.create<scf::ForOp>( 718*4ead2cf7SAlex Zinenko loc, cloningMap.lookupOrDefault(lowerBound), 719*4ead2cf7SAlex Zinenko cloningMap.lookupOrDefault(upperBound), 720*4ead2cf7SAlex Zinenko cloningMap.lookupOrDefault(step)); 721*4ead2cf7SAlex Zinenko newIndex = loopOp.getInductionVar(); 722*4ead2cf7SAlex Zinenko rewriter.setInsertionPointToStart(loopOp.getBody()); 723*4ead2cf7SAlex Zinenko // Put a sentinel into the worklist so we know when to pop out of the loop 724*4ead2cf7SAlex Zinenko // body again. We use the launchOp here, as that cannot be part of the 725*4ead2cf7SAlex Zinenko // bodies instruction. 726*4ead2cf7SAlex Zinenko worklist.push_back(launchOp.getOperation()); 727*4ead2cf7SAlex Zinenko } 728*4ead2cf7SAlex Zinenko cloningMap.map(iv, newIndex); 729*4ead2cf7SAlex Zinenko } 730*4ead2cf7SAlex Zinenko Block *body = parallelOp.getBody(); 731*4ead2cf7SAlex Zinenko worklist.reserve(worklist.size() + body->getOperations().size()); 732*4ead2cf7SAlex Zinenko for (Operation &op : llvm::reverse(body->without_terminator())) 733*4ead2cf7SAlex Zinenko worklist.push_back(&op); 734*4ead2cf7SAlex Zinenko return success(); 735*4ead2cf7SAlex Zinenko } 736*4ead2cf7SAlex Zinenko 737*4ead2cf7SAlex Zinenko /// Lower a `scf.parallel` operation into a corresponding `gpu.launch` 738*4ead2cf7SAlex Zinenko /// operation. 739*4ead2cf7SAlex Zinenko /// 740*4ead2cf7SAlex Zinenko /// This essentially transforms a loop nest into a corresponding SIMT function. 741*4ead2cf7SAlex Zinenko /// The conversion is driven by mapping annotations on the `scf.parallel` 742*4ead2cf7SAlex Zinenko /// operations. The mapping is provided via a `DictionaryAttribute` named 743*4ead2cf7SAlex Zinenko /// `mapping`, which has three entries: 744*4ead2cf7SAlex Zinenko /// - processor: the hardware id to map to. 0-2 are block dimensions, 3-5 are 745*4ead2cf7SAlex Zinenko /// thread dimensions and 6 is sequential. 746*4ead2cf7SAlex Zinenko /// - map : An affine map that is used to pre-process hardware ids before 747*4ead2cf7SAlex Zinenko /// substitution. 748*4ead2cf7SAlex Zinenko /// - bound : An affine map that is used to compute the bound of the hardware 749*4ead2cf7SAlex Zinenko /// id based on an upper bound of the number of iterations. 750*4ead2cf7SAlex Zinenko /// If the `scf.parallel` contains nested `scf.parallel` operations, those 751*4ead2cf7SAlex Zinenko /// need to be annotated, as well. Structurally, the transformation works by 752*4ead2cf7SAlex Zinenko /// splicing all operations from nested `scf.parallel` operations into a single 753*4ead2cf7SAlex Zinenko /// sequence. Indices mapped to hardware ids are substituted with those ids, 754*4ead2cf7SAlex Zinenko /// wheras sequential mappings result in a sequential for-loop. To have more 755*4ead2cf7SAlex Zinenko /// flexibility when mapping code to hardware ids, the transform supports two 756*4ead2cf7SAlex Zinenko /// affine maps. The first `map` is used to compute the actual index for 757*4ead2cf7SAlex Zinenko /// substitution from the hardware id. The second `bound` is used to compute the 758*4ead2cf7SAlex Zinenko /// launch dimension for the hardware id from the number of iterations the 759*4ead2cf7SAlex Zinenko /// mapped loop is performing. Note that the number of iterations might be 760*4ead2cf7SAlex Zinenko /// imprecise if the corresponding loop-bounds are loop-dependent. In such case, 761*4ead2cf7SAlex Zinenko /// the hardware id might iterate over additional indices. The transformation 762*4ead2cf7SAlex Zinenko /// caters for this by predicating the created sequence of instructions on 763*4ead2cf7SAlex Zinenko /// the actual loop bound. This only works if an static upper bound for the 764*4ead2cf7SAlex Zinenko /// dynamic loop bound can be derived, currently via analyzing `affine.min` 765*4ead2cf7SAlex Zinenko /// operations. 766*4ead2cf7SAlex Zinenko LogicalResult 767*4ead2cf7SAlex Zinenko ParallelToGpuLaunchLowering::matchAndRewrite(ParallelOp parallelOp, 768*4ead2cf7SAlex Zinenko PatternRewriter &rewriter) const { 769*4ead2cf7SAlex Zinenko // Create a launch operation. We start with bound one for all grid/block 770*4ead2cf7SAlex Zinenko // sizes. Those will be refined later as we discover them from mappings. 771*4ead2cf7SAlex Zinenko Location loc = parallelOp.getLoc(); 772*4ead2cf7SAlex Zinenko Value constantOne = rewriter.create<ConstantIndexOp>(parallelOp.getLoc(), 1); 773*4ead2cf7SAlex Zinenko gpu::LaunchOp launchOp = rewriter.create<gpu::LaunchOp>( 774*4ead2cf7SAlex Zinenko parallelOp.getLoc(), constantOne, constantOne, constantOne, constantOne, 775*4ead2cf7SAlex Zinenko constantOne, constantOne); 776*4ead2cf7SAlex Zinenko rewriter.setInsertionPointToEnd(&launchOp.body().front()); 777*4ead2cf7SAlex Zinenko rewriter.create<gpu::TerminatorOp>(loc); 778*4ead2cf7SAlex Zinenko rewriter.setInsertionPointToStart(&launchOp.body().front()); 779*4ead2cf7SAlex Zinenko 780*4ead2cf7SAlex Zinenko BlockAndValueMapping cloningMap; 781*4ead2cf7SAlex Zinenko llvm::DenseMap<gpu::Processor, Value> launchBounds; 782*4ead2cf7SAlex Zinenko SmallVector<Operation *, 16> worklist; 783*4ead2cf7SAlex Zinenko if (failed(processParallelLoop(parallelOp, launchOp, cloningMap, worklist, 784*4ead2cf7SAlex Zinenko launchBounds, rewriter))) 785*4ead2cf7SAlex Zinenko return failure(); 786*4ead2cf7SAlex Zinenko 787*4ead2cf7SAlex Zinenko // Whether we have seen any side-effects. Reset when leaving an inner scope. 788*4ead2cf7SAlex Zinenko bool seenSideeffects = false; 789*4ead2cf7SAlex Zinenko // Whether we have left a nesting scope (and hence are no longer innermost). 790*4ead2cf7SAlex Zinenko bool leftNestingScope = false; 791*4ead2cf7SAlex Zinenko while (!worklist.empty()) { 792*4ead2cf7SAlex Zinenko Operation *op = worklist.pop_back_val(); 793*4ead2cf7SAlex Zinenko // Now walk over the body and clone it. 794*4ead2cf7SAlex Zinenko // TODO: This is only correct if there either is no further scf.parallel 795*4ead2cf7SAlex Zinenko // nested or this code is side-effect free. Otherwise we might need 796*4ead2cf7SAlex Zinenko // predication. We are overly conservative for now and only allow 797*4ead2cf7SAlex Zinenko // side-effects in the innermost scope. 798*4ead2cf7SAlex Zinenko if (auto nestedParallel = dyn_cast<ParallelOp>(op)) { 799*4ead2cf7SAlex Zinenko // Before entering a nested scope, make sure there have been no 800*4ead2cf7SAlex Zinenko // sideeffects until now. 801*4ead2cf7SAlex Zinenko if (seenSideeffects) 802*4ead2cf7SAlex Zinenko return failure(); 803*4ead2cf7SAlex Zinenko // A nested scf.parallel needs insertion of code to compute indices. 804*4ead2cf7SAlex Zinenko // Insert that now. This will also update the worklist with the loops 805*4ead2cf7SAlex Zinenko // body. 806*4ead2cf7SAlex Zinenko if (failed(processParallelLoop(nestedParallel, launchOp, cloningMap, 807*4ead2cf7SAlex Zinenko worklist, launchBounds, rewriter))) 808*4ead2cf7SAlex Zinenko return failure(); 809*4ead2cf7SAlex Zinenko } else if (op == launchOp.getOperation()) { 810*4ead2cf7SAlex Zinenko // Found our sentinel value. We have finished the operations from one 811*4ead2cf7SAlex Zinenko // nesting level, pop one level back up. 812*4ead2cf7SAlex Zinenko auto parent = rewriter.getInsertionPoint()->getParentOp(); 813*4ead2cf7SAlex Zinenko rewriter.setInsertionPointAfter(parent); 814*4ead2cf7SAlex Zinenko leftNestingScope = true; 815*4ead2cf7SAlex Zinenko seenSideeffects = false; 816*4ead2cf7SAlex Zinenko } else { 817*4ead2cf7SAlex Zinenko // Otherwise we copy it over. 818*4ead2cf7SAlex Zinenko Operation *clone = rewriter.clone(*op, cloningMap); 819*4ead2cf7SAlex Zinenko cloningMap.map(op->getResults(), clone->getResults()); 820*4ead2cf7SAlex Zinenko // Check for side effects. 821*4ead2cf7SAlex Zinenko // TODO: Handle region side effects properly. 822*4ead2cf7SAlex Zinenko seenSideeffects |= !MemoryEffectOpInterface::hasNoEffect(clone) || 823*4ead2cf7SAlex Zinenko clone->getNumRegions() != 0; 824*4ead2cf7SAlex Zinenko // If we are no longer in the innermost scope, sideeffects are disallowed. 825*4ead2cf7SAlex Zinenko if (seenSideeffects && leftNestingScope) 826*4ead2cf7SAlex Zinenko return failure(); 827*4ead2cf7SAlex Zinenko } 828*4ead2cf7SAlex Zinenko } 829*4ead2cf7SAlex Zinenko 830*4ead2cf7SAlex Zinenko // Now that we succeeded creating the launch operation, also update the 831*4ead2cf7SAlex Zinenko // bounds. 832*4ead2cf7SAlex Zinenko for (auto bound : launchBounds) 833*4ead2cf7SAlex Zinenko launchOp.setOperand(getLaunchOpArgumentNum(std::get<0>(bound)), 834*4ead2cf7SAlex Zinenko std::get<1>(bound)); 835*4ead2cf7SAlex Zinenko 836*4ead2cf7SAlex Zinenko rewriter.eraseOp(parallelOp); 837*4ead2cf7SAlex Zinenko return success(); 838*4ead2cf7SAlex Zinenko } 839*4ead2cf7SAlex Zinenko 840*4ead2cf7SAlex Zinenko void mlir::populateParallelLoopToGPUPatterns(OwningRewritePatternList &patterns, 841*4ead2cf7SAlex Zinenko MLIRContext *ctx) { 842*4ead2cf7SAlex Zinenko patterns.insert<ParallelToGpuLaunchLowering>(ctx); 843*4ead2cf7SAlex Zinenko } 844