[flang-commits] [flang] [flang][acc] Add ACCDeclareActionConversion pass (PR #181894)

via flang-commits flang-commits at lists.llvm.org
Tue Feb 17 12:12:53 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-fir-hlfir

Author: Razvan Lupusoru (razvanlupusoru)

<details>
<summary>Changes</summary>

Implements the allocation and deallocation semantics for allocatables and pointers in declare directives (OpenACC 3.4, Section 2.13.2). For a non-shared memory device, allocate/deallocate must keep local and device memory in sync.

Lowering generates recipe functions (with acc dialect ops to create the device copy) and attaches an attribute to the operations that allocate or deallocate the object. This pass finds those operations and inserts calls to the corresponding recipe.

Example: for "!$acc declare create(arr)" and "allocate(arr(100))" in a subroutine, the pass inserts a call to the post-alloc recipe after the store so the device copy is created.

---
Full diff: https://github.com/llvm/llvm-project/pull/181894.diff


5 Files Affected:

- (modified) flang/include/flang/Optimizer/OpenACC/Passes.h (+1) 
- (modified) flang/include/flang/Optimizer/OpenACC/Passes.td (+13) 
- (added) flang/lib/Optimizer/OpenACC/Transforms/ACCDeclareActionConversion.cpp (+213) 
- (modified) flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt (+1) 
- (added) flang/test/Fir/OpenACC/acc-declare-action-conversion.mlir (+97) 


``````````diff
diff --git a/flang/include/flang/Optimizer/OpenACC/Passes.h b/flang/include/flang/Optimizer/OpenACC/Passes.h
index fd1c4db8d78c0..c9341e862bcd8 100644
--- a/flang/include/flang/Optimizer/OpenACC/Passes.h
+++ b/flang/include/flang/Optimizer/OpenACC/Passes.h
@@ -30,6 +30,7 @@ namespace acc {
 
 std::unique_ptr<mlir::Pass> createACCInitializeFIRAnalysesPass();
 std::unique_ptr<mlir::Pass> createACCOptimizeFirstprivateMapPass();
+std::unique_ptr<mlir::Pass> createACCDeclareActionConversionPass();
 std::unique_ptr<mlir::Pass> createACCRecipeBufferizationPass();
 std::unique_ptr<mlir::Pass> createACCUseDeviceCanonicalizerPass();
 
diff --git a/flang/include/flang/Optimizer/OpenACC/Passes.td b/flang/include/flang/Optimizer/OpenACC/Passes.td
index ea324b0ae5f7f..1ffffcb145a03 100644
--- a/flang/include/flang/Optimizer/OpenACC/Passes.td
+++ b/flang/include/flang/Optimizer/OpenACC/Passes.td
@@ -70,6 +70,19 @@ def ACCUseDeviceCanonicalizer
   let dependentDialects = ["mlir::acc::OpenACCDialect", "fir::FIROpsDialect"];
 }
 
+def ACCDeclareActionConversion
+    : Pass<"fir-acc-declare-action-conversion", "mlir::ModuleOp"> {
+  let summary = "Convert acc declare action attributes to function calls";
+  let description = [{
+    Implements OpenACC specification (section 2.13.2 in OpenACC 3.4) for
+    allocation/deallocation actions of allocatables and pointers in declare
+    directives. Lowering generates recipe functions and marks the operations
+    that allocate or deallocate the object; this pass finds those operations
+    and inserts calls to the corresponding recipes.
+  }];
+  let dependentDialects = ["mlir::func::FuncDialect"];
+}
+
 def ACCOptimizeFirstprivateMap
     : Pass<"acc-optimize-firstprivate-map", "mlir::func::FuncOp"> {
   let summary = "Optimize firstprivate mapping";
diff --git a/flang/lib/Optimizer/OpenACC/Transforms/ACCDeclareActionConversion.cpp b/flang/lib/Optimizer/OpenACC/Transforms/ACCDeclareActionConversion.cpp
new file mode 100644
index 0000000000000..bfdefcf2c3091
--- /dev/null
+++ b/flang/lib/Optimizer/OpenACC/Transforms/ACCDeclareActionConversion.cpp
@@ -0,0 +1,213 @@
+//===- ACCDeclareActionConversion.cpp -------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implements the allocation and deallocation semantics for allocatables and
+// pointers in declare directives. OpenACC 3.4, Section 2.13.2: in Fortran, if
+// a variable in the declare var-list has the allocatable or pointer attribute,
+// then for a non-shared memory device, an allocate (or intrinsic assignment
+// that allocates) allocates in both local and device memory and sets the
+// dynamic reference counter to one; a deallocate (or assignment that
+// deallocates) deallocates from both and sets the counter to zero.
+//
+// How this pass works:
+// - Lowering generates recipe functions that hold the recipe for creating the
+//   device copy (using acc dialect operations, e.g. acc.create).
+// - Lowering also attaches an attribute to the operations that allocate or
+//   deallocate the object.
+// - This pass finds operations with that attribute and inserts calls to the
+//   corresponding recipe.
+//
+// Example:
+//   module mm
+//     real, allocatable :: arr(:)
+//     !$acc declare create(arr)
+//   contains
+//     subroutine sub()
+//       allocate(arr(100))
+//     end subroutine sub
+//   end module mm
+//
+// Relevant IR before this pass (recipe function and store with attribute):
+//   func.func private @_QMmmEarr_acc_declare_update_desc_post_alloc(...) {
+//     ...  // acc ops to create/register device copy
+//     return
+//   }
+//   func.func @_QMmmPsub() {
+//     ...
+//     fir.store %box to %desc {acc.declare_action = #acc.declare_action<
+//       postAlloc = @_QMmmEarr_acc_declare_update_desc_post_alloc>} ...
+//   }
+//
+// After this pass (call to recipe inserted after the store):
+//   func.func @_QMmmPsub() {
+//     ...
+//     fir.store %box to %desc ...
+//     call @_QMmmEarr_acc_declare_update_desc_post_alloc()
+//   }
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FIRType.h"
+#include "flang/Optimizer/OpenACC/Passes.h"
+#include "flang/Optimizer/OpenACC/Support/FIROpenACCUtils.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/Operation.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/IR/Value.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include "llvm/Support/Debug.h"
+
+#define DEBUG_TYPE "fir-acc-declare-action-conversion"
+
+namespace fir {
+namespace acc {
+#define GEN_PASS_DEF_ACCDECLAREACTIONCONVERSION
+#include "flang/Optimizer/OpenACC/Passes.h.inc"
+} // namespace acc
+} // namespace fir
+
+using namespace mlir;
+
+namespace {
+
+// Fortran runtime symbol names for pointer allocate/deallocate.
+static constexpr llvm::StringRef pointerAllocateName =
+    "_FortranAPointerAllocate";
+static constexpr llvm::StringRef pointerDeallocateName =
+    "_FortranAPointerDeallocate";
+
+class ACCDeclareActionConversion
+    : public fir::acc::impl::ACCDeclareActionConversionBase<
+          ACCDeclareActionConversion> {
+public:
+  using fir::acc::impl::ACCDeclareActionConversionBase<
+      ACCDeclareActionConversion>::ACCDeclareActionConversionBase;
+
+  void runOnOperation() override {
+    ModuleOp mod = getOperation();
+    OpBuilder builder(mod);
+
+    mod.walk([&](Operation *op) {
+      auto declareAction = op->getAttrOfType<acc::DeclareActionAttr>(
+          acc::getDeclareActionAttrName());
+      if (!declareAction)
+        return;
+
+      LLVM_DEBUG(llvm::dbgs() << "Found " << acc::getDeclareActionAttrName()
+                              << " on: " << *op << "\n");
+
+      auto preAlloc = declareAction.getPreAlloc();
+      auto postAlloc = declareAction.getPostAlloc();
+      auto preDealloc = declareAction.getPreDealloc();
+      auto postDealloc = declareAction.getPostDealloc();
+
+      if (!preAlloc && !postAlloc && !preDealloc && !postDealloc)
+        return;
+
+      for (auto action : {preAlloc, postAlloc, preDealloc, postDealloc}) {
+        if (!action)
+          continue;
+
+        if (auto func = dyn_cast<SymbolRefAttr>(action)) {
+          Operation *funcDef = SymbolTable::lookupNearestSymbolFrom(op, func);
+          if (!funcDef)
+            continue;
+
+          if (auto funcOp = dyn_cast<func::FuncOp>(funcDef))
+            if (!funcOp->hasAttr(mlir::acc::getDeclareActionAttrName()))
+              funcOp->setAttr(mlir::acc::getDeclareActionAttrName(),
+                              mlir::UnitAttr::get(funcOp.getContext()));
+
+          if (action == declareAction.getPreAlloc() ||
+              action == declareAction.getPreDealloc())
+            builder.setInsertionPoint(op);
+          else
+            builder.setInsertionPointAfter(op);
+
+          auto funcOp = dyn_cast<func::FuncOp>(funcDef);
+          if (!funcOp) {
+            op->emitError("declare action callee is not a func.func operation");
+            return;
+          }
+          SmallVector<Value> argVec;
+          if (funcOp.getNumArguments() > 0) {
+            Value varRef =
+                llvm::TypeSwitch<Operation *, Value>(op)
+                    .Case<fir::StoreOp>(
+                        [&](auto store) { return store.getMemref(); })
+                    .Case<fir::BoxAddrOp>(
+                        [&](auto boxAddr) { return boxAddr.getVal(); })
+                    .Case<fir::CallOp>([&](fir::CallOp call) -> Value {
+                      if (auto callee = call.getCalleeAttr()) {
+                        StringRef funcName =
+                            callee.getLeafReference().getValue();
+                        if (funcName == pointerAllocateName ||
+                            funcName == pointerDeallocateName) {
+                          auto args = call.getArgs();
+                          if (args.empty())
+                            return {};
+                          Value boxRef = args[0];
+                          if (!fir::isBoxAddress(boxRef.getType()))
+                            return {};
+                          return boxRef;
+                        }
+                      }
+                      return {};
+                    })
+                    .Default([](Operation *) { return Value(); });
+
+            if (!varRef) {
+              op->emitError(
+                  "could not find argument for declare action recipe call");
+              return;
+            }
+            if (fir::isa_box_type(varRef.getType())) {
+              auto loadOp = varRef.getDefiningOp<fir::LoadOp>();
+              if (!loadOp) {
+                op->emitError("varRef for declare action is not from fir.load");
+                return;
+              }
+              varRef = loadOp.getMemref();
+            }
+            varRef = fir::acc::getOriginalDef(varRef, /*stripDeclare=*/false);
+            // Runtime calls (e.g. PointerAllocate) use ref<box<none>>; recipe
+            // expects typed box ref. Look through one convert to get the typed
+            // ref when getOriginalDef stopped at the convert (original LRO
+            // semantics: do not look through when result is box none).
+            Type recipeArgTy = funcOp.getFunctionType().getInput(0);
+            if (varRef.getType() != recipeArgTy) {
+              if (auto convertOp = varRef.getDefiningOp<fir::ConvertOp>()) {
+                Value converted = convertOp.getValue();
+                if (converted.getType() == recipeArgTy)
+                  varRef = converted;
+              }
+            }
+            if (varRef.getType() != recipeArgTy) {
+              op->emitError("declare action recipe expects typed box ref");
+              return;
+            }
+            argVec.push_back(varRef);
+          }
+          func::CallOp::create(builder, op->getLoc(),
+                               funcOp.getFunctionType().getResults(),
+                               funcOp.getSymName(), argVec);
+        }
+      }
+    });
+  }
+};
+
+} // namespace
+
+std::unique_ptr<mlir::Pass> fir::acc::createACCDeclareActionConversionPass() {
+  return std::make_unique<ACCDeclareActionConversion>();
+}
diff --git a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
index 27c5ee64aea27..5bf4e629861cf 100644
--- a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_flang_library(FIROpenACCTransforms
+  ACCDeclareActionConversion.cpp
   ACCInitializeFIRAnalyses.cpp
   ACCOptimizeFirstprivateMap.cpp
   ACCRecipeBufferization.cpp
diff --git a/flang/test/Fir/OpenACC/acc-declare-action-conversion.mlir b/flang/test/Fir/OpenACC/acc-declare-action-conversion.mlir
new file mode 100644
index 0000000000000..d310a6ed6d058
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-declare-action-conversion.mlir
@@ -0,0 +1,97 @@
+// RUN: fir-opt %s --fir-acc-declare-action-conversion -o - | FileCheck %s
+
+// Check declare_action conversion for global variables
+// module mm
+// contains
+//   subroutine sub()
+//     real :: arr(10)
+//     !$acc declare create(arr)
+//     arr(1) = 3.0
+//   end subroutine sub
+// end module mm
+//
+// program main
+//   use mm
+//   call sub()
+// end program main
+module {
+  fir.global @_QMmmEarr {acc.declare = #acc.declare<dataClause =  acc_copyin>} : !fir.box<!fir.heap<!fir.array<?xf32>>> {
+  }
+  func.func private @_QMmmEarr_acc_declare_update_desc_post_alloc() {
+    return
+  }
+// CHECK: func.func private @_QMmmEarr_acc_declare_update_desc_post_alloc() attributes {acc.declare_action}
+// CHECK-LABEL: func.func @_QMmmPsub
+  func.func @_QMmmPsub() {
+    %c100 = arith.constant 100 : index
+    %c0 = arith.constant 0 : index
+    %0 = fir.dummy_scope : !fir.dscope
+    %1 = fir.address_of(@_QMmmEarr) : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+    %2 = fir.declare %1 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QMmmEarr"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+    %3 = fir.allocmem !fir.array<?xf32>, %c100 {fir.must_be_heap = true, uniq_name = "_QMmmEarr.alloc"}
+    %4 = fir.shape %c100 : (index) -> !fir.shape<1>
+    %5 = fir.embox %3(%4) : (!fir.heap<!fir.array<?xf32>>, !fir.shape<1>) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+    fir.store %5 to %2 {acc.declare_action = #acc.declare_action<postAlloc = @_QMmmEarr_acc_declare_update_desc_post_alloc>} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+    return
+  }
+// CHECK: fir.store
+// CHECK: call @_QMmmEarr_acc_declare_update_desc_post_alloc
+}
+
+
+// Test declare_action on runtime allocation path (fir.call) with box<none> argument.
+module {
+  func.func private @_QFdeclareAp_acc_declare_post_alloc(%arg0: !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) {
+    return
+  }
+  func.func @_QPdeclare() {
+    %1 = fir.alloca !fir.box<!fir.ptr<!fir.array<?xf32>>> {uniq_name = "_QFdeclareEap"}
+    %2 = fir.declare %1 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QFdeclareEap"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+    %3 = fir.convert %2 : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<none>>
+    %f = arith.constant 0 : i1
+    %absent = fir.absent !fir.box<none>
+    %s_i8 = fir.zero_bits !fir.ref<i8>
+    %c4_i32 = arith.constant 4 : i32
+    fir.call @_FortranAPointerAllocate(%3, %f, %absent, %s_i8, %c4_i32) {acc.declare_action = #acc.declare_action<postAlloc = @_QFdeclareAp_acc_declare_post_alloc>} : (!fir.ref<!fir.box<none>>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
+    return
+  }
+// CHECK-LABEL: func.func @_QPdeclare
+// CHECK: %[[DECL:.*]] = fir.declare %{{.*}} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+// CHECK: %[[CVT:.*]] = fir.convert %[[DECL]] : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<none>>
+// CHECK: fir.call @_FortranAPointerAllocate(%[[CVT]],
+// CHECK-NEXT: call @_QFdeclareAp_acc_declare_post_alloc(%[[DECL]])
+  func.func private @_FortranAPointerAllocate(!fir.ref<!fir.box<none>>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
+}
+
+
+// Test structured declare_action (allocatable with postAlloc)
+module {
+ func.func private @_QMmmFsubEarr_acc_declare_update_desc_post_alloc(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) {
+   return
+ }
+ func.func private @_QMmmFsubEarr_acc_declare_update_desc_pre_dealloc(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) {
+   return
+ }
+ func.func private @_QMmmFsubEarr_acc_declare_update_desc_post_dealloc(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) {
+   return
+ }
+// CHECK: func.func private @_QMmmFsubEarr_acc_declare_update_desc_post_alloc(%{{.*}}) attributes {acc.declare_action}
+// CHECK-LABEL: func.func @_QMmmPsub
+  func.func @_QMmmPsub() {
+    %c100 = arith.constant 100 : index
+    %c0 = arith.constant 0 : index
+    %1 = fir.alloca !fir.box<!fir.heap<!fir.array<?xf32>>> {bindc_name = "arr", uniq_name = "_QMmmFsubEarr"}
+    %2 = fir.zero_bits !fir.heap<!fir.array<?xf32>>
+    %3 = fir.shape %c0 : (index) -> !fir.shape<1>
+    %4 = fir.embox %2(%3) : (!fir.heap<!fir.array<?xf32>>, !fir.shape<1>) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+    %5 = fir.declare %1 {acc.declare = #acc.declare<dataClause =  acc_copyin>, fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QMmmFsubEarr"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+    %8 = fir.allocmem !fir.array<?xf32>, %c100 {fir.must_be_heap = true, uniq_name = "_QMmmFsubEarr.alloc"}
+    %9 = fir.shape %c100 : (index) -> !fir.shape<1>
+    %10 = fir.embox %8(%9) : (!fir.heap<!fir.array<?xf32>>, !fir.shape<1>) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+    fir.store %10 to %5 {acc.declare_action = #acc.declare_action<postAlloc = @_QMmmFsubEarr_acc_declare_update_desc_post_alloc>} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+    return
+  }
+// CHECK: %[[DECLARE:.*]] = fir.declare {{.*}}
+// CHECK: fir.store
+// CHECK: call @_QMmmFsubEarr_acc_declare_update_desc_post_alloc(%[[DECLARE]])
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/181894


More information about the flang-commits mailing list