[Mlir-commits] [flang] [mlir] [mlir][OpenMP] Model memory effects of omp.section and omp.sections (PR #230167)
llvmlistbot at llvm.org
llvmlistbot at 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 ®ion : 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 Mlir-commits
mailing list