[flang-commits] [flang] [flang][acc][lowering] Declare undeclared acc routine bind(name) targets (PR #203088)
via flang-commits
flang-commits at lists.llvm.org
Wed Jun 10 12:56:14 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-openacc
Author: Andre Kuhlenschmidt (akuhlens)
<details>
<summary>Changes</summary>
An `!$acc routine ... bind(target)` can name a target with no func.func in the program unit. Lowering emits an acc.routine op referencing it, and external-name-interop / nvhpc-acc-bind-routine then reference an undeclared symbol, tripping the MLIR verifier ("does not reference a symbol in the current scope").
Materialize a private func.func for each otherwise-undeclared bind target after primary translation, gated on the decorated procedure having been lowered (so the declared set matches the acc.routine bind references actually emitted). bind(symbol) targets reuse getOrDeclareFunction (mangled, honoring BIND(C)); bind("string") targets use the new getOrDeclareNamedFunction (verbatim asm name, left untouched by external-name-interop).
---
Full diff: https://github.com/llvm/llvm-project/pull/203088.diff
10 Files Affected:
- (modified) flang/include/flang/Lower/CallInterface.h (+8)
- (modified) flang/include/flang/Lower/OpenACC.h (+9)
- (modified) flang/lib/Lower/Bridge.cpp (+10)
- (modified) flang/lib/Lower/CallInterface.cpp (+18)
- (modified) flang/lib/Lower/OpenACC.cpp (+66)
- (added) flang/test/Lower/OpenACC/acc-routine-bind-devtype-undeclared.f90 (+17)
- (added) flang/test/Lower/OpenACC/acc-routine-bind-gate-skip.f90 (+14)
- (added) flang/test/Lower/OpenACC/acc-routine-bind-string-undeclared.f90 (+18)
- (added) flang/test/Lower/OpenACC/acc-routine-bind-undeclared.f90 (+17)
- (added) flang/test/Transforms/external-name-interop-acc-routine-bind.fir (+13)
``````````diff
diff --git a/flang/include/flang/Lower/CallInterface.h b/flang/include/flang/Lower/CallInterface.h
index 348987cb76c5e..89d2fe9f04c29 100644
--- a/flang/include/flang/Lower/CallInterface.h
+++ b/flang/include/flang/Lower/CallInterface.h
@@ -477,6 +477,14 @@ mlir::func::FuncOp
getOrDeclareFunction(const Fortran::evaluate::ProcedureDesignator &,
Fortran::lower::AbstractConverter &);
+/// Declare or find the mlir::func::FuncOp for an external procedure named
+/// verbatim by \p name (not mangled). Declare it with \p type, or () -> () when
+/// \p type is null.
+mlir::func::FuncOp
+getOrDeclareNamedFunction(llvm::StringRef name,
+ Fortran::lower::AbstractConverter &,
+ mlir::FunctionType type = {});
+
/// Return the type of an argument that is a dummy procedure. This may be an
/// mlir::FunctionType, but it can also be a more elaborate type based on the
/// function type (like a tuple<function type, length type> for character
diff --git a/flang/include/flang/Lower/OpenACC.h b/flang/include/flang/Lower/OpenACC.h
index 8ba72f356656e..572660f07bc6b 100644
--- a/flang/include/flang/Lower/OpenACC.h
+++ b/flang/include/flang/Lower/OpenACC.h
@@ -51,6 +51,7 @@ struct OpenACCRoutineConstruct;
namespace semantics {
class OpenACCRoutineInfo;
+class Scope;
class SemanticsContext;
class Symbol;
} // namespace semantics
@@ -89,6 +90,14 @@ void genOpenACCRoutineConstruct(
void declareExternalAccModuleDeclareActionRecipes(
AbstractConverter &, fir::FirOpBuilder &,
const Fortran::semantics::Symbol &);
+
+/// Declare a private func.func for every `acc routine` bind(name) target not
+/// otherwise declared in this program unit. A target is declared only when its
+/// decorated procedure has a func.func. \p scope defaults to the global scope.
+void materializeOpenACCRoutineBindTargets(
+ AbstractConverter &, Fortran::semantics::SemanticsContext &,
+ const Fortran::semantics::Scope *scope = nullptr);
+
void attachDeclarePostAllocAction(AbstractConverter &, fir::FirOpBuilder &,
const Fortran::semantics::Symbol &);
void attachDeclarePreDeallocAction(AbstractConverter &, fir::FirOpBuilder &,
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 78f9de9c9420e..58b1cc9f15095 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -597,6 +597,16 @@ class FirConverter : public Fortran::lower::AbstractConverter {
u);
}
+ // Declare any `acc routine` bind(name) targets not otherwise declared, so a
+ // live symbol exists for later passes. No-op for targets already lowered in
+ // this unit.
+ if (getFoldingContext().languageFeatures().IsEnabled(
+ Fortran::common::LanguageFeature::OpenACC))
+ createBuilderOutsideOfFuncOpAndDo([&]() {
+ Fortran::lower::materializeOpenACCRoutineBindTargets(
+ *this, bridge.getSemanticsContext());
+ });
+
// Once all the code has been translated, create global runtime type info
// data structures for the derived types that have been processed, as well
// as fir.type_info operations for the dispatch tables.
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index 3fcf314faefb6..45bdb669126cf 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -1739,6 +1739,24 @@ mlir::func::FuncOp Fortran::lower::getOrDeclareFunction(
return SignatureBuilder{proc, converter}.getOrCreateFuncOp();
}
+mlir::func::FuncOp Fortran::lower::getOrDeclareNamedFunction(
+ llvm::StringRef name, Fortran::lower::AbstractConverter &converter,
+ mlir::FunctionType type) {
+ mlir::ModuleOp module = converter.getModuleOp();
+ mlir::SymbolTable *symbolTable = converter.getMLIRSymbolTable();
+ if (mlir::func::FuncOp func =
+ fir::FirOpBuilder::getNamedFunction(module, symbolTable, name))
+ return func;
+
+ // The name is its own external spelling; create it verbatim with an
+ // implicit-interface () -> () signature when no type is given.
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ if (!type)
+ type = mlir::FunctionType::get(builder.getContext(), {}, {});
+ return fir::FirOpBuilder::createFunction(builder.getUnknownLoc(), module,
+ name, type, symbolTable);
+}
+
// Is it required to pass a dummy procedure with \p characteristics as a tuple
// containing the function address and the result length ?
static bool mustPassLengthWithDummyProcedure(
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 1c51cf7fa6ca5..82e95cd252dcd 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -13,7 +13,9 @@
#include "flang/Lower/OpenACC.h"
#include "flang/Common/idioms.h"
+#include "flang/Evaluate/call.h"
#include "flang/Lower/Bridge.h"
+#include "flang/Lower/CallInterface.h"
#include "flang/Lower/ConvertType.h"
#include "flang/Lower/ConvertVariable.h"
#include "flang/Lower/DirectivesCommon.h"
@@ -4535,6 +4537,70 @@ void Fortran::lower::genOpenACCRoutineConstruct(
workerDeviceTypes, vectorDeviceTypes);
}
+void Fortran::lower::materializeOpenACCRoutineBindTargets(
+ Fortran::lower::AbstractConverter &converter,
+ Fortran::semantics::SemanticsContext &semaCtx,
+ const Fortran::semantics::Scope *scope) {
+ const Fortran::semantics::Scope &root =
+ scope ? *scope : semaCtx.globalScope();
+
+ // Recurse into child scopes first (modules, subprograms, etc.).
+ for (const Fortran::semantics::Scope &child : root.children())
+ materializeOpenACCRoutineBindTargets(converter, semaCtx, &child);
+
+ mlir::ModuleOp module = converter.getModuleOp();
+ mlir::SymbolTable *symbolTable = converter.getMLIRSymbolTable();
+
+ // Declare a single bind(name) target. A bind("string") target is a raw asm
+ // name used verbatim -- no Fortran-symbol resolution or name mangling --
+ // whereas a bind(symbol) target is mangled; a func.func is declared for both.
+ auto declareBindTarget =
+ [&](const Fortran::semantics::OpenACCRoutineDeviceTypeInfo &dti) {
+ const std::variant<std::string, Fortran::semantics::SymbolRef> *bind =
+ dti.bindName();
+ if (!bind)
+ return;
+ if (const auto *bindSym =
+ std::get_if<Fortran::semantics::SymbolRef>(bind)) {
+ // bind(identifier): declared with external-procedure mangling,
+ // matching the bind reference.
+ Fortran::evaluate::ProcedureDesignator proc(*bindSym);
+ (void)Fortran::lower::getOrDeclareFunction(proc, converter);
+ } else if (const auto *bindStr = std::get_if<std::string>(bind)) {
+ // bind("string"): the literal is its own external name, declared
+ // verbatim.
+ (void)Fortran::lower::getOrDeclareNamedFunction(*bindStr, converter);
+ }
+ };
+
+ for (const auto &it : root) {
+ const Fortran::semantics::Symbol &ultimate = it.second->GetUltimate();
+ const std::vector<Fortran::semantics::OpenACCRoutineInfo> *infos = nullptr;
+ if (const auto *d =
+ ultimate.detailsIf<Fortran::semantics::SubprogramDetails>())
+ infos = &d->openACCRoutineInfos();
+ else if (const auto *d =
+ ultimate.detailsIf<Fortran::semantics::ProcEntityDetails>())
+ infos = &d->openACCRoutineInfos();
+ if (!infos || infos->empty())
+ continue;
+
+ // An acc.routine bind reference is emitted only when the decorated
+ // procedure was lowered to a func.func; gate on that so a never-lowered
+ // procedure produces no spurious declaration.
+ if (!fir::FirOpBuilder::getNamedFunction(module, symbolTable,
+ converter.mangleName(ultimate)))
+ continue;
+
+ for (const Fortran::semantics::OpenACCRoutineInfo &info : *infos) {
+ declareBindTarget(info); // device-independent bind
+ for (const Fortran::semantics::OpenACCRoutineDeviceTypeInfo &dti :
+ info.deviceTypeInfos())
+ declareBindTarget(dti);
+ }
+ }
+}
+
static void
genACC(Fortran::lower::AbstractConverter &converter,
Fortran::lower::pft::Evaluation &eval,
diff --git a/flang/test/Lower/OpenACC/acc-routine-bind-devtype-undeclared.f90 b/flang/test/Lower/OpenACC/acc-routine-bind-devtype-undeclared.f90
new file mode 100644
index 0000000000000..7aee3350738b0
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-routine-bind-devtype-undeclared.f90
@@ -0,0 +1,17 @@
+! A device_type-specific bind to an undeclared target is also declared.
+
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+subroutine s_bind_devtype(n, x)
+ integer :: n, i
+ real :: x(n)
+ !$acc routine(aclear) seq device_type(nvidia) bind(aclear_dev)
+ external :: aclear
+ !$acc parallel loop
+ do i = 1, n
+ call aclear(x(i))
+ end do
+end subroutine
+
+! CHECK: acc.routine @{{.*}} func(@_QPaclear){{.*}}@_QPaclear_dev
+! CHECK: func.func private @_QPaclear_dev
diff --git a/flang/test/Lower/OpenACC/acc-routine-bind-gate-skip.f90 b/flang/test/Lower/OpenACC/acc-routine-bind-gate-skip.f90
new file mode 100644
index 0000000000000..c578d09f1a541
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-routine-bind-gate-skip.f90
@@ -0,0 +1,14 @@
+! A decorated acc routine bind(...) whose procedure is never lowered gets no
+! bind-target declaration and no acc.routine.
+
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+subroutine s_gate_skip()
+ !$acc routine(unused_ext) seq bind(unused_ext_dev)
+ external :: unused_ext
+end subroutine
+
+! CHECK-LABEL: func.func @_QPs_gate_skip
+! CHECK-NOT: @_QPunused_ext_dev
+! CHECK-NOT: @_QPunused_ext
+! CHECK-NOT: acc.routine
diff --git a/flang/test/Lower/OpenACC/acc-routine-bind-string-undeclared.f90 b/flang/test/Lower/OpenACC/acc-routine-bind-string-undeclared.f90
new file mode 100644
index 0000000000000..98a7c3cdc9007
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-routine-bind-string-undeclared.f90
@@ -0,0 +1,18 @@
+! A bind("name") target is its own external name, declared verbatim (not
+! mangled), so later passes reference a live symbol.
+
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+! CHECK: acc.routine @{{.*}} func(@_QPaclear) bind("aclear_dev") seq
+! CHECK: func.func private @aclear_dev
+
+subroutine s_bind_str_undeclared(n, x)
+ integer :: n, i
+ real :: x(n)
+ !$acc routine(aclear) seq bind("aclear_dev")
+ external :: aclear
+ !$acc parallel loop
+ do i = 1, n
+ call aclear(x(i))
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenACC/acc-routine-bind-undeclared.f90 b/flang/test/Lower/OpenACC/acc-routine-bind-undeclared.f90
new file mode 100644
index 0000000000000..2438513b5bac7
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-routine-bind-undeclared.f90
@@ -0,0 +1,17 @@
+! An otherwise-undeclared acc routine bind(name) target still gets a func.func.
+
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+! CHECK: acc.routine @{{.*}} func(@_QPaclear) bind(@_QPaclear_seq) seq
+! CHECK: func.func private @_QPaclear_seq
+
+subroutine s_bind_undeclared(n, x)
+ integer :: n, i
+ real :: x(n)
+ !$acc routine(aclear) seq bind(aclear_seq)
+ external :: aclear
+ !$acc parallel loop
+ do i = 1, n
+ call aclear(x(i))
+ end do
+end subroutine
diff --git a/flang/test/Transforms/external-name-interop-acc-routine-bind.fir b/flang/test/Transforms/external-name-interop-acc-routine-bind.fir
new file mode 100644
index 0000000000000..ba85e7596e0f1
--- /dev/null
+++ b/flang/test/Transforms/external-name-interop-acc-routine-bind.fir
@@ -0,0 +1,13 @@
+// Given a declared (otherwise-undeclared) bind target, external-name-interop
+// renames the func.func and the acc.routine bind reference together.
+
+// RUN: fir-opt %s --external-name-interop | FileCheck %s
+
+module {
+ acc.routine @acc_routine_0 func(@_QPaclear) bind(@_QPaclear_seq) seq
+ func.func private @_QPaclear(!fir.ref<f32>) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_0]>}
+ func.func private @_QPaclear_seq()
+}
+
+// CHECK: acc.routine @acc_routine_0 func(@aclear_) bind(@aclear_seq_) seq
+// CHECK: func.func private @aclear_seq_() attributes {{{.*}}fir.internal_name = "_QPaclear_seq"{{.*}}}
``````````
</details>
https://github.com/llvm/llvm-project/pull/203088
More information about the flang-commits
mailing list