[flang-commits] [flang] 0f5e9be - [flang][OpenMP] Fix crash when a sliced array is specified in a forall within a workshare construct (#170913)

via flang-commits flang-commits at lists.llvm.org
Thu Mar 5 04:59:25 PST 2026


Author: Carlos Seo
Date: 2026-03-05T09:59:20-03:00
New Revision: 0f5e9bee834adf5cef9e243103b023e20ae2b0f7

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

LOG: [flang][OpenMP] Fix crash when a sliced array is specified in a forall within a workshare construct (#170913)

This is a fix for two problems that caused a crash:

1. Thread-local variables sometimes are required to be parallelized.
Added a special case to handle this in
`LowerWorkshare.cpp:isSafeToParallelize`.
2. Race condition caused by a `nowait` added to the `omp.workshare` if
it is the last operation in a block. This allowed multiple threads to
execute the `omp.workshare` region concurrently. Since
_FortranAPushValue modifies a shared stack, this concurrent access
causes a crash. Disable the addition of `nowait` and rely on the
implicit barrier at the the of the `omp.workshare` region.

Fixes #143330

Added: 
    flang/test/Integration/OpenMP/workshare-forall-sliced-array.f90
    flang/test/Transforms/OpenMP/lower-workshare-thread-local.mlir

Modified: 
    flang/lib/Optimizer/OpenMP/LowerWorkshare.cpp

Removed: 
    


################################################################################
diff  --git a/flang/lib/Optimizer/OpenMP/LowerWorkshare.cpp b/flang/lib/Optimizer/OpenMP/LowerWorkshare.cpp
index f6af684f06edb..ee42801da09d9 100644
--- a/flang/lib/Optimizer/OpenMP/LowerWorkshare.cpp
+++ b/flang/lib/Optimizer/OpenMP/LowerWorkshare.cpp
@@ -16,6 +16,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <flang/Optimizer/Analysis/AliasAnalysis.h>
 #include <flang/Optimizer/Builder/FIRBuilder.h>
 #include <flang/Optimizer/Dialect/FIROps.h>
 #include <flang/Optimizer/Dialect/FIRType.h>
@@ -37,6 +38,7 @@
 #include <mlir/IR/PatternMatch.h>
 #include <mlir/IR/Value.h>
 #include <mlir/IR/Visitors.h>
+#include <mlir/Interfaces/LoopLikeInterface.h>
 #include <mlir/Interfaces/SideEffectInterfaces.h>
 #include <mlir/Support/LLVM.h>
 
@@ -135,9 +137,123 @@ static bool mustParallelizeOp(Operation *op) {
       .wasInterrupted();
 }
 
+// Determines if a memory reference is thread-local in an OpenMP context.
+//
+// This is a best-effort analysis. We cannot definitively determine if code
+// is inside a parallel region when it's in a function called from that
+// region. However, we can identify common patterns of thread-local memory:
+//
+// 1. Memory allocated via fir.alloca inside the enclosing omp.parallel region
+// 2. Memory that comes from OpenMP clause block arguments that create
+//    thread-local storage (private, firstprivate, lastprivate, reduction,
+//    linear clauses)
+//
+// Returns true if the memory reference appears to be thread-local and thus
+// safe to parallelize (each thread should access its own copy).
+static bool isOpenMPThreadLocalMemory(Operation *op, Value mem) {
+  // Use AliasAnalysis to trace through declares, converts, reboxes, etc.
+  // to find the underlying source of the memory reference.
+  fir::AliasAnalysis aliasAnalysis;
+  fir::AliasAnalysis::Source source = aliasAnalysis.getSource(mem);
+
+  // Check if the source is a Value (not a global symbol).
+  mlir::Value sourceValue =
+      llvm::dyn_cast_if_present<mlir::Value>(source.origin.u);
+  if (!sourceValue)
+    return false;
+
+  // Case 1: Memory allocated by fir.alloca inside the enclosing parallel
+  // region is thread-private (each thread gets its own stack allocation).
+  // Note: fir.allocmem is NOT thread-local even inside omp.parallel.
+  if (source.kind == fir::AliasAnalysis::SourceKind::Allocate) {
+    if (auto alloca = sourceValue.getDefiningOp<fir::AllocaOp>()) {
+      if (auto parallelOp = alloca->getParentOfType<omp::ParallelOp>()) {
+        if (op->getParentOfType<omp::ParallelOp>() == parallelOp)
+          return true;
+      }
+    }
+    // When the alias analysis encounters an hlfir.declare/fir.declare on a
+    // private clause block argument, it marks the SourceKind as Allocate and
+    // sets the source value to the declare op result (not the block arg).
+    // Trace through the declare to check if the underlying Memref is a
+    // private block argument.
+    Value declMemref;
+    if (auto hlfirDecl = sourceValue.getDefiningOp<hlfir::DeclareOp>())
+      declMemref = hlfirDecl.getMemref();
+    else if (auto firDecl = sourceValue.getDefiningOp<fir::DeclareOp>())
+      declMemref = firDecl.getMemref();
+    if (declMemref) {
+      if (auto blockArg = llvm::dyn_cast<BlockArgument>(declMemref)) {
+        Operation *parentOp = blockArg.getOwner()->getParentOp();
+        if (auto argIface =
+                llvm::dyn_cast<omp::BlockArgOpenMPOpInterface>(parentOp)) {
+          if (llvm::is_contained(argIface.getPrivateBlockArgs(), blockArg))
+            return true;
+        }
+      }
+    }
+  }
+
+  // Case 2: Memory from OpenMP clause block arguments that create thread-local
+  // storage. These clauses create private copies for each thread:
+  // - private: uninitialized thread-local copy
+  // - firstprivate: thread-local copy initialized from original
+  // - lastprivate: thread-local copy, value copied back after construct
+  // - reduction: thread-local copy for reduction operations
+  // - linear: thread-local copy with linear modification
+  //
+  // Check if the source value is a block argument of an OpenMP operation
+  // that implements BlockArgOpenMPOpInterface.
+  if (auto blockArg = llvm::dyn_cast<BlockArgument>(sourceValue)) {
+    Operation *parentOp = blockArg.getOwner()->getParentOp();
+    if (auto argIface =
+            llvm::dyn_cast<omp::BlockArgOpenMPOpInterface>(parentOp)) {
+      // Check if this block argument corresponds to a privatizing clause.
+      // Private, reduction, and in_reduction clauses create thread-local
+      // memory.
+      auto isInBlockArgs = [&](auto blockArgs) {
+        return llvm::is_contained(blockArgs, blockArg);
+      };
+
+      if (isInBlockArgs(argIface.getPrivateBlockArgs()))
+        return true;
+      if (isInBlockArgs(argIface.getReductionBlockArgs()))
+        return true;
+      if (isInBlockArgs(argIface.getInReductionBlockArgs()))
+        return true;
+      if (isInBlockArgs(argIface.getTaskReductionBlockArgs()))
+        return true;
+    }
+  }
+
+  return false;
+}
+
 static bool isSafeToParallelize(Operation *op) {
-  return isa<hlfir::DeclareOp>(op) || isa<fir::DeclareOp>(op) ||
-         isMemoryEffectFree(op);
+  if (isa<hlfir::DeclareOp>(op) || isa<fir::DeclareOp>(op) ||
+      isMemoryEffectFree(op))
+    return true;
+
+  // Thread-local variables allocated in the OpenMP parallel region or coming
+  // from privatizing clauses are private to each thread and thus safe (and
+  // sometimes required) to parallelize. If the compiler wraps such load/stores
+  // in an omp.single block, only one thread updates its local copy, while
+  // all other threads read uninitialized data (see issue #143330).
+  // Use MemoryEffectOpInterface to check all memory effects generically.
+  // This also handles hlfir.assign which is present when the pass runs before
+  // HLFIR lowering.
+  if (auto memEffects = dyn_cast<MemoryEffectOpInterface>(op)) {
+    SmallVector<MemoryEffects::EffectInstance> effects;
+    memEffects.getEffects(effects);
+    if (!effects.empty() &&
+        llvm::all_of(effects, [&](const MemoryEffects::EffectInstance &effect) {
+          Value val = effect.getValue();
+          return val && isOpenMPThreadLocalMemory(op, val);
+        }))
+      return true;
+  }
+
+  return false;
 }
 
 /// Simple shallow copies suffice for our purposes in this pass, so we implement
@@ -335,6 +451,13 @@ static void parallelizeRegion(Region &sourceRegion, Region &targetRegion,
 
     for (auto [i, opOrSingle] : llvm::enumerate(regions)) {
       bool isLast = i + 1 == regions.size();
+      // Make sure shared runtime calls are synchronized: disable `nowait`
+      // insertion, and rely on the implicit barrier at the end of the
+      // omp.workshare block. This applies to any loop-like operation
+      // (fir.do_loop, fir.iterate_while, fir.do_concurrent.loop, etc.)
+      // because iterations could overlap if nowait is used.
+      if (isa<LoopLikeOpInterface>(block.getParentOp()))
+        isLast = false;
       if (std::holds_alternative<SingleRegion>(opOrSingle)) {
         OpBuilder singleBuilder(sourceRegion.getContext());
         Block *singleBlock = new Block();

diff  --git a/flang/test/Integration/OpenMP/workshare-forall-sliced-array.f90 b/flang/test/Integration/OpenMP/workshare-forall-sliced-array.f90
new file mode 100644
index 0000000000000..88d1062b091bf
--- /dev/null
+++ b/flang/test/Integration/OpenMP/workshare-forall-sliced-array.f90
@@ -0,0 +1,57 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! Regression test for https://github.com/llvm/llvm-project/issues/143330
+! Verify that forall constructs with sliced arrays inside workshare are
+! correctly lowered without causing race conditions or crashes.
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix HLFIR
+!RUN: %flang_fc1 -emit-fir -fopenmp %s -o - | FileCheck %s --check-prefix FIR
+!RUN: %flang_fc1 -emit-llvm -fopenmp %s -o - | FileCheck %s --check-prefix LLVM
+
+subroutine workshare_forall_sliced(a1)
+  integer :: a1(2,1,3)
+  !$omp parallel
+  !$omp workshare
+  forall (n1=1:1)
+     forall (n2=1:3)
+        a1(:,n1,n2) = a1(:,n1,n2)+1
+     end forall
+  end forall
+  !$omp end workshare
+  !$omp end parallel
+end subroutine
+
+! HLFIR-LABEL: func.func @_QPworkshare_forall_sliced
+! HLFIR:       omp.parallel {
+! HLFIR:         omp.workshare {
+! HLFIR:           hlfir.forall
+! HLFIR:             hlfir.forall
+! HLFIR:               hlfir.region_assign
+! HLFIR:           omp.terminator
+! HLFIR:         }
+! HLFIR:         omp.terminator
+! HLFIR:       }
+
+! After workshare lowering, the forall should be in omp.single (since it
+! contains operations that are not safe to parallelize across threads).
+! The key fix is that this compiles without crashing and the runtime
+! executes correctly.
+
+! FIR-LABEL: func.func @_QPworkshare_forall_sliced
+! FIR:       omp.parallel {
+! FIR:         omp.single
+! FIR:           fir.do_loop
+! FIR:             fir.do_loop
+! FIR:         omp.barrier
+! FIR:         omp.terminator
+! FIR:       }
+
+! Verify LLVM IR is generated successfully (the original issue caused crashes)
+! LLVM-LABEL: define {{.*}}workshare_forall_sliced
+! LLVM:       call {{.*}}@__kmpc_fork_call

diff  --git a/flang/test/Transforms/OpenMP/lower-workshare-thread-local.mlir b/flang/test/Transforms/OpenMP/lower-workshare-thread-local.mlir
new file mode 100644
index 0000000000000..d046ef4cc46f7
--- /dev/null
+++ b/flang/test/Transforms/OpenMP/lower-workshare-thread-local.mlir
@@ -0,0 +1,294 @@
+// RUN: fir-opt --split-input-file --lower-workshare --allow-unregistered-dialect %s | FileCheck %s
+
+// Tests for thread-local memory handling in workshare lowering (#143330):
+// 1. Thread-local variables (from fir.alloca in omp.parallel or from OpenMP
+//    private/reduction clauses) should be parallelized, not wrapped in omp.single
+// 2. nowait should not be added to omp.single when inside loop-like operations
+//    that contain omp.workshare.loop_wrapper
+
+
+// Check that fir.alloca inside omp.parallel creates thread-local memory,
+// and stores to it should be parallelized (not wrapped in omp.single).
+
+// CHECK-LABEL: func.func @thread_local_alloca_store
+func.func @thread_local_alloca_store() {
+  omp.parallel {
+    // The alloca is inside omp.parallel, so it's thread-local
+    %alloca = fir.alloca i32
+    omp.workshare {
+      %c1 = arith.constant 1 : i32
+      // This store should NOT be in omp.single because %alloca is thread-local
+      fir.store %c1 to %alloca : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel {
+// CHECK-NEXT:    %[[ALLOCA:.*]] = fir.alloca i32
+// CHECK-NEXT:    %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-NEXT:    fir.store %[[C1]] to %[[ALLOCA]] : !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that memory accessed through fir.declare is also recognized as thread-local
+// when the underlying alloca is in the parallel region.
+
+// CHECK-LABEL: func.func @thread_local_with_declare
+func.func @thread_local_with_declare() {
+  omp.parallel {
+    %alloca = fir.alloca i32
+    %declare = fir.declare %alloca {uniq_name = "local_var"} : (!fir.ref<i32>) -> !fir.ref<i32>
+    omp.workshare {
+      %c42 = arith.constant 42 : i32
+      // Store through declare should still be recognized as thread-local
+      fir.store %c42 to %declare : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel {
+// CHECK-NEXT:    %[[ALLOCA:.*]] = fir.alloca i32
+// CHECK-NEXT:    %[[DECLARE:.*]] = fir.declare %[[ALLOCA]]
+// CHECK-NEXT:    %[[C42:.*]] = arith.constant 42 : i32
+// CHECK-NEXT:    fir.store %[[C42]] to %[[DECLARE]] : !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that private clause block arguments are recognized as thread-local.
+
+omp.private {type = private} @x_private : i32
+
+// CHECK-LABEL: func.func @private_clause_thread_local
+func.func @private_clause_thread_local(%arg0: !fir.ref<i32>) {
+  omp.parallel private(@x_private %arg0 -> %priv_arg : !fir.ref<i32>) {
+    omp.workshare {
+      %c10 = arith.constant 10 : i32
+      // Store to private variable should NOT be in omp.single
+      fir.store %c10 to %priv_arg : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel private(@x_private %{{.*}} -> %[[PRIV_ARG:.*]] : !fir.ref<i32>) {
+// CHECK-NEXT:    %[[C10:.*]] = arith.constant 10 : i32
+// CHECK-NEXT:    fir.store %[[C10]] to %[[PRIV_ARG]] : !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that hlfir.assign to a private clause variable through hlfir.declare
+// is recognized as thread-local. The alias analysis marks private items as
+// SourceKind::Allocate with the declare result as source value, so we need
+// to trace through the declare to find the private block argument.
+
+// CHECK-LABEL: func.func @hlfir_assign_private_clause
+func.func @hlfir_assign_private_clause(%arg0: !fir.ref<i32>) {
+  omp.parallel private(@x_private %arg0 -> %priv_arg : !fir.ref<i32>) {
+    %decl:2 = hlfir.declare %priv_arg {uniq_name = "x"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+    omp.workshare {
+      %c1 = arith.constant 1 : i32
+      // hlfir.assign to private variable should NOT be in omp.single
+      hlfir.assign %c1 to %decl#0 : i32, !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel private(@x_private %{{.*}} -> %[[PRIV_ARG:.*]] : !fir.ref<i32>) {
+// CHECK-NEXT:    %[[DECL:.*]]:2 = hlfir.declare %[[PRIV_ARG]]
+// CHECK-NEXT:    %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-NEXT:    hlfir.assign %[[C1]] to %[[DECL]]#0 : i32, !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that reduction clause block arguments are recognized as thread-local.
+
+omp.declare_reduction @add_reduction_i32 : i32 init {
+^bb0(%arg0: i32):
+  %c0 = arith.constant 0 : i32
+  omp.yield(%c0 : i32)
+} combiner {
+^bb0(%arg0: i32, %arg1: i32):
+  %0 = arith.addi %arg0, %arg1 : i32
+  omp.yield(%0 : i32)
+}
+
+// CHECK-LABEL: func.func @reduction_clause_thread_local
+func.func @reduction_clause_thread_local(%arg0: !fir.ref<i32>) {
+  omp.parallel reduction(@add_reduction_i32 %arg0 -> %red_arg : !fir.ref<i32>) {
+    omp.workshare {
+      %c5 = arith.constant 5 : i32
+      // Store to reduction variable should NOT be in omp.single
+      fir.store %c5 to %red_arg : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel reduction(@add_reduction_i32 %{{.*}} -> %[[RED_ARG:.*]] : !fir.ref<i32>) {
+// CHECK-NEXT:    %[[C5:.*]] = arith.constant 5 : i32
+// CHECK-NEXT:    fir.store %[[C5]] to %[[RED_ARG]] : !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that nowait is NOT added to omp.single when inside fir.do_loop
+// that contains omp.workshare.loop_wrapper. This prevents race conditions
+// when multiple threads execute 
diff erent loop iterations concurrently.
+// The workshare.loop_wrapper triggers recursive parallelization of the loop body.
+
+// CHECK-LABEL: func.func @no_nowait_in_loop_with_workshare_wrapper
+func.func @no_nowait_in_loop_with_workshare_wrapper(%arg0: !fir.ref<i32>) {
+  omp.parallel {
+    omp.workshare {
+      %c1 = arith.constant 1 : index
+      %c10 = arith.constant 10 : index
+      fir.do_loop %i = %c1 to %c10 step %c1 {
+        // This side-effecting op will be wrapped in omp.single without nowait
+        "test.side_effect"(%arg0) : (!fir.ref<i32>) -> ()
+        // The workshare.loop_wrapper triggers recursive processing of the loop
+        omp.workshare.loop_wrapper {
+          omp.loop_nest (%j) : index = (%c1) to (%c10) inclusive step (%c1) {
+            "test.inner"() : () -> ()
+            omp.yield
+          }
+        }
+      }
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// The omp.single inside the loop should NOT have nowait
+// CHECK:       omp.parallel {
+// CHECK:         fir.do_loop
+// CHECK:           omp.single {
+// CHECK:             "test.side_effect"
+// CHECK:             omp.terminator
+// CHECK-NEXT:      }
+// CHECK:           omp.wsloop {
+// CHECK:         }
+// CHECK:         omp.barrier
+// CHECK:       }
+
+
+// Check that thread-local store inside a loop with workshare.loop_wrapper
+// is correctly parallelized (not wrapped in omp.single).
+
+// CHECK-LABEL: func.func @thread_local_store_in_loop_with_wrapper
+func.func @thread_local_store_in_loop_with_wrapper() {
+  omp.parallel {
+    %alloca = fir.alloca i32
+    omp.workshare {
+      %c1 = arith.constant 1 : index
+      %c10 = arith.constant 10 : index
+      fir.do_loop %i = %c1 to %c10 step %c1 {
+        %c99 = arith.constant 99 : i32
+        // Store to thread-local alloca should NOT be in omp.single
+        fir.store %c99 to %alloca : !fir.ref<i32>
+        omp.workshare.loop_wrapper {
+          omp.loop_nest (%j) : index = (%c1) to (%c10) inclusive step (%c1) {
+            "test.inner"() : () -> ()
+            omp.yield
+          }
+        }
+      }
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel {
+// CHECK-NEXT:    %[[ALLOCA:.*]] = fir.alloca i32
+// CHECK:         fir.do_loop
+// The store should be outside omp.single
+// CHECK:           %[[C99:.*]] = arith.constant 99 : i32
+// CHECK-NEXT:      fir.store %[[C99]] to %[[ALLOCA]] : !fir.ref<i32>
+// CHECK:           omp.wsloop {
+// CHECK:         }
+// CHECK:         omp.barrier
+// CHECK:       }
+
+
+// Check that non-thread-local memory is still wrapped in omp.single.
+// This is the baseline case to ensure we haven't broken normal behavior.
+
+// CHECK-LABEL: func.func @non_thread_local_needs_single
+func.func @non_thread_local_needs_single(%arg0: !fir.ref<i32>) {
+  omp.parallel {
+    omp.workshare {
+      %c1 = arith.constant 1 : i32
+      // arg0 is shared memory, store must be in omp.single
+      fir.store %c1 to %arg0 : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// CHECK:       omp.parallel {
+// CHECK-NEXT:    omp.single nowait {
+// CHECK-NEXT:      %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-NEXT:      fir.store %[[C1]] to %{{.*}} : !fir.ref<i32>
+// CHECK-NEXT:      omp.terminator
+// CHECK-NEXT:    }
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }
+
+
+// Check that loads from thread-local alloca combined with stores are parallelized.
+
+// CHECK-LABEL: func.func @thread_local_load_and_store
+func.func @thread_local_load_and_store() {
+  omp.parallel {
+    %alloca = fir.alloca i32
+    omp.workshare {
+      %c1 = arith.constant 1 : i32
+      fir.store %c1 to %alloca : !fir.ref<i32>
+      %val = fir.load %alloca : !fir.ref<i32>
+      fir.store %val to %alloca : !fir.ref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// All operations on thread-local memory should be outside omp.single
+
+// CHECK:       omp.parallel {
+// CHECK-NEXT:    %[[ALLOCA:.*]] = fir.alloca i32
+// CHECK-NEXT:    %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-NEXT:    fir.store %[[C1]] to %[[ALLOCA]] : !fir.ref<i32>
+// CHECK-NEXT:    %[[VAL:.*]] = fir.load %[[ALLOCA]] : !fir.ref<i32>
+// CHECK-NEXT:    fir.store %[[VAL]] to %[[ALLOCA]] : !fir.ref<i32>
+// CHECK-NEXT:    omp.barrier
+// CHECK-NEXT:    omp.terminator
+// CHECK-NEXT:  }


        


More information about the flang-commits mailing list