[flang-commits] [flang] c1324c4 - [flang][cuda] Implicitly attribute ALLOCATABLE/POINTER components as managed (#223087)

via flang-commits flang-commits at lists.llvm.org
Sun Sep 20 22:36:47 PDT 2026


Author: Kareem Ergawy
Date: 2026-09-21T07:36:39+02:00
New Revision: c1324c4b3404119e3128874351eebb3400c2b5c0

URL: https://github.com/llvm/llvm-project/commit/c1324c4b3404119e3128874351eebb3400c2b5c0
DIFF: https://github.com/llvm/llvm-project/commit/c1324c4b3404119e3128874351eebb3400c2b5c0.diff

LOG: [flang][cuda] Implicitly attribute ALLOCATABLE/POINTER components as managed (#223087)

Under `-gpu=mem:managed`, `resolve-names` implicitly attributes
allocatables and
pointers declared in an ordinary scope as managed.

Apply the same attribution to components in `Post(ComponentDecl)`. An
explicitly
attributed component keeps its own attribute, and a translation unit
without
CUDA Fortran enabled is left alone.

Added: 
    flang/test/Lower/CUDA/cuda-gpu-managed-components-order.cuf
    flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf

Modified: 
    flang/include/flang/Semantics/symbol.h
    flang/lib/Lower/Allocatable.cpp
    flang/lib/Semantics/check-allocate.cpp
    flang/lib/Semantics/resolve-names.cpp
    flang/lib/Semantics/tools.cpp

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 6408ec6f435e8..35838b74dd53e 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -467,6 +467,10 @@ class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
   void set_cudaDataAttr(std::optional<common::CUDADataAttr> attr) {
     cudaDataAttr_ = attr;
   }
+  bool cudaDataAttrIsImplicit() const { return cudaDataAttrIsImplicit_; }
+  void set_cudaDataAttrIsImplicit(bool yes = true) {
+    cudaDataAttrIsImplicit_ = yes;
+  }
   // Specification expressions from the bounds of a zero-size explicit-shape
   // bounds array (F2023).  The entity is scalar, so these bounds are not
   // part of shape(), but they are still specification expressions that must be
@@ -487,6 +491,8 @@ class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
   common::IgnoreTKRSet ignoreTKR_;
   const Symbol *commonBlock_{nullptr}; // common block this object is in
   std::optional<common::CUDADataAttr> cudaDataAttr_;
+  bool cudaDataAttrIsImplicit_{false}; // Tracks whether cudaDataAttr_ was
+                                       // applied implicitly by the compiler
   friend llvm::raw_ostream &operator<<(
       llvm::raw_ostream &, const ObjectEntityDetails &);
 };

diff  --git a/flang/lib/Lower/Allocatable.cpp b/flang/lib/Lower/Allocatable.cpp
index 5f10319ab0101..982a0b4401dd0 100644
--- a/flang/lib/Lower/Allocatable.cpp
+++ b/flang/lib/Lower/Allocatable.cpp
@@ -525,6 +525,21 @@ class AllocateStmtHelper {
     if (!isCudaAllocate)
       isCudaAllocate = propagateCUDAAttrsFromParent(alloc, cudaSymForAlloc);
 
+    const auto *details{
+        alloc.getSymbol()
+            .GetUltimate()
+            .detailsIf<Fortran::semantics::ObjectEntityDetails>()};
+    const bool attrIsImplicit{details && details->cudaDataAttrIsImplicit()};
+
+    // An enclosing object that did ask for a memory space of its own takes
+    // precedence over implicit an attribute.
+    if (attrIsImplicit) {
+      // The result is deliberately unused: reaching here means the object
+      // carries an attribute, so isCudaAllocate is already true. Only the
+      // symbol the allocator is taken from needs to change.
+      propagateCUDAAttrsFromParent(alloc, cudaSymForAlloc);
+    }
+
     bool isCudaDeviceContext = cuf::isCUDADeviceContext(builder.getRegion());
     unsigned allocatorIdx = Fortran::lower::getAllocatorIdx(*cudaSymForAlloc);
 
@@ -1102,10 +1117,16 @@ void Fortran::lower::genDeallocateStmt(
               Fortran::lower::getTypeDescAddr(converter, loc, *derivedTypeSpec);
         }
     }
-    // ALLOCATE gives the object's own attribute precedence over the parent's;
-    // both sides must match or the allocators 
diff er.
+
+    const auto *details{
+        symbol.GetUltimate()
+            .detailsIf<Fortran::semantics::ObjectEntityDetails>()};
+    // An enclosing object that did ask for a memory space of its own takes
+    // precedence over implicit an attribute.
+    const bool attrIsImplicit{details && details->cudaDataAttrIsImplicit()};
+
     const Fortran::semantics::Symbol *cudaSymbol = nullptr;
-    if (!Fortran::semantics::HasCUDAAttr(symbol) &&
+    if ((!Fortran::semantics::HasCUDAAttr(symbol) || attrIsImplicit) &&
         !Fortran::semantics::HasCUDAComponent(symbol))
       cudaSymbol = getCUDAAttrParentSymbol(allocateObject);
     mlir::Value beginOpValue =

diff  --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp
index 4bbc652f6f9cd..e797b978c4bd3 100644
--- a/flang/lib/Semantics/check-allocate.cpp
+++ b/flang/lib/Semantics/check-allocate.cpp
@@ -724,7 +724,11 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) {
     std::optional<common::CUDADataAttr> attr{
         details ? details->cudaDataAttr() : std::nullopt};
     const parser::Name &base{parser::GetFirstName(*component)};
-    if (attr && base.symbol && IsCUDADevice(*base.symbol)) {
+    // An attribute the compiler applied implicitly is not a user requirement,
+    // so there is no conflict to report: the memory space the user did ask
+    // for takes precedence over it.
+    const bool attrIsImplicit{details && details->cudaDataAttrIsImplicit()};
+    if (attr && !attrIsImplicit && base.symbol && IsCUDADevice(*base.symbol)) {
       if (*attr == common::CUDADataAttr::Pinned ||
           *attr == common::CUDADataAttr::Managed ||
           *attr == common::CUDADataAttr::Unified) {

diff  --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 20a098d9f0732..c5f7fba49fffb 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -7601,6 +7601,20 @@ void DeclarationVisitor::Post(const parser::ComponentDecl &x) {
   if (OkToAddComponent(name)) {
     auto &symbol{DeclareObjectEntity(name, attrs)};
     SetCUDADataAttr(name.source, symbol, cudaDataAttr());
+
+    // Implicitely attribute allocatable/pointer components with `managed`
+    // memory if CUDA and `-gpu=mem:managed` are enabled.
+    if (auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
+      if ((IsAllocatable(symbol) || IsPointer(symbol)) &&
+          !object->cudaDataAttr() &&
+          context().languageFeatures().IsEnabled(
+              common::LanguageFeature::CUDA) &&
+          context().languageFeatures().IsEnabled(
+              common::LanguageFeature::CudaManaged)) {
+        object->set_cudaDataAttr(common::CUDADataAttr::Managed);
+        object->set_cudaDataAttrIsImplicit();
+      }
+    }
     if (symbol.has<ObjectEntityDetails>()) {
       if (auto &init{std::get<std::optional<parser::Initialization>>(x.t)}) {
         Initialization(name, *init, /*inComponentDecl=*/true);

diff  --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index 64e76aa02fe6d..ac74a041ed2e3 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -1203,7 +1203,33 @@ std::optional<common::CUDADataAttr> GetCUDADataAttr(const Symbol *symbol) {
     const Fortran::semantics::DerivedTypeSpec *derived{
         type ? type->AsDerived() : nullptr};
     if (derived) {
-      if (FindCUDADeviceAllocatableUltimateComponent(*derived)) {
+      // Examine every device-allocatable ultimate component, not just the
+      // first one: whether the object has to be relocated depends on all of
+      // them, so stopping at the first would make the answer depend on the
+      // order the components happen to be declared in.
+      bool anyDeviceAllocatable{false};
+      bool anyExplicit{false};
+      UltimateComponentIterator ultimates{*derived};
+      for (const Symbol &comp : ultimates) {
+        if (IsDeviceAllocatable(comp)) {
+          anyDeviceAllocatable = true;
+          const auto *compDetails{comp.detailsIf<ObjectEntityDetails>()};
+          if (!compDetails || !compDetails->cudaDataAttrIsImplicit()) {
+            anyExplicit = true;
+            break;
+          }
+        }
+      }
+      if (anyDeviceAllocatable) {
+        // The compiler applied every one of those attributes, not the user, so
+        // the memory space the user did ask for on the object takes precedence
+        // over them.
+        if (details->cudaDataAttr() && !anyExplicit) {
+          return details->cudaDataAttr();
+        }
+        // A component the user did attribute keeps the existing behavior: the
+        // object is placed in managed memory so that the component's
+        // descriptors stay addressable.
         return common::CUDADataAttr::Managed;
       }
     }

diff  --git a/flang/test/Lower/CUDA/cuda-gpu-managed-components-order.cuf b/flang/test/Lower/CUDA/cuda-gpu-managed-components-order.cuf
new file mode 100644
index 0000000000000..1994cc471b71c
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-gpu-managed-components-order.cuf
@@ -0,0 +1,44 @@
+! Two derived types that each combine a MANAGED component with a DEVICE one,
+! 
diff ering only in whether the user attributed the MANAGED component and in
+! the order the two components are declared. The same CHECK lines are applied
+! to both, so they pass only if the compiler treats the two identically.
+
+! RUN: rm -rf %t && split-file %s %t
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %t/implicit_managed_first.cuf -o - \
+! RUN:   | FileCheck %s
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %t/explicit_managed_last.cuf -o - \
+! RUN:   | FileCheck %s
+
+!--- implicit_managed_first.cuf
+subroutine s()
+  type :: t
+     real, allocatable :: m(:)           ! attributed by the compiler
+     real, allocatable, device :: d(:)
+  end type
+  type(t), managed :: obj
+  allocate(obj%m(100))
+  allocate(obj%d(100))
+end subroutine
+
+!--- explicit_managed_last.cuf
+subroutine s()
+  type :: t
+     real, allocatable, device :: d(:)
+     real, allocatable, managed :: m(:)  ! attributed by the user
+  end type
+  type(t), managed :: obj
+  allocate(obj%m(100))
+  allocate(obj%d(100))
+end subroutine
+
+! A component the user attributed relocates the object, whichever order the
+! components are declared in.
+! CHECK-LABEL: func.func @_QPs()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "obj", data_attr = #cuf.cuda<managed>
+
+! Each component is then allocated in its own memory space: the MANAGED one
+! through the managed allocator, the DEVICE one through the device allocator.
+! CHECK: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+! CHECK: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32

diff  --git a/flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf b/flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf
new file mode 100644
index 0000000000000..72a330a3e429b
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf
@@ -0,0 +1,203 @@
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %s -o - | FileCheck %s
+
+! Under -gpu=managed, ALLOCATABLE and POINTER components of derived types are
+! implicitly attributed as managed, exactly as allocatables and pointers
+! declared in an ordinary scope are (see cuda-gpu-managed.cuf).
+
+module mod_derived
+  type :: t
+     real, allocatable :: alc(:)
+     real, pointer :: ptr(:) => null()
+     real :: fixed(4)
+  end type t
+  type :: outer
+     type(t) :: inner
+  end type outer
+  type(t) :: mod_obj
+end module
+
+! -----------------------------------------------------------------------------
+! ALLOCATABLE component of a local derived-type object
+! -----------------------------------------------------------------------------
+subroutine test_component_local()
+  use mod_derived
+  type(t) :: obj
+  allocate(obj%alc(100))
+  deallocate(obj%alc)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_local()
+! CHECK: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+
+! -----------------------------------------------------------------------------
+! POINTER component of a local derived-type object
+! -----------------------------------------------------------------------------
+subroutine test_component_pointer()
+  use mod_derived
+  type(t) :: obj
+  allocate(obj%ptr(100))
+  deallocate(obj%ptr)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_pointer()
+! CHECK: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>, pointer} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<managed>, pointer} -> i32
+
+! -----------------------------------------------------------------------------
+! ALLOCATABLE component of a module-scope derived-type object
+! -----------------------------------------------------------------------------
+subroutine test_component_module()
+  use mod_derived
+  allocate(mod_obj%alc(100))
+  deallocate(mod_obj%alc)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_module()
+! CHECK: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+
+! -----------------------------------------------------------------------------
+! ALLOCATABLE component reached through a nested derived type
+! -----------------------------------------------------------------------------
+subroutine test_component_nested()
+  use mod_derived
+  type(outer) :: o
+  allocate(o%inner%alc(100))
+  deallocate(o%inner%alc)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_nested()
+! CHECK: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+
+! -----------------------------------------------------------------------------
+! An explicitly attributed component keeps its own allocator; the
+! implicit attribution must not override it.
+! -----------------------------------------------------------------------------
+module mod_explicit
+  type :: td
+     real, allocatable, device :: dev(:)
+  end type td
+end module
+
+subroutine test_component_explicit_device()
+  use mod_explicit
+  type(td) :: obj
+  allocate(obj%dev(100))
+  deallocate(obj%dev)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_explicit_device()
+! CHECK: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+
+! -----------------------------------------------------------------------------
+! The implicit attribution must not override a memory space the user
+! asked for on the enclosing object. Allocating such a component is also not
+! diagnosed, since the user never attributed it (see cuf32.cuf for the
+! explicitly attributed case, which is still rejected).
+! -----------------------------------------------------------------------------
+subroutine test_component_in_device_object()
+  use mod_derived
+  type(t), device :: obj
+  allocate(obj%alc(100))
+  deallocate(obj%alc)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_in_device_object()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "obj", data_attr = #cuf.cuda<device>
+! CHECK: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+
+! -----------------------------------------------------------------------------
+! Same for a POINTER component.
+! -----------------------------------------------------------------------------
+subroutine test_pointer_component_in_device_object()
+  use mod_derived
+  type(t), device :: obj
+  allocate(obj%ptr(100))
+  deallocate(obj%ptr)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_pointer_component_in_device_object()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "obj", data_attr = #cuf.cuda<device>
+! CHECK: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>, pointer} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<device>, pointer} -> i32
+
+! -----------------------------------------------------------------------------
+! The enclosing object is reached through an array element.
+! -----------------------------------------------------------------------------
+subroutine test_component_in_device_array_element()
+  use mod_derived
+  type(t), device, allocatable :: objs(:)
+  allocate(objs(2))
+  allocate(objs(1)%alc(100))
+  deallocate(objs(1)%alc)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_component_in_device_array_element()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "objs", data_attr = #cuf.cuda<device>
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+! CHECK: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! CHECK: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+! CHECK: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+
+! -----------------------------------------------------------------------------
+! Whether the enclosing object is relocated depends on all of its
+! device-allocatable ultimate components, not on whichever one is declared
+! first. The explicitly attributed component here is DEVICE rather than
+! MANAGED, so that allocating the implicit one in a DEVICE object is not
+! diagnosed and the object's own attribute stays observable.
+! -----------------------------------------------------------------------------
+module mod_order
+  type :: implicit_first
+     real, allocatable :: implicit_comp(:)
+     real, allocatable, device :: explicit_comp(:)
+  end type
+  type :: explicit_first
+     real, allocatable, device :: explicit_comp(:)
+     real, allocatable :: implicit_comp(:)
+  end type
+  type :: only_implicit
+     real, allocatable :: a(:)
+     real, allocatable :: b(:)
+  end type
+end module
+
+! A user-attributed component relocates the object, whichever order it is in.
+subroutine test_order_implicit_first()
+  use mod_order
+  type(implicit_first), device :: d
+  allocate(d%implicit_comp(100))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_order_implicit_first()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "d", data_attr = #cuf.cuda<managed>
+
+subroutine test_order_explicit_first()
+  use mod_order
+  type(explicit_first), device :: d
+  allocate(d%implicit_comp(100))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_order_explicit_first()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "d", data_attr = #cuf.cuda<managed>
+
+! With every component implicitly attributed, the object keeps the memory
+! space the user asked for.
+subroutine test_order_only_implicit()
+  use mod_order
+  type(only_implicit), device :: d
+  allocate(d%a(100))
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_order_only_implicit()
+! CHECK: cuf.alloc {{.*}} {bindc_name = "d", data_attr = #cuf.cuda<device>


        


More information about the flang-commits mailing list