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

via flang-commits flang-commits at lists.llvm.org
Tue Jul 28 12:49:42 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-fir-hlfir

Author: Zhen Wang (wangzpgi)

<details>
<summary>Changes</summary>

Assignments involving managed or unified data are lowered to `cuf.data_transfer`, a synchronous copy. The predicate only excluded scalar left-hand sides, so array sections were transferred too, turning a loop that assigns one section per iteration into a sequence of blocking copies.

Require both sides to be whole array designators for the assignment to be a transfer. Managed data is host-addressable, so performing section and element assignments on the host is safe. Whole-array assignments and managed function results keep synchronizing; device memory is unaffected. This follows the same rule as the reference compiler, which emits a managed copy only when both sides are whole variables or components.

---
Full diff: https://github.com/llvm/llvm-project/pull/212593.diff


2 Files Affected:

- (modified) flang/include/flang/Evaluate/tools.h (+18-6) 
- (modified) flang/test/Lower/CUDA/cuda-managed-assign.cuf (+187-7) 


``````````diff
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>

``````````

</details>


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


More information about the flang-commits mailing list