[flang-commits] [flang] [flang][cuda] Restrict managed data transfers to whole-array assignments (PR #212593)

Zhen Wang via flang-commits flang-commits at lists.llvm.org
Wed Jul 29 09:18:49 PDT 2026


https://github.com/wangzpgi updated https://github.com/llvm/llvm-project/pull/212593

>From c27ce86054cc6014a3f6e03f1eda8f082a7b4711 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 10:31:18 -0700
Subject: [PATCH 1/8] Restrict managed data transfers to whole-array
 assignments

---
 flang/include/flang/Evaluate/tools.h          |  24 ++-
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 194 +++++++++++++++++-
 2 files changed, 205 insertions(+), 13 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index a60848bea714e..28cf9b5cc3c82 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1392,6 +1392,14 @@ template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
   return GetNbOfCUDADeviceSymbols(expr) > 0;
 }
 
+// Check if the expression designates an entire array variable or component, as
+// opposed to an array section, an array element, or a computed value. Unlike
+// !IsArraySection(), a whole array component of a scalar base (a%b) qualifies.
+template <typename A> inline bool IsWholeArrayDesignator(const A &expr) {
+  return expr.Rank() > 0 &&
+      UnwrapWholeSymbolOrComponentDataRef(expr) != nullptr;
+}
+
 // Check if any of the symbols part of the lhs or rhs expression has a CUDA
 // device attribute.
 template <typename A, typename B>
@@ -1408,25 +1416,29 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
     return true; // Managed arrays initialization is performed on the device.
   }
 
+  // A whole-array assignment whose right-hand side is a managed/unified
+  // variable is a synchronous data transfer that waits for previously launched
+  // kernels. Subsections are excluded: a loop assigning one section at a time
+  // would otherwise become a sequence of blocking copies.
+  bool wholeArrayTransfer{
+      IsWholeArrayDesignator(lhs) && IsWholeArrayDesignator(rhs)};
+
   // Managed/unified data is host-addressable, so several assignments are
   // performed on the host and need no explicit data transfer:
   // - A whole-allocatable left-hand side involving managed/unified data: the
   //   assignment has reallocation semantics and is performed on the host.
-  // - Element-wise (scalar) access to managed/unified data.
+  // - Element-wise (scalar) accesses and array-section assignments.
   // - A right-hand side expression involving managed/unified data assigned into
   //   a host-addressable (managed/unified or host) left-hand side: evaluating
   //   it on the host avoids materializing a temporary.
   // - A managed/unified left-hand side assigned from host-only data.
-  // A whole-array assignment whose right-hand side is a managed/unified
-  // variable is a synchronous data transfer that waits for previously launched
-  // kernels.
   if ((IsAllocatableDesignator(lhs) &&
           (lhsNbManagedSymbols >= 1 || rhsNbManagedSymbols >= 1)) ||
       (lhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
-          lhs.Rank() == 0) ||
+          !wholeArrayTransfer) ||
       (lhsNbManagedSymbols == 0 && !HasCUDADeviceAttrs(lhs) &&
           rhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
-          lhs.Rank() == 0) ||
+          !wholeArrayTransfer) ||
       (rhsNbManagedSymbols >= 1 && !IsVariable(rhs) &&
           (lhsNbManagedSymbols >= 1 || !HasCUDADeviceAttrs(lhs))) ||
       (lhsNbManagedSymbols >= 1 && rhsNbSymbols == 0)) {
diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index 126169d3d40b3..ee8c0f5a2aef0 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -1,10 +1,11 @@
 ! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
 
-! A whole-array assignment whose right-hand side is a managed variable or a
-! managed function result is a synchronous data transfer (matching CUDA Fortran
-! assignment-statement semantics). Element-wise (scalar) accesses and right-hand
-! side expressions involving managed data are performed on the host and need no
-! transfer.
+! An assignment between whole managed arrays, or from a managed function result,
+! is a synchronous data transfer (matching CUDA Fortran assignment-statement
+! semantics). Managed data is host-addressable, so element-wise accesses,
+! array-section assignments, and right-hand side expressions involving managed
+! data are performed on the host: a loop assigning one section at a time would
+! otherwise become a sequence of blocking copies.
 
 module mfr
 contains
@@ -16,6 +17,8 @@ contains
   end function
 end module
 
+! Whole-array assignments are data transfers.
+
 subroutine managed_array_assign()
   integer(4), managed :: ma(16), mb(16)
   integer(4) :: ha(16)
@@ -29,6 +32,34 @@ end subroutine
 ! CHECK: cuf.data_transfer %{{.*}} to %{{.*}} {hasManagedOrUnifedSymbols, transfer_kind = #cuf.cuda_transfer<host_device>}
 ! CHECK: cuf.data_transfer %{{.*}} to %{{.*}} {hasManagedOrUnifedSymbols, transfer_kind = #cuf.cuda_transfer<device_host>}
 
+subroutine managed_array_assign_2d()
+  integer(4), managed :: ma(4,16), mb(4,16)
+  integer(4) :: ha(4,16)
+  ma = mb
+  ha = ma
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_array_assign_2d()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! A whole array component of a scalar base is a whole-array designator.
+subroutine managed_component_assign()
+  type :: t
+    integer(4) :: m(16)
+  end type
+  type(t), managed :: ma, mb
+  type(t) :: ha
+  ma%m = mb%m
+  ha%m = mb%m
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_component_assign()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! Assignments performed on the host.
+
 ! A right-hand side expression involving managed data is evaluated on the host
 ! to avoid materializing a temporary, so no data transfer is generated.
 subroutine managed_expr_assign()
@@ -53,13 +84,162 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_scalar_access
 ! CHECK-NOT: cuf.data_transfer
 
+subroutine managed_element_assign(n)
+  integer :: n, i
+  integer(4), managed :: ma(16), mb(16)
+  integer(4) :: ha(16)
+  do i = 1, n
+    ma(i) = mb(i)   ! managed element = managed element
+    ha(i) = mb(i)   ! host element = managed element
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_element_assign
+! CHECK-NOT: cuf.data_transfer
+
+subroutine managed_section_assign(n)
+  integer :: n, i
+  integer(4), managed :: ma(4,16), mb(4,16)
+  integer(4) :: ha(4,16)
+  do i = 1, n
+    ma(:,i) = mb(:,i)   ! managed section = managed section
+    ha(:,i) = mb(:,i)   ! host section = managed section
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_section_assign
+! CHECK-NOT: cuf.data_transfer
+
+subroutine managed_section_assign_1d(n)
+  integer :: n
+  integer(4), managed :: ma(16), mb(16)
+  integer(4) :: ha(16)
+  ma(1:8) = mb(1:8)         ! contiguous section
+  ma(1:16:2) = mb(1:16:2)   ! strided section
+  ha(1:8) = mb(1:8)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_section_assign_1d
+! CHECK-NOT: cuf.data_transfer
+
+! The whole-array test is syntactic: a section that happens to cover the whole
+! array is still a section.
+subroutine managed_full_slice_assign()
+  integer(4), managed :: ma(4,16), mb(4,16)
+  integer(4) :: ha(4,16)
+  ma(:,:) = mb(:,:)
+  ha(:,:) = mb(:,:)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_full_slice_assign()
+! CHECK-NOT: cuf.data_transfer
+
+subroutine managed_component_section_assign()
+  type :: t
+    integer(4) :: m(16)
+  end type
+  type(t), managed :: ma, mb
+  ma%m(1:8) = mb%m(1:8)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_component_section_assign()
+! CHECK-NOT: cuf.data_transfer
+
+! Both sides must be whole arrays for the assignment to be a transfer.
+subroutine managed_mixed_whole_section()
+  integer(4), managed :: ma(4), mb(4,16)
+  integer(4) :: ha(4)
+  ma = mb(:,1)        ! whole = section
+  mb(:,1) = ma        ! section = whole
+  ha = mb(:,1)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_mixed_whole_section()
+! CHECK-NOT: cuf.data_transfer
+
+! A whole-allocatable left-hand side has reallocation semantics and is performed
+! on the host.
+subroutine managed_alloc_assign(n)
+  integer :: n
+  integer(4), allocatable, managed :: ma(:), mb(:)
+  allocate(ma(n), mb(n))
+  ma = mb
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_alloc_assign
+! CHECK-NOT: cuf.data_transfer
+
+! The unified attribute follows the same rules as managed.
+subroutine unified_section_assign(n)
+  integer :: n, i
+  integer(4), unified :: ua(4,16), ub(4,16)
+  integer(4) :: ha(4,16)
+  ua = ub                ! whole array: data transfer
+  do i = 1, n
+    ua(:,i) = ub(:,i)    ! sections: performed on the host
+    ha(:,i) = ub(:,i)
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunified_section_assign
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK-NOT: cuf.data_transfer
+
+! Cases that remain data transfers.
+
+! Device memory is not host-addressable, so sections and elements involving it
+! are still transferred.
+subroutine managed_device_section(n)
+  integer :: n, i
+  integer(4), managed :: ma(4,16)
+  integer(4), device :: da(4,16)
+  integer(4) :: ha(4,16)
+  do i = 1, n
+    ma(:,i) = da(:,i)   ! managed section = device section
+    ha(:,i) = da(:,i)   ! host section = device section
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_device_section
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! Initialization of managed data from a host-only right-hand side is performed
+! on the device, whether the left-hand side is a whole array or a section.
+subroutine managed_init(n)
+  integer :: n, i
+  integer(4), managed :: ma(4,16)
+  integer(4) :: ha(4,16)
+  ma = 0
+  do i = 1, n
+    ma(:,i) = ha(:,i)
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_init
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
+
 ! A managed function result may be produced by an asynchronous kernel, so
-! consuming it in an assignment is a synchronizing data transfer.
+! consuming it in an assignment is a synchronizing data transfer, independently
+! of the shape of the left-hand side.
 subroutine managed_func_result()
   use mfr
   integer(4), managed :: b(4)
+  integer(4) :: hb(4)
   b = fr(4)
+  hb = fr(4)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPmanaged_func_result()
-! CHECK: cuf.data_transfer
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+subroutine managed_func_result_section()
+  use mfr
+  integer(4), managed :: b(4)
+  b(1:2) = fr(2)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_func_result_section()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>

>From a5032185c4faca28b5f5c49a5b8bf78b147d76f8 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 13:50:26 -0700
Subject: [PATCH 2/8] Align managed data transfers with CUDA Fortran Guide
 3.4.1

---
 flang/include/flang/Evaluate/tools.h          |  28 +--
 flang/test/Lower/CUDA/cuda-data-transfer.cuf  |   4 +-
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 199 +++++++++++-------
 3 files changed, 141 insertions(+), 90 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 28cf9b5cc3c82..9c9abfc5d3e94 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1411,23 +1411,25 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
   if (HasNonAllocatableModuleCUDAManagedSymbols(lhs))
     return false;
 
-  if (lhsNbManagedSymbols >= 1 && lhs.Rank() > 0 && rhsNbSymbols == 0 &&
-      rhsNbManagedSymbols == 0 && (IsVariable(rhs) || IsConstantExpr(rhs))) {
+  if (lhsNbManagedSymbols >= 1 && IsWholeArrayDesignator(lhs) &&
+      rhsNbSymbols == 0 && rhsNbManagedSymbols == 0 &&
+      (IsVariable(rhs) || IsConstantExpr(rhs))) {
     return true; // Managed arrays initialization is performed on the device.
   }
 
-  // A whole-array assignment whose right-hand side is a managed/unified
-  // variable is a synchronous data transfer that waits for previously launched
-  // kernels. Subsections are excluded: a loop assigning one section at a time
-  // would otherwise become a sequence of blocking copies.
-  bool wholeArrayTransfer{
-      IsWholeArrayDesignator(lhs) && IsWholeArrayDesignator(rhs)};
+  // Per CUDA Fortran Programming Guide 3.4.1, an assignment involving
+  // managed/unified data is a synchronous copy when the managed operand is a
+  // whole variable or array, and is performed by host code when it is an array
+  // section: managed data is host-addressable, and a loop assigning one section
+  // at a time would otherwise become a sequence of blocking copies. Sections
+  // are still copied when the other side is device data.
+  bool wholeLhs{IsWholeArrayDesignator(lhs)};
+  bool wholeRhs{IsWholeArrayDesignator(rhs)};
 
-  // Managed/unified data is host-addressable, so several assignments are
-  // performed on the host and need no explicit data transfer:
+  // Assignments that are performed on the host and need no data transfer:
   // - A whole-allocatable left-hand side involving managed/unified data: the
   //   assignment has reallocation semantics and is performed on the host.
-  // - Element-wise (scalar) accesses and array-section assignments.
+  // - A managed/unified operand that is an array section or an element.
   // - A right-hand side expression involving managed/unified data assigned into
   //   a host-addressable (managed/unified or host) left-hand side: evaluating
   //   it on the host avoids materializing a temporary.
@@ -1435,10 +1437,10 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
   if ((IsAllocatableDesignator(lhs) &&
           (lhsNbManagedSymbols >= 1 || rhsNbManagedSymbols >= 1)) ||
       (lhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
-          !wholeArrayTransfer) ||
+          !(wholeLhs && wholeRhs)) ||
       (lhsNbManagedSymbols == 0 && !HasCUDADeviceAttrs(lhs) &&
           rhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
-          !wholeArrayTransfer) ||
+          !wholeRhs) ||
       (rhsNbManagedSymbols >= 1 && !IsVariable(rhs) &&
           (lhsNbManagedSymbols >= 1 || !HasCUDADeviceAttrs(lhs))) ||
       (lhsNbManagedSymbols >= 1 && rhsNbSymbols == 0)) {
diff --git a/flang/test/Lower/CUDA/cuda-data-transfer.cuf b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
index 54cf439e66d9c..c6a47f227e531 100644
--- a/flang/test/Lower/CUDA/cuda-data-transfer.cuf
+++ b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
@@ -634,6 +634,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPsub33
 ! CHECK-NOT: cuf.data_transfer 
 
+! A managed array section is assigned on the host: only a whole managed array is
+! initialized on the device (CUDA Fortran Programming Guide 3.4.1).
 subroutine sub34(n)
   integer :: n
   real(2), managed, allocatable :: dx(:)
@@ -642,7 +644,7 @@ subroutine sub34(n)
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPsub34
-! CHECK:  cuf.data_transfer %{{.*}} to %{{.*}} {hasManagedOrUnifedSymbols, transfer_kind = #cuf.cuda_transfer<host_device>} : f16, !fir.box<!fir.array<?xf16>>
+! CHECK-NOT: cuf.data_transfer
 
 module managed_mod
   integer, managed :: marray(10)
diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index ee8c0f5a2aef0..227a8e1445bd5 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -1,11 +1,11 @@
 ! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
 
-! An assignment between whole managed arrays, or from a managed function result,
-! is a synchronous data transfer (matching CUDA Fortran assignment-statement
-! semantics). Managed data is host-addressable, so element-wise accesses,
-! array-section assignments, and right-hand side expressions involving managed
-! data are performed on the host: a loop assigning one section at a time would
-! otherwise become a sequence of blocking copies.
+! CUDA Fortran Programming Guide 3.4.1: an assignment involving managed data is a
+! synchronous copy when the managed operand is a whole variable or array, and is
+! performed by host code when it is an array section. Sections are still copied
+! when the other side is device data, which is not host-addressable. A managed
+! function result may be produced by an asynchronous kernel, so consuming it is
+! also a synchronizing copy.
 
 module mfr
 contains
@@ -17,13 +17,13 @@ contains
   end function
 end module
 
-! Whole-array assignments are data transfers.
+! Assignments that are data transfers.
 
 subroutine managed_array_assign()
   integer(4), managed :: ma(16), mb(16)
   integer(4) :: ha(16)
   ma = mb    ! managed = managed
-  ma = ha    ! managed = host
+  ma = ha    ! managed = host array
   ha = ma    ! host = managed
 end subroutine
 
@@ -58,10 +58,86 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
+! A whole managed array is copied from a conforming constant, host scalar, host
+! array or host array section.
+subroutine managed_whole_from_host(hs)
+  integer(4) :: hs
+  integer(4), managed :: ma(4)
+  integer(4) :: hb(4,16)
+  ma = 0
+  ma = hs
+  ma = hb(:,1)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_whole_from_host
+! 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>
+
+! Device memory is not host-addressable, so an assignment between managed and
+! device data is a copy in both directions, including for sections.
+subroutine managed_device_transfer(n)
+  integer :: n, i
+  integer(4), managed :: ma(4,16)
+  integer(4), device :: da(4,16)
+  integer(4) :: ha(4,16)
+  ma = da                ! whole managed = whole device
+  da = ma                ! whole device = whole managed
+  do i = 1, n
+    ma(:,i) = da(:,i)    ! managed section = device section
+    da(:,i) = ma(:,i)    ! device section = managed section
+    ha(:,i) = da(:,i)    ! host section = device section
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_device_transfer
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! The rule keys on the managed operand: a host section may be the target of a
+! whole managed array copy.
+subroutine host_section_from_managed(n)
+  integer :: n, i
+  integer(4), managed :: mb(4)
+  integer(4) :: ha(4,16)
+  do i = 1, n
+    ha(:,i) = mb
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPhost_section_from_managed
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! A managed function result is a synchronizing copy, independently of the shape of
+! the left-hand side.
+subroutine managed_func_result()
+  use mfr
+  integer(4), managed :: b(4)
+  integer(4) :: hb(4)
+  b = fr(4)
+  hb = fr(4)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_func_result()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+subroutine managed_func_result_section()
+  use mfr
+  integer(4), managed :: b(4)
+  b(1:2) = fr(2)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_func_result_section()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+
 ! Assignments performed on the host.
 
-! A right-hand side expression involving managed data is evaluated on the host
-! to avoid materializing a temporary, so no data transfer is generated.
+! A right-hand side expression involving managed data is evaluated on the host to
+! avoid materializing a temporary.
 subroutine managed_expr_assign()
   integer(4), managed :: ma(16), mb(16)
   integer(4) :: ha(16)
@@ -72,12 +148,24 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_expr_assign()
 ! CHECK-NOT: cuf.data_transfer
 
+! Scalar and element-wise accesses of managed data are performed on the host.
+subroutine managed_scalar_assign()
+  integer(4), managed :: ms, mt
+  integer(4) :: hs
+  ms = mt
+  hs = ms
+  ms = hs
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_scalar_assign()
+! CHECK-NOT: cuf.data_transfer
+
 subroutine managed_scalar_access(n)
   integer :: n, i
   integer(4), managed :: ma(16)
   integer(4) :: ha(16)
   do i = 1, n
-    ha(i) = ma(i) + 1   ! element-wise access is performed on the host
+    ha(i) = ma(i) + 1
   end do
 end subroutine
 
@@ -97,6 +185,26 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_element_assign
 ! CHECK-NOT: cuf.data_transfer
 
+! A managed array section is assigned on the host, whatever the host right-hand
+! side is.
+subroutine managed_section_from_host(n, hs)
+  integer :: n, i
+  integer(4) :: hs
+  integer(4), managed :: mb(4,16)
+  integer(4) :: ha(4), hb(4,16)
+  do i = 1, n
+    mb(:,i) = 0
+    mb(:,i) = hs
+    mb(:,i) = ha
+    mb(:,i) = hb(:,i)
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_section_from_host
+! CHECK-NOT: cuf.data_transfer
+
+! A managed array section on either side is assigned on the host: a loop assigning
+! one section at a time would otherwise become a sequence of blocking copies.
 subroutine managed_section_assign(n)
   integer :: n, i
   integer(4), managed :: ma(4,16), mb(4,16)
@@ -110,8 +218,7 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_section_assign
 ! CHECK-NOT: cuf.data_transfer
 
-subroutine managed_section_assign_1d(n)
-  integer :: n
+subroutine managed_section_assign_1d()
   integer(4), managed :: ma(16), mb(16)
   integer(4) :: ha(16)
   ma(1:8) = mb(1:8)         ! contiguous section
@@ -145,13 +252,12 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_component_section_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-! Both sides must be whole arrays for the assignment to be a transfer.
 subroutine managed_mixed_whole_section()
   integer(4), managed :: ma(4), mb(4,16)
   integer(4) :: ha(4)
-  ma = mb(:,1)        ! whole = section
-  mb(:,1) = ma        ! section = whole
-  ha = mb(:,1)
+  ma = mb(:,1)        ! whole managed = managed section
+  mb(:,1) = ma        ! managed section = whole managed
+  ha = mb(:,1)        ! whole host = managed section
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPmanaged_mixed_whole_section()
@@ -184,62 +290,3 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPunified_section_assign
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK-NOT: cuf.data_transfer
-
-! Cases that remain data transfers.
-
-! Device memory is not host-addressable, so sections and elements involving it
-! are still transferred.
-subroutine managed_device_section(n)
-  integer :: n, i
-  integer(4), managed :: ma(4,16)
-  integer(4), device :: da(4,16)
-  integer(4) :: ha(4,16)
-  do i = 1, n
-    ma(:,i) = da(:,i)   ! managed section = device section
-    ha(:,i) = da(:,i)   ! host section = device section
-  end do
-end subroutine
-
-! CHECK-LABEL: func.func @_QPmanaged_device_section
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
-
-! Initialization of managed data from a host-only right-hand side is performed
-! on the device, whether the left-hand side is a whole array or a section.
-subroutine managed_init(n)
-  integer :: n, i
-  integer(4), managed :: ma(4,16)
-  integer(4) :: ha(4,16)
-  ma = 0
-  do i = 1, n
-    ma(:,i) = ha(:,i)
-  end do
-end subroutine
-
-! CHECK-LABEL: func.func @_QPmanaged_init
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
-
-! A managed function result may be produced by an asynchronous kernel, so
-! consuming it in an assignment is a synchronizing data transfer, independently
-! of the shape of the left-hand side.
-subroutine managed_func_result()
-  use mfr
-  integer(4), managed :: b(4)
-  integer(4) :: hb(4)
-  b = fr(4)
-  hb = fr(4)
-end subroutine
-
-! CHECK-LABEL: func.func @_QPmanaged_func_result()
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
-
-subroutine managed_func_result_section()
-  use mfr
-  integer(4), managed :: b(4)
-  b(1:2) = fr(2)
-end subroutine
-
-! CHECK-LABEL: func.func @_QPmanaged_func_result_section()
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>

>From bfa26000d0900fa5deda198c0a3de996dc7c4b1b Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 16:02:34 -0700
Subject: [PATCH 3/8] better comments

---
 flang/include/flang/Evaluate/tools.h          | 31 ++++++------
 flang/test/Lower/CUDA/cuda-data-transfer.cuf  |  4 +-
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 49 +++++++++----------
 3 files changed, 39 insertions(+), 45 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 9c9abfc5d3e94..4d78ace505f24 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1392,9 +1392,9 @@ template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
   return GetNbOfCUDADeviceSymbols(expr) > 0;
 }
 
-// Check if the expression designates an entire array variable or component, as
-// opposed to an array section, an array element, or a computed value. Unlike
-// !IsArraySection(), a whole array component of a scalar base (a%b) qualifies.
+// True for a whole array variable or a whole array component (a%b), false for an
+// array section, an array element or a computed value. Note that IsArraySection()
+// counts a%b as a section, so this is not its negation.
 template <typename A> inline bool IsWholeArrayDesignator(const A &expr) {
   return expr.Rank() > 0 &&
       UnwrapWholeSymbolOrComponentDataRef(expr) != nullptr;
@@ -1417,23 +1417,20 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
     return true; // Managed arrays initialization is performed on the device.
   }
 
-  // Per CUDA Fortran Programming Guide 3.4.1, an assignment involving
-  // managed/unified data is a synchronous copy when the managed operand is a
-  // whole variable or array, and is performed by host code when it is an array
-  // section: managed data is host-addressable, and a loop assigning one section
-  // at a time would otherwise become a sequence of blocking copies. Sections
-  // are still copied when the other side is device data.
+  // CUDA Fortran Programming Guide 3.4.1: an assignment involving managed or
+  // unified data is a copy when the managed side is a whole variable or array,
+  // and is done on the host when it is a section. The host can read and write
+  // managed data directly, and copying section by section in a loop is slow.
   bool wholeLhs{IsWholeArrayDesignator(lhs)};
   bool wholeRhs{IsWholeArrayDesignator(rhs)};
 
-  // Assignments that are performed on the host and need no data transfer:
-  // - A whole-allocatable left-hand side involving managed/unified data: the
-  //   assignment has reallocation semantics and is performed on the host.
-  // - A managed/unified operand that is an array section or an element.
-  // - A right-hand side expression involving managed/unified data assigned into
-  //   a host-addressable (managed/unified or host) left-hand side: evaluating
-  //   it on the host avoids materializing a temporary.
-  // - A managed/unified left-hand side assigned from host-only data.
+  // Assignments done on the host, with no copy:
+  // - A whole allocatable left-hand side: the assignment may reallocate it,
+  //   which is done on the host.
+  // - A managed operand that is an array section or an element.
+  // - An expression involving managed data assigned to a managed or host
+  //   left-hand side: evaluating it on the host avoids a temporary.
+  // - A managed left-hand side assigned from host-only data.
   if ((IsAllocatableDesignator(lhs) &&
           (lhsNbManagedSymbols >= 1 || rhsNbManagedSymbols >= 1)) ||
       (lhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
diff --git a/flang/test/Lower/CUDA/cuda-data-transfer.cuf b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
index c6a47f227e531..32cfa62d0fe3c 100644
--- a/flang/test/Lower/CUDA/cuda-data-transfer.cuf
+++ b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
@@ -634,8 +634,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPsub33
 ! CHECK-NOT: cuf.data_transfer 
 
-! A managed array section is assigned on the host: only a whole managed array is
-! initialized on the device (CUDA Fortran Programming Guide 3.4.1).
+! Only a whole managed array is initialized on the device; a section is assigned on
+! the host (CUDA Fortran Programming Guide 3.4.1).
 subroutine sub34(n)
   integer :: n
   real(2), managed, allocatable :: dx(:)
diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index 227a8e1445bd5..f4f0af92f77b9 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -1,11 +1,10 @@
 ! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
 
 ! CUDA Fortran Programming Guide 3.4.1: an assignment involving managed data is a
-! synchronous copy when the managed operand is a whole variable or array, and is
-! performed by host code when it is an array section. Sections are still copied
-! when the other side is device data, which is not host-addressable. A managed
-! function result may be produced by an asynchronous kernel, so consuming it is
-! also a synchronizing copy.
+! copy when the managed side is a whole variable or array, and is done on the host
+! when it is a section. Sections are still copied when the other side is device
+! data, which the host cannot access directly. A managed function result may come
+! from a kernel that is still running, so reading it is also a copy.
 
 module mfr
 contains
@@ -17,7 +16,7 @@ contains
   end function
 end module
 
-! Assignments that are data transfers.
+! Assignments that are copies.
 
 subroutine managed_array_assign()
   integer(4), managed :: ma(16), mb(16)
@@ -43,7 +42,7 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
-! A whole array component of a scalar base is a whole-array designator.
+! A whole array component (a%b) counts as a whole array.
 subroutine managed_component_assign()
   type :: t
     integer(4) :: m(16)
@@ -58,8 +57,8 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
-! A whole managed array is copied from a conforming constant, host scalar, host
-! array or host array section.
+! A whole managed array is copied from a constant, a host scalar, a host array or
+! a host section.
 subroutine managed_whole_from_host(hs)
   integer(4) :: hs
   integer(4), managed :: ma(4)
@@ -74,8 +73,8 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<host_device>
 
-! Device memory is not host-addressable, so an assignment between managed and
-! device data is a copy in both directions, including for sections.
+! The host cannot access device memory directly, so an assignment between managed
+! and device data is a copy in both directions, sections included.
 subroutine managed_device_transfer(n)
   integer :: n, i
   integer(4), managed :: ma(4,16)
@@ -97,7 +96,7 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
-! The rule keys on the managed operand: a host section may be the target of a
+! Only the managed side has to be whole: a host section can be the target of a
 ! whole managed array copy.
 subroutine host_section_from_managed(n)
   integer :: n, i
@@ -111,8 +110,7 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPhost_section_from_managed
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
-! A managed function result is a synchronizing copy, independently of the shape of
-! the left-hand side.
+! A managed function result is copied whatever the shape of the left-hand side.
 subroutine managed_func_result()
   use mfr
   integer(4), managed :: b(4)
@@ -134,10 +132,10 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_func_result_section()
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 
-! Assignments performed on the host.
+! Assignments done on the host.
 
-! A right-hand side expression involving managed data is evaluated on the host to
-! avoid materializing a temporary.
+! An expression involving managed data is evaluated on the host, which avoids a
+! temporary.
 subroutine managed_expr_assign()
   integer(4), managed :: ma(16), mb(16)
   integer(4) :: ha(16)
@@ -148,7 +146,7 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_expr_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-! Scalar and element-wise accesses of managed data are performed on the host.
+! Scalar and single-element accesses are done on the host.
 subroutine managed_scalar_assign()
   integer(4), managed :: ms, mt
   integer(4) :: hs
@@ -185,8 +183,7 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_element_assign
 ! CHECK-NOT: cuf.data_transfer
 
-! A managed array section is assigned on the host, whatever the host right-hand
-! side is.
+! A managed section is assigned on the host, whatever the host right-hand side is.
 subroutine managed_section_from_host(n, hs)
   integer :: n, i
   integer(4) :: hs
@@ -203,8 +200,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_section_from_host
 ! CHECK-NOT: cuf.data_transfer
 
-! A managed array section on either side is assigned on the host: a loop assigning
-! one section at a time would otherwise become a sequence of blocking copies.
+! A managed section on either side is assigned on the host: copying section by
+! section in a loop is slow.
 subroutine managed_section_assign(n)
   integer :: n, i
   integer(4), managed :: ma(4,16), mb(4,16)
@@ -229,8 +226,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_section_assign_1d
 ! CHECK-NOT: cuf.data_transfer
 
-! The whole-array test is syntactic: a section that happens to cover the whole
-! array is still a section.
+! A section that covers the whole array is still a section: what matters is the
+! form of the reference, not the extent it happens to cover.
 subroutine managed_full_slice_assign()
   integer(4), managed :: ma(4,16), mb(4,16)
   integer(4) :: ha(4,16)
@@ -263,8 +260,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_mixed_whole_section()
 ! CHECK-NOT: cuf.data_transfer
 
-! A whole-allocatable left-hand side has reallocation semantics and is performed
-! on the host.
+! An allocatable left-hand side may be reallocated, so the assignment is done on
+! the host.
 subroutine managed_alloc_assign(n)
   integer :: n
   integer(4), allocatable, managed :: ma(:), mb(:)

>From 291ae9d23e4632f47be9a7e57037f7a56cc76411 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 16:10:12 -0700
Subject: [PATCH 4/8] format

---
 flang/include/flang/Evaluate/tools.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 4d78ace505f24..aa6f77a34ca2e 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1392,9 +1392,9 @@ template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
   return GetNbOfCUDADeviceSymbols(expr) > 0;
 }
 
-// True for a whole array variable or a whole array component (a%b), false for an
-// array section, an array element or a computed value. Note that IsArraySection()
-// counts a%b as a section, so this is not its negation.
+// True for a whole array variable or a whole array component (a%b), false for
+// an array section, an array element or a computed value. Note that
+// IsArraySection() counts a%b as a section, so this is not its negation.
 template <typename A> inline bool IsWholeArrayDesignator(const A &expr) {
   return expr.Rank() > 0 &&
       UnwrapWholeSymbolOrComponentDataRef(expr) != nullptr;

>From 362392eeadcbfda88864eebb63edc3e4ec55c08d Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 16:44:46 -0700
Subject: [PATCH 5/8] match reference compiler behavior

---
 flang/include/flang/Evaluate/tools.h          |  26 +++--
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 103 +++++++++---------
 2 files changed, 67 insertions(+), 62 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index aa6f77a34ca2e..5c08a476f50da 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1392,12 +1392,13 @@ template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
   return GetNbOfCUDADeviceSymbols(expr) > 0;
 }
 
-// True for a whole array variable or a whole array component (a%b), false for
-// an array section, an array element or a computed value. Note that
-// IsArraySection() counts a%b as a section, so this is not its negation.
-template <typename A> inline bool IsWholeArrayDesignator(const A &expr) {
-  return expr.Rank() > 0 &&
-      UnwrapWholeSymbolOrComponentDataRef(expr) != nullptr;
+// True for a whole reference to a managed or unified array: a whole array
+// variable, or a whole array component that itself has the attribute (a%b where
+// b is managed). An array section, an array element, a component of a managed
+// object and a computed value are all false.
+template <typename A> inline bool IsWholeManagedArray(const A &expr) {
+  const Symbol *sym{UnwrapWholeSymbolOrComponentDataRef(expr)};
+  return expr.Rank() > 0 && sym && IsCUDAManagedOrUnifiedSymbol(*sym);
 }
 
 // Check if any of the symbols part of the lhs or rhs expression has a CUDA
@@ -1411,30 +1412,31 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
   if (HasNonAllocatableModuleCUDAManagedSymbols(lhs))
     return false;
 
-  if (lhsNbManagedSymbols >= 1 && IsWholeArrayDesignator(lhs) &&
+  if (lhsNbManagedSymbols >= 1 && IsWholeManagedArray(lhs) &&
       rhsNbSymbols == 0 && rhsNbManagedSymbols == 0 &&
       (IsVariable(rhs) || IsConstantExpr(rhs))) {
-    return true; // Managed arrays initialization is performed on the device.
+    return true; // Initializing a whole managed array is done on the device.
   }
 
   // CUDA Fortran Programming Guide 3.4.1: an assignment involving managed or
   // unified data is a copy when the managed side is a whole variable or array,
   // and is done on the host when it is a section. The host can read and write
   // managed data directly, and copying section by section in a loop is slow.
-  bool wholeLhs{IsWholeArrayDesignator(lhs)};
-  bool wholeRhs{IsWholeArrayDesignator(rhs)};
+  bool wholeLhs{IsWholeManagedArray(lhs)};
+  bool wholeRhs{IsWholeManagedArray(rhs)};
 
   // Assignments done on the host, with no copy:
   // - A whole allocatable left-hand side: the assignment may reallocate it,
   //   which is done on the host.
-  // - A managed operand that is an array section or an element.
+  // - No managed operand is a whole array: they are all sections or elements,
+  //   which the host can read and write in place.
   // - An expression involving managed data assigned to a managed or host
   //   left-hand side: evaluating it on the host avoids a temporary.
   // - A managed left-hand side assigned from host-only data.
   if ((IsAllocatableDesignator(lhs) &&
           (lhsNbManagedSymbols >= 1 || rhsNbManagedSymbols >= 1)) ||
       (lhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
-          !(wholeLhs && wholeRhs)) ||
+          !(wholeLhs || wholeRhs)) ||
       (lhsNbManagedSymbols == 0 && !HasCUDADeviceAttrs(lhs) &&
           rhsNbManagedSymbols >= 1 && rhsNbManagedSymbols == rhsNbSymbols &&
           !wholeRhs) ||
diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index f4f0af92f77b9..11c2267d2af02 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -16,7 +16,7 @@ contains
   end function
 end module
 
-! Assignments that are copies.
+! Assignments that are data transfers.
 
 subroutine managed_array_assign()
   integer(4), managed :: ma(16), mb(16)
@@ -42,20 +42,32 @@ end subroutine
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
 
-! A whole array component (a%b) counts as a whole array.
-subroutine managed_component_assign()
+! A whole array component counts as a whole array when the component itself has
+! the managed attribute.
+subroutine managed_component_attr_assign()
   type :: t
-    integer(4) :: m(16)
+    integer(4), managed :: m(16)
   end type
-  type(t), managed :: ma, mb
-  type(t) :: ha
-  ma%m = mb%m
-  ha%m = mb%m
+  type(t) :: x, y
+  x%m = y%m
 end subroutine
 
-! CHECK-LABEL: func.func @_QPmanaged_component_assign()
+! CHECK-LABEL: func.func @_QPmanaged_component_attr_assign()
 ! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_host>
+
+! 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)
+  integer(4) :: ha(4)
+  ma = mb(:,1)        ! whole managed = managed section
+  mb(:,1) = ma        ! managed section = whole managed
+  ha = mb(:,1)        ! whole host = managed section: performed on the host
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmanaged_mixed_whole_section()
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
+! CHECK-NOT: cuf.data_transfer
 
 ! A whole managed array is copied from a constant, a host scalar, a host array or
 ! a host section.
@@ -137,27 +149,15 @@ end subroutine
 ! An expression involving managed data is evaluated on the host, which avoids a
 ! temporary.
 subroutine managed_expr_assign()
-  integer(4), managed :: ma(16), mb(16)
+  integer(4), managed :: ma(16)
   integer(4) :: ha(16)
-  ma = mb + 1    ! managed = managed expression
-  ha = ma + 1    ! host = managed expression
+  ha = ma + 1
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPmanaged_expr_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-! Scalar and single-element accesses are done on the host.
-subroutine managed_scalar_assign()
-  integer(4), managed :: ms, mt
-  integer(4) :: hs
-  ms = mt
-  hs = ms
-  ms = hs
-end subroutine
-
-! CHECK-LABEL: func.func @_QPmanaged_scalar_assign()
-! CHECK-NOT: cuf.data_transfer
-
+! Element-wise accesses of managed data are performed on the host.
 subroutine managed_scalar_access(n)
   integer :: n, i
   integer(4), managed :: ma(16)
@@ -183,7 +183,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_element_assign
 ! CHECK-NOT: cuf.data_transfer
 
-! A managed section is assigned on the host, whatever the host right-hand side is.
+! A managed array section is assigned on the host, whatever the host right-hand
+! side is.
 subroutine managed_section_from_host(n, hs)
   integer :: n, i
   integer(4) :: hs
@@ -200,8 +201,8 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_section_from_host
 ! CHECK-NOT: cuf.data_transfer
 
-! A managed section on either side is assigned on the host: copying section by
-! section in a loop is slow.
+! A managed array section on either side is assigned on the host: a loop assigning
+! one section at a time would otherwise become a sequence of blocking copies.
 subroutine managed_section_assign(n)
   integer :: n, i
   integer(4), managed :: ma(4,16), mb(4,16)
@@ -238,52 +239,54 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPmanaged_full_slice_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-subroutine managed_component_section_assign()
+! A component of a managed object is not a whole managed array: the attribute is
+! on the object, not on the component.
+subroutine managed_component_assign()
   type :: t
     integer(4) :: m(16)
   end type
   type(t), managed :: ma, mb
-  ma%m(1:8) = mb%m(1:8)
+  type(t) :: ha
+  ma%m = mb%m
+  ha%m = mb%m
 end subroutine
 
-! CHECK-LABEL: func.func @_QPmanaged_component_section_assign()
+! CHECK-LABEL: func.func @_QPmanaged_component_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-subroutine managed_mixed_whole_section()
-  integer(4), managed :: ma(4), mb(4,16)
-  integer(4) :: ha(4)
-  ma = mb(:,1)        ! whole managed = managed section
-  mb(:,1) = ma        ! managed section = whole managed
-  ha = mb(:,1)        ! whole host = managed section
+subroutine managed_component_section_assign()
+  type :: t
+    integer(4) :: m(16)
+  end type
+  type(t), managed :: ma, mb
+  ma%m(1:8) = mb%m(1:8)
 end subroutine
 
-! CHECK-LABEL: func.func @_QPmanaged_mixed_whole_section()
+! CHECK-LABEL: func.func @_QPmanaged_component_section_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-! An allocatable left-hand side may be reallocated, so the assignment is done on
-! the host.
-subroutine managed_alloc_assign(n)
-  integer :: n
-  integer(4), allocatable, managed :: ma(:), mb(:)
-  allocate(ma(n), mb(n))
-  ma = mb
+subroutine managed_component_attr_section_assign()
+  type :: t
+    integer(4), managed :: m(16)
+  end type
+  type(t) :: x, y
+  x%m(1:8) = y%m(1:8)
 end subroutine
 
-! CHECK-LABEL: func.func @_QPmanaged_alloc_assign
+! CHECK-LABEL: func.func @_QPmanaged_component_attr_section_assign()
 ! CHECK-NOT: cuf.data_transfer
 
-! The unified attribute follows the same rules as managed.
+! Sections of unified data are assigned on the host, like sections of managed
+! data.
 subroutine unified_section_assign(n)
   integer :: n, i
   integer(4), unified :: ua(4,16), ub(4,16)
   integer(4) :: ha(4,16)
-  ua = ub                ! whole array: data transfer
   do i = 1, n
-    ua(:,i) = ub(:,i)    ! sections: performed on the host
+    ua(:,i) = ub(:,i)
     ha(:,i) = ub(:,i)
   end do
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPunified_section_assign
-! CHECK: cuf.data_transfer {{.*}}#cuf.cuda_transfer<device_device>
 ! CHECK-NOT: cuf.data_transfer

>From dfede790a3e4f3c52014691455243d15254eb7e2 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 17:02:50 -0700
Subject: [PATCH 6/8] cleanup code

---
 flang/include/flang/Evaluate/tools.h | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 5c08a476f50da..4c4f7912a506a 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1412,12 +1412,6 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
   if (HasNonAllocatableModuleCUDAManagedSymbols(lhs))
     return false;
 
-  if (lhsNbManagedSymbols >= 1 && IsWholeManagedArray(lhs) &&
-      rhsNbSymbols == 0 && rhsNbManagedSymbols == 0 &&
-      (IsVariable(rhs) || IsConstantExpr(rhs))) {
-    return true; // Initializing a whole managed array is done on the device.
-  }
-
   // CUDA Fortran Programming Guide 3.4.1: an assignment involving managed or
   // unified data is a copy when the managed side is a whole variable or array,
   // and is done on the host when it is a section. The host can read and write
@@ -1425,6 +1419,11 @@ inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
   bool wholeLhs{IsWholeManagedArray(lhs)};
   bool wholeRhs{IsWholeManagedArray(rhs)};
 
+  if (wholeLhs && rhsNbSymbols == 0 && rhsNbManagedSymbols == 0 &&
+      (IsVariable(rhs) || IsConstantExpr(rhs))) {
+    return true; // Initializing a whole managed array is done on the device.
+  }
+
   // Assignments done on the host, with no copy:
   // - A whole allocatable left-hand side: the assignment may reallocate it,
   //   which is done on the host.

>From 165f8240d8653270b788569f90208b03cd8f476a Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 17:22:05 -0700
Subject: [PATCH 7/8] remove unified test case, will add in a follow-up change
 for unified

---
 flang/test/Lower/CUDA/cuda-managed-assign.cuf | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/flang/test/Lower/CUDA/cuda-managed-assign.cuf b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
index 11c2267d2af02..00d087d4eb676 100644
--- a/flang/test/Lower/CUDA/cuda-managed-assign.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-assign.cuf
@@ -275,18 +275,3 @@ end subroutine
 
 ! CHECK-LABEL: func.func @_QPmanaged_component_attr_section_assign()
 ! CHECK-NOT: cuf.data_transfer
-
-! Sections of unified data are assigned on the host, like sections of managed
-! data.
-subroutine unified_section_assign(n)
-  integer :: n, i
-  integer(4), unified :: ua(4,16), ub(4,16)
-  integer(4) :: ha(4,16)
-  do i = 1, n
-    ua(:,i) = ub(:,i)
-    ha(:,i) = ub(:,i)
-  end do
-end subroutine
-
-! CHECK-LABEL: func.func @_QPunified_section_assign
-! CHECK-NOT: cuf.data_transfer

>From f9fc891edf55267afe04040173ea851c8b4e6279 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 28 Jul 2026 20:59:19 -0700
Subject: [PATCH 8/8] fix comment

---
 flang/test/Lower/CUDA/cuda-data-transfer.cuf | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/flang/test/Lower/CUDA/cuda-data-transfer.cuf b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
index 32cfa62d0fe3c..56d2b86e7efdb 100644
--- a/flang/test/Lower/CUDA/cuda-data-transfer.cuf
+++ b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
@@ -634,8 +634,9 @@ end subroutine
 ! CHECK-LABEL: func.func @_QPsub33
 ! CHECK-NOT: cuf.data_transfer 
 
-! Only a whole managed array is initialized on the device; a section is assigned on
-! the host (CUDA Fortran Programming Guide 3.4.1).
+! A section reference is assigned on the host even when it covers the whole array:
+! what matters is the form of the reference, not the extent it happens to cover
+! (CUDA Fortran Programming Guide 3.4.1).
 subroutine sub34(n)
   integer :: n
   real(2), managed, allocatable :: dx(:)



More information about the flang-commits mailing list