[Mlir-commits] [flang] [mlir] [mlir][OpenMP] Add memory effects of single op (PR #237929)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Nov 2 03:51:24 PST 2026
https://github.com/JRP-arm created https://github.com/llvm/llvm-project/pull/237929
Added a new attribute for the copyprivate clause `copyprivate_assume_bounded_recipe_effects`. Applied when the copy private variables are of a specific type.
Add direct memory effect tests, CSE & canonicalisation tests.
Add tests for correct application of the `copyprivate_assume_bounded_recipe_effects` attribute.
AI-Assisted-by: Codex (OpenAI)
>From 2d2a7ef3528b04bd0af60ba76ae55701c195a40f Mon Sep 17 00:00:00 2001
From: "Jamie.Rowlinson-Patel at arm.com" <jamie.rowlinson-patel at arm.com>
Date: Mon, 26 Oct 2026 11:09:45 +0000
Subject: [PATCH] [mlir][OpenMP] Add memory effects of single op
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 68 +++---
flang/test/Lower/OpenMP/compound.f90 | 14 +-
.../Lower/OpenMP/recipe-memory-effects.f90 | 199 ++++++++++++++++++
.../mlir/Dialect/OpenMP/OpenMPClauses.td | 20 +-
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 10 +-
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 49 ++++-
mlir/test/Dialect/OpenMP/canonicalize.mlir | 28 +++
mlir/test/Dialect/OpenMP/cse.mlir | 21 ++
mlir/test/Dialect/OpenMP/memory-effects.mlir | 84 ++++++++
9 files changed, 445 insertions(+), 48 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 98a0a54ad2a6f2..63ebe6224bea4b 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1858,6 +1858,30 @@ struct OpWithBodyGenInfo {
int collapseValue = 0;
};
+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);
+}
+
/// Create the body (block) for an OpenMP Operation.
///
/// \param [in] op - the operation the body belongs to.
@@ -1926,6 +1950,17 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
Fortran::lower::omp::isLastItemInQueue(item, queue),
/*useDelayedPrivatization=*/false, info.symTable);
tempDsp->processStep1();
+
+ // TODO: omp.single still uses eager privatisation.
+ // Use clauseOps.privateVars inside genSingleOp once
+ // delayedPrivatization is turned on.
+ if (auto singleOp = llvm::dyn_cast<mlir::omp::SingleOp>(op)) {
+ const auto &privateSyms = tempDsp->getAllSymbolsToPrivatize();
+ if (llvm::all_of(privateSyms, [](const semantics::Symbol *sym) {
+ return isNonDerivedNonPointerItem(*sym);
+ }))
+ singleOp.setPrivateAssumeBoundedRecipeEffects(true);
+ }
}
if (info.dir == llvm::omp::Directive::OMPD_parallel) {
@@ -3564,30 +3599,6 @@ 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,
@@ -3945,11 +3956,18 @@ genSingleOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::omp::SingleOperands clauseOps;
genSingleClauses(converter, semaCtx, item->clauses, loc, clauseOps);
- return genOpWithBody<mlir::omp::SingleOp>(
+ auto singleOp = genOpWithBody<mlir::omp::SingleOp>(
OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
llvm::omp::Directive::OMPD_single)
.setClauses(&item->clauses),
queue, item, clauseOps);
+
+ if (llvm::all_of(clauseOps.copyprivateVars, [](mlir::Value variable) {
+ return isNonDerivedNonPointer(variable);
+ }))
+ singleOp.setCopyprivateAssumeBoundedRecipeEffects(true);
+
+ return singleOp;
}
static bool isDuplicateMappedSymbol(
diff --git a/flang/test/Lower/OpenMP/compound.f90 b/flang/test/Lower/OpenMP/compound.f90
index d61745345a6405..f06edf36244e5e 100644
--- a/flang/test/Lower/OpenMP/compound.f90
+++ b/flang/test/Lower/OpenMP/compound.f90
@@ -784,7 +784,7 @@ subroutine single_loop()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_parallel()
implicit none
@@ -803,7 +803,7 @@ subroutine single_parallel()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_simd()
implicit none
@@ -825,7 +825,7 @@ subroutine single_simd()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_target()
implicit none
@@ -844,7 +844,7 @@ subroutine single_target()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_target_data()
implicit none
@@ -864,7 +864,7 @@ subroutine single_target_data()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_task()
implicit none
@@ -883,7 +883,7 @@ subroutine single_task()
! CHECK-NEXT: }
! CHECK-NOT: omp.combined
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine single_taskloop()
implicit none
@@ -908,7 +908,7 @@ subroutine single_taskloop()
! CHECK: omp.terminator
! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
! CHECK: omp.terminator
-! CHECK-NEXT: } {{{.*}}omp.combined{{.*}}}
+! CHECK-NEXT: }{{.*}} {{{.*}}omp.combined{{.*}}}
subroutine target_task()
implicit none
diff --git a/flang/test/Lower/OpenMP/recipe-memory-effects.f90 b/flang/test/Lower/OpenMP/recipe-memory-effects.f90
index 631906ee7b45ba..1e4d748492e2ea 100644
--- a/flang/test/Lower/OpenMP/recipe-memory-effects.f90
+++ b/flang/test/Lower/OpenMP/recipe-memory-effects.f90
@@ -158,4 +158,203 @@ subroutine derived_common_private()
z%value = x
!$omp end sections
end subroutine
+
+ ! Eager privatisation emits the privatisation recipe inside the single
+ ! region, preventing getEffects() from providing its memory effects.
+ ! Make sure these are emitted so recursive analyis records their memory
+ ! effects.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPsingle_eager_firstprivate(
+ ! CHECK: %[[ORIGINAL:.*]] = fir.alloca i32
+ ! CHECK: %[[ORIGINAL_DECL:.*]]:2 = hlfir.declare %[[ORIGINAL]]
+ ! CHECK: omp.parallel {
+ ! CHECK-NEXT: omp.single {
+ ! CHECK-NEXT: %[[PRIVATE:.*]] = fir.alloca i32
+ ! CHECK-NEXT: %[[PRIVATE_DECL:.*]]:2 = hlfir.declare %[[PRIVATE]]
+ ! CHECK-NEXT: %[[INITIAL:.*]] = fir.load %[[ORIGINAL_DECL]]#0 : !fir.ref<i32>
+ ! CHECK-NEXT: hlfir.assign %[[INITIAL]] to %[[PRIVATE_DECL]]#0 : i32, !fir.ref<i32>
+ ! CHECK-NEXT: %[[VALUE:.*]] = fir.load %[[PRIVATE_DECL]]#0 : !fir.ref<i32>
+ ! CHECK-NEXT: %[[ONE:.*]] = arith.constant 1 : i32
+ ! CHECK-NEXT: %[[UPDATED:.*]] = arith.addi %[[VALUE]], %[[ONE]] : i32
+ ! CHECK-NEXT: hlfir.assign %[[UPDATED]] to %[[PRIVATE_DECL]]#0 : i32, !fir.ref<i32>
+ ! CHECK-NEXT: omp.terminator
+ ! CHECK-NEXT: } <copyprivate_assume_bounded_recipe_effects, private_assume_bounded_recipe_effects>
+ ! CHECK-NEXT: omp.terminator
+ ! CHECK-NEXT: }
+ ! CHECK-NEXT: %[[AFTER:.*]] = fir.load %[[ORIGINAL_DECL]]#0 : !fir.ref<i32>
+ ! CHECK-NEXT: hlfir.assign %[[AFTER]] to %{{.*}} : i32, !fir.ref<i32>
+ ! CHECK-NEXT: return
+ subroutine single_eager_firstprivate(result)
+ integer :: result, x
+ x = 10
+ !$omp parallel shared(x)
+ !$omp single firstprivate(x)
+ x = x + 1
+ !$omp end single
+ !$omp end parallel
+ result = x
+ end subroutine
+
+ ! Eager single privatization maps COMMON members rather than the block.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPsingle_intrinsic_common_private(
+ ! CHECK: omp.single {
+ ! CHECK: } <copyprivate_assume_bounded_recipe_effects, private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine single_intrinsic_common_private()
+ integer :: x
+ real :: y
+ common /single_private_block/ x, y
+ !$omp single private(/single_private_block/)
+ x = 1
+ y = 1.0
+ !$omp end single
+ end subroutine
+
+ ! Firstprivate COMMON blocks must also be checked member by member.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPsingle_intrinsic_common_firstprivate(
+ ! CHECK: omp.single {
+ ! CHECK: } <copyprivate_assume_bounded_recipe_effects, private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine single_intrinsic_common_firstprivate()
+ integer :: x
+ real :: y
+ common /single_firstprivate_block/ x, y
+ !$omp single firstprivate(/single_firstprivate_block/)
+ x = x + 1
+ y = y + 1.0
+ !$omp end single
+ end subroutine
+
+ ! One derived member prevents the assumption for the entire COMMON block.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPsingle_derived_common_private(
+ ! CHECK: omp.single {
+ ! CHECK-NOT: private_assume_bounded_recipe_effects
+ ! CHECK: } <copyprivate_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine single_derived_common_private()
+ integer :: x
+ type(record) :: z
+ common /single_derived_block/ x, z
+ !$omp single private(/single_derived_block/)
+ x = 1
+ z%value = x
+ !$omp end single
+ end subroutine
+
+ ! Direct intrinsic scalars and fixed-size arrays have bounded copy functions.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPintrinsic_copyprivate(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine intrinsic_copyprivate()
+ integer :: i, a(4)
+ real :: r
+ complex :: c
+ logical :: l
+ character(8) :: s
+ !$omp parallel private(i, a, r, c, l, s)
+ !$omp single
+ !$omp end single copyprivate(i, a, r, c, l, s)
+ !$omp end parallel
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPno_copyprivate(
+ ! CHECK: omp.single {
+ ! CHECK: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine no_copyprivate()
+ !$omp single
+ !$omp end single
+ end subroutine
+
+ ! Copyprivate and private make independent assumptions.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPbounded_copyprivate_derived_private(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine bounded_copyprivate_derived_private()
+ type record
+ integer :: value
+ end type
+ type(record) :: z
+ integer :: i
+ !$omp parallel private(i)
+ !$omp single private(z)
+ !$omp end single copyprivate(i)
+ !$omp end parallel
+ end subroutine
+
+ ! One derived item prevents the assumption for the entire copyprivate clause.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPderived_copyprivate_bounded_private(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK-NOT: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: } <private_assume_bounded_recipe_effects>
+ ! CHECK: return
+ subroutine derived_copyprivate_bounded_private()
+ type record
+ integer :: value
+ end type
+ type(record) :: z
+ integer :: i, p
+ !$omp parallel private(i, z)
+ !$omp single private(p)
+ !$omp end single copyprivate(i, z)
+ !$omp end parallel
+ end subroutine
+
+ ! Allocatable copies may allocate, free, and access pointee storage.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPallocatable_scalar_copyprivate(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK-NOT: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine allocatable_scalar_copyprivate()
+ integer, allocatable :: a
+ !$omp parallel private(a)
+ !$omp single
+ !$omp end single copyprivate(a)
+ !$omp end parallel
+ end subroutine
+
+ ! Allocatables may access pointees of the copyprivate variable, violating the
+ ! copyprivate_assume_bounded_recipe_effects attribute contract.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPallocatable_array_copyprivate(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK-NOT: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine allocatable_array_copyprivate()
+ integer, allocatable :: a(:)
+ !$omp parallel private(a)
+ !$omp single
+ !$omp end single copyprivate(a)
+ !$omp end parallel
+ end subroutine
+
+ ! Pointer variables may access pointees of the copyprivate variable, violating
+ ! the copyprivate_assume_bounded_recipe_effects attribute contract.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPpointer_copyprivate(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK-NOT: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine pointer_copyprivate()
+ integer, pointer :: p, a(:)
+ !$omp parallel private(p, a)
+ !$omp single
+ !$omp end single copyprivate(p, a)
+ !$omp end parallel
+ end subroutine
+
+ ! Check the members of a threadprivate COMMON block.
+ ! CHECK-LABEL: func.func @_QMrecipe_memory_effectsPintrinsic_common_copyprivate(
+ ! CHECK: omp.single copyprivate(
+ ! CHECK: copyprivate_assume_bounded_recipe_effects
+ ! CHECK: return
+ subroutine intrinsic_common_copyprivate()
+ integer :: i
+ real :: r
+ common /intrinsic_copy_block/ i, r
+ !$omp threadprivate(/intrinsic_copy_block/)
+ !$omp parallel
+ !$omp single
+ !$omp end single copyprivate(/intrinsic_copy_block/)
+ !$omp end parallel
+ end subroutine
end module
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index ab6e7178c9f2e9..eddd4fde2cd6bd 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -241,13 +241,16 @@ def OpenMP_CollapseClause : OpenMP_CollapseClauseSkip<>;
class OpenMP_CopyprivateClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = false,
- bit description = false, bit extraClassDeclaration = false
+ bit description = false, bit extraClassDeclaration = false,
+ bit boundedRecipeEffects = false
> : OpenMP_Clause<traits, arguments, assemblyFormat, description,
extraClassDeclaration> {
- let arguments = (ins
+ let arguments = !con((ins
Variadic<OpenMP_PointerLikeType>:$copyprivate_vars,
OptionalAttr<SymbolRefArrayAttr>:$copyprivate_syms
- );
+ ), !if(boundedRecipeEffects, (ins
+ UnitAttr:$copyprivate_assume_bounded_recipe_effects
+ ), (ins)));
let optAssemblyFormat = [{
`copyprivate` `(`
@@ -259,6 +262,15 @@ class OpenMP_CopyprivateClauseSkip<
If `copyprivate` variables and functions are specified, then each thread
variable is updated with the variable value of the thread that executed
the single region, using the specified copy functions.
+
+ The optional `copyprivate_assume_bounded_recipe_effects` unit attribute
+ asserts that referenced copyprivatisation recipes may only read or write
+ `copyprivate` variables associated with the clause.
+ These effects apply only to storage directly designated by the `copyprivate`
+ variables. Recipes may not access other storage, including globals or
+ pointees reached by loading pointers from these variables. Without the
+ attribute, referenced copy functions are assumed to have arbitrary memory
+ effects.
}];
}
@@ -1515,7 +1527,6 @@ def OpenMP_PriorityClause : OpenMP_PriorityClauseSkip<>;
class OpenMP_PrivateClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = 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> {
@@ -1584,7 +1595,6 @@ def OpenMP_ProcBindClause : OpenMP_ProcBindClauseSkip<>;
class OpenMP_ReductionClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = 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> {
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index d08b27b90ede5c..021ed0ce7d35e8 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -370,10 +370,12 @@ def SectionsOp : OpenMP_Op<"sections", traits = [
//===----------------------------------------------------------------------===//
def SingleOp : OpenMP_Op<"single", traits = [
- AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>
+ AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
+ DeclareOpInterfaceMethods<MemoryEffectsOpInterface>, RecursiveMemoryEffects
], clauses = [
- OpenMP_AllocateClause, OpenMP_CopyprivateClause, OpenMP_NowaitClause,
- OpenMP_PrivateClause
+ OpenMP_AllocateClause,
+ OpenMP_CopyprivateClauseSkip<boundedRecipeEffects = true>, OpenMP_NowaitClause,
+ OpenMP_PrivateClauseSkip<boundedRecipeEffects = true>
], singleRegion = true> {
let summary = "single directive";
let description = [{
@@ -390,7 +392,7 @@ def SingleOp : OpenMP_Op<"single", traits = [
let assemblyFormat = clausesAssemblyFormat # [{
custom<PrivateRegion>($region, $private_vars, type($private_vars),
- $private_syms, $private_needs_barrier) attr-dict
+ $private_syms, $private_needs_barrier) 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 1c869ad04bbcf6..7e2d94e8f74c0b 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3594,13 +3594,15 @@ void SingleOp::build(OpBuilder &builder, OperationState &state,
const SingleOperands &clauses) {
MLIRContext *ctx = builder.getContext();
// TODO Store clauses in op: privateVars, privateSyms, privateNeedsBarrier
- SingleOp::build(builder, state, clauses.allocateVars, clauses.allocatorVars,
- makeDenseI64ArrayAttr(ctx, clauses.allocateAlignments),
- makeDenseI64ArrayAttr(ctx, clauses.allocatePrivateIndices),
- clauses.copyprivateVars,
- makeArrayAttr(ctx, clauses.copyprivateSyms), clauses.nowait,
- /*private_vars=*/{}, /*private_syms=*/nullptr,
- /*private_needs_barrier=*/nullptr);
+ SingleOp::build(
+ builder, state, clauses.allocateVars, clauses.allocatorVars,
+ makeDenseI64ArrayAttr(ctx, clauses.allocateAlignments),
+ makeDenseI64ArrayAttr(ctx, clauses.allocatePrivateIndices),
+ clauses.copyprivateVars, makeArrayAttr(ctx, clauses.copyprivateSyms),
+ /*copyprivate_assume_bounded_recipe_effects=*/nullptr, clauses.nowait,
+ /*private_vars=*/{}, /*private_syms=*/nullptr,
+ /*private_needs_barrier=*/nullptr,
+ /*private_assume_bounded_recipe_effects=*/nullptr);
}
LogicalResult SingleOp::verify() {
@@ -3614,6 +3616,39 @@ LogicalResult SingleOp::verify() {
getCopyprivateSyms());
}
+void SingleOp::getEffects(
+ llvm::SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+ &effects) {
+ // The presence of the nowait clause eliminates the implicit barrier at the
+ // end of a single construct.
+ if (!getNowait()) {
+ getBarrierEffects(effects);
+ }
+
+ // Eager privatisation emits explicit operations and leaves privateVars
+ // empty. Since the privatisation operations are emitted explicitly, their
+ // memory effects are accounted for by recursive analysis.
+ // Once single uses delayed privatisation `!getPrivateVars().empty()` will no
+ // longer be redundant.
+ bool hasUnknownRecipeEffects =
+ (!getPrivateVars().empty() && !getPrivateAssumeBoundedRecipeEffects()) ||
+ (!getCopyprivateVars().empty() &&
+ !getCopyprivateAssumeBoundedRecipeEffects());
+ if (hasUnknownRecipeEffects) {
+ addUnknownMemoryEffects(effects);
+ } else {
+ getPrivatisationEffects(*this, effects);
+ // The unknown executing thread supplies the value and the other threads
+ // receive it, so each copyprivate operand may be both read and written.
+ OperandRange copyprivateVars = getCopyprivateVars();
+ for (OpOperand &variable :
+ MutableArrayRef(copyprivateVars.getBase(), copyprivateVars.size())) {
+ effects.emplace_back(MemoryEffects::Read::get(), &variable);
+ effects.emplace_back(MemoryEffects::Write::get(), &variable);
+ }
+ }
+}
+
//===----------------------------------------------------------------------===//
// WorkshareOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/canonicalize.mlir b/mlir/test/Dialect/OpenMP/canonicalize.mlir
index 4b65b0d648f048..1483452e818a7e 100644
--- a/mlir/test/Dialect/OpenMP/canonicalize.mlir
+++ b/mlir/test/Dialect/OpenMP/canonicalize.mlir
@@ -223,3 +223,31 @@ func.func @sections_nowait_call() {
}
func.func private @opaque()
+
+// -----
+
+// A single with no side effects can be removed when it has no implicit barrier.
+// CHECK-LABEL: func.func @single_nowait_no_side_effects
+// CHECK-NEXT: return
+func.func @single_nowait_no_side_effects(%a: i32, %b: i32) {
+ omp.single nowait {
+ %unused = arith.addi %a, %b : i32
+ omp.terminator
+ }
+ return
+}
+
+// -----
+
+// The implicit barrier must survive even when the single's body is empty.
+// CHECK-LABEL: func.func @single_only_terminator
+// CHECK-NEXT: omp.single {
+// CHECK-NEXT: omp.terminator
+// CHECK-NEXT: }
+// CHECK-NEXT: return
+func.func @single_only_terminator() {
+ omp.single {
+ omp.terminator
+ }
+ return
+}
diff --git a/mlir/test/Dialect/OpenMP/cse.mlir b/mlir/test/Dialect/OpenMP/cse.mlir
index 919547efc2a092..11d78881e2a47a 100644
--- a/mlir/test/Dialect/OpenMP/cse.mlir
+++ b/mlir/test/Dialect/OpenMP/cse.mlir
@@ -18,3 +18,24 @@ func.func @sections_barrier_between_loads(%shared: memref<i32>) -> (i32, i32) {
%after = memref.load %shared[] : memref<i32>
return %before, %after : i32, i32
}
+
+// -----
+
+// Without the private-clause assumption, arbitrary memory effects are assumed
+// so no CSE is possible.
+omp.private {type = private} @recipe : memref<i32>
+
+// CHECK-LABEL: func.func @single_unbounded_private(
+// CHECK-SAME: %[[X:[^:]+]]: memref<i32>
+// CHECK: %[[BEFORE:.*]] = memref.load %[[X]][] : memref<i32>
+// CHECK: omp.single nowait
+// CHECK: %[[AFTER:.*]] = memref.load %[[X]][] : memref<i32>
+// CHECK: return %[[BEFORE]], %[[AFTER]] : i32, i32
+func.func @single_unbounded_private(%x: memref<i32>, %original: memref<i32>) -> (i32, i32) {
+ %before = memref.load %x[] : memref<i32>
+ omp.single nowait private(@recipe %original -> %copy : memref<i32>) {
+ omp.terminator
+ }
+ %after = memref.load %x[] : 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 b52f815c5e05cb..f2372cd471c497 100644
--- a/mlir/test/Dialect/OpenMP/memory-effects.mlir
+++ b/mlir/test/Dialect/OpenMP/memory-effects.mlir
@@ -173,3 +173,87 @@ func.func @sections_bounded_private_bounded_reduction(%x: memref<i32>, %y: memre
} <{private_assume_bounded_recipe_effects, reduction_assume_bounded_recipe_effects}>
return
}
+
+func.func private @copy_i32(memref<i32>, memref<i32>)
+func.func private @copy_ptr(!llvm.ptr, !llvm.ptr)
+
+func.func @single_unbounded_copyprivate_effects(%x: memref<i32>) {
+ // An unannotated copy function may access arbitrary memory, allocate or free.
+ // 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.single copyprivate(%x -> @copy_i32 : memref<i32>) {
+ omp.terminator
+ }
+ return
+}
+
+func.func @single_bounded_copyprivate_effects(%x: memref<i32>, %y: !llvm.ptr) {
+ // 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 'write' on op operand 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>'}}
+ omp.single copyprivate(%x -> @copy_i32 : memref<i32>, %y -> @copy_ptr : !llvm.ptr) {
+ omp.terminator
+ } <{copyprivate_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @single_bounded_private_unbounded_copyprivate(%x: memref<i32>, %y: memref<i32>) {
+ // A bounded private clause does not constrain the copy function.
+ // 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.single copyprivate(%x -> @copy_i32 : memref<i32>)
+ private(@private_i32 %y -> %py : memref<i32>) {
+ omp.terminator
+ } <{private_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @single_unbounded_private_bounded_copyprivate(%x: memref<i32>, %y: memref<i32>) {
+ // A bounded copy function does not constrain the private recipe.
+ // 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.single copyprivate(%x -> @copy_i32 : memref<i32>)
+ private(@private_i32 %y -> %py : memref<i32>) {
+ omp.terminator
+ } <{copyprivate_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @single_bounded_private_bounded_copyprivate(%x: memref<i32>, %y: memref<i32>) {
+ // 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 1, 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 0, on resource '<Default>'}}
+ // expected-remark at below {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+ omp.single copyprivate(%x -> @copy_i32 : memref<i32>)
+ private(@private_i32 %y -> %py : memref<i32>) {
+ omp.terminator
+ } <{copyprivate_assume_bounded_recipe_effects, private_assume_bounded_recipe_effects}>
+ return
+}
+
+func.func @single_empty_copyprivate_effects() {
+ // expected-remark at below {{recursive memory effects}}
+ // expected-remark at below {{operation has no memory effects}}
+ omp.single nowait {
+ omp.terminator
+ } <{copyprivate_assume_bounded_recipe_effects}>
+ return
+}
More information about the Mlir-commits
mailing list