[mlir][acc] Introduce ACCToLLVM and executable directive codegen (#213165)

Adds initial infrastructure for converting the acc dialect to LLVM,
specifically around generating libacctarget runtime calls. The current
libacctarget APIs are not yet finalized, but the draft proposal can be
found at https://github.com/llvm/llvm-project/pull/197894. This PR adds
codegen for acc init, shutdown, set, and wait.
This commit is contained in:
Razvan Lupusoru 2026-07-31 11:04:46 -07:00 committed by GitHub
parent 7afc89970f
commit 71e5cb7526
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1266 additions and 0 deletions

View file

@ -0,0 +1,38 @@
//===- ACCToLLVM.h - Convert OpenACC to LLVM dialect ------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVM_H
#define MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVM_H
#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
#include <memory>
namespace mlir {
class ConversionTarget;
class LLVMTypeConverter;
class Pass;
class RewritePatternSet;
#define GEN_PASS_DECL_CONVERTACCTOLLVMPASS
#include "mlir/Conversion/Passes.h.inc"
/// Configure conversion legality for OpenACC executable directives lowered to
/// runtime calls.
void configureACCExecutableDirectiveConversionLegality(
ConversionTarget &target);
/// Populate patterns that lower OpenACC executable directives (init, shutdown,
/// wait, set) to LLVM runtime calls.
void populateACCExecutableDirectivePatterns(
LLVMTypeConverter &converter, RewritePatternSet &patterns,
const acc::ACCRuntimeCallConfig &config = {});
} // namespace mlir
#endif // MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVM_H

View file

@ -0,0 +1,52 @@
//===- ACCToLLVMUtils.h - OpenACC to LLVM helpers ---------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVMUTILS_H
#define MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVMUTILS_H
#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Location.h"
#include "llvm/ADT/StringRef.h"
#include <optional>
#include <string>
namespace mlir {
namespace acc {
/// Unfuses fused locations, returning the last sub-location.
Location unfuseLoc(Location loc);
/// Returns file:line:column location information when available.
std::optional<FileLineColLoc> getFileLineColLoc(Location loc,
bool errorOnInvalidLocation);
/// Returns the enclosing function symbol name for \p op.
StringRef getParentFunctionName(Operation *op);
/// Returns the enclosing function symbol name for \p value's defining op.
StringRef getParentFunctionName(Value value);
/// Returns the first non-empty enclosing function name from \p values.
StringRef getParentFunctionName(ValueRange values);
/// Creates or reuses a module-internal null-terminated string global.
Value getOrCreateGlobalString(Location loc, OpBuilder &builder, StringRef name,
StringRef value, ModuleOp module);
/// Returns a pointer to a constant global holding an ident_t for OpenACC
/// runtime calls.
Value createIdent(Location loc, StringRef functionName, OpBuilder &builder,
ModuleOp module, const ACCRuntimeCallConfig &config);
} // namespace acc
} // namespace mlir
#endif // MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVMUTILS_H

View file

@ -58,6 +58,7 @@
#include "mlir/Conversion/MemRefToSPIRV/MemRefToSPIRVPass.h"
#include "mlir/Conversion/NVGPUToNVVM/NVGPUToNVVM.h"
#include "mlir/Conversion/NVVMToLLVM/NVVMToLLVM.h"
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h"
#include "mlir/Conversion/OpenACCToSCF/ConvertOpenACCToSCF.h"
#include "mlir/Conversion/OpenMPToLLVM/ConvertOpenMPToLLVM.h"
#include "mlir/Conversion/PDLToPDLInterp/PDLToPDLInterp.h"

View file

@ -1122,6 +1122,15 @@ def ConvertOpenACCToSCFPass : Pass<"convert-openacc-to-scf", "ModuleOp"> {
let dependentDialects = ["scf::SCFDialect", "acc::OpenACCDialect"];
}
//===----------------------------------------------------------------------===//
// OpenACCToLLVM
//===----------------------------------------------------------------------===//
def ConvertACCToLLVMPass : Pass<"acc-to-llvm", "ModuleOp"> {
let summary = "Lower OpenACC operations to LLVM dialect.";
let dependentDialects = ["LLVM::LLVMDialect", "acc::OpenACCDialect"];
}
//===----------------------------------------------------------------------===//
// OpenMPToLLVM
//===----------------------------------------------------------------------===//

View file

@ -0,0 +1,57 @@
//===- OpenACCRuntimeFunctions.def - ACC runtime catalog --------*- 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
//
//===----------------------------------------------------------------------===//
/// \file
///
/// X-macro catalog of OpenACC compiler-to-runtime entry points
/// (`__tgt_acc_*`). Include this file after defining ACC_RTL.
///
/// ACC_RTL(Enum, NameStr, IsVarArg, ReturnType, ...)
/// Enum - enumerator used as RuntimeFunction::Enum
/// NameStr - default runtime symbol name
/// IsVarArg - whether the function is variadic
/// ReturnType - MLIR LLVM return type token (e.g. Void, Int32, Ptr)
/// ... - MLIR LLVM parameter type tokens
///
/// Use __ACC_RTL(Name, ...), which expands to
/// ACC_RTL(ACCRTL_##Name, "__" #Name, ...).
///
//===----------------------------------------------------------------------===//
#ifndef ACC_RTL
#error "define ACC_RTL before including OpenACCRuntimeFunctions.def"
#endif
#define __ACC_RTL(Name, IsVarArg, ReturnType, ...) \
ACC_RTL(ACCRTL_##Name, "__" #Name, IsVarArg, ReturnType, __VA_ARGS__)
// void __tgt_acc_init(ident_t *, int64_t flags, int64_t device_type,
// int64_t device_num);
__ACC_RTL(tgt_acc_init, false, Void, Ptr, Int64, Int64, Int64)
// void __tgt_acc_shutdown(ident_t *, int64_t flags, int64_t device_type,
// int64_t device_num);
__ACC_RTL(tgt_acc_shutdown, false, Void, Ptr, Int64, Int64, Int64)
// int32_t __tgt_acc_wait(ident_t *, int64_t flags, int64_t device_type,
// int32_t device_num, int32_t wait_num, int64_t *waits,
// int64_t async_queue);
__ACC_RTL(tgt_acc_wait, false, Int32, Ptr, Int64, Int64, Int32, Int32, Ptr,
Int64)
// void __tgt_acc_set_default_async(ident_t *, int64_t async_queue);
__ACC_RTL(tgt_acc_set_default_async, false, Void, Ptr, Int64)
// void __tgt_acc_set_device_num(ident_t *, int64_t flags, int64_t device_type,
// int64_t device_num);
__ACC_RTL(tgt_acc_set_device_num, false, Void, Ptr, Int64, Int64, Int64)
// void __tgt_acc_set_device_type(ident_t *, int64_t flags, int64_t device_type);
__ACC_RTL(tgt_acc_set_device_type, false, Void, Ptr, Int64, Int64)
#undef __ACC_RTL
#undef ACC_RTL

View file

@ -0,0 +1,102 @@
//===- OpenACCRuntimeUtils.h - OpenACC runtime call utilities ---*- 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
//
//===----------------------------------------------------------------------===//
//
// Utilities for resolving OpenACC compiler-to-runtime entry points declared in
// OpenACCRuntimeFunctions.def.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_OPENACC_OPENACCRUNTIMEUTILS_H
#define MLIR_DIALECT_OPENACC_OPENACCRUNTIMEUTILS_H
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringRef.h"
#include <cstdint>
#include <functional>
#include <string>
namespace mlir {
namespace acc {
/// IDs for OpenACC compiler-to-runtime entry points (`__tgt_acc_*`).
enum class RuntimeFunction {
#define ACC_RTL(Enum, ...) Enum,
#include "mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def"
};
/// Returns the default runtime symbol name for \p fn.
StringRef getRuntimeFunctionName(RuntimeFunction fn);
/// Builds the LLVM function type for \p fn in \p ctx.
LLVM::LLVMFunctionType getRuntimeFunctionType(MLIRContext *ctx,
RuntimeFunction fn);
/// Optional overrides for OpenACC to LLVM runtime lowering.
class ACCRuntimeCallConfig {
public:
using FunctionDisplayNameFn = std::function<std::string(StringRef)>;
void setName(RuntimeFunction fn, StringRef name);
StringRef getName(RuntimeFunction fn) const;
void setFunctionDisplayNameFn(FunctionDisplayNameFn fn);
std::string getFunctionDisplayName(StringRef mangledOrSymbol) const;
/// Map an OpenACC dialect \p DeviceType to the integer encoding expected by
/// the target runtime. Dialect ordinals and runtime ABI values are not
/// required to match; callers must install a mapping that matches their
/// runtime. Querying an unmapped type is an error.
void setDeviceTypeRuntimeValue(DeviceType type, int64_t runtimeValue);
int64_t getDeviceTypeRuntimeValue(DeviceType type) const;
/// Runtime encoding of `acc_async_sync`, used when an operation carries no
/// `async` clause. OpenACC defines the name of this queue but leaves its
/// value to the implementation, so it is part of the runtime ABI.
void setAsyncSyncRuntimeValue(int64_t runtimeValue);
int64_t getAsyncSyncRuntimeValue() const;
/// Runtime encoding of `acc_async_noval`, used for an `async` clause without
/// an argument. As with `acc_async_sync`, the value is implementation-defined
void setAsyncNoValueRuntimeValue(int64_t runtimeValue);
int64_t getAsyncNoValueRuntimeValue() const;
private:
DenseMap<RuntimeFunction, std::string> overrides;
DenseMap<DeviceType, int64_t> deviceTypeRuntimeValues;
FunctionDisplayNameFn functionDisplayNameFn;
// Default to the encodings used by openacc.h (`acc_async_sync` /
// `acc_async_noval`).
int64_t asyncSyncRuntimeValue = -1;
int64_t asyncNoValueRuntimeValue = -4;
};
/// Install a device-type mapping that uses OpenACC dialect enum ordinals as the
/// runtime encoding. This is only correct when the target runtime happens to
/// use the same numbering; runtimes with a different ABI must install their
/// own mapping via \c setDeviceTypeRuntimeValue.
void populateDialectIdentityDeviceTypeMapping(ACCRuntimeCallConfig &config);
/// Declares (if needed) and returns a call to the runtime function identified
/// by \p fn using the name from \p config. Fails and emits a diagnostic if the
/// symbol is already declared with a signature the runtime cannot be called
/// through.
FailureOr<LLVM::CallOp> createRuntimeCall(Location loc, OpBuilder &builder,
ModuleOp module, RuntimeFunction fn,
const ACCRuntimeCallConfig &config,
ArrayRef<Value> arguments);
} // namespace acc
} // namespace mlir
#endif // MLIR_DIALECT_OPENACC_OPENACCRUNTIMEUTILS_H

View file

@ -51,6 +51,7 @@ add_subdirectory(ShardToMPI)
add_subdirectory(MPIToLLVM)
add_subdirectory(NVGPUToNVVM)
add_subdirectory(NVVMToLLVM)
add_subdirectory(OpenACCToLLVM)
add_subdirectory(OpenACCToSCF)
add_subdirectory(OpenMPToLLVM)
add_subdirectory(PDLToPDLInterp)

View file

@ -0,0 +1,305 @@
//===- 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);
}

View file

@ -0,0 +1,53 @@
//===- ACCToLLVM.cpp - Convert OpenACC to LLVM dialect ----------*- 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
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h"
#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
#include "mlir/Pass/Pass.h"
namespace mlir {
#define GEN_PASS_DEF_CONVERTACCTOLLVMPASS
#include "mlir/Conversion/Passes.h.inc"
} // namespace mlir
using namespace mlir;
namespace {
struct ConvertACCToLLVMPass
: public impl::ConvertACCToLLVMPassBase<ConvertACCToLLVMPass> {
using Base::Base;
void runOnOperation() override;
};
} // namespace
void ConvertACCToLLVMPass::runOnOperation() {
ModuleOp module = getOperation();
LLVMTypeConverter converter(&getContext());
RewritePatternSet patterns(&getContext());
arith::populateArithToLLVMConversionPatterns(converter, patterns);
cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
populateFuncToLLVMConversionPatterns(converter, patterns);
// The device_type numbering is implementation-defined by the target
// runtime. For now assume the same numbering as the OpenACC dialect.
acc::ACCRuntimeCallConfig runtimeConfig;
acc::populateDialectIdentityDeviceTypeMapping(runtimeConfig);
populateACCExecutableDirectivePatterns(converter, patterns, runtimeConfig);
LLVMConversionTarget target(getContext());
configureACCExecutableDirectiveConversionLegality(target);
if (failed(applyPartialConversion(module, target, std::move(patterns))))
signalPassFailure();
}

View file

@ -0,0 +1,171 @@
//===- ACCToLLVMUtils.cpp - OpenACC to LLVM helpers -------------*- 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
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallString.h"
using namespace mlir;
using namespace mlir::acc;
Location acc::unfuseLoc(Location loc) {
while (auto fusedLoc = dyn_cast<FusedLoc>(loc))
loc = fusedLoc.getLocations().back();
return loc;
}
std::optional<FileLineColLoc>
acc::getFileLineColLoc(Location loc, bool errorOnInvalidLocation) {
Location unfusedLoc = unfuseLoc(loc);
if (auto fileLoc = dyn_cast<FileLineColLoc>(unfusedLoc))
return fileLoc;
if (auto callSiteLoc = dyn_cast<CallSiteLoc>(unfusedLoc)) {
if (auto calleeFileLoc = getFileLineColLoc(callSiteLoc.getCallee(), false))
return calleeFileLoc;
if (auto callerFileLoc =
getFileLineColLoc(callSiteLoc.getCaller(), errorOnInvalidLocation))
return callerFileLoc;
}
if (errorOnInvalidLocation)
llvm_unreachable(
"cannot get file:line information: invalid Location information");
return std::nullopt;
}
StringRef acc::getParentFunctionName(Operation *op) {
if (!op)
return "";
if (auto parentFuncOp = op->getParentOfType<func::FuncOp>())
return parentFuncOp.getName();
if (auto parentFuncOp = op->getParentOfType<LLVM::LLVMFuncOp>())
return parentFuncOp.getSymName();
return "";
}
StringRef acc::getParentFunctionName(Value value) {
if (auto *op = value.getDefiningOp())
return getParentFunctionName(op);
return "";
}
StringRef acc::getParentFunctionName(ValueRange values) {
for (Value value : values) {
if (StringRef name = getParentFunctionName(value); !name.empty())
return name;
}
return "";
}
/// Creates or reuses a module-internal null-terminated string global and
/// returns the GlobalOp.
static LLVM::GlobalOp getOrCreateGlobalStringOp(Location loc,
OpBuilder &builder,
StringRef name, StringRef value,
ModuleOp module) {
if (auto global = module.lookupSymbol<LLVM::GlobalOp>(name))
return global;
// Materialize the global through the incoming builder so that it stays
// tracked when the caller is a dialect conversion rewriter.
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(module.getBody());
SmallString<32> nullTermStr(value);
nullTermStr.push_back('\0');
auto arrayTy = LLVM::LLVMArrayType::get(builder.getI8Type(),
nullTermStr.size_in_bytes());
return LLVM::GlobalOp::create(builder, loc, arrayTy, /*isConstant=*/true,
LLVM::Linkage::Internal, name,
builder.getStringAttr(nullTermStr),
/*alignment=*/0);
}
Value acc::getOrCreateGlobalString(Location loc, OpBuilder &builder,
StringRef name, StringRef value,
ModuleOp module) {
Type i64Ty = builder.getI64Type();
Type ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
LLVM::GlobalOp global =
getOrCreateGlobalStringOp(loc, builder, name, value, module);
Value globalPtr = LLVM::AddressOfOp::create(builder, loc, global);
Value cst0 =
LLVM::ConstantOp::create(builder, loc, i64Ty, builder.getIndexAttr(0));
return LLVM::GEPOp::create(builder, loc, ptrTy, global.getType(), globalPtr,
ArrayRef<Value>({cst0, cst0}));
}
Value acc::createIdent(Location loc, StringRef functionName, OpBuilder &builder,
ModuleOp module, const ACCRuntimeCallConfig &config) {
MLIRContext *ctx = builder.getContext();
Type i32Ty = builder.getI32Type();
Type i64Ty = builder.getI64Type();
Type ptrTy = LLVM::LLVMPointerType::get(ctx);
Type structTy = LLVM::LLVMStructType::getLiteral(
ctx, {i32Ty, i32Ty, i32Ty, i32Ty, ptrTy});
std::string source;
std::string sourceGlobalName;
if (auto fileLineColLoc =
getFileLineColLoc(loc, /*errorOnInvalidLocation=*/false)) {
std::string filename = fileLineColLoc->getFilename().str();
std::string line = std::to_string(fileLineColLoc->getLine());
std::string column = std::to_string(fileLineColLoc->getColumn());
std::string functionDisplayName =
functionName.empty() ? std::string()
: config.getFunctionDisplayName(functionName);
source = ";";
source += filename + ";";
source += functionDisplayName + ";";
source += line + ";";
source += column + ";";
source += ";";
sourceGlobalName = "loc_";
sourceGlobalName += line + "_";
sourceGlobalName += column + "_";
sourceGlobalName +=
std::to_string(static_cast<uint64_t>(llvm::hash_value(source)));
} else {
source = ";unknown;unknown;0;0;;";
sourceGlobalName = "loc__";
}
std::string identGlobalName = "ident_";
identGlobalName += sourceGlobalName;
auto identGlobal = module.lookupSymbol<LLVM::GlobalOp>(identGlobalName);
if (!identGlobal) {
LLVM::GlobalOp sourceGlobal = getOrCreateGlobalStringOp(
loc, builder, sourceGlobalName, source, module);
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointAfter(sourceGlobal);
identGlobal = LLVM::GlobalOp::create(
builder, loc, structTy, /*isConstant=*/true, LLVM::Linkage::Internal,
identGlobalName, /*value=*/Attribute(), /*alignment=*/0);
Block *block = builder.createBlock(&identGlobal.getInitializerRegion());
builder.setInsertionPointToStart(block);
Value ident = LLVM::ZeroOp::create(builder, loc, structTy);
Value sourceBase = LLVM::AddressOfOp::create(builder, loc, sourceGlobal);
Value cst0 =
LLVM::ConstantOp::create(builder, loc, i64Ty, builder.getIndexAttr(0));
Value sourcePtr =
LLVM::GEPOp::create(builder, loc, ptrTy, sourceGlobal.getType(),
sourceBase, ArrayRef<Value>({cst0, cst0}));
ident = LLVM::InsertValueOp::create(builder, loc, structTy, ident,
sourcePtr, ArrayRef<int64_t>{4});
LLVM::ReturnOp::create(builder, loc, ident);
}
return LLVM::AddressOfOp::create(builder, loc, identGlobal);
}

View file

@ -0,0 +1,22 @@
add_mlir_conversion_library(MLIROpenACCToLLVM
ACCExecutableDirectivePatterns.cpp
ACCToLLVMUtils.cpp
ACCToLLVM.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/OpenACCToLLVM
DEPENDS
MLIRConversionPassIncGen
LINK_LIBS PUBLIC
MLIRArithToLLVM
MLIRControlFlowToLLVM
MLIRFuncToLLVM
MLIRIR
MLIRLLVMCommonConversion
MLIRLLVMDialect
MLIROpenACCDialect
MLIROpenACCUtils
MLIRTransforms
)

View file

@ -6,6 +6,7 @@ add_mlir_dialect_library(MLIROpenACCUtils
OpenACCUtilsReduction.cpp
OpenACCUtilsTiling.cpp
OpenACCUtilsType.cpp
OpenACCRuntimeUtils.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACC
@ -25,6 +26,7 @@ add_mlir_dialect_library(MLIROpenACCUtils
MLIRComplexDialect
MLIRDataLayoutInterfaces
MLIRGPUDialect
MLIRLLVMDialect
MLIRMemRefDialect
MLIROpenACCDialect
MLIRIR

View file

@ -0,0 +1,127 @@
//===- OpenACCRuntimeUtils.cpp - OpenACC runtime call utilities -*- 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
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
#include "mlir/IR/SymbolTable.h"
#include "llvm/Support/ErrorHandling.h"
#include <optional>
using namespace mlir;
using namespace mlir::acc;
StringRef acc::getRuntimeFunctionName(RuntimeFunction fn) {
switch (fn) {
#define ACC_RTL(Enum, Str, ...) \
case RuntimeFunction::Enum: \
return Str;
#include "mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def"
}
llvm_unreachable("unknown ACC runtime function");
}
LLVM::LLVMFunctionType acc::getRuntimeFunctionType(MLIRContext *ctx,
RuntimeFunction fn) {
Type Void = LLVM::LLVMVoidType::get(ctx);
Type Ptr = LLVM::LLVMPointerType::get(ctx);
Type Int32 = IntegerType::get(ctx, 32);
Type Int64 = IntegerType::get(ctx, 64);
switch (fn) {
#define ACC_RTL(Enum, Str, IsVarArg, ReturnType, ...) \
case RuntimeFunction::Enum: \
return LLVM::LLVMFunctionType::get(ReturnType, \
ArrayRef<Type>{__VA_ARGS__}, IsVarArg);
#include "mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def"
}
llvm_unreachable("unknown ACC runtime function");
}
void ACCRuntimeCallConfig::setName(RuntimeFunction fn, StringRef name) {
overrides[fn] = name.str();
}
StringRef ACCRuntimeCallConfig::getName(RuntimeFunction fn) const {
if (auto it = overrides.find(fn); it != overrides.end())
return it->second;
return getRuntimeFunctionName(fn);
}
void ACCRuntimeCallConfig::setFunctionDisplayNameFn(FunctionDisplayNameFn fn) {
functionDisplayNameFn = std::move(fn);
}
std::string
ACCRuntimeCallConfig::getFunctionDisplayName(StringRef mangledOrSymbol) const {
if (functionDisplayNameFn)
return functionDisplayNameFn(mangledOrSymbol);
return mangledOrSymbol.str();
}
void ACCRuntimeCallConfig::setDeviceTypeRuntimeValue(DeviceType type,
int64_t runtimeValue) {
deviceTypeRuntimeValues[type] = runtimeValue;
}
int64_t ACCRuntimeCallConfig::getDeviceTypeRuntimeValue(DeviceType type) const {
if (auto it = deviceTypeRuntimeValues.find(type);
it != deviceTypeRuntimeValues.end())
return it->second;
llvm::report_fatal_error(
llvm::Twine("missing OpenACC runtime device-type mapping for ") +
stringifyDeviceType(type));
}
void ACCRuntimeCallConfig::setAsyncSyncRuntimeValue(int64_t runtimeValue) {
asyncSyncRuntimeValue = runtimeValue;
}
int64_t ACCRuntimeCallConfig::getAsyncSyncRuntimeValue() const {
return asyncSyncRuntimeValue;
}
void ACCRuntimeCallConfig::setAsyncNoValueRuntimeValue(int64_t runtimeValue) {
asyncNoValueRuntimeValue = runtimeValue;
}
int64_t ACCRuntimeCallConfig::getAsyncNoValueRuntimeValue() const {
return asyncNoValueRuntimeValue;
}
void acc::populateDialectIdentityDeviceTypeMapping(
ACCRuntimeCallConfig &config) {
for (uint32_t value = 0; value <= getMaxEnumValForDeviceType(); ++value)
if (std::optional<DeviceType> type = symbolizeDeviceType(value))
config.setDeviceTypeRuntimeValue(*type, value);
}
FailureOr<LLVM::CallOp>
acc::createRuntimeCall(Location loc, OpBuilder &builder, ModuleOp module,
RuntimeFunction fn, const ACCRuntimeCallConfig &config,
ArrayRef<Value> arguments) {
MLIRContext *ctx = builder.getContext();
LLVM::LLVMFunctionType fnTy = getRuntimeFunctionType(ctx, fn);
StringRef symbolName = config.getName(fn);
SymbolTable symbolTable(module);
auto func = symbolTable.lookup<LLVM::LLVMFuncOp>(symbolName);
if (func) {
// An existing declaration with a different signature cannot be called with
// the arguments expected by the runtime entry point.
if (func.getFunctionType() != fnTy)
return emitError(loc) << "OpenACC runtime function '" << symbolName
<< "' is already declared with signature "
<< func.getFunctionType() << ", expected " << fnTy;
} else {
OpBuilder moduleBuilder = OpBuilder::atBlockEnd(module.getBody());
func = LLVM::LLVMFuncOp::create(moduleBuilder, loc, symbolName, fnTy);
}
return LLVM::CallOp::create(builder, loc, func, arguments);
}

View file

@ -0,0 +1,156 @@
// RUN: mlir-opt %s -acc-to-llvm -split-input-file | FileCheck %s
// CHECK-LABEL: llvm.func @test_init_shutdown
// CHECK-NOT: acc.init
// CHECK-NOT: acc.shutdown
// The device type is materialized first, then the ident, the flags and finally
// the device number widened to i64. With the default ACCRuntimeCallConfig
// mapping (dialect ordinal), nvidia is 5.
// CHECK: %[[NVIDIA:.*]] = llvm.mlir.constant(5 : i64) : i64
// CHECK: %[[IDENT:.*]] = llvm.mlir.addressof @[[ID:ident_[^ ]+]]
// CHECK: %[[FLAGS:.*]] = llvm.mlir.constant(0 : i64) : i64
// CHECK: %[[DEVNUM:.*]] = llvm.mlir.constant(2 : i64) : i64
// CHECK: llvm.call @__tgt_acc_init(%[[IDENT]], %[[FLAGS]], %[[NVIDIA]], %[[DEVNUM]]) : (!llvm.ptr, i64, i64, i64) -> ()
// CHECK: llvm.call @__tgt_acc_shutdown
// Without a device number, the current device (-1) is used, once per device type.
// CHECK: %[[NVIDIA2:.*]] = llvm.mlir.constant(5 : i64) : i64
// CHECK: %[[CURRENT:.*]] = llvm.mlir.constant(-1 : i64) : i64
// CHECK: llvm.call @__tgt_acc_init(%{{.*}}, %{{.*}}, %[[NVIDIA2]], %[[CURRENT]])
// With the default mapping, host is 3.
// CHECK: %[[HOST:.*]] = llvm.mlir.constant(3 : i64) : i64
// CHECK: llvm.call @__tgt_acc_init(%{{.*}}, %{{.*}}, %[[HOST]], %{{.*}})
// CHECK: llvm.call @__tgt_acc_shutdown
// CHECK: llvm.call @__tgt_acc_shutdown
// Without device types, DeviceType::None maps to 0 under the default mapping.
// CHECK: %[[NONE:.*]] = llvm.mlir.constant(0 : i64) : i64
// CHECK: llvm.call @__tgt_acc_init(%{{.*}}, %{{.*}}, %[[NONE]], %{{.*}})
// CHECK: llvm.call @__tgt_acc_shutdown
// CHECK: llvm.call @__tgt_acc_init
// CHECK: llvm.call @__tgt_acc_shutdown
module {
func.func @test_init_shutdown() {
%c2_i32 = arith.constant 2 : i32
acc.init device_num(%c2_i32 : i32) attributes {device_types = [#acc.device_type<nvidia>]}
acc.shutdown device_num(%c2_i32 : i32) attributes {device_types = [#acc.device_type<nvidia>]}
acc.init attributes {device_types = [#acc.device_type<nvidia>, #acc.device_type<host>]}
acc.shutdown attributes {device_types = [#acc.device_type<nvidia>, #acc.device_type<host>]}
acc.init device_num(%c2_i32 : i32)
acc.shutdown device_num(%c2_i32 : i32)
acc.init
acc.shutdown
return
}
}
// -----
// CHECK-LABEL: llvm.func @test_set
// CHECK: llvm.call @__tgt_acc_set_default_async
// CHECK: %[[NVIDIA:.*]] = llvm.mlir.constant(5 : i64) : i64
// CHECK: llvm.call @__tgt_acc_set_device_num(%{{.*}}, %{{.*}}, %[[NVIDIA]], %{{.*}})
module {
func.func @test_set() {
%c0_i32 = arith.constant 0 : i32
%c1_i32 = arith.constant 1 : i32
acc.set default_async(%c1_i32 : i32) device_num(%c0_i32 : i32) attributes {device_type = #acc.device_type<nvidia>}
return
}
}
// -----
// CHECK-LABEL: llvm.func @test_set_device_type
// CHECK: %[[HOST:.*]] = llvm.mlir.constant(3 : i64) : i64
// CHECK: llvm.call @__tgt_acc_set_device_type(%{{.*}}, %{{.*}}, %[[HOST]])
module {
func.func @test_set_device_type() {
acc.set attributes {device_type = #acc.device_type<host>}
return
}
}
// -----
// CHECK-LABEL: llvm.func @test_if
// CHECK: llvm.cond_br %{{.*}}, ^[[THEN:bb[0-9]+]], ^[[CONT:bb[0-9]+]]
// CHECK: ^[[THEN]]:
// CHECK: llvm.call @__tgt_acc_init
// CHECK: llvm.br ^[[CONT]]
// CHECK: ^[[CONT]]:
module {
func.func @test_if(%cond: i1) {
acc.init if(%cond) attributes {device_types = [#acc.device_type<nvidia>]}
return
}
}
// -----
// The ident is a constant global whose source field points at the location
// string. Call sites take the address of that ident, not of the string.
// CHECK: llvm.mlir.global internal constant @[[$SRC:loc_10_1_[0-9]+]](";init-shutdown-set.mlir;test_init_with_loc;10;1;;\00")
// CHECK: llvm.mlir.global internal constant @[[$IDENT:ident_loc_10_1_[0-9]+]]() {{.*}} : !llvm.struct<(i32, i32, i32, i32, ptr)> {
// CHECK: llvm.mlir.zero : !llvm.struct<(i32, i32, i32, i32, ptr)>
// CHECK: llvm.mlir.addressof @[[$SRC]]
// CHECK: llvm.getelementptr
// CHECK: llvm.insertvalue {{.*}}[4]
// CHECK: llvm.return
// CHECK-LABEL: llvm.func @test_init_with_loc
// CHECK: llvm.mlir.addressof @[[$IDENT]]
// CHECK: llvm.call @__tgt_acc_init
#loc = loc("init-shutdown-set.mlir":10:1)
module {
func.func @test_init_with_loc() {
acc.init loc(#loc)
return
}
}
// -----
// Operations without file:line information fall back to an unknown ident.
// CHECK: llvm.mlir.global internal constant @loc__(";unknown;unknown;0;0;;\00")
// CHECK: llvm.mlir.global internal constant @ident_loc__() {{.*}} : !llvm.struct<(i32, i32, i32, i32, ptr)> {
// CHECK: llvm.mlir.zero : !llvm.struct<(i32, i32, i32, i32, ptr)>
// CHECK: llvm.mlir.addressof @loc__
// CHECK: llvm.getelementptr
// CHECK: llvm.insertvalue {{.*}}[4]
// CHECK: llvm.return
// CHECK-LABEL: llvm.func @test_init_unknown_loc
// CHECK: llvm.mlir.addressof @ident_loc__
// CHECK: llvm.call @__tgt_acc_init
module {
func.func @test_init_unknown_loc() {
acc.init loc(unknown)
return
}
}
// -----
// Two operations at the same line and column of different files must not share
// a location global, otherwise the ident would name the wrong file.
// CHECK-DAG: llvm.mlir.global internal constant @[[$LOC_A:loc_7_3_[0-9]+]](";a.mlir;test_distinct_files;7;3;;\00")
// CHECK-DAG: llvm.mlir.global internal constant @[[$LOC_B:loc_7_3_[0-9]+]](";b.mlir;test_distinct_files;7;3;;\00")
// CHECK-LABEL: llvm.func @test_distinct_files
// CHECK: llvm.mlir.addressof @ident_[[$LOC_A]]
// CHECK: llvm.call @__tgt_acc_init
// CHECK: llvm.mlir.addressof @ident_[[$LOC_B]]
// CHECK: llvm.call @__tgt_acc_shutdown
module {
func.func @test_distinct_files() {
acc.init loc("a.mlir":7:3)
acc.shutdown loc("b.mlir":7:3)
return
}
}

View file

@ -0,0 +1,26 @@
// RUN: mlir-opt %s -acc-to-llvm -verify-diagnostics -split-input-file | FileCheck %s
// A declaration of a runtime symbol with an incompatible signature cannot be
// called with the arguments the conversion emits.
module {
llvm.func @__tgt_acc_wait(!llvm.ptr, i64, i64, i64) -> i32
func.func @mismatched_wait() {
// expected-error @below {{OpenACC runtime function '__tgt_acc_wait' is already declared with signature}}
// expected-error @below {{failed to legalize operation 'acc.wait'}}
acc.wait
return
}
}
// -----
// A matching declaration is reused.
// CHECK-LABEL: llvm.func @matching_init
// CHECK: llvm.call @__tgt_acc_init
module {
llvm.func @__tgt_acc_init(!llvm.ptr, i64, i64, i64)
func.func @matching_init() {
acc.init
return
}
}

View file

@ -0,0 +1,144 @@
// RUN: mlir-opt %s -acc-to-llvm -split-input-file | FileCheck %s
// Empty wait: waitNum=0, null wait list, sync async queue (-1).
// CHECK-LABEL: llvm.func @test_wait_empty
// CHECK: llvm.mlir.constant(-1 : i64)
// CHECK: llvm.mlir.constant(0 : i32)
// CHECK: llvm.mlir.zero : !llvm.ptr
// CHECK: llvm.call @__tgt_acc_wait
module {
func.func @test_wait_empty() {
acc.wait
return
}
}
// -----
// Wait operands: waitNum matches the list length, values are stored into an
// alloca'd wait list, and the async queue remains sync (-1).
// CHECK-LABEL: llvm.func @test_wait_operands
// CHECK: %[[ASYNC:.*]] = llvm.mlir.constant(-1 : i64)
// CHECK: %[[WAIT0:.*]] = llvm.mlir.constant(0 : i64)
// CHECK: %[[WAIT_NUM:.*]] = llvm.mlir.constant(1 : i32)
// CHECK: %[[WAIT_LIST:.*]] = llvm.alloca %[[WAIT_NUM]] x i64
// CHECK: %[[IDX:.*]] = llvm.mlir.constant(0 : i32)
// CHECK: %[[WAIT_SLOT:.*]] = llvm.getelementptr %[[WAIT_LIST]][%[[IDX]]] : (!llvm.ptr, i32) -> !llvm.ptr, i64
// CHECK: llvm.store %[[WAIT0]], %[[WAIT_SLOT]] : i64, !llvm.ptr
// CHECK: llvm.call @__tgt_acc_wait(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}, %[[WAIT_NUM]], %[[WAIT_LIST]], %[[ASYNC]])
module {
func.func @test_wait_operands() {
%c0 = arith.constant 0 : i32
acc.wait(%c0 : i32)
return
}
}
// -----
// Async with an explicit queue value: the operand is widened to i64 (folded
// through ArithToLLVM into a constant here) and passed as the async queue.
// CHECK-LABEL: llvm.func @test_wait_async
// CHECK: llvm.mlir.constant(1 : i64)
// CHECK: llvm.call @__tgt_acc_wait
module {
func.func @test_wait_async() {
%c1 = arith.constant 1 : i32
acc.wait async(%c1 : i32)
return
}
}
// -----
// Non-constant async operand: must widen i32 to i64 before the runtime call
// (constant folding hides this on the previous test).
// CHECK-LABEL: llvm.func @test_wait_async_arg
// CHECK: llvm.sext %{{.*}} : i32 to i64
// CHECK: llvm.call @__tgt_acc_wait
module {
func.func @test_wait_async_arg(%q: i32) {
acc.wait async(%q : i32)
return
}
}
// -----
// Async with no value: OpenACC async sentinel -4.
// CHECK-LABEL: llvm.func @test_wait_async_noval
// CHECK: llvm.mlir.constant(-4 : i64)
// CHECK: llvm.call @__tgt_acc_wait
module {
func.func @test_wait_async_noval() {
acc.wait async
return
}
}
// -----
// CHECK-LABEL: llvm.func @test_wait_if
// CHECK: llvm.cond_br %{{.*}}, ^[[THEN:bb[0-9]+]], ^[[CONT:bb[0-9]+]]
// CHECK: ^[[THEN]]:
// CHECK: llvm.call @__tgt_acc_wait
// CHECK: llvm.br ^[[CONT]]
// CHECK: ^[[CONT]]:
module {
func.func @test_wait_if(%cond: i1) {
acc.wait if(%cond)
return
}
}
// -----
// The ident is a constant global whose source field points at the location
// string. Call sites take the address of that ident, not of the string.
// CHECK: llvm.mlir.global internal constant @[[$SRC:loc_5_3_[0-9]+]](";wait.mlir;test_wait_with_loc;5;3;;\00")
// CHECK: llvm.mlir.global internal constant @[[$IDENT:ident_loc_5_3_[0-9]+]]() {{.*}} : !llvm.struct<(i32, i32, i32, i32, ptr)> {
// CHECK: llvm.mlir.zero : !llvm.struct<(i32, i32, i32, i32, ptr)>
// CHECK: llvm.mlir.addressof @[[$SRC]]
// CHECK: llvm.getelementptr
// CHECK: llvm.insertvalue {{.*}}[4]
// CHECK: llvm.return
// CHECK-LABEL: llvm.func @test_wait_with_loc
// CHECK: llvm.mlir.addressof @[[$IDENT]]
// CHECK: llvm.call @__tgt_acc_wait
#loc = loc("wait.mlir":5:3)
module {
func.func @test_wait_with_loc() {
acc.wait loc(#loc)
return
}
}
// -----
// Operations without file:line information fall back to an unknown ident.
// CHECK: llvm.mlir.global internal constant @loc__(";unknown;unknown;0;0;;\00")
// CHECK: llvm.mlir.global internal constant @ident_loc__() {{.*}} : !llvm.struct<(i32, i32, i32, i32, ptr)> {
// CHECK: llvm.mlir.zero : !llvm.struct<(i32, i32, i32, i32, ptr)>
// CHECK: llvm.mlir.addressof @loc__
// CHECK: llvm.getelementptr
// CHECK: llvm.insertvalue {{.*}}[4]
// CHECK: llvm.return
// CHECK-LABEL: llvm.func @test_wait_unknown_loc
// CHECK: llvm.mlir.addressof @ident_loc__
// CHECK: llvm.call @__tgt_acc_wait
module {
func.func @test_wait_unknown_loc() {
acc.wait loc(unknown)
return
}
}