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>(&currentLoop.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