[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