108778d8cSAlex Zinenko //===- MemoryPromotion.cpp - Utilities for moving data across GPU memories ===//
208778d8cSAlex Zinenko //
308778d8cSAlex Zinenko // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
408778d8cSAlex Zinenko // See https://llvm.org/LICENSE.txt for license information.
508778d8cSAlex Zinenko // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
608778d8cSAlex Zinenko //
708778d8cSAlex Zinenko //===----------------------------------------------------------------------===//
808778d8cSAlex Zinenko //
908778d8cSAlex Zinenko // This file implements utilities that allow one to create IR moving the data
1008778d8cSAlex Zinenko // across different levels of the GPU memory hierarchy.
1108778d8cSAlex Zinenko //
1208778d8cSAlex Zinenko //===----------------------------------------------------------------------===//
1308778d8cSAlex Zinenko 
1408778d8cSAlex Zinenko #include "mlir/Dialect/GPU/MemoryPromotion.h"
1508778d8cSAlex Zinenko #include "mlir/Dialect/GPU/GPUDialect.h"
1608778d8cSAlex Zinenko #include "mlir/Dialect/LoopOps/LoopOps.h"
1708778d8cSAlex Zinenko #include "mlir/EDSC/Builders.h"
1808778d8cSAlex Zinenko #include "mlir/EDSC/Helpers.h"
1908778d8cSAlex Zinenko #include "mlir/Pass/Pass.h"
2008778d8cSAlex Zinenko #include "mlir/Support/Functional.h"
2108778d8cSAlex Zinenko #include "mlir/Transforms/LoopUtils.h"
2208778d8cSAlex Zinenko 
2308778d8cSAlex Zinenko using namespace mlir;
2408778d8cSAlex Zinenko using namespace mlir::gpu;
2508778d8cSAlex Zinenko 
2608778d8cSAlex Zinenko /// Returns the textual name of a GPU dimension.
2708778d8cSAlex Zinenko static StringRef getDimName(unsigned dim) {
2808778d8cSAlex Zinenko   if (dim == 0)
2908778d8cSAlex Zinenko     return "x";
3008778d8cSAlex Zinenko   if (dim == 1)
3108778d8cSAlex Zinenko     return "y";
3208778d8cSAlex Zinenko   if (dim == 2)
3308778d8cSAlex Zinenko     return "z";
3408778d8cSAlex Zinenko 
3508778d8cSAlex Zinenko   llvm_unreachable("dimension ID overflow");
3608778d8cSAlex Zinenko }
3708778d8cSAlex Zinenko 
3808778d8cSAlex Zinenko /// Emits the (imperfect) loop nest performing the copy between "from" and "to"
3908778d8cSAlex Zinenko /// values using the bounds derived from the "from" value. Emits at least
4008778d8cSAlex Zinenko /// GPUDialect::getNumWorkgroupDimensions() loops, completing the nest with
4108778d8cSAlex Zinenko /// single-iteration loops. Maps the innermost loops to thread dimensions, in
4208778d8cSAlex Zinenko /// reverse order to enable access coalescing in the innermost loop.
4308778d8cSAlex Zinenko static void insertCopyLoops(OpBuilder &builder, Location loc,
4408778d8cSAlex Zinenko                             edsc::MemRefView &bounds, Value from, Value to) {
4508778d8cSAlex Zinenko   // Create EDSC handles for bounds.
4608778d8cSAlex Zinenko   unsigned rank = bounds.rank();
4708778d8cSAlex Zinenko   SmallVector<edsc::ValueHandle, 4> lbs, ubs, steps;
4808778d8cSAlex Zinenko 
4908778d8cSAlex Zinenko   // Make sure we have enough loops to use all thread dimensions, these trivial
5008778d8cSAlex Zinenko   // loops should be outermost and therefore inserted first.
5108778d8cSAlex Zinenko   if (rank < GPUDialect::getNumWorkgroupDimensions()) {
5208778d8cSAlex Zinenko     unsigned extraLoops = GPUDialect::getNumWorkgroupDimensions() - rank;
5308778d8cSAlex Zinenko     edsc::ValueHandle zero = edsc::intrinsics::constant_index(0);
5408778d8cSAlex Zinenko     edsc::ValueHandle one = edsc::intrinsics::constant_index(1);
5508778d8cSAlex Zinenko     lbs.resize(extraLoops, zero);
5608778d8cSAlex Zinenko     ubs.resize(extraLoops, one);
5708778d8cSAlex Zinenko     steps.resize(extraLoops, one);
5808778d8cSAlex Zinenko   }
5908778d8cSAlex Zinenko 
60*73f371c3SKazuaki Ishizaki   // Add existing bounds.
6108778d8cSAlex Zinenko   lbs.append(bounds.getLbs().begin(), bounds.getLbs().end());
6208778d8cSAlex Zinenko   ubs.append(bounds.getUbs().begin(), bounds.getUbs().end());
6308778d8cSAlex Zinenko 
6408778d8cSAlex Zinenko   // Emit constant operations for steps.
6508778d8cSAlex Zinenko   steps.reserve(lbs.size());
6608778d8cSAlex Zinenko   llvm::transform(
6708778d8cSAlex Zinenko       bounds.getSteps(), std::back_inserter(steps),
6808778d8cSAlex Zinenko       [](int64_t step) { return edsc::intrinsics::constant_index(step); });
6908778d8cSAlex Zinenko 
7008778d8cSAlex Zinenko   // Obtain thread identifiers and block sizes, necessary to map to them.
7108778d8cSAlex Zinenko   auto indexType = builder.getIndexType();
7208778d8cSAlex Zinenko   SmallVector<Value, 3> threadIds, blockDims;
7308778d8cSAlex Zinenko   for (unsigned i = 0; i < 3; ++i) {
7408778d8cSAlex Zinenko     auto dimName = builder.getStringAttr(getDimName(i));
7508778d8cSAlex Zinenko     threadIds.push_back(
7608778d8cSAlex Zinenko         builder.create<gpu::ThreadIdOp>(loc, indexType, dimName));
7708778d8cSAlex Zinenko     blockDims.push_back(
7808778d8cSAlex Zinenko         builder.create<gpu::BlockDimOp>(loc, indexType, dimName));
7908778d8cSAlex Zinenko   }
8008778d8cSAlex Zinenko 
8108778d8cSAlex Zinenko   // Produce the loop nest with copies.
8208778d8cSAlex Zinenko   auto ivs = edsc::makeIndexHandles(lbs.size());
8308778d8cSAlex Zinenko   auto ivPtrs =
8408778d8cSAlex Zinenko       edsc::makeHandlePointers(MutableArrayRef<edsc::IndexHandle>(ivs));
8508778d8cSAlex Zinenko   edsc::LoopNestBuilder(ivPtrs, lbs, ubs, steps)([&]() {
8608778d8cSAlex Zinenko     auto activeIvs = llvm::makeArrayRef(ivs).take_back(rank);
8708778d8cSAlex Zinenko     edsc::StdIndexedValue fromHandle(from), toHandle(to);
8808778d8cSAlex Zinenko     toHandle(activeIvs) = fromHandle(activeIvs);
8908778d8cSAlex Zinenko   });
9008778d8cSAlex Zinenko 
9108778d8cSAlex Zinenko   // Map the innermost loops to threads in reverse order.
9208778d8cSAlex Zinenko   for (auto en :
9308778d8cSAlex Zinenko        llvm::enumerate(llvm::reverse(llvm::makeArrayRef(ivs).take_back(
9408778d8cSAlex Zinenko            GPUDialect::getNumWorkgroupDimensions())))) {
9508778d8cSAlex Zinenko     auto loop = cast<loop::ForOp>(
9608778d8cSAlex Zinenko         en.value().getValue().getParentRegion()->getParentOp());
9708778d8cSAlex Zinenko     mapLoopToProcessorIds(loop, {threadIds[en.index()]},
9808778d8cSAlex Zinenko                           {blockDims[en.index()]});
9908778d8cSAlex Zinenko   }
10008778d8cSAlex Zinenko }
10108778d8cSAlex Zinenko 
10208778d8cSAlex Zinenko /// Emits the loop nests performing the copy to the designated location in the
10308778d8cSAlex Zinenko /// beginning of the region, and from the designated location immediately before
10408778d8cSAlex Zinenko /// the terminator of the first block of the region. The region is expected to
10508778d8cSAlex Zinenko /// have one block. This boils down to the following structure
10608778d8cSAlex Zinenko ///
10708778d8cSAlex Zinenko ///   ^bb(...):
10808778d8cSAlex Zinenko ///     <loop-bound-computation>
10908778d8cSAlex Zinenko ///     for %arg0 = ... to ... step ... {
11008778d8cSAlex Zinenko ///       ...
11108778d8cSAlex Zinenko ///         for %argN = <thread-id-x> to ... step <block-dim-x> {
11208778d8cSAlex Zinenko ///           %0 = load %from[%arg0, ..., %argN]
11308778d8cSAlex Zinenko ///           store %0, %to[%arg0, ..., %argN]
11408778d8cSAlex Zinenko ///         }
11508778d8cSAlex Zinenko ///       ...
11608778d8cSAlex Zinenko ///     }
11708778d8cSAlex Zinenko ///     gpu.barrier
11808778d8cSAlex Zinenko ///     <... original body ...>
11908778d8cSAlex Zinenko ///     gpu.barrier
12008778d8cSAlex Zinenko ///     for %arg0 = ... to ... step ... {
12108778d8cSAlex Zinenko ///       ...
12208778d8cSAlex Zinenko ///         for %argN = <thread-id-x> to ... step <block-dim-x> {
12308778d8cSAlex Zinenko ///           %1 = load %to[%arg0, ..., %argN]
12408778d8cSAlex Zinenko ///           store %1, %from[%arg0, ..., %argN]
12508778d8cSAlex Zinenko ///         }
12608778d8cSAlex Zinenko ///       ...
12708778d8cSAlex Zinenko ///     }
12808778d8cSAlex Zinenko ///
12908778d8cSAlex Zinenko /// Inserts the barriers unconditionally since different threads may be copying
13008778d8cSAlex Zinenko /// values and reading them. An analysis would be required to eliminate barriers
13108778d8cSAlex Zinenko /// in case where value is only used by the thread that copies it. Both copies
13208778d8cSAlex Zinenko /// are inserted unconditionally, an analysis would be required to only copy
13308778d8cSAlex Zinenko /// live-in and live-out values when necessary. This copies the entire memref
13408778d8cSAlex Zinenko /// pointed to by "from". In case a smaller block would be sufficient, the
13508778d8cSAlex Zinenko /// caller can create a subview of the memref and promote it instead.
13608778d8cSAlex Zinenko static void insertCopies(Region &region, Location loc, Value from, Value to) {
13708778d8cSAlex Zinenko   auto fromType = from.getType().cast<MemRefType>();
13808778d8cSAlex Zinenko   auto toType = to.getType().cast<MemRefType>();
13908778d8cSAlex Zinenko   (void)fromType;
14008778d8cSAlex Zinenko   (void)toType;
14108778d8cSAlex Zinenko   assert(fromType.getShape() == toType.getShape());
14208778d8cSAlex Zinenko   assert(fromType.getRank() != 0);
14308778d8cSAlex Zinenko   assert(has_single_element(region) &&
14408778d8cSAlex Zinenko          "unstructured control flow not supported");
14508778d8cSAlex Zinenko 
14608778d8cSAlex Zinenko   OpBuilder builder(region.getContext());
14708778d8cSAlex Zinenko   builder.setInsertionPointToStart(&region.front());
14808778d8cSAlex Zinenko 
14908778d8cSAlex Zinenko   edsc::ScopedContext edscContext(builder, loc);
15008778d8cSAlex Zinenko   edsc::MemRefView fromView(from);
15108778d8cSAlex Zinenko   insertCopyLoops(builder, loc, fromView, from, to);
15208778d8cSAlex Zinenko   builder.create<gpu::BarrierOp>(loc);
15308778d8cSAlex Zinenko 
15408778d8cSAlex Zinenko   builder.setInsertionPoint(&region.front().back());
15508778d8cSAlex Zinenko   builder.create<gpu::BarrierOp>(loc);
15608778d8cSAlex Zinenko   insertCopyLoops(builder, loc, fromView, to, from);
15708778d8cSAlex Zinenko }
15808778d8cSAlex Zinenko 
15908778d8cSAlex Zinenko /// Promotes a function argument to workgroup memory in the given function. The
16008778d8cSAlex Zinenko /// copies will be inserted in the beginning and in the end of the function.
16108778d8cSAlex Zinenko void mlir::promoteToWorkgroupMemory(GPUFuncOp op, unsigned arg) {
16208778d8cSAlex Zinenko   Value value = op.getArgument(arg);
16308778d8cSAlex Zinenko   auto type = value.getType().dyn_cast<MemRefType>();
16408778d8cSAlex Zinenko   assert(type && type.hasStaticShape() && "can only promote memrefs");
16508778d8cSAlex Zinenko 
16608778d8cSAlex Zinenko   Value attribution =
16708778d8cSAlex Zinenko       op.addWorkgroupAttribution(type.getShape(), type.getElementType());
16808778d8cSAlex Zinenko 
16908778d8cSAlex Zinenko   // Replace the uses first since only the original uses are currently present.
17008778d8cSAlex Zinenko   // Then insert the copies.
17108778d8cSAlex Zinenko   value.replaceAllUsesWith(attribution);
17208778d8cSAlex Zinenko   insertCopies(op.getBody(), op.getLoc(), value, attribution);
17308778d8cSAlex Zinenko }
174