[flang-commits] [flang] [flang][cuda] Resolve scalar constant address to device copy for reads (PR #227802)
via flang-commits
flang-commits at lists.llvm.org
Thu Oct 1 13:55:00 PDT 2026
https://github.com/yebinchon updated https://github.com/llvm/llvm-project/pull/227802
>From 363b1cfa5955569da3db07d400956d6594e97e20 Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Wed, 30 Sep 2026 09:48:46 -0700
Subject: [PATCH 1/3] keep constant scalar reads on host
---
flang/lib/Lower/Bridge.cpp | 32 ++++++++++++++++++++
flang/test/Lower/CUDA/cuda-data-transfer.cuf | 25 +++++++++++++++
2 files changed, 57 insertions(+)
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 8bd073a479e19..0081e0e71eb4c 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -5484,6 +5484,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
hlfir::Entity &lhs, hlfir::Entity &rhs,
bool isWholeAllocatableAssignment,
bool keepLhsLengthInAllocatableAssignment) {
+
bool lhsIsDevice = Fortran::evaluate::HasCUDADeviceAttrs(assign.lhs);
// A managed/unified/device function result is not visible to the symbol
// collection used by HasCUDADeviceAttrs (a ProcedureRef contributes no
@@ -5587,10 +5588,41 @@ class FirConverter : public Fortran::lower::AbstractConverter {
keepLhsLengthInAllocatableAssignment);
return;
}
+ // True iff every CUDA symbol the RHS references is a scalar constant (or
+ // host-resident pinned). A scalar constant keeps a synchronized host
+ // copy, so such a read can be done on the host with no device->host copy
+ // (issuing one would pass the host copy's address as a bogus "device"
+ // source). Array constants live in real device memory with no host copy,
+ // so a read of one still needs the copy.
+ auto rhsIsScalarConstantOnly = [&]() {
+ bool sawConstant = false;
+ for (const Fortran::semantics::Symbol &sym :
+ Fortran::evaluate::CollectCudaSymbols(assign.rhs)) {
+ const Fortran::semantics::Symbol &ultimate = sym.GetUltimate();
+ std::optional<Fortran::common::CUDADataAttr> attr =
+ Fortran::semantics::GetCUDADataAttr(&ultimate);
+ if (!attr || *attr == Fortran::common::CUDADataAttr::Pinned)
+ continue; // host-resident
+ if (*attr == Fortran::common::CUDADataAttr::Constant &&
+ ultimate.Rank() == 0) {
+ sawConstant = true;
+ continue;
+ }
+ return false; // device/managed/unified/array constant -> keep copy
+ }
+ return sawConstant;
+ };
+
auto transferKindAttr = cuf::DataTransferKindAttr::get(
builder.getContext(), cuf::DataTransferKind::DeviceHost);
if (fir::isa_trivial(rhsVal.getType())) {
fir::StoreOp::create(builder, loc, rhsVal, lhsVal);
+ } else if (rhsIsScalarConstantOnly()) {
+ // Assign on the host: the RHS's only device residence is scalar
+ // constant data, which has a synchronized host copy.
+ hlfir::AssignOp::create(builder, loc, rhs, lhs,
+ isWholeAllocatableAssignment,
+ keepLhsLengthInAllocatableAssignment);
} else {
cuf::DataTransferOp::create(builder, loc, rhsVal, lhsVal, shape,
transferKindAttr,
diff --git a/flang/test/Lower/CUDA/cuda-data-transfer.cuf b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
index 7cc40773d2789..55db7ab2657fa 100644
--- a/flang/test/Lower/CUDA/cuda-data-transfer.cuf
+++ b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
@@ -29,6 +29,7 @@ module mod1
real(kind=8), device, allocatable, dimension(:) :: p
real, constant :: c1 = 1.0
+ real, constant :: carr(10)
real, device :: d1 = 1.0
interface
@@ -795,3 +796,27 @@ end subroutine
! CHECK: arith.divf
! CHECK: hlfir.assign
! CHECK: cuf.data_transfer
+
+! A host read of a scalar constant broadcast into a host array keeps a
+! synchronized host copy, so it is a plain host assignment with no transfer.
+subroutine sub47()
+ use mod1
+ real, allocatable :: e(:)
+ allocate(e(10))
+ e = c1
+end subroutine
+
+! CHECK-LABEL: func.func @_QPsub47()
+! CHECK: hlfir.assign
+! CHECK-NOT: cuf.data_transfer
+
+! An array constant lives in device memory (no host copy), so a host read of
+! one must still be copied from the device.
+subroutine sub48()
+ use mod1
+ real :: harr(10)
+ harr = carr
+end subroutine
+
+! CHECK-LABEL: func.func @_QPsub48()
+! CHECK: cuf.data_transfer %{{.*}} to %{{.*}} {transfer_kind = #cuf.cuda_transfer<device_host>}
>From 5ba37816a9aa47c6d241cf4317e5ad31824c66f5 Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Wed, 30 Sep 2026 16:41:22 -0700
Subject: [PATCH 2/3] restore data transfer for host=constant. resolve constant
address to decive copy
---
flang/lib/Lower/Bridge.cpp | 32 -------
.../Transforms/CUDA/CUFOpConversion.cpp | 91 ++++++++++++-------
flang/test/Fir/CUDA/cuda-data-transfer.fir | 44 +++++++++
flang/test/Fir/CUDA/cuda-global-addr.mlir | 12 ++-
flang/test/Lower/CUDA/cuda-data-transfer.cuf | 25 -----
5 files changed, 109 insertions(+), 95 deletions(-)
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 0081e0e71eb4c..8bd073a479e19 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -5484,7 +5484,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
hlfir::Entity &lhs, hlfir::Entity &rhs,
bool isWholeAllocatableAssignment,
bool keepLhsLengthInAllocatableAssignment) {
-
bool lhsIsDevice = Fortran::evaluate::HasCUDADeviceAttrs(assign.lhs);
// A managed/unified/device function result is not visible to the symbol
// collection used by HasCUDADeviceAttrs (a ProcedureRef contributes no
@@ -5588,41 +5587,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
keepLhsLengthInAllocatableAssignment);
return;
}
- // True iff every CUDA symbol the RHS references is a scalar constant (or
- // host-resident pinned). A scalar constant keeps a synchronized host
- // copy, so such a read can be done on the host with no device->host copy
- // (issuing one would pass the host copy's address as a bogus "device"
- // source). Array constants live in real device memory with no host copy,
- // so a read of one still needs the copy.
- auto rhsIsScalarConstantOnly = [&]() {
- bool sawConstant = false;
- for (const Fortran::semantics::Symbol &sym :
- Fortran::evaluate::CollectCudaSymbols(assign.rhs)) {
- const Fortran::semantics::Symbol &ultimate = sym.GetUltimate();
- std::optional<Fortran::common::CUDADataAttr> attr =
- Fortran::semantics::GetCUDADataAttr(&ultimate);
- if (!attr || *attr == Fortran::common::CUDADataAttr::Pinned)
- continue; // host-resident
- if (*attr == Fortran::common::CUDADataAttr::Constant &&
- ultimate.Rank() == 0) {
- sawConstant = true;
- continue;
- }
- return false; // device/managed/unified/array constant -> keep copy
- }
- return sawConstant;
- };
-
auto transferKindAttr = cuf::DataTransferKindAttr::get(
builder.getContext(), cuf::DataTransferKind::DeviceHost);
if (fir::isa_trivial(rhsVal.getType())) {
fir::StoreOp::create(builder, loc, rhsVal, lhsVal);
- } else if (rhsIsScalarConstantOnly()) {
- // Assign on the host: the RHS's only device residence is scalar
- // constant data, which has a synchronized host copy.
- hlfir::AssignOp::create(builder, loc, rhs, lhs,
- isWholeAllocatableAssignment,
- keepLhsLengthInAllocatableAssignment);
} else {
cuf::DataTransferOp::create(builder, loc, rhsVal, lhsVal, shape,
transferKindAttr,
diff --git a/flang/lib/Optimizer/Transforms/CUDA/CUFOpConversion.cpp b/flang/lib/Optimizer/Transforms/CUDA/CUFOpConversion.cpp
index 596368d94e434..49af22f2b119c 100644
--- a/flang/lib/Optimizer/Transforms/CUDA/CUFOpConversion.cpp
+++ b/flang/lib/Optimizer/Transforms/CUDA/CUFOpConversion.cpp
@@ -105,6 +105,29 @@ static bool isScalarCudaConstantGlobal(fir::GlobalOp global) {
fir::isa_trivial(fir::unwrapRefType(global.getType()));
}
+// The fir.addr_of feeding a declared value, or null.
+static fir::AddrOfOp getDeclaredAddrOf(mlir::Value val) {
+ if (auto declareOp = val.getDefiningOp<fir::DeclareOp>())
+ return declareOp.getMemref().getDefiningOp<fir::AddrOfOp>();
+ if (auto declareOp = val.getDefiningOp<hlfir::DeclareOp>())
+ return declareOp.getMemref().getDefiningOp<fir::AddrOfOp>();
+ return {};
+}
+
+// Address of the host shadow when val designates a scalar CUDA constant, null
+// otherwise.
+static fir::AddrOfOp
+getScalarConstantShadowAddr(mlir::Value val, const mlir::SymbolTable &symtab) {
+ fir::AddrOfOp addrOfOp = getDeclaredAddrOf(val);
+ if (!addrOfOp)
+ return {};
+ auto global = symtab.lookup<fir::GlobalOp>(
+ addrOfOp.getSymbol().getRootReference().getValue());
+ if (!isScalarCudaConstantGlobal(global))
+ return {};
+ return addrOfOp;
+}
+
struct DeclareOpConversion : public mlir::OpRewritePattern<fir::DeclareOp> {
using OpRewritePattern::OpRewritePattern;
@@ -300,6 +323,31 @@ struct CUFDataTransferOpConversion
mlir::Value modeValue =
builder.createIntegerConstant(loc, builder.getI32Type(), mode);
+ // A host read of a scalar CUDA constant takes the device copy. Its host
+ // shadow is maintained only to service host writes; resolve the source to
+ // the registered device address so the device->host copy reads real device
+ // memory. The host shadow address would be an invalid "device" source for
+ // cudaMemcpy on a strict runtime. This is the read counterpart of the
+ // scalar-constant handling in the host-to-device and device-to-device
+ // paths below.
+ //
+ // Unlike those paths, this rewrites the op source itself and must run
+ // before the descriptor split: a read destination can be a scalar, an
+ // array (scalar broadcast, lowered via CUFDataTransferCstDesc) or a
+ // descriptor, and each of those paths reads op.getSrc() at a different
+ // point below. Fixing up the source here lets every one of them pick up the
+ // device address.
+ if (op.getTransferKind() == cuf::DataTransferKind::DeviceHost) {
+ if (fir::AddrOfOp shadow =
+ getScalarConstantShadowAddr(op.getSrc(), symtab)) {
+ mlir::Value devAddr = cuf::DeviceAddressOp::create(
+ rewriter, loc, op.getSrc().getType(), shadow.getSymbol());
+ rewriter.startOpModification(op);
+ op.getSrcMutable().assign(devAddr);
+ rewriter.finalizeOpModification(op);
+ }
+ }
+
// Convert data transfer without any descriptor.
if (!mlir::isa<fir::BaseBoxType>(srcTy) &&
!mlir::isa<fir::BaseBoxType>(dstTy)) {
@@ -352,41 +400,14 @@ struct CUFDataTransferOpConversion
mlir::Value dst = op.getDst();
mlir::Value src = op.getSrc();
- // Scalar CUDA constants keep a host shadow for host reads. Host-to-device
- // assignments also update the device constant symbol.
- auto getAddrOf = [](mlir::Value val) -> fir::AddrOfOp {
- if (auto declareOp = val.getDefiningOp<fir::DeclareOp>())
- return declareOp.getMemref().getDefiningOp<fir::AddrOfOp>();
- if (auto declareOp = val.getDefiningOp<hlfir::DeclareOp>())
- return declareOp.getMemref().getDefiningOp<fir::AddrOfOp>();
- return {};
- };
- // Address of the host shadow when val designates a scalar CUDA constant,
- // null otherwise.
- auto getShadowAddrOf = [&](mlir::Value val) -> fir::AddrOfOp {
- fir::AddrOfOp addrOfOp = getAddrOf(val);
- if (!addrOfOp)
- return {};
- auto global = symtab.lookup<fir::GlobalOp>(
- addrOfOp.getSymbol().getRootReference().getValue());
- if (!isScalarCudaConstantGlobal(global))
- return {};
- return addrOfOp;
- };
- if (op.getTransferKind() == cuf::DataTransferKind::DeviceHost) {
- if (getShadowAddrOf(src) && fir::isa_ref_type(dst.getType())) {
- mlir::Value hostValue = fir::LoadOp::create(builder, loc, src);
- hostValue = createConvertOp(rewriter, loc, dstTy, hostValue);
- fir::StoreOp::create(builder, loc, hostValue, dst);
- rewriter.eraseOp(op);
- return mlir::success();
- }
- }
+ // A scalar CUDA constant keeps a host shadow that host-to-device
+ // assignments update (so host writes stay visible); host reads take the
+ // device copy and were redirected to it above.
if (op.getTransferKind() == cuf::DataTransferKind::HostDevice) {
// A non-null shadow means the destination is a scalar constant. Keep
- // its shadow up to date for later host reads, then aim the copy at the
- // device symbol instead of the shadow.
- if (fir::AddrOfOp addrOfOp = getShadowAddrOf(dst)) {
+ // its host shadow up to date, then aim the copy at the device symbol
+ // instead of the shadow.
+ if (fir::AddrOfOp addrOfOp = getScalarConstantShadowAddr(dst, symtab)) {
mlir::Value hostValue = src;
if (fir::isa_ref_type(src.getType()))
hostValue = fir::LoadOp::create(builder, loc, src);
@@ -403,8 +424,8 @@ struct CUFDataTransferOpConversion
// hand is a host address and cannot be an operand of a device to
// device copy. Route the transfer through the shadow. There are three
// cases, depending on which side is a scalar constant.
- fir::AddrOfOp srcShadow = getShadowAddrOf(src);
- fir::AddrOfOp dstShadow = getShadowAddrOf(dst);
+ fir::AddrOfOp srcShadow = getScalarConstantShadowAddr(src, symtab);
+ fir::AddrOfOp dstShadow = getScalarConstantShadowAddr(dst, symtab);
if (srcShadow || dstShadow) {
if (dstShadow) {
if (srcShadow) {
diff --git a/flang/test/Fir/CUDA/cuda-data-transfer.fir b/flang/test/Fir/CUDA/cuda-data-transfer.fir
index 99ba60ad1e6a5..904f005b948e0 100644
--- a/flang/test/Fir/CUDA/cuda-data-transfer.fir
+++ b/flang/test/Fir/CUDA/cuda-data-transfer.fir
@@ -233,6 +233,50 @@ func.func @_QPsub9() {
// CHECK: %[[SRC:.*]] = fir.convert %[[LOCAL]] : (!fir.ref<!fir.array<5xi32>>) -> !fir.llvm_ptr<i8>
// CHECK: fir.call @_FortranACUFDataTransferPtrPtr(%[[DST]], %[[SRC]], %{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (!fir.llvm_ptr<i8>, !fir.llvm_ptr<i8>, i64, i32, !fir.ref<i8>, i32) -> ()
+fir.global @_QMmscalEc0 {data_attr = #cuf.cuda<constant>} : i32 {
+ %0 = arith.constant 2 : i32
+ fir.has_value %0 : i32
+}
+func.func @_QPsub_cst_read() {
+ %0 = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFsub_cst_readEi"}
+ %1 = fir.declare %0 {uniq_name = "_QFsub_cst_readEi"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %2 = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+ %3 = fir.declare %2 {data_attr = #cuf.cuda<constant>, uniq_name = "_QMmscalEc0"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ cuf.data_transfer %3 to %1 {transfer_kind = #cuf.cuda_transfer<device_host>} : !fir.ref<i32>, !fir.ref<i32>
+ return
+}
+
+// A host read of a scalar constant resolves to the device copy: the source is
+// routed through _FortranACUFGetDeviceAddress rather than loaded from the host
+// shadow.
+// CHECK-LABEL: func.func @_QPsub_cst_read()
+// CHECK: fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL:.*]] = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL_CONV:.*]] = fir.convert %[[GBL]] : (!fir.ref<i32>) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFGetDeviceAddress(%[[GBL_CONV]], %{{.*}}, %{{.*}}) : (!fir.llvm_ptr<i8>, !fir.ref<i8>, i32) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFDataTransferPtrPtr(
+
+func.func @_QPsub_cst_bcast() {
+ %c10 = arith.constant 10 : index
+ %0 = fir.alloca !fir.array<10xi32> {bindc_name = "e", uniq_name = "_QFsub_cst_bcastEe"}
+ %1 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %2 = fir.declare %0(%1) {uniq_name = "_QFsub_cst_bcastEe"} : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xi32>>
+ %3 = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+ %4 = fir.declare %3 {data_attr = #cuf.cuda<constant>, uniq_name = "_QMmscalEc0"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ cuf.data_transfer %4 to %2 {transfer_kind = #cuf.cuda_transfer<device_host>} : !fir.ref<i32>, !fir.ref<!fir.array<10xi32>>
+ return
+}
+
+// A scalar constant broadcast into a host array is also read from the device
+// copy: the source is routed through _FortranACUFGetDeviceAddress and then
+// broadcast by _FortranACUFDataTransferCstDesc.
+// CHECK-LABEL: func.func @_QPsub_cst_bcast()
+// CHECK: fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL:.*]] = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL_CONV:.*]] = fir.convert %[[GBL]] : (!fir.ref<i32>) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFGetDeviceAddress(%[[GBL_CONV]], %{{.*}}, %{{.*}}) : (!fir.llvm_ptr<i8>, !fir.ref<i8>, i32) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFDataTransferCstDesc(
+
fir.global @_QMmod1Ea {data_attr = #cuf.cuda<device>} : !fir.box<!fir.heap<!fir.array<?xi32>>> {
%c0 = arith.constant 0 : index
%0 = fir.zero_bits !fir.heap<!fir.array<?xi32>>
diff --git a/flang/test/Fir/CUDA/cuda-global-addr.mlir b/flang/test/Fir/CUDA/cuda-global-addr.mlir
index 74eadbca407ec..33a29dc22fa35 100644
--- a/flang/test/Fir/CUDA/cuda-global-addr.mlir
+++ b/flang/test/Fir/CUDA/cuda-global-addr.mlir
@@ -170,13 +170,19 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<f80, dense<128> :
// CHECK: fir.call @_FortranACUFGetDeviceAddress
// CHECK-NOT: fir.load %{{.*}} : !fir.ref<i32>
// CHECK: fir.call @_QPuse_index
+// A host read of a scalar CUDA constant is copied from the device symbol. The
+// host shadow is never read: the source is resolved to the device address and
+// the value is brought in with a device to host copy (mode 1).
// CHECK-LABEL: func.func @_QQconstant_scalar_device_to_host()
// CHECK: %[[ADDR:.*]] = fir.address_of(@_QMcon3Ezzz) : !fir.ref<i32>
// CHECK: %[[DECL:.*]] = fir.declare %[[ADDR]] {data_attr = #cuf.cuda<constant>, uniq_name = "_QMcon3Ezzz"} : (!fir.ref<i32>) -> !fir.ref<i32>
// CHECK: %[[DST:.*]] = fir.alloca i32
-// CHECK: %[[VALUE:.*]] = fir.load %[[DECL]] : !fir.ref<i32>
-// CHECK: fir.store %[[VALUE]] to %[[DST]] : !fir.ref<i32>
-// CHECK-NOT: fir.call @_FortranACUFDataTransferPtrPtr
+// CHECK-NOT: fir.load %[[DECL]] : !fir.ref<i32>
+// CHECK: fir.call @_FortranACUFGetDeviceAddress
+// CHECK: %[[DEVADDR:.*]] = fir.convert %{{.*}} : (!fir.llvm_ptr<i8>) -> !fir.ref<i32>
+// CHECK: %[[DSTCONV:.*]] = fir.convert %[[DST]] : (!fir.ref<i32>) -> !fir.llvm_ptr<i8>
+// CHECK: %[[SRCCONV:.*]] = fir.convert %[[DEVADDR]] : (!fir.ref<i32>) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFDataTransferPtrPtr(%[[DSTCONV]], %[[SRCCONV]], %{{[^,]*}}, %c1_i32
// -----
diff --git a/flang/test/Lower/CUDA/cuda-data-transfer.cuf b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
index 55db7ab2657fa..7cc40773d2789 100644
--- a/flang/test/Lower/CUDA/cuda-data-transfer.cuf
+++ b/flang/test/Lower/CUDA/cuda-data-transfer.cuf
@@ -29,7 +29,6 @@ module mod1
real(kind=8), device, allocatable, dimension(:) :: p
real, constant :: c1 = 1.0
- real, constant :: carr(10)
real, device :: d1 = 1.0
interface
@@ -796,27 +795,3 @@ end subroutine
! CHECK: arith.divf
! CHECK: hlfir.assign
! CHECK: cuf.data_transfer
-
-! A host read of a scalar constant broadcast into a host array keeps a
-! synchronized host copy, so it is a plain host assignment with no transfer.
-subroutine sub47()
- use mod1
- real, allocatable :: e(:)
- allocate(e(10))
- e = c1
-end subroutine
-
-! CHECK-LABEL: func.func @_QPsub47()
-! CHECK: hlfir.assign
-! CHECK-NOT: cuf.data_transfer
-
-! An array constant lives in device memory (no host copy), so a host read of
-! one must still be copied from the device.
-subroutine sub48()
- use mod1
- real :: harr(10)
- harr = carr
-end subroutine
-
-! CHECK-LABEL: func.func @_QPsub48()
-! CHECK: cuf.data_transfer %{{.*}} to %{{.*}} {transfer_kind = #cuf.cuda_transfer<device_host>}
>From 1aedbd4c0afc236c39f1e5d089fc67ebf64e7e0e Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Thu, 1 Oct 2026 12:57:19 -0700
Subject: [PATCH 3/3] add array destination test
---
flang/test/Fir/CUDA/cuda-data-transfer.fir | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/flang/test/Fir/CUDA/cuda-data-transfer.fir b/flang/test/Fir/CUDA/cuda-data-transfer.fir
index 904f005b948e0..002894bb0ab17 100644
--- a/flang/test/Fir/CUDA/cuda-data-transfer.fir
+++ b/flang/test/Fir/CUDA/cuda-data-transfer.fir
@@ -277,6 +277,26 @@ func.func @_QPsub_cst_bcast() {
// CHECK: fir.call @_FortranACUFGetDeviceAddress(%[[GBL_CONV]], %{{.*}}, %{{.*}}) : (!fir.llvm_ptr<i8>, !fir.ref<i8>, i32) -> !fir.llvm_ptr<i8>
// CHECK: fir.call @_FortranACUFDataTransferCstDesc(
+func.func @_QPsub_cst_bcast_alloc() {
+ %0 = fir.alloca !fir.box<!fir.heap<!fir.array<?xi32>>> {bindc_name = "e", uniq_name = "_QFsub_cst_bcast_allocEe"}
+ %1:2 = hlfir.declare %0 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFsub_cst_bcast_allocEe"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ %2 = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+ %3 = fir.declare %2 {data_attr = #cuf.cuda<constant>, uniq_name = "_QMmscalEc0"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ cuf.data_transfer %3 to %1#0 {transfer_kind = #cuf.cuda_transfer<device_host>} : !fir.ref<i32>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ return
+}
+
+// A scalar constant broadcast into an allocatable host array goes through the
+// descriptor path, but the source is still resolved to the device copy with
+// _FortranACUFGetDeviceAddress before being broadcast by
+// _FortranACUFDataTransferCstDesc.
+// CHECK-LABEL: func.func @_QPsub_cst_bcast_alloc()
+// CHECK: fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL:.*]] = fir.address_of(@_QMmscalEc0) : !fir.ref<i32>
+// CHECK: %[[GBL_CONV:.*]] = fir.convert %[[GBL]] : (!fir.ref<i32>) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFGetDeviceAddress(%[[GBL_CONV]], %{{.*}}, %{{.*}}) : (!fir.llvm_ptr<i8>, !fir.ref<i8>, i32) -> !fir.llvm_ptr<i8>
+// CHECK: fir.call @_FortranACUFDataTransferCstDesc(
+
fir.global @_QMmod1Ea {data_attr = #cuf.cuda<device>} : !fir.box<!fir.heap<!fir.array<?xi32>>> {
%c0 = arith.constant 0 : index
%0 = fir.zero_bits !fir.heap<!fir.array<?xi32>>
More information about the flang-commits
mailing list