[flang-commits] [flang] [flang][CUDA] Preserve data attrs on assignment RHS (PR #207849)

Andre Kuhlenschmidt via flang-commits flang-commits at lists.llvm.org
Tue Aug 18 10:35:01 PDT 2026


https://github.com/akuhlens updated https://github.com/llvm/llvm-project/pull/207849

>From f40cf336b44c88f83048fc1e1f7058f970eb0a6b Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Thu, 2 Jul 2026 17:49:01 -0700
Subject: [PATCH 1/7] [flang][CUDA] Preserve data attrs on assignment RHS

Treat a parenthesized variable as a source of CUDA data attributes when checking dummy/actual argument compatibility. Defined assignment parenthesizes non-VALUE RHS actuals to preserve value semantics, but the CUDA address space of the referenced data is unchanged by that materialization.

Add CUDA semantics coverage for a device RHS selected through defined assignment.
---
 flang/lib/Semantics/check-call.cpp            | 35 ++++++++++++++++++-
 .../CUDA/cuf-defined-assignment-rhs.cuf       | 34 ++++++++++++++++++
 2 files changed, 68 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf

diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp
index 8ba83be132723..b73f1810eb7cd 100644
--- a/flang/lib/Semantics/check-call.cpp
+++ b/flang/lib/Semantics/check-call.cpp
@@ -27,6 +27,37 @@ namespace characteristics = Fortran::evaluate::characteristics;
 
 namespace Fortran::semantics {
 
+template <typename A> static bool IsParenthesizedVariable(const A &) {
+  return false;
+}
+
+template <typename T>
+static bool IsParenthesizedVariable(const evaluate::Expr<T> &expr) {
+  if (const auto *parens{
+          evaluate::UnwrapExpr<evaluate::Parentheses<T>>(expr)}) {
+    return evaluate::IsVariable(parens->left());
+  }
+  return false;
+}
+
+template <evaluate::TypeCategory CAT>
+static bool IsParenthesizedVariable(
+    const evaluate::Expr<evaluate::SomeKind<CAT>> &expr) {
+  return common::visit(
+      [](const auto &x) { return IsParenthesizedVariable(x); }, expr.u);
+}
+
+static bool IsParenthesizedVariable(
+    const evaluate::Expr<evaluate::SomeType> &expr) {
+  return common::visit(
+      [](const auto &x) { return IsParenthesizedVariable(x); }, expr.u);
+}
+
+static bool IsVariableOrParenthesizedVariable(
+    const evaluate::Expr<evaluate::SomeType> &expr) {
+  return evaluate::IsVariable(expr) || IsParenthesizedVariable(expr);
+}
+
 void CheckImplicitInterfaceArgKeywords(
     const evaluate::ActualArgument &arg, parser::ContextualMessages &messages) {
   auto restorer{
@@ -1132,7 +1163,9 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
       !FindOpenACCConstructContaining(scope)) {
     std::optional<common::CUDADataAttr> actualDataAttr, dummyDataAttr;
     // For a%b%c, the last symbol with a CUDA data attribute wins
-    if (actualIsVariable) {
+    // Parentheses here mean expression/copy semantics, but CUDA address space
+    // attributes remain stable when a variable is materialized as a copy.
+    if (IsVariableOrParenthesizedVariable(actual)) {
       for (const Symbol &s : evaluate::GetSymbolVector(actual)) {
         if (const auto *object{s.detailsIf<ObjectEntityDetails>()}) {
           if (auto cudaAttr{object->cudaDataAttr()}) {
diff --git a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
new file mode 100644
index 0000000000000..bd0ab5f7c3068
--- /dev/null
+++ b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
@@ -0,0 +1,34 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1
+
+module m
+  type box
+    integer :: n
+  end type
+
+  interface assignment(=)
+    module procedure assign_host
+    module procedure assign_device
+  end interface
+
+contains
+  subroutine assign_host(lhs, rhs)
+    type(box), intent(inout) :: lhs
+    real, intent(in) :: rhs(:)
+  end subroutine
+
+  subroutine assign_device(lhs, rhs)
+    type(box), intent(inout) :: lhs
+    real, device, intent(in) :: rhs(:)
+  end subroutine
+end module
+
+subroutine test
+  use m
+  type(box) :: lhs
+  real :: host(4)
+  real, device, allocatable :: dev(:)
+  allocate(dev(4))
+
+  lhs = host
+  lhs = dev
+end subroutine

>From 89c1a81f8c17f8474498d440cfad61ba20880e20 Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Mon, 13 Jul 2026 16:38:17 -0700
Subject: [PATCH 2/7] [flang][cuda] Preserve device RHS refs in defined
 assignment

Defined assignment normally parenthesizes the RHS actual to model copy semantics. For host-side CUDA assignments from device data, the assignment body performs the value transfer with a CUDA memcpy, so preserving the device variable reference keeps the CUDA data attribute available while still producing value-like behavior.

Do not parenthesize defined-assignment RHS actuals when the actual is a DEVICE variable or has the VALUE attribute. Keep source-parenthesized actuals as expressions for normal argument checking, and add coverage for the host-side device-to-component assignment reproducer.
---
 flang/lib/Semantics/check-call.cpp            | 35 +--------
 flang/lib/Semantics/expression.cpp            | 23 ++++--
 .../CUDA/cuf-defined-assignment-rhs.cuf       | 72 ++++++++++++++++++-
 3 files changed, 90 insertions(+), 40 deletions(-)

diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp
index b73f1810eb7cd..8ba83be132723 100644
--- a/flang/lib/Semantics/check-call.cpp
+++ b/flang/lib/Semantics/check-call.cpp
@@ -27,37 +27,6 @@ namespace characteristics = Fortran::evaluate::characteristics;
 
 namespace Fortran::semantics {
 
-template <typename A> static bool IsParenthesizedVariable(const A &) {
-  return false;
-}
-
-template <typename T>
-static bool IsParenthesizedVariable(const evaluate::Expr<T> &expr) {
-  if (const auto *parens{
-          evaluate::UnwrapExpr<evaluate::Parentheses<T>>(expr)}) {
-    return evaluate::IsVariable(parens->left());
-  }
-  return false;
-}
-
-template <evaluate::TypeCategory CAT>
-static bool IsParenthesizedVariable(
-    const evaluate::Expr<evaluate::SomeKind<CAT>> &expr) {
-  return common::visit(
-      [](const auto &x) { return IsParenthesizedVariable(x); }, expr.u);
-}
-
-static bool IsParenthesizedVariable(
-    const evaluate::Expr<evaluate::SomeType> &expr) {
-  return common::visit(
-      [](const auto &x) { return IsParenthesizedVariable(x); }, expr.u);
-}
-
-static bool IsVariableOrParenthesizedVariable(
-    const evaluate::Expr<evaluate::SomeType> &expr) {
-  return evaluate::IsVariable(expr) || IsParenthesizedVariable(expr);
-}
-
 void CheckImplicitInterfaceArgKeywords(
     const evaluate::ActualArgument &arg, parser::ContextualMessages &messages) {
   auto restorer{
@@ -1163,9 +1132,7 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
       !FindOpenACCConstructContaining(scope)) {
     std::optional<common::CUDADataAttr> actualDataAttr, dummyDataAttr;
     // For a%b%c, the last symbol with a CUDA data attribute wins
-    // Parentheses here mean expression/copy semantics, but CUDA address space
-    // attributes remain stable when a variable is materialized as a copy.
-    if (IsVariableOrParenthesizedVariable(actual)) {
+    if (actualIsVariable) {
       for (const Symbol &s : evaluate::GetSymbolVector(actual)) {
         if (const auto *object{s.detailsIf<ObjectEntityDetails>()}) {
           if (auto cudaAttr{object->cudaDataAttr()}) {
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 633c778915a76..3c7756e83b95c 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5683,17 +5683,30 @@ std::optional<ProcedureRef> ArgumentAnalyzer::GetDefinedAssignmentProc(
   }
   ActualArguments actualsCopy{actuals_};
   // Ensure that the RHS argument is not passed as a variable unless
-  // the dummy argument has the VALUE attribute.
-  if (evaluate::IsVariable(actualsCopy.at(1).value().UnwrapExpr())) {
+  // the dummy argument has the VALUE attribute, or the actual's own attributes
+  // already require reference semantics that must be preserved.
+  if (const auto *rhsExpr{actualsCopy.at(1).value().UnwrapExpr()};
+      evaluate::IsVariable(rhsExpr)) {
     auto chars{evaluate::characteristics::Procedure::Characterize(
         *proc, context_.GetFoldingContext())};
     const auto *rhsDummy{chars && chars->dummyArguments.size() == 2
             ? std::get_if<evaluate::characteristics::DummyDataObject>(
                   &chars->dummyArguments.at(1).u)
             : nullptr};
-    if (!rhsDummy ||
-        !rhsDummy->attrs.test(
-            evaluate::characteristics::DummyDataObject::Attr::Value)) {
+    std::optional<common::CUDADataAttr> rhsDataAttr;
+    for (const Symbol &symbol : evaluate::GetSymbolVector(*rhsExpr)) {
+      if (auto cudaAttr{GetCUDADataAttr(&symbol)}) {
+        rhsDataAttr = *cudaAttr;
+      }
+    }
+    const Symbol *rhsFirstSymbol{evaluate::GetFirstSymbol(*rhsExpr)};
+    const bool preserveActualReference{
+        (rhsDataAttr && *rhsDataAttr == common::CUDADataAttr::Device) ||
+        (rhsFirstSymbol && IsValue(*rhsFirstSymbol))};
+    if (!preserveActualReference &&
+        (!rhsDummy ||
+            !rhsDummy->attrs.test(
+                evaluate::characteristics::DummyDataObject::Attr::Value))) {
       actualsCopy.at(1).value().Parenthesize();
     }
   }
diff --git a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
index bd0ab5f7c3068..5836a33aa9dea 100644
--- a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
+++ b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
@@ -1,4 +1,6 @@
 ! RUN: %python %S/../test_errors.py %s %flang_fc1
+! RUN: rm -rf %t && mkdir -p %t && %flang_fc1 -module-dir %t \
+! RUN:   -fdebug-unparse %s 2>&1 | FileCheck %s
 
 module m
   type box
@@ -8,6 +10,7 @@ module m
   interface assignment(=)
     module procedure assign_host
     module procedure assign_device
+    module procedure assign_scalar
   end interface
 
 contains
@@ -20,15 +23,82 @@ contains
     type(box), intent(inout) :: lhs
     real, device, intent(in) :: rhs(:)
   end subroutine
+
+  subroutine assign_scalar(lhs, rhs)
+    type(box), intent(inout) :: lhs
+    real, intent(in) :: rhs
+  end subroutine
 end module
 
-subroutine test
+subroutine test_defined_assignment
   use m
   type(box) :: lhs
   real :: host(4)
   real, device, allocatable :: dev(:)
   allocate(dev(4))
 
+  ! CHECK: CALL assign_host(lhs,(host))
   lhs = host
+
+  ! CHECK: CALL assign_device(lhs,dev)
   lhs = dev
 end subroutine
+
+module issue_reproducer
+  type t
+    real :: x(100)
+  end type
+
+  interface assignment(=)
+    module procedure assign_t
+  end interface
+
+contains
+  subroutine assign_t(host_t, dev_x)
+    type(t), intent(inout) :: host_t
+    real, device, intent(in) :: dev_x(:)
+
+    host_t%x = dev_x
+  end subroutine
+end module
+
+subroutine test_issue_reproducer
+  use issue_reproducer
+  real :: y(100)
+  real, device :: x(100)
+  type(t) :: tee
+
+  x = y
+  ! CHECK: CALL assign_t(tee,x)
+  tee = x
+end subroutine
+
+subroutine test_value_actual(rhs)
+  use m
+  type(box) :: lhs
+  real, value :: rhs
+
+  ! CHECK: CALL assign_scalar(lhs,rhs)
+  lhs = rhs
+end subroutine
+
+subroutine test_host_scalar_actual(rhs)
+  use m
+  type(box) :: lhs
+  real :: rhs
+
+  ! CHECK: CALL assign_scalar(lhs,(rhs))
+  lhs = rhs
+end subroutine
+
+subroutine test_explicit_parenthesized_actuals
+  use m
+  type(box) :: lhs
+  real :: host(4)
+  real, device, allocatable :: dev(:)
+  allocate(dev(4))
+
+  call assign_host(lhs, host)
+  call assign_host(lhs, (host))
+  call assign_device(lhs, dev)
+end subroutine

>From db948566b4c8267a8efc3b832d53ee1802941da6 Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Tue, 14 Jul 2026 11:46:06 -0700
Subject: [PATCH 3/7] [flang][cuda] Note device RHS assignment limits

Document that preserving DEVICE RHS references in defined assignment may need to be limited to device-to-host transfers or unambiguous host-code RHS references.
---
 flang/lib/Semantics/expression.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 3c7756e83b95c..0d4bee2f70c77 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5700,6 +5700,8 @@ std::optional<ProcedureRef> ArgumentAnalyzer::GetDefinedAssignmentProc(
       }
     }
     const Symbol *rhsFirstSymbol{evaluate::GetFirstSymbol(*rhsExpr)};
+    // TODO: This DEVICE exception may need to be limited to device-to-host
+    // transfers or to RHS references that appear in unambiguous host code.
     const bool preserveActualReference{
         (rhsDataAttr && *rhsDataAttr == common::CUDADataAttr::Device) ||
         (rhsFirstSymbol && IsValue(*rhsFirstSymbol))};

>From fe010ac666c80925d45a6624a7773f299a8af8e5 Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Tue, 14 Jul 2026 12:01:26 -0700
Subject: [PATCH 4/7] [flang][cuda] Test parenthesized device RHS assignment

Add coverage showing that a source-parenthesized DEVICE variable on the RHS is treated as an expression and does not take the defined-assignment DEVICE-variable reference exception.
---
 flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
index 5836a33aa9dea..a1d11256b1321 100644
--- a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
+++ b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs.cuf
@@ -42,6 +42,11 @@ subroutine test_defined_assignment
 
   ! CHECK: CALL assign_device(lhs,dev)
   lhs = dev
+
+  ! A source-parenthesized DEVICE variable is an expression, so it does not
+  ! trigger the DEVICE-variable RHS exception.
+  ! CHECK: CALL assign_host(lhs,(dev))
+  lhs = (dev)
 end subroutine
 
 module issue_reproducer

>From 1aa95d8dcd15764b80a9f40b8e555f5e8c4972b1 Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Thu, 23 Jul 2026 02:47:40 -0700
Subject: [PATCH 5/7] [flang][cuda] Avoid HLFIR copies for device RHS data

When lowering hlfir.region_assign for user-defined assignment, avoid materializing a host-side RHS value copy when the yielded RHS is CUDA device data. Preserve the address instead so the downstream CUDA transfer path can handle the device reference.

Add FIR coverage for host RHS copies, device RHS variables and sections, and parenthesized device RHS expressions.
---
 .../LowerHLFIROrderedAssignments.cpp          |  73 ++++++++++++
 .../order_assignments/cuf-device-rhs.fir      | 105 ++++++++++++++++++
 2 files changed, 178 insertions(+)
 create mode 100644 flang/test/HLFIR/order_assignments/cuf-device-rhs.fir

diff --git a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
index dcc40c7b25c5c..76268d5af425f 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
@@ -23,13 +23,17 @@
 #include "flang/Optimizer/Builder/HLFIRTools.h"
 #include "flang/Optimizer/Builder/TemporaryStorage.h"
 #include "flang/Optimizer/Builder/Todo.h"
+#include "flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h"
+#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/Passes.h"
 #include "mlir/Analysis/Liveness.h"
 #include "mlir/IR/Dominance.h"
 #include "mlir/IR/IRMapping.h"
+#include "mlir/Interfaces/FunctionInterfaces.h"
 #include "mlir/Transforms/DialectConversion.h"
 #include "mlir/Transforms/RegionUtils.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/Debug.h"
 #include <unordered_set>
@@ -687,6 +691,59 @@ static hlfir::YieldOp getYield(mlir::Region &region) {
   return yield;
 }
 
+static bool isCUDADeviceDataAttr(mlir::Attribute attr) {
+  if (auto dataAttr = mlir::dyn_cast_if_present<cuf::DataAttributeAttr>(attr))
+    return dataAttr.getValue() == cuf::DataAttribute::Device;
+  return false;
+}
+
+static bool hasCUDADeviceDataAttr(mlir::BlockArgument blockArg) {
+  mlir::Block *owner = blockArg.getOwner();
+  if (!owner)
+    return false;
+  mlir::Operation *parentOp = owner->getParentOp();
+  if (!parentOp)
+    return false;
+  auto funcLike = mlir::dyn_cast<mlir::FunctionOpInterface>(parentOp);
+  if (!funcLike || blockArg.getArgNumber() >= funcLike.getNumArguments())
+    return false;
+  unsigned argNumber = blockArg.getArgNumber();
+  return isCUDADeviceDataAttr(
+             funcLike.getArgAttr(argNumber, cuf::getDataAttrName())) ||
+         isCUDADeviceDataAttr(
+             funcLike.getArgAttr(argNumber, cuf::dataAttrName));
+}
+
+static bool isCUDADeviceData(mlir::Value value) {
+  llvm::SmallPtrSet<void *, 8> visited;
+  mlir::Value current = value;
+  while (current) {
+    if (!visited.insert(current.getAsOpaquePointer()).second)
+      return false;
+
+    if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(current))
+      return hasCUDADeviceDataAttr(blockArg);
+
+    mlir::Operation *defOp = current.getDefiningOp();
+    if (!defOp)
+      return false;
+
+    if (cuf::hasDataAttr(defOp, cuf::DataAttribute::Device))
+      return true;
+
+    if (auto result = mlir::dyn_cast<mlir::OpResult>(current))
+      if (auto viewOp =
+              mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp))
+        if (mlir::Value source = viewOp.getViewSource(result)) {
+          current = source;
+          continue;
+        }
+
+    return false;
+  }
+  return false;
+}
+
 OrderedAssignmentRewriter::ValueAndCleanUp
 OrderedAssignmentRewriter::generateYieldedEntity(
     mlir::Region &region, std::optional<mlir::Type> castToType) {
@@ -1066,6 +1123,14 @@ getAssignIfLeftHandSideRegion(mlir::Region &region) {
   return nullptr;
 }
 
+static hlfir::RegionAssignOp
+getAssignIfRightHandSideRegion(mlir::Region &region) {
+  auto assign = mlir::dyn_cast<hlfir::RegionAssignOp>(region.getParentOp());
+  if (assign && (&assign.getRhsRegion() == &region))
+    return assign;
+  return nullptr;
+}
+
 static bool isPointerAssignmentRHS(mlir::Region &region) {
   auto assign = mlir::dyn_cast<hlfir::RegionAssignOp>(region.getParentOp());
   return assign && assign.isPointerAssignment() &&
@@ -1199,6 +1264,14 @@ void OrderedAssignmentRewriter::generateSaveEntity(
            "rhs cannot be used in the loop nest where it is saved");
     return saveNonVectorSubscriptedAddress(savedEntity);
   }
+  if (hlfir::RegionAssignOp regionAssignOp =
+          getAssignIfRightHandSideRegion(region);
+      regionAssignOp && !regionAssignOp.getUserDefinedAssignment().empty() &&
+      isCUDADeviceData(getYield(region).getEntity())) {
+    // Avoid materializing a host-side value copy of CUDA device data. The
+    // address is preserved instead, matching CUDA data-transfer semantics.
+    return saveNonVectorSubscriptedAddress(savedEntity);
+  }
 
   mlir::Location loc = region.getParentOp()->getLoc();
   // Evaluate the region inside the loop nest (if any).
diff --git a/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir b/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir
new file mode 100644
index 0000000000000..db75c48baaa8e
--- /dev/null
+++ b/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir
@@ -0,0 +1,105 @@
+// Test code generation of hlfir.region_assign with CUDA device RHS variables.
+// RUN: fir-opt %s --lower-hlfir-ordered-assignments | FileCheck %s
+
+func.func @host_rhs_still_saved(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
+                                %rhs_ref: !fir.ref<!fir.array<10xf32>>) {
+  %c10 = arith.constant 10 : index
+  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  %rhs:2 = hlfir.declare %rhs_ref(%shape) {uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  hlfir.region_assign {
+    hlfir.yield %rhs#0 : !fir.ref<!fir.array<10xf32>>
+  } to {
+    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
+  } user_defined_assign (%arg0: !fir.ref<!fir.array<10xf32>>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
+    fir.call @assign_array(%arg1, %arg0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
+  }
+  return
+}
+
+// CHECK-LABEL:   func.func @host_rhs_still_saved(
+// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "rhs"}
+// CHECK:           %[[EXPR:.*]] = hlfir.as_expr %[[RHS]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
+// CHECK:           %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]](%{{.*}}) {uniq_name = ".tmp.assign"}
+// CHECK:           fir.call @assign_array(%{{.*}}, %[[TMP]]#0)
+
+func.func @device_rhs_not_copied(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
+                                 %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
+  %c10 = arith.constant 10 : index
+  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  %rhs:2 = hlfir.declare %rhs_ref(%shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  hlfir.region_assign {
+    hlfir.yield %rhs#0 : !fir.ref<!fir.array<10xf32>>
+  } to {
+    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
+  } user_defined_assign (%arg0: !fir.ref<!fir.array<10xf32>>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
+    fir.call @assign_array(%arg1, %arg0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
+  }
+  return
+}
+
+// CHECK-LABEL:   func.func @device_rhs_not_copied(
+// CHECK:           %[[LHS:.*]]:2 = hlfir.declare
+// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
+// CHECK-NOT:       hlfir.as_expr
+// CHECK-NOT:       uniq_name = ".tmp.assign"
+// CHECK:           fir.call @assign_array(%[[LHS]]#0, %[[RHS]]#0)
+
+func.func @device_rhs_section_not_copied(%lhs_ref: !fir.ref<!fir.array<9xf32>>,
+                                         %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  %c9 = arith.constant 9 : index
+  %c10 = arith.constant 10 : index
+  %lhs_shape = fir.shape %c9 : (index) -> !fir.shape<1>
+  %rhs_shape = fir.shape %c10 : (index) -> !fir.shape<1>
+  %lhs:2 = hlfir.declare %lhs_ref(%lhs_shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<9xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>)
+  %rhs:2 = hlfir.declare %rhs_ref(%rhs_shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  hlfir.region_assign {
+    %section = hlfir.designate %rhs#0 (%c2:%c10:%c1)  shape %lhs_shape : (!fir.ref<!fir.array<10xf32>>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<9xf32>>
+    hlfir.yield %section : !fir.ref<!fir.array<9xf32>>
+  } to {
+    hlfir.yield %lhs#0 : !fir.ref<!fir.array<9xf32>>
+  } user_defined_assign (%arg0: !fir.ref<!fir.array<9xf32>>) to (%arg1: !fir.ref<!fir.array<9xf32>>) {
+    fir.call @assign_array9(%arg1, %arg0) : (!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>) -> ()
+  }
+  return
+}
+
+// CHECK-LABEL:   func.func @device_rhs_section_not_copied(
+// CHECK:           %[[LHS:.*]]:2 = hlfir.declare
+// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
+// CHECK:           %[[SECTION:.*]] = hlfir.designate %[[RHS]]#0
+// CHECK-NOT:       hlfir.as_expr
+// CHECK-NOT:       uniq_name = ".tmp.assign"
+// CHECK:           fir.call @assign_array9(%[[LHS]]#0, %[[SECTION]])
+
+func.func @parenthesized_device_rhs_still_saved(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
+                                                %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
+  %c10 = arith.constant 10 : index
+  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  %rhs:2 = hlfir.declare %rhs_ref(%shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+  hlfir.region_assign {
+    %expr = hlfir.as_expr %rhs#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
+    hlfir.yield %expr : !hlfir.expr<10xf32>
+  } to {
+    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
+  } user_defined_assign (%arg0: !hlfir.expr<10xf32>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
+    %copy:3 = hlfir.associate %arg0(%shape) {uniq_name = "adapt.valuebyref"} : (!hlfir.expr<10xf32>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>, i1)
+    fir.call @assign_array(%arg1, %copy#0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
+    hlfir.end_associate %copy#1, %copy#2 : !fir.ref<!fir.array<10xf32>>, i1
+  }
+  return
+}
+
+// CHECK-LABEL:   func.func @parenthesized_device_rhs_still_saved(
+// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
+// CHECK:           %[[EXPR:.*]] = hlfir.as_expr %[[RHS]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
+// CHECK:           %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]](%{{.*}}) {uniq_name = ".tmp.assign"}
+// CHECK:           %[[COPY_EXPR:.*]] = hlfir.as_expr %[[TMP]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
+// CHECK:           hlfir.associate %[[COPY_EXPR]](%{{.*}}) {uniq_name = "adapt.valuebyref"}
+
+func.func private @assign_array(!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
+func.func private @assign_array9(!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>) -> ()

>From 3cee2d8ff119e66e6cc21640794418c0480ef700 Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Thu, 23 Jul 2026 13:42:01 -0700
Subject: [PATCH 6/7] [flang][cuda] Lower device RHS assignments directly

Lower ordinary user-defined assignments with a CUDA DEVICE RHS designator directly to the resolved assignment procedure instead of emitting hlfir.region_assign. This keeps the DEVICE designator as the call actual while source-level assignment information is still available, avoiding the later ordered-assignment copy path that would materialize an invalid host-side RHS value copy.

Keep WHERE, FORALL, and vector-subscript LHS cases on the region_assign path. Move the regression coverage to a source lowering test and remove the HLFIR transform-specific workaround.
---
 flang/lib/Lower/Bridge.cpp                    |  37 +++++-
 .../LowerHLFIROrderedAssignments.cpp          |  73 ------------
 .../order_assignments/cuf-device-rhs.fir      | 105 ------------------
 .../Lower/CUDA/cuf-defined-assignment-rhs.cuf |  61 ++++++++++
 4 files changed, 93 insertions(+), 183 deletions(-)
 delete mode 100644 flang/test/HLFIR/order_assignments/cuf-device-rhs.fir
 create mode 100644 flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index bff6b51e50e18..a060deac76f7f 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -5539,6 +5539,15 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         !lhsType->HasDeferredTypeParameter();
     const bool lhsHasVectorSubscripts =
         Fortran::evaluate::HasVectorSubscript(assign.lhs);
+    const bool rhsIsCUDADeviceDesignator = [&]() {
+      if (!userDefinedAssignment || !Fortran::evaluate::IsVariable(assign.rhs))
+        return false;
+      for (const Fortran::semantics::Symbol &sym :
+           Fortran::evaluate::CollectCudaSymbols(assign.rhs))
+        if (Fortran::semantics::IsCUDADevice(sym))
+          return true;
+      return false;
+    }();
 
     // Helper to generate the code evaluating the right-hand side.
     auto evaluateRhs = [&](Fortran::lower::StatementContext &stmtCtx) {
@@ -5584,6 +5593,28 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       return lhs;
     };
 
+    auto cleanupImplicitTemps = [&]() {
+      if (hasCUDAImplicitTransfer && !isInDeviceContext) {
+        localSymbols.popScope();
+        for (mlir::Value temp : implicitTemps)
+          fir::FreeMemOp::create(builder, loc, temp);
+      }
+    };
+
+    // Keep CUDA DEVICE designator RHS values as call actuals for user-defined
+    // assignment. A region_assign would later try to enforce RHS value
+    // semantics with a host-side copy, which cannot be formed for DEVICE data.
+    if (!isInsideHlfirForallOrWhere() && !lhsHasVectorSubscripts &&
+        rhsIsCUDADeviceDesignator) {
+      Fortran::lower::StatementContext localStmtCtx;
+      hlfir::Entity rhs = evaluateRhs(localStmtCtx);
+      hlfir::Entity lhs = evaluateLhs(localStmtCtx);
+      Fortran::lower::convertUserDefinedAssignmentToHLFIR(
+          loc, *this, *userDefinedAssignment, lhs, rhs, localSymbols);
+      cleanupImplicitTemps();
+      return;
+    }
+
     if (!isInsideHlfirForallOrWhere() && !lhsHasVectorSubscripts &&
         !userDefinedAssignment) {
       Fortran::lower::StatementContext localStmtCtx;
@@ -5620,11 +5651,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
                                   keepLhsLengthInAllocatableAssignment);
         }
       }
-      if (hasCUDAImplicitTransfer && !isInDeviceContext) {
-        localSymbols.popScope();
-        for (mlir::Value temp : implicitTemps)
-          fir::FreeMemOp::create(builder, loc, temp);
-      }
+      cleanupImplicitTemps();
       return;
     }
     // Assignments inside Forall, Where, or assignments to a vector subscripted
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
index 76268d5af425f..dcc40c7b25c5c 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIROrderedAssignments.cpp
@@ -23,17 +23,13 @@
 #include "flang/Optimizer/Builder/HLFIRTools.h"
 #include "flang/Optimizer/Builder/TemporaryStorage.h"
 #include "flang/Optimizer/Builder/Todo.h"
-#include "flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h"
-#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/Passes.h"
 #include "mlir/Analysis/Liveness.h"
 #include "mlir/IR/Dominance.h"
 #include "mlir/IR/IRMapping.h"
-#include "mlir/Interfaces/FunctionInterfaces.h"
 #include "mlir/Transforms/DialectConversion.h"
 #include "mlir/Transforms/RegionUtils.h"
-#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/Debug.h"
 #include <unordered_set>
@@ -691,59 +687,6 @@ static hlfir::YieldOp getYield(mlir::Region &region) {
   return yield;
 }
 
-static bool isCUDADeviceDataAttr(mlir::Attribute attr) {
-  if (auto dataAttr = mlir::dyn_cast_if_present<cuf::DataAttributeAttr>(attr))
-    return dataAttr.getValue() == cuf::DataAttribute::Device;
-  return false;
-}
-
-static bool hasCUDADeviceDataAttr(mlir::BlockArgument blockArg) {
-  mlir::Block *owner = blockArg.getOwner();
-  if (!owner)
-    return false;
-  mlir::Operation *parentOp = owner->getParentOp();
-  if (!parentOp)
-    return false;
-  auto funcLike = mlir::dyn_cast<mlir::FunctionOpInterface>(parentOp);
-  if (!funcLike || blockArg.getArgNumber() >= funcLike.getNumArguments())
-    return false;
-  unsigned argNumber = blockArg.getArgNumber();
-  return isCUDADeviceDataAttr(
-             funcLike.getArgAttr(argNumber, cuf::getDataAttrName())) ||
-         isCUDADeviceDataAttr(
-             funcLike.getArgAttr(argNumber, cuf::dataAttrName));
-}
-
-static bool isCUDADeviceData(mlir::Value value) {
-  llvm::SmallPtrSet<void *, 8> visited;
-  mlir::Value current = value;
-  while (current) {
-    if (!visited.insert(current.getAsOpaquePointer()).second)
-      return false;
-
-    if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(current))
-      return hasCUDADeviceDataAttr(blockArg);
-
-    mlir::Operation *defOp = current.getDefiningOp();
-    if (!defOp)
-      return false;
-
-    if (cuf::hasDataAttr(defOp, cuf::DataAttribute::Device))
-      return true;
-
-    if (auto result = mlir::dyn_cast<mlir::OpResult>(current))
-      if (auto viewOp =
-              mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp))
-        if (mlir::Value source = viewOp.getViewSource(result)) {
-          current = source;
-          continue;
-        }
-
-    return false;
-  }
-  return false;
-}
-
 OrderedAssignmentRewriter::ValueAndCleanUp
 OrderedAssignmentRewriter::generateYieldedEntity(
     mlir::Region &region, std::optional<mlir::Type> castToType) {
@@ -1123,14 +1066,6 @@ getAssignIfLeftHandSideRegion(mlir::Region &region) {
   return nullptr;
 }
 
-static hlfir::RegionAssignOp
-getAssignIfRightHandSideRegion(mlir::Region &region) {
-  auto assign = mlir::dyn_cast<hlfir::RegionAssignOp>(region.getParentOp());
-  if (assign && (&assign.getRhsRegion() == &region))
-    return assign;
-  return nullptr;
-}
-
 static bool isPointerAssignmentRHS(mlir::Region &region) {
   auto assign = mlir::dyn_cast<hlfir::RegionAssignOp>(region.getParentOp());
   return assign && assign.isPointerAssignment() &&
@@ -1264,14 +1199,6 @@ void OrderedAssignmentRewriter::generateSaveEntity(
            "rhs cannot be used in the loop nest where it is saved");
     return saveNonVectorSubscriptedAddress(savedEntity);
   }
-  if (hlfir::RegionAssignOp regionAssignOp =
-          getAssignIfRightHandSideRegion(region);
-      regionAssignOp && !regionAssignOp.getUserDefinedAssignment().empty() &&
-      isCUDADeviceData(getYield(region).getEntity())) {
-    // Avoid materializing a host-side value copy of CUDA device data. The
-    // address is preserved instead, matching CUDA data-transfer semantics.
-    return saveNonVectorSubscriptedAddress(savedEntity);
-  }
 
   mlir::Location loc = region.getParentOp()->getLoc();
   // Evaluate the region inside the loop nest (if any).
diff --git a/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir b/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir
deleted file mode 100644
index db75c48baaa8e..0000000000000
--- a/flang/test/HLFIR/order_assignments/cuf-device-rhs.fir
+++ /dev/null
@@ -1,105 +0,0 @@
-// Test code generation of hlfir.region_assign with CUDA device RHS variables.
-// RUN: fir-opt %s --lower-hlfir-ordered-assignments | FileCheck %s
-
-func.func @host_rhs_still_saved(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
-                                %rhs_ref: !fir.ref<!fir.array<10xf32>>) {
-  %c10 = arith.constant 10 : index
-  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
-  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  %rhs:2 = hlfir.declare %rhs_ref(%shape) {uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  hlfir.region_assign {
-    hlfir.yield %rhs#0 : !fir.ref<!fir.array<10xf32>>
-  } to {
-    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
-  } user_defined_assign (%arg0: !fir.ref<!fir.array<10xf32>>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
-    fir.call @assign_array(%arg1, %arg0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
-  }
-  return
-}
-
-// CHECK-LABEL:   func.func @host_rhs_still_saved(
-// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "rhs"}
-// CHECK:           %[[EXPR:.*]] = hlfir.as_expr %[[RHS]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
-// CHECK:           %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]](%{{.*}}) {uniq_name = ".tmp.assign"}
-// CHECK:           fir.call @assign_array(%{{.*}}, %[[TMP]]#0)
-
-func.func @device_rhs_not_copied(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
-                                 %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
-  %c10 = arith.constant 10 : index
-  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
-  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  %rhs:2 = hlfir.declare %rhs_ref(%shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  hlfir.region_assign {
-    hlfir.yield %rhs#0 : !fir.ref<!fir.array<10xf32>>
-  } to {
-    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
-  } user_defined_assign (%arg0: !fir.ref<!fir.array<10xf32>>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
-    fir.call @assign_array(%arg1, %arg0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
-  }
-  return
-}
-
-// CHECK-LABEL:   func.func @device_rhs_not_copied(
-// CHECK:           %[[LHS:.*]]:2 = hlfir.declare
-// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
-// CHECK-NOT:       hlfir.as_expr
-// CHECK-NOT:       uniq_name = ".tmp.assign"
-// CHECK:           fir.call @assign_array(%[[LHS]]#0, %[[RHS]]#0)
-
-func.func @device_rhs_section_not_copied(%lhs_ref: !fir.ref<!fir.array<9xf32>>,
-                                         %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
-  %c1 = arith.constant 1 : index
-  %c2 = arith.constant 2 : index
-  %c9 = arith.constant 9 : index
-  %c10 = arith.constant 10 : index
-  %lhs_shape = fir.shape %c9 : (index) -> !fir.shape<1>
-  %rhs_shape = fir.shape %c10 : (index) -> !fir.shape<1>
-  %lhs:2 = hlfir.declare %lhs_ref(%lhs_shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<9xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>)
-  %rhs:2 = hlfir.declare %rhs_ref(%rhs_shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  hlfir.region_assign {
-    %section = hlfir.designate %rhs#0 (%c2:%c10:%c1)  shape %lhs_shape : (!fir.ref<!fir.array<10xf32>>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<9xf32>>
-    hlfir.yield %section : !fir.ref<!fir.array<9xf32>>
-  } to {
-    hlfir.yield %lhs#0 : !fir.ref<!fir.array<9xf32>>
-  } user_defined_assign (%arg0: !fir.ref<!fir.array<9xf32>>) to (%arg1: !fir.ref<!fir.array<9xf32>>) {
-    fir.call @assign_array9(%arg1, %arg0) : (!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>) -> ()
-  }
-  return
-}
-
-// CHECK-LABEL:   func.func @device_rhs_section_not_copied(
-// CHECK:           %[[LHS:.*]]:2 = hlfir.declare
-// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
-// CHECK:           %[[SECTION:.*]] = hlfir.designate %[[RHS]]#0
-// CHECK-NOT:       hlfir.as_expr
-// CHECK-NOT:       uniq_name = ".tmp.assign"
-// CHECK:           fir.call @assign_array9(%[[LHS]]#0, %[[SECTION]])
-
-func.func @parenthesized_device_rhs_still_saved(%lhs_ref: !fir.ref<!fir.array<10xf32>>,
-                                                %rhs_ref: !fir.ref<!fir.array<10xf32>> {cuf.data_attr = #cuf.cuda<device>}) {
-  %c10 = arith.constant 10 : index
-  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
-  %lhs:2 = hlfir.declare %lhs_ref(%shape) {uniq_name = "lhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  %rhs:2 = hlfir.declare %rhs_ref(%shape) {data_attr = #cuf.cuda<device>, uniq_name = "rhs"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
-  hlfir.region_assign {
-    %expr = hlfir.as_expr %rhs#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
-    hlfir.yield %expr : !hlfir.expr<10xf32>
-  } to {
-    hlfir.yield %lhs#0 : !fir.ref<!fir.array<10xf32>>
-  } user_defined_assign (%arg0: !hlfir.expr<10xf32>) to (%arg1: !fir.ref<!fir.array<10xf32>>) {
-    %copy:3 = hlfir.associate %arg0(%shape) {uniq_name = "adapt.valuebyref"} : (!hlfir.expr<10xf32>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>, i1)
-    fir.call @assign_array(%arg1, %copy#0) : (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
-    hlfir.end_associate %copy#1, %copy#2 : !fir.ref<!fir.array<10xf32>>, i1
-  }
-  return
-}
-
-// CHECK-LABEL:   func.func @parenthesized_device_rhs_still_saved(
-// CHECK:           %[[RHS:.*]]:2 = hlfir.declare {{.*}} {data_attr = #cuf.cuda<device>
-// CHECK:           %[[EXPR:.*]] = hlfir.as_expr %[[RHS]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
-// CHECK:           %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]](%{{.*}}) {uniq_name = ".tmp.assign"}
-// CHECK:           %[[COPY_EXPR:.*]] = hlfir.as_expr %[[TMP]]#0 : (!fir.ref<!fir.array<10xf32>>) -> !hlfir.expr<10xf32>
-// CHECK:           hlfir.associate %[[COPY_EXPR]](%{{.*}}) {uniq_name = "adapt.valuebyref"}
-
-func.func private @assign_array(!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>) -> ()
-func.func private @assign_array9(!fir.ref<!fir.array<9xf32>>, !fir.ref<!fir.array<9xf32>>) -> ()
diff --git a/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf b/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf
new file mode 100644
index 0000000000000..22cbd0469c7ec
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf
@@ -0,0 +1,61 @@
+! RUN: rm -rf %t && mkdir -p %t
+! RUN: bbc -emit-hlfir -fcuda --module=%t %s -o - | FileCheck %s
+
+module defined_assignment_device_rhs
+  type t
+    real :: x(100)
+  end type
+
+  interface assignment(=)
+    module procedure assign_t
+  end interface
+
+contains
+  subroutine assign_t(host_t, dev_x)
+    type(t), intent(inout) :: host_t
+    real, device, intent(in) :: dev_x(:)
+
+    host_t%x = dev_x
+  end subroutine
+end module
+
+! CHECK-LABEL: func.func @_QMdefined_assignment_device_rhsPassign_t(
+! CHECK-SAME: %[[HOST_T_ARG:.*]]: !fir.ref<!fir.type<_QMdefined_assignment_device_rhsTt{{.*}}> {{.*}}, %[[DEV_X_ARG:.*]]: !fir.box<!fir.array<?xf32>> {cuf.data_attr = #cuf.cuda<device>, fir.bindc_name = "dev_x"})
+! CHECK: %[[DEV_X:.*]]:2 = hlfir.declare %[[DEV_X_ARG]] dummy_scope %{{[0-9]+}} arg {{[0-9]+}} {data_attr = #cuf.cuda<device>
+! CHECK: %[[HOST_T:.*]]:2 = hlfir.declare %[[HOST_T_ARG]] dummy_scope %{{[0-9]+}} arg {{[0-9]+}}
+! CHECK: %[[X_COMPONENT:.*]] = hlfir.designate %[[HOST_T]]#0{"x"}
+! CHECK: cuf.data_transfer %[[DEV_X]]#0 to %[[X_COMPONENT]] {transfer_kind = #cuf.cuda_transfer<device_host>}
+
+subroutine test_device_rhs
+  use defined_assignment_device_rhs
+  real :: y(100)
+  real, device :: x(100)
+  type(t) :: tee
+
+  x = y
+  tee = x
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_rhs()
+! CHECK: %[[TEE:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_device_rhsEtee"}
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {data_attr = #cuf.cuda<device>, uniq_name = "_QFtest_device_rhsEx"}
+! CHECK: %[[Y:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {uniq_name = "_QFtest_device_rhsEy"}
+! CHECK: cuf.data_transfer %[[Y]]#0 to %[[X]]#0 {transfer_kind = #cuf.cuda_transfer<host_device>}
+! CHECK-NOT: hlfir.region_assign
+! CHECK: fir.call @_QMdefined_assignment_device_rhsPassign_t(%[[TEE]]#0, %{{.*}}) {{.*}}: (!fir.ref<!fir.type<_QMdefined_assignment_device_rhsTt{{.*}}>>, !fir.box<!fir.array<?xf32>>) -> ()
+
+subroutine test_device_rhs_section
+  use defined_assignment_device_rhs
+  real, device :: x(100)
+  type(t) :: tee
+
+  tee = x(2:100)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_rhs_section()
+! CHECK: %[[TEE:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_device_rhs_sectionEtee"}
+! CHECK: %[[X:.*]]:2 = hlfir.declare %{{.*}}(%{{.*}}) {data_attr = #cuf.cuda<device>, uniq_name = "_QFtest_device_rhs_sectionEx"}
+! CHECK-NOT: hlfir.region_assign
+! CHECK: %[[SECTION:.*]] = hlfir.designate %[[X]]#0 (%{{.*}}:%{{.*}}:%{{.*}})  shape %{{.*}} : (!fir.ref<!fir.array<100xf32>>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<99xf32>>
+! CHECK-NOT: hlfir.region_assign
+! CHECK: fir.call @_QMdefined_assignment_device_rhsPassign_t(%[[TEE]]#0, %{{.*}}) {{.*}}: (!fir.ref<!fir.type<_QMdefined_assignment_device_rhsTt{{.*}}>>, !fir.box<!fir.array<?xf32>>) -> ()

>From 6a8f7e66dddaf030bcc1e5228a2e0780bb0a1edd Mon Sep 17 00:00:00 2001
From: Andre Kuhlenschmidt <akuhlenschmi at nvidia.com>
Date: Fri, 7 Aug 2026 16:51:25 -0700
Subject: [PATCH 7/7] [flang][CUDA] Diagnose unsupported device RHS defined
 assignment

---
 flang/lib/Semantics/expression.cpp            | 35 ++++++++++++-
 .../Lower/CUDA/cuf-defined-assignment-rhs.cuf | 16 ++++++
 ...ned-assignment-rhs-not-yet-implemented.cuf | 51 +++++++++++++++++++
 3 files changed, 101 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Semantics/CUDA/cuf-defined-assignment-rhs-not-yet-implemented.cuf

diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 0d4bee2f70c77..efbf862a72c51 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5591,7 +5591,40 @@ std::optional<ProcedureRef> ArgumentAnalyzer::TryDefinedAssignment() {
   bool isAmbiguous{false};
   if (std::optional<ProcedureRef> procRef{
           GetDefinedAssignmentProc(isAmbiguous)}) {
-    if (context_.inWhereBody() && !procRef->proc().IsElemental()) { // C1032
+    bool hasCUDADeviceRhs{false};
+    for (const semantics::Symbol &symbol : CollectCudaSymbols(rhs)) {
+      if (semantics::IsCUDADevice(symbol)) {
+        hasCUDADeviceRhs = true;
+        break;
+      }
+    }
+    const semantics::Symbol *rhsSymbol{UnwrapWholeSymbolDataRef(rhs)};
+    bool hasCUDADeviceAssociateRhs{false};
+    if (rhsSymbol) {
+      if (const auto *associate{
+              rhsSymbol->detailsIf<semantics::AssocEntityDetails>()}) {
+        if (const auto &selector{associate->expr()}) {
+          for (const semantics::Symbol &symbol :
+              CollectCudaSymbols(*selector)) {
+            if (semantics::IsCUDADevice(symbol)) {
+              hasCUDADeviceAssociateRhs = true;
+              break;
+            }
+          }
+        }
+      }
+    }
+    if (hasCUDADeviceRhs && context_.inWhereBody()) {
+      context_.Say(
+          "Defined assignment in WHERE with a CUDA DEVICE right-hand side is not yet implemented"_todo_en_US);
+    } else if (hasCUDADeviceRhs && procRef->proc().IsElemental()) {
+      context_.Say(
+          "Elemental defined assignment with a CUDA DEVICE right-hand side is not yet implemented"_todo_en_US);
+    } else if (hasCUDADeviceAssociateRhs) {
+      context_.Say(
+          "Defined assignment from an ASSOCIATE name with a CUDA DEVICE target is not yet implemented"_todo_en_US);
+    } else if (context_.inWhereBody() &&
+        !procRef->proc().IsElemental()) { // C1032
       context_.Say(
           "Defined assignment in WHERE must be elemental, but '%s' is not"_err_en_US,
           DEREF(procRef->proc().GetSymbol()).name());
diff --git a/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf b/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf
index 22cbd0469c7ec..ba0ea1ec1cef7 100644
--- a/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf
+++ b/flang/test/Lower/CUDA/cuf-defined-assignment-rhs.cuf
@@ -59,3 +59,19 @@ end subroutine
 ! CHECK: %[[SECTION:.*]] = hlfir.designate %[[X]]#0 (%{{.*}}:%{{.*}}:%{{.*}})  shape %{{.*}} : (!fir.ref<!fir.array<100xf32>>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<99xf32>>
 ! CHECK-NOT: hlfir.region_assign
 ! CHECK: fir.call @_QMdefined_assignment_device_rhsPassign_t(%[[TEE]]#0, %{{.*}}) {{.*}}: (!fir.ref<!fir.type<_QMdefined_assignment_device_rhsTt{{.*}}>>, !fir.box<!fir.array<?xf32>>) -> ()
+
+subroutine test_device_rhs_pointer
+  use defined_assignment_device_rhs
+  real, device, target :: x(100)
+  real, device, pointer :: p(:)
+  type(t) :: tee
+
+  p => x
+  tee = p
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_rhs_pointer()
+! CHECK: %[[P:.*]]:2 = hlfir.declare %{{.*}} {data_attr = #cuf.cuda<device>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFtest_device_rhs_pointerEp"}
+! CHECK: %[[TEE:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_device_rhs_pointerEtee"}
+! CHECK-NOT: hlfir.region_assign
+! CHECK: fir.call @_QMdefined_assignment_device_rhsPassign_t(%[[TEE]]#0, %{{.*}}) {{.*}}: (!fir.ref<!fir.type<_QMdefined_assignment_device_rhsTt{{.*}}>>, !fir.box<!fir.array<?xf32>>) -> ()
diff --git a/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs-not-yet-implemented.cuf b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs-not-yet-implemented.cuf
new file mode 100644
index 0000000000000..bb120e72f1229
--- /dev/null
+++ b/flang/test/Semantics/CUDA/cuf-defined-assignment-rhs-not-yet-implemented.cuf
@@ -0,0 +1,51 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1
+
+module defined_assignment_device_rhs_todo
+  type t
+    integer :: n
+  end type
+
+  interface assignment(=)
+    module procedure assign_t
+  end interface
+
+contains
+  elemental subroutine assign_t(host_t, dev_x)
+    type(t), intent(inout) :: host_t
+    integer, device, intent(in) :: dev_x
+
+    host_t%n = dev_x
+  end subroutine
+end module
+
+subroutine test_associate_device_rhs
+  use defined_assignment_device_rhs_todo
+  integer, device :: x(4)
+  type(t) :: tee(4)
+
+  associate (a => x)
+    !ERROR: not yet implemented: Defined assignment from an ASSOCIATE name with a CUDA DEVICE target is not yet implemented
+    tee = a
+  end associate
+end subroutine
+
+subroutine test_elemental_device_rhs
+  use defined_assignment_device_rhs_todo
+  integer, device :: x(4)
+  type(t) :: tee(4)
+
+  !ERROR: not yet implemented: Elemental defined assignment with a CUDA DEVICE right-hand side is not yet implemented
+  tee = x
+end subroutine
+
+subroutine test_where_device_rhs
+  use defined_assignment_device_rhs_todo
+  integer, device :: x(4)
+  logical :: mask(4)
+  type(t) :: tee(4)
+
+  where (mask)
+    !ERROR: not yet implemented: Defined assignment in WHERE with a CUDA DEVICE right-hand side is not yet implemented
+    tee = x
+  end where
+end subroutine



More information about the flang-commits mailing list