[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