[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 &region);
+
 //===----------------------------------------------------------------------===//
 // 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 &region) {
+  // 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 &current->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