[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
Thu Oct 8 08:56:51 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-openmp

@llvm/pr-subscribers-mlir

Author: JRP-arm

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/230167.diff


5 Files Affected:

- (modified) flang/test/Lower/OpenMP/sections.f90 (+2-3) 
- (modified) mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td (+4-2) 
- (modified) mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp (+94) 
- (modified) mlir/test/Dialect/OpenMP/canonicalize.mlir (+80) 
- (added) mlir/test/Dialect/OpenMP/memory-effects.mlir (+78) 


``````````diff
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
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/230167


More information about the flang-commits mailing list