[flang-commits] [flang] [flang][cuda] Back `ALLOCATABLE` components with managed memory under `-gpu=mem:managed` (PR #223087)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Mon Sep 14 08:04:55 PDT 2026


https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/223087

>From 2ab1bd122e94df0d78d9deffb5f675c7993d5af8 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Wed, 9 Sep 2026 22:20:57 -0700
Subject: [PATCH] [flang][cuda] Back ALLOCATABLE components with managed memory
 under -gpu=mem:managed

Under -gpu=mem:managed, select the managed allocator index at the ALLOCATE site for ALLOCATABLE/POINTER components, reusing the branch #210149 added for -gpu=unified; the existing kDefaultAllocator test filters out the already-attributed entities.
---
 flang/lib/Lower/Allocatable.cpp               |  40 ++++--
 .../CUDA/cuda-gpu-managed-components.cuf      | 133 ++++++++++++++++++
 2 files changed, 160 insertions(+), 13 deletions(-)
 create mode 100644 flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf

diff --git a/flang/lib/Lower/Allocatable.cpp b/flang/lib/Lower/Allocatable.cpp
index 5f10319ab0101..991498993e703 100644
--- a/flang/lib/Lower/Allocatable.cpp
+++ b/flang/lib/Lower/Allocatable.cpp
@@ -528,17 +528,25 @@ class AllocateStmtHelper {
     bool isCudaDeviceContext = cuf::isCUDADeviceContext(builder.getRegion());
     unsigned allocatorIdx = Fortran::lower::getAllocatorIdx(*cudaSymForAlloc);
 
-    // Under -gpu=mem:unified, back plain (unattributed) allocatables/pointers
-    // with managed memory by selecting the unified allocator index at the
-    // ALLOCATE site. The symbol stays unattributed, so argument passing,
-    // interfaces, and COMMON legality are unaffected.
+    // Under -gpu=mem:unified or -gpu=mem:managed, back plain (unattributed)
+    // allocatables/pointers with managed memory by selecting the unified or
+    // managed allocator index at the ALLOCATE site. The symbol stays
+    // unattributed, so argument passing, interfaces, and COMMON legality are
+    // unaffected.
     bool implicitManagedBacking = false;
     if (allocatorIdx == kDefaultAllocator && !isCudaAllocate &&
-        !isCudaDeviceContext && (box.isAllocatable() || box.isPointer()) &&
-        converter.getFoldingContext().languageFeatures().IsEnabled(
-            Fortran::common::LanguageFeature::CudaUnified)) {
-      allocatorIdx = kUnifiedAllocatorPos;
-      implicitManagedBacking = true;
+        !isCudaDeviceContext && (box.isAllocatable() || box.isPointer())) {
+      const Fortran::common::LanguageFeatureControl &features =
+          converter.getFoldingContext().languageFeatures();
+      if (features.IsEnabled(Fortran::common::LanguageFeature::CudaUnified)) {
+        allocatorIdx = kUnifiedAllocatorPos;
+        implicitManagedBacking = true;
+      } else if (features.IsEnabled(Fortran::common::LanguageFeature::CUDA) &&
+                 features.IsEnabled(
+                     Fortran::common::LanguageFeature::CudaManaged)) {
+        allocatorIdx = kManagedAllocatorPos;
+        implicitManagedBacking = true;
+      }
     }
 
     // The inlined allocation path emits a plain heap allocmem that ignores the
@@ -982,15 +990,21 @@ genDeallocate(fir::FirOpBuilder &builder,
   bool isCudaSymbol =
       cudaSymbol && Fortran::semantics::HasCUDAAttr(*cudaSymbol);
   bool isCudaDeviceContext = cuf::isCUDADeviceContext(builder.getRegion());
-  // A plain allocatable/pointer under -gpu=mem:unified was given the unified
+  // A plain allocatable/pointer under -gpu=mem:unified, or an ALLOCATABLE/
+  // POINTER component under -gpu=mem:managed, was given the unified/managed
   // allocator index at ALLOCATE, so its deallocation must go through the
   // runtime (which honors that index) rather than an inlined freemem that would
-  // call libc free() on managed memory.
+  // call libc free() on managed memory. This mirrors the ALLOCATE site in
+  // genSimpleAllocation(); the two must agree or allocation and deallocation
+  // use different allocators.
+  const Fortran::common::LanguageFeatureControl &features =
+      converter.getFoldingContext().languageFeatures();
   bool implicitManagedBacking =
       !isCudaSymbol && !isCudaDeviceContext &&
       (box.isAllocatable() || box.isPointer()) &&
-      converter.getFoldingContext().languageFeatures().IsEnabled(
-          Fortran::common::LanguageFeature::CudaUnified);
+      (features.IsEnabled(Fortran::common::LanguageFeature::CudaUnified) ||
+       (features.IsEnabled(Fortran::common::LanguageFeature::CUDA) &&
+        features.IsEnabled(Fortran::common::LanguageFeature::CudaManaged)));
   bool inlineDeallocation =
       !box.isDerived() && !box.isPolymorphic() && !box.hasAssumedRank() &&
       !box.isUnlimitedPolymorphic() && !errorManager.hasStatSpec() &&
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..7936129d3ebb9
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-gpu-managed-components.cuf
@@ -0,0 +1,133 @@
+! RUN: bbc -emit-hlfir -fcuda -gpu=managed %s -o - | \
+! RUN:   FileCheck %s --check-prefixes=CHECK,MANAGED
+! RUN: bbc -emit-hlfir -fcuda -gpu=unified %s -o - | \
+! RUN:   FileCheck %s --check-prefixes=CHECK,UNIFIED
+
+! Test -gpu=managed and -gpu=unified for ALLOCATABLE and POINTER components of
+! derived types. Both modes back them with CUDA managed memory; they differ only
+! in the allocator index (managed = 3, unified = 4).
+!
+! Components live in the derived type's own scope, so they are not reached by
+! the implicit-attribute sweep that covers allocatables and pointers declared in
+! an ordinary scope (see cuda-gpu-managed.cuf). They get the allocator index at
+! the ALLOCATE site instead, which is why they use the runtime allocation path
+! rather than cuf.allocate. Under -gpu=unified there is no such sweep at all, so
+! every unattributed allocatable and pointer takes that path.
+!
+! The containing object stays an ordinary object: it carries no CUDA data
+! attribute and must not be placed in managed memory via cuf.alloc, which would
+! also put its non-allocatable components there.
+
+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
+
+! -----------------------------------------------------------------------------
+! Test 1: 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-NOT: cuf.alloc
+! CHECK: fir.alloca !fir.type<_QMmod_derivedTt
+! CHECK: hlfir.designate {{.*}}{"alc"}
+! MANAGED: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! UNIFIED: fir.embox {{.*}} {allocator_idx = 4 : i32}
+! CHECK: fir.call @_FortranAAllocatableAllocate
+! CHECK-NOT: fir.allocmem
+! The matching DEALLOCATE must go through the runtime too, so the managed
+! allocator frees what it allocated; an inlined fir.freemem would call libc
+! free() on managed memory.
+! CHECK: fir.call @_FortranAAllocatableDeallocate
+! CHECK-NOT: fir.freemem
+
+! -----------------------------------------------------------------------------
+! Test 2: 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-NOT: cuf.alloc
+! CHECK: hlfir.designate {{.*}}{"ptr"}
+! MANAGED: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! UNIFIED: fir.embox {{.*}} {allocator_idx = 4 : i32}
+! CHECK: fir.call @_FortranAPointerAllocate
+! CHECK: fir.call @_FortranAPointerDeallocate
+! CHECK-NOT: fir.freemem
+
+! -----------------------------------------------------------------------------
+! Test 3: 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-NOT: cuf.alloc
+! MANAGED: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! UNIFIED: fir.embox {{.*}} {allocator_idx = 4 : i32}
+! CHECK: fir.call @_FortranAAllocatableAllocate
+! CHECK: fir.call @_FortranAAllocatableDeallocate
+! CHECK-NOT: fir.freemem
+
+! -----------------------------------------------------------------------------
+! Test 4: 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-NOT: cuf.alloc
+! MANAGED: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! UNIFIED: fir.embox {{.*}} {allocator_idx = 4 : i32}
+! CHECK: fir.call @_FortranAAllocatableAllocate
+! CHECK: fir.call @_FortranAAllocatableDeallocate
+! CHECK-NOT: fir.freemem
+
+! -----------------------------------------------------------------------------
+! Test 5: an explicitly attributed component keeps its own allocator
+! -----------------------------------------------------------------------------
+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}
+
+! The module derived-type object itself is not CUDA-attributed: only its
+! ALLOCATABLE/POINTER components are redirected, and only at ALLOCATE.
+! CHECK: fir.global @_QMmod_derivedEmod_obj : !fir.type<_QMmod_derivedTt
+! CHECK-NOT: data_attr



More information about the flang-commits mailing list