[flang-commits] [flang] 72015f2 - [flang][cuda] Skip device-only specifics at host call sites in generic resolution (#234710)
via flang-commits
flang-commits at lists.llvm.org
Sun Oct 25 15:19:33 PDT 2026
Author: Valentin Clement (バレンタイン クレメン)
Date: 2026-10-25T15:19:28-07:00
New Revision: 72015f2d65274c8586ff420aa9158050058f1584
URL: https://github.com/llvm/llvm-project/commit/72015f2d65274c8586ff420aa9158050058f1584
DIFF: https://github.com/llvm/llvm-project/commit/72015f2d65274c8586ff420aa9158050058f1584.diff
LOG: [flang][cuda] Skip device-only specifics at host call sites in generic resolution (#234710)
When CUDA is enabled and the call site is not in a device context, skip
specifics whose only CUDA subprogram attribute is DEVICE. HOST,DEVICE
and
unattributed specifics are unaffected. Call sites inside CUF kernel and
OpenACC regions, which share Scope::Kind::OpenACCConstruct and may be
device code in a host subprogram, are left unfiltered.
Added:
flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
flang/test/Lower/CUDA/cuda-generic-separate-device-specific.cuf
flang/test/Semantics/CUDA/cuf-generic-host-device-only.cuf
Modified:
flang/lib/Semantics/expression.cpp
Removed:
################################################################################
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 58d36f164afa40..d806c752901af0 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3323,6 +3323,58 @@ static bool IsCUDADeviceCallable(const Symbol &symbol) {
*attrs == common::CUDASubprogramAttrs::HostDevice);
}
+// A specific with only the DEVICE attribute cannot be referenced from host
+// code, whatever its dummy arguments' CUDA data attributes score.
+static bool IsCUDADeviceOnly(const Symbol &symbol) {
+ const Symbol *ultimate{&symbol.GetUltimate()};
+ // Type-bound specifics: inspect the bound procedure.
+ if (const auto *binding{
+ ultimate->detailsIf<semantics::ProcBindingDetails>()}) {
+ ultimate = &binding->symbol().GetUltimate();
+ }
+ const auto *subprogram{ultimate->detailsIf<semantics::SubprogramDetails>()};
+ auto attrs{subprogram ? subprogram->cudaSubprogramAttrs() : std::nullopt};
+ return attrs && *attrs == common::CUDASubprogramAttrs::Device;
+}
+
+// True when code in this scope may run on the device. FindCUDADeviceContext
+// reads the scope symbol's own CUDA attributes, which a separate module
+// procedure implementation does not carry: its interface does.
+static bool MayRunOnCUDADevice(const semantics::Scope &scope) {
+ if (semantics::FindCUDADeviceContext(&scope) ||
+ semantics::HasOpenACCRoutineDirective(&scope)) {
+ return true;
+ }
+ for (const semantics::Scope *s{&scope}; !s->IsGlobal(); s = &s->parent()) {
+ // CUF kernel and OpenACC regions share Scope::Kind::OpenACCConstruct and
+ // may hold device code in a host subprogram.
+ if (s->kind() == semantics::Scope::Kind::OpenACCConstruct) {
+ return true;
+ }
+ if (const Symbol *symbol{s->symbol()}) {
+ const auto *subp{symbol->detailsIf<semantics::SubprogramDetails>()};
+ if (subp && subp->moduleInterface()) {
+ // Kernels (GLOBAL, GRID_GLOBAL) run device code too, so any attribute
+ // other than HOST makes this a device context.
+ const auto *interface{subp->moduleInterface()
+ ->GetUltimate()
+ .detailsIf<semantics::SubprogramDetails>()};
+ auto attrs{interface ? interface->cudaSubprogramAttrs() : std::nullopt};
+ if (attrs && *attrs != common::CUDASubprogramAttrs::Host) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// Specifics of intrinsic modules such as CUDADEVICE (e.g. ATOMICADD).
+static bool IsFromIntrinsicModule(const Symbol &symbol) {
+ const Symbol *module{symbol.GetUltimate().owner().GetSymbol()};
+ return module && module->attrs().test(semantics::Attr::INTRINSIC);
+}
+
static bool IsCudaDeviceIntrinsicShadowedByHostProcedure(
const parser::CharBlock &callSite, semantics::SemanticsContext &context,
const Symbol *resolution, bool isSubroutine) {
@@ -3361,60 +3413,82 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
const auto *genericDetails{ultimate.detailsIf<semantics::GenericDetails>()};
if (genericDetails) {
std::optional<CudaMatchingDistance> crtMatchingDistance;
- for (const Symbol &specific0 : genericDetails->specificProcs()) {
- const Symbol &specific1{BypassGeneric(specific0)};
- if (isSubroutine != !IsFunction(specific1)) {
- continue;
- }
- const Symbol *specific{ResolveForward(specific1)};
- if (!specific) {
- continue;
- }
- if (std::optional<characteristics::Procedure> procedure{
- characteristics::Procedure::Characterize(
- ProcedureDesignator{*specific}, context_.foldingContext(),
- /*emitError=*/false)}) {
- ActualArguments localActuals{actuals};
- if (specific->has<semantics::ProcBindingDetails>()) {
- if (!adjustActuals.value()(*specific, localActuals)) {
- continue;
- }
+ bool inHostCode{
+ context_.languageFeatures().IsEnabled(common::LanguageFeature::CUDA) &&
+ !GetContextualMessages().at().empty() &&
+ !MayRunOnCUDADevice(context_.FindScope(GetContextualMessages().at()))};
+ // Prefer specifics callable from host code at a host call site. If none
+ // matches, still accept device-only specifics of intrinsic modules, which
+ // host code has always been allowed to reference.
+ bool skipDeviceOnly{inHostCode};
+ const auto triedBefore{tried.size()};
+ for (;;) {
+ for (const Symbol &specific0 : genericDetails->specificProcs()) {
+ const Symbol &specific1{BypassGeneric(specific0)};
+ if (isSubroutine != !IsFunction(specific1)) {
+ continue;
}
- tried.push_back(*specific);
- if (semantics::CheckInterfaceForGeneric(*procedure, localActuals,
- context_, false /* no integer conversions */) &&
- CheckCompatibleArguments(
- *procedure, localActuals, foldingContext_)) {
- CudaMatchingDistance d{
- ComputeCudaMatchingDistance(context_, *procedure, localActuals)};
- if ((procedure->IsElemental() && elemental) ||
- (!procedure->IsElemental() && nonElemental)) {
- if (crtMatchingDistance) {
- int cmp{CompareCudaMatchingDistance(d, *crtMatchingDistance)};
- if (cmp > 0) {
- continue;
- }
- if (cmp == 0) {
- // 16.9.144(6): a bare NULL() is not allowed as an actual
- // argument to a generic procedure if the specific procedure
- // cannot be unambiguously distinguished
- // Underspecified external procedure actual arguments can
- // also lead to ambiguity.
- return {nullptr, true /* due to ambiguity */, std::move(tried)};
- }
- // Matching distance is smaller than the previously matched
- // specific. Let it go through so the current procedure is picked.
+ const Symbol *specific{ResolveForward(specific1)};
+ if (!specific) {
+ continue;
+ }
+ if (inHostCode && IsCUDADeviceOnly(*specific) &&
+ (skipDeviceOnly || !IsFromIntrinsicModule(*specific))) {
+ continue;
+ }
+ if (std::optional<characteristics::Procedure> procedure{
+ characteristics::Procedure::Characterize(
+ ProcedureDesignator{*specific}, context_.foldingContext(),
+ /*emitError=*/false)}) {
+ ActualArguments localActuals{actuals};
+ if (specific->has<semantics::ProcBindingDetails>()) {
+ if (!adjustActuals.value()(*specific, localActuals)) {
+ continue;
}
}
- if (!procedure->IsElemental()) {
- // takes priority over elemental match
- nonElemental = specific;
- } else {
- elemental = specific;
+ tried.push_back(*specific);
+ if (semantics::CheckInterfaceForGeneric(*procedure, localActuals,
+ context_, false /* no integer conversions */) &&
+ CheckCompatibleArguments(
+ *procedure, localActuals, foldingContext_)) {
+ CudaMatchingDistance d{ComputeCudaMatchingDistance(
+ context_, *procedure, localActuals)};
+ if ((procedure->IsElemental() && elemental) ||
+ (!procedure->IsElemental() && nonElemental)) {
+ if (crtMatchingDistance) {
+ int cmp{CompareCudaMatchingDistance(d, *crtMatchingDistance)};
+ if (cmp > 0) {
+ continue;
+ }
+ if (cmp == 0) {
+ // 16.9.144(6): a bare NULL() is not allowed as an actual
+ // argument to a generic procedure if the specific procedure
+ // cannot be unambiguously distinguished
+ // Underspecified external procedure actual arguments can
+ // also lead to ambiguity.
+ return {
+ nullptr, true /* due to ambiguity */, std::move(tried)};
+ }
+ // Matching distance is smaller than the previously matched
+ // specific. Let it go through so the current procedure is
+ // picked.
+ }
+ }
+ if (!procedure->IsElemental()) {
+ // takes priority over elemental match
+ nonElemental = specific;
+ } else {
+ elemental = specific;
+ }
+ crtMatchingDistance = std::move(d);
}
- crtMatchingDistance = std::move(d);
}
}
+ if (!skipDeviceOnly || nonElemental || elemental) {
+ break;
+ }
+ skipDeviceOnly = false;
+ tried.erase(tried.begin() + triedBefore, tried.end());
}
}
// Is there a derived type of the same name?
diff --git a/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf b/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
new file mode 100644
index 00000000000000..8590e3a6370560
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
@@ -0,0 +1,67 @@
+! RUN: bbc -emit-hlfir -fcuda -fopenacc %s -o - | FileCheck %s
+
+! A host call site must not resolve to a device-only specific, even when
+! managed actuals score the DEVICE dummies as a closer match.
+
+module m
+ interface sg
+ module procedure s_host
+ module procedure s_dev
+ end interface
+ real(8), managed, allocatable :: a(:), y(:)
+ type t
+ contains
+ procedure, nopass :: tb_host => s_host
+ procedure, nopass :: tb_dev => s_dev
+ generic :: tsg => tb_host, tb_dev
+ end type
+contains
+ subroutine s_host(x, y)
+ real(8), intent(in) :: x
+ real(8), intent(out) :: y
+ y = 2.0d0*x
+ end subroutine
+
+ attributes(device) subroutine s_dev(x, y)
+ real(8), device :: x
+ real(8), device :: y
+ y = x*x
+ end subroutine
+
+ subroutine acc_caller(i)
+ !$acc routine seq nohost
+ integer :: i
+ call sg(a(i), y(i))
+ end subroutine
+
+ subroutine tb_caller(i)
+ integer :: i
+ type(t) :: obj
+ call obj%tsg(a(i), y(i))
+ end subroutine
+
+ subroutine host_caller(i)
+ integer :: i
+ call sg(a(i), y(i))
+ end subroutine
+
+ subroutine intrinsic_caller(i)
+ use cudadevice, only: atomicadd
+ integer :: i, r
+ r = atomicadd(i, 1)
+ end subroutine
+end module
+
+! CHECK-LABEL: func.func @_QMmPacc_caller
+! CHECK: fir.call @_QMmPs_dev
+
+! CHECK-LABEL: func.func @_QMmPtb_caller
+! CHECK: fir.call @_QMmPs_host
+
+! CHECK-LABEL: func.func @_QMmPhost_caller
+! CHECK: fir.call @_QMmPs_host
+
+! A device-only specific of an intrinsic module is still accepted when no
+! host-callable specific matches.
+! CHECK-LABEL: func.func @_QMmPintrinsic_caller
+! CHECK: llvm.atomicrmw add
diff --git a/flang/test/Lower/CUDA/cuda-generic-separate-device-specific.cuf b/flang/test/Lower/CUDA/cuda-generic-separate-device-specific.cuf
new file mode 100644
index 00000000000000..288cf8e7ed9d6e
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-generic-separate-device-specific.cuf
@@ -0,0 +1,53 @@
+! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
+
+! A separate module procedure whose interface is attributes(device), global or
+! grid_global runs on the device, so its generic call may select the
+! device-only specific.
+
+module parent
+ interface sg
+ module procedure s_host
+ module procedure s_dev
+ end interface
+ interface
+ attributes(device) module subroutine caller(x)
+ real(8), device :: x
+ end subroutine
+ attributes(global) module subroutine kernel(x)
+ real(8), device :: x
+ end subroutine
+ attributes(grid_global) module subroutine gkernel(x)
+ real(8), device :: x
+ end subroutine
+ end interface
+contains
+ subroutine s_host(x)
+ real(8), intent(in) :: x
+ end subroutine
+
+ attributes(device) subroutine s_dev(x)
+ real(8), device :: x
+ end subroutine
+end module
+
+submodule(parent) implementation
+contains
+ module procedure caller
+ call sg(x)
+ end procedure
+ module procedure kernel
+ call sg(x)
+ end procedure
+ module procedure gkernel
+ call sg(x)
+ end procedure
+end submodule
+
+! CHECK-LABEL: func.func @_QMparentPcaller
+! CHECK: fir.call @_QMparentPs_dev
+
+! CHECK-LABEL: func.func @_QMparentPkernel
+! CHECK: fir.call @_QMparentPs_dev
+
+! CHECK-LABEL: func.func @_QMparentPgkernel
+! CHECK: fir.call @_QMparentPs_dev
diff --git a/flang/test/Semantics/CUDA/cuf-generic-host-device-only.cuf b/flang/test/Semantics/CUDA/cuf-generic-host-device-only.cuf
new file mode 100644
index 00000000000000..ae71d3d3887fd2
--- /dev/null
+++ b/flang/test/Semantics/CUDA/cuf-generic-host-device-only.cuf
@@ -0,0 +1,26 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1
+
+! A generic whose only matching specific is a user-defined DEVICE procedure
+! must not resolve from host code.
+
+module m
+ interface only_dev
+ module procedure s_dev
+ end interface
+ real(8), managed, allocatable :: a(:)
+contains
+ attributes(device) subroutine s_dev(x)
+ real(8), device :: x
+ end subroutine
+
+ subroutine host_caller(i)
+ integer :: i
+ !ERROR: No specific subroutine of generic 'only_dev' matches the actual arguments
+ call only_dev(a(i))
+ end subroutine
+
+ attributes(device) subroutine device_caller(x)
+ real(8), device :: x
+ call only_dev(x)
+ end subroutine
+end module
More information about the flang-commits
mailing list