[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