llvm-project/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
Tres Popp 9a52ea5cf9 Create a gpu.module operation for the GPU Dialect.
Summary:
This is based on the use of code constantly checking for an attribute on
a model and instead represents the distinct operaion with a different
op. Instead, this op can be used to provide better filtering.

Reverts "Revert "[mlir] Create a gpu.module operation for the GPU Dialect.""

This reverts commit ac446302ca4145cdc89f377c0c364c29ee303be5 after
fixing internal Google issues.

This additionally updates ROCDL lowering to use the new gpu.module.

Reviewers: herhut, mravishankar, antiagainst, nicolasvasilache

Subscribers: jholewinski, mgorny, mehdi_amini, jpienaar, burmako, shauheen, csigg, arpith-jacob, mgester, lucyrfox, aartbik, liufengdb, llvm-commits, mravishankar, rriddle, antiagainst, bkramer

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D72921
2020-01-21 14:05:03 +01:00

758 lines
34 KiB
C++

//===- LowerGpuOpsToNVVMOps.cpp - MLIR GPU to NVVM lowering passes --------===//
//
// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a pass to generate NVVMIR operations for higher-level
// GPU operations.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h"
#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVMPass.h"
#include "mlir/Dialect/GPU/GPUDialect.h"
#include "mlir/Dialect/LLVMIR/NVVMDialect.h"
#include "mlir/IR/BlockAndValueMapping.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/Support/FormatVariadic.h"
#include "../GPUCommon/IndexIntrinsicsOpLowering.h"
#include "../GPUCommon/OpToFuncCallLowering.h"
using namespace mlir;
namespace {
/// Derived type converter for GPU to NVVM lowering. The GPU dialect uses memory
/// space 5 for private memory attributions, but NVVM represents private
/// memory allocations as local `alloca`s in the default address space. This
/// converter drops the private memory space to support the use case above.
class NVVMTypeConverter : public LLVMTypeConverter {
public:
using LLVMTypeConverter::LLVMTypeConverter;
Type convertType(Type type) override {
auto memref = type.dyn_cast<MemRefType>();
if (memref &&
memref.getMemorySpace() == gpu::GPUDialect::getPrivateAddressSpace()) {
type = MemRefType::get(memref.getShape(), memref.getElementType(),
memref.getAffineMaps());
}
return LLVMTypeConverter::convertType(type);
}
};
/// Converts all_reduce op to LLVM/NVVM ops.
struct GPUAllReduceOpLowering : public LLVMOpLowering {
using AccumulatorFactory =
std::function<Value(Location, Value, Value, ConversionPatternRewriter &)>;
explicit GPUAllReduceOpLowering(LLVMTypeConverter &lowering_)
: LLVMOpLowering(gpu::AllReduceOp::getOperationName(),
lowering_.getDialect()->getContext(), lowering_),
int32Type(LLVM::LLVMType::getInt32Ty(lowering_.getDialect())) {}
PatternMatchResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = op->getLoc();
Value operand = operands.front();
// TODO(csigg): Generalize to other types of accumulation.
assert(op->getOperand(0).getType().isIntOrFloat());
// Create the reduction using an accumulator factory.
AccumulatorFactory factory =
getFactory(cast<gpu::AllReduceOp>(op), operand);
assert(factory && "failed to create accumulator factory");
Value result = createBlockReduce(loc, operand, factory, rewriter);
rewriter.replaceOp(op, {result});
return matchSuccess();
}
private:
/// Returns an accumulator factory using either the op attribute or the body
/// region.
AccumulatorFactory getFactory(gpu::AllReduceOp allReduce,
Value operand) const {
if (!allReduce.body().empty()) {
return getFactory(allReduce.body());
}
if (allReduce.op()) {
auto type = operand.getType().cast<LLVM::LLVMType>();
return getFactory(*allReduce.op(), type.getUnderlyingType());
}
return AccumulatorFactory();
}
/// Returns an accumulator factory that clones the body. The body's entry
/// block is expected to have 2 arguments. The gpu.yield return the
/// accumulated value of the same type.
AccumulatorFactory getFactory(Region &body) const {
return AccumulatorFactory([&](Location loc, Value lhs, Value rhs,
ConversionPatternRewriter &rewriter) {
Block *block = rewriter.getInsertionBlock();
Block *split = rewriter.splitBlock(block, rewriter.getInsertionPoint());
// Insert accumulator body between split block.
BlockAndValueMapping mapping;
mapping.map(body.front().getArgument(0), lhs);
mapping.map(body.front().getArgument(1), rhs);
rewriter.cloneRegionBefore(body, *split->getParent(),
split->getIterator(), mapping);
// Add branch before inserted body, into body.
block = block->getNextNode();
rewriter.create<LLVM::BrOp>(loc, ArrayRef<Value>{},
llvm::makeArrayRef(block), ValueRange());
// Replace all gpu.yield ops with branch out of body.
for (; block != split; block = block->getNextNode()) {
Operation *terminator = block->getTerminator();
if (!llvm::isa<gpu::YieldOp>(terminator))
continue;
rewriter.setInsertionPointToEnd(block);
rewriter.replaceOpWithNewOp<LLVM::BrOp>(
terminator, ArrayRef<Value>{}, llvm::makeArrayRef(split),
ValueRange(terminator->getOperand(0)));
}
// Return accumulator result.
rewriter.setInsertionPointToStart(split);
return split->addArgument(lhs.getType());
});
}
/// Returns an accumulator factory that creates an op specified by opName.
AccumulatorFactory getFactory(StringRef opName, llvm::Type *type) const {
if (type->isVectorTy() || type->isArrayTy())
return getFactory(opName, type->getSequentialElementType());
bool isFloatingPoint = type->isFloatingPointTy();
if (opName == "add") {
return isFloatingPoint ? getFactory<LLVM::FAddOp>()
: getFactory<LLVM::AddOp>();
}
if (opName == "mul") {
return isFloatingPoint ? getFactory<LLVM::FMulOp>()
: getFactory<LLVM::MulOp>();
}
return AccumulatorFactory();
}
/// Returns an accumulator factory that creates an op of type T.
template <typename T> AccumulatorFactory getFactory() const {
return [](Location loc, Value lhs, Value rhs,
ConversionPatternRewriter &rewriter) {
return rewriter.create<T>(loc, lhs.getType(), lhs, rhs);
};
}
/// Creates an all_reduce across the block.
///
/// First reduce the elements within a warp. The first thread of each warp
/// writes the intermediate result to shared memory. After synchronizing the
/// block, the first warp reduces the values from shared memory. The result
/// is broadcasted to all threads through shared memory.
///
/// %warp_reduce = `createWarpReduce(%operand)`
/// %shared_mem_ptr = llvm.mlir.addressof @reduce_buffer
/// %zero = llvm.mlir.constant(0 : i32) : !llvm.i32
/// %lane_id = nvvm.read.ptx.sreg.laneid : !llvm.i32
/// %is_first_lane = llvm.icmp "eq" %lane_id, %zero : !llvm.i1
/// %thread_idx = `getLinearThreadIndex()` : !llvm.i32
/// llvm.cond_br %is_first_lane, ^then1, ^continue1
/// ^then1:
/// %warp_id = `getWarpId()`
/// %store_dst = llvm.getelementptr %shared_mem_ptr[%zero, %warp_id]
/// llvm.store %store_dst, %warp_reduce
/// llvm.br ^continue1
/// ^continue1:
/// nvvm.barrier0
/// %num_warps = `getNumWarps()` : !llvm.i32
/// %is_valid_warp = llvm.icmp "slt" %thread_idx, %num_warps
/// %result_ptr = llvm.getelementptr %shared_mem_ptr[%zero, %zero]
/// llvm.cond_br %is_first_lane, ^then2, ^continue2
/// ^then2:
/// %load_src = llvm.getelementptr %shared_mem_ptr[%zero, %thread_idx]
/// %value = llvm.load %load_src
/// %result = `createWarpReduce(%value)`
/// llvm.store %result_ptr, %result
/// llvm.br ^continue2
/// ^continue2:
/// nvvm.barrier0
/// %result = llvm.load %result_ptr
/// return %result
///
Value createBlockReduce(Location loc, Value operand,
AccumulatorFactory &accumFactory,
ConversionPatternRewriter &rewriter) const {
auto type = operand.getType().cast<LLVM::LLVMType>();
// Create shared memory array to store the warp reduction.
auto module = operand.getDefiningOp()->getParentOfType<gpu::GPUModuleOp>();
assert(module && "op must belong to a module");
Value sharedMemPtr =
createSharedMemoryArray(loc, module, type, kWarpSize, rewriter);
Value zero = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(0u));
Value laneId = rewriter.create<NVVM::LaneIdOp>(loc, int32Type);
Value isFirstLane = rewriter.create<LLVM::ICmpOp>(
loc, LLVM::ICmpPredicate::eq, laneId, zero);
Value threadIdx = getLinearThreadIndex(loc, rewriter);
Value blockSize = getBlockSize(loc, rewriter);
Value activeWidth = getActiveWidth(loc, threadIdx, blockSize, rewriter);
// Reduce elements within each warp to produce the intermediate results.
Value warpReduce = createWarpReduce(loc, activeWidth, laneId, operand,
accumFactory, rewriter);
// Write the intermediate results to shared memory, using the first lane of
// each warp.
createPredicatedBlock(loc, rewriter, isFirstLane, [&] {
Value warpId = getDivideByWarpSize(threadIdx, rewriter);
Value storeDst = rewriter.create<LLVM::GEPOp>(
loc, type, sharedMemPtr, ArrayRef<Value>({zero, warpId}));
rewriter.create<LLVM::StoreOp>(loc, warpReduce, storeDst);
});
rewriter.create<NVVM::Barrier0Op>(loc);
Value numWarps = getNumWarps(loc, blockSize, rewriter);
Value isValidWarp = rewriter.create<LLVM::ICmpOp>(
loc, LLVM::ICmpPredicate::slt, threadIdx, numWarps);
Value resultPtr = rewriter.create<LLVM::GEPOp>(
loc, type, sharedMemPtr, ArrayRef<Value>({zero, zero}));
// Use the first numWarps threads to reduce the intermediate results from
// shared memory. The final result is written to shared memory again.
createPredicatedBlock(loc, rewriter, isValidWarp, [&] {
Value loadSrc = rewriter.create<LLVM::GEPOp>(
loc, type, sharedMemPtr, ArrayRef<Value>({zero, threadIdx}));
Value value = rewriter.create<LLVM::LoadOp>(loc, type, loadSrc);
Value result = createWarpReduce(loc, numWarps, laneId, value,
accumFactory, rewriter);
rewriter.create<LLVM::StoreOp>(loc, result, resultPtr);
});
rewriter.create<NVVM::Barrier0Op>(loc);
// Load and return result from shared memory.
Value result = rewriter.create<LLVM::LoadOp>(loc, type, resultPtr);
return result;
}
/// Creates an if-block skeleton and calls the two factories to generate the
/// ops in the `then` and `else` block..
///
/// llvm.cond_br %condition, ^then, ^continue
/// ^then:
/// %then_operands = `thenOpsFactory()`
/// llvm.br ^continue(%then_operands)
/// ^else:
/// %else_operands = `elseOpsFactory()`
/// llvm.br ^continue(%else_operands)
/// ^continue(%block_operands):
///
template <typename ThenOpsFactory, typename ElseOpsFactory>
void createIf(Location loc, ConversionPatternRewriter &rewriter,
Value condition, ThenOpsFactory &&thenOpsFactory,
ElseOpsFactory &&elseOpsFactory) const {
Block *currentBlock = rewriter.getInsertionBlock();
auto currentPoint = rewriter.getInsertionPoint();
Block *thenBlock = rewriter.splitBlock(currentBlock, currentPoint);
Block *elseBlock = rewriter.splitBlock(thenBlock, thenBlock->begin());
Block *continueBlock = rewriter.splitBlock(elseBlock, elseBlock->begin());
rewriter.setInsertionPointToEnd(currentBlock);
rewriter.create<LLVM::CondBrOp>(loc, llvm::makeArrayRef(condition),
ArrayRef<Block *>{thenBlock, elseBlock});
auto addBranch = [&](ValueRange operands) {
rewriter.create<LLVM::BrOp>(loc, ArrayRef<Value>{},
llvm::makeArrayRef(continueBlock),
llvm::makeArrayRef(operands));
};
rewriter.setInsertionPointToStart(thenBlock);
auto thenOperands = thenOpsFactory();
addBranch(thenOperands);
rewriter.setInsertionPointToStart(elseBlock);
auto elseOperands = elseOpsFactory();
addBranch(elseOperands);
assert(thenOperands.size() == elseOperands.size());
rewriter.setInsertionPointToStart(continueBlock);
for (auto operand : thenOperands)
continueBlock->addArgument(operand.getType());
}
/// Shortcut for createIf with empty else block and no block operands.
template <typename Factory>
void createPredicatedBlock(Location loc, ConversionPatternRewriter &rewriter,
Value condition,
Factory &&predicatedOpsFactory) const {
createIf(
loc, rewriter, condition,
[&] {
predicatedOpsFactory();
return ArrayRef<Value>();
},
[&] { return ArrayRef<Value>(); });
}
/// Creates a reduction across the first activeWidth lanes of a warp.
/// The first lane returns the result, all others return values are undefined.
Value createWarpReduce(Location loc, Value activeWidth, Value laneId,
Value operand, AccumulatorFactory accumFactory,
ConversionPatternRewriter &rewriter) const {
Value warpSize = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize));
Value isPartialWarp = rewriter.create<LLVM::ICmpOp>(
loc, LLVM::ICmpPredicate::slt, activeWidth, warpSize);
auto type = operand.getType().cast<LLVM::LLVMType>();
createIf(
loc, rewriter, isPartialWarp,
// Generate reduction over a (potentially) partial warp.
[&] {
Value value = operand;
Value one = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(1));
// Bit mask of active lanes: `(1 << activeWidth) - 1`.
Value activeMask = rewriter.create<LLVM::SubOp>(
loc, int32Type,
rewriter.create<LLVM::ShlOp>(loc, int32Type, one, activeWidth),
one);
// Clamp lane: `activeWidth - 1`
Value maskAndClamp =
rewriter.create<LLVM::SubOp>(loc, int32Type, activeWidth, one);
auto dialect = lowering.getDialect();
auto predTy = LLVM::LLVMType::getInt1Ty(dialect);
auto shflTy = LLVM::LLVMType::getStructTy(dialect, {type, predTy});
auto returnValueAndIsValidAttr = rewriter.getUnitAttr();
// Repeatedly shuffle value from 'laneId ^ i' and accumulate if source
// lane is within the active range. All lanes contain the final
// result, but only the first lane's result is used.
for (int i = 1; i < kWarpSize; i <<= 1) {
Value offset = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(i));
Value shfl = rewriter.create<NVVM::ShflBflyOp>(
loc, shflTy, activeMask, value, offset, maskAndClamp,
returnValueAndIsValidAttr);
Value isActiveSrcLane = rewriter.create<LLVM::ExtractValueOp>(
loc, predTy, shfl, rewriter.getIndexArrayAttr(1));
// Skip the accumulation if the shuffle op read from a lane outside
// of the active range.
createIf(
loc, rewriter, isActiveSrcLane,
[&] {
Value shflValue = rewriter.create<LLVM::ExtractValueOp>(
loc, type, shfl, rewriter.getIndexArrayAttr(0));
return SmallVector<Value, 1>{
accumFactory(loc, value, shflValue, rewriter)};
},
[&] { return llvm::makeArrayRef(value); });
value = rewriter.getInsertionBlock()->getArgument(0);
}
return SmallVector<Value, 1>{value};
},
// Generate a reduction over the entire warp. This is a specialization
// of the above reduction with unconditional accumulation.
[&] {
Value value = operand;
Value activeMask = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(~0u));
Value maskAndClamp = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize - 1));
for (int i = 1; i < kWarpSize; i <<= 1) {
Value offset = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(i));
Value shflValue = rewriter.create<NVVM::ShflBflyOp>(
loc, type, activeMask, value, offset, maskAndClamp,
/*return_value_and_is_valid=*/UnitAttr());
value = accumFactory(loc, value, shflValue, rewriter);
}
return SmallVector<Value, 1>{value};
});
return rewriter.getInsertionBlock()->getArgument(0);
}
/// Creates a global array stored in shared memory.
Value createSharedMemoryArray(Location loc, gpu::GPUModuleOp module,
LLVM::LLVMType elementType, int numElements,
ConversionPatternRewriter &rewriter) const {
OpBuilder builder(module.body());
auto arrayType = LLVM::LLVMType::getArrayTy(elementType, numElements);
StringRef name = "reduce_buffer";
auto globalOp = builder.create<LLVM::GlobalOp>(
loc, arrayType.cast<LLVM::LLVMType>(),
/*isConstant=*/false, LLVM::Linkage::Internal, name,
/*value=*/Attribute(), gpu::GPUDialect::getWorkgroupAddressSpace());
return rewriter.create<LLVM::AddressOfOp>(loc, globalOp);
}
/// Returns the index of the thread within the block.
Value getLinearThreadIndex(Location loc,
ConversionPatternRewriter &rewriter) const {
Value dimX = rewriter.create<NVVM::BlockDimXOp>(loc, int32Type);
Value dimY = rewriter.create<NVVM::BlockDimYOp>(loc, int32Type);
Value idX = rewriter.create<NVVM::ThreadIdXOp>(loc, int32Type);
Value idY = rewriter.create<NVVM::ThreadIdYOp>(loc, int32Type);
Value idZ = rewriter.create<NVVM::ThreadIdZOp>(loc, int32Type);
Value tmp1 = rewriter.create<LLVM::MulOp>(loc, int32Type, idZ, dimY);
Value tmp2 = rewriter.create<LLVM::AddOp>(loc, int32Type, tmp1, idY);
Value tmp3 = rewriter.create<LLVM::MulOp>(loc, int32Type, tmp2, dimX);
return rewriter.create<LLVM::AddOp>(loc, int32Type, tmp3, idX);
}
/// Returns the number of threads in the block.
Value getBlockSize(Location loc, ConversionPatternRewriter &rewriter) const {
Value dimX = rewriter.create<NVVM::BlockDimXOp>(loc, int32Type);
Value dimY = rewriter.create<NVVM::BlockDimYOp>(loc, int32Type);
Value dimZ = rewriter.create<NVVM::BlockDimZOp>(loc, int32Type);
Value dimXY = rewriter.create<LLVM::MulOp>(loc, int32Type, dimX, dimY);
return rewriter.create<LLVM::MulOp>(loc, int32Type, dimXY, dimZ);
}
/// Returns the number of warps in the block.
Value getNumWarps(Location loc, Value blockSize,
ConversionPatternRewriter &rewriter) const {
auto warpSizeMinusOne = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize - 1));
auto biasedBlockSize = rewriter.create<LLVM::AddOp>(
loc, int32Type, blockSize, warpSizeMinusOne);
return getDivideByWarpSize(biasedBlockSize, rewriter);
}
/// Returns the number of active threads in the warp, not clamped to 32.
Value getActiveWidth(Location loc, Value threadIdx, Value blockSize,
ConversionPatternRewriter &rewriter) const {
Value threadIdxMask = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(~(kWarpSize - 1)));
Value numThreadsWithSmallerWarpId =
rewriter.create<LLVM::AndOp>(loc, threadIdx, threadIdxMask);
return rewriter.create<LLVM::SubOp>(loc, blockSize,
numThreadsWithSmallerWarpId);
}
/// Returns value divided by the warp size (i.e. 32).
Value getDivideByWarpSize(Value value,
ConversionPatternRewriter &rewriter) const {
auto loc = value.getLoc();
auto warpSize = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize));
return rewriter.create<LLVM::SDivOp>(loc, int32Type, value, warpSize);
}
LLVM::LLVMType int32Type;
static constexpr int kWarpSize = 32;
};
struct GPUShuffleOpLowering : public LLVMOpLowering {
explicit GPUShuffleOpLowering(LLVMTypeConverter &lowering_)
: LLVMOpLowering(gpu::ShuffleOp::getOperationName(),
lowering_.getDialect()->getContext(), lowering_) {}
/// Lowers a shuffle to the corresponding NVVM op.
///
/// Convert the `width` argument into an activeMask (a bitmask which specifies
/// which threads participate in the shuffle) and a maskAndClamp (specifying
/// the highest lane which participates in the shuffle).
///
/// %one = llvm.constant(1 : i32) : !llvm.i32
/// %shl = llvm.shl %one, %width : !llvm.i32
/// %active_mask = llvm.sub %shl, %one : !llvm.i32
/// %mask_and_clamp = llvm.sub %width, %one : !llvm.i32
/// %shfl = nvvm.shfl.sync.bfly %active_mask, %value, %offset,
/// %mask_and_clamp : !llvm<"{ float, i1 }">
/// %shfl_value = llvm.extractvalue %shfl[0 : index] :
/// !llvm<"{ float, i1 }">
/// %shfl_pred = llvm.extractvalue %shfl[1 : index] :
/// !llvm<"{ float, i1 }">
PatternMatchResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = op->getLoc();
gpu::ShuffleOpOperandAdaptor adaptor(operands);
auto dialect = lowering.getDialect();
auto valueTy = adaptor.value().getType().cast<LLVM::LLVMType>();
auto int32Type = LLVM::LLVMType::getInt32Ty(dialect);
auto predTy = LLVM::LLVMType::getInt1Ty(dialect);
auto resultTy = LLVM::LLVMType::getStructTy(dialect, {valueTy, predTy});
Value one = rewriter.create<LLVM::ConstantOp>(
loc, int32Type, rewriter.getI32IntegerAttr(1));
// Bit mask of active lanes: `(1 << activeWidth) - 1`.
Value activeMask = rewriter.create<LLVM::SubOp>(
loc, int32Type,
rewriter.create<LLVM::ShlOp>(loc, int32Type, one, adaptor.width()),
one);
// Clamp lane: `activeWidth - 1`
Value maskAndClamp =
rewriter.create<LLVM::SubOp>(loc, int32Type, adaptor.width(), one);
auto returnValueAndIsValidAttr = rewriter.getUnitAttr();
Value shfl = rewriter.create<NVVM::ShflBflyOp>(
loc, resultTy, activeMask, adaptor.value(), adaptor.offset(),
maskAndClamp, returnValueAndIsValidAttr);
Value shflValue = rewriter.create<LLVM::ExtractValueOp>(
loc, valueTy, shfl, rewriter.getIndexArrayAttr(0));
Value isActiveSrcLane = rewriter.create<LLVM::ExtractValueOp>(
loc, predTy, shfl, rewriter.getIndexArrayAttr(1));
rewriter.replaceOp(op, {shflValue, isActiveSrcLane});
return matchSuccess();
}
};
struct GPUFuncOpLowering : LLVMOpLowering {
explicit GPUFuncOpLowering(LLVMTypeConverter &typeConverter)
: LLVMOpLowering(gpu::GPUFuncOp::getOperationName(),
typeConverter.getDialect()->getContext(),
typeConverter) {}
PatternMatchResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
assert(operands.empty() && "func op is not expected to have operands");
auto gpuFuncOp = cast<gpu::GPUFuncOp>(op);
Location loc = gpuFuncOp.getLoc();
SmallVector<LLVM::GlobalOp, 3> workgroupBuffers;
workgroupBuffers.reserve(gpuFuncOp.getNumWorkgroupAttributions());
for (auto en : llvm::enumerate(gpuFuncOp.getWorkgroupAttributions())) {
Value attribution = en.value();
auto type = attribution.getType().dyn_cast<MemRefType>();
assert(type && type.hasStaticShape() && "unexpected type in attribution");
uint64_t numElements = type.getNumElements();
auto elementType =
lowering.convertType(type.getElementType()).cast<LLVM::LLVMType>();
auto arrayType = LLVM::LLVMType::getArrayTy(elementType, numElements);
std::string name =
llvm::formatv("__wg_{0}_{1}", gpuFuncOp.getName(), en.index());
auto globalOp = rewriter.create<LLVM::GlobalOp>(
gpuFuncOp.getLoc(), arrayType, /*isConstant=*/false,
LLVM::Linkage::Internal, name, /*value=*/Attribute(),
gpu::GPUDialect::getWorkgroupAddressSpace());
workgroupBuffers.push_back(globalOp);
}
// Rewrite the original GPU function to an LLVM function.
auto funcType = lowering.convertType(gpuFuncOp.getType())
.cast<LLVM::LLVMType>()
.getPointerElementTy();
// Remap proper input types.
TypeConverter::SignatureConversion signatureConversion(
gpuFuncOp.front().getNumArguments());
for (unsigned i = 0, e = funcType.getFunctionNumParams(); i < e; ++i)
signatureConversion.addInputs(i, funcType.getFunctionParamType(i));
// Create the new function operation. Only copy those attributes that are
// not specific to function modeling.
SmallVector<NamedAttribute, 4> attributes;
for (const auto &attr : gpuFuncOp.getAttrs()) {
if (attr.first.is(SymbolTable::getSymbolAttrName()) ||
attr.first.is(impl::getTypeAttrName()) ||
attr.first.is(gpu::GPUFuncOp::getNumWorkgroupAttributionsAttrName()))
continue;
attributes.push_back(attr);
}
auto llvmFuncOp = rewriter.create<LLVM::LLVMFuncOp>(
gpuFuncOp.getLoc(), gpuFuncOp.getName(), funcType,
LLVM::Linkage::External, attributes);
{
// Insert operations that correspond to converted workgroup and private
// memory attributions to the body of the function. This must operate on
// the original function, before the body region is inlined in the new
// function to maintain the relation between block arguments and the
// parent operation that assigns their semantics.
OpBuilder::InsertionGuard guard(rewriter);
// Rewrite workgroup memory attributions to addresses of global buffers.
rewriter.setInsertionPointToStart(&gpuFuncOp.front());
unsigned numProperArguments = gpuFuncOp.getNumArguments();
auto i32Type = LLVM::LLVMType::getInt32Ty(lowering.getDialect());
Value zero = nullptr;
if (!workgroupBuffers.empty())
zero = rewriter.create<LLVM::ConstantOp>(loc, i32Type,
rewriter.getI32IntegerAttr(0));
for (auto en : llvm::enumerate(workgroupBuffers)) {
LLVM::GlobalOp global = en.value();
Value address = rewriter.create<LLVM::AddressOfOp>(loc, global);
auto elementType = global.getType().getArrayElementType();
Value memory = rewriter.create<LLVM::GEPOp>(
loc, elementType.getPointerTo(global.addr_space().getZExtValue()),
address, ArrayRef<Value>{zero, zero});
// Build a memref descriptor pointing to the buffer to plug with the
// existing memref infrastructure. This may use more registers than
// otherwise necessary given that memref sizes are fixed, but we can try
// and canonicalize that away later.
Value attribution = gpuFuncOp.getWorkgroupAttributions()[en.index()];
auto type = attribution.getType().cast<MemRefType>();
auto descr = MemRefDescriptor::fromStaticShape(rewriter, loc, lowering,
type, memory);
signatureConversion.remapInput(numProperArguments + en.index(), descr);
}
// Rewrite private memory attributions to alloca'ed buffers.
unsigned numWorkgroupAttributions =
gpuFuncOp.getNumWorkgroupAttributions();
auto int64Ty = LLVM::LLVMType::getInt64Ty(lowering.getDialect());
for (auto en : llvm::enumerate(gpuFuncOp.getPrivateAttributions())) {
Value attribution = en.value();
auto type = attribution.getType().cast<MemRefType>();
assert(type && type.hasStaticShape() &&
"unexpected type in attribution");
// Explicitly drop memory space when lowering private memory
// attributions since NVVM models it as `alloca`s in the default
// memory space and does not support `alloca`s with addrspace(5).
auto ptrType = lowering.convertType(type.getElementType())
.cast<LLVM::LLVMType>()
.getPointerTo();
Value numElements = rewriter.create<LLVM::ConstantOp>(
gpuFuncOp.getLoc(), int64Ty,
rewriter.getI64IntegerAttr(type.getNumElements()));
Value allocated = rewriter.create<LLVM::AllocaOp>(
gpuFuncOp.getLoc(), ptrType, numElements, /*alignment=*/0);
auto descr = MemRefDescriptor::fromStaticShape(rewriter, loc, lowering,
type, allocated);
signatureConversion.remapInput(
numProperArguments + numWorkgroupAttributions + en.index(), descr);
}
}
// Move the region to the new function, update the entry block signature.
rewriter.inlineRegionBefore(gpuFuncOp.getBody(), llvmFuncOp.getBody(),
llvmFuncOp.end());
rewriter.applySignatureConversion(&llvmFuncOp.getBody(),
signatureConversion);
{
// For memref-typed arguments, insert the relevant loads in the beginning
// of the block to comply with the LLVM dialect calling convention. This
// needs to be done after signature conversion to get the right types.
OpBuilder::InsertionGuard guard(rewriter);
Block &block = llvmFuncOp.front();
rewriter.setInsertionPointToStart(&block);
for (auto en : llvm::enumerate(gpuFuncOp.getType().getInputs())) {
if (!en.value().isa<MemRefType>() &&
!en.value().isa<UnrankedMemRefType>())
continue;
BlockArgument arg = block.getArgument(en.index());
Value loaded = rewriter.create<LLVM::LoadOp>(loc, arg);
rewriter.replaceUsesOfBlockArgument(arg, loaded);
}
}
rewriter.eraseOp(gpuFuncOp);
return matchSuccess();
}
};
struct GPUReturnOpLowering : public LLVMOpLowering {
GPUReturnOpLowering(LLVMTypeConverter &typeConverter)
: LLVMOpLowering(gpu::ReturnOp::getOperationName(),
typeConverter.getDialect()->getContext(),
typeConverter) {}
PatternMatchResult
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, operands,
ArrayRef<Block *>());
return matchSuccess();
}
};
/// Import the GPU Ops to NVVM Patterns.
#include "GPUToNVVM.cpp.inc"
/// A pass that replaces all occurrences of GPU device operations with their
/// corresponding NVVM equivalent.
///
/// This pass only handles device code and is not meant to be run on GPU host
/// code.
class LowerGpuOpsToNVVMOpsPass
: public OperationPass<LowerGpuOpsToNVVMOpsPass, gpu::GPUModuleOp> {
public:
void runOnOperation() override {
gpu::GPUModuleOp m = getOperation();
OwningRewritePatternList patterns;
NVVMTypeConverter converter(m.getContext());
populateStdToLLVMConversionPatterns(converter, patterns);
populateGpuToNVVMConversionPatterns(converter, patterns);
ConversionTarget target(getContext());
target.addIllegalDialect<gpu::GPUDialect>();
target.addIllegalOp<LLVM::FAbsOp, LLVM::FCeilOp, LLVM::CosOp,
LLVM::ExpOp>();
target.addIllegalOp<FuncOp>();
target.addLegalDialect<LLVM::LLVMDialect>();
target.addLegalDialect<NVVM::NVVMDialect>();
// TODO(csigg): Remove once we support replacing non-root ops.
target.addLegalOp<gpu::YieldOp, gpu::GPUModuleOp, gpu::ModuleEndOp>();
if (failed(applyPartialConversion(m, target, patterns, &converter)))
signalPassFailure();
}
};
} // anonymous namespace
void mlir::populateGpuToNVVMConversionPatterns(
LLVMTypeConverter &converter, OwningRewritePatternList &patterns) {
populateWithGenerated(converter.getDialect()->getContext(), &patterns);
patterns
.insert<GPUIndexIntrinsicOpLowering<gpu::ThreadIdOp, NVVM::ThreadIdXOp,
NVVM::ThreadIdYOp, NVVM::ThreadIdZOp>,
GPUIndexIntrinsicOpLowering<gpu::BlockDimOp, NVVM::BlockDimXOp,
NVVM::BlockDimYOp, NVVM::BlockDimZOp>,
GPUIndexIntrinsicOpLowering<gpu::BlockIdOp, NVVM::BlockIdXOp,
NVVM::BlockIdYOp, NVVM::BlockIdZOp>,
GPUIndexIntrinsicOpLowering<gpu::GridDimOp, NVVM::GridDimXOp,
NVVM::GridDimYOp, NVVM::GridDimZOp>,
GPUAllReduceOpLowering, GPUShuffleOpLowering, GPUFuncOpLowering,
GPUReturnOpLowering>(converter);
patterns.insert<OpToFuncCallLowering<AbsFOp>>(converter, "__nv_fabsf",
"__nv_fabs");
patterns.insert<OpToFuncCallLowering<CeilFOp>>(converter, "__nv_ceilf",
"__nv_ceil");
patterns.insert<OpToFuncCallLowering<CosOp>>(converter, "__nv_cosf",
"__nv_cos");
patterns.insert<OpToFuncCallLowering<ExpOp>>(converter, "__nv_expf",
"__nv_exp");
}
std::unique_ptr<OpPassBase<gpu::GPUModuleOp>>
mlir::createLowerGpuOpsToNVVMOpsPass() {
return std::make_unique<LowerGpuOpsToNVVMOpsPass>();
}
static PassRegistration<LowerGpuOpsToNVVMOpsPass>
pass("convert-gpu-to-nvvm", "Generate NVVM operations for gpu operations");