[flang-commits] [flang] [mlir] [mlir][OpenMP] Model memory effects of omp.section and omp.sections (PR #230167)

via flang-commits flang-commits at lists.llvm.org
Tue Oct 13 01:59:21 PDT 2026


https://github.com/JRP-arm updated https://github.com/llvm/llvm-project/pull/230167

>From 369d67c9ed18f9220c3c3068cdd20fe360c7465e Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-252-16-38.eu-west-1.compute.internal>
Date: Fri, 2 Oct 2026 10:16:31 +0000
Subject: [PATCH 1/6] [mlir][OpenMP] Model memory effects of omp.section and
 omp.sections

Model recursive effects of section bodies and account for implicit barriers,
private and lastprivate variables, and reduction recipes in sections.
Add shared helpers for symbol-referenced privatisation recipes, test memory
effects and canonicalisation, and update the Flang sections lowering tests.
---
 flang/test/Lower/OpenMP/sections.f90          |  5 +-
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  6 +-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  | 94 +++++++++++++++++++
 mlir/test/Dialect/OpenMP/canonicalize.mlir    | 80 ++++++++++++++++
 mlir/test/Dialect/OpenMP/memory-effects.mlir  | 78 +++++++++++++++
 5 files changed, 258 insertions(+), 5 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenMP/memory-effects.mlir

diff --git a/flang/test/Lower/OpenMP/sections.f90 b/flang/test/Lower/OpenMP/sections.f90
index f1e089caa8101..10668c291996f 100644
--- a/flang/test/Lower/OpenMP/sections.f90
+++ b/flang/test/Lower/OpenMP/sections.f90
@@ -56,6 +56,7 @@
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   omp.sections nowait {
+!CHECK:     fir.call @_QPfoo() fastmath<contract> : () -> ()
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   return
@@ -77,6 +78,7 @@ program sample
     !$omp end sections
 
     !$omp sections
+        call foo()
     !$omp end sections nowait
 end program sample
 
@@ -87,9 +89,6 @@ end program sample
 !CHECK:   %[[TEMP:.*]] = fir.load %[[ARG_DECL]]#0 : !fir.ref<f32>
 !CHECK:   hlfir.assign %[[TEMP]] to %[[PRIVATE_ALPHA_DECL]]#0 : f32, !fir.ref<f32>
 !CHECK:   omp.sections {
-!CHECK:     omp.section  {
-!CHECK:       omp.terminator
-!CHECK:     }
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   omp.sections {
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 6bc99cf765699..8c0780c23c13f 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -276,7 +276,8 @@ def TeamsOp : OpenMP_Op<"teams", traits = [
 //===----------------------------------------------------------------------===//
 
 def SectionOp : OpenMP_Op<"section", traits = [
-    BlockArgOpenMPOpInterface, HasParent<"SectionsOp">
+    BlockArgOpenMPOpInterface, HasParent<"SectionsOp">,
+    RecursiveMemoryEffects
   ], singleRegion = true> {
   let summary = "section directive";
   let description = [{
@@ -298,7 +299,8 @@ def SectionOp : OpenMP_Op<"section", traits = [
 }
 
 def SectionsOp : OpenMP_Op<"sections", traits = [
-    AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>
+    AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
+    DeclareOpInterfaceMethods<MemoryEffectsOpInterface>, RecursiveMemoryEffects
   ], clauses = [
     OpenMP_AllocateClause, OpenMP_NowaitClause, OpenMP_PrivateClause,
     OpenMP_ReductionClause
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index c6a424bd93214..0b70d8d75da19 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3258,6 +3258,100 @@ LogicalResult SectionsOp::verifyRegions() {
   return success();
 }
 
+/// Return whether all operations in the op's regions are memory-effect-free.
+static bool areRegionsMemoryEffectFree(Operation *op) {
+  for (Region &region : op->getRegions())
+    for (Operation &nestedOp : region.getOps())
+      if (!isMemoryEffectFree(&nestedOp))
+        return false;
+  return true;
+}
+
+/// Model private storage and the effects of referenced privatisation recipes.
+template <typename OpType>
+static void getPrivatisationEffects(
+    OpType op, SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  std::optional<ArrayAttr> privateSyms = op.getPrivateSyms();
+  if (!privateSyms)
+    return;
+
+  MutableOperandRange privateVars = op.getPrivateVarsMutable();
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  Block::BlockArgListType privateArgs = iface.getPrivateBlockArgs();
+  for (auto entry : llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
+    OpOperand &original = std::get<0>(entry);
+    BlockArgument privateCopy = std::get<1>(entry);
+
+    effects.emplace_back(MemoryEffects::Allocate::get(), privateCopy);
+    effects.emplace_back(MemoryEffects::Free::get(), privateCopy);
+    // private variables may read the original as a mold
+    effects.emplace_back(MemoryEffects::Read::get(), &original);
+
+    auto privateSym = cast<SymbolRefAttr>(std::get<2>(entry));
+    auto privatiser =
+        SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, privateSym);
+    // RecursiveMemoryEffects only covers regions nested in the directive, not
+    // symbol-referenced regions, user defined types have arbitrary recipe bodies.
+    // Default to a conservative model of read and write to arbitrary memory.
+    if (!privatiser || !areRegionsMemoryEffectFree(privatiser)) {
+      effects.emplace_back(MemoryEffects::Read::get());
+      effects.emplace_back(MemoryEffects::Write::get());
+    }
+  }
+}
+
+void SectionsOp::getEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause
+  // is also present
+  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
+    effects.emplace_back(MemoryEffects::Read::get());
+    effects.emplace_back(MemoryEffects::Write::get());
+  }
+
+  getPrivatisationEffects(*this, effects);
+  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
+  // We cannot determine if the variable is lastprivate, so assume all privates
+  // are.
+  for (auto entry :
+       llvm::zip_equal(getPrivateVarsMutable(), iface.getPrivateBlockArgs())) {
+    effects.emplace_back(MemoryEffects::Read::get(), std::get<1>(entry));
+    effects.emplace_back(MemoryEffects::Write::get(), &std::get<0>(entry));
+  }
+
+  Block::BlockArgListType reductionArgs = iface.getReductionBlockArgs();
+  MutableOperandRange reductionVars = getReductionVarsMutable();
+
+  for (auto entry : llvm::zip_equal(reductionVars, reductionArgs)) {
+    OpOperand &original = std::get<0>(entry);
+    BlockArgument privateAccumulator = std::get<1>(entry);
+
+    effects.emplace_back(MemoryEffects::Allocate::get(), privateAccumulator);
+    effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
+
+    effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
+
+    effects.emplace_back(MemoryEffects::Read::get(), &original);
+    effects.emplace_back(MemoryEffects::Write::get(), &original);
+  }
+
+  if (auto reductionSyms = getReductionSyms()) {
+    for (Attribute attr : *reductionSyms) {
+      auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
+          *this, cast<SymbolRefAttr>(attr));
+      if (recipe && areRegionsMemoryEffectFree(recipe))
+        continue;
+      // RecursiveMemoryEffects only covers operations nested in this directive.
+      // Reduction recipes are separate declarations referenced by symbol, so
+      // their effects must be accounted for explicitly.
+      effects.emplace_back(MemoryEffects::Read::get());
+      effects.emplace_back(MemoryEffects::Write::get());
+      break;
+    }
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/canonicalize.mlir b/mlir/test/Dialect/OpenMP/canonicalize.mlir
index cc42f2ed8618c..4b65b0d648f04 100644
--- a/mlir/test/Dialect/OpenMP/canonicalize.mlir
+++ b/mlir/test/Dialect/OpenMP/canonicalize.mlir
@@ -143,3 +143,83 @@ func.func @constant_hoisting_target(%x : !llvm.ptr) {
 // CHECK-NOT: arith.constant
 // CHECK: omp.target
 // CHECK: arith.constant
+
+// -----
+
+// Pure sections and their enclosing nowait construct can both be removed.
+// CHECK-LABEL: func.func @sections_nowait_no_side_effects
+// CHECK-NEXT: return
+func.func @sections_nowait_no_side_effects(%a: i32, %b: i32) {
+  omp.sections nowait {
+    omp.section {
+      %unused = arith.addi %a, %b : i32
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Sections also retain their implicit barrier when their body is empty.
+// CHECK-LABEL: func.func @sections_only_terminator
+// CHECK-NEXT: omp.sections {
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_only_terminator() {
+  omp.sections {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Effects must be considered recursively through both sections and section.
+// CHECK-LABEL: func.func @sections_nowait_store
+// CHECK-SAME: (%[[X:.*]]: memref<i32>, %[[VALUE:.*]]: i32)
+// CHECK-NEXT: omp.sections nowait {
+// CHECK-NEXT: omp.section {
+// CHECK-NEXT: memref.store %[[VALUE]], %[[X]][] : memref<i32>
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_nowait_store(%x: memref<i32>, %value: i32) {
+  omp.sections nowait {
+    omp.section {
+      memref.store %value, %x[] : memref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Unknown effects in a nested operation also prevent removal.
+// CHECK-LABEL: func.func @sections_nowait_call
+// CHECK-NEXT: omp.sections nowait {
+// CHECK-NEXT: omp.section {
+// CHECK-NEXT: func.call @opaque() : () -> ()
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_nowait_call() {
+  omp.sections nowait {
+    omp.section {
+      func.call @opaque() : () -> ()
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+func.func private @opaque()
diff --git a/mlir/test/Dialect/OpenMP/memory-effects.mlir b/mlir/test/Dialect/OpenMP/memory-effects.mlir
new file mode 100644
index 0000000000000..06dd2d7f49344
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -0,0 +1,78 @@
+// RUN: mlir-opt %s --test-side-effects --verify-diagnostics
+
+func.func @sections_effects() {
+  // An implicit barrier reads and writes unspecified memory.
+  // expected-remark at +2 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on resource '<Default>'}}
+  omp.sections {
+    omp.section {
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+func.func @sections_nowait_no_effects() {
+  // expected-remark at +1 {{operation has no memory effects}}
+  omp.sections nowait {
+    omp.section {
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+omp.private {type = private} @sections_p : memref<i32>
+
+func.func @sections_private_effects(%x: memref<i32>) {
+  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+  omp.sections nowait private(@sections_p %x -> %private_x : memref<i32>) {
+    omp.section {
+    ^bb0(%section_x: memref<i32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+omp.declare_reduction @add_f32 : f32
+init {
+^bb0(%unused: f32):
+  // expected-remark at +1 {{operation has no memory effects}}
+  %zero = arith.constant 0.0 : f32
+  omp.yield(%zero : f32)
+}
+combiner {
+^bb0(%lhs: f32, %rhs: f32):
+  // expected-remark at +1 {{operation has no memory effects}}
+  %sum = arith.addf %lhs, %rhs : f32
+  omp.yield(%sum : f32)
+}
+
+func.func @sections_reduction_effects(%x: memref<f32>) {
+  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause is also present,
+  // requiring the below two remarks
+  // expected-remark at +7 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +6 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+  omp.sections nowait reduction(
+      @add_f32 %x -> %private_x : memref<f32>) {
+    omp.section {
+    ^bb0(%section_x: memref<f32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}

>From 0e9bb2e56dbe74f9d8e9afe89095aaa6bb37e8a3 Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-252-16-38.eu-west-1.compute.internal>
Date: Fri, 2 Oct 2026 10:16:31 +0000
Subject: [PATCH 2/6] [mlir][OpenMP] Model memory effects of omp.section and
 omp.sections

Model recursive effects of section bodies and account for implicit barriers,
private and lastprivate variables, and reduction recipes in sections.
Add shared helpers for symbol-referenced privatisation recipes, test memory
effects and canonicalisation, and update the Flang sections lowering tests.
---
 flang/test/Lower/OpenMP/sections.f90          |   5 +-
 .../mlir/Dialect/OpenMP/OpenMPDialect.h       |   9 ++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |   6 +-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  | 124 ++++++++++++++++++
 mlir/test/Dialect/OpenMP/canonicalize.mlir    |  80 +++++++++++
 mlir/test/Dialect/OpenMP/memory-effects.mlir  |  78 +++++++++++
 6 files changed, 297 insertions(+), 5 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenMP/memory-effects.mlir

diff --git a/flang/test/Lower/OpenMP/sections.f90 b/flang/test/Lower/OpenMP/sections.f90
index f1e089caa8101..10668c291996f 100644
--- a/flang/test/Lower/OpenMP/sections.f90
+++ b/flang/test/Lower/OpenMP/sections.f90
@@ -56,6 +56,7 @@
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   omp.sections nowait {
+!CHECK:     fir.call @_QPfoo() fastmath<contract> : () -> ()
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   return
@@ -77,6 +78,7 @@ program sample
     !$omp end sections
 
     !$omp sections
+        call foo()
     !$omp end sections nowait
 end program sample
 
@@ -87,9 +89,6 @@ end program sample
 !CHECK:   %[[TEMP:.*]] = fir.load %[[ARG_DECL]]#0 : !fir.ref<f32>
 !CHECK:   hlfir.assign %[[TEMP]] to %[[PRIVATE_ALPHA_DECL]]#0 : f32, !fir.ref<f32>
 !CHECK:   omp.sections {
-!CHECK:     omp.section  {
-!CHECK:       omp.terminator
-!CHECK:     }
 !CHECK:     omp.terminator
 !CHECK:   }
 !CHECK:   omp.sections {
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
index a6854752f75b6..0af6515309ad3 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
@@ -61,6 +61,15 @@ std::tuple<NewCliOp, OpOperand *, OpOperand *> decodeCli(mlir::Value cli);
 /// OpenMP dialect enum. The LLVM 'default' and 'unknown' kinds have no dialect
 /// counterpart and are not valid inputs.
 ClauseProcBindKind convertProcBindKind(llvm::omp::ProcBindKind kind);
+
+/// Models shared OpenMP runtime bookkeeping used to manage threadprivate
+/// variables including caches.
+struct ThreadprivateRuntimeResource
+    : SideEffects::Resource::Base<ThreadprivateRuntimeResource> {
+  StringRef getName() const final { return "OpenMPThreadprivateRuntime"; }
+
+  bool isAddressable() const override { return false; }
+};
 } // namespace mlir::omp
 
 #endif // MLIR_DIALECT_OPENMP_OPENMPDIALECT_H_
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 6bc99cf765699..8c0780c23c13f 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -276,7 +276,8 @@ def TeamsOp : OpenMP_Op<"teams", traits = [
 //===----------------------------------------------------------------------===//
 
 def SectionOp : OpenMP_Op<"section", traits = [
-    BlockArgOpenMPOpInterface, HasParent<"SectionsOp">
+    BlockArgOpenMPOpInterface, HasParent<"SectionsOp">,
+    RecursiveMemoryEffects
   ], singleRegion = true> {
   let summary = "section directive";
   let description = [{
@@ -298,7 +299,8 @@ def SectionOp : OpenMP_Op<"section", traits = [
 }
 
 def SectionsOp : OpenMP_Op<"sections", traits = [
-    AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>
+    AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
+    DeclareOpInterfaceMethods<MemoryEffectsOpInterface>, RecursiveMemoryEffects
   ], clauses = [
     OpenMP_AllocateClause, OpenMP_NowaitClause, OpenMP_PrivateClause,
     OpenMP_ReductionClause
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index c6a424bd93214..89c830dc07b32 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3258,6 +3258,130 @@ LogicalResult SectionsOp::verifyRegions() {
   return success();
 }
 
+/// Return whether all operations in the op's regions are memory-effect-free.
+static bool areRegionsMemoryEffectFree(Operation *op) {
+  for (Region &region : op->getRegions())
+    for (Operation &nestedOp : region.getOps())
+      if (!isMemoryEffectFree(&nestedOp))
+        return false;
+  return true;
+}
+
+/// Model private storage and the effects of referenced privatisation recipes.
+template <typename OpType>
+static void getPrivatisationEffects(
+    OpType op, SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  std::optional<ArrayAttr> privateSyms = op.getPrivateSyms();
+  if (!privateSyms)
+    return;
+
+  MutableOperandRange privateVars = op.getPrivateVarsMutable();
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  Block::BlockArgListType privateArgs = iface.getPrivateBlockArgs();
+  for (auto entry : llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
+    OpOperand &original = std::get<0>(entry);
+    BlockArgument privateCopy = std::get<1>(entry);
+
+    effects.emplace_back(MemoryEffects::Allocate::get(), privateCopy);
+    effects.emplace_back(MemoryEffects::Free::get(), privateCopy);
+    // private variables may read the original as a mold
+    effects.emplace_back(MemoryEffects::Read::get(), &original);
+
+    auto privateSym = cast<SymbolRefAttr>(std::get<2>(entry));
+    auto privatiser =
+        SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, privateSym);
+    // RecursiveMemoryEffects only covers regions nested in the directive, not
+    // symbol-referenced regions, user defined types have arbitrary recipe
+    // bodies. Default to a conservative model of read and write to arbitrary
+    // memory.
+    if (!privatiser || !areRegionsMemoryEffectFree(privatiser)) {
+      effects.emplace_back(MemoryEffects::Read::get());
+      effects.emplace_back(MemoryEffects::Write::get());
+    }
+  }
+}
+
+template <typename OpType>
+static void
+getLastprivateEffects(OpType op,
+                      SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  // We cannot determine if the variable is lastprivate, so assume all private
+  // variables are.
+  for (auto entry : llvm::zip_equal(op.getPrivateVarsMutable(),
+                                    iface.getPrivateBlockArgs())) {
+    effects.emplace_back(MemoryEffects::Read::get(), std::get<1>(entry));
+    effects.emplace_back(MemoryEffects::Write::get(), &std::get<0>(entry));
+  }
+}
+
+/// Model accumulator initialisation and destruction and referenced recipes for
+/// a reduction clause.
+static void
+getReductionEffects(Operation *op, MutableOperandRange reductionVars,
+                    ArrayRef<BlockArgument> reductionArgs,
+                    std::optional<ArrayAttr> reductionSyms,
+                    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  for (auto entry : llvm::zip_equal(reductionVars, reductionArgs)) {
+    OpOperand &original = std::get<0>(entry);
+    BlockArgument privateAccumulator = std::get<1>(entry);
+
+    effects.emplace_back(MemoryEffects::Allocate::get(), privateAccumulator);
+    effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
+
+    effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
+    effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
+
+    effects.emplace_back(MemoryEffects::Read::get(), &original);
+    effects.emplace_back(MemoryEffects::Write::get(), &original);
+  }
+
+  if (reductionSyms) {
+    for (Attribute attr : *reductionSyms) {
+      auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
+          op, cast<SymbolRefAttr>(attr));
+      if (recipe && areRegionsMemoryEffectFree(recipe))
+        continue;
+      // RecursiveMemoryEffects only covers operations nested in this directive.
+      // Reduction recipes are separate declarations referenced by symbol, so
+      // their effects must be accounted for explicitly.
+      effects.emplace_back(MemoryEffects::Read::get());
+      effects.emplace_back(MemoryEffects::Write::get());
+      break;
+    }
+  }
+}
+
+static void getBarrierEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  effects.emplace_back(MemoryEffects::Read::get(),
+                       ThreadprivateRuntimeResource::get());
+  effects.emplace_back(MemoryEffects::Write::get(),
+                       ThreadprivateRuntimeResource::get());
+}
+
+void SectionsOp::getEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  // The presence of the nowait clause eliminates the implicit barrier at the
+  // end of a sections construct
+  // TODO: Presence of a nowait clause does not currently eliminate an implicit
+  // barrier if the reduction clause is also present (when it should), so we
+  // keep the barrier.
+  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
+    getBarrierEffects(effects);
+  }
+
+  getPrivatisationEffects(*this, effects);
+  getLastprivateEffects(*this, effects);
+
+  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
+  getReductionEffects(*this, getReductionVarsMutable(),
+                      iface.getReductionBlockArgs(), getReductionSyms(),
+                      effects);
+}
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/canonicalize.mlir b/mlir/test/Dialect/OpenMP/canonicalize.mlir
index cc42f2ed8618c..4b65b0d648f04 100644
--- a/mlir/test/Dialect/OpenMP/canonicalize.mlir
+++ b/mlir/test/Dialect/OpenMP/canonicalize.mlir
@@ -143,3 +143,83 @@ func.func @constant_hoisting_target(%x : !llvm.ptr) {
 // CHECK-NOT: arith.constant
 // CHECK: omp.target
 // CHECK: arith.constant
+
+// -----
+
+// Pure sections and their enclosing nowait construct can both be removed.
+// CHECK-LABEL: func.func @sections_nowait_no_side_effects
+// CHECK-NEXT: return
+func.func @sections_nowait_no_side_effects(%a: i32, %b: i32) {
+  omp.sections nowait {
+    omp.section {
+      %unused = arith.addi %a, %b : i32
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Sections also retain their implicit barrier when their body is empty.
+// CHECK-LABEL: func.func @sections_only_terminator
+// CHECK-NEXT: omp.sections {
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_only_terminator() {
+  omp.sections {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Effects must be considered recursively through both sections and section.
+// CHECK-LABEL: func.func @sections_nowait_store
+// CHECK-SAME: (%[[X:.*]]: memref<i32>, %[[VALUE:.*]]: i32)
+// CHECK-NEXT: omp.sections nowait {
+// CHECK-NEXT: omp.section {
+// CHECK-NEXT: memref.store %[[VALUE]], %[[X]][] : memref<i32>
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_nowait_store(%x: memref<i32>, %value: i32) {
+  omp.sections nowait {
+    omp.section {
+      memref.store %value, %x[] : memref<i32>
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Unknown effects in a nested operation also prevent removal.
+// CHECK-LABEL: func.func @sections_nowait_call
+// CHECK-NEXT: omp.sections nowait {
+// CHECK-NEXT: omp.section {
+// CHECK-NEXT: func.call @opaque() : () -> ()
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @sections_nowait_call() {
+  omp.sections nowait {
+    omp.section {
+      func.call @opaque() : () -> ()
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+func.func private @opaque()
diff --git a/mlir/test/Dialect/OpenMP/memory-effects.mlir b/mlir/test/Dialect/OpenMP/memory-effects.mlir
new file mode 100644
index 0000000000000..06dd2d7f49344
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -0,0 +1,78 @@
+// RUN: mlir-opt %s --test-side-effects --verify-diagnostics
+
+func.func @sections_effects() {
+  // An implicit barrier reads and writes unspecified memory.
+  // expected-remark at +2 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on resource '<Default>'}}
+  omp.sections {
+    omp.section {
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+func.func @sections_nowait_no_effects() {
+  // expected-remark at +1 {{operation has no memory effects}}
+  omp.sections nowait {
+    omp.section {
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+omp.private {type = private} @sections_p : memref<i32>
+
+func.func @sections_private_effects(%x: memref<i32>) {
+  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+  omp.sections nowait private(@sections_p %x -> %private_x : memref<i32>) {
+    omp.section {
+    ^bb0(%section_x: memref<i32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+omp.declare_reduction @add_f32 : f32
+init {
+^bb0(%unused: f32):
+  // expected-remark at +1 {{operation has no memory effects}}
+  %zero = arith.constant 0.0 : f32
+  omp.yield(%zero : f32)
+}
+combiner {
+^bb0(%lhs: f32, %rhs: f32):
+  // expected-remark at +1 {{operation has no memory effects}}
+  %sum = arith.addf %lhs, %rhs : f32
+  omp.yield(%sum : f32)
+}
+
+func.func @sections_reduction_effects(%x: memref<f32>) {
+  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause is also present,
+  // requiring the below two remarks
+  // expected-remark at +7 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +6 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+  omp.sections nowait reduction(
+      @add_f32 %x -> %private_x : memref<f32>) {
+    omp.section {
+    ^bb0(%section_x: memref<f32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}

>From 3666bef6b66ec3c8e54bec62a4cd7f6b58a0c56d Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-252-16-38.eu-west-1.compute.internal>
Date: Fri, 9 Oct 2026 09:12:52 +0000
Subject: [PATCH 3/6] Revert "Merge branch 'pr/openmp-memory-effects-sections'
 of https://github.com/JRP-arm/llvm-project into
 pr/openmp-memory-effects-sections"

This reverts commit 2340f288446ebff445652ccc6ddcc7e0210cb24b, reversing
changes made to 279f7e27cd31fa3ed939488ad46e14ca202bd98b.
---
 .../mlir/Dialect/OpenMP/OpenMPDialect.h       |  9 ---
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  | 76 ++++++-------------
 2 files changed, 23 insertions(+), 62 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
index 0af6515309ad3..a6854752f75b6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
@@ -61,15 +61,6 @@ std::tuple<NewCliOp, OpOperand *, OpOperand *> decodeCli(mlir::Value cli);
 /// OpenMP dialect enum. The LLVM 'default' and 'unknown' kinds have no dialect
 /// counterpart and are not valid inputs.
 ClauseProcBindKind convertProcBindKind(llvm::omp::ProcBindKind kind);
-
-/// Models shared OpenMP runtime bookkeeping used to manage threadprivate
-/// variables including caches.
-struct ThreadprivateRuntimeResource
-    : SideEffects::Resource::Base<ThreadprivateRuntimeResource> {
-  StringRef getName() const final { return "OpenMPThreadprivateRuntime"; }
-
-  bool isAddressable() const override { return false; }
-};
 } // namespace mlir::omp
 
 #endif // MLIR_DIALECT_OPENMP_OPENMPDIALECT_H_
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 89c830dc07b32..0b70d8d75da19 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3291,9 +3291,8 @@ static void getPrivatisationEffects(
     auto privatiser =
         SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, privateSym);
     // RecursiveMemoryEffects only covers regions nested in the directive, not
-    // symbol-referenced regions, user defined types have arbitrary recipe
-    // bodies. Default to a conservative model of read and write to arbitrary
-    // memory.
+    // symbol-referenced regions, user defined types have arbitrary recipe bodies.
+    // Default to a conservative model of read and write to arbitrary memory.
     if (!privatiser || !areRegionsMemoryEffectFree(privatiser)) {
       effects.emplace_back(MemoryEffects::Read::get());
       effects.emplace_back(MemoryEffects::Write::get());
@@ -3301,27 +3300,29 @@ static void getPrivatisationEffects(
   }
 }
 
-template <typename OpType>
-static void
-getLastprivateEffects(OpType op,
-                      SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
-  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
-  // We cannot determine if the variable is lastprivate, so assume all private
-  // variables are.
-  for (auto entry : llvm::zip_equal(op.getPrivateVarsMutable(),
-                                    iface.getPrivateBlockArgs())) {
+void SectionsOp::getEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause
+  // is also present
+  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
+    effects.emplace_back(MemoryEffects::Read::get());
+    effects.emplace_back(MemoryEffects::Write::get());
+  }
+
+  getPrivatisationEffects(*this, effects);
+  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
+  // We cannot determine if the variable is lastprivate, so assume all privates
+  // are.
+  for (auto entry :
+       llvm::zip_equal(getPrivateVarsMutable(), iface.getPrivateBlockArgs())) {
     effects.emplace_back(MemoryEffects::Read::get(), std::get<1>(entry));
     effects.emplace_back(MemoryEffects::Write::get(), &std::get<0>(entry));
   }
-}
 
-/// Model accumulator initialisation and destruction and referenced recipes for
-/// a reduction clause.
-static void
-getReductionEffects(Operation *op, MutableOperandRange reductionVars,
-                    ArrayRef<BlockArgument> reductionArgs,
-                    std::optional<ArrayAttr> reductionSyms,
-                    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  Block::BlockArgListType reductionArgs = iface.getReductionBlockArgs();
+  MutableOperandRange reductionVars = getReductionVarsMutable();
+
   for (auto entry : llvm::zip_equal(reductionVars, reductionArgs)) {
     OpOperand &original = std::get<0>(entry);
     BlockArgument privateAccumulator = std::get<1>(entry);
@@ -3330,16 +3331,15 @@ getReductionEffects(Operation *op, MutableOperandRange reductionVars,
     effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
 
     effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
-    effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
 
     effects.emplace_back(MemoryEffects::Read::get(), &original);
     effects.emplace_back(MemoryEffects::Write::get(), &original);
   }
 
-  if (reductionSyms) {
+  if (auto reductionSyms = getReductionSyms()) {
     for (Attribute attr : *reductionSyms) {
       auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
-          op, cast<SymbolRefAttr>(attr));
+          *this, cast<SymbolRefAttr>(attr));
       if (recipe && areRegionsMemoryEffectFree(recipe))
         continue;
       // RecursiveMemoryEffects only covers operations nested in this directive.
@@ -3352,36 +3352,6 @@ getReductionEffects(Operation *op, MutableOperandRange reductionVars,
   }
 }
 
-static void getBarrierEffects(
-    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
-        &effects) {
-  effects.emplace_back(MemoryEffects::Read::get(),
-                       ThreadprivateRuntimeResource::get());
-  effects.emplace_back(MemoryEffects::Write::get(),
-                       ThreadprivateRuntimeResource::get());
-}
-
-void SectionsOp::getEffects(
-    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
-        &effects) {
-  // The presence of the nowait clause eliminates the implicit barrier at the
-  // end of a sections construct
-  // TODO: Presence of a nowait clause does not currently eliminate an implicit
-  // barrier if the reduction clause is also present (when it should), so we
-  // keep the barrier.
-  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
-    getBarrierEffects(effects);
-  }
-
-  getPrivatisationEffects(*this, effects);
-  getLastprivateEffects(*this, effects);
-
-  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
-  getReductionEffects(*this, getReductionVarsMutable(),
-                      iface.getReductionBlockArgs(), getReductionSyms(),
-                      effects);
-}
-
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//

>From 94636ce56edced6a6d6e54f64d2a97d79b59d849 Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-252-16-38.eu-west-1.compute.internal>
Date: Fri, 9 Oct 2026 09:12:52 +0000
Subject: [PATCH 4/6] Revert "Merge branch 'pr/openmp-memory-effects-sections'
 of https://github.com/JRP-arm/llvm-project into
 pr/openmp-memory-effects-sections"

This reverts commit 2340f288446ebff445652ccc6ddcc7e0210cb24b, reversing
changes made to 279f7e27cd31fa3ed939488ad46e14ca202bd98b.
---
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 75 ++++++++++++++------
 mlir/test/Dialect/OpenMP/cse.mlir            | 20 ++++++
 mlir/test/Dialect/OpenMP/memory-effects.mlir | 11 +--
 3 files changed, 78 insertions(+), 28 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenMP/cse.mlir

diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 0b70d8d75da19..2d5319881161f 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3285,14 +3285,16 @@ static void getPrivatisationEffects(
     effects.emplace_back(MemoryEffects::Allocate::get(), privateCopy);
     effects.emplace_back(MemoryEffects::Free::get(), privateCopy);
     // private variables may read the original as a mold
+    // firstprivate variables do it regardless
     effects.emplace_back(MemoryEffects::Read::get(), &original);
 
     auto privateSym = cast<SymbolRefAttr>(std::get<2>(entry));
     auto privatiser =
         SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, privateSym);
     // RecursiveMemoryEffects only covers regions nested in the directive, not
-    // symbol-referenced regions, user defined types have arbitrary recipe bodies.
-    // Default to a conservative model of read and write to arbitrary memory.
+    // symbol-referenced regions, user defined types have arbitrary recipe
+    // bodies. Default to a conservative model of read and write to arbitrary
+    // memory.
     if (!privatiser || !areRegionsMemoryEffectFree(privatiser)) {
       effects.emplace_back(MemoryEffects::Read::get());
       effects.emplace_back(MemoryEffects::Write::get());
@@ -3300,29 +3302,27 @@ static void getPrivatisationEffects(
   }
 }
 
-void SectionsOp::getEffects(
-    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
-        &effects) {
-  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause
-  // is also present
-  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
-    effects.emplace_back(MemoryEffects::Read::get());
-    effects.emplace_back(MemoryEffects::Write::get());
-  }
-
-  getPrivatisationEffects(*this, effects);
-  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
-  // We cannot determine if the variable is lastprivate, so assume all privates
-  // are.
-  for (auto entry :
-       llvm::zip_equal(getPrivateVarsMutable(), iface.getPrivateBlockArgs())) {
+template <typename OpType>
+static void
+getLastprivateEffects(OpType op,
+                      SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  // We cannot determine if the variable is lastprivate, so assume all private
+  // variables are.
+  for (auto entry : llvm::zip_equal(op.getPrivateVarsMutable(),
+                                    iface.getPrivateBlockArgs())) {
     effects.emplace_back(MemoryEffects::Read::get(), std::get<1>(entry));
     effects.emplace_back(MemoryEffects::Write::get(), &std::get<0>(entry));
   }
+}
 
-  Block::BlockArgListType reductionArgs = iface.getReductionBlockArgs();
-  MutableOperandRange reductionVars = getReductionVarsMutable();
-
+/// Model accumulator initialisation and destruction and referenced recipes for
+/// a reduction clause.
+static void
+getReductionEffects(Operation *op, MutableOperandRange reductionVars,
+                    ArrayRef<BlockArgument> reductionArgs,
+                    std::optional<ArrayAttr> reductionSyms,
+                    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
   for (auto entry : llvm::zip_equal(reductionVars, reductionArgs)) {
     OpOperand &original = std::get<0>(entry);
     BlockArgument privateAccumulator = std::get<1>(entry);
@@ -3331,15 +3331,16 @@ void SectionsOp::getEffects(
     effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
 
     effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
+    effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
 
     effects.emplace_back(MemoryEffects::Read::get(), &original);
     effects.emplace_back(MemoryEffects::Write::get(), &original);
   }
 
-  if (auto reductionSyms = getReductionSyms()) {
+  if (reductionSyms) {
     for (Attribute attr : *reductionSyms) {
       auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
-          *this, cast<SymbolRefAttr>(attr));
+          op, cast<SymbolRefAttr>(attr));
       if (recipe && areRegionsMemoryEffectFree(recipe))
         continue;
       // RecursiveMemoryEffects only covers operations nested in this directive.
@@ -3352,6 +3353,34 @@ void SectionsOp::getEffects(
   }
 }
 
+static void getBarrierEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  effects.emplace_back(MemoryEffects::Read::get());
+  effects.emplace_back(MemoryEffects::Write::get());
+}
+
+void SectionsOp::getEffects(
+    llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  // The presence of the nowait clause eliminates the implicit barrier at the
+  // end of a sections construct
+  // TODO: Presence of a nowait clause does not currently eliminate an implicit
+  // barrier if the reduction clause is also present (when it should), so we
+  // keep the barrier.
+  if (!getNowait() || (!getReductionVarsMutable().empty() && getNowait())) {
+    getBarrierEffects(effects);
+  }
+
+  getPrivatisationEffects(*this, effects);
+  getLastprivateEffects(*this, effects);
+
+  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
+  getReductionEffects(*this, getReductionVarsMutable(),
+                      iface.getReductionBlockArgs(), getReductionSyms(),
+                      effects);
+}
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/cse.mlir b/mlir/test/Dialect/OpenMP/cse.mlir
new file mode 100644
index 0000000000000..919547efc2a09
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/cse.mlir
@@ -0,0 +1,20 @@
+// RUN: mlir-opt %s -cse -split-input-file | FileCheck %s
+
+// An implicit barrier synchronises shared memory, so loads on opposite sides
+// must remain.
+// CHECK-LABEL: func.func @sections_barrier_between_loads(
+// CHECK: %[[BEFORE:.*]] = memref.load %[[SHARED:.*]][] : memref<i32>
+// CHECK: omp.sections {
+// CHECK: %[[AFTER:.*]] = memref.load %[[SHARED]][] : memref<i32>
+// CHECK: return %[[BEFORE]], %[[AFTER]] : i32, i32
+func.func @sections_barrier_between_loads(%shared: memref<i32>) -> (i32, i32) {
+  %before = memref.load %shared[] : memref<i32>
+  omp.sections {
+    omp.section {
+      omp.terminator
+    }
+    omp.terminator
+  }
+  %after = memref.load %shared[] : memref<i32>
+  return %before, %after : i32, i32
+}
diff --git a/mlir/test/Dialect/OpenMP/memory-effects.mlir b/mlir/test/Dialect/OpenMP/memory-effects.mlir
index 06dd2d7f49344..d0fcb05f3f66c 100644
--- a/mlir/test/Dialect/OpenMP/memory-effects.mlir
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -59,11 +59,12 @@ combiner {
 func.func @sections_reduction_effects(%x: memref<f32>) {
   // TODO: Nowait does not eliminate an implicit barrier if the reduction clause is also present,
   // requiring the below two remarks
-  // expected-remark at +7 {{found an instance of 'read' on resource '<Default>'}}
-  // expected-remark at +6 {{found an instance of 'write' on resource '<Default>'}}
-  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +3 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +8 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +7 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +6 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
   // expected-remark at +2 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
   // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
   omp.sections nowait reduction(

>From c91f6837b0a53b7c53eb5f7f8158fe353d61e112 Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-249-51-4.eu-west-1.compute.internal>
Date: Mon, 12 Oct 2026 07:55:52 +0000
Subject: [PATCH 5/6] Improve readability of loop variables

---
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 31 ++++++++------------
 1 file changed, 12 insertions(+), 19 deletions(-)

diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 2d5319881161f..d5d91faf5e14a 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3278,19 +3278,15 @@ static void getPrivatisationEffects(
   MutableOperandRange privateVars = op.getPrivateVarsMutable();
   auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
   Block::BlockArgListType privateArgs = iface.getPrivateBlockArgs();
-  for (auto entry : llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
-    OpOperand &original = std::get<0>(entry);
-    BlockArgument privateCopy = std::get<1>(entry);
-
-    effects.emplace_back(MemoryEffects::Allocate::get(), privateCopy);
-    effects.emplace_back(MemoryEffects::Free::get(), privateCopy);
-    // private variables may read the original as a mold
+  for (auto [sourceVar, privateVar, privateSym] : llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
+    effects.emplace_back(MemoryEffects::Allocate::get(), privateVar);
+    effects.emplace_back(MemoryEffects::Free::get(), privateVar);
+    // private variables may read the sourceVar as a mold
     // firstprivate variables do it regardless
-    effects.emplace_back(MemoryEffects::Read::get(), &original);
+    effects.emplace_back(MemoryEffects::Read::get(), &sourceVar);
 
-    auto privateSym = cast<SymbolRefAttr>(std::get<2>(entry));
     auto privatiser =
-        SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, privateSym);
+        SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, cast<SymbolRefAttr>(privateSym));
     // RecursiveMemoryEffects only covers regions nested in the directive, not
     // symbol-referenced regions, user defined types have arbitrary recipe
     // bodies. Default to a conservative model of read and write to arbitrary
@@ -3309,10 +3305,10 @@ getLastprivateEffects(OpType op,
   auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
   // We cannot determine if the variable is lastprivate, so assume all private
   // variables are.
-  for (auto entry : llvm::zip_equal(op.getPrivateVarsMutable(),
+  for (auto [sourceVar, privateVar] : llvm::zip_equal(op.getPrivateVarsMutable(),
                                     iface.getPrivateBlockArgs())) {
-    effects.emplace_back(MemoryEffects::Read::get(), std::get<1>(entry));
-    effects.emplace_back(MemoryEffects::Write::get(), &std::get<0>(entry));
+    effects.emplace_back(MemoryEffects::Read::get(), privateVar);
+    effects.emplace_back(MemoryEffects::Write::get(), &sourceVar);
   }
 }
 
@@ -3323,18 +3319,15 @@ getReductionEffects(Operation *op, MutableOperandRange reductionVars,
                     ArrayRef<BlockArgument> reductionArgs,
                     std::optional<ArrayAttr> reductionSyms,
                     SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
-  for (auto entry : llvm::zip_equal(reductionVars, reductionArgs)) {
-    OpOperand &original = std::get<0>(entry);
-    BlockArgument privateAccumulator = std::get<1>(entry);
-
+  for (auto [sourceVar, privateAccumulator] : llvm::zip_equal(reductionVars, reductionArgs)) {
     effects.emplace_back(MemoryEffects::Allocate::get(), privateAccumulator);
     effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
 
     effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
     effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
 
-    effects.emplace_back(MemoryEffects::Read::get(), &original);
-    effects.emplace_back(MemoryEffects::Write::get(), &original);
+    effects.emplace_back(MemoryEffects::Read::get(), &sourceVar);
+    effects.emplace_back(MemoryEffects::Write::get(), &sourceVar);
   }
 
   if (reductionSyms) {

>From 2a4790023d41ae19d472c372c1792f9cc14468ca Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com"
 <jamrow01 at ip-10-249-51-4.eu-west-1.compute.internal>
Date: Mon, 12 Oct 2026 14:06:58 +0000
Subject: [PATCH 6/6] Improve accuracy of modelling memory effects of reduction
 and private recipe bodies.

Add tests for tracing recipe arguments through subviews and nested effects
---
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 222 +++++++++++++------
 mlir/test/Dialect/OpenMP/memory-effects.mlir | 165 +++++++++++---
 2 files changed, 294 insertions(+), 93 deletions(-)

diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index d5d91faf5e14a..13a87ebff2516 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -24,6 +24,7 @@
 #include "mlir/IR/SymbolTable.h"
 #include "mlir/Interfaces/FoldInterfaces.h"
 #include "mlir/Interfaces/SideEffectInterfaces.h"
+#include "mlir/Interfaces/ViewLikeInterface.h"
 
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/PostOrderIterator.h"
@@ -3258,91 +3259,181 @@ LogicalResult SectionsOp::verifyRegions() {
   return success();
 }
 
-/// Return whether all operations in the op's regions are memory-effect-free.
-static bool areRegionsMemoryEffectFree(Operation *op) {
-  for (Region &region : op->getRegions())
-    for (Operation &nestedOp : region.getOps())
-      if (!isMemoryEffectFree(&nestedOp))
-        return false;
-  return true;
+using RecipeEffectTarget = std::variant<OpOperand *, BlockArgument>;
+
+/// Follow views to the underlying value, which may be a recipe argument.
+static Value getRecipeEffectBase(Value value) {
+  while (value) {
+    auto view = value.getDefiningOp<ViewLikeOpInterface>();
+    if (!view || value != view.getViewDest())
+      break;
+    value = view.getViewSource();
+  }
+  return value;
 }
 
-/// Model private storage and the effects of referenced privatisation recipes.
+/// Append argument mappings for private variable recipes.
+static void populateRecipeEffectMapping(
+    PrivateClauseOp recipe, OpOperand &sourceVar, BlockArgument privateVar,
+    SmallVectorImpl<std::pair<Value, RecipeEffectTarget>> &mapping) {
+  auto add = [&](BlockArgument arg, RecipeEffectTarget target) {
+    if (arg)
+      mapping.emplace_back(arg, target);
+  };
+
+  add(recipe.getInitMoldArg(), &sourceVar);
+  add(recipe.getInitPrivateArg(), privateVar);
+  add(recipe.getCopyMoldArg(), &sourceVar);
+  add(recipe.getCopyPrivateArg(), privateVar);
+  add(recipe.getDeallocMoldArg(), privateVar);
+}
+
+/// Append argument mappings for a reduction recipe.
+static void populateRecipeEffectMapping(
+    DeclareReductionOp recipe, OpOperand &sourceVar,
+    BlockArgument privateAccumulator,
+    SmallVectorImpl<std::pair<Value, RecipeEffectTarget>> &mapping) {
+  auto mapRegion = [&](Region &region, ArrayRef<RecipeEffectTarget> targets) {
+    if (region.empty())
+      return;
+    
+    for (auto [arg, target] : llvm::zip(region.getArguments(), targets))
+      mapping.emplace_back(arg, target);
+  };
+
+  mapRegion(recipe.getAllocRegion(), {&sourceVar});
+  // Not all combiner recipes are the same, some arguments may not always 
+  // be the same variable.
+  mapRegion(recipe.getInitializerRegion(), {&sourceVar, privateAccumulator});
+  mapRegion(recipe.getReductionRegion(), {&sourceVar, privateAccumulator});
+  mapping.emplace_back(recipe.getReductionLhsArg(), privateAccumulator);
+  mapRegion(recipe.getAtomicReductionRegion(),
+            {&sourceVar, privateAccumulator});
+  mapRegion(recipe.getCleanupRegion(), {privateAccumulator});
+}
+
+/// Add arbitrary read, write, allocate, and free effects.
+static void addUnknownMemoryEffects(
+    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  effects.emplace_back(MemoryEffects::Read::get());
+  effects.emplace_back(MemoryEffects::Write::get());
+  effects.emplace_back(MemoryEffects::Allocate::get());
+  effects.emplace_back(MemoryEffects::Free::get());
+}
+
+/// Add the effects of a symbol-referenced recipe to the corresponding
+/// variables in its directive.
+static void
+getRecipeEffects(Operation *recipe,
+                 ArrayRef<std::pair<Value, RecipeEffectTarget>> mapping,
+                 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  for (Region &region : recipe->getRegions()) {
+    region.walk([&](Operation *op) {
+      auto recipeEffects = dyn_cast<MemoryEffectOpInterface>(op);
+      if (!recipeEffects) {
+        if (hasUnknownEffects(op)) {
+          // An operation without known effects may access arbitrary memory.
+          addUnknownMemoryEffects(effects);
+        }
+        return;
+      }
+
+      SmallVector<MemoryEffects::EffectInstance> opEffects;
+      recipeEffects.getEffects(opEffects);
+      for (const MemoryEffects::EffectInstance &effect : opEffects) {
+        Value affectedValue = effect.getValue();
+        Value base = getRecipeEffectBase(effect.getValue());
+        bool affectsFullRegion =
+            effect.getEffectOnFullRegion() && affectedValue == base;
+        bool mapped = false;
+        for (const auto &[arg, target] : mapping) {
+          // Check if the operand corresponds to a recipe argument
+          if (arg != base)
+            continue;
+          mapped = true;
+          std::visit(
+              [&](auto mappedValue) {
+                effects.emplace_back(effect.getEffect(), mappedValue,
+                                     effect.getParameters(), affectsFullRegion,
+                                     effect.getResource());
+              },
+              target);
+        }
+        if (!mapped) {
+          effects.emplace_back(effect.getEffect(), effect.getParameters(),
+                               false, effect.getResource());
+        }
+      }
+    });
+  }
+}
+
+/// Model private storage and the effects of privatisation recipes.
 template <typename OpType>
 static void getPrivatisationEffects(
     OpType op, SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  MutableOperandRange privateVars = op.getPrivateVarsMutable();
+  Block::BlockArgListType privateArgs = iface.getPrivateBlockArgs();
   std::optional<ArrayAttr> privateSyms = op.getPrivateSyms();
   if (!privateSyms)
     return;
 
-  MutableOperandRange privateVars = op.getPrivateVarsMutable();
-  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
-  Block::BlockArgListType privateArgs = iface.getPrivateBlockArgs();
-  for (auto [sourceVar, privateVar, privateSym] : llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
+  for (auto [sourceVar, privateVar, privateSym] :
+       llvm::zip_equal(privateVars, privateArgs, *privateSyms)) {
+    // Storage is allocated by the directive even if the recipe has no regions.
+    // Implicit stack storage is released by the enclosing allocation scope.
     effects.emplace_back(MemoryEffects::Allocate::get(), privateVar);
-    effects.emplace_back(MemoryEffects::Free::get(), privateVar);
-    // private variables may read the sourceVar as a mold
-    // firstprivate variables do it regardless
-    effects.emplace_back(MemoryEffects::Read::get(), &sourceVar);
-
-    auto privatiser =
-        SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(op, cast<SymbolRefAttr>(privateSym));
-    // RecursiveMemoryEffects only covers regions nested in the directive, not
-    // symbol-referenced regions, user defined types have arbitrary recipe
-    // bodies. Default to a conservative model of read and write to arbitrary
-    // memory.
-    if (!privatiser || !areRegionsMemoryEffectFree(privatiser)) {
-      effects.emplace_back(MemoryEffects::Read::get());
-      effects.emplace_back(MemoryEffects::Write::get());
+
+    auto privatiser = SymbolTable::lookupNearestSymbolFrom<PrivateClauseOp>(
+        op, cast<SymbolRefAttr>(privateSym));
+    if (!privatiser) {
+      continue;
     }
-  }
-}
 
-template <typename OpType>
-static void
-getLastprivateEffects(OpType op,
-                      SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
-  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
-  // We cannot determine if the variable is lastprivate, so assume all private
-  // variables are.
-  for (auto [sourceVar, privateVar] : llvm::zip_equal(op.getPrivateVarsMutable(),
-                                    iface.getPrivateBlockArgs())) {
-    effects.emplace_back(MemoryEffects::Read::get(), privateVar);
-    effects.emplace_back(MemoryEffects::Write::get(), &sourceVar);
+    SmallVector<std::pair<Value, RecipeEffectTarget>> mapping;
+    populateRecipeEffectMapping(privatiser, sourceVar, privateVar, mapping);
+    getRecipeEffects(privatiser, mapping, effects);
   }
 }
 
 /// Model accumulator initialisation and destruction and referenced recipes for
 /// a reduction clause.
+template <typename OpType>
 static void
-getReductionEffects(Operation *op, MutableOperandRange reductionVars,
-                    ArrayRef<BlockArgument> reductionArgs,
-                    std::optional<ArrayAttr> reductionSyms,
+getReductionEffects(OpType op,
                     SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
-  for (auto [sourceVar, privateAccumulator] : llvm::zip_equal(reductionVars, reductionArgs)) {
-    effects.emplace_back(MemoryEffects::Allocate::get(), privateAccumulator);
-    effects.emplace_back(MemoryEffects::Free::get(), privateAccumulator);
+  auto iface = cast<BlockArgOpenMPOpInterface>(op.getOperation());
+  auto reductionVars = op.getReductionVarsMutable();
+  auto reductionArgs = iface.getReductionBlockArgs();
+  auto reductionSyms = op.getReductionSyms();
+  auto reductionByref = op.getReductionByref();
 
-    effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
-    effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
+  if (!reductionSyms)
+    return;
 
-    effects.emplace_back(MemoryEffects::Read::get(), &sourceVar);
-    effects.emplace_back(MemoryEffects::Write::get(), &sourceVar);
-  }
+  for (auto [index, sourceVar, privateAccumulator, recipeSym] :
+       llvm::enumerate(reductionVars, reductionArgs, *reductionSyms)) {
+    if (!reductionByref || !(*reductionByref)[index]) {
+      // By-value recipes operate on scalar values. The directive allocates
+      // accumulator storage and loads/stores values around recipe invocations.
+      // By-reference recipes account for these effects in their bodies.
+      effects.emplace_back(MemoryEffects::Allocate::get(), privateAccumulator);
+      effects.emplace_back(MemoryEffects::Read::get(), &sourceVar);
+      effects.emplace_back(MemoryEffects::Write::get(), privateAccumulator);
+      effects.emplace_back(MemoryEffects::Read::get(), privateAccumulator);
+      effects.emplace_back(MemoryEffects::Write::get(), &sourceVar);
+    }
 
-  if (reductionSyms) {
-    for (Attribute attr : *reductionSyms) {
-      auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
-          op, cast<SymbolRefAttr>(attr));
-      if (recipe && areRegionsMemoryEffectFree(recipe))
-        continue;
-      // RecursiveMemoryEffects only covers operations nested in this directive.
-      // Reduction recipes are separate declarations referenced by symbol, so
-      // their effects must be accounted for explicitly.
-      effects.emplace_back(MemoryEffects::Read::get());
-      effects.emplace_back(MemoryEffects::Write::get());
-      break;
+    auto recipe = SymbolTable::lookupNearestSymbolFrom<DeclareReductionOp>(
+        op, cast<SymbolRefAttr>(recipeSym));
+    if (!recipe) {
+      continue;
     }
+
+    SmallVector<std::pair<Value, RecipeEffectTarget>> mapping;
+    populateRecipeEffectMapping(recipe, sourceVar, privateAccumulator, mapping);
+    getRecipeEffects(recipe, mapping, effects);
   }
 }
 
@@ -3366,12 +3457,7 @@ void SectionsOp::getEffects(
   }
 
   getPrivatisationEffects(*this, effects);
-  getLastprivateEffects(*this, effects);
-
-  auto iface = cast<BlockArgOpenMPOpInterface>(getOperation());
-  getReductionEffects(*this, getReductionVarsMutable(),
-                      iface.getReductionBlockArgs(), getReductionSyms(),
-                      effects);
+  getReductionEffects(*this, effects);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/memory-effects.mlir b/mlir/test/Dialect/OpenMP/memory-effects.mlir
index d0fcb05f3f66c..0c3f00674cb1b 100644
--- a/mlir/test/Dialect/OpenMP/memory-effects.mlir
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -13,25 +13,11 @@ func.func @sections_effects() {
   return
 }
 
-func.func @sections_nowait_no_effects() {
-  // expected-remark at +1 {{operation has no memory effects}}
-  omp.sections nowait {
-    omp.section {
-      omp.terminator
-    }
-    omp.terminator
-  }
-  return
-}
-
 omp.private {type = private} @sections_p : memref<i32>
 
-func.func @sections_private_effects(%x: memref<i32>) {
-  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +4 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +3 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
-  // expected-remark at +2 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+func.func @sections_private_implicit_allocation(%x: memref<i32>) {
+  // The directive allocates private storage even though the recipe is empty.
+  // expected-remark at +1 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
   omp.sections nowait private(@sections_p %x -> %private_x : memref<i32>) {
     omp.section {
     ^bb0(%section_x: memref<i32>):
@@ -57,15 +43,15 @@ combiner {
 }
 
 func.func @sections_reduction_effects(%x: memref<f32>) {
-  // TODO: Nowait does not eliminate an implicit barrier if the reduction clause is also present,
-  // requiring the below two remarks
-  // expected-remark at +8 {{found an instance of 'read' on resource '<Default>'}}
-  // expected-remark at +7 {{found an instance of 'write' on resource '<Default>'}}
-  // expected-remark at +6 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +5 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +4 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // The first two effects come from the implicit barrier, which nowait does
+  // not currently eliminate when a reduction clause is present. Allocation,
+  // initialization and combination access memory outside the by-value recipe.
+  // expected-remark at +7 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +6 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
   // expected-remark at +3 {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
-  // expected-remark at +2 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
   // expected-remark at +1 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
   omp.sections nowait reduction(
       @add_f32 %x -> %private_x : memref<f32>) {
@@ -77,3 +63,132 @@ func.func @sections_reduction_effects(%x: memref<f32>) {
   }
   return
 }
+
+// Effects nested in the copy region must be traced through subviews to the
+// original mold and private arguments.
+omp.private {type = firstprivate} @sections_copy_subviews : memref<4xi32> copy {
+^bb0(%mold: memref<4xi32>, %private: memref<4xi32>):
+  // Use scf.execute_region to test recursive effects.
+  scf.execute_region {
+    // expected-remark at +1 {{operation has no memory effects}}
+    %c0 = arith.constant 0 : index
+    // expected-remark at +1 {{operation has no memory effects}}
+    %mold_view = memref.subview %mold[1] [2] [1] : memref<4xi32> to memref<2xi32, strided<[1], offset: 1>>
+    // expected-remark at +1 {{operation has no memory effects}}
+    %private_view = memref.subview %private[1] [2] [1] : memref<4xi32> to memref<2xi32, strided<[1], offset: 1>>
+    // expected-remark at +1 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+    %value = memref.load %mold_view[%c0] : memref<2xi32, strided<[1], offset: 1>>
+    // expected-remark at +1 {{found an instance of 'write' on op operand 1, on resource '<Default>'}}
+    memref.store %value, %private_view[%c0] : memref<2xi32, strided<[1], offset: 1>>
+    scf.yield
+  }
+  omp.yield(%private : memref<4xi32>)
+} dealloc {
+^bb0(%private: memref<4xi32>):
+  // expected-remark at +1 {{found an instance of 'free' on op operand 0, on resource '<Default>'}}
+  memref.dealloc %private : memref<4xi32>
+  omp.yield
+}
+
+func.func @sections_private_subview_effects(%x: memref<4xi32>) {
+  // expected-remark at +4 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  omp.sections nowait private(@sections_copy_subviews %x -> %private_x : memref<4xi32>) {
+    omp.section {
+    ^bb0(%section_x: memref<4xi32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// Model allocation, initialisation, combination and cleanup effects. 
+// The allocation is local to the recipe initialiser.
+// The combiner LHS can be the source or a private accumulator.
+omp.declare_reduction @sections_add_byref : memref<f32>
+alloc {
+  // expected-remark at +1 {{found an instance of 'allocate' on op result 0, on resource '<Default>'}}
+  %allocated = memref.alloc() : memref<f32>
+  omp.yield(%allocated : memref<f32>)
+}
+init {
+^bb0(%mold: memref<f32>, %private: memref<f32>):
+  // expected-remark at +1 {{operation has no memory effects}}
+  %zero = arith.constant 0.0 : f32
+  // expected-remark at +1 {{found an instance of 'write' on op operand 1, on resource '<Default>'}}
+  memref.store %zero, %private[] : memref<f32>
+  omp.yield(%private : memref<f32>)
+}
+combiner {
+^bb0(%lhs: memref<f32>, %rhs: memref<f32>):
+  // expected-remark at +1 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  %left = memref.load %lhs[] : memref<f32>
+  // expected-remark at +1 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  %right = memref.load %rhs[] : memref<f32>
+  // expected-remark at +1 {{operation has no memory effects}}
+  %sum = arith.addf %left, %right : f32
+  // expected-remark at +1 {{found an instance of 'write' on op operand 1, on resource '<Default>'}}
+  memref.store %sum, %lhs[] : memref<f32>
+  omp.yield(%lhs : memref<f32>)
+}
+cleanup {
+^bb0(%private: memref<f32>):
+  // expected-remark at +1 {{found an instance of 'free' on op operand 0, on resource '<Default>'}}
+  memref.dealloc %private : memref<f32>
+  omp.yield
+}
+
+func.func @sections_reduction_recipe_effects(%x: memref<f32>) {
+  // The first two effects come from the implicit barrier.
+  // expected-remark at +8 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +7 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +6 {{found an instance of 'allocate' on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+  omp.sections nowait reduction(byref @sections_add_byref %x -> %private_x : memref<f32>) {
+    omp.section {
+    ^bb0(%section_x: memref<f32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}
+
+// Unknown effects inside a nested region require an untargeted read and write,
+// while retaining known effects already collected from the recipe.
+func.func private @unknown_recipe_effects(memref<i32>, i32)
+
+omp.private {type = firstprivate} @sections_unknown_copy : memref<i32> copy {
+^bb0(%mold: memref<i32>, %private: memref<i32>):
+  // expected-remark at +1 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  %value = memref.load %mold[] : memref<i32>
+  scf.execute_region {
+    func.call @unknown_recipe_effects(%private, %value) : (memref<i32>, i32) -> ()
+    scf.yield
+  }
+  omp.yield(%private : memref<i32>)
+}
+
+func.func @sections_private_unknown_recipe_effects(%x: memref<i32>) {
+  // expected-remark at +6 {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+  // expected-remark at +5 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+  // expected-remark at +4 {{found an instance of 'read' on resource '<Default>'}}
+  // expected-remark at +3 {{found an instance of 'write' on resource '<Default>'}}
+  // expected-remark at +2 {{found an instance of 'allocate' on resource '<Default>'}}
+  // expected-remark at +1 {{found an instance of 'free' on resource '<Default>'}}
+  omp.sections nowait private(@sections_unknown_copy %x -> %private_x : memref<i32>) {
+    omp.section {
+    ^bb0(%section_x: memref<i32>):
+      omp.terminator
+    }
+    omp.terminator
+  }
+  return
+}



More information about the flang-commits mailing list