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 ®ion, 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(®ion.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(®ion.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