blob: ae4090d00bd260b71058257be566fbd8e7664576 [file] [edit]
//===- ACCExecutableDirectivePatterns.cpp - ACC exec patterns ---*- C++ -*-===//
//
// Part of the LLVM 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
//
//===----------------------------------------------------------------------===//
//
// Lowers OpenACC executable directives (init, shutdown, wait, set) to calls to
// an OpenACC offloading runtime compiler interface.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h"
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h"
#include "mlir/Conversion/LLVMCommon/Pattern.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include <cstdint>
#include <iterator>
using namespace mlir;
using namespace mlir::acc;
namespace {
static Value castToI64(Location loc, Value value,
ConversionPatternRewriter &rewriter) {
Type i64Ty = IntegerType::get(rewriter.getContext(), 64);
unsigned bitwidth = value.getType().getIntOrFloatBitWidth();
if (bitwidth > 64)
return arith::TruncIOp::create(rewriter, loc, i64Ty, value);
if (bitwidth < 64)
return arith::ExtSIOp::create(rewriter, loc, i64Ty, value);
return value;
}
static Value getAsyncQueue(WaitOp op, ConversionPatternRewriter &rewriter,
const ACCRuntimeCallConfig &config) {
Location loc = op->getLoc();
Type i64Ty = IntegerType::get(rewriter.getContext(), 64);
if (op.getAsync())
return LLVM::ConstantOp::create(rewriter, loc, i64Ty,
config.getAsyncNoValueRuntimeValue());
if (Value asyncValue = op.getAsyncOperand()) {
asyncValue = rewriter.getRemappedValue(asyncValue);
return castToI64(loc, asyncValue, rewriter);
}
return LLVM::ConstantOp::create(rewriter, loc, i64Ty,
config.getAsyncSyncRuntimeValue());
}
static LogicalResult createIfThen(Location loc, Value ifCond,
ConversionPatternRewriter &rewriter,
function_ref<LogicalResult()> thenFn) {
Block *parentBlock = rewriter.getInsertionBlock();
Block *continueBlock =
rewriter.splitBlock(parentBlock, rewriter.getInsertionPoint());
Block *thenBlock = rewriter.createBlock(
parentBlock->getParent(), std::next(Region::iterator(parentBlock)));
rewriter.setInsertionPointToEnd(parentBlock);
LLVM::CondBrOp::create(rewriter, loc, ifCond, thenBlock, ValueRange{},
continueBlock, ValueRange{});
rewriter.setInsertionPointToStart(thenBlock);
LogicalResult result = thenFn();
rewriter.setInsertionPointToEnd(thenBlock);
LLVM::BrOp::create(rewriter, loc, ValueRange{}, continueBlock);
rewriter.setInsertionPointToStart(continueBlock);
return result;
}
/// Run \p emitFn, guarded by a branch on \p ifCond when it is present.
static LogicalResult emitGuardedByIfCond(Location loc, Value ifCond,
ConversionPatternRewriter &rewriter,
function_ref<LogicalResult()> emitFn) {
if (ifCond)
return createIfThen(loc, ifCond, rewriter, emitFn);
return emitFn();
}
template <typename OpTy>
struct ACCExecutableDirectivePattern : public ConvertOpToLLVMPattern<OpTy> {
ACCExecutableDirectivePattern(const LLVMTypeConverter &converter,
const ACCRuntimeCallConfig &config,
PatternBenefit benefit = 1)
: ConvertOpToLLVMPattern<OpTy>(converter, benefit), config(config) {}
ACCRuntimeCallConfig config;
};
struct WaitOpLowering : public ACCExecutableDirectivePattern<WaitOp> {
using ACCExecutableDirectivePattern<WaitOp>::ACCExecutableDirectivePattern;
LogicalResult
matchAndRewrite(WaitOp op, WaitOp::Adaptor,
ConversionPatternRewriter &rewriter) const override {
Location loc = op->getLoc();
ModuleOp module = op->getParentOfType<ModuleOp>();
Type i32Ty = rewriter.getI32Type();
Type i64Ty = rewriter.getI64Type();
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
auto emitWait = [&]() -> LogicalResult {
Value asyncQueue = getAsyncQueue(op, rewriter, config);
SmallVector<Value> waitValues;
for (Value operand : op.getWaitOperands())
waitValues.push_back(
castToI64(loc, rewriter.getRemappedValue(operand), rewriter));
unsigned size = waitValues.size();
Value waitNum = LLVM::ConstantOp::create(rewriter, loc, i32Ty, size);
Value waitList;
if (size == 0) {
waitList = LLVM::ZeroOp::create(rewriter, loc, ptrTy);
} else {
waitList = LLVM::AllocaOp::create(rewriter, loc, ptrTy, i64Ty, waitNum);
for (auto [index, waitValue] : llvm::enumerate(waitValues)) {
Value idx = LLVM::ConstantOp::create(rewriter, loc, i32Ty,
static_cast<int64_t>(index));
Value elementPtr = LLVM::GEPOp::create(
rewriter, loc, ptrTy, i64Ty, waitList, ArrayRef<Value>{idx});
LLVM::StoreOp::create(rewriter, loc, waitValue, elementPtr);
}
}
StringRef functionName = getParentFunctionName(waitValues);
if (functionName.empty())
functionName = getParentFunctionName(op);
Value ident = createIdent(loc, functionName, rewriter, module, config);
Value flags = LLVM::ConstantOp::create(rewriter, loc, i64Ty, 0);
Value deviceType = LLVM::ConstantOp::create(
rewriter, loc, i64Ty,
config.getDeviceTypeRuntimeValue(DeviceType::None));
Value deviceNum = LLVM::ConstantOp::create(rewriter, loc, i32Ty, 0);
return createRuntimeCall(
loc, rewriter, module, RuntimeFunction::ACCRTL_tgt_acc_wait, config,
{ident, flags, deviceType, deviceNum, waitNum, waitList, asyncQueue});
};
if (failed(emitGuardedByIfCond(loc, op.getIfCond(), rewriter, emitWait)))
return failure();
rewriter.eraseOp(op);
return success();
}
};
/// Emit a call to a runtime entry point taking
/// `(ident, flags, deviceType, deviceNum)`. A null `deviceNum` selects the
/// current device.
static LogicalResult
emitDeviceOperationCall(Location loc, RuntimeFunction fn, DeviceType deviceType,
Value deviceNum, StringRef functionName,
ModuleOp module, ConversionPatternRewriter &rewriter,
const ACCRuntimeCallConfig &config) {
Type i64Ty = rewriter.getI64Type();
Value deviceTypeValue = LLVM::ConstantOp::create(
rewriter, loc, i64Ty, config.getDeviceTypeRuntimeValue(deviceType));
Value ident = createIdent(loc, functionName, rewriter, module, config);
Value flags = LLVM::ConstantOp::create(rewriter, loc, i64Ty, 0);
Value deviceNumValue =
deviceNum ? castToI64(loc, deviceNum, rewriter)
: LLVM::ConstantOp::create(rewriter, loc, i64Ty, -1);
return createRuntimeCall(loc, rewriter, module, fn, config,
{ident, flags, deviceTypeValue, deviceNumValue});
}
static LogicalResult rewriteInitOrShutdown(Operation *op, Value deviceNum,
ArrayAttr deviceTypesAttr,
Value ifCond, bool isInit,
ConversionPatternRewriter &rewriter,
const ACCRuntimeCallConfig &config) {
ModuleOp module = op->getParentOfType<ModuleOp>();
Location loc = op->getLoc();
auto emitCalls = [&]() -> LogicalResult {
StringRef functionName = deviceNum ? getParentFunctionName(deviceNum)
: getParentFunctionName(op);
RuntimeFunction fn = isInit ? RuntimeFunction::ACCRTL_tgt_acc_init
: RuntimeFunction::ACCRTL_tgt_acc_shutdown;
auto emitOne = [&](DeviceType deviceType) {
return emitDeviceOperationCall(loc, fn, deviceType, deviceNum,
functionName, module, rewriter, config);
};
if (!deviceTypesAttr)
return emitOne(DeviceType::None);
for (Attribute attr : deviceTypesAttr) {
if (auto typeAttr = dyn_cast<DeviceTypeAttr>(attr))
if (failed(emitOne(typeAttr.getValue())))
return failure();
}
return success();
};
if (failed(emitGuardedByIfCond(loc, ifCond, rewriter, emitCalls)))
return failure();
rewriter.eraseOp(op);
return success();
}
struct InitOpLowering : public ACCExecutableDirectivePattern<InitOp> {
using ACCExecutableDirectivePattern<InitOp>::ACCExecutableDirectivePattern;
LogicalResult
matchAndRewrite(InitOp op, InitOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
return rewriteInitOrShutdown(op, adaptor.getDeviceNum(),
op.getDeviceTypesAttr(), op.getIfCond(),
/*isInit=*/true, rewriter, config);
}
};
struct ShutdownOpLowering : public ACCExecutableDirectivePattern<ShutdownOp> {
using ACCExecutableDirectivePattern<
ShutdownOp>::ACCExecutableDirectivePattern;
LogicalResult
matchAndRewrite(ShutdownOp op, ShutdownOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
return rewriteInitOrShutdown(op, adaptor.getDeviceNum(),
op.getDeviceTypesAttr(), op.getIfCond(),
/*isInit=*/false, rewriter, config);
}
};
struct SetOpLowering : public ACCExecutableDirectivePattern<SetOp> {
using ACCExecutableDirectivePattern<SetOp>::ACCExecutableDirectivePattern;
LogicalResult
matchAndRewrite(SetOp op, SetOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
ModuleOp module = op->getParentOfType<ModuleOp>();
Location loc = op.getLoc();
Type i64Ty = rewriter.getI64Type();
auto emitSet = [&]() -> LogicalResult {
if (Value asyncValue = adaptor.getDefaultAsync()) {
asyncValue = castToI64(loc, asyncValue, rewriter);
Value ident = createIdent(loc, getParentFunctionName(asyncValue),
rewriter, module, config);
if (failed(createRuntimeCall(
loc, rewriter, module,
RuntimeFunction::ACCRTL_tgt_acc_set_default_async, config,
{ident, asyncValue})))
return failure();
}
if (op.getDeviceNum()) {
Value deviceNum = adaptor.getDeviceNum();
DeviceType deviceType = DeviceType::None;
if (auto deviceTypeAttr = op.getDeviceTypeAttr())
deviceType = deviceTypeAttr.getValue();
return emitDeviceOperationCall(
loc, RuntimeFunction::ACCRTL_tgt_acc_set_device_num, deviceType,
deviceNum, getParentFunctionName(deviceNum), module, rewriter,
config);
}
if (auto deviceTypeAttr = op.getDeviceTypeAttr()) {
Value deviceTypeValue = LLVM::ConstantOp::create(
rewriter, loc, i64Ty,
config.getDeviceTypeRuntimeValue(deviceTypeAttr.getValue()));
Value ident = createIdent(loc, StringRef(), rewriter, module, config);
Value flags = LLVM::ConstantOp::create(rewriter, loc, i64Ty, 0);
return createRuntimeCall(
loc, rewriter, module,
RuntimeFunction::ACCRTL_tgt_acc_set_device_type, config,
{ident, flags, deviceTypeValue});
}
return success();
};
if (failed(emitGuardedByIfCond(loc, op.getIfCond(), rewriter, emitSet)))
return failure();
rewriter.eraseOp(op);
return success();
}
};
} // namespace
void mlir::configureACCExecutableDirectiveConversionLegality(
ConversionTarget &target) {
target.addIllegalOp<acc::InitOp, acc::ShutdownOp, acc::WaitOp, acc::SetOp>();
}
void mlir::populateACCExecutableDirectivePatterns(
LLVMTypeConverter &converter, RewritePatternSet &patterns,
const acc::ACCRuntimeCallConfig &config) {
patterns
.add<WaitOpLowering, InitOpLowering, ShutdownOpLowering, SetOpLowering>(
converter, config);
}