[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