[flang-commits] [flang] 340b35c - [flang][acc] Don't treat a managed pointer as device resident data (#222769)

via flang-commits flang-commits at lists.llvm.org
Tue Sep 22 10:48:11 PDT 2026


Author: yebinchon
Date: 2026-09-22T10:48:03-07:00
New Revision: 340b35cd504e01e4eeff0381bd5eb317dfb7f504

URL: https://github.com/llvm/llvm-project/commit/340b35cd504e01e4eeff0381bd5eb317dfb7f504
DIFF: https://github.com/llvm/llvm-project/commit/340b35cd504e01e4eeff0381bd5eb317dfb7f504.diff

LOG: [flang][acc] Don't treat a managed pointer as device resident data (#222769)

isDeviceData classifies every entity with a device-data CUDA attribute
(device, managed, constant, shared, unified) as device data, and so all
pointers and allocatables under mem:managed/unified are treated as
deviceptr.

However, managed and unified guarantee accessibility from device but not
that the storage is already residing on device. Treating these
unconditionally as "on device" (deviceptr) asserts to the runtime that
it is a valid device address and skips the mapping/attach the runtime
needs.

This patch adds a Util helper "isDeviceResident" which conservatively
returns false for managed/unified.

The guard for deviceptr is changed from isDeviceValue to
isDeviceResident, and does NOT give deviceptr for managed and unified.
These entities are now mapped (copyin/out) instead.

---------

Co-authored-by: Yebin Chon <ychon at nvidia.com>

Added: 
    flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir

Modified: 
    flang/include/flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h
    flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
    flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
    flang/lib/Optimizer/Dialect/CUF/Attributes/CUFAttr.cpp
    flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
    flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
    mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
    mlir/include/mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td
    mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
    mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
    mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
    mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
    mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h b/flang/include/flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h
index 6ce3c5ef716368..4082abd2431c24 100644
--- a/flang/include/flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h
+++ b/flang/include/flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h
@@ -143,6 +143,16 @@ bool isDeviceDataAttribute(cuf::DataAttribute attr);
 /// whose value returns true for `isDeviceDataAttribute`.
 bool hasDeviceDataAttr(mlir::Operation *op);
 
+/// Check if a CUDA data attribute represents managed or unified storage.
+/// Returns true for Managed and Unified attributes. Such storage is
+/// device-accessible but may migrate rather than being statically guaranteed
+/// to reside on the device.
+bool isManagedOrUnifiedDataAttribute(cuf::DataAttribute attr);
+
+/// Returns true if the operation has a `cuf::DataAttributeAttr`
+/// whose value returns true for `isManagedOrUnifiedDataAttribute`.
+bool hasManagedOrUnifiedDataAttr(mlir::Operation *op);
+
 } // namespace cuf
 
 #endif // FORTRAN_OPTIMIZER_DIALECT_CUF_CUFATTR_H

diff  --git a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
index c9ed3cfe9a0136..585448134e1ace 100644
--- a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
+++ b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
@@ -70,7 +70,8 @@ struct GlobalVariableModel
   bool isConstant(mlir::Operation *op) const;
   bool hasInitializer(mlir::Operation *op) const;
   mlir::Region *getInitRegion(mlir::Operation *op) const;
-  bool isDeviceData(mlir::Operation *op) const;
+  bool isDeviceAccessible(mlir::Operation *op) const;
+  bool isInDeviceMemory(mlir::Operation *op) const;
   bool isCompilerGenerated(mlir::Operation *op) const;
 };
 

diff  --git a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
index 4e4e0d5f2c6f2e..b98461c378ba99 100644
--- a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
+++ b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
@@ -60,7 +60,9 @@ struct OpenACCPointerLikeModel
   mlir::MemRefType getAsMemRefType(mlir::Type pointer,
                                    mlir::ModuleOp module) const;
 
-  bool isDeviceData(mlir::Type pointer, mlir::Value var) const;
+  bool isDeviceAccessible(mlir::Type pointer, mlir::Value var) const;
+
+  bool isInDeviceMemory(mlir::Type pointer, mlir::Value var) const;
 };
 
 template <typename T>
@@ -119,7 +121,9 @@ struct OpenACCMappableModel
                         mlir::acc::ReductionOperator op,
                         mlir::Attribute fastmathFlags) const;
 
-  bool isDeviceData(mlir::Type type, mlir::Value var) const;
+  bool isDeviceAccessible(mlir::Type type, mlir::Value var) const;
+
+  bool isInDeviceMemory(mlir::Type type, mlir::Value var) const;
 };
 
 struct OpenACCReducibleLogicalModel

diff  --git a/flang/lib/Optimizer/Dialect/CUF/Attributes/CUFAttr.cpp b/flang/lib/Optimizer/Dialect/CUF/Attributes/CUFAttr.cpp
index 5aa1b04877b39c..a8860eda736267 100644
--- a/flang/lib/Optimizer/Dialect/CUF/Attributes/CUFAttr.cpp
+++ b/flang/lib/Optimizer/Dialect/CUF/Attributes/CUFAttr.cpp
@@ -65,4 +65,15 @@ bool hasDeviceDataAttr(mlir::Operation *op) {
   return false;
 }
 
+bool isManagedOrUnifiedDataAttribute(cuf::DataAttribute attr) {
+  return attr == cuf::DataAttribute::Managed ||
+         attr == cuf::DataAttribute::Unified;
+}
+
+bool hasManagedOrUnifiedDataAttr(mlir::Operation *op) {
+  if (auto dataAttr = getDataAttr(op))
+    return isManagedOrUnifiedDataAttribute(dataAttr.getValue());
+  return false;
+}
+
 } // namespace cuf

diff  --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
index 1c714f06757453..9a6771e7f07259 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
@@ -131,12 +131,23 @@ mlir::Region *GlobalVariableModel::getInitRegion(mlir::Operation *op) const {
   return globalOp.hasInitializationBody() ? &globalOp.getRegion() : nullptr;
 }
 
-bool GlobalVariableModel::isDeviceData(mlir::Operation *op) const {
+bool GlobalVariableModel::isDeviceAccessible(mlir::Operation *op) const {
   if (auto dataAttr = cuf::getDataAttr(op))
     return cuf::isDeviceDataAttribute(dataAttr.getValue());
   return false;
 }
 
+bool GlobalVariableModel::isInDeviceMemory(mlir::Operation *op) const {
+  // A global is in device memory when it carries a device-data attribute that
+  // denotes physically device-resident storage. Storage that is device-
+  // accessible but physically shared with the host (and so may migrate on
+  // demand) is accessible but not guaranteed to be in device memory.
+  if (auto dataAttr = cuf::getDataAttr(op))
+    return cuf::isDeviceDataAttribute(dataAttr.getValue()) &&
+           !cuf::isManagedOrUnifiedDataAttribute(dataAttr.getValue());
+  return false;
+}
+
 bool GlobalVariableModel::isCompilerGenerated(mlir::Operation *op) const {
   auto globalOp = mlir::cast<fir::GlobalOp>(op);
   return fir::NameUniquer::isCompilerGenerated(globalOp.getSymName());

diff  --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index fbaf1010b6b794..73be000f701b42 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -32,6 +32,7 @@
 #include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/Support/LLVM.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/CommandLine.h"
@@ -1755,8 +1756,11 @@ template mlir::Value OpenACCPointerLikeModel<fir::LLVMPointerType>::genCast(
     mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
     mlir::Value value, mlir::Type resultType) const;
 
-/// Check CUDA attributes on a function argument.
-static bool hasCUDADeviceAttrOnFuncArg(mlir::BlockArgument blockArg) {
+/// Check CUDA attributes on a function argument, testing the attribute
+/// with the provided predicate (e.g. device-data vs device-resident).
+static bool
+hasCUDADataAttrOnFuncArg(mlir::BlockArgument blockArg,
+                         llvm::function_ref<bool(cuf::DataAttribute)> matches) {
   auto *owner = blockArg.getOwner();
   if (!owner)
     return false;
@@ -1770,89 +1774,142 @@ static bool hasCUDADeviceAttrOnFuncArg(mlir::BlockArgument blockArg) {
     if (argIndex < funcLike.getNumArguments())
       if (auto attr = funcLike.getArgAttr(argIndex, cuf::getDataAttrName()))
         if (auto cudaAttr = mlir::dyn_cast<cuf::DataAttributeAttr>(attr))
-          return cuf::isDeviceDataAttribute(cudaAttr.getValue());
+          return matches(cudaAttr.getValue());
   }
   return false;
 }
 
-/// Shared implementation for checking if a value represents device data.
-static bool isDeviceDataImpl(mlir::Value var) {
+/// Shared walk that returns true if `var` (after stripping casts/views and
+/// following partial-entity access and address-of edges to the underlying
+/// storage) is ultimately backed by storage whose CUDA data attribute
+/// satisfies `matches`. The predicate selects the storage property of
+/// interest, e.g.:
+///   - device accessibility: cuf::isDeviceDataAttribute
+///   - device memory (physically resident): device-data minus managed/unified
+static bool underlyingStorageHasDataAttr(
+    mlir::Value var, llvm::function_ref<bool(cuf::DataAttribute)> matches) {
   // Strip casts to find the underlying value.
   mlir::Value currentVal =
       fir::acc::getOriginalDef(var, /*stripDeclare=*/false);
 
+  // Dummy arguments carry their attribute on the enclosing function.
   if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(currentVal))
-    return hasCUDADeviceAttrOnFuncArg(blockArg);
+    return hasCUDADataAttrOnFuncArg(blockArg, matches);
 
   mlir::Operation *defOp = currentVal.getDefiningOp();
   assert(defOp && "expected defining op for non-block-argument value");
 
-  // Check for CUDA attributes on the defining operation.
-  if (cuf::hasDeviceDataAttr(defOp))
-    return true;
+  // Check for a matching CUDA data attribute on the defining operation.
+  if (auto dataAttr = cuf::getDataAttr(defOp))
+    if (matches(dataAttr.getValue()))
+      return true;
 
-  // Handle operations that access a partial entity - check if the base entity
-  // is device data.
+  // Handle operations that access a partial entity - check the base entity.
   if (auto partialAccess =
           mlir::dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp))
     if (mlir::Value base = partialAccess.getBaseEntity())
-      return isDeviceDataImpl(base);
+      return underlyingStorageHasDataAttr(base, matches);
 
   // Handle fir.embox, fir.rebox, and similar ops via
-  // FortranObjectViewOpInterface to check if the underlying source is device
-  // data.
+  // FortranObjectViewOpInterface to check the underlying source.
   if (auto viewOp = mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp))
     if (mlir::Value source = viewOp.getViewSource(defOp->getResult(0)))
-      return isDeviceDataImpl(source);
+      return underlyingStorageHasDataAttr(source, matches);
 
-  // Handle address_of - check the referenced global.
+  // Handle address_of - check the referenced global's data attribute.
   if (auto addrOfIface =
           mlir::dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
     auto symbol = addrOfIface.getSymbol();
     if (auto global = mlir::SymbolTable::lookupNearestSymbolFrom<
             mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
-      return global.isDeviceData();
+      if (auto dataAttr = cuf::getDataAttr(global.getOperation()))
+        return matches(dataAttr.getValue());
     return false;
   }
 
   return false;
 }
 
+/// Device accessibility: storage the current device can reach, regardless of
+/// where it physically resides.
+static bool isDeviceAccessibleImpl(mlir::Value var) {
+  return underlyingStorageHasDataAttr(var, cuf::isDeviceDataAttribute);
+}
+
+/// Device memory: storage that is physically connected to (resident in) the
+/// device. This is device-accessible storage minus host-shared storage that
+/// may migrate on demand (managed/unified).
+static bool isInDeviceMemoryImpl(mlir::Value var) {
+  return underlyingStorageHasDataAttr(var, [](cuf::DataAttribute attr) {
+    return cuf::isDeviceDataAttribute(attr) &&
+           !cuf::isManagedOrUnifiedDataAttribute(attr);
+  });
+}
+
 template <typename Ty>
-bool OpenACCPointerLikeModel<Ty>::isDeviceData(mlir::Type pointer,
-                                               mlir::Value var) const {
-  return isDeviceDataImpl(var);
+bool OpenACCPointerLikeModel<Ty>::isDeviceAccessible(mlir::Type pointer,
+                                                     mlir::Value var) const {
+  return isDeviceAccessibleImpl(var);
 }
 
-template bool OpenACCPointerLikeModel<fir::ReferenceType>::isDeviceData(
+template bool OpenACCPointerLikeModel<fir::ReferenceType>::isDeviceAccessible(
+    mlir::Type, mlir::Value) const;
+template bool OpenACCPointerLikeModel<fir::PointerType>::isDeviceAccessible(
+    mlir::Type, mlir::Value) const;
+template bool OpenACCPointerLikeModel<fir::HeapType>::isDeviceAccessible(
+    mlir::Type, mlir::Value) const;
+template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::isDeviceAccessible(
+    mlir::Type, mlir::Value) const;
+
+template <typename Ty>
+bool OpenACCMappableModel<Ty>::isDeviceAccessible(mlir::Type type,
+                                                  mlir::Value var) const {
+  return isDeviceAccessibleImpl(var);
+}
+
+template bool OpenACCMappableModel<fir::BaseBoxType>::isDeviceAccessible(
+    mlir::Type, mlir::Value) const;
+template bool OpenACCMappableModel<fir::ReferenceType>::isDeviceAccessible(
     mlir::Type, mlir::Value) const;
 template bool
-    OpenACCPointerLikeModel<fir::PointerType>::isDeviceData(mlir::Type,
+    OpenACCMappableModel<fir::HeapType>::isDeviceAccessible(mlir::Type,
                                                             mlir::Value) const;
-template bool
-    OpenACCPointerLikeModel<fir::HeapType>::isDeviceData(mlir::Type,
-                                                         mlir::Value) const;
-template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::isDeviceData(
+template bool OpenACCMappableModel<fir::PointerType>::isDeviceAccessible(
     mlir::Type, mlir::Value) const;
 
 template <typename Ty>
-bool OpenACCMappableModel<Ty>::isDeviceData(mlir::Type type,
-                                            mlir::Value var) const {
-  return isDeviceDataImpl(var);
+bool OpenACCPointerLikeModel<Ty>::isInDeviceMemory(mlir::Type pointer,
+                                                   mlir::Value var) const {
+  return isInDeviceMemoryImpl(var);
 }
 
+template bool OpenACCPointerLikeModel<fir::ReferenceType>::isInDeviceMemory(
+    mlir::Type, mlir::Value) const;
+template bool OpenACCPointerLikeModel<fir::PointerType>::isInDeviceMemory(
+    mlir::Type, mlir::Value) const;
 template bool
-    OpenACCMappableModel<fir::BaseBoxType>::isDeviceData(mlir::Type,
-                                                         mlir::Value) const;
+    OpenACCPointerLikeModel<fir::HeapType>::isInDeviceMemory(mlir::Type,
+                                                             mlir::Value) const;
+template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::isInDeviceMemory(
+    mlir::Type, mlir::Value) const;
+
+template <typename Ty>
+bool OpenACCMappableModel<Ty>::isInDeviceMemory(mlir::Type type,
+                                                mlir::Value var) const {
+  return isInDeviceMemoryImpl(var);
+}
+
 template bool
-    OpenACCMappableModel<fir::ReferenceType>::isDeviceData(mlir::Type,
-                                                           mlir::Value) const;
+    OpenACCMappableModel<fir::BaseBoxType>::isInDeviceMemory(mlir::Type,
+                                                             mlir::Value) const;
+template bool OpenACCMappableModel<fir::ReferenceType>::isInDeviceMemory(
+    mlir::Type, mlir::Value) const;
 template bool
-    OpenACCMappableModel<fir::HeapType>::isDeviceData(mlir::Type,
-                                                      mlir::Value) const;
+    OpenACCMappableModel<fir::HeapType>::isInDeviceMemory(mlir::Type,
+                                                          mlir::Value) const;
 template bool
-    OpenACCMappableModel<fir::PointerType>::isDeviceData(mlir::Type,
-                                                         mlir::Value) const;
+    OpenACCMappableModel<fir::PointerType>::isInDeviceMemory(mlir::Type,
+                                                             mlir::Value) const;
 
 std::optional<mlir::arith::AtomicRMWKind>
 OpenACCReducibleLogicalModel::getAtomicRMWKind(

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 00000000000000..2d7c90a11f1d16
--- /dev/null
+++ b/flang/test/Transforms/OpenACC/acc-implicit-data-device-data.fir
@@ -0,0 +1,183 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-data)" -split-input-file | FileCheck %s
+
+// Implicit `deviceptr` classification keys on device residency, not device
+// accessibility.
+//
+// A `managed`/`unified` attribute makes storage reachable from the device
+// (device-accessible), but the storage is physically shared with the host and may
+// migrate on demand, so it is not guaranteed to be in device memory. Such data 
+// must be mapped so the runtime can attach rather than be  treated as `deviceptr`.
+//
+// `device` (and `constant`/`shared`) storage is physically in device memory and
+// is therefore 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 physically in device memory 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)
+
+// -----
+
+// The underlying-storage walk sees through fir.declare (partial-entity access):
+// a managed descriptor stays mapped even behind a declare.
+func.func @managed_declare() {
+  %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 {uniq_name = "_QFEp"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+  acc.parallel {
+    %2 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_declare
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A managed module global reached via fir.address_of must be mapped, not
+// deviceptr. The walk follows the address-of edge to the global's attribute.
+fir.global @mg_managed {data_attr = #cuf.cuda<managed>} : !fir.array<10xf64> {
+  %0 = fir.zero_bits !fir.array<10xf64>
+  fir.has_value %0 : !fir.array<10xf64>
+}
+func.func @managed_global() {
+  %0 = fir.address_of(@mg_managed) : !fir.ref<!fir.array<10xf64>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.array<10xf64>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_global
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.array<10xf64>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A device module global is in device memory and stays deviceptr.
+fir.global @mg_device {data_attr = #cuf.cuda<device>} : !fir.array<10xf64> {
+  %0 = fir.zero_bits !fir.array<10xf64>
+  fir.has_value %0 : !fir.array<10xf64>
+}
+func.func @device_global() {
+  %0 = fir.address_of(@mg_device) : !fir.ref<!fir.array<10xf64>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.array<10xf64>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @device_global
+// CHECK-NOT: acc.copyin
+// CHECK: acc.deviceptr varPtr(%{{.*}} : !fir.ref<!fir.array<10xf64>>) implicit(true)
+
+// -----
+
+// A managed dummy argument must be mapped, not deviceptr. The walk reads the
+// attribute off the enclosing function argument.
+func.func @managed_dummy(%arg0: !fir.ref<!fir.array<10xf64>> {cuf.data_attr = #cuf.cuda<managed>, fir.bindc_name = "a"}) {
+  acc.parallel {
+    %0 = fir.load %arg0 : !fir.ref<!fir.array<10xf64>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_dummy
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.array<10xf64>>) dataClause(acc_copy) implicit(true)
+
+// -----
+
+// A device dummy argument is in device memory and stays deviceptr.
+func.func @device_dummy(%arg0: !fir.ref<!fir.array<10xf64>> {cuf.data_attr = #cuf.cuda<device>, fir.bindc_name = "a"}) {
+  acc.parallel {
+    %0 = fir.load %arg0 : !fir.ref<!fir.array<10xf64>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @device_dummy
+// CHECK-NOT: acc.copyin
+// CHECK: acc.deviceptr varPtr(%{{.*}} : !fir.ref<!fir.array<10xf64>>) implicit(true)
+
+// -----
+
+// A managed polymorphic (CLASS) POINTER descriptor must be mapped, not
+// deviceptr -- same rule as the non-polymorphic descriptors.
+func.func @managed_class_ptr() {
+  %0 = cuf.alloc !fir.class<!fir.ptr<!fir.array<?xf64>>> {bindc_name = "cp", data_attr = #cuf.cuda<managed>, uniq_name = "_QFEcp"} -> !fir.ref<!fir.class<!fir.ptr<!fir.array<?xf64>>>>
+  acc.parallel {
+    %1 = fir.load %0 : !fir.ref<!fir.class<!fir.ptr<!fir.array<?xf64>>>>
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: func.func @managed_class_ptr
+// CHECK-NOT: acc.deviceptr
+// CHECK: acc.copyin varPtr(%{{.*}} : !fir.ref<!fir.class<!fir.ptr<!fir.array<?xf64>>>>) dataClause(acc_copy) implicit(true)

diff  --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
index 7cc849c38a1c9a..dcd21975591726 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
@@ -80,9 +80,20 @@ def GlobalVariableOpInterface : OpInterface<"GlobalVariableOpInterface"> {
     InterfaceMethod<"Get the initialization region (returns nullptr if none)",
       "::mlir::Region*", "getInitRegion", (ins)>,
     InterfaceMethod<"Check if the global variable is device data",
-      "bool", "isDeviceData", (ins), [{
+      "bool", "isDeviceAccessible", (ins), [{
         return false;
       }]>,
+    InterfaceMethod<[{
+        Check if the global variable is backed by memory that is residing in 
+        the current device. This is a subset of `isDeviceAccessible`: memory
+        that is accessible from the device but is physically shared with the
+        host (and so may migrate on demand) is accessible but not in device 
+        memory.
+      }],
+      "bool", "isInDeviceMemory", (ins), [{
+        return ::mlir::cast<::mlir::acc::GlobalVariableOpInterface>($_op)
+            .isDeviceAccessible();
+      }]>,
     InterfaceMethod<"Check if the global variable is compiler generated", "bool",
       "isCompilerGenerated", (ins), [{
         return false;

diff  --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td
index 3b73a2e7eec17d..818384b3f23e66 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td
@@ -265,13 +265,31 @@ def OpenACC_PointerLikeTypeInterface : TypeInterface<"PointerLikeType"> {
         Returns true if the pointer points to device data.
       }],
       /*retTy=*/"bool",
-      /*methodName=*/"isDeviceData",
+      /*methodName=*/"isDeviceAccessible",
       /*args=*/(ins "::mlir::Value":$var),
       /*methodBody=*/"",
       /*defaultImplementation=*/[{
         return false;
       }]
     >,
+    InterfaceMethod<
+      /*description=*/[{
+        Returns true if the pointer points to memory that is residing in the
+        current device. This is a subset of `isDeviceAccessible`: memory that
+        is accessible from the device but is physically shared with the host
+        (and so may migrate on demand) is accessible but not in guaranteed to
+        be in device memory, and therefore must still be mapped or attached
+        rather than treated as already resident.
+      }],
+      /*retTy=*/"bool",
+      /*methodName=*/"isInDeviceMemory",
+      /*args=*/(ins "::mlir::Value":$var),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return ::mlir::cast<::mlir::acc::PointerLikeType>($_type)
+            .isDeviceAccessible(var);
+      }]
+    >,
   ];
 }
 
@@ -547,13 +565,31 @@ def OpenACC_MappableTypeInterface : TypeInterface<"MappableType"> {
         Returns true if the variable represents device data.
       }],
       /*retTy=*/"bool",
-      /*methodName=*/"isDeviceData",
+      /*methodName=*/"isDeviceAccessible",
       /*args=*/(ins "::mlir::Value":$var),
       /*methodBody=*/"",
       /*defaultImplementation=*/[{
         return false;
       }]
     >,
+    InterfaceMethod<
+      /*description=*/[{
+        Returns true if the pointer points to memory that is residing in the
+        current device. This is a subset of `isDeviceAccessible`: memory that
+        is accessible from the device but is physically shared with the host
+        (and so may migrate on demand) is accessible but not in guaranteed to
+        be in device memory, and therefore must still be mapped or attached
+        rather than treated as already resident.
+      }],
+      /*retTy=*/"bool",
+      /*methodName=*/"isInDeviceMemory",
+      /*args=*/(ins "::mlir::Value":$var),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return ::mlir::cast<::mlir::acc::MappableType>($_type)
+            .isDeviceAccessible(var);
+      }]
+    >,
   ];
 }
 

diff  --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index 74e7ec920d17d4..2d8780ecf4b16a 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -98,7 +98,21 @@ bool isValidSymbolUse(mlir::Operation *user, mlir::SymbolRefAttr symbol,
 /// clause.
 /// \param val The value to check
 /// \return true if the value is device data, false otherwise
-bool isDeviceValue(mlir::Value val);
+bool isDeviceAccessibleValue(mlir::Value val);
+
+/// Check if a value is backed by memory that is residing in the current device,
+/// and therefore requires no runtime mapping or attach.
+///
+/// This is stricter than isDeviceAccessibleValue: isDeviceAccessibleValue
+/// answers device accessibility (whether the current device can reach the
+/// storage, regardless of where it physically resides), whereas this answers
+/// device residence (whether the storage physically lives in device memory).
+/// Storage that is accessible but physically shared with the host may migrate
+/// on demand, so it is not in device memory: it must still be mapped so the
+/// runtime can attach rather than be treated as already resident.
+/// \param val The value to check
+/// \return true if the value is in device memory, false otherwise
+bool isInDeviceMemoryValue(mlir::Value val);
 
 /// Check if a value use is valid in an OpenACC region.
 /// This is true if:

diff  --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index f6a32e0a4a4803..8a55ae5ef59705 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -279,7 +279,7 @@ struct MemRefPointerLikeModel
     return {};
   }
 
-  bool isDeviceData(Type pointer, Value var) const {
+  bool isDeviceAccessible(Type pointer, Value var) const {
     auto memrefTy = cast<T>(pointer);
     Attribute memSpace = memrefTy.getMemorySpace();
     return isa_and_nonnull<gpu::AddressSpaceAttr>(memSpace);
@@ -405,12 +405,19 @@ struct MemrefGlobalVariableModel
     return nullptr;
   }
 
-  bool isDeviceData(Operation *op) const {
+  bool isDeviceAccessible(Operation *op) const {
     auto globalOp = cast<memref::GlobalOp>(op);
     Attribute memSpace = globalOp.getType().getMemorySpace();
     return isa_and_nonnull<gpu::AddressSpaceAttr>(memSpace);
   }
 
+  bool isInDeviceMemory(Operation *op) const {
+    // A memref address space models storage that is physically resident on the
+    // device, so a device-accessible global is also in device memory. (There
+    // is no host-shared/migratable address space to exclude here.)
+    return isDeviceAccessible(op);
+  }
+
   bool isCompilerGenerated(Operation *op) const { return false; }
 };
 

diff  --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
index 5ee3a21fab9018..99970b49ebca64 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 {
@@ -292,7 +294,7 @@ static bool isCandidateForImplicitData(Value val, Region &accRegion,
     return false;
 
   // Device data is a candidate - it will get a deviceptr clause.
-  if (acc::isDeviceValue(val))
+  if (acc::isDeviceAccessibleValue(val))
     return true;
 
   // If it is otherwise valid, skip it.
@@ -457,10 +459,15 @@ Operation *ACCImplicitData::generateDataClauseOpForCandidate(
       typeCategory, acc::VariableTypeCategory::aggregate);
   Location loc = computeConstructOp->getLoc();
 
-  if (acc::isDeviceValue(var)) {
-    // If the variable is device data, use deviceptr clause.
+  // `deviceptr` asserts the value's storage is already in device memory; no
+  // runtime mapping or attach is performed. Storage that is device-accessible
+  // but physically shared with the host may migrate on demand, so it is not
+  // guaranteed to be in device memory and must still be mapped rather than
+  // treated as deviceptr.
+  if (acc::isInDeviceMemoryValue(var)) {
+    // If the variable is in device memory, use deviceptr clause.
     LLVM_DEBUG(llvm::dbgs() << "Using deviceptr clause because variable is "
-                               "device data\n");
+                               "in device memory\n");
     return acc::DevicePtrOp::create(builder, loc, var,
                                     /*structured=*/true, /*implicit=*/true,
                                     accSupport.getVariableName(var));

diff  --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 8a529b0ac77418..268bbea3bee8f3 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -245,7 +245,7 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
   // Device data is already resident on the device and does not need mapping.
   if (auto globalVar =
           mlir::dyn_cast<mlir::acc::GlobalVariableOpInterface>(definingOp))
-    if (globalVar.isDeviceData())
+    if (globalVar.isDeviceAccessible())
       return true;
 
   // Check if the defining op is a function
@@ -276,15 +276,15 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
   return hasDeclare;
 }
 
-bool mlir::acc::isDeviceValue(mlir::Value val) {
+bool mlir::acc::isDeviceAccessibleValue(mlir::Value val) {
   // Check if the value is device data via type interfaces.
   // Device data is already resident on the device and does not need mapping.
   if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
-    if (mappableTy.isDeviceData(val))
+    if (mappableTy.isDeviceAccessible(val))
       return true;
 
   if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
-    if (pointerLikeTy.isDeviceData(val))
+    if (pointerLikeTy.isDeviceAccessible(val))
       return true;
 
   mlir::Operation *defOp = val.getDefiningOp();
@@ -305,7 +305,7 @@ bool mlir::acc::isDeviceValue(mlir::Value val) {
   if (auto partialAccess =
           dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
     if (mlir::Value base = partialAccess.getBaseEntity())
-      return isDeviceValue(base);
+      return isDeviceAccessibleValue(base);
   }
 
   // Handle address_of - check if the referenced global is device data.
@@ -314,7 +314,51 @@ bool mlir::acc::isDeviceValue(mlir::Value val) {
     auto symbol = addrOfIface.getSymbol();
     if (auto global = mlir::SymbolTable::lookupNearestSymbolFrom<
             mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
-      return global.isDeviceData();
+      return global.isDeviceAccessible();
+  }
+
+  return false;
+}
+
+bool mlir::acc::isInDeviceMemoryValue(mlir::Value val) {
+  // In-device-memory data is a subset of device-accessible data: it must be
+  // accessible and its storage must physically reside in device memory.
+  if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
+    if (mappableTy.isInDeviceMemory(val))
+      return true;
+
+  if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
+    if (pointerLikeTy.isInDeviceMemory(val))
+      return true;
+
+  mlir::Operation *defOp = val.getDefiningOp();
+  if (!defOp)
+    return false;
+
+  // `acc.declare` with deviceptr marks data whose storage is already in device
+  // memory.
+  if (auto declareAttr =
+          defOp->getDiscardableAttrOfType<mlir::acc::DeclareAttr>(
+              mlir::acc::getDeclareAttrName()))
+    if (declareAttr.getDataClause().getValue() ==
+        mlir::acc::DataClause::acc_deviceptr)
+      return true;
+
+  // Handle operations that access a partial entity - check if the base entity
+  // is in device memory.
+  if (auto partialAccess =
+          dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
+    if (mlir::Value base = partialAccess.getBaseEntity())
+      return isInDeviceMemoryValue(base);
+  }
+
+  // Handle address_of - check if the referenced global is in device memory.
+  if (auto addrOfIface =
+          dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
+    auto symbol = addrOfIface.getSymbol();
+    if (auto global = mlir::SymbolTable::lookupNearestSymbolFrom<
+            mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
+      return global.isInDeviceMemory();
   }
 
   return false;
@@ -336,7 +380,7 @@ bool mlir::acc::isValidValueUse(mlir::Value val, mlir::Region &region) {
     return true;
 
   // If this is device data, it is valid.
-  if (isDeviceValue(val))
+  if (isDeviceAccessibleValue(val))
     return true;
 
   // Arguments of an enclosing acc routine are already on the device.

diff  --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
index e83a26a184cb53..6d6d62d74f6124 100644
--- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
@@ -1397,7 +1397,7 @@ TEST_F(OpenACCUtilsTest, getDominatingDataClausesEmpty) {
 }
 
 //===----------------------------------------------------------------------===//
-// isDeviceValue Tests
+// isDeviceAccessibleValue Tests
 //===----------------------------------------------------------------------===//
 
 namespace {
@@ -1440,7 +1440,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueMemrefGlobalAddressSpace) {
   Value val = allocOp->getResult();
 
   // Should return true since memref has GPU global address space
-  EXPECT_TRUE(isDeviceValue(val));
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueMemrefWorkgroupAddressSpace) {
@@ -1456,7 +1456,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueMemrefWorkgroupAddressSpace) {
   Value val = allocOp->getResult();
 
   // Should return true since memref has GPU workgroup address space
-  EXPECT_TRUE(isDeviceValue(val));
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueMemrefPrivateAddressSpace) {
@@ -1472,7 +1472,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueMemrefPrivateAddressSpace) {
   Value val = allocOp->getResult();
 
   // Should return true since memref has GPU private address space
-  EXPECT_TRUE(isDeviceValue(val));
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueMemrefNoAddressSpace) {
@@ -1484,7 +1484,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueMemrefNoAddressSpace) {
   Value val = allocOp->getResult();
 
   // Should return false since memref has no GPU address space
-  EXPECT_FALSE(isDeviceValue(val));
+  EXPECT_FALSE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueNonMappableType) {
@@ -1494,7 +1494,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueNonMappableType) {
   Value val = constOp->getResult();
 
   // Should return false since i32 is not a MappableType or PointerLikeType
-  EXPECT_FALSE(isDeviceValue(val));
+  EXPECT_FALSE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueGlobalWithGPUAddressSpace) {
@@ -1525,7 +1525,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueGlobalWithGPUAddressSpace) {
   Value val = getGlobalOp->getResult();
 
   // Should return true since the global has GPU address space
-  EXPECT_TRUE(isDeviceValue(val));
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueGlobalWithoutGPUAddressSpace) {
@@ -1552,7 +1552,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueGlobalWithoutGPUAddressSpace) {
   Value val = getGlobalOp->getResult();
 
   // Should return false since the global has no GPU address space
-  EXPECT_FALSE(isDeviceValue(val));
+  EXPECT_FALSE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueAccDeclareDeviceptr) {
@@ -1562,7 +1562,7 @@ TEST_F(OpenACCUtilsTest, isDeviceValueAccDeclareDeviceptr) {
   Value val = memrefViewFromBlockArgWithDeclare(
       b, loc, &context, DataClause::acc_deviceptr, module.get(),
       "test_memref_view_declare_devptr");
-  EXPECT_TRUE(isDeviceValue(val));
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
 }
 
 TEST_F(OpenACCUtilsTest, isDeviceValueAccDeclareNonDeviceptr) {
@@ -1572,7 +1572,88 @@ TEST_F(OpenACCUtilsTest, isDeviceValueAccDeclareNonDeviceptr) {
   Value val = memrefViewFromBlockArgWithDeclare(
       b, loc, &context, DataClause::acc_copyin, module.get(),
       "test_memref_view_declare_copyin");
-  EXPECT_FALSE(isDeviceValue(val));
+  EXPECT_FALSE(isDeviceAccessibleValue(val));
+}
+
+//===----------------------------------------------------------------------===//
+// isInDeviceMemoryValue Tests
+//===----------------------------------------------------------------------===//
+
+// For types that only establish device accessibility (such as a memref in a
+// GPU address space) and do not override isInDeviceMemory, the default mirrors
+// isDeviceAccessible: purely device-side storage stays in device memory.
+TEST_F(OpenACCUtilsTest, isInDeviceMemoryMemrefGlobalAddressSpace) {
+  auto gpuAddressSpace =
+      gpu::AddressSpaceAttr::get(&context, gpu::AddressSpace::Global);
+  auto memrefTy =
+      MemRefType::get({10}, b.getI32Type(), AffineMap(), gpuAddressSpace);
+
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  Value val = allocOp->getResult();
+
+  // Device-side memref is both accessible and in device memory.
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
+  EXPECT_TRUE(isInDeviceMemoryValue(val));
+}
+
+TEST_F(OpenACCUtilsTest, isInDeviceMemoryMemrefNoAddressSpace) {
+  auto memrefTy = MemRefType::get({10}, b.getI32Type());
+
+  OwningOpRef<memref::AllocaOp> allocOp =
+      memref::AllocaOp::create(b, loc, memrefTy);
+  Value val = allocOp->getResult();
+
+  // A host memref is neither accessible nor in device memory.
+  EXPECT_FALSE(isDeviceAccessibleValue(val));
+  EXPECT_FALSE(isInDeviceMemoryValue(val));
+}
+
+TEST_F(OpenACCUtilsTest, isInDeviceMemoryAccDeclareDeviceptr) {
+  OwningOpRef<ModuleOp> module = ModuleOp::create(loc);
+  OpBuilder::InsertionGuard guard(b);
+  b.setInsertionPointToStart(module->getBody());
+  Value val = memrefViewFromBlockArgWithDeclare(
+      b, loc, &context, DataClause::acc_deviceptr, module.get(),
+      "test_memref_view_declare_devptr_in_device_memory");
+  // `deviceptr`-declared storage is already in device memory.
+  EXPECT_TRUE(isInDeviceMemoryValue(val));
+}
+
+namespace {
+// Test-only pointer-like model for storage that is device-accessible but not
+// physically in device memory. This mirrors how a type backed by host-shared,
+// on-demand-migrating storage behaves: the current device can reach it, so it
+// is accessible, but because it is physically shared with the host it may
+// migrate and therefore may not be in device memory - it must still be
+// mapped/attached rather than treated as deviceptr.
+struct AccessibleNotInDeviceMemoryModel
+    : public PointerLikeType::ExternalModel<AccessibleNotInDeviceMemoryModel,
+                                            VectorType> {
+  Type getElementType(Type pointer) const {
+    return cast<VectorType>(pointer).getElementType();
+  }
+  bool isDeviceAccessible(Type pointer, Value var) const { return true; }
+  bool isInDeviceMemory(Type pointer, Value var) const { return false; }
+};
+} // namespace
+
+// The essential accessibility-vs-residence split: a value can be device
+// accessible yet not in device memory, in which case it must be mapped rather
+// than classified as deviceptr. No built-in MLIR type diverges here (their
+// isInDeviceMemory defaults to isDeviceAccessible), so use a test-only model.
+TEST_F(OpenACCUtilsTest, isInDeviceMemoryAccessibleButNotInDeviceMemory) {
+  VectorType::attachInterface<AccessibleNotInDeviceMemoryModel>(context);
+
+  auto vecTy = VectorType::get({4}, b.getI32Type());
+  auto zeroAttr = cast<TypedAttr>(b.getZeroAttr(vecTy));
+  OwningOpRef<arith::ConstantOp> constOp =
+      arith::ConstantOp::create(b, loc, zeroAttr);
+  Value val = constOp->getResult();
+
+  // Accessible from the device, but physically host-shared: must be mapped.
+  EXPECT_TRUE(isDeviceAccessibleValue(val));
+  EXPECT_FALSE(isInDeviceMemoryValue(val));
 }
 
 //===----------------------------------------------------------------------===//


        


More information about the flang-commits mailing list