[flang-commits] [flang] [flang][cuda] Skip device-only specifics at host call sites in generic resolution (PR #234710)
Valentin Clement バレンタイン クレメン via flang-commits
flang-commits at lists.llvm.org
Thu Oct 22 21:57:13 PDT 2026
https://github.com/clementval updated https://github.com/llvm/llvm-project/pull/234710
>From 7ba61a696417bd6831cfc71f52c42aba8b1a1365 Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Thu, 22 Oct 2026 10:29:36 -0700
Subject: [PATCH 1/5] [flang][cuda] Skip device-only specifics at host call
sites in generic resolution
---
flang/lib/Semantics/expression.cpp | 26 +++++++++++++++
...cuda-generic-host-call-device-specific.cuf | 33 +++++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index ec7a6646fd9939..644787122ad73e 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3305,6 +3305,15 @@ 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 auto *subprogram{
+ symbol.GetUltimate().detailsIf<semantics::SubprogramDetails>()};
+ auto attrs{subprogram ? subprogram->cudaSubprogramAttrs() : std::nullopt};
+ return attrs && *attrs == common::CUDASubprogramAttrs::Device;
+}
+
static bool IsCudaDeviceIntrinsicShadowedByHostProcedure(
const parser::CharBlock &callSite, semantics::SemanticsContext &context,
const Symbol *resolution, bool isSubroutine) {
@@ -3343,6 +3352,20 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
const auto *genericDetails{ultimate.detailsIf<semantics::GenericDetails>()};
if (genericDetails) {
std::optional<CudaMatchingDistance> crtMatchingDistance;
+ // CUF kernel and OpenACC regions share Scope::Kind::OpenACCConstruct and
+ // may hold device code in a host subprogram; leave those unfiltered.
+ bool inHostCode{
+ context_.languageFeatures().IsEnabled(common::LanguageFeature::CUDA) &&
+ !GetContextualMessages().at().empty()};
+ if (inHostCode) {
+ const semantics::Scope &scope{
+ context_.FindScope(GetContextualMessages().at())};
+ inHostCode = !semantics::FindCUDADeviceContext(&scope);
+ for (const semantics::Scope *s{&scope}; inHostCode && !s->IsGlobal();
+ s = &s->parent()) {
+ inHostCode = s->kind() != semantics::Scope::Kind::OpenACCConstruct;
+ }
+ }
for (const Symbol &specific0 : genericDetails->specificProcs()) {
const Symbol &specific1{BypassGeneric(specific0)};
if (isSubroutine != !IsFunction(specific1)) {
@@ -3352,6 +3375,9 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
if (!specific) {
continue;
}
+ if (inHostCode && IsCUDADeviceOnly(*specific)) {
+ continue;
+ }
if (std::optional<characteristics::Procedure> procedure{
characteristics::Procedure::Characterize(
ProcedureDesignator{*specific}, context_.foldingContext(),
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..4ffba989ddfa99
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
@@ -0,0 +1,33 @@
+! RUN: bbc -emit-hlfir -fcuda %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(:)
+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 host_caller(i)
+ integer :: i
+ call sg(a(i), y(i))
+ end subroutine
+end module
+
+! CHECK-LABEL: func.func @_QMmPhost_caller
+! CHECK: fir.call @_QMmPs_host
+! CHECK-NOT: fir.call @_QMmPs_dev
>From 0b2c362ddbabb357cf20f391f44cab6b0380ecbe Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Thu, 22 Oct 2026 11:13:30 -0700
Subject: [PATCH 2/5] Add test for openacc routine
---
flang/lib/Semantics/expression.cpp | 13 ++++++---
...cuda-generic-host-call-device-specific.cuf | 27 +++++++++++++++++--
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 644787122ad73e..d780114892ef28 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3308,8 +3308,13 @@ static bool IsCUDADeviceCallable(const Symbol &symbol) {
// 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 auto *subprogram{
- symbol.GetUltimate().detailsIf<semantics::SubprogramDetails>()};
+ 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;
}
@@ -3360,7 +3365,9 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
if (inHostCode) {
const semantics::Scope &scope{
context_.FindScope(GetContextualMessages().at())};
- inHostCode = !semantics::FindCUDADeviceContext(&scope);
+ // An OpenACC routine body may run on the device.
+ inHostCode = !semantics::FindCUDADeviceContext(&scope) &&
+ !semantics::HasOpenACCRoutineDirective(&scope);
for (const semantics::Scope *s{&scope}; inHostCode && !s->IsGlobal();
s = &s->parent()) {
inHostCode = s->kind() != semantics::Scope::Kind::OpenACCConstruct;
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
index 4ffba989ddfa99..c9eae39e610304 100644
--- a/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
+++ b/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
@@ -1,4 +1,4 @@
-! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
+! 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.
@@ -9,6 +9,12 @@ module m
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
@@ -22,12 +28,29 @@ contains
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
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
-! CHECK-NOT: fir.call @_QMmPs_dev
>From 47e1b30cbef1df48cb7b1c30a3e5aa363021f01a Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Thu, 22 Oct 2026 11:35:31 -0700
Subject: [PATCH 3/5] add test
---
flang/lib/Semantics/expression.cpp | 41 ++++++++++++++++++++----------
1 file changed, 27 insertions(+), 14 deletions(-)
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index d780114892ef28..be85a02ad46da7 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3319,6 +3319,31 @@ static bool IsCUDADeviceOnly(const Symbol &symbol) {
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() &&
+ IsCUDADeviceCallable(*subp->moduleInterface())) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
static bool IsCudaDeviceIntrinsicShadowedByHostProcedure(
const parser::CharBlock &callSite, semantics::SemanticsContext &context,
const Symbol *resolution, bool isSubroutine) {
@@ -3357,22 +3382,10 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
const auto *genericDetails{ultimate.detailsIf<semantics::GenericDetails>()};
if (genericDetails) {
std::optional<CudaMatchingDistance> crtMatchingDistance;
- // CUF kernel and OpenACC regions share Scope::Kind::OpenACCConstruct and
- // may hold device code in a host subprogram; leave those unfiltered.
bool inHostCode{
context_.languageFeatures().IsEnabled(common::LanguageFeature::CUDA) &&
- !GetContextualMessages().at().empty()};
- if (inHostCode) {
- const semantics::Scope &scope{
- context_.FindScope(GetContextualMessages().at())};
- // An OpenACC routine body may run on the device.
- inHostCode = !semantics::FindCUDADeviceContext(&scope) &&
- !semantics::HasOpenACCRoutineDirective(&scope);
- for (const semantics::Scope *s{&scope}; inHostCode && !s->IsGlobal();
- s = &s->parent()) {
- inHostCode = s->kind() != semantics::Scope::Kind::OpenACCConstruct;
- }
- }
+ !GetContextualMessages().at().empty() &&
+ !MayRunOnCUDADevice(context_.FindScope(GetContextualMessages().at()))};
for (const Symbol &specific0 : genericDetails->specificProcs()) {
const Symbol &specific1{BypassGeneric(specific0)};
if (isSubroutine != !IsFunction(specific1)) {
>From 6954e255b1d8702438fffabd7fa75d128f27fd2c Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Thu, 22 Oct 2026 11:50:41 -0700
Subject: [PATCH 4/5] Add global grid_global
---
flang/lib/Semantics/expression.cpp | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index be85a02ad46da7..9673dda9de102d 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3335,9 +3335,16 @@ static bool MayRunOnCUDADevice(const semantics::Scope &scope) {
}
if (const Symbol *symbol{s->symbol()}) {
const auto *subp{symbol->detailsIf<semantics::SubprogramDetails>()};
- if (subp && subp->moduleInterface() &&
- IsCUDADeviceCallable(*subp->moduleInterface())) {
- return true;
+ 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;
+ }
}
}
}
>From c324d3afffb6ae7d5194fc6563eeb9fc51e1eef8 Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Thu, 22 Oct 2026 21:55:54 -0700
Subject: [PATCH 5/5] Fix test
---
flang/lib/Semantics/expression.cpp | 123 ++++++++++--------
...cuda-generic-host-call-device-specific.cuf | 11 ++
.../cuda-generic-separate-device-specific.cuf | 53 ++++++++
.../CUDA/cuf-generic-host-device-only.cuf | 26 ++++
4 files changed, 162 insertions(+), 51 deletions(-)
create mode 100644 flang/test/Lower/CUDA/cuda-generic-separate-device-specific.cuf
create mode 100644 flang/test/Semantics/CUDA/cuf-generic-host-device-only.cuf
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 9673dda9de102d..08c5500415810a 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -3351,6 +3351,12 @@ static bool MayRunOnCUDADevice(const semantics::Scope &scope) {
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) {
@@ -3393,63 +3399,78 @@ auto ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol,
context_.languageFeatures().IsEnabled(common::LanguageFeature::CUDA) &&
!GetContextualMessages().at().empty() &&
!MayRunOnCUDADevice(context_.FindScope(GetContextualMessages().at()))};
- 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 (inHostCode && IsCUDADeviceOnly(*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;
- }
+ // 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
index c9eae39e610304..8590e3a6370560 100644
--- a/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
+++ b/flang/test/Lower/CUDA/cuda-generic-host-call-device-specific.cuf
@@ -44,6 +44,12 @@ contains
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
@@ -54,3 +60,8 @@ end module
! 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