[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