[flang-commits] [flang] [flang][fir] do not move alloca outside parallel regions in StackArrays (PR #218679)
via flang-commits
flang-commits at lists.llvm.org
Wed Aug 26 01:05:40 PDT 2026
https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/218679
>From 7781b855f9b498c0ae47b796656ab6ba9c914592 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Tue, 25 Aug 2026 04:51:24 -0700
Subject: [PATCH 1/2] [flang][fir] do not move alloca outside parallel regions
in StackArrays
---
.../flang/Optimizer/Builder/FIRBuilder.h | 7 +
flang/lib/Optimizer/Builder/FIRBuilder.cpp | 61 ++---
.../lib/Optimizer/Transforms/StackArrays.cpp | 91 +++++---
.../target-private-allocatable.f90 | 5 +-
.../OpenACC/optional-firstprivate-recipe.fir | 4 +-
.../OpenACC/optional-firstprivate.fir | 4 +-
.../test/Transforms/allocation-placement.fir | 21 ++
.../Transforms/stack-arrays-alloca-scope.fir | 214 ++++++++++++++++++
8 files changed, 343 insertions(+), 64 deletions(-)
create mode 100644 flang/test/Transforms/stack-arrays-alloca-scope.fir
diff --git a/flang/include/flang/Optimizer/Builder/FIRBuilder.h b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
index fa695a73e6c54..22d0a6f7febbc 100644
--- a/flang/include/flang/Optimizer/Builder/FIRBuilder.h
+++ b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
@@ -50,6 +50,13 @@ inline mlir::Type getIntPtrType(mlir::OpBuilder &builder) {
return builder.getI64Type();
}
+/// Get the block of \p region where alloca-like operations should be inserted:
+/// the block where the closest parent operation owning the stack allocations of
+/// \p region expects them (an OpenACC compute construct, an outlineable OpenMP
+/// operation, a privatization or reduction recipe, ...), or the entry block of
+/// the enclosing function.
+mlir::Block *getAllocaBlock(mlir::Region ®ion);
+
//===----------------------------------------------------------------------===//
// FirOpBuilder
//===----------------------------------------------------------------------===//
diff --git a/flang/lib/Optimizer/Builder/FIRBuilder.cpp b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
index 3d10e82930d9b..a84d4fd978e05 100644
--- a/flang/lib/Optimizer/Builder/FIRBuilder.cpp
+++ b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
@@ -278,36 +278,43 @@ mlir::Value fir::FirOpBuilder::allocateLocal(
}
/// Get the block for adding Allocas.
-mlir::Block *fir::FirOpBuilder::getAllocaBlock() {
- if (auto accComputeRegionIface =
- getRegion().getParentOfType<mlir::acc::ComputeRegionOpInterface>()) {
- return accComputeRegionIface.getAllocaBlock();
- }
-
- if (auto ompOutlineableIface =
- getRegion()
- .getParentOfType<mlir::omp::OutlineableOpenMPOpInterface>()) {
- return ompOutlineableIface.getAllocaBlock();
- }
-
- if (auto recipeIface =
- getRegion().getParentOfType<mlir::accomp::RecipeInterface>()) {
- return recipeIface.getAllocaBlock(getRegion());
+mlir::Block *fir::getAllocaBlock(mlir::Region ®ion) {
+ // Find the closest parent operation that knows where its allocas belong.
+ for (mlir::Region *current = ®ion; current;
+ current = current->getParentRegion()) {
+ mlir::Operation *parent = current->getParentOp();
+ if (!parent)
+ break;
+
+ if (auto accComputeRegionIface =
+ mlir::dyn_cast<mlir::acc::ComputeRegionOpInterface>(parent))
+ return accComputeRegionIface.getAllocaBlock();
+
+ if (auto ompOutlineableIface =
+ mlir::dyn_cast<mlir::omp::OutlineableOpenMPOpInterface>(parent))
+ return ompOutlineableIface.getAllocaBlock();
+
+ if (auto recipeIface =
+ mlir::dyn_cast<mlir::accomp::RecipeInterface>(parent))
+ return recipeIface.getAllocaBlock(*current);
+
+ if (auto cufKernelOp = mlir::dyn_cast<cuf::KernelOp>(parent))
+ return &cufKernelOp.getRegion().front();
+
+ if (auto doConcurentOp = mlir::dyn_cast<fir::DoConcurrentOp>(parent))
+ return doConcurentOp.getBody();
+
+ if (mlir::isa<fir::LocalitySpecifierOp, fir::DeclareReductionOp>(parent))
+ return ¤t->front();
}
- if (auto cufKernelOp = getRegion().getParentOfType<cuf::KernelOp>())
- return &cufKernelOp.getRegion().front();
-
- if (auto doConcurentOp = getRegion().getParentOfType<fir::DoConcurrentOp>())
- return doConcurentOp.getBody();
-
- if (auto firLocalOp = getRegion().getParentOfType<fir::LocalitySpecifierOp>())
- return &getRegion().front();
-
- if (auto firLocalOp = getRegion().getParentOfType<fir::DeclareReductionOp>())
- return &getRegion().front();
+ auto func = region.getParentOfType<mlir::func::FuncOp>();
+ assert(func && "region must be inside a function");
+ return &func.front();
+}
- return getEntryBlock();
+mlir::Block *fir::FirOpBuilder::getAllocaBlock() {
+ return fir::getAllocaBlock(getRegion());
}
static mlir::ArrayAttr makeI64ArrayAttr(llvm::ArrayRef<int64_t> values,
diff --git a/flang/lib/Optimizer/Transforms/StackArrays.cpp b/flang/lib/Optimizer/Transforms/StackArrays.cpp
index 77861e67a07b1..93869e76e8fe5 100644
--- a/flang/lib/Optimizer/Transforms/StackArrays.cpp
+++ b/flang/lib/Optimizer/Transforms/StackArrays.cpp
@@ -492,13 +492,48 @@ llvm::LogicalResult fir::AllocMemConversion::matchAndRewrite(
return mlir::success();
}
-static bool isInLoop(mlir::Block *block) {
- return mlir::LoopLikeOpInterface::blockIsInLoop(block);
+/// Return true if \p block can reach itself, i.e. it belongs to a control flow
+/// graph loop. This mirrors the control flow graph part of
+/// mlir::LoopLikeOpInterface::blockIsInLoop, which cannot be used here because
+/// it also walks the parent operations (see isInStackGrowingLoop).
+static bool isInCFGLoop(mlir::Block *block) {
+ llvm::DenseSet<mlir::Block *> visited;
+ llvm::SmallVector<mlir::Block *> stack{block};
+ while (!stack.empty()) {
+ mlir::Block *current = stack.pop_back_val();
+ auto [it, inserted] = visited.insert(current);
+ if (!inserted) {
+ if (current == block)
+ return true;
+ continue;
+ }
+ for (mlir::Block *successor : current->getSuccessors())
+ stack.push_back(successor);
+ }
+ return false;
}
-static bool isInLoop(mlir::Operation *op) {
- return isInLoop(op->getBlock()) ||
- op->getParentOfType<mlir::LoopLikeOpInterface>();
+/// Return true if a stack allocation placed in \p block would be repeated
+/// without its stack space being given back, making the stack grow. Such an
+/// allocation needs an explicit stack save/restore around it.
+static bool isInStackGrowingLoop(mlir::Block *block) {
+ while (block) {
+ if (isInCFGLoop(block))
+ return true;
+ mlir::Operation *parent = block->getParentOp();
+ if (!parent || mlir::isa<mlir::FunctionOpInterface>(parent))
+ return false;
+ // A loop whose body is an automatic allocation scope (acc.loop for
+ // instance) gives its stack space back at the end of every iteration.
+ if (mlir::isa<mlir::LoopLikeOpInterface>(parent))
+ return !parent->hasTrait<mlir::OpTrait::AutomaticAllocationScope>();
+ block = parent->getBlock();
+ }
+ return false;
+}
+
+static bool isInStackGrowingLoop(mlir::Operation *op) {
+ return isInStackGrowingLoop(op->getBlock());
}
fir::InsertionPoint fir::AllocMemConversion::findAllocaInsertionPoint(
@@ -508,16 +543,19 @@ fir::InsertionPoint fir::AllocMemConversion::findAllocaInsertionPoint(
// block so that we do not allocate stack space in a loop. However,
// the operands to the alloca may not be available that early, so insert it
// after the last operand becomes available
- // If the old allocmem op was in an openmp region then it should not be moved
- // outside of that
+ // It must also stay in the block where the enclosing construct expects its
+ // stack allocations: a construct modelling parallelism (an OpenACC compute
+ // construct or an outlineable OpenMP operation for instance) needs a distinct
+ // allocation for each of its concurrent executions.
LLVM_DEBUG(llvm::dbgs() << "StackArrays: findAllocaInsertionPoint: "
<< oldAlloc << "\n");
- // check that an Operation or Block we are about to return is not in a loop
+ // check that an Operation or Block we are about to return does not make the
+ // stack grow
auto checkReturn = [&](auto *point) -> InsertionPoint {
- if (isInLoop(point)) {
+ if (isInStackGrowingLoop(point)) {
mlir::Operation *oldAllocOp = oldAlloc.getOperation();
- if (isInLoop(oldAllocOp)) {
+ if (isInStackGrowingLoop(oldAllocOp)) {
// where we want to put it is in a loop, and even the old location is in
// a loop. Give up.
return findAllocaLoopInsertionPoint(oldAlloc, freeOps);
@@ -527,8 +565,10 @@ fir::InsertionPoint fir::AllocMemConversion::findAllocaInsertionPoint(
return {point};
};
- auto oldOmpRegion =
- oldAlloc->getParentOfType<mlir::omp::OutlineableOpenMPOpInterface>();
+ // The earliest block where the alloca may be created, and the region it is
+ // part of, which the allocation cannot leave.
+ mlir::Block *allocaBlock = fir::getAllocaBlock(*oldAlloc->getParentRegion());
+ mlir::Region *allocaRegion = allocaBlock->getParent();
// Find when the last operand value becomes available
mlir::Block *operandsBlock = nullptr;
@@ -590,31 +630,20 @@ fir::InsertionPoint fir::AllocMemConversion::findAllocaInsertionPoint(
}
}
- // check we aren't moving out of an omp region
- auto lastOpOmpRegion =
- lastOperand->getParentOfType<mlir::omp::OutlineableOpenMPOpInterface>();
- if (lastOpOmpRegion == oldOmpRegion)
+ // Check we aren't moving the allocation out of the region it belongs to.
+ if (allocaRegion->isAncestor(lastOperand->getParentRegion()))
return checkReturn(lastOperand);
- // Presumably this happened because the operands became ready before the
- // start of this openmp region. (lastOpOmpRegion != oldOmpRegion) should
- // imply that oldOmpRegion comes after lastOpOmpRegion.
- return checkReturn(oldOmpRegion.getAllocaBlock());
+ // The operands became ready before the start of the enclosing construct.
+ LLVM_DEBUG(llvm::dbgs() << "--Last operand is defined outside of the "
+ "region the allocation belongs to\n");
+ return checkReturn(allocaBlock);
}
// There were no value operands to the allocmem so we are safe to insert it
// as early as we want
-
- // handle openmp case
- if (oldOmpRegion)
- return checkReturn(oldOmpRegion.getAllocaBlock());
-
- // fall back to the function entry block
- mlir::func::FuncOp func = oldAlloc->getParentOfType<mlir::func::FuncOp>();
- assert(func && "This analysis is run on func.func");
- mlir::Block &entryBlock = func.getBlocks().front();
- LLVM_DEBUG(llvm::dbgs() << "--Placing at the start of func entry block\n");
- return checkReturn(&entryBlock);
+ LLVM_DEBUG(llvm::dbgs() << "--Placing at the start of the alloca block\n");
+ return checkReturn(allocaBlock);
}
fir::InsertionPoint fir::AllocMemConversion::findAllocaLoopInsertionPoint(
diff --git a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-allocatable.f90 b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-allocatable.f90
index 114a75c1603d4..8c5ee99b26fd5 100644
--- a/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-allocatable.f90
+++ b/flang/test/Lower/OpenMP/DelayedPrivatization/target-private-allocatable.f90
@@ -77,12 +77,13 @@ end subroutine target_allocatable
! CPU-SAME: @[[VAR_PRIVATIZER_SYM]] %[[VAR_DECL]]#0 -> %{{.*}} [map_idx=0] : [[TYPE]]) {
! GPU-LABEL: omp.private {type = private} {{.*}} init {
+! GPU-NOT: fir.allocmem i32
+! GPU: %[[PRIV_ALLOC:.*]] = fir.alloca i32
! GPU: fir.if %{{.*}} {
! GPU-NEXT: %[[ZERO_BOX:.*]] = fir.embox %{{.*}}
! GPU-NEXT: fir.store %[[ZERO_BOX]] to %{{.*}}
! GPU-NEXT: } else {
-! GPU-NOT: fir.allocmem i32
-! GPU-NEXT: %[[PRIV_ALLOC:.*]] = fir.alloca i32
+! GPU-NOT: fir.allocmem i32
! GPU-NEXT: %[[PRIV_ALLOC_BOX:.*]] = fir.embox %[[PRIV_ALLOC]]
! GPU-NEXT: fir.store %[[PRIV_ALLOC_BOX]] to %{{.*}}
! GPU-NEXT: }
diff --git a/flang/test/Transforms/OpenACC/optional-firstprivate-recipe.fir b/flang/test/Transforms/OpenACC/optional-firstprivate-recipe.fir
index 3a326aee0d692..d295e0dc35782 100644
--- a/flang/test/Transforms/OpenACC/optional-firstprivate-recipe.fir
+++ b/flang/test/Transforms/OpenACC/optional-firstprivate-recipe.fir
@@ -15,10 +15,10 @@ func.func @test_optional_firstprivate_f32(%arg0: !fir.ref<f32> {fir.bindc_name =
// CHECK-LABEL: acc.firstprivate.recipe @firstprivatization_optional_ref_f32 : !fir.ref<f32> init {
// CHECK: ^bb0(%{{.*}}: !fir.ref<f32>):
+// CHECK: %[[TEMP:.*]] = fir.alloca f32
// CHECK: %{{.*}} = fir.is_present %{{.*}} : (!fir.ref<f32>) -> i1
// CHECK: fir.if %{{.*}} -> (!fir.ref<f32>) {
-// CHECK: %{{.*}} = fir.alloca f32
-// CHECK: fir.result %{{.*}} : !fir.ref<f32>
+// CHECK: fir.result %[[TEMP]] : !fir.ref<f32>
// CHECK: } else {
// CHECK: %{{.*}} = fir.absent !fir.ref<f32>
// CHECK: fir.result %{{.*}} : !fir.ref<f32>
diff --git a/flang/test/Transforms/OpenACC/optional-firstprivate.fir b/flang/test/Transforms/OpenACC/optional-firstprivate.fir
index 44a59d33291f5..b453641d1c92a 100644
--- a/flang/test/Transforms/OpenACC/optional-firstprivate.fir
+++ b/flang/test/Transforms/OpenACC/optional-firstprivate.fir
@@ -20,10 +20,10 @@ func.func @test_optional_firstprivate(%arg0: !fir.ref<i32> {fir.bindc_name = "x"
// After acc-recipe-materialization: recipe is materialized, check the inlined body
// CHECK: acc.firstprivate_map varPtr
// CHECK: acc.parallel {
+// CHECK: %[[TEMP:.*]] = fir.alloca i32
// CHECK: fir.is_present
// CHECK: fir.if %{{.*}} -> (!fir.ref<i32>) {
-// CHECK: fir.alloca i32
-// CHECK: fir.result
+// CHECK: fir.result %[[TEMP]]
// CHECK: } else {
// CHECK: fir.absent !fir.ref<i32>
// CHECK: fir.result
diff --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index ef8fce82c03ac..8b0a1acb74122 100644
--- a/flang/test/Transforms/allocation-placement.fir
+++ b/flang/test/Transforms/allocation-placement.fir
@@ -81,4 +81,25 @@ func.func @dyn_temp(%n: index) {
return
}
+// Small temporary inside an OpenACC compute construct -> stack, but the
+// allocation must stay inside the construct.
+// CHECK-LABEL: func.func @small_temp_in_acc_parallel
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: fir.alloca !fir.array<10xi32>
+// CHECK-NOT: fir.allocmem
+func.func @small_temp_in_acc_parallel() {
+ %c0 = arith.constant 0 : index
+ %v = arith.constant 0 : i32
+ acc.parallel {
+ %0 = fir.allocmem !fir.array<10xi32>
+ %r = fir.convert %0 : (!fir.heap<!fir.array<10xi32>>) -> !fir.ref<!fir.array<10xi32>>
+ %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<10xi32>>, index) -> !fir.ref<i32>
+ fir.store %v to %e : !fir.ref<i32>
+ fir.freemem %0 : !fir.heap<!fir.array<10xi32>>
+ acc.yield
+ }
+ return
+}
+
}
diff --git a/flang/test/Transforms/stack-arrays-alloca-scope.fir b/flang/test/Transforms/stack-arrays-alloca-scope.fir
new file mode 100644
index 0000000000000..342f09ccfaf6d
--- /dev/null
+++ b/flang/test/Transforms/stack-arrays-alloca-scope.fir
@@ -0,0 +1,214 @@
+// RUN: fir-opt --stack-arrays %s | FileCheck %s
+
+// Test that stack allocations are created in the block where the enclosing
+// construct expects them (fir::getAllocaBlock) and are not hoisted out of it:
+// each concurrent execution of a construct modelling parallelism needs its own
+// storage.
+
+// The allocation has no operand but must still stay inside the compute
+// construct instead of being hoisted to the function entry block.
+func.func @acc_parallel_no_operand() {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.parallel {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_parallel_no_operand()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+// CHECK-NOT: fir.allocmem
+// CHECK-NOT: fir.freemem
+
+func.func @acc_serial_no_operand() {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.serial {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_serial_no_operand()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.serial {
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+
+func.func @acc_kernels_no_operand() {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.kernels {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ acc.terminator
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_kernels_no_operand()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.kernels {
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+
+// The extent operand is available before the compute construct: the allocation
+// can only be hoisted up to the beginning of the construct.
+func.func @acc_parallel_operand_outside(%n: index) {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ %size = arith.addi %n, %n : index
+ acc.parallel {
+ %mem = fir.allocmem !fir.array<?xi32>, %size
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<?xi32>>) -> !fir.ref<!fir.array<?xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<?xi32>>
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_parallel_operand_outside(
+// CHECK: %[[SIZE:.*]] = arith.addi
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: fir.alloca !fir.array<?xi32>, %[[SIZE]]
+
+// The extent operand is defined inside the compute construct: the allocation is
+// placed right after it, as it would be in a plain function.
+func.func @acc_parallel_operand_inside(%n: index) {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.parallel {
+ %size = arith.addi %n, %n : index
+ %mem = fir.allocmem !fir.array<?xi32>, %size
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<?xi32>>) -> !fir.ref<!fir.array<?xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<?xi32>>
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_parallel_operand_inside(
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: %[[SIZE:.*]] = arith.addi
+// CHECK-NEXT: fir.alloca !fir.array<?xi32>, %[[SIZE]]
+
+// Hoisting out of a sequential loop is still done, but only up to the beginning
+// of the compute construct.
+func.func @acc_parallel_sequential_loop() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.parallel {
+ fir.do_loop %iv = %c1 to %c10 step %c1 {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ }
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_parallel_sequential_loop()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+// CHECK: fir.do_loop
+// CHECK-NOT: fir.alloca
+
+// An acc.loop is both loop-like and an automatic allocation scope: the
+// allocation stays in it, and no stack save/restore is needed since the stack
+// space is given back at the end of every iteration.
+func.func @acc_loop_in_parallel() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.parallel {
+ acc.loop control(%iv : index) = (%c1 : index) to (%c10 : index) step (%c1 : index) {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ acc.yield
+ } inclusiveUpperbound(array<i1: true>) seq
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @acc_loop_in_parallel()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: acc.loop
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+// CHECK-NOT: llvm.intr.stacksave
+// CHECK-NOT: llvm.intr.stackrestore
+
+// Hoisting out of a sequential loop nested in an acc.loop removes the need for
+// a stack save/restore.
+func.func @sequential_loop_in_acc_loop() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.parallel {
+ acc.loop control(%iv : index) = (%c1 : index) to (%c10 : index) step (%c1 : index) {
+ fir.do_loop %jv = %c1 to %c10 step %c1 {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ }
+ acc.yield
+ } inclusiveUpperbound(array<i1: true>) seq
+ acc.yield
+ }
+ return
+}
+// CHECK-LABEL: func.func @sequential_loop_in_acc_loop()
+// CHECK-NOT: fir.alloca
+// CHECK: acc.parallel {
+// CHECK-NEXT: acc.loop
+// CHECK-NEXT: fir.alloca !fir.array<42xi32>
+// CHECK: fir.do_loop
+// CHECK-NOT: fir.alloca
+// CHECK-NOT: llvm.intr.stacksave
+// CHECK-NOT: llvm.intr.stackrestore
+
+// acc.data is not a compute construct and does not own the allocations of its
+// region: hoisting to the function entry block is allowed.
+func.func @acc_data_no_operand() {
+ %c0 = arith.constant 0 : index
+ %c0_i32 = arith.constant 0 : i32
+ acc.data {
+ %mem = fir.allocmem !fir.array<42xi32>
+ %ref = fir.convert %mem : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+ %elt = fir.coordinate_of %ref, %c0 : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+ fir.store %c0_i32 to %elt : !fir.ref<i32>
+ fir.freemem %mem : !fir.heap<!fir.array<42xi32>>
+ acc.terminator
+ } defaultAttr(none)
+ return
+}
+// CHECK-LABEL: func.func @acc_data_no_operand()
+// CHECK: fir.alloca !fir.array<42xi32>
+// CHECK: acc.data
>From 14dc152bd3c5728a1075b242ae139b854b45f7eb Mon Sep 17 00:00:00 2001
From: jeanPerier <jean.perier.polytechnique at gmail.com>
Date: Wed, 26 Aug 2026 10:05:30 +0200
Subject: [PATCH 2/2] avoid unused iterator declaration
---
flang/lib/Optimizer/Transforms/StackArrays.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/flang/lib/Optimizer/Transforms/StackArrays.cpp b/flang/lib/Optimizer/Transforms/StackArrays.cpp
index 93869e76e8fe5..8c2b39c40762c 100644
--- a/flang/lib/Optimizer/Transforms/StackArrays.cpp
+++ b/flang/lib/Optimizer/Transforms/StackArrays.cpp
@@ -501,8 +501,7 @@ static bool isInCFGLoop(mlir::Block *block) {
llvm::SmallVector<mlir::Block *> stack{block};
while (!stack.empty()) {
mlir::Block *current = stack.pop_back_val();
- auto [it, inserted] = visited.insert(current);
- if (!inserted) {
+ if (!visited.insert(current).second) {
if (current == block)
return true;
continue;
More information about the flang-commits
mailing list