[Mlir-commits] [mlir] [mlir][acc] Introduce ACCToLLVM and executable directive codegen (PR #213165)
Razvan Lupusoru
llvmlistbot at llvm.org
Thu Jul 30 15:49:54 PDT 2026
https://github.com/razvanlupusoru created https://github.com/llvm/llvm-project/pull/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.
>From 565bc9e11410f7fed88edfc12b3a64005ab0e4c2 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Wed, 29 Jul 2026 09:58:39 -0700
Subject: [PATCH] [mlir][acc] Introduce ACCToLLVM and executable directive
codegen
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.
---
.../mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h | 38 +++
.../Conversion/OpenACCToLLVM/ACCToLLVMUtils.h | 52 +++
mlir/include/mlir/Conversion/Passes.h | 1 +
mlir/include/mlir/Conversion/Passes.td | 9 +
.../OpenACC/OpenACCRuntimeFunctions.def | 57 ++++
.../Dialect/OpenACC/OpenACCRuntimeUtils.h | 102 ++++++
mlir/lib/Conversion/CMakeLists.txt | 1 +
.../ACCExecutableDirectivePatterns.cpp | 305 ++++++++++++++++++
.../Conversion/OpenACCToLLVM/ACCToLLVM.cpp | 53 +++
.../OpenACCToLLVM/ACCToLLVMUtils.cpp | 171 ++++++++++
.../Conversion/OpenACCToLLVM/CMakeLists.txt | 22 ++
mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt | 2 +
.../OpenACC/Utils/OpenACCRuntimeUtils.cpp | 127 ++++++++
.../OpenACCToLLVM/init-shutdown-set.mlir | 156 +++++++++
.../runtime-declaration-mismatch.mlir | 26 ++
mlir/test/Conversion/OpenACCToLLVM/wait.mlir | 144 +++++++++
16 files changed, 1266 insertions(+)
create mode 100644 mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
create mode 100644 mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
create mode 100644 mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def
create mode 100644 mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h
create mode 100644 mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
create mode 100644 mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVM.cpp
create mode 100644 mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
create mode 100644 mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt
create mode 100644 mlir/lib/Dialect/OpenACC/Utils/OpenACCRuntimeUtils.cpp
create mode 100644 mlir/test/Conversion/OpenACCToLLVM/init-shutdown-set.mlir
create mode 100644 mlir/test/Conversion/OpenACCToLLVM/runtime-declaration-mismatch.mlir
create mode 100644 mlir/test/Conversion/OpenACCToLLVM/wait.mlir
diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
new file mode 100644
index 0000000000000..02bb6104a238d
--- /dev/null
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
@@ -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
diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
new file mode 100644
index 0000000000000..7e16cf17c4489
--- /dev/null
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
@@ -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
diff --git a/mlir/include/mlir/Conversion/Passes.h b/mlir/include/mlir/Conversion/Passes.h
index ca971fe4b90d8..8758f5c96c3db 100644
--- a/mlir/include/mlir/Conversion/Passes.h
+++ b/mlir/include/mlir/Conversion/Passes.h
@@ -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"
diff --git a/mlir/include/mlir/Conversion/Passes.td b/mlir/include/mlir/Conversion/Passes.td
index f0567d347ee39..f13cb9a801139 100644
--- a/mlir/include/mlir/Conversion/Passes.td
+++ b/mlir/include/mlir/Conversion/Passes.td
@@ -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
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def b/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def
new file mode 100644
index 0000000000000..338f3950216f5
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def
@@ -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
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h
new file mode 100644
index 0000000000000..4274bacd974eb
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h
@@ -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
diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt
index c926ee89151ba..cee1a069ad3b1 100644
--- a/mlir/lib/Conversion/CMakeLists.txt
+++ b/mlir/lib/Conversion/CMakeLists.txt
@@ -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)
diff --git a/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp b/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
new file mode 100644
index 0000000000000..ae4090d00bd26
--- /dev/null
+++ b/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
@@ -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);
+}
diff --git a/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVM.cpp b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVM.cpp
new file mode 100644
index 0000000000000..326f97b8aa2d6
--- /dev/null
+++ b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVM.cpp
@@ -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();
+}
diff --git a/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
new file mode 100644
index 0000000000000..96a306d24b491
--- /dev/null
+++ b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
@@ -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);
+}
diff --git a/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt b/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt
new file mode 100644
index 0000000000000..933ba3e921017
--- /dev/null
+++ b/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt
@@ -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
+)
diff --git a/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
index fc30625964c43..359cf23aa4abf 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACC/Utils/CMakeLists.txt
@@ -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
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCRuntimeUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCRuntimeUtils.cpp
new file mode 100644
index 0000000000000..405b73a74d95d
--- /dev/null
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCRuntimeUtils.cpp
@@ -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);
+}
diff --git a/mlir/test/Conversion/OpenACCToLLVM/init-shutdown-set.mlir b/mlir/test/Conversion/OpenACCToLLVM/init-shutdown-set.mlir
new file mode 100644
index 0000000000000..cb522d71d2b94
--- /dev/null
+++ b/mlir/test/Conversion/OpenACCToLLVM/init-shutdown-set.mlir
@@ -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
+ }
+}
diff --git a/mlir/test/Conversion/OpenACCToLLVM/runtime-declaration-mismatch.mlir b/mlir/test/Conversion/OpenACCToLLVM/runtime-declaration-mismatch.mlir
new file mode 100644
index 0000000000000..453bc56871b10
--- /dev/null
+++ b/mlir/test/Conversion/OpenACCToLLVM/runtime-declaration-mismatch.mlir
@@ -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
+ }
+}
diff --git a/mlir/test/Conversion/OpenACCToLLVM/wait.mlir b/mlir/test/Conversion/OpenACCToLLVM/wait.mlir
new file mode 100644
index 0000000000000..87aae3fb27bb7
--- /dev/null
+++ b/mlir/test/Conversion/OpenACCToLLVM/wait.mlir
@@ -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
+ }
+}
More information about the Mlir-commits
mailing list