[flang-commits] [flang] 6a7945b - [flang][openacc] Erase unused stack allocations in compute regions (#227807)

via flang-commits flang-commits at lists.llvm.org
Wed Sep 30 20:12:26 PDT 2026


Author: Valentin Clement (バレンタイン クレメン)
Date: 2026-09-30T20:12:15-07:00
New Revision: 6a7945b744e251cd6c843521a5e918e364a49dc5

URL: https://github.com/llvm/llvm-project/commit/6a7945b744e251cd6c843521a5e918e364a49dc5
DIFF: https://github.com/llvm/llvm-project/commit/6a7945b744e251cd6c843521a5e918e364a49dc5.diff

LOG: [flang][openacc] Erase unused stack allocations in compute regions (#227807)

ACCEraseUnusedKernelAllocations only deleted unused fir.allocmem. A
dynamic fir.alloca, memref.alloca, or memref.alloc inside
acc.compute_region has the same problem: fir.declare's debug effect and
the matching free keep it alive through ordinary dead-code elimination,
and lowering turns it into a checked device malloc.
Delete those allocations when they have no uses, or when every use is
fir.freemem, memref.dealloc, a view such as fir.convert, or fir.declare.
A load, store, or other memory use still keeps the allocation.

This can happen when using stack arrays flags which replace the
fir.allocmem

Added: 
    

Modified: 
    flang/include/flang/Optimizer/OpenACC/Passes.td
    flang/lib/Optimizer/OpenACC/Transforms/ACCEraseUnusedKernelAllocations.cpp
    flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
    flang/test/Fir/OpenACC/acc-erase-unused-kernel-allocations.mlir

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Optimizer/OpenACC/Passes.td b/flang/include/flang/Optimizer/OpenACC/Passes.td
index 47c23cdb78862..2a584ca96796c 100644
--- a/flang/include/flang/Optimizer/OpenACC/Passes.td
+++ b/flang/include/flang/Optimizer/OpenACC/Passes.td
@@ -103,16 +103,18 @@ def ACCDeclareActionConversion
 
 def ACCEraseUnusedKernelAllocations
     : Pass<"acc-erase-unused-kernel-allocations", "mlir::func::FuncOp"> {
-  let summary = "Erase unused fir.allocmem inside acc.compute_region";
+  let summary = "Erase unused allocations inside acc.compute_region";
   let description = [{
-    Deletes a fir.allocmem inside an acc.compute_region when its storage is
-    never read or written. The allocation may have no uses, or its only uses
+    Deletes an allocation inside an acc.compute_region when its storage is
+    never read or written. The allocation may be fir.allocmem, fir.alloca,
+    memref.alloc, or memref.alloca. It may have no uses, or its only uses
     may be a view of that storage (such as fir.convert), fir.declare, and
-    fir.freemem. fir.declare's debug effect and fir.freemem keep that chain
-    alive through ordinary DCE, so it would otherwise be lowered to a checked
-    device malloc. A load, store, or other memory use keeps the allocation.
-    An unused private-recipe allocation is deleted the same way as an unused
-    source array.
+    the matching free (fir.freemem or memref.dealloc). fir.declare's debug
+    effect and the free keep that chain alive through ordinary DCE, so a
+    dynamic allocation would otherwise be lowered to a checked device malloc.
+    A load, store, or other memory use keeps the allocation. An unused
+    private-recipe allocation is deleted the same way as an unused source
+    array.
   }];
 }
 

diff  --git a/flang/lib/Optimizer/OpenACC/Transforms/ACCEraseUnusedKernelAllocations.cpp b/flang/lib/Optimizer/OpenACC/Transforms/ACCEraseUnusedKernelAllocations.cpp
index 9c0e3495f2526..b39018291acd2 100644
--- a/flang/lib/Optimizer/OpenACC/Transforms/ACCEraseUnusedKernelAllocations.cpp
+++ b/flang/lib/Optimizer/OpenACC/Transforms/ACCEraseUnusedKernelAllocations.cpp
@@ -1,4 +1,4 @@
-//===- ACCEraseUnusedKernelAllocations.cpp - Drop dead kernel allocmem ---===//
+//===- ACCEraseUnusedKernelAllocations.cpp - Drop dead kernel allocs -----===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,16 +6,19 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Erase fir.allocmem inside acc.compute_region when the storage is never read
-// or written. fir.declare's debug effect and fir.freemem keep that chain alive
-// through ordinary DCE, and lowering it would produce a checked device malloc.
-// An unused private-recipe allocation is deleted the same way as an unused
-// source array.
+// Erase an allocation inside acc.compute_region when the storage is never
+// read or written. Covered allocations are fir.allocmem, fir.alloca,
+// memref.alloc, and memref.alloca. fir.declare's debug effect and the
+// matching free (fir.freemem or memref.dealloc) keep that chain alive
+// through ordinary DCE, and a dynamic allocation would otherwise be lowered
+// to a checked device malloc. An unused private-recipe allocation is deleted
+// the same way as an unused source array.
 //
 //===----------------------------------------------------------------------===//
 
 #include "flang/Optimizer/Dialect/FIROps.h"
 #include "flang/Optimizer/OpenACC/Passes.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
 #include "mlir/IR/Value.h"
 #include "mlir/Interfaces/ViewLikeInterface.h"
@@ -31,14 +34,29 @@ namespace {
 
 using namespace mlir;
 
-// True when every use of \p value is fir.freemem, a view of that value
-// (ViewLikeOpInterface, including fir.convert), or a fir.declare of that
-// value, and the same is true of those results. No uses is included: the
-// allocation is dead. fir.declare is not view-like; it only carries debug
-// info for the memref.
+// fir.freemem or memref.dealloc of \p value.
+bool isDeallocOf(Operation *user, Value value) {
+  if (auto freeMem = dyn_cast<fir::FreeMemOp>(user))
+    return freeMem.getHeapref() == value;
+  if (auto dealloc = dyn_cast<memref::DeallocOp>(user))
+    return dealloc.getMemref() == value;
+  return false;
+}
+
+// Heap and stack allocations that privatization may turn into a device malloc.
+bool isErasedAlloc(Operation *op) {
+  return isa<fir::AllocMemOp, fir::AllocaOp, memref::AllocOp, memref::AllocaOp>(
+      op);
+}
+
+// True when every use of \p value is a free of that value, a view of that
+// value (ViewLikeOpInterface, including fir.convert), or a fir.declare of
+// that value, and the same is true of those results. No uses is included:
+// the allocation is dead. fir.declare is not view-like; it only carries
+// debug info for the memref.
 bool isDeadAllocChain(Value value, SmallPtrSetImpl<Operation *> &bookkeeping) {
   for (Operation *user : value.getUsers()) {
-    if (isa<fir::FreeMemOp>(user)) {
+    if (isDeallocOf(user, value)) {
       bookkeeping.insert(user);
       continue;
     }
@@ -98,19 +116,21 @@ class ACCEraseUnusedKernelAllocations
 public:
   void runOnOperation() override {
     func::FuncOp func = getOperation();
-    SmallVector<fir::AllocMemOp> dead;
-    func.walk([&](fir::AllocMemOp alloc) {
-      if (!alloc->getParentOfType<acc::ComputeRegionOp>())
+    SmallVector<Operation *> dead;
+    func.walk([&](Operation *op) {
+      if (!isErasedAlloc(op) || op->getNumResults() != 1)
+        return;
+      if (!op->getParentOfType<acc::ComputeRegionOp>())
         return;
       SmallPtrSet<Operation *, 8> bookkeeping;
-      if (!isDeadAllocChain(alloc.getResult(), bookkeeping))
+      if (!isDeadAllocChain(op->getResult(0), bookkeeping))
         return;
-      dead.push_back(alloc);
+      dead.push_back(op);
     });
 
-    for (fir::AllocMemOp alloc : dead) {
+    for (Operation *alloc : dead) {
       SmallPtrSet<Operation *, 8> bookkeeping;
-      if (!isDeadAllocChain(alloc.getResult(), bookkeeping))
+      if (!isDeadAllocChain(alloc->getResult(0), bookkeeping))
         continue;
       SmallVector<Operation *> toErase(bookkeeping.begin(), bookkeeping.end());
       toErase.push_back(alloc);

diff  --git a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
index ae12ff6abee82..06564230b7513 100644
--- a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
@@ -28,6 +28,7 @@ add_flang_library(FIROpenACCTransforms
   MLIR_LIBS
   MLIRArithDialect
   MLIRIR
+  MLIRMemRefDialect
   MLIRPass
   MLIROpenACCDialect
   MLIROpenACCUtils

diff  --git a/flang/test/Fir/OpenACC/acc-erase-unused-kernel-allocations.mlir b/flang/test/Fir/OpenACC/acc-erase-unused-kernel-allocations.mlir
index 402feb1644633..57ff004358494 100644
--- a/flang/test/Fir/OpenACC/acc-erase-unused-kernel-allocations.mlir
+++ b/flang/test/Fir/OpenACC/acc-erase-unused-kernel-allocations.mlir
@@ -39,6 +39,38 @@ module {
     } <{origin = "acc.routine"}>
     return
   }
+
+  // A dynamic stack or memref heap allocation that is never read or written
+  // is deleted. A stack slot outside the compute region is left alone.
+  func.func @erase_dead_stack_keep_used() {
+    %c1 = arith.constant 1 : index
+    %pw = acc.par_width %c1 par_dim(#acc.par_dim<block_x>)
+    acc.compute_region launch(%arg0 = %pw) {
+      %n = arith.constant 4 : index
+      %c0 = arith.constant 0 : index
+      %shape = fir.shape %n : (index) -> !fir.shape<1>
+      %shift = fir.shape_shift %c0, %n : (index, index) -> !fir.shapeshift<1>
+      %dead_stack = memref.alloca(%n) {bindc_name = "dead_stack", uniq_name = "dead_stack"} : memref<?xf64>
+      %dead_stack_ref = fir.convert %dead_stack : (memref<?xf64>) -> !fir.ref<!fir.array<?xf64>>
+      %dead_stack_decl = fir.declare %dead_stack_ref(%shift) {uniq_name = "dead_stack"} : (!fir.ref<!fir.array<?xf64>>, !fir.shapeshift<1>) -> !fir.ref<!fir.array<?xf64>>
+      %dead_alloca = fir.alloca !fir.array<?xf64>, %n {bindc_name = "dead_alloca", uniq_name = "dead_alloca"}
+      %dead_alloca_decl = fir.declare %dead_alloca(%shape) {uniq_name = "dead_alloca"} : (!fir.ref<!fir.array<?xf64>>, !fir.shape<1>) -> !fir.ref<!fir.array<?xf64>>
+      %dead_heap = memref.alloc(%n) {bindc_name = "dead_heap", uniq_name = "dead_heap"} : memref<?xf64>
+      memref.dealloc %dead_heap : memref<?xf64>
+      %live_stack = memref.alloca(%n) {bindc_name = "live_stack", uniq_name = "live_stack"} : memref<?xf64>
+      %live_stack_ref = fir.convert %live_stack : (memref<?xf64>) -> !fir.ref<!fir.array<?xf64>>
+      %live_stack_decl = fir.declare %live_stack_ref(%shape) {uniq_name = "live_stack"} : (!fir.ref<!fir.array<?xf64>>, !fir.shape<1>) -> !fir.ref<!fir.array<?xf64>>
+      %elem = fir.array_coor %live_stack_decl(%shape) %c0 : (!fir.ref<!fir.array<?xf64>>, !fir.shape<1>, index) -> !fir.ref<f64>
+      %val = fir.load %elem : !fir.ref<f64>
+      fir.store %val to %elem : !fir.ref<f64>
+      %live_alloca = fir.alloca f64 {bindc_name = "live_alloca", uniq_name = "live_alloca"}
+      %zero = arith.constant 0.0 : f64
+      fir.store %zero to %live_alloca : !fir.ref<f64>
+      acc.yield
+    } <{origin = "acc.routine"}>
+    %host_stack = memref.alloca() {uniq_name = "host_stack"} : memref<f64>
+    return
+  }
 }
 
 // CHECK-LABEL: func.func @erase_dead_keep_used
@@ -49,3 +81,14 @@ module {
 // CHECK: fir.allocmem {{.*}}bindc_name = "acc.private.init"
 // CHECK-NOT: fir.allocmem
 // CHECK-NOT: uniq_name = "dead"
+
+// CHECK-LABEL: func.func @erase_dead_stack_keep_used
+// CHECK-NOT: uniq_name = "dead_stack"
+// CHECK-NOT: uniq_name = "dead_alloca"
+// CHECK-NOT: uniq_name = "dead_heap"
+// CHECK: memref.alloca{{.*}}uniq_name = "live_stack"
+// CHECK: fir.alloca {{.*}}uniq_name = "live_alloca"
+// CHECK: uniq_name = "host_stack"
+// CHECK-NOT: uniq_name = "dead_stack"
+// CHECK-NOT: uniq_name = "dead_alloca"
+// CHECK-NOT: uniq_name = "dead_heap"


        


More information about the flang-commits mailing list