[flang-commits] [flang] [mlir] [flang][acc] Don't treat a managed pointer descriptor as device data (PR #222769)

via flang-commits flang-commits at lists.llvm.org
Fri Sep 18 13:54:45 PDT 2026


https://github.com/yebinchon updated https://github.com/llvm/llvm-project/pull/222769

>From d39b5810a17c6e43affad23180c763015ddb207f Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Thu, 10 Sep 2026 13:40:44 -0700
Subject: [PATCH 1/5] [flang][acc] Don't treat a managed pointer descriptor as
 device data

---
 .../Support/FIROpenACCTypeInterfaces.cpp      | 11 ++-
 .../Transforms/OpenACC/acc-implicit-data.fir  | 90 +++++++++++++++++++
 2 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index fbaf1010b6b79..d9a9e3a45116e 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -1788,8 +1788,17 @@ static bool isDeviceDataImpl(mlir::Value var) {
   assert(defOp && "expected defining op for non-block-argument value");
 
   // Check for CUDA attributes on the defining operation.
-  if (cuf::hasDeviceDataAttr(defOp))
+  if (cuf::hasDeviceDataAttr(defOp)) {
+    // A `managed` attribute on a POINTER describes the storage of the
+    // descriptor, not the target it designates: the target is established by
+    // pointer assignment and may live in host memory. An ALLOCATABLE obtains
+    // its storage through the attributed entity, so there the attribute does
+    // describe the data.
+    if (cuf::hasDataAttr(defOp, cuf::DataAttribute::Managed) &&
+        fir::isPointerType(currentVal.getType()))
+      return false;
     return true;
+  }
 
   // Handle operations that access a partial entity - check if the base entity
   // is device data.
diff --git a/flang/test/Transforms/OpenACC/acc-implicit-data.fir b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
index 736c6f1e9bf2a..078e9f16889a6 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-data.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
@@ -397,6 +397,96 @@ func.func private @_FortranAAllocatableSetBounds(!fir.ref<!fir.box<none>>, i32,
 
 // -----
 
+// Test that a managed POINTER descriptor is not treated as device data. The
+// attribute describes the storage of the descriptor, not the target it
+// designates: the target is established by pointer assignment and may live in
+// host memory, so the descriptor needs a copy rather than a deviceptr.
+func.func @test_cuda_managed_pointer_no_implicit_deviceptr(%arg0: !fir.box<!fir.heap<!fir.array<?xf64>>>) {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant 2.500000e+00 : f64
+  %0 = cuf.alloc !fir.box<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "p", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEp"} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  acc.serial {
+    %2:3 = fir.box_dims %arg0, %c0 : (!fir.box<!fir.heap<!fir.array<?xf64>>>, index) -> (index, index, index)
+    %3 = fir.shift %2#0 : (index) -> !fir.shift<1>
+    %4 = fir.rebox %arg0(%3) : (!fir.box<!fir.heap<!fir.array<?xf64>>>, !fir.shift<1>) -> !fir.box<!fir.ptr<!fir.array<?xf64>>>
+    fir.store %4 to %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+    %5 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+    %6 = fir.box_addr %5 : (!fir.box<!fir.ptr<!fir.array<?xf64>>>) -> !fir.ptr<!fir.array<?xf64>>
+    %7 = fir.coordinate_of %6, %c0 : (!fir.ptr<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+    fir.store %cst to %7 : !fir.ref<f64>
+    acc.yield
+  }
+  cuf.free %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>> {data_attr = #cuf.cuda<managed>}
+  return
+}
+
+// CHECK-LABEL: func.func @test_cuda_managed_pointer_no_implicit_deviceptr
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>){{.*}}name("p")
+// CHECK: acc.copyout {{.*}}name("p")
+// CHECK-NOT: acc.deviceptr
+
+// -----
+
+// Test that a managed POINTER descriptor of polymorphic type is recognized:
+// a CLASS(..), POINTER descriptor is a fir.class rather than a fir.box.
+func.func @test_cuda_managed_class_pointer_no_implicit_deviceptr() {
+  %0 = cuf.alloc !fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>> {bindc_name = "p", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEp"} -> !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
+  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>) -> !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
+  acc.serial {
+    %2 = fir.load %1 : !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @test_cuda_managed_class_pointer_no_implicit_deviceptr
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin {{.*}}name("p")
+// CHECK-NOT: acc.deviceptr
+
+// -----
+
+// Test that a managed ALLOCATABLE descriptor is still device data: an
+// ALLOCATABLE obtains its storage through the attributed entity, so there the
+// attribute does describe the data.
+func.func @test_cuda_managed_allocatable_implicit_deviceptr() {
+  %0 = cuf.alloc !fir.box<!fir.heap<!fir.array<?xf64>>> {bindc_name = "a", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEa"} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFEa"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  acc.serial {
+    %2 = fir.load %1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @test_cuda_managed_allocatable_implicit_deviceptr
+// CHECK-NOT: acc.copyin
+// CHECK: acc.deviceptr {{.*}}name("a")
+// CHECK-NOT: acc.copyout
+
+// -----
+
+// Test that only the managed attribute is special-cased: a POINTER descriptor
+// in device memory is device data.
+func.func @test_cuda_device_pointer_implicit_deviceptr() {
+  %0 = cuf.alloc !fir.box<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "p", data_attr = #cuf.cuda<device>, uniq_name = "_QFEp"} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  %1 = fir.declare %0 {data_attr = #cuf.cuda<device>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  acc.serial {
+    %2 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @test_cuda_device_pointer_implicit_deviceptr
+// CHECK-NOT: acc.copyin
+// CHECK: acc.deviceptr {{.*}}name("p")
+// CHECK-NOT: acc.copyout
+
+// -----
+
 // Test argument mapped with deviceptr but used not via data mapping.
 func.func @test_fir_declare_deviceptr_arg_in_parallel(%arg0: !fir.ref<!fir.array<10xf64>>) {
   %c10 = arith.constant 10 : index

>From fabd8f504cb9db35b8cf5273afabe4e56ba020c9 Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Wed, 16 Sep 2026 14:33:20 -0700
Subject: [PATCH 2/5] remove pointer specific check; also return false for
 unified

---
 .../Support/FIROpenACCTypeInterfaces.cpp      | 25 +++---
 .../OpenACC/acc-implicit-data-device-data.fir | 77 ++++++++++++++++
 .../Transforms/OpenACC/acc-implicit-data.fir  | 90 -------------------
 3 files changed, 92 insertions(+), 100 deletions(-)
 create mode 100644 flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index d9a9e3a45116e..7c4ae44d67e30 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -1788,16 +1788,21 @@ static bool isDeviceDataImpl(mlir::Value var) {
   assert(defOp && "expected defining op for non-block-argument value");
 
   // Check for CUDA attributes on the defining operation.
-  if (cuf::hasDeviceDataAttr(defOp)) {
-    // A `managed` attribute on a POINTER describes the storage of the
-    // descriptor, not the target it designates: the target is established by
-    // pointer assignment and may live in host memory. An ALLOCATABLE obtains
-    // its storage through the attributed entity, so there the attribute does
-    // describe the data.
-    if (cuf::hasDataAttr(defOp, cuf::DataAttribute::Managed) &&
-        fir::isPointerType(currentVal.getType()))
-      return false;
-    return true;
+  if (auto dataAttr = cuf::getDataAttr(defOp)) {
+    cuf::DataAttribute attr = dataAttr.getValue();
+    if (cuf::isDeviceDataAttribute(attr)) {
+      // Managed/Unified establish device *accessibility*, not device
+      // *residency*: the data is reachable from the device but is not an
+      // already-resident device object. Treating it as deviceptr skips the
+      // mapping/attach the runtime needs, so classify it as non-device data
+      // and let it be mapped instead. Device/Constant/Shared are genuinely
+      // device-resident and stay device data. Applies regardless of whether
+      // the entity is a pointer or allocatable.
+      if (attr == cuf::DataAttribute::Managed ||
+          attr == cuf::DataAttribute::Unified)
+        return false;
+      return true;
+    }
   }
 
   // Handle operations that access a partial entity - check if the base entity
diff --git a/flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir b/flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir
new file mode 100644
index 0000000000000..d371d50c82137
--- /dev/null
+++ b/flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir
@@ -0,0 +1,77 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-data)" -split-input-file | FileCheck %s
+
+// Implicit data classification of CUDA data attributes for compute constructs.
+//
+// A `managed`/`unified` attribute establishes device *accessibility*, not device
+// *residency*: the storage is reachable from the device but is not an already
+// resident device object, so it must be mapped (so the runtime can attach and,
+// where needed, privatize it) rather than treated as `deviceptr`. This holds for
+// both POINTER (!fir.box<!fir.ptr>) and ALLOCATABLE (!fir.box<!fir.heap>)
+// descriptors -- the classification keys on the attribute, not the type.
+//
+// `device` (and `constant`/`shared`) are genuinely device resident and stay
+// device data, i.e. they are given `deviceptr`.
+
+// A managed POINTER descriptor must be mapped, not deviceptr.
+func.func @managed_ptr_desc() {
+  %0 = cuf.alloc !fir.box<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "p", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEp"} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_ptr_desc
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+// CHECK: acc.copyout accPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) to varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A managed ALLOCATABLE descriptor must be mapped, not deviceptr (same rule).
+func.func @managed_alloc_desc() {
+  %0 = cuf.alloc !fir.box<!fir.heap<!fir.array<?xf64>>> {bindc_name = "a", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEa"} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_alloc_desc
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+// CHECK: acc.copyout accPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) to varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A unified ALLOCATABLE descriptor must be mapped, not deviceptr.
+func.func @unified_alloc_desc() {
+  %0 = cuf.alloc !fir.box<!fir.heap<!fir.array<?xf64>>> {bindc_name = "u", data_attr = #cuf.cuda<unified>, uniq_name = "_QFEu"} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @unified_alloc_desc
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A device descriptor is genuinely device resident and stays deviceptr.
+func.func @device_alloc_desc() {
+  %0 = cuf.alloc !fir.box<!fir.heap<!fir.array<?xf64>>> {bindc_name = "d", data_attr = #cuf.cuda<device>, uniq_name = "_QFEd"} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @device_alloc_desc
+// CHECK-NOT: acc.copyin
+// CHECK: acc.deviceptr varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) implicit(true)
diff --git a/flang/test/Transforms/OpenACC/acc-implicit-data.fir b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
index 078e9f16889a6..736c6f1e9bf2a 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-data.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
@@ -397,96 +397,6 @@ func.func private @_FortranAAllocatableSetBounds(!fir.ref<!fir.box<none>>, i32,
 
 // -----
 
-// Test that a managed POINTER descriptor is not treated as device data. The
-// attribute describes the storage of the descriptor, not the target it
-// designates: the target is established by pointer assignment and may live in
-// host memory, so the descriptor needs a copy rather than a deviceptr.
-func.func @test_cuda_managed_pointer_no_implicit_deviceptr(%arg0: !fir.box<!fir.heap<!fir.array<?xf64>>>) {
-  %c0 = arith.constant 0 : index
-  %cst = arith.constant 2.500000e+00 : f64
-  %0 = cuf.alloc !fir.box<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "p", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEp"} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-  acc.serial {
-    %2:3 = fir.box_dims %arg0, %c0 : (!fir.box<!fir.heap<!fir.array<?xf64>>>, index) -> (index, index, index)
-    %3 = fir.shift %2#0 : (index) -> !fir.shift<1>
-    %4 = fir.rebox %arg0(%3) : (!fir.box<!fir.heap<!fir.array<?xf64>>>, !fir.shift<1>) -> !fir.box<!fir.ptr<!fir.array<?xf64>>>
-    fir.store %4 to %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-    %5 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-    %6 = fir.box_addr %5 : (!fir.box<!fir.ptr<!fir.array<?xf64>>>) -> !fir.ptr<!fir.array<?xf64>>
-    %7 = fir.coordinate_of %6, %c0 : (!fir.ptr<!fir.array<?xf64>>, index) -> !fir.ref<f64>
-    fir.store %cst to %7 : !fir.ref<f64>
-    acc.yield
-  }
-  cuf.free %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>> {data_attr = #cuf.cuda<managed>}
-  return
-}
-
-// CHECK-LABEL: func.func @test_cuda_managed_pointer_no_implicit_deviceptr
-// CHECK-NOT: acc.deviceptr
-// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>){{.*}}name("p")
-// CHECK: acc.copyout {{.*}}name("p")
-// CHECK-NOT: acc.deviceptr
-
-// -----
-
-// Test that a managed POINTER descriptor of polymorphic type is recognized:
-// a CLASS(..), POINTER descriptor is a fir.class rather than a fir.box.
-func.func @test_cuda_managed_class_pointer_no_implicit_deviceptr() {
-  %0 = cuf.alloc !fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>> {bindc_name = "p", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEp"} -> !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
-  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>) -> !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
-  acc.serial {
-    %2 = fir.load %1 : !fir.ref<!fir.class<!fir.ptr<!fir.type<_QMmTt{i:i32}>>>>
-    acc.yield
-  }
-  return
-}
-
-// CHECK-LABEL: func.func @test_cuda_managed_class_pointer_no_implicit_deviceptr
-// CHECK-NOT: acc.deviceptr
-// CHECK: acc.copyin {{.*}}name("p")
-// CHECK-NOT: acc.deviceptr
-
-// -----
-
-// Test that a managed ALLOCATABLE descriptor is still device data: an
-// ALLOCATABLE obtains its storage through the attributed entity, so there the
-// attribute does describe the data.
-func.func @test_cuda_managed_allocatable_implicit_deviceptr() {
-  %0 = cuf.alloc !fir.box<!fir.heap<!fir.array<?xf64>>> {bindc_name = "a", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEa"} -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
-  %1 = fir.declare %0 {data_attr = #cuf.cuda<managed>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFEa"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
-  acc.serial {
-    %2 = fir.load %1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
-    acc.yield
-  }
-  return
-}
-
-// CHECK-LABEL: func.func @test_cuda_managed_allocatable_implicit_deviceptr
-// CHECK-NOT: acc.copyin
-// CHECK: acc.deviceptr {{.*}}name("a")
-// CHECK-NOT: acc.copyout
-
-// -----
-
-// Test that only the managed attribute is special-cased: a POINTER descriptor
-// in device memory is device data.
-func.func @test_cuda_device_pointer_implicit_deviceptr() {
-  %0 = cuf.alloc !fir.box<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "p", data_attr = #cuf.cuda<device>, uniq_name = "_QFEp"} -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-  %1 = fir.declare %0 {data_attr = #cuf.cuda<device>, fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFEp"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-  acc.serial {
-    %2 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
-    acc.yield
-  }
-  return
-}
-
-// CHECK-LABEL: func.func @test_cuda_device_pointer_implicit_deviceptr
-// CHECK-NOT: acc.copyin
-// CHECK: acc.deviceptr {{.*}}name("p")
-// CHECK-NOT: acc.copyout
-
-// -----
-
 // Test argument mapped with deviceptr but used not via data mapping.
 func.func @test_fir_declare_deviceptr_arg_in_parallel(%arg0: !fir.ref<!fir.array<10xf64>>) {
   %c10 = arith.constant 10 : index

>From ac158cc52b614db501b057568be51235864b8fdb Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Wed, 16 Sep 2026 14:35:43 -0700
Subject: [PATCH 3/5] comment cleanup

---
 .../OpenACC/Support/FIROpenACCTypeInterfaces.cpp      | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index 7c4ae44d67e30..cb6d551173496 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -1791,13 +1791,12 @@ static bool isDeviceDataImpl(mlir::Value var) {
   if (auto dataAttr = cuf::getDataAttr(defOp)) {
     cuf::DataAttribute attr = dataAttr.getValue();
     if (cuf::isDeviceDataAttribute(attr)) {
-      // Managed/Unified establish device *accessibility*, not device
-      // *residency*: the data is reachable from the device but is not an
+      // Managed/Unified establish device accessibility, not device
+      // residency: the data is reachable from the device but is not an
       // already-resident device object. Treating it as deviceptr skips the
-      // mapping/attach the runtime needs, so classify it as non-device data
-      // and let it be mapped instead. Device/Constant/Shared are genuinely
-      // device-resident and stay device data. Applies regardless of whether
-      // the entity is a pointer or allocatable.
+      // mapping/attach, so classify it as non-device data and let it be mapped
+      // instead. Device/Constant/Shared are genuinely device-resident and stay
+      // device data.
       if (attr == cuf::DataAttribute::Managed ||
           attr == cuf::DataAttribute::Unified)
         return false;

>From ee588fd2e1c846e4ef5e64e8a8cad07b3b1114fc Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Wed, 16 Sep 2026 14:37:02 -0700
Subject: [PATCH 4/5] comment cleanup

---
 .../Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index cb6d551173496..0ef09b0fcb044 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -1792,8 +1792,8 @@ static bool isDeviceDataImpl(mlir::Value var) {
     cuf::DataAttribute attr = dataAttr.getValue();
     if (cuf::isDeviceDataAttribute(attr)) {
       // Managed/Unified establish device accessibility, not device
-      // residency: the data is reachable from the device but is not an
-      // already-resident device object. Treating it as deviceptr skips the
+      // residency: the data is reachable from the device but may not already
+      // reside on the device. Treating it as deviceptr skips the
       // mapping/attach, so classify it as non-device data and let it be mapped
       // instead. Device/Constant/Shared are genuinely device-resident and stay
       // device data.

>From d9056eaf5369544cc7a4d099818dea3fda2388ac Mon Sep 17 00:00:00 2001
From: Yebin Chon <ychon at nvidia.com>
Date: Fri, 18 Sep 2026 12:44:14 -0700
Subject: [PATCH 5/5] managed/unified check only for deviceptr

---
 .../Support/FIROpenACCTypeInterfaces.cpp      | 17 +------
 .../OpenACC/Transforms/ACCImplicitData.cpp    | 46 ++++++++++++++++---
 2 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index 0ef09b0fcb044..fbaf1010b6b79 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -1788,21 +1788,8 @@ static bool isDeviceDataImpl(mlir::Value var) {
   assert(defOp && "expected defining op for non-block-argument value");
 
   // Check for CUDA attributes on the defining operation.
-  if (auto dataAttr = cuf::getDataAttr(defOp)) {
-    cuf::DataAttribute attr = dataAttr.getValue();
-    if (cuf::isDeviceDataAttribute(attr)) {
-      // Managed/Unified establish device accessibility, not device
-      // residency: the data is reachable from the device but may not already
-      // reside on the device. Treating it as deviceptr skips the
-      // mapping/attach, so classify it as non-device data and let it be mapped
-      // instead. Device/Constant/Shared are genuinely device-resident and stay
-      // device data.
-      if (attr == cuf::DataAttribute::Managed ||
-          attr == cuf::DataAttribute::Unified)
-        return false;
-      return true;
-    }
-  }
+  if (cuf::hasDeviceDataAttr(defOp))
+    return true;
 
   // Handle operations that access a partial entity - check if the base entity
   // is device data.
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
index 5ee3a21fab901..f6ea7ba5445ba 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
@@ -216,6 +216,8 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
 #include <type_traits>
 
 namespace mlir {
@@ -458,12 +460,44 @@ Operation *ACCImplicitData::generateDataClauseOpForCandidate(
   Location loc = computeConstructOp->getLoc();
 
   if (acc::isDeviceValue(var)) {
-    // If the variable is device data, use deviceptr clause.
-    LLVM_DEBUG(llvm::dbgs() << "Using deviceptr clause because variable is "
-                               "device data\n");
-    return acc::DevicePtrOp::create(builder, loc, var,
-                                    /*structured=*/true, /*implicit=*/true,
-                                    accSupport.getVariableName(var));
+    // `deviceptr` asserts the value is already device-*resident*: no runtime
+    // mapping or attach is performed. CUDA managed/unified storage is device-
+    // *accessible* but not resident, so it must be mapped (so the runtime can
+    // attach and, where needed, privatize it) rather than treated as
+    // deviceptr. isDeviceValue answers accessibility, so refine it to
+    // residence here by excluding managed/unified.
+    //
+    // FIRST-PASS: this pass lives in mlir core and cannot link the CUF dialect,
+    // so the data attribute is matched by name+mnemonic instead of via
+    // cuf::getDataAttr. Replace with an acc::isDeviceResident interface query.
+    auto isManagedOrUnified = [](mlir::Value v) {
+      mlir::Operation *defOp = v.getDefiningOp();
+      if (!defOp)
+        return false;
+      for (llvm::StringRef name : {"data_attr", "cuf.data_attr"}) {
+        if (mlir::Attribute a = defOp->getAttr(name)) {
+          std::string s;
+          llvm::raw_string_ostream os(s);
+          a.print(os);
+          llvm::StringRef sv(s);
+          if (sv.contains("managed") || sv.contains("unified"))
+            return true;
+        }
+      }
+      return false;
+    };
+
+    if (!isManagedOrUnified(var)) {
+      // If the variable is device-resident data, use deviceptr clause.
+      LLVM_DEBUG(llvm::dbgs() << "Using deviceptr clause because variable is "
+                                 "device data\n");
+      return acc::DevicePtrOp::create(builder, loc, var,
+                                      /*structured=*/true, /*implicit=*/true,
+                                      accSupport.getVariableName(var));
+    }
+    LLVM_DEBUG(llvm::dbgs()
+               << "Not using deviceptr for managed/unified variable "
+                  "(device-accessible, not device-resident)\n");
   }
 
   Operation *op = nullptr;



More information about the flang-commits mailing list