[flang-commits] [flang] f583b91 - [flang][fir] do not move alloca outside parallel regions in StackArrays (#218679)

via flang-commits flang-commits at lists.llvm.org
Thu Aug 27 01:48:15 PDT 2026


Author: jeanPerier
Date: 2026-08-27T10:48:10+02:00
New Revision: f583b914dec4df685d47e5c9e88ca45420687dc0

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

LOG: [flang][fir] do not move alloca outside parallel regions in StackArrays (#218679)

The StackArrays and AllocationPlacement passes where moving allocations
outside of do concurrent and OpenACC region when turning them from
allocmem to alloca.

Fix this by replacing the existing OpenMP special handling by the
generic `getAllocaBlock` helper.

Also prevents the insertion of stacksave/stackrestore when looplike
operations have the AutomaticAllocationScope traits since they are
alreay in charge of the cleanup (otherwise stacksave/stackrestore were
inserted at the beginning and end of acc.loop when turning alloca into
allocmem which makes later analysis harder while they are not needed).

In the update of getAllocaBlock, I also noticed it was testing parent
parallel regions in "dialect order" instead of returning the closest
parent.

Assisted-by: AI

Added: 
    flang/test/Transforms/stack-arrays-alloca-scope.fir

Modified: 
    flang/include/flang/Optimizer/Builder/FIRBuilder.h
    flang/lib/Optimizer/Builder/FIRBuilder.cpp
    flang/lib/Optimizer/Transforms/StackArrays.cpp
    flang/test/Lower/OpenMP/DelayedPrivatization/target-private-allocatable.f90
    flang/test/Transforms/OpenACC/optional-firstprivate-recipe.fir
    flang/test/Transforms/OpenACC/optional-firstprivate.fir
    flang/test/Transforms/allocation-placement.fir

Removed: 
    


################################################################################
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..8c2b39c40762c 100644
--- a/flang/lib/Optimizer/Transforms/StackArrays.cpp
+++ b/flang/lib/Optimizer/Transforms/StackArrays.cpp
@@ -492,13 +492,47 @@ 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();
+    if (!visited.insert(current).second) {
+      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 +542,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 +564,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 +629,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


        


More information about the flang-commits mailing list