[flang-commits] [flang] [flang][cuda] Honor component CUDA attrs in assignment copies (PR #220398)

Valentin Clement バレンタイン クレメン via flang-commits flang-commits at lists.llvm.org
Tue Sep 1 14:50:09 PDT 2026


https://github.com/clementval created https://github.com/llvm/llvm-project/pull/220398

A device component of a managed object was classified as managed because IsCUDADataTransfer counted every symbol in the reference. Classify each side from the designated data so a component attribute hides the base.

>From 228ce5211d104993e0cd145a145bf0246e1d6627 Mon Sep 17 00:00:00 2001
From: Valentin Clement <clementval at gmail.com>
Date: Tue, 1 Sep 2026 14:46:09 -0700
Subject: [PATCH] [flang][cuda] Honor component CUDA attrs in assignment copies

A device component of a managed object was classified as managed because
IsCUDADataTransfer counted every symbol in the reference. Classify each
side from the designated data so a component attribute hides the base.
---
 flang/include/flang/Evaluate/tools.h          | 72 +++++++++++++++++--
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 39 ++++++++++
 2 files changed, 107 insertions(+), 4 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 8cfc6adfcc941..e8847f50549d1 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -22,6 +22,7 @@
 #include "flang/Semantics/attr.h"
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/symbol.h"
+#include <algorithm>
 #include <array>
 #include <optional>
 #include <set>
@@ -1353,6 +1354,64 @@ inline bool IsCUDAUnifiedSymbol(const Symbol &sym) {
   return IsCUDADataAttrSymbol(sym, common::CUDADataAttr::Unified);
 }
 
+inline bool HasCUDADataAttr(const Symbol &sym) {
+  const auto *details{
+      sym.GetUltimate().detailsIf<semantics::ObjectEntityDetails>()};
+  return details && details->cudaDataAttr().has_value();
+}
+
+// The data attribute of a component describes the data that the component
+// designates, so it hides the attribute of the object that the component is
+// taken from: in a%b, where a is managed and b is device, a%b designates
+// device data. Collect the symbols of the expression, leaving out the ones
+// that a component with an attribute hides.
+template <typename A>
+semantics::UnorderedSymbolSet CollectEffectiveCudaSymbols(const A &expr) {
+  semantics::UnorderedSymbolSet result{CollectCudaSymbols(expr)};
+  SymbolVector symbols{GetSymbolVector(expr)};
+  // GetSymbolVector lists the base of a component chain before its components.
+  // Reverse it to visit the innermost component of a chain first.
+  std::reverse(symbols.begin(), symbols.end());
+  bool hidden{false};
+  for (const Symbol &sym : symbols) {
+    bool isComponent{sym.owner().IsDerivedType()};
+    if (hidden) {
+      result.erase(sym);
+    } else if (isComponent && HasCUDADataAttr(sym)) {
+      hidden = true;
+    }
+    if (!isComponent) {
+      hidden = false; // The base ends the component chain.
+    }
+  }
+  return result;
+}
+
+// Get the number of symbols with the CUDA managed attribute in a set.
+inline int CountCUDAManagedSymbols(
+    const semantics::UnorderedSymbolSet &symbols) {
+  int count{0};
+  for (const Symbol &sym : symbols) {
+    if (IsCUDAManagedSymbol(sym)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+// Get the number of symbols with a CUDA device attribute other than unified in
+// a set.
+inline int CountCUDANonUnifiedSymbols(
+    const semantics::UnorderedSymbolSet &symbols) {
+  int count{0};
+  for (const Symbol &sym : symbols) {
+    if (IsCUDADeviceSymbol(sym) && !IsCUDAUnifiedSymbol(sym)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
 // Non-allocatable module-level managed/unified variables use pointer
 // indirection through a companion global in __nv_managed_data__.
 // Explicit data transfers (cudaMemcpy) must be avoided for these
@@ -1470,15 +1529,20 @@ template <typename A> inline bool IsWholeManagedArray(const A &expr) {
 // Unified data is host memory that the device can also access, so it takes the
 // place of host data in the rules above and an assignment between unified sides
 // is host code.
+// The side of an assignment is classified from the data it designates, so the
+// attribute of a component prevails over the attribute of the object it is
+// taken from.
 // Return true if the assignment is one of the copies above.
 template <typename A, typename B>
 inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
+  semantics::UnorderedSymbolSet lhsSymbols{CollectEffectiveCudaSymbols(lhs)};
+  semantics::UnorderedSymbolSet rhsSymbols{CollectEffectiveCudaSymbols(rhs)};
   // Unified data is left out of these counts and checks so that it is handled
   // as host data.
-  bool lhsHasManaged{HasCUDAManagedSymbols(lhs)};
-  bool lhsIsHost{!HasCUDANonUnifiedSymbols(lhs)};
-  int rhsNbManagedSymbols{GetNbOfCUDAManagedSymbols(rhs)};
-  int rhsNbSymbols{GetNbOfCUDANonUnifiedSymbols(rhs)};
+  bool lhsHasManaged{CountCUDAManagedSymbols(lhsSymbols) > 0};
+  bool lhsIsHost{CountCUDANonUnifiedSymbols(lhsSymbols) == 0};
+  int rhsNbManagedSymbols{CountCUDAManagedSymbols(rhsSymbols)};
+  int rhsNbSymbols{CountCUDANonUnifiedSymbols(rhsSymbols)};
 
   if (HasNonAllocatableModuleCUDAManagedSymbols(lhs))
     return false;
diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index 09bfcd5296c89..2a201f56dedce 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -64,6 +64,45 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_component_attr_assign()
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 
+! The attribute of a component describes the data the component designates, so it
+! prevails over the attribute of the object the component is taken from. A device
+! component of a managed object is device data, which the host cannot write in
+! place, whatever the form of the reference.
+subroutine device_component_of_managed(n, i)
+  integer :: n, i
+  type :: t
+    integer(4), allocatable, device :: dcomp(:)
+  end type
+  type(t), allocatable, managed :: cm(:)
+  integer(4) :: ha(3)
+  allocate(cm(n))
+  allocate(cm(i)%dcomp(3))
+  cm(i)%dcomp = 10         ! device component = constant
+  cm(i)%dcomp = ha         ! device component = host array
+  cm(i)%dcomp(1:2) = 10    ! device component section = constant
+  ha = cm(i)%dcomp         ! host array = device component
+end subroutine
+
+! CHECK-LABEL: func.func @_QPdevice_component_of_managed
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! The object's attribute still applies to a component that has none.
+subroutine host_component_of_managed(n, i)
+  integer :: n, i
+  type :: t
+    integer(4) :: m(16)
+  end type
+  type(t), allocatable, managed :: cm(:)
+  allocate(cm(n))
+  cm(i)%m = 10
+end subroutine
+
+! CHECK-LABEL: func.func @_QPhost_component_of_managed
+! CHECK-NOT: cuf.data_transfer
+
 ! One whole managed side is enough: the other may be a section.
 subroutine managed_mixed_whole_section()
   integer(4), managed :: ma(4), mb(4,16)



More information about the flang-commits mailing list