[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