[flang-commits] [flang] [flang][cuda] Limit mem:managed generic matching to allocatable and p… (PR #223801)

Valentin Clement バレンタイン クレメン via flang-commits flang-commits at lists.llvm.org
Tue Sep 15 12:48:48 PDT 2026


https://github.com/clementval created https://github.com/llvm/llvm-project/pull/223801

…ointer

managed memory mode only puts allocatable and pointer allocations in managed memory. Static and automatic host objects stay host-resident, so they must not match a device/managed/unified dummy during generic resolution.

Keep the -gpu=mem:unified relaxation for all host variables, which are device-accessible in that mode.

This stops host calls such as sum(host_array(:)) from resolving to a CUDA Fortran device-dummy specific.

>From 68847c9e5992fbc4af74cf9d276ae94fd0201cf9 Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Tue, 15 Sep 2026 12:46:17 -0700
Subject: [PATCH] [flang][cuda] Limit mem:managed generic matching to
 allocatable and pointer

managed memory mode only puts allocatable and pointer allocations in
managed memory. Static and automatic host objects stay host-resident, so
they must not match a device/managed/unified dummy during generic
resolution.

Keep the -gpu=mem:unified relaxation for all host variables, which are
device-accessible in that mode.

This stops host calls such as sum(host_array(:)) from resolving to a
CUDA Fortran device-dummy specific.
---
 flang/include/flang/Support/Fortran.h         |  2 +-
 flang/lib/Semantics/check-call.cpp            | 12 +++-
 flang/lib/Semantics/expression.cpp            | 11 ++-
 flang/lib/Support/Fortran.cpp                 | 18 +++--
 .../CUDA/cuf-matching-managed-static.cuf      | 69 +++++++++++++++++++
 5 files changed, 103 insertions(+), 9 deletions(-)
 create mode 100644 flang/test/Semantics/CUDA/cuf-matching-managed-static.cuf

diff --git a/flang/include/flang/Support/Fortran.h b/flang/include/flang/Support/Fortran.h
index 95290e7a36b203..315924e5bd9ab9 100644
--- a/flang/include/flang/Support/Fortran.h
+++ b/flang/include/flang/Support/Fortran.h
@@ -105,7 +105,7 @@ bool AreCompatibleCUDADataAttrs(std::optional<CUDADataAttr>,
     std::optional<CUDADataAttr>, IgnoreTKRSet, bool allowUnifiedMatchingRule,
     bool isHostDeviceProcedure,
     const LanguageFeatureControl *features = nullptr,
-    bool actualIsVariable = true);
+    bool actualIsVariable = true, bool actualIsAllocatableOrPointer = false);
 
 // Format vector type as Fortran string
 std::string FormatVectorTypeAsFortran(
diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp
index 784ca0468a487f..2fd7ff5ac1a5c9 100644
--- a/flang/lib/Semantics/check-call.cpp
+++ b/flang/lib/Semantics/check-call.cpp
@@ -1211,13 +1211,23 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
     bool isHostDeviceProc{procedure.cudaSubprogramAttrs &&
         *procedure.cudaSubprogramAttrs ==
             common::CUDASubprogramAttrs::HostDevice};
+    bool actualIsAllocatableOrPointer{false};
+    if (actualIsVariable) {
+      for (const Symbol &s : evaluate::GetSymbolVector(actual)) {
+        if (IsAllocatableOrPointer(ResolveAssociations(s))) {
+          actualIsAllocatableOrPointer = true;
+          break;
+        }
+      }
+    }
     // TYPE(*) assumed-size/rank dummies are opaque buffers (e.g. MPI) and do
     // not impose a CUDA address space on their actual argument.
     bool skipCudaDataAttrCheck{IsCUDAAddressSpaceAgnostic(dummy)};
     if (!skipCudaDataAttrCheck &&
         !common::AreCompatibleCUDADataAttrs(dummyDataAttr, actualDataAttr,
             dummy.ignoreTKR, /*allowUnifiedMatchingRule=*/true,
-            isHostDeviceProc, &context.languageFeatures(), actualIsVariable)) {
+            isHostDeviceProc, &context.languageFeatures(), actualIsVariable,
+            actualIsAllocatableOrPointer)) {
       auto toStr{[](std::optional<common::CUDADataAttr> x) {
         return x ? "ATTRIBUTES("s +
                 parser::ToUpperCaseLetters(common::EnumToString(*x)) + ")"s
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 91e98d5c8018c0..deead9391e9f52 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -2983,17 +2983,22 @@ static int GetMatchingDistance(const common::LanguageFeatureControl &features,
 
   std::optional<common::CUDADataAttr> actualDataAttr, dummyDataAttr;
   // True when an unattributed actual may use the implicit CUDA memory mode
-  // matching enabled by -gpu=mem:unified or -gpu=mem:managed.
+  // matching enabled by -gpu=mem:unified (any variable) or -gpu=mem:managed
+  // (allocatable/pointer objects only).
   bool actualCanUseImplicitCudaMemoryMode{false};
   if (actual) {
     if (auto *expr{actual->UnwrapExpr()}) {
       if (evaluate::IsVariable(*expr)) {
-        actualCanUseImplicitCudaMemoryMode = true;
+        bool actualIsAllocatableOrPointer{false};
         // Match check-call.cpp: walk the whole designator so e.g. b%a picks up
         // ATTRIBUTES(DEVICE) from the base b when the component a has no CUDA
         // attribute (OpenACC use_device(b) + doit(b%a)), not only from the
         // last symbol (GetLastSymbol would only see a).
         for (const Symbol &s : evaluate::GetSymbolVector(*expr)) {
+          if (semantics::IsAllocatableOrPointer(
+                  semantics::ResolveAssociations(s))) {
+            actualIsAllocatableOrPointer = true;
+          }
           if (const auto *object{
                   s.detailsIf<semantics::ObjectEntityDetails>()}) {
             if (auto cudaAttr{object->cudaDataAttr()}) {
@@ -3001,6 +3006,8 @@ static int GetMatchingDistance(const common::LanguageFeatureControl &features,
             }
           }
         }
+        actualCanUseImplicitCudaMemoryMode =
+            isCudaUnified || (isCudaManaged && actualIsAllocatableOrPointer);
       } else if (const auto *actualLastSymbol{evaluate::GetLastSymbol(*expr)}) {
         // Propagate any explicit CUDA data attribute from the referenced
         // symbol (e.g. a device array operand inside RESHAPE()) so that
diff --git a/flang/lib/Support/Fortran.cpp b/flang/lib/Support/Fortran.cpp
index 80243d2b10b228..5b9acd3256afd2 100644
--- a/flang/lib/Support/Fortran.cpp
+++ b/flang/lib/Support/Fortran.cpp
@@ -108,13 +108,19 @@ std::string AsFortran(IgnoreTKRSet tkr) {
 bool AreCompatibleCUDADataAttrs(std::optional<CUDADataAttr> x,
     std::optional<CUDADataAttr> y, IgnoreTKRSet ignoreTKR,
     bool allowUnifiedMatchingRule, bool isHostDeviceProcedure,
-    const LanguageFeatureControl *features, bool actualIsVariable) {
+    const LanguageFeatureControl *features, bool actualIsVariable,
+    bool actualIsAllocatableOrPointer) {
   bool isCudaManaged{features
           ? features->IsEnabled(common::LanguageFeature::CudaManaged)
           : false};
   bool isCudaUnified{features
           ? features->IsEnabled(common::LanguageFeature::CudaUnified)
           : false};
+  // -gpu=mem:unified makes ordinary host variables device-accessible.
+  // -gpu=mem:managed only puts allocatable/pointer allocations in managed
+  // memory; static and automatic host objects remain host-resident.
+  bool actualHasImplicitCudaMemory{actualIsVariable &&
+      (isCudaUnified || (isCudaManaged && actualIsAllocatableOrPointer))};
   if (ignoreTKR.test(common::IgnoreTKR::Device)) {
     return true;
   }
@@ -163,18 +169,20 @@ bool AreCompatibleCUDADataAttrs(std::optional<CUDADataAttr> x,
         // Non-variable actuals (expression results, intrinsic call results)
         // are host temporaries whose storage is not accessible from device
         // code even under unified memory, so the relaxation does not apply.
-        if (!y && (isCudaUnified || isCudaManaged) &&
-            !ignoreTKR.test(IgnoreTKR::Managed) && actualIsVariable) {
+        // Under -gpu=mem:managed, only allocatable/pointer actuals have
+        // implicit managed storage.
+        if (!y && actualHasImplicitCudaMemory &&
+            !ignoreTKR.test(IgnoreTKR::Managed)) {
           return true;
         }
       } else if (*x == CUDADataAttr::Managed) {
         if ((y && *y == CUDADataAttr::Unified) ||
-            (!y && (isCudaUnified || isCudaManaged) && actualIsVariable)) {
+            (!y && actualHasImplicitCudaMemory)) {
           return true;
         }
       } else if (*x == CUDADataAttr::Unified) {
         if ((y && *y == CUDADataAttr::Managed) ||
-            (!y && (isCudaUnified || isCudaManaged) && actualIsVariable)) {
+            (!y && actualHasImplicitCudaMemory)) {
           return true;
         }
       }
diff --git a/flang/test/Semantics/CUDA/cuf-matching-managed-static.cuf b/flang/test/Semantics/CUDA/cuf-matching-managed-static.cuf
new file mode 100644
index 00000000000000..1daf5c6d11f355
--- /dev/null
+++ b/flang/test/Semantics/CUDA/cuf-matching-managed-static.cuf
@@ -0,0 +1,69 @@
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %s -o - | FileCheck %s --check-prefix=MANAGED
+! RUN: bbc -emit-hlfir -fcuda -gpu=unified %s -o - | FileCheck %s --check-prefix=UNIFIED
+
+! Under -gpu=mem:managed, only allocatable and pointer objects are
+! implicitly managed. A static host array (and a section of one) must
+! keep matching the host specific, not a device, managed, or unified dummy.
+! Under -gpu=mem:unified, every host variable is device-accessible, so the
+! same static array matches the unified specific.
+
+module m
+  integer :: global_host_static(2)
+  integer, allocatable :: global_host_alloc(:)
+  integer, pointer :: global_host_ptr(:)
+  interface gen
+    module procedure sub_host
+    module procedure sub_device
+    module procedure sub_managed
+    module procedure sub_unified
+  end interface
+contains
+  subroutine sub_host(x)
+    integer :: x(:)
+  end subroutine
+  subroutine sub_device(x)
+    integer, device :: x(:)
+  end subroutine
+  subroutine sub_managed(x)
+    integer, managed :: x(:)
+  end subroutine
+  subroutine sub_unified(x)
+    integer, unified :: x(:)
+  end subroutine
+end module
+
+subroutine driver
+  use m
+  integer :: host_static(2)
+  integer, allocatable :: host_alloc(:)
+  integer, pointer :: host_ptr(:)
+  allocate(host_alloc(2), host_ptr(2))
+
+  call gen(host_static)
+  call gen(host_static(:))
+  call gen(host_alloc)
+  call gen(host_alloc(:))
+  call gen(host_ptr)
+  call gen(global_host_static)
+  call gen(global_host_alloc)
+  call gen(global_host_ptr)
+end subroutine
+
+! MANAGED-LABEL: func.func @_QPdriver
+! MANAGED: fir.call @_QMmPsub_host
+! MANAGED: fir.call @_QMmPsub_host
+! MANAGED: fir.call @_QMmPsub_managed
+! MANAGED: fir.call @_QMmPsub_managed
+! MANAGED: fir.call @_QMmPsub_managed
+! MANAGED: fir.call @_QMmPsub_host
+! MANAGED: fir.call @_QMmPsub_managed
+! MANAGED: fir.call @_QMmPsub_managed
+
+! UNIFIED-LABEL: func.func @_QPdriver
+! UNIFIED: fir.call @_QMmPsub_unified
+! UNIFIED: fir.call @_QMmPsub_unified
+! UNIFIED: fir.call @_QMmPsub_unified
+! UNIFIED: fir.call @_QMmPsub_unified
+! UNIFIED: fir.call @_QMmPsub_unified
+
+



More information about the flang-commits mailing list