[flang-commits] [flang] [flang][acc] Implement PointerLikeType API gen[Allocate/Free/Copy] (PR #163660)

Razvan Lupusoru via flang-commits flang-commits at lists.llvm.org
Thu Oct 16 11:28:32 PDT 2025


https://github.com/razvanlupusoru updated https://github.com/llvm/llvm-project/pull/163660

>From 3d6c19ff10ee906947a657fcc5dfebd30e031c30 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Wed, 15 Oct 2025 16:45:45 -0700
Subject: [PATCH 1/2] [flang][acc] Implement PointerLikeType API
 gen[Allocate/Free/Copy]

Implements genAllocate, genFree, and genCopy for FIR pointer types
(fir.ref, fir.ptr, fir.heap, fir.llvm_ptr) in the OpenACC
PointerLikeType
interface.

- genAllocate: Uses fir.alloca for stack types, fir.allocmem for heap
  types. Returns null for dynamic/unknown types (unlimited polymorphic,
  dynamic arrays, dynamic character lengths).
- genFree: Generates fir.freemem for heap allocations.
- genCopy: Uses fir.load+fir.store for trivial types (scalars),
  hlfir.assign for non-trivial types (arrays, derived types,
characters).
  Returns false for unsupported dynamic types.

Adds comprehensive MLIR tests covering various FIR types and edge cases.
---
 .../Support/FIROpenACCTypeInterfaces.h        |  14 ++
 .../Support/FIROpenACCTypeInterfaces.cpp      | 222 ++++++++++++++++++
 .../OpenACC/pointer-like-interface-alloc.mlir | 122 ++++++++++
 .../OpenACC/pointer-like-interface-copy.mlir  | 120 ++++++++++
 .../OpenACC/pointer-like-interface-free.mlir  |  94 ++++++++
 flang/tools/fir-opt/CMakeLists.txt            |   1 +
 flang/tools/fir-opt/fir-opt.cpp               |   6 +-
 7 files changed, 578 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Fir/OpenACC/pointer-like-interface-alloc.mlir
 create mode 100644 flang/test/Fir/OpenACC/pointer-like-interface-copy.mlir
 create mode 100644 flang/test/Fir/OpenACC/pointer-like-interface-free.mlir

diff --git a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
index 408f0392e4271..4817ed933ba06 100644
--- a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
+++ b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.h
@@ -29,6 +29,20 @@ struct OpenACCPointerLikeModel
   getPointeeTypeCategory(mlir::Type pointer,
                          mlir::TypedValue<mlir::acc::PointerLikeType> varPtr,
                          mlir::Type varType) const;
+
+  mlir::Value genAllocate(mlir::Type pointer, mlir::OpBuilder &builder,
+                          mlir::Location loc, llvm::StringRef varName,
+                          mlir::Type varType, mlir::Value originalVar,
+                          bool &needsFree) const;
+
+  bool genFree(mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+               mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+               mlir::Value allocRes, mlir::Type varType) const;
+
+  bool genCopy(mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+               mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+               mlir::TypedValue<mlir::acc::PointerLikeType> source,
+               mlir::Type varType) const;
 };
 
 template <typename T>
diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index 9bf10b53108c0..c543ecbbef2b0 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -751,4 +751,226 @@ template bool OpenACCMappableModel<fir::PointerType>::generatePrivateDestroy(
     mlir::Type type, mlir::OpBuilder &builder, mlir::Location loc,
     mlir::Value privatized) const;
 
+template <typename Ty>
+mlir::Value OpenACCPointerLikeModel<Ty>::genAllocate(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
+    bool &needsFree) const {
+
+  // Get the element type from the pointer type
+  mlir::Type eleTy = mlir::cast<Ty>(pointer).getElementType();
+
+  // Unlimited polymorphic (class(*)) cannot be handled - size unknown
+  if (fir::isUnlimitedPolymorphicType(eleTy))
+    return {};
+
+  // Character types with dynamic length cannot be handled without a descriptor.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(eleTy))
+    if (charTy.hasDynamicLen())
+      return {};
+
+  // Return null for dynamic or unknown shape arrays because the size of the
+  // allocation cannot be determined simply from the type.
+  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy))
+    if (seqTy.hasDynamicExtents() || seqTy.hasUnknownShape())
+      return {};
+
+  // Use heap allocation for fir.heap, stack allocation for others (fir.ref,
+  // fir.ptr, fir.llvm_ptr). For fir.ptr, which is supposed to represent a
+  // Fortran pointer type, it feels a bit odd to "allocate" since it is meant
+  // to point to an existing entity - but one can imagine where a pointee is
+  // privatized - thus it makes sense to issue an allocate.
+  mlir::Value allocation;
+  if (std::is_same_v<Ty, fir::HeapType>) {
+    needsFree = true;
+    allocation = fir::AllocMemOp::create(builder, loc, eleTy);
+  } else {
+    needsFree = false;
+    allocation = fir::AllocaOp::create(builder, loc, eleTy);
+  }
+
+  // Convert to the requested pointer type if needed.
+  // This means converting from a fir.ref to either a fir.llvm_ptr or a fir.ptr.
+  // fir.heap is already correct type in this case.
+  if (allocation.getType() != pointer) {
+    assert(!(std::is_same_v<Ty, fir::HeapType>) &&
+           "fir.heap is already correct type because of allocmem");
+    return fir::ConvertOp::create(builder, loc, pointer, allocation);
+  }
+
+  return allocation;
+}
+
+template mlir::Value OpenACCPointerLikeModel<fir::ReferenceType>::genAllocate(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
+    bool &needsFree) const;
+
+template mlir::Value OpenACCPointerLikeModel<fir::PointerType>::genAllocate(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
+    bool &needsFree) const;
+
+template mlir::Value OpenACCPointerLikeModel<fir::HeapType>::genAllocate(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
+    bool &needsFree) const;
+
+template mlir::Value OpenACCPointerLikeModel<fir::LLVMPointerType>::genAllocate(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
+    bool &needsFree) const;
+
+static mlir::Value stripCasts(mlir::Value value, bool stripDeclare = true) {
+  mlir::Value currentValue = value;
+
+  while (currentValue) {
+    auto *definingOp = currentValue.getDefiningOp();
+    if (!definingOp)
+      break;
+
+    if (auto convertOp = mlir::dyn_cast<fir::ConvertOp>(definingOp)) {
+      currentValue = convertOp.getValue();
+      continue;
+    }
+
+    if (auto viewLike = mlir::dyn_cast<mlir::ViewLikeOpInterface>(definingOp)) {
+      currentValue = viewLike.getViewSource();
+      continue;
+    }
+
+    if (stripDeclare) {
+      if (auto declareOp = mlir::dyn_cast<hlfir::DeclareOp>(definingOp)) {
+        currentValue = declareOp.getMemref();
+        continue;
+      }
+
+      if (auto declareOp = mlir::dyn_cast<fir::DeclareOp>(definingOp)) {
+        currentValue = declareOp.getMemref();
+        continue;
+      }
+    }
+    break;
+  }
+
+  return currentValue;
+}
+
+template <typename Ty>
+bool OpenACCPointerLikeModel<Ty>::genFree(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+    mlir::Value allocRes, mlir::Type varType) const {
+
+  // If pointer type is HeapType, assume it's a heap allocation
+  if (std::is_same_v<Ty, fir::HeapType>) {
+    fir::FreeMemOp::create(builder, loc, varToFree);
+    return true;
+  }
+
+  // Use allocRes if provided to determine the allocation type
+  mlir::Value valueToInspect = allocRes ? allocRes : varToFree;
+
+  // Strip casts and declare operations to find the original allocation
+  mlir::Value strippedValue = stripCasts(valueToInspect);
+  mlir::Operation *originalAlloc = strippedValue.getDefiningOp();
+
+  // If we found an AllocMemOp (heap allocation), free it
+  if (mlir::isa_and_nonnull<fir::AllocMemOp>(originalAlloc)) {
+    mlir::Value toFree = varToFree;
+    if (!mlir::isa<fir::HeapType>(valueToInspect.getType()))
+      toFree = fir::ConvertOp::create(
+          builder, loc,
+          fir::HeapType::get(varToFree.getType().getElementType()), toFree);
+    fir::FreeMemOp::create(builder, loc, toFree);
+    return true;
+  }
+
+  // If we found an AllocaOp (stack allocation), no deallocation needed
+  if (mlir::isa_and_nonnull<fir::AllocaOp>(originalAlloc))
+    return true;
+
+  // Unable to determine allocation type
+  return false;
+}
+
+template bool OpenACCPointerLikeModel<fir::ReferenceType>::genFree(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+    mlir::Value allocRes, mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::PointerType>::genFree(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+    mlir::Value allocRes, mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::HeapType>::genFree(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+    mlir::Value allocRes, mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::genFree(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
+    mlir::Value allocRes, mlir::Type varType) const;
+
+template <typename Ty>
+bool OpenACCPointerLikeModel<Ty>::genCopy(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+    mlir::TypedValue<mlir::acc::PointerLikeType> source,
+    mlir::Type varType) const {
+
+  // Check that source and destination types match
+  if (source.getType() != destination.getType())
+    return false;
+
+  mlir::Type eleTy = mlir::cast<Ty>(pointer).getElementType();
+
+  // Unlimited polymorphic (class(*)) cannot be handled because source and
+  // destination types are not known.
+  if (fir::isUnlimitedPolymorphicType(eleTy))
+    return false;
+
+  // Dynamic lengths without descriptor do not have size information.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(eleTy))
+    if (charTy.hasDynamicLen())
+      return false;
+  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy))
+    if (seqTy.hasDynamicExtents() || seqTy.hasUnknownShape())
+      return false;
+
+  if (fir::isa_trivial(eleTy)) {
+    auto loadVal = fir::LoadOp::create(builder, loc, source);
+    fir::StoreOp::create(builder, loc, loadVal, destination);
+  } else {
+    hlfir::AssignOp::create(builder, loc, source, destination);
+  }
+  return true;
+}
+
+template bool OpenACCPointerLikeModel<fir::ReferenceType>::genCopy(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+    mlir::TypedValue<mlir::acc::PointerLikeType> source,
+    mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::PointerType>::genCopy(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+    mlir::TypedValue<mlir::acc::PointerLikeType> source,
+    mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::HeapType>::genCopy(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+    mlir::TypedValue<mlir::acc::PointerLikeType> source,
+    mlir::Type varType) const;
+
+template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::genCopy(
+    mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
+    mlir::TypedValue<mlir::acc::PointerLikeType> destination,
+    mlir::TypedValue<mlir::acc::PointerLikeType> source,
+    mlir::Type varType) const;
+
 } // namespace fir::acc
diff --git a/flang/test/Fir/OpenACC/pointer-like-interface-alloc.mlir b/flang/test/Fir/OpenACC/pointer-like-interface-alloc.mlir
new file mode 100644
index 0000000000000..0da360a26b3e6
--- /dev/null
+++ b/flang/test/Fir/OpenACC/pointer-like-interface-alloc.mlir
@@ -0,0 +1,122 @@
+// RUN: fir-opt %s --split-input-file --pass-pipeline="builtin.module(func.func(test-acc-pointer-like-interface{test-mode=alloc}))" 2>&1 | FileCheck %s
+
+// The tests here use a synthetic hlfir.declare in order to ensure that the hlfir dialect is
+// loaded. This is required because the pass used is part of OpenACC test passes outside of
+// flang and the APIs being test may generate hlfir even when it does not appear.
+
+func.func @test_ref_scalar_alloc() {
+  %0 = fir.alloca f32 {test.ptr}
+  %1:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca f32 {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.alloca f32
+  return
+}
+
+// -----
+
+func.func @test_ref_static_array_alloc() {
+  %0 = fir.alloca !fir.array<10x20xf32> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.array<10x20xf32> {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.alloca !fir.array<10x20xf32>
+  return
+}
+
+// -----
+
+func.func @test_ref_derived_type_alloc() {
+  %0 = fir.alloca !fir.type<_QTt{i:i32}> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}> {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}>
+  return
+}
+
+// -----
+
+func.func @test_heap_scalar_alloc() {
+  %0 = fir.allocmem f32 {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.allocmem f32 {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.allocmem f32
+  return
+}
+
+// -----
+
+func.func @test_heap_static_array_alloc() {
+  %0 = fir.allocmem !fir.array<10x20xf32> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.allocmem !fir.array<10x20xf32> {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.allocmem !fir.array<10x20xf32>
+  return
+}
+
+// -----
+
+func.func @test_ptr_scalar_alloc() {
+  %0 = fir.alloca f32
+  %1 = fir.convert %0 {test.ptr} : (!fir.ref<f32>) -> !fir.ptr<f32>
+  %2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation
+  // CHECK: Generated: %{{.*}} = fir.alloca f32
+  // CHECK: Generated: %{{.*}} = fir.convert %{{.*}} : (!fir.ref<f32>) -> !fir.ptr<f32>
+  return
+}
+
+// -----
+
+func.func @test_llvm_ptr_scalar_alloc() {
+  %0 = fir.alloca f32
+  %1 = fir.convert %0 {test.ptr} : (!fir.ref<f32>) -> !fir.llvm_ptr<f32>
+  %2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation
+  // CHECK: Generated: %{{.*}} = fir.alloca f32
+  // CHECK: Generated: %{{.*}} = fir.convert %{{.*}} : (!fir.ref<f32>) -> !fir.llvm_ptr<f32>
+  return
+}
+
+// -----
+
+func.func @test_dynamic_array_alloc_fails(%arg0: !fir.ref<!fir.array<?xf32>>) {
+  %0 = fir.convert %arg0 {test.ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate alloc for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
+  return
+}
+
+// -----
+
+func.func @test_unlimited_polymorphic_alloc_fails() {
+  %0 = fir.alloca !fir.class<none> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate alloc for operation: %{{.*}} = fir.alloca !fir.class<none> {test.ptr}
+  return
+}
+
+// -----
+
+func.func @test_dynamic_char_alloc_fails(%arg0: !fir.ref<!fir.char<1,?>>) {
+  %0 = fir.convert %arg0 {test.ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate alloc for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
+  return
+}
+
+// -----
+
+func.func @test_static_char_alloc() {
+  %0 = fir.alloca !fir.char<1,10> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.char<1,10> {test.ptr}
+  // CHECK: Generated: %{{.*}} = fir.alloca !fir.char<1,10>
+  return
+}
diff --git a/flang/test/Fir/OpenACC/pointer-like-interface-copy.mlir b/flang/test/Fir/OpenACC/pointer-like-interface-copy.mlir
new file mode 100644
index 0000000000000..99fc012cb903e
--- /dev/null
+++ b/flang/test/Fir/OpenACC/pointer-like-interface-copy.mlir
@@ -0,0 +1,120 @@
+// RUN: fir-opt %s --split-input-file --pass-pipeline="builtin.module(func.func(test-acc-pointer-like-interface{test-mode=copy}))" 2>&1 | FileCheck %s
+
+// The tests here use a synthetic hlfir.declare in order to ensure that the hlfir dialect is
+// loaded. This is required because the pass used is part of OpenACC test passes outside of
+// flang and the APIs being test may generate hlfir even when it does not appear.
+
+func.func @test_copy_scalar() {
+  %src = fir.alloca f32 {test.src_ptr}
+  %dest = fir.alloca f32 {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated copy from source: %{{.*}} = fir.alloca f32 {test.src_ptr} to destination: %{{.*}} = fir.alloca f32 {test.dest_ptr}
+  // CHECK: Generated: %{{.*}} = fir.load %{{.*}} : !fir.ref<f32>
+  // CHECK: Generated: fir.store %{{.*}} to %{{.*}} : !fir.ref<f32>
+  return
+}
+
+// -----
+
+func.func @test_copy_static_array() {
+  %src = fir.alloca !fir.array<10x20xf32> {test.src_ptr}
+  %dest = fir.alloca !fir.array<10x20xf32> {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated copy from source: %{{.*}} = fir.alloca !fir.array<10x20xf32> {test.src_ptr} to destination: %{{.*}} = fir.alloca !fir.array<10x20xf32> {test.dest_ptr}
+  // CHECK: Generated: hlfir.assign %{{.*}} to %{{.*}} : !fir.ref<!fir.array<10x20xf32>>, !fir.ref<!fir.array<10x20xf32>>
+  return
+}
+
+// -----
+
+func.func @test_copy_derived_type() {
+  %src = fir.alloca !fir.type<_QTt{i:i32}> {test.src_ptr}
+  %dest = fir.alloca !fir.type<_QTt{i:i32}> {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated copy from source: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}> {test.src_ptr} to destination: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}> {test.dest_ptr}
+  // CHECK: Generated: hlfir.assign %{{.*}} to %{{.*}} : !fir.ref<!fir.type<_QTt{i:i32}>>, !fir.ref<!fir.type<_QTt{i:i32}>>
+  return
+}
+
+// -----
+
+func.func @test_copy_heap_scalar() {
+  %src = fir.allocmem f32 {test.src_ptr}
+  %dest = fir.allocmem f32 {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated copy from source: %{{.*}} = fir.allocmem f32 {test.src_ptr} to destination: %{{.*}} = fir.allocmem f32 {test.dest_ptr}
+  // CHECK: Generated: %{{.*}} = fir.load %{{.*}} : !fir.heap<f32>
+  // CHECK: Generated: fir.store %{{.*}} to %{{.*}} : !fir.heap<f32>
+  return
+}
+
+// -----
+
+func.func @test_copy_static_char() {
+  %src = fir.alloca !fir.char<1,10> {test.src_ptr}
+  %dest = fir.alloca !fir.char<1,10> {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated copy from source: %{{.*}} = fir.alloca !fir.char<1,10> {test.src_ptr} to destination: %{{.*}} = fir.alloca !fir.char<1,10> {test.dest_ptr}
+  // CHECK: Generated: hlfir.assign %{{.*}} to %{{.*}} : !fir.ref<!fir.char<1,10>>, !fir.ref<!fir.char<1,10>>
+  return
+}
+
+// -----
+
+func.func @test_copy_mismatched_types_fails() {
+  %src = fir.alloca f32 {test.src_ptr}
+  %dest = fir.alloca f64 {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate copy from source: %{{.*}} = fir.alloca f32 {test.src_ptr} to destination: %{{.*}} = fir.alloca f64 {test.dest_ptr}
+  return
+}
+
+// -----
+
+func.func @test_copy_mismatched_shapes_fails() {
+  %src = fir.alloca !fir.array<10xf32> {test.src_ptr}
+  %dest = fir.alloca !fir.array<20xf32> {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate copy from source: %{{.*}} = fir.alloca !fir.array<10xf32> {test.src_ptr} to destination: %{{.*}} = fir.alloca !fir.array<20xf32> {test.dest_ptr}
+  return
+}
+
+// -----
+
+func.func @test_copy_dynamic_array_fails(%arg0: !fir.ref<!fir.array<?xf32>>, %arg1: !fir.ref<!fir.array<?xf32>>) {
+  %src = fir.convert %arg0 {test.src_ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
+  %dest = fir.convert %arg1 {test.dest_ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate copy from source: %{{.*}} = fir.convert %{{.*}} {test.src_ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>> to destination: %{{.*}} = fir.convert %{{.*}} {test.dest_ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
+  return
+}
+
+// -----
+
+func.func @test_copy_unlimited_polymorphic_fails() {
+  %src = fir.alloca !fir.class<none> {test.src_ptr}
+  %dest = fir.alloca !fir.class<none> {test.dest_ptr}
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate copy from source: %{{.*}} = fir.alloca !fir.class<none> {test.src_ptr} to destination: %{{.*}} = fir.alloca !fir.class<none> {test.dest_ptr}
+  return
+}
+
+// -----
+
+func.func @test_copy_dynamic_char_fails(%arg0: !fir.ref<!fir.char<1,?>>, %arg1: !fir.ref<!fir.char<1,?>>) {
+  %src = fir.convert %arg0 {test.src_ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
+  %dest = fir.convert %arg1 {test.dest_ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
+  %var = fir.alloca f32
+  %0:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Failed to generate copy from source: %{{.*}} = fir.convert %{{.*}} {test.src_ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>> to destination: %{{.*}} = fir.convert %{{.*}} {test.dest_ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
+  return
+}
diff --git a/flang/test/Fir/OpenACC/pointer-like-interface-free.mlir b/flang/test/Fir/OpenACC/pointer-like-interface-free.mlir
new file mode 100644
index 0000000000000..6334752d9e998
--- /dev/null
+++ b/flang/test/Fir/OpenACC/pointer-like-interface-free.mlir
@@ -0,0 +1,94 @@
+// RUN: fir-opt %s --split-input-file --pass-pipeline="builtin.module(func.func(test-acc-pointer-like-interface{test-mode=free}))" 2>&1 | FileCheck %s
+
+// The tests here use a synthetic hlfir.declare in order to ensure that the hlfir dialect is
+// loaded. This is required because the pass used is part of OpenACC test passes outside of
+// flang and the APIs being test may generate hlfir even when it does not appear.
+
+func.func @test_ref_scalar_free() {
+  %0 = fir.alloca f32 {test.ptr}
+  %1:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.alloca f32 {test.ptr}
+  // CHECK-NOT: Generated
+  return
+}
+
+// -----
+
+func.func @test_heap_scalar_free() {
+  %0 = fir.allocmem f32 {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.allocmem f32 {test.ptr}
+  // CHECK: Generated: fir.freemem %{{.*}} : !fir.heap<f32>
+  return
+}
+
+// -----
+
+func.func @test_heap_array_free() {
+  %0 = fir.allocmem !fir.array<10x20xf32> {test.ptr}
+  %var = fir.alloca f32
+  %1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.allocmem !fir.array<10x20xf32> {test.ptr}
+  // CHECK: Generated: fir.freemem %{{.*}} : !fir.heap<!fir.array<10x20xf32>>
+  return
+}
+
+// -----
+
+func.func @test_convert_walking_free() {
+  %0 = fir.alloca f32
+  %1 = fir.convert %0 {test.ptr} : (!fir.ref<f32>) -> !fir.ptr<f32>
+  %2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.ref<f32>) -> !fir.ptr<f32>
+  // CHECK-NOT: Generated
+  return
+}
+
+// -----
+
+func.func @test_declare_walking_free() {
+  %0 = fir.alloca f32
+  %1 = fir.declare %0 {test.ptr, uniq_name = "x"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.declare %{{.*}} {test.ptr, uniq_name = "x"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  // CHECK-NOT: Generated
+  return
+}
+
+// -----
+
+func.func @test_hlfir_declare_walking_free() {
+  %0 = fir.alloca f32
+  %1:2 = hlfir.declare %0 {test.ptr, uniq_name = "x"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  %var = fir.alloca f32
+  %2:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation
+  // CHECK-NOT: Generated
+  return
+}
+
+// -----
+
+func.func @test_heap_through_convert_free() {
+  %0 = fir.allocmem f32
+  %1 = fir.convert %0 {test.ptr} : (!fir.heap<f32>) -> !fir.llvm_ptr<f32>
+  %var = fir.alloca f32
+  %2:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.heap<f32>) -> !fir.llvm_ptr<f32>
+  // CHECK: Generated: %{{.*}} = fir.convert %{{.*}} : (!fir.llvm_ptr<f32>) -> !fir.heap<f32>
+  // CHECK: Generated: fir.freemem %{{.*}} : !fir.heap<f32>
+  return
+}
+
+// -----
+
+func.func @test_heap_through_declare_free() {
+  %0 = fir.allocmem f32
+  %1 = fir.declare %0 {test.ptr, uniq_name = "x"} : (!fir.heap<f32>) -> !fir.heap<f32>
+  %var = fir.alloca f32
+  %2:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  // CHECK: Successfully generated free for operation: %{{.*}} = fir.declare %{{.*}} {test.ptr, uniq_name = "x"} : (!fir.heap<f32>) -> !fir.heap<f32>
+  // CHECK: Generated: fir.freemem %{{.*}} : !fir.heap<f32>
+  return
+}
diff --git a/flang/tools/fir-opt/CMakeLists.txt b/flang/tools/fir-opt/CMakeLists.txt
index 4ee9752727b87..27b4e8e864074 100644
--- a/flang/tools/fir-opt/CMakeLists.txt
+++ b/flang/tools/fir-opt/CMakeLists.txt
@@ -7,6 +7,7 @@ if(FLANG_INCLUDE_TESTS)
   set(test_libs
     FIRTestAnalysis
     FIRTestOpenACCInterfaces
+    MLIROpenACCTestPasses
     MLIRTestIR
     )
 endif()
diff --git a/flang/tools/fir-opt/fir-opt.cpp b/flang/tools/fir-opt/fir-opt.cpp
index d66fc3f08bdf8..cb1180bb27fbb 100644
--- a/flang/tools/fir-opt/fir-opt.cpp
+++ b/flang/tools/fir-opt/fir-opt.cpp
@@ -29,7 +29,10 @@ void registerTestFIROpenACCInterfacesPass();
 // Defined in mlir/test, no pulic header.
 namespace mlir {
 void registerSideEffectTestPasses();
-}
+namespace test {
+void registerTestOpenACC();
+} // namespace test
+} // namespace mlir
 
 int main(int argc, char **argv) {
   fir::support::registerMLIRPassesForFortranTools();
@@ -41,6 +44,7 @@ int main(int argc, char **argv) {
   fir::test::registerTestFIRAliasAnalysisPass();
   fir::test::registerTestFIROpenACCInterfacesPass();
   mlir::registerSideEffectTestPasses();
+  mlir::test::registerTestOpenACC();
 #endif
   DialectRegistry registry;
   fir::support::registerDialects(registry);

>From 1ddf3cfdc1ba501d132f1be8bb240c2fd7b6638f Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Thu, 16 Oct 2025 11:28:20 -0700
Subject: [PATCH 2/2] Include box rejection and better dynamic size checks

---
 .../Support/FIROpenACCTypeInterfaces.cpp      | 67 ++++++++++++-------
 1 file changed, 43 insertions(+), 24 deletions(-)

diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index c543ecbbef2b0..ed9e41c743754 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -757,23 +757,27 @@ mlir::Value OpenACCPointerLikeModel<Ty>::genAllocate(
     llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
     bool &needsFree) const {
 
-  // Get the element type from the pointer type
-  mlir::Type eleTy = mlir::cast<Ty>(pointer).getElementType();
+  // Unwrap to get the pointee type.
+  mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
+  assert(pointeeTy && "expected pointee type to be extractable");
+
+  // Box types are descriptors that contain both metadata and a pointer to data.
+  // The `genAllocate` API is designed for simple allocations and cannot
+  // properly handle the dual nature of boxes. Using `generatePrivateInit`
+  // instead can allocate both the descriptor and its referenced data. For use
+  // cases that require an empty descriptor storage, potentially this could be
+  // implemented here.
+  if (fir::isa_box_type(pointeeTy))
+    return {};
 
   // Unlimited polymorphic (class(*)) cannot be handled - size unknown
-  if (fir::isUnlimitedPolymorphicType(eleTy))
+  if (fir::isUnlimitedPolymorphicType(pointeeTy))
     return {};
 
-  // Character types with dynamic length cannot be handled without a descriptor.
-  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(eleTy))
-    if (charTy.hasDynamicLen())
-      return {};
-
-  // Return null for dynamic or unknown shape arrays because the size of the
+  // Return null for dynamic size types because the size of the
   // allocation cannot be determined simply from the type.
-  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy))
-    if (seqTy.hasDynamicExtents() || seqTy.hasUnknownShape())
-      return {};
+  if (fir::hasDynamicSize(pointeeTy))
+    return {};
 
   // Use heap allocation for fir.heap, stack allocation for others (fir.ref,
   // fir.ptr, fir.llvm_ptr). For fir.ptr, which is supposed to represent a
@@ -783,10 +787,10 @@ mlir::Value OpenACCPointerLikeModel<Ty>::genAllocate(
   mlir::Value allocation;
   if (std::is_same_v<Ty, fir::HeapType>) {
     needsFree = true;
-    allocation = fir::AllocMemOp::create(builder, loc, eleTy);
+    allocation = fir::AllocMemOp::create(builder, loc, pointeeTy);
   } else {
     needsFree = false;
-    allocation = fir::AllocaOp::create(builder, loc, eleTy);
+    allocation = fir::AllocaOp::create(builder, loc, pointeeTy);
   }
 
   // Convert to the requested pointer type if needed.
@@ -862,6 +866,17 @@ bool OpenACCPointerLikeModel<Ty>::genFree(
     mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
     mlir::Value allocRes, mlir::Type varType) const {
 
+  // Unwrap to get the pointee type.
+  mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
+  assert(pointeeTy && "expected pointee type to be extractable");
+
+  // Box types contain both a descriptor and data. The `genFree` API
+  // handles simple deallocations and cannot properly manage both parts.
+  // Using `generatePrivateDestroy` instead can free both the descriptor and
+  // its referenced data.
+  if (fir::isa_box_type(pointeeTy))
+    return false;
+
   // If pointer type is HeapType, assume it's a heap allocation
   if (std::is_same_v<Ty, fir::HeapType>) {
     fir::FreeMemOp::create(builder, loc, varToFree);
@@ -925,22 +940,26 @@ bool OpenACCPointerLikeModel<Ty>::genCopy(
   if (source.getType() != destination.getType())
     return false;
 
-  mlir::Type eleTy = mlir::cast<Ty>(pointer).getElementType();
+  // Unwrap to get the pointee type.
+  mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
+  assert(pointeeTy && "expected pointee type to be extractable");
+
+  // Box types contain both a descriptor and referenced data. The genCopy API
+  // handles simple copies and cannot properly manage both parts.
+  if (fir::isa_box_type(pointeeTy))
+    return false;
 
   // Unlimited polymorphic (class(*)) cannot be handled because source and
   // destination types are not known.
-  if (fir::isUnlimitedPolymorphicType(eleTy))
+  if (fir::isUnlimitedPolymorphicType(pointeeTy))
     return false;
 
-  // Dynamic lengths without descriptor do not have size information.
-  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(eleTy))
-    if (charTy.hasDynamicLen())
-      return false;
-  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy))
-    if (seqTy.hasDynamicExtents() || seqTy.hasUnknownShape())
-      return false;
+  // Return false for dynamic size types because the copy logic
+  // cannot be determined simply from the type.
+  if (fir::hasDynamicSize(pointeeTy))
+    return false;
 
-  if (fir::isa_trivial(eleTy)) {
+  if (fir::isa_trivial(pointeeTy)) {
     auto loadVal = fir::LoadOp::create(builder, loc, source);
     fir::StoreOp::create(builder, loc, loadVal, destination);
   } else {



More information about the flang-commits mailing list