[Mlir-commits] [mlir] b31fdd0 - [mlir][OpenMP] Model memory effects of omp.section and omp.sections (#230167)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Oct 30 09:00:27 PDT 2026
Author: JRP-arm
Date: 2026-10-30T16:00:20Z
New Revision: b31fdd062478b0a69686796f57d8c9e541853d25
URL: https://github.com/llvm/llvm-project/commit/b31fdd062478b0a69686796f57d8c9e541853d25
DIFF: https://github.com/llvm/llvm-project/commit/b31fdd062478b0a69686796f57d8c9e541853d25.diff
LOG: [mlir][OpenMP] Model memory effects of omp.section and omp.sections (#230167)
Model recursive effects of section construct bodies.
Account for implicit barriers, private and reduction recipes in sections
constructs.
Add a new attribute to private and reduction clauses, indicating when we
can assume their recipe bodies have restricted memory effects.
Add shared helper functions for applying the attribute in suitable
situations (non-derived, non-allocatable reductions and privatisations).
Add shared helper functions for recording restricted memory effects.
Update the Flang sections lowering tests.
Assisted by: AI (codex)
Added:
flang/test/Lower/OpenMP/recipe-memory-effects.f90
mlir/test/Dialect/OpenMP/cse.mlir
Modified:
flang/lib/Lower/OpenMP/OpenMP.cpp
flang/test/Lower/OpenMP/sections.f90
mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
mlir/test/Dialect/OpenMP/canonicalize.mlir
mlir/test/Dialect/OpenMP/memory-effects.mlir
Removed:
################################################################################
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3ce793989dee30..98a0a54ad2a6f2 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3564,6 +3564,30 @@ findEnclosingParallelOp(fir::FirOpBuilder &builder) {
return {};
}
+static bool isNonDerivedNonPointer(const semantics::Symbol &sym) {
+ const auto &ultimate = sym.GetUltimate();
+ const auto *type = ultimate.GetType();
+ return type && type->AsIntrinsic() && !semantics::IsAllocatable(ultimate) &&
+ !semantics::IsPointer(ultimate);
+}
+
+static bool isNonDerivedNonPointer(const mlir::Value &variable) {
+ return !fir::isAllocatableType(variable.getType()) &&
+ !fir::isa_derived(fir::getFortranElementType(variable.getType())) &&
+ !fir::isPointerType(variable.getType());
+}
+
+static bool isNonDerivedNonPointerItem(const semantics::Symbol &sym) {
+ const auto &ultimate = sym.GetUltimate();
+ if (const auto *common =
+ ultimate.detailsIf<semantics::CommonBlockDetails>()) {
+ return llvm::all_of(common->objects(), [](const auto &member) {
+ return isNonDerivedNonPointer(*member);
+ });
+ }
+ return isNonDerivedNonPointer(ultimate);
+}
+
static mlir::omp::SectionsOp
genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -3657,6 +3681,23 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
// SECTIONS construct.
auto sectionsOp = mlir::omp::SectionsOp::create(builder, loc, clauseOps);
+ // TODO: sections still uses eager privatisation. Use clauseOps.privateVars
+ // once delayedPrivatization is turned on.
+ const auto &privateSyms = dsp.getAllSymbolsToPrivatize();
+ if (!privateSyms.empty() &&
+ llvm::all_of(privateSyms, [](const semantics::Symbol *sym) {
+ return isNonDerivedNonPointerItem(*sym);
+ }))
+ sectionsOp.setPrivateAssumeBoundedRecipeEffects(true);
+
+ if (!clauseOps.reductionVars.empty() &&
+ llvm::all_of(clauseOps.reductionVars, [lpAlloca](mlir::Value variable) {
+ // The generated conditional-lastprivate recipe only accesses the
+ // intrinsic value and index fields of its accumulator, despite its
+ // record type.
+ return variable == lpAlloca || isNonDerivedNonPointer(variable);
+ }))
+ sectionsOp.setReductionAssumeBoundedRecipeEffects(true);
// Create entry block with reduction variables as arguments.
ObjectEntryBlockArgs args;
diff --git a/flang/test/Lower/OpenMP/recipe-memory-effects.f90 b/flang/test/Lower/OpenMP/recipe-memory-effects.f90
new file mode 100644
index 00000000000000..631906ee7b45ba
--- /dev/null
+++ b/flang/test/Lower/OpenMP/recipe-memory-effects.f90
@@ -0,0 +1,161 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 %s -o - | FileCheck %s
+
+! Each clause has its own bounded-recipe-effects assumption, which requires
+! all variables in that clause to be non-derived and non-allocatable.
+module recipe_memory_effects
+ implicit none
+ type :: record
+ sequence
+ integer :: value
+ end type
+contains
+ ! Intrinsic private and reduction variables are eligible independently.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPintrinsic_clauses(
+ ! CHECK: omp.sections reduction(
+ ! CHECK: } <private_assume_bounded_recipe_effects, reduction_assume_bounded_recipe_effects>
+ subroutine intrinsic_clauses(x, y, p, f, l)
+ integer :: x, p, f, l
+ real :: y
+ !$omp sections reduction(+:x,y) private(p) firstprivate(f) lastprivate(l)
+ p = f
+ l = p
+ x = x + p
+ y = y + 1.0
+ !$omp end sections
+ end subroutine
+
+ ! Non-derived private, firstprivate and lastprivate variables are eligible
+ ! without a reduction clause.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPintrinsic_private(
+ ! CHECK: omp.sections {
+ ! CHECK: } <private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine intrinsic_private(p, f, l)
+ integer :: p, f, l
+ !$omp sections private(p) firstprivate(f) lastprivate(l)
+ p = f
+ l = p
+ !$omp end sections
+ end subroutine
+
+ ! An allocatable variable prevents the assumption for the whole private clause.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPboxed_private(
+ ! CHECK: omp.sections {
+ ! CHECK-NOT: assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine boxed_private(a, p)
+ real, allocatable :: a(:)
+ real, pointer :: p
+ !$omp sections private(a, p)
+ a = 1.0
+ nullify(p)
+ !$omp end sections
+ end subroutine
+
+ ! Scalar allocatables are also excluded, independently of the other clause.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPallocatable_private_scalar_reduction(
+ ! CHECK: omp.sections reduction(
+ ! CHECK-NOT: private_assume_bounded_recipe_effects
+ ! CHECK: } <reduction_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine allocatable_private_scalar_reduction(a, x)
+ real, allocatable :: a
+ integer :: x
+ !$omp sections private(a) reduction(+:x)
+ x = x + 1
+ !$omp end sections
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPallocatable_reduction_scalar_private(
+ ! CHECK: omp.sections reduction(
+ ! CHECK-NOT: reduction_assume_bounded_recipe_effects
+ ! CHECK: } <private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine allocatable_reduction_scalar_private(a, x)
+ real, allocatable :: a
+ integer :: x
+ !$omp sections reduction(+:a) private(x)
+ x = 1
+ a = a + x
+ !$omp end sections
+ end subroutine
+
+ ! A derived private variable prevents the assumption for the private clause.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPderived_private(
+ ! CHECK: omp.sections {
+ ! CHECK-NOT: assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine derived_private()
+ integer :: i
+ type(record) :: z
+ !$omp sections private(i, z)
+ i = 1
+ z%value = i
+ !$omp end sections
+ end subroutine
+
+ ! The generated conditional-lastprivate record is eligible.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPconditional_lastprivate(
+ ! CHECK: omp.sections reduction(
+ ! CHECK: } <reduction_assume_bounded_recipe_effects>
+ subroutine conditional_lastprivate(x, y)
+ integer :: x, y
+ !$omp sections lastprivate(conditional:x) firstprivate(x) reduction(+:y)
+ x = x + 1
+ y = y + x
+ !$omp end sections
+ end subroutine
+
+ ! A derived reduction prevents the assumption for the whole reduction clause,
+ ! independently of the non-derived private variables.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPconditional_lastprivate_derived_reduction(
+ ! CHECK: omp.sections reduction({{(byref )?}}@
+ ! CHECK-NOT: reduction_assume_bounded_recipe_effects
+ ! CHECK: } <private_assume_bounded_recipe_effects>
+ ! CHECK-NOT: reduction_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine conditional_lastprivate_derived_reduction(x, z)
+ integer :: x, p
+ type(record) :: z
+ !$omp declare reduction(record_sum : record : &
+ !$omp & omp_out%value = omp_out%value + omp_in%value) &
+ !$omp & initializer(omp_priv%value = 0)
+ !$omp parallel
+ !$omp sections lastprivate(conditional:x) reduction(record_sum:z) private(p)
+ p = 1
+ x = p
+ z%value = z%value + x
+ !$omp end sections
+ !$omp end parallel
+ end subroutine
+
+ ! A common block is eligible when all its members are non-derived.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPintrinsic_common_private(
+ ! CHECK: omp.sections {
+ ! CHECK: } <private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine intrinsic_common_private()
+ integer :: x
+ real :: y
+ common /intrinsic_block/ x, y
+ !$omp sections private(/intrinsic_block/)
+ x = 1
+ y = 1.0
+ !$omp end sections
+ end subroutine
+
+ ! A derived common-block member prevents the private-clause assumption.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPderived_common_private(
+ ! CHECK: omp.sections {
+ ! CHECK-NOT: assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine derived_common_private()
+ integer :: x
+ type(record) :: z
+ common /derived_block/ x, z
+ !$omp sections private(/derived_block/)
+ x = 1
+ z%value = x
+ !$omp end sections
+ end subroutine
+end module
diff --git a/flang/test/Lower/OpenMP/sections.f90 b/flang/test/Lower/OpenMP/sections.f90
index f1e089caa81017..08a69a56c42010 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
@@ -75,8 +76,8 @@ program sample
count = count * eta
double_count = count - eta
!$omp end sections
-
!$omp sections
+ call foo()
!$omp end sections nowait
end program sample
@@ -87,9 +88,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/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index 81419ac4b9920b..ab6e7178c9f2e9 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -1514,22 +1514,37 @@ def OpenMP_PriorityClause : OpenMP_PriorityClauseSkip<>;
class OpenMP_PrivateClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = false,
- bit description = false, bit extraClassDeclaration = false
+ bit description = false, bit extraClassDeclaration = false,
+ // Only sections currently consumes the recipe-effect assumption.
+ bit boundedRecipeEffects = false
> : OpenMP_Clause<traits, arguments, assemblyFormat, description,
extraClassDeclaration> {
let traits = [
BlockArgOpenMPOpInterface
];
- let arguments = (ins
+ let arguments = !con((ins
Variadic<AnyType>:$private_vars,
OptionalAttr<SymbolRefArrayAttr>:$private_syms,
// Set this attribute if a barrier is needed after initialization and
// copying of lastprivate variables.
UnitAttr:$private_needs_barrier
- );
+ ), !if(boundedRecipeEffects, (ins
+ UnitAttr:$private_assume_bounded_recipe_effects
+ ), (ins)));
- // TODO: Add description.
+ let description = [{
+ The optional `private_assume_bounded_recipe_effects` unit attribute asserts
+ that all regions of every referenced privatisation recipe may only read the
+ original variable and allocate, write or free private storage.
+ Lastprivate copy-back is represented by explicit nested operations.
+
+ These effects apply only to storage directly designated by the original
+ variables and their corresponding private block arguments. Recipes may not
+ access other storage, including globals or pointees reached by loading
+ pointers from these variables. Without the attribute, referenced
+ privatisation recipes are assumed to have arbitrary memory effects.
+ }];
// Assembly format not defined because this clause must be processed together
// with the first region of the operation, as it defines entry block
// arguments.
@@ -1568,19 +1583,23 @@ def OpenMP_ProcBindClause : OpenMP_ProcBindClauseSkip<>;
class OpenMP_ReductionClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = false,
- bit description = false, bit extraClassDeclaration = false
+ bit description = false, bit extraClassDeclaration = false,
+ // Only sections currently consumes the recipe-effect assumption.
+ bit boundedRecipeEffects = false
> : OpenMP_Clause<traits, arguments, assemblyFormat, description,
extraClassDeclaration> {
let traits = [
BlockArgOpenMPOpInterface
];
- let arguments = (ins
+ let arguments = !con((ins
OptionalAttr<ReductionModifierAttr>:$reduction_mod,
Variadic<OpenMP_PointerLikeType>:$reduction_vars,
OptionalAttr<DenseBoolArrayAttr>:$reduction_byref,
OptionalAttr<SymbolRefArrayAttr>:$reduction_syms
- );
+ ), !if(boundedRecipeEffects, (ins
+ UnitAttr:$reduction_assume_bounded_recipe_effects
+ ), (ins)));
let extraClassDeclaration = [{
/// Returns the number of reduction variables.
@@ -1601,6 +1620,17 @@ class OpenMP_ReductionClauseSkip<
specifies how to combine the values from each iteration, section, team,
thread or simd lane defined by the operation's region into the final value,
which is available in the accumulator after they all complete.
+
+ The optional `reduction_assume_bounded_recipe_effects` unit attribute
+ asserts that all regions of every referenced reduction recipe may only read
+ or write the original variable and allocate, free, read or write private
+ accumulators.
+
+ These effects apply only to storage directly designated by the original
+ variables and their corresponding private block arguments. Recipes may not
+ access other storage, including globals or pointees reached by loading
+ pointers from these variables. Without the attribute, referenced reduction
+ recipes are assumed to have arbitrary memory effects.
}];
// Assembly format not defined because this clause must be processed together
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 307b91ebe99af2..d08b27b90ede5c 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -306,7 +306,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 = [{
@@ -329,10 +330,12 @@ def SectionOp : OpenMP_Op<"section", traits = [
def SectionsOp : OpenMP_Op<"sections", traits = [
AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
- HasParallelRegion
+ HasParallelRegion,
+ DeclareOpInterfaceMethods<MemoryEffectsOpInterface>, RecursiveMemoryEffects
], clauses = [
- OpenMP_AllocateClause, OpenMP_NowaitClause, OpenMP_PrivateClause,
- OpenMP_ReductionClause
+ OpenMP_AllocateClause, OpenMP_NowaitClause,
+ OpenMP_PrivateClauseSkip<boundedRecipeEffects = true>,
+ OpenMP_ReductionClauseSkip<boundedRecipeEffects = true>
], singleRegion = true> {
let summary = "sections construct";
let description = [{
@@ -355,7 +358,7 @@ def SectionsOp : OpenMP_Op<"sections", traits = [
let assemblyFormat = clausesAssemblyFormat # [{
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
- type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
+ type($reduction_vars), $reduction_byref, $reduction_syms) prop-dict attr-dict
}];
let hasVerifier = 1;
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 0b0d4f6f658c67..1c869ad04bbcf6 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3435,9 +3435,11 @@ void SectionsOp::build(OpBuilder &builder, OperationState &state,
makeDenseI64ArrayAttr(ctx, clauses.allocatePrivateIndices),
clauses.nowait, /*private_vars=*/{},
/*private_syms=*/nullptr, /*private_needs_barrier=*/nullptr,
+ /*private_assume_bounded_recipe_effects=*/nullptr,
clauses.reductionMod, clauses.reductionVars,
makeDenseBoolArrayAttr(ctx, clauses.reductionByref),
- makeArrayAttr(ctx, clauses.reductionSyms));
+ makeArrayAttr(ctx, clauses.reductionSyms),
+ /*reduction_assume_bounded_recipe_effects=*/nullptr);
}
LogicalResult SectionsOp::verify() {
@@ -3465,6 +3467,92 @@ LogicalResult SectionsOp::verifyRegions() {
return success();
}
+/// 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());
+}
+
+/// Privatisation may read the original as a mold or firstprivate copy source.
+/// Any lastprivate copy-back is explicit in the nested operations.
+static void getPrivatisationEffects(
+ Operation *op, SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+ auto iface = dyn_cast<BlockArgOpenMPOpInterface>(op);
+
+ OperandRange sourceVars = iface.getPrivateVars();
+ for (OpOperand &source :
+ MutableArrayRef(sourceVars.getBase(), sourceVars.size()))
+ effects.emplace_back(MemoryEffects::Read::get(), &source);
+ for (BlockArgument priv : iface.getPrivateBlockArgs()) {
+ effects.emplace_back(MemoryEffects::Write::get(), priv);
+ effects.emplace_back(MemoryEffects::Allocate::get(), priv);
+ effects.emplace_back(MemoryEffects::Free::get(), priv);
+ }
+}
+
+/// Reductions initialise private accumulators and combine them into the
+/// original variables. Account for all three reduction clauses.
+static void
+getReductionEffects(Operation *op,
+ SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+ auto iface = dyn_cast<BlockArgOpenMPOpInterface>(op);
+
+ auto addEffects = [&](OperandRange sourceVars,
+ ArrayRef<BlockArgument> privateArgs) {
+ for (OpOperand &source :
+ MutableArrayRef(sourceVars.getBase(), sourceVars.size())) {
+ effects.emplace_back(MemoryEffects::Read::get(), &source);
+ effects.emplace_back(MemoryEffects::Write::get(), &source);
+ }
+
+ for (BlockArgument priv : privateArgs) {
+ effects.emplace_back(MemoryEffects::Allocate::get(), priv);
+ effects.emplace_back(MemoryEffects::Free::get(), priv);
+ effects.emplace_back(MemoryEffects::Read::get(), priv);
+ effects.emplace_back(MemoryEffects::Write::get(), priv);
+ }
+ };
+
+ addEffects(iface.getReductionVars(), iface.getReductionBlockArgs());
+ addEffects(iface.getInReductionVars(), iface.getInReductionBlockArgs());
+ addEffects(iface.getTaskReductionVars(), iface.getTaskReductionBlockArgs());
+}
+
+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);
+ }
+
+ bool hasUnknownRecipeEffects =
+ (!getPrivateVars().empty() && !getPrivateAssumeBoundedRecipeEffects()) ||
+ (!getReductionVars().empty() &&
+ !getReductionAssumeBoundedRecipeEffects());
+
+ if (hasUnknownRecipeEffects) {
+ addUnknownMemoryEffects(effects);
+ } else {
+ getPrivatisationEffects(*this, effects);
+ getReductionEffects(*this, effects);
+ }
+}
+
//===----------------------------------------------------------------------===//
// ScopeOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/canonicalize.mlir b/mlir/test/Dialect/OpenMP/canonicalize.mlir
index cc42f2ed8618c7..4b65b0d648f048 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/cse.mlir b/mlir/test/Dialect/OpenMP/cse.mlir
new file mode 100644
index 00000000000000..919547efc2a092
--- /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 1efa0acd23da0e..b52f815c5e05cb 100644
--- a/mlir/test/Dialect/OpenMP/memory-effects.mlir
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -16,4 +16,160 @@ func.func @master_recursive_memory_effects() {
omp.terminator
}
return
-}
\ No newline at end of file
+}
+
+func.func @sections_effects() {
+ // An implicit barrier reads and writes unspecified memory.
+ // expected-remark at below {{recursive memory effects}}
+ // 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.terminator
+ }
+ return
+}
+
+func.func @sections_nowait_effects() {
+ // Nowait removes the implicit barrier effects.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at +1 {{operation has no memory effects}}
+ omp.sections nowait {
+ omp.terminator
+ }
+ return
+}
+
+omp.private {type = firstprivate} @private_i32 : memref<i32> copy {
+^bb0(%original: memref<i32>, %private: memref<i32>):
+ // expected-remark at +1 {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+ %v = memref.load %original[] : memref<i32>
+ // expected-remark at +1 {{found an instance of 'write' on op operand 1, on resource '<Default>'}}
+ memref.store %v, %private[] : memref<i32>
+ omp.yield(%private : memref<i32>)
+}
+omp.private {type = private} @private_ptr : !llvm.ptr
+
+func.func @sections_bounded_private_effects(%x: memref<i32>, %y: !llvm.ptr) {
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on op operand 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on block argument 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on block argument 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on block argument 1, on resource '<Default>'}}
+ omp.sections nowait private(@private_i32 %x -> %px,
+ @private_ptr %y -> %py : memref<i32>, !llvm.ptr) {
+ omp.terminator
+ } <{private_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @sections_unbounded_private_effects(%x: memref<i32>) {
+ // Without the flag, all private recipe effects are arbitrary.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on resource '<Default>'}}
+ omp.sections nowait private(@private_i32 %x -> %px : memref<i32>) {
+ 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_bounded_reduction_effects(%x: memref<f32>) {
+ // TODO: nowait does not currently eliminate the barrier with reductions.
+ // The first two effects come from the implicit barrier.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+ omp.sections nowait reduction(@add_f32 %x -> %private_x : memref<f32>) {
+ omp.terminator
+ } <{reduction_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @sections_unbounded_reduction_effects(%x: memref<f32>) {
+ // Without the flag, even a pure recipe is modeled with arbitrary effects.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on resource '<Default>'}}
+ omp.sections nowait reduction(@add_f32 %x -> %p : memref<f32>) {
+ omp.terminator
+ }
+ return
+}
+
+func.func @sections_bounded_private_unbounded_reduction(%x: memref<i32>, %y: memref<f32>) {
+ // A bounded private clause does not constrain an unbounded reduction clause.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on resource '<Default>'}}
+ omp.sections nowait private(@private_i32 %x -> %px : memref<i32>)
+ reduction(@add_f32 %y -> %py : memref<f32>) {
+ omp.terminator
+ } <{private_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @sections_unbounded_private_bounded_reduction(%x: memref<i32>, %y: memref<f32>) {
+ // A bounded reduction clause does not constrain an unbounded private clause.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on resource '<Default>'}}
+ omp.sections nowait private(@private_i32 %x -> %px : memref<i32>)
+ reduction(@add_f32 %y -> %py : memref<f32>) {
+ omp.terminator
+ } <{reduction_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @sections_bounded_private_bounded_reduction(%x: memref<i32>, %y: memref<f32>) {
+ // The private and reduction clauses make independent assumptions.
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{found an instance of 'read' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on op operand 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on block argument 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on op operand 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on op operand 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'allocate' on block argument 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'free' on block argument 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'read' on block argument 1, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on block argument 1, on resource '<Default>'}}
+ omp.sections nowait private(@private_i32 %x -> %px : memref<i32>)
+ reduction(@add_f32 %y -> %py : memref<f32>) {
+ omp.terminator
+ } <{private_assume_bounded_recipe_effects, reduction_assume_bounded_recipe_effects}>
+ return
+}
More information about the Mlir-commits
mailing list