[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 Jul 14 11:46:59 PDT 2026


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

>From 399739ffead78424596b0b112b305e9217da255c 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/3] [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 16c1e166ded66..060459ea0d495 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{
@@ -1124,7 +1155,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 2b28fbc9dfb67fd41b93cde09d3f031b88bf69a3 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/3] [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 060459ea0d495..16c1e166ded66 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{
@@ -1155,9 +1124,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 c860f999ab239..f59a20ae285c3 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5434,17 +5434,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 a66675a617acc81a6614fa83a336edf2ecdbd21c 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/3] [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 f59a20ae285c3..424eda3a35e32 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5451,6 +5451,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))};



More information about the flang-commits mailing list