[flang-commits] [flang] [flang][cuda] Implicitly attribute allocatables/pointers under -gpu=unified (PR #209292)

Vijay Kandiah via flang-commits flang-commits at lists.llvm.org
Mon Jul 13 17:52:11 PDT 2026


https://github.com/VijayKandiah updated https://github.com/llvm/llvm-project/pull/209292

>From 0d922239d81054c502a221affa3a751d6ddb6679 Mon Sep 17 00:00:00 2001
From: Vijay Kandiah <vkandiah at nvidia.com>
Date: Mon, 13 Jul 2026 13:05:19 -0700
Subject: [PATCH 1/2] [flang][cuda] Implicitly attribute allocatables/pointers
 under -gpu=unified

---
 flang/lib/Semantics/resolve-names.cpp         | 35 +++++++++++-----
 .../CUDA/cuda-implicit-managed-alloc.cuf      | 41 +++++++++++++++++++
 2 files changed, 66 insertions(+), 10 deletions(-)
 create mode 100644 flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 996c05d0e193b..5dfcc2c622453 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10630,23 +10630,38 @@ void ResolveNamesVisitor::FinishSpecificationPart(
     if (auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
       if ((IsAllocatable(symbol) || IsPointer(symbol)) &&
           !object->cudaDataAttr()) {
+        const bool cudaEnabled{context().languageFeatures().IsEnabled(
+            common::LanguageFeature::CUDA)};
+        const bool cudaManaged{context().languageFeatures().IsEnabled(
+            common::LanguageFeature::CudaManaged)};
+        const bool cudaUnified{context().languageFeatures().IsEnabled(
+            common::LanguageFeature::CudaUnified)};
         // Implicitly treat allocatable/pointer arrays as managed when feature
         // is enabled. This is done after all explicit CUDA attributes have
         // been processed. Only applies when CUDA Fortran is enabled; otherwise
         // -gpu=mem:managed on a non-CUDA-Fortran translation unit (e.g. pure
         // OpenACC) would incorrectly route every allocatable through the CUDA
-        // Fortran managed descriptor pipeline.
-        if (context().languageFeatures().IsEnabled(
-                common::LanguageFeature::CudaManaged) &&
+        // Fortran managed descriptor pipeline. Under -gpu=mem:unified prefer
+        // the Unified attribute where it is legal (host subprogram, main
+        // program, or component) so generic resolution still selects the
+        // unified specific; fall back to Managed elsewhere (module scope,
+        // device subprograms), which uses the same allocator.
+        if (cudaEnabled && (cudaManaged || cudaUnified)) {
+          const Scope &owner{symbol.owner()};
+          const bool unifiedAllowed{!IsCUDADeviceContext(&owner) &&
+              (owner.IsDerivedType() ||
+                  owner.kind() == Scope::Kind::MainProgram ||
+                  owner.kind() == Scope::Kind::Subprogram)};
+          object->set_cudaDataAttr(cudaUnified && unifiedAllowed
+                  ? common::CUDADataAttr::Unified
+                  : common::CUDADataAttr::Managed);
+          // Implicitly treat allocatable arrays as pinned when feature is
+          // enabled.
+        } else if (IsAllocatable(symbol) &&
             context().languageFeatures().IsEnabled(
-                common::LanguageFeature::CUDA))
-          object->set_cudaDataAttr(common::CUDADataAttr::Managed);
-        // Implicitly treat allocatable arrays as pinned when feature is
-        // enabled.
-        else if (IsAllocatable(symbol) &&
-            context().languageFeatures().IsEnabled(
-                common::LanguageFeature::CudaPinned))
+                common::LanguageFeature::CudaPinned)) {
           object->set_cudaDataAttr(common::CUDADataAttr::Pinned);
+        }
       }
     }
   }
diff --git a/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf b/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf
new file mode 100644
index 0000000000000..4e052ad839970
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf
@@ -0,0 +1,41 @@
+! RUN: bbc -emit-hlfir -fcuda -gpu=unified %s -o - | FileCheck %s --check-prefix=UNI
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %s -o - | FileCheck %s --check-prefix=MAN
+
+! Under -gpu=unified and -gpu=managed, allocatables and pointers with no
+! explicit CUDA data attribute are implicitly attributed. -gpu=unified prefers
+! the Unified attribute where ATTRIBUTES(UNIFIED) is legal (host subprogram /
+! main program); module-scope objects fall back to Managed, which carries no
+! such scope restriction.
+
+module m
+  real, allocatable :: mod_arr(:)
+  real, pointer :: mod_ptr(:)
+contains
+  subroutine mod_sub()
+    real, allocatable :: loc_arr(:)
+    real, pointer :: loc_ptr(:)
+  end subroutine
+end module
+
+program p
+  real, allocatable :: prog_arr(:)
+end program
+
+! Host-subprogram locals: Unified under -gpu=unified, Managed under
+! -gpu=managed.
+! UNI: hlfir.declare {{.*}} {data_attr = #cuf.cuda<unified>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QMmFmod_subEloc_arr"}
+! UNI: hlfir.declare {{.*}} {data_attr = #cuf.cuda<unified>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMmFmod_subEloc_ptr"}
+! MAN: hlfir.declare {{.*}} {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QMmFmod_subEloc_arr"}
+! MAN: hlfir.declare {{.*}} {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMmFmod_subEloc_ptr"}
+
+! Main-program local: Unified under -gpu=unified, Managed under
+! -gpu=managed.
+! UNI: hlfir.declare {{.*}} {data_attr = #cuf.cuda<unified>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFEprog_arr"}
+! MAN: hlfir.declare {{.*}} {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFEprog_arr"}
+
+! Module-scope globals: Managed in both modes (ATTRIBUTES(UNIFIED) is not legal
+! at module scope, so the Unified case falls back to Managed).
+! UNI: fir.global @_QMmEmod_arr {data_attr = #cuf.cuda<managed>}
+! UNI: fir.global @_QMmEmod_ptr {data_attr = #cuf.cuda<managed>}
+! MAN: fir.global @_QMmEmod_arr {data_attr = #cuf.cuda<managed>}
+! MAN: fir.global @_QMmEmod_ptr {data_attr = #cuf.cuda<managed>}

>From f28e21e412d8ae11dbe69720d38a86d029d3f342 Mon Sep 17 00:00:00 2001
From: Vijay Kandiah <vkandiah at nvidia.com>
Date: Mon, 13 Jul 2026 17:51:59 -0700
Subject: [PATCH 2/2] [flang][cuda] Extend implicit -gpu=unified attr to
 derived-type components

---
 flang/lib/Semantics/resolve-names.cpp         | 88 +++++++++++--------
 .../CUDA/cuda-implicit-managed-alloc.cuf      | 19 +++-
 2 files changed, 67 insertions(+), 40 deletions(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 5dfcc2c622453..279ce2546087c 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -1124,6 +1124,7 @@ class DeclarationVisitor : public ArraySpecVisitor,
 protected:
   bool BeginDecl();
   void EndDecl();
+  void SetImplicitCUDADataAttr(Symbol &);
   Symbol &DeclareObjectEntity(const parser::Name &, Attrs = Attrs{});
   // Make sure that there's an entity in an enclosing scope called Name
   Symbol &FindOrDeclareEnclosingEntity(const parser::Name &);
@@ -7125,6 +7126,11 @@ bool DeclarationVisitor::Pre(const parser::DerivedTypeDef &x) {
   const auto &componentDefs{
       std::get<std::list<parser::Statement<parser::ComponentDefStmt>>>(x.t)};
   Walk(componentDefs);
+  // Components live in the derived type's own scope, which is not visited when
+  // the enclosing specification part is finished, so attribute them here.
+  for (auto &pair : scope) {
+    SetImplicitCUDADataAttr(*pair.second);
+  }
   if (derivedTypeInfo_.sequence) {
     details.set_sequence(true);
     if (componentDefs.empty()) {
@@ -10584,6 +10590,50 @@ void ResolveNamesVisitor::CreateGeneric(const parser::GenericSpec &x) {
   info.Resolve(&MakeSymbol(symbolName, Attrs{}, std::move(genericDetails)));
 }
 
+// Applies the implicit CUDA data attribute (managed/unified/pinned) to an
+// allocatable or pointer object entity when the matching -gpu=mem: mode is
+// enabled. Used for locals and module variables as well as derived-type
+// components.
+void DeclarationVisitor::SetImplicitCUDADataAttr(Symbol &symbol) {
+  auto *object{symbol.detailsIf<ObjectEntityDetails>()};
+  if (!object || object->cudaDataAttr() ||
+      !(IsAllocatable(symbol) || IsPointer(symbol))) {
+    return;
+  }
+  const bool cudaEnabled{
+      context().languageFeatures().IsEnabled(common::LanguageFeature::CUDA)};
+  const bool cudaManaged{context().languageFeatures().IsEnabled(
+      common::LanguageFeature::CudaManaged)};
+  const bool cudaUnified{context().languageFeatures().IsEnabled(
+      common::LanguageFeature::CudaUnified)};
+  // Implicitly treat allocatable/pointer arrays as managed when feature
+  // is enabled. This is done after all explicit CUDA attributes have
+  // been processed. Only applies when CUDA Fortran is enabled; otherwise
+  // -gpu=mem:managed on a non-CUDA-Fortran translation unit (e.g. pure
+  // OpenACC) would incorrectly route every allocatable through the CUDA
+  // Fortran managed descriptor pipeline. Under -gpu=mem:unified prefer
+  // the Unified attribute where it is legal (host subprogram, main
+  // program, or component) so generic resolution still selects the
+  // unified specific; fall back to Managed elsewhere (module scope,
+  // device subprograms), which uses the same allocator.
+  if (cudaEnabled && (cudaManaged || cudaUnified)) {
+    const Scope &owner{symbol.owner()};
+    const bool unifiedAllowed{!IsCUDADeviceContext(&owner) &&
+        (owner.IsDerivedType() ||
+            owner.kind() == Scope::Kind::MainProgram ||
+            owner.kind() == Scope::Kind::Subprogram)};
+    object->set_cudaDataAttr(cudaUnified && unifiedAllowed
+            ? common::CUDADataAttr::Unified
+            : common::CUDADataAttr::Managed);
+    // Implicitly treat allocatable arrays as pinned when feature is
+    // enabled.
+  } else if (IsAllocatable(symbol) &&
+      context().languageFeatures().IsEnabled(
+          common::LanguageFeature::CudaPinned)) {
+    object->set_cudaDataAttr(common::CUDADataAttr::Pinned);
+  }
+}
+
 void ResolveNamesVisitor::FinishSpecificationPart(
     const std::list<parser::DeclarationConstruct> &decls) {
   misparsedStmtFuncFound_ = false;
@@ -10627,43 +10677,7 @@ void ResolveNamesVisitor::FinishSpecificationPart(
       }
     }
 
-    if (auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
-      if ((IsAllocatable(symbol) || IsPointer(symbol)) &&
-          !object->cudaDataAttr()) {
-        const bool cudaEnabled{context().languageFeatures().IsEnabled(
-            common::LanguageFeature::CUDA)};
-        const bool cudaManaged{context().languageFeatures().IsEnabled(
-            common::LanguageFeature::CudaManaged)};
-        const bool cudaUnified{context().languageFeatures().IsEnabled(
-            common::LanguageFeature::CudaUnified)};
-        // Implicitly treat allocatable/pointer arrays as managed when feature
-        // is enabled. This is done after all explicit CUDA attributes have
-        // been processed. Only applies when CUDA Fortran is enabled; otherwise
-        // -gpu=mem:managed on a non-CUDA-Fortran translation unit (e.g. pure
-        // OpenACC) would incorrectly route every allocatable through the CUDA
-        // Fortran managed descriptor pipeline. Under -gpu=mem:unified prefer
-        // the Unified attribute where it is legal (host subprogram, main
-        // program, or component) so generic resolution still selects the
-        // unified specific; fall back to Managed elsewhere (module scope,
-        // device subprograms), which uses the same allocator.
-        if (cudaEnabled && (cudaManaged || cudaUnified)) {
-          const Scope &owner{symbol.owner()};
-          const bool unifiedAllowed{!IsCUDADeviceContext(&owner) &&
-              (owner.IsDerivedType() ||
-                  owner.kind() == Scope::Kind::MainProgram ||
-                  owner.kind() == Scope::Kind::Subprogram)};
-          object->set_cudaDataAttr(cudaUnified && unifiedAllowed
-                  ? common::CUDADataAttr::Unified
-                  : common::CUDADataAttr::Managed);
-          // Implicitly treat allocatable arrays as pinned when feature is
-          // enabled.
-        } else if (IsAllocatable(symbol) &&
-            context().languageFeatures().IsEnabled(
-                common::LanguageFeature::CudaPinned)) {
-          object->set_cudaDataAttr(common::CUDADataAttr::Pinned);
-        }
-      }
-    }
+    SetImplicitCUDADataAttr(symbol);
   }
   currScope().InstantiateDerivedTypes();
   for (const auto &decl : decls) {
diff --git a/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf b/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf
index 4e052ad839970..ec75078527c50 100644
--- a/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf
+++ b/flang/test/Lower/CUDA/cuda-implicit-managed-alloc.cuf
@@ -1,15 +1,21 @@
 ! RUN: bbc -emit-hlfir -fcuda -gpu=unified %s -o - | FileCheck %s --check-prefix=UNI
 ! RUN: bbc -emit-hlfir -fcuda -gpu=managed %s -o - | FileCheck %s --check-prefix=MAN
+! RUN: bbc -fcuda -gpu=unified --dump-symbols %s 2>&1 | FileCheck %s --check-prefix=UNI-SYM
+! RUN: bbc -fcuda -gpu=managed --dump-symbols %s 2>&1 | FileCheck %s --check-prefix=MAN-SYM
 
 ! Under -gpu=unified and -gpu=managed, allocatables and pointers with no
 ! explicit CUDA data attribute are implicitly attributed. -gpu=unified prefers
-! the Unified attribute where ATTRIBUTES(UNIFIED) is legal (host subprogram /
-! main program); module-scope objects fall back to Managed, which carries no
-! such scope restriction.
+! the Unified attribute where ATTRIBUTES(UNIFIED) is legal (host subprogram,
+! main program, or derived-type component); module-scope objects fall back to
+! Managed, which carries no such scope restriction.
 
 module m
   real, allocatable :: mod_arr(:)
   real, pointer :: mod_ptr(:)
+  type :: t
+    real, allocatable :: comp_arr(:)
+    real, pointer :: comp_ptr(:)
+  end type
 contains
   subroutine mod_sub()
     real, allocatable :: loc_arr(:)
@@ -39,3 +45,10 @@ end program
 ! UNI: fir.global @_QMmEmod_ptr {data_attr = #cuf.cuda<managed>}
 ! MAN: fir.global @_QMmEmod_arr {data_attr = #cuf.cuda<managed>}
 ! MAN: fir.global @_QMmEmod_ptr {data_attr = #cuf.cuda<managed>}
+
+! Derived-type components: Unified under -gpu=unified, Managed under
+! -gpu=managed. Checked via the symbol table.
+! UNI-SYM: comp_arr, ALLOCATABLE {{.*}} cudaDataAttr: Unified
+! UNI-SYM: comp_ptr, POINTER {{.*}} cudaDataAttr: Unified
+! MAN-SYM: comp_arr, ALLOCATABLE {{.*}} cudaDataAttr: Managed
+! MAN-SYM: comp_ptr, POINTER {{.*}} cudaDataAttr: Managed



More information about the flang-commits mailing list