[flang-commits] [flang] 1338905 - [Flang][OpenMP] Add support for scope construct (#193098)
via flang-commits
flang-commits at lists.llvm.org
Mon Apr 27 11:49:37 PDT 2026
Author: Sunil Shrestha
Date: 2026-04-27T13:49:29-05:00
New Revision: 133890586eafce8478d596fa422367e45944798a
URL: https://github.com/llvm/llvm-project/commit/133890586eafce8478d596fa422367e45944798a
DIFF: https://github.com/llvm/llvm-project/commit/133890586eafce8478d596fa422367e45944798a.diff
LOG: [Flang][OpenMP] Add support for scope construct (#193098)
Add support for OpenMP scope construct. The private, reduction, and
nowait clauses (introduced in 5.1) are fully supported. Support for the
firstprivate clause (5.2) is also included. The allocate clause (5.2) is
lowered into the omp.scope MLIR op, but MLIR-to-LLVM IR translation is
not yet implemented.
When nowait is combined with reduction, the reduce function argument to
__kmpc_reduce_nowait is passed as null to prevent the runtime from
selecting the tree reduction method for host, which carries a barrier.
Assisted-by: Claude Opus 4.6
Added:
flang/test/Lower/OpenMP/scope.f90
flang/test/Lower/OpenMP/target-scope.f90
mlir/test/Target/LLVMIR/openmp-scope.mlir
Modified:
flang/lib/Lower/OpenMP/OpenMP.cpp
llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
mlir/test/Target/LLVMIR/openmp-todo.mlir
Removed:
flang/test/Lower/OpenMP/Todo/scope-allocate.f90
flang/test/Lower/OpenMP/Todo/scope-firstprivate.f90
flang/test/Lower/OpenMP/Todo/scope.f90
################################################################################
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 6f3e19a84ef19..fdceaae7e2aac 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1749,6 +1749,17 @@ genSimdImplicitLinear(lower::AbstractConverter &converter,
}
}
+static void genScopeClauses(
+ lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx,
+ const List<Clause> &clauses, mlir::Location loc,
+ mlir::omp::ScopeOperands &clauseOps,
+ llvm::SmallVectorImpl<const semantics::Symbol *> &reductionSyms) {
+ ClauseProcessor cp(converter, semaCtx, clauses);
+ cp.processAllocate(clauseOps);
+ cp.processNowait(clauseOps);
+ cp.processReduction(loc, clauseOps, reductionSyms);
+}
+
static void genSingleClauses(lower::AbstractConverter &converter,
semantics::SemanticsContext &semaCtx,
const List<Clause> &clauses, mlir::Location loc,
@@ -2660,14 +2671,40 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
return sectionsOp;
}
-static mlir::Operation *
+static mlir::omp::ScopeOp
genScopeOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
mlir::Location loc, const ConstructQueue &queue,
ConstructQueue::const_iterator item) {
- if (!semaCtx.langOptions().OpenMPSimd)
- TODO(loc, "Scope construct");
- return nullptr;
+ lower::SymMapScope scope(symTable);
+ mlir::omp::ScopeOperands clauseOps;
+ llvm::SmallVector<const semantics::Symbol *> reductionSyms;
+ genScopeClauses(converter, semaCtx, item->clauses, loc, clauseOps,
+ reductionSyms);
+
+ std::optional<DataSharingProcessor> dsp;
+ if (enableDelayedPrivatization) {
+ dsp.emplace(converter, semaCtx, item->clauses, eval,
+ lower::omp::isLastItemInQueue(item, queue),
+ /*useDelayedPrivatization=*/true, symTable);
+ dsp->processStep1(&clauseOps);
+ }
+
+ EntryBlockArgs args;
+ if (dsp)
+ args.priv.syms = dsp->getDelayedPrivSymbols();
+ args.priv.vars = clauseOps.privateVars;
+ args.reduction.syms = reductionSyms;
+ args.reduction.vars = clauseOps.reductionVars;
+
+ return genOpWithBody<mlir::omp::ScopeOp>(
+ OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
+ llvm::omp::Directive::OMPD_scope)
+ .setClauses(&item->clauses)
+ .setEntryBlockArgs(&args)
+ .setDataSharingProcessor(enableDelayedPrivatization ? &dsp.value()
+ : nullptr),
+ queue, item, clauseOps);
}
static mlir::omp::SingleOp
diff --git a/flang/test/Lower/OpenMP/Todo/scope-allocate.f90 b/flang/test/Lower/OpenMP/Todo/scope-allocate.f90
deleted file mode 100644
index 5a834c81a852c..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/scope-allocate.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -o - %s -fopenmp-version=52 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: Scope construct
-program omp_scope
- integer i
- i = 10
-
- !$omp scope allocate(x) private(x)
- print *, "omp scope", i
- !$omp end scope
-
-end program omp_scope
diff --git a/flang/test/Lower/OpenMP/Todo/scope-firstprivate.f90 b/flang/test/Lower/OpenMP/Todo/scope-firstprivate.f90
deleted file mode 100644
index 87bcecb817da4..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/scope-firstprivate.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -o - %s -fopenmp-version=52 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: Scope construct
-program omp_scope
- integer i
- i = 10
-
- !$omp scope firstprivate(x)
- print *, "omp scope", i
- !$omp end scope
-
-end program omp_scope
diff --git a/flang/test/Lower/OpenMP/Todo/scope.f90 b/flang/test/Lower/OpenMP/Todo/scope.f90
deleted file mode 100644
index 16a067dc8f256..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/scope.f90
+++ /dev/null
@@ -1,13 +0,0 @@
-! RUN: %not_todo_cmd bbc -emit-fir -fopenmp -o - %s -fopenmp-version=51 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -o - %s -fopenmp-version=51 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: Scope construct
-program omp_scope
- integer i
- i = 10
-
- !$omp scope private(i)
- print *, "omp scope", i
- !$omp end scope
-
-end program omp_scope
diff --git a/flang/test/Lower/OpenMP/scope.f90 b/flang/test/Lower/OpenMP/scope.f90
new file mode 100644
index 0000000000000..baa5ed493052b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/scope.f90
@@ -0,0 +1,114 @@
+! This test checks the lowering of OpenMP scope construct.
+
+! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+
+! Module-level declarations emitted before any function body.
+
+! CHECK-LABEL: omp.private {type = firstprivate} @_QFomp_scope_firstprivateEi_firstprivate_i32 : i32 copy {
+! CHECK: fir.load %{{.*}} : !fir.ref<i32>
+! CHECK: hlfir.assign %{{.*}} to %{{.*}} : i32, !fir.ref<i32>
+! CHECK: omp.yield
+
+! CHECK-LABEL: omp.declare_reduction @add_reduction_i32 : i32 init {
+! CHECK: arith.constant 0 : i32
+! CHECK: omp.yield
+! CHECK-LABEL: } combiner {
+! CHECK: arith.addi
+! CHECK: omp.yield
+
+! CHECK: omp.private {type = private} @_QFomp_scope_privateEi_private_i32 : i32
+
+! CHECK-LABEL: func @_QPomp_scope_basic
+subroutine omp_scope_basic()
+ integer :: x
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_basicEx"}
+ x = 10
+
+ ! CHECK: omp.scope {
+ !$omp scope
+ print *, "In scope", x
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+end subroutine
+
+! CHECK-LABEL: func @_QPomp_scope_nowait
+subroutine omp_scope_nowait()
+ integer :: x
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_nowaitEx"}
+ x = 10
+
+ ! CHECK: omp.scope nowait {
+ !$omp scope
+ print *, "In scope", x
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope nowait
+end subroutine
+
+! CHECK-LABEL: func @_QPomp_scope_private
+subroutine omp_scope_private()
+ integer :: i
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_privateEi"}
+ i = 10
+
+ ! CHECK: omp.scope private(@_QFomp_scope_privateEi_private_i32 %{{.*}}#0 -> %[[PRIV:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[PDECL:.*]]:2 = hlfir.declare %[[PRIV]] {uniq_name = "_QFomp_scope_privateEi"}
+ !$omp scope private(i)
+ ! CHECK: fir.load %[[PDECL]]#0 : !fir.ref<i32>
+ print *, "omp scope", i
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+end subroutine
+
+! CHECK-LABEL: func @_QPomp_scope_reduction
+subroutine omp_scope_reduction()
+ integer :: sum
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_reductionEsum"}
+ sum = 0
+
+ ! CHECK: omp.scope reduction(@add_reduction_i32 %{{.*}}#0 -> %[[REDUC:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[RDECL:.*]]:2 = hlfir.declare %[[REDUC]] {uniq_name = "_QFomp_scope_reductionEsum"}
+ ! CHECK: fir.load %[[RDECL]]#0 : !fir.ref<i32>
+ ! CHECK: arith.addi %{{.*}}, %{{.*}} : i32
+ ! CHECK: hlfir.assign %{{.*}} to %[[RDECL]]#0 : i32, !fir.ref<i32>
+ !$omp scope reduction(+:sum)
+ sum = sum + 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+end subroutine
+
+! CHECK-LABEL: func @_QPomp_scope_firstprivate
+subroutine omp_scope_firstprivate()
+ integer :: i
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_firstprivateEi"}
+ i = 42
+
+ ! CHECK: omp.scope private(@_QFomp_scope_firstprivateEi_firstprivate_i32 %{{.*}}#0 -> %[[FP:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[FPDECL:.*]]:2 = hlfir.declare %[[FP]] {uniq_name = "_QFomp_scope_firstprivateEi"}
+ !$omp scope firstprivate(i)
+ ! CHECK: fir.load %[[FPDECL]]#0 : !fir.ref<i32>
+ print *, "omp scope", i
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+end subroutine
+
+! CHECK-LABEL: func @_QPomp_scope_allocate
+subroutine omp_scope_allocate()
+ integer :: i
+ ! CHECK: hlfir.declare %{{.*}} {uniq_name = "_QFomp_scope_allocateEi"}
+ i = 0
+
+ ! CHECK: omp.scope allocate(%{{.*}} : i32 -> %{{.*}}#0 : !fir.ref<i32>) private(@_QFomp_scope_allocateEi_private_i32 %{{.*}}#0 -> %[[APRIV:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[ADECL:.*]]:2 = hlfir.declare %[[APRIV]] {uniq_name = "_QFomp_scope_allocateEi"}
+ !$omp scope private(i) allocate(i)
+ ! CHECK: hlfir.assign %{{.*}} to %[[ADECL]]#0 : i32, !fir.ref<i32>
+ i = 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+end subroutine
diff --git a/flang/test/Lower/OpenMP/target-scope.f90 b/flang/test/Lower/OpenMP/target-scope.f90
new file mode 100644
index 0000000000000..d0900bd20e81f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/target-scope.f90
@@ -0,0 +1,135 @@
+! This test checks the lowering of OpenMP scope construct inside a target region.
+
+! RUN: bbc -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
+
+! Module-level declarations emitted before any function body.
+
+! CHECK: omp.private {type = private} @_QFtarget_scope_allocateEi_private_i32 : i32
+
+! CHECK-LABEL: omp.private {type = firstprivate} @_QFtarget_scope_firstprivateEi_firstprivate_i32 : i32 copy {
+! CHECK: fir.load %{{.*}} : !fir.ref<i32>
+! CHECK: hlfir.assign %{{.*}} to %{{.*}} : i32, !fir.ref<i32>
+! CHECK: omp.yield
+
+! CHECK-LABEL: omp.declare_reduction @add_reduction_i32 : i32 init {
+! CHECK: arith.constant 0 : i32
+! CHECK: omp.yield
+! CHECK-LABEL: } combiner {
+! CHECK: arith.addi
+! CHECK: omp.yield
+
+! CHECK: omp.private {type = private} @_QFtarget_scope_privateEi_private_i32 : i32
+
+! CHECK-LABEL: func @_QPtarget_scope_basic
+subroutine target_scope_basic()
+ integer :: x
+ x = 10
+
+ !$omp target
+ ! CHECK: omp.map.info var_ptr(%{{.*}} : !fir.ref<i32>, i32)
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[XARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[XARG]] {uniq_name = "_QFtarget_scope_basicEx"}
+ ! CHECK: omp.scope {
+ !$omp scope
+ x = x + 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+ !$omp end target
+end subroutine
+
+! CHECK-LABEL: func @_QPtarget_scope_nowait
+subroutine target_scope_nowait()
+ integer :: x
+ x = 10
+
+ !$omp target
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[XARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[XARG]] {uniq_name = "_QFtarget_scope_nowaitEx"}
+ ! CHECK: omp.scope nowait {
+ !$omp scope
+ x = x + 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope nowait
+ !$omp end target
+end subroutine
+
+! CHECK-LABEL: func @_QPtarget_scope_private
+subroutine target_scope_private()
+ integer :: i
+ i = 0
+
+ !$omp target
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[IARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[IARG]] {uniq_name = "_QFtarget_scope_privateEi"}
+ ! CHECK: omp.scope private(@_QFtarget_scope_privateEi_private_i32 %{{.*}}#0 -> %[[PRIV:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[PDECL:.*]]:2 = hlfir.declare %[[PRIV]] {uniq_name = "_QFtarget_scope_privateEi"}
+ !$omp scope private(i)
+ ! CHECK: hlfir.assign %{{.*}} to %[[PDECL]]#0 : i32, !fir.ref<i32>
+ i = 42
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+ !$omp end target
+end subroutine
+
+! CHECK-LABEL: func @_QPtarget_scope_reduction
+subroutine target_scope_reduction()
+ integer :: sum
+ sum = 0
+
+ !$omp target
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[SARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[SARG]] {uniq_name = "_QFtarget_scope_reductionEsum"}
+ ! CHECK: omp.scope reduction(@add_reduction_i32 %{{.*}}#0 -> %[[REDUC:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[RDECL:.*]]:2 = hlfir.declare %[[REDUC]] {uniq_name = "_QFtarget_scope_reductionEsum"}
+ !$omp scope reduction(+:sum)
+ ! CHECK: fir.load %[[RDECL]]#0 : !fir.ref<i32>
+ ! CHECK: hlfir.assign %{{.*}} to %[[RDECL]]#0 : i32, !fir.ref<i32>
+ sum = sum + 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+ !$omp end target
+end subroutine
+
+! CHECK-LABEL: func @_QPtarget_scope_firstprivate
+subroutine target_scope_firstprivate()
+ integer :: i
+ i = 42
+
+ !$omp target
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[IARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[IARG]] {uniq_name = "_QFtarget_scope_firstprivateEi"}
+ ! CHECK: omp.scope private(@_QFtarget_scope_firstprivateEi_firstprivate_i32 %{{.*}}#0 -> %[[FP:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[FPDECL:.*]]:2 = hlfir.declare %[[FP]] {uniq_name = "_QFtarget_scope_firstprivateEi"}
+ !$omp scope firstprivate(i)
+ ! CHECK: fir.load %[[FPDECL]]#0 : !fir.ref<i32>
+ ! CHECK: hlfir.assign %{{.*}} to %[[FPDECL]]#0 : i32, !fir.ref<i32>
+ i = i + 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+ !$omp end target
+end subroutine
+
+! CHECK-LABEL: func @_QPtarget_scope_allocate
+subroutine target_scope_allocate()
+ integer :: i
+ i = 0
+
+ !$omp target
+ ! CHECK: omp.target map_entries(%{{.*}} -> %[[IARG:.*]] : !fir.ref<i32>) {
+ ! CHECK: hlfir.declare %[[IARG]] {uniq_name = "_QFtarget_scope_allocateEi"}
+ ! CHECK: omp.scope allocate(%{{.*}} : i32 -> %{{.*}}#0 : !fir.ref<i32>) private(@_QFtarget_scope_allocateEi_private_i32 %{{.*}}#0 -> %[[APRIV:.*]] : !fir.ref<i32>) {
+ ! CHECK: %[[ADECL:.*]]:2 = hlfir.declare %[[APRIV]] {uniq_name = "_QFtarget_scope_allocateEi"}
+ !$omp scope private(i) allocate(i)
+ ! CHECK: hlfir.assign %{{.*}} to %[[ADECL]]#0 : i32, !fir.ref<i32>
+ i = 1
+ ! CHECK: omp.terminator
+ ! CHECK: }
+ !$omp end scope
+ !$omp end target
+end subroutine
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index ba78783c7f13e..5b54df30cbf19 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -3046,6 +3046,19 @@ class OpenMPIRBuilder {
ArrayRef<llvm::Value *> CPVars = {},
ArrayRef<llvm::Function *> CPFuncs = {});
+ /// Generator for '#omp scope'
+ ///
+ /// \param Loc The source location description.
+ /// \param BodyGenCB Callback that will generate the region code.
+ /// \param FiniCB Callback to finalize variable copies.
+ /// \param IsNowait If false, a barrier is emitted.
+ ///
+ /// \returns The insertion position *after* the scope.
+ LLVM_ABI InsertPointOrErrorTy createScope(const LocationDescription &Loc,
+ BodyGenCallbackTy BodyGenCB,
+ FinalizeCallbackTy FiniCB,
+ bool IsNowait);
+
/// Generator for '#omp master'
///
/// \param Loc The insert and source location description.
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 3cf4f09f02e29..24cf5c389d8fe 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -7602,6 +7602,34 @@ OpenMPIRBuilder::InsertPointOrErrorTy OpenMPIRBuilder::createSingle(
return Builder.saveIP();
}
+OpenMPIRBuilder::InsertPointOrErrorTy
+OpenMPIRBuilder::createScope(const LocationDescription &Loc,
+ BodyGenCallbackTy BodyGenCB,
+ FinalizeCallbackTy FiniCB, bool IsNowait) {
+
+ if (!updateToLocation(Loc))
+ return Loc.IP;
+
+ // All threads execute the scope body — no conditional entry.
+ InsertPointOrErrorTy AfterIP = EmitOMPInlinedRegion(
+ Directive::OMPD_scope, /*EntryCall=*/nullptr, /*ExitCall=*/nullptr,
+ BodyGenCB, FiniCB, /*Conditional=*/false, /*HasFinalize=*/true,
+ /*IsCancellable=*/false);
+ if (!AfterIP)
+ return AfterIP.takeError();
+
+ Builder.restoreIP(*AfterIP);
+ if (!IsNowait) {
+ AfterIP = createBarrier(LocationDescription(Builder.saveIP(), Loc.DL),
+ omp::Directive::OMPD_unknown,
+ /*ForceSimpleCall=*/false,
+ /*CheckCancelFlag=*/false);
+ if (!AfterIP)
+ return AfterIP.takeError();
+ }
+ return Builder.saveIP();
+}
+
OpenMPIRBuilder::InsertPointOrErrorTy OpenMPIRBuilder::createCritical(
const LocationDescription &Loc, BodyGenCallbackTy BodyGenCB,
FinalizeCallbackTy FiniCB, StringRef CriticalName, Value *HintInst) {
diff --git a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
index 964f40469f4a9..b5bb82276298f 100644
--- a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
@@ -8376,4 +8376,90 @@ TEST_F(OpenMPIRBuilderTest, EmitOffloadingArraysNonContigCountExpression) {
EXPECT_EQ(dyn_cast<AllocaInst>(RTArgs.SizesArray), nullptr);
}
+TEST_F(OpenMPIRBuilderTest, ScopeDirective) {
+ using InsertPointTy = OpenMPIRBuilder::InsertPointTy;
+ OpenMPIRBuilder OMPBuilder(*M);
+ OMPBuilder.initialize();
+ F->setName("func");
+ IRBuilder<> Builder(BB);
+
+ OpenMPIRBuilder::LocationDescription Loc({Builder.saveIP(), DL});
+
+ // Track the basic block where the body was emitted so we can find the
+ // barrier that scope must insert after the body.
+ BasicBlock *BodyBB = nullptr;
+ auto BodyGenCB = [&](InsertPointTy AllocaIP, InsertPointTy CodeGenIP,
+ ArrayRef<BasicBlock *> DeallocBlocks) {
+ BodyBB = CodeGenIP.getBlock();
+ Builder.restoreIP(CodeGenIP);
+ // Emit a no-op store so the body block is non-empty.
+ Builder.CreateStore(Builder.getInt32(42),
+ Builder.CreateAlloca(Builder.getInt32Ty()));
+ };
+
+ auto FiniCB = [&](InsertPointTy IP) {};
+
+ ASSERT_EXPECTED_INIT(InsertPointTy, AfterIP,
+ OMPBuilder.createScope(Loc, BODYGENCB_WRAPPER(BodyGenCB),
+ FINICB_WRAPPER(FiniCB),
+ /*IsNowait=*/false));
+ Builder.restoreIP(AfterIP);
+ Builder.CreateRetVoid();
+ OMPBuilder.finalize();
+
+ EXPECT_FALSE(verifyModule(*M, &errs()));
+
+ // Scope with IsNowait=false must emit a __kmpc_barrier after the body.
+ bool FoundBarrier = false;
+ for (BasicBlock &Block : *F) {
+ for (Instruction &I : Block) {
+ auto *CI = dyn_cast<CallInst>(&I);
+ if (CI && CI->getCalledFunction() &&
+ CI->getCalledFunction()->getName() == "__kmpc_barrier") {
+ FoundBarrier = true;
+ break;
+ }
+ }
+ }
+ EXPECT_TRUE(FoundBarrier);
+}
+
+TEST_F(OpenMPIRBuilderTest, ScopeDirectiveNowait) {
+ using InsertPointTy = OpenMPIRBuilder::InsertPointTy;
+ OpenMPIRBuilder OMPBuilder(*M);
+ OMPBuilder.initialize();
+ F->setName("func");
+ IRBuilder<> Builder(BB);
+
+ OpenMPIRBuilder::LocationDescription Loc({Builder.saveIP(), DL});
+
+ auto BodyGenCB = [&](InsertPointTy AllocaIP, InsertPointTy CodeGenIP,
+ ArrayRef<BasicBlock *> DeallocBlocks) {
+ Builder.restoreIP(CodeGenIP);
+ Builder.CreateStore(Builder.getInt32(42),
+ Builder.CreateAlloca(Builder.getInt32Ty()));
+ };
+
+ auto FiniCB = [&](InsertPointTy IP) {};
+
+ ASSERT_EXPECTED_INIT(InsertPointTy, AfterIP,
+ OMPBuilder.createScope(Loc, BODYGENCB_WRAPPER(BodyGenCB),
+ FINICB_WRAPPER(FiniCB),
+ /*IsNowait=*/true));
+ Builder.restoreIP(AfterIP);
+ Builder.CreateRetVoid();
+ OMPBuilder.finalize();
+
+ EXPECT_FALSE(verifyModule(*M, &errs()));
+
+ // Scope with IsNowait=true must NOT emit any __kmpc_barrier.
+ for (BasicBlock &Block : *F) {
+ for (Instruction &I : Block) {
+ auto *CI = dyn_cast<CallInst>(&I);
+ if (CI && CI->getCalledFunction())
+ EXPECT_NE(CI->getCalledFunction()->getName(), "__kmpc_barrier");
+ }
+ }
+}
+
} // namespace
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 9954e1f233542..cddbe4a318e69 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -357,6 +357,36 @@ def SingleOp : OpenMP_Op<"single", traits = [
let hasVerifier = 1;
}
+//===----------------------------------------------------------------------===//
+// Scope Construct (OpenMP 5.1)
+//===----------------------------------------------------------------------===//
+
+def ScopeOp
+ : OpenMP_Op<"scope", traits = [AttrSizedOperandSegments],
+ clauses = [OpenMP_AllocateClause, OpenMP_NowaitClause,
+ OpenMP_PrivateClause, OpenMP_ReductionClause],
+ singleRegion = true> {
+ let summary = "scope directive";
+ let description = [{
+ The scope construct defines a structured block that is executed by all
+ threads in the current team, providing a lexical scope for
+ privatization. An implicit barrier occurs at the end of the region
+ unless the `nowait` clause is present.
+
+ Introduced in OpenMP 5.1.
+ }]#clausesDescription;
+
+ let builders = [OpBuilder<(ins CArg<"const ScopeOperands &">:$clauses)>];
+
+ 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
+ }];
+
+ let hasVerifier = 1;
+}
+
//===---------------------------------------------------------------------===//
// OpenMP Canonical Loop Info Creation
//===---------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index ab86081b6c995..bb300c4afa562 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -2934,6 +2934,34 @@ LogicalResult SectionsOp::verifyRegions() {
return success();
}
+//===----------------------------------------------------------------------===//
+// ScopeOp
+//===----------------------------------------------------------------------===//
+
+void ScopeOp::build(OpBuilder &builder, OperationState &state,
+ const ScopeOperands &clauses) {
+ MLIRContext *ctx = builder.getContext();
+ ScopeOp::build(builder, state, clauses.allocateVars, clauses.allocatorVars,
+ clauses.nowait, clauses.privateVars,
+ makeArrayAttr(ctx, clauses.privateSyms),
+ clauses.privateNeedsBarrier, clauses.reductionMod,
+ clauses.reductionVars,
+ makeDenseBoolArrayAttr(ctx, clauses.reductionByref),
+ makeArrayAttr(ctx, clauses.reductionSyms));
+}
+
+LogicalResult ScopeOp::verify() {
+ if (getAllocateVars().size() != getAllocatorVars().size())
+ return emitError(
+ "expected equal sizes for allocate and allocator variables");
+
+ if (failed(verifyPrivateVarList(*this)))
+ return failure();
+
+ return verifyReductionVarList(*this, getReductionSyms(), getReductionVars(),
+ getReductionByref());
+}
+
//===----------------------------------------------------------------------===//
// SingleOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index dedd9c0e902c8..53020dc867926 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -398,6 +398,10 @@ static LogicalResult checkImplementationStatus(Operation &op) {
checkPrivate(op, result);
checkReduction(op, result);
})
+ .Case([&](omp::ScopeOp op) {
+ checkAllocate(op, result);
+ checkReduction(op, result);
+ })
.Case([&](omp::SingleOp op) {
checkAllocate(op, result);
checkPrivate(op, result);
@@ -2011,6 +2015,92 @@ convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
privateReductionVariables, isByRef, sectionsOp.getNowait());
}
+/// Converts an OpenMP scope construct into LLVM IR.
+static LogicalResult
+convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
+ llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+
+ if (failed(checkImplementationStatus(*scopeOp)))
+ return failure();
+
+ llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
+ assert(isByRef.size() == scopeOp.getNumReductionVars());
+
+ PrivateVarsInfo privateVarsInfo(scopeOp);
+
+ SmallVector<omp::DeclareReductionOp> reductionDecls;
+ collectReductionDecls(scopeOp, reductionDecls);
+ InsertPointTy allocaIP = findAllocInsertPoints(builder, moduleTranslation);
+
+ SmallVector<llvm::Value *> privateReductionVariables(
+ scopeOp.getNumReductionVars());
+ DenseMap<Value, llvm::Value *> reductionVariableMap;
+
+ MutableArrayRef<BlockArgument> reductionArgs =
+ cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
+
+ // Allocate private vars before the scope body
+ llvm::Expected<llvm::BasicBlock *> afterAllocas = allocatePrivateVars(
+ scopeOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
+ if (failed(handleError(afterAllocas, *scopeOp)))
+ return failure();
+
+ if (failed(allocAndInitializeReductionVars(
+ scopeOp, reductionArgs, builder, moduleTranslation, allocaIP,
+ reductionDecls, privateReductionVariables, reductionVariableMap,
+ isByRef)))
+ return failure();
+
+ auto bodyCB =
+ [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
+ llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
+ builder.restoreIP(codeGenIP);
+
+ if (handleError(
+ initPrivateVars(builder, moduleTranslation, privateVarsInfo),
+ *scopeOp)
+ .failed())
+ return llvm::make_error<PreviouslyReportedError>();
+
+ if (failed(copyFirstPrivateVars(
+ scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
+ privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
+ scopeOp.getPrivateNeedsBarrier())))
+ return llvm::make_error<PreviouslyReportedError>();
+
+ return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
+ moduleTranslation)
+ .takeError();
+ };
+
+ auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
+ InsertPointTy oldIP = builder.saveIP();
+ builder.restoreIP(codeGenIP);
+ if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
+ scopeOp.getLoc(), privateVarsInfo)))
+ return llvm::make_error<PreviouslyReportedError>();
+ builder.restoreIP(oldIP);
+ return llvm::Error::success();
+ };
+
+ llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+ llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+ ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
+
+ if (failed(handleError(afterIP, *scopeOp)))
+ return failure();
+
+ builder.restoreIP(*afterIP);
+
+ // Process the reductions if required.
+ return createReductionsAndCleanup(
+ scopeOp, builder, moduleTranslation, allocaIP, reductionDecls,
+ privateReductionVariables, isByRef, scopeOp.getNowait(),
+ /*isTeamsReduction=*/false);
+}
+
/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
static LogicalResult
convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
@@ -8155,6 +8245,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
.Case([&](omp::SectionsOp) {
return convertOmpSections(*op, builder, moduleTranslation);
})
+ .Case([&](omp::ScopeOp op) {
+ return convertOmpScope(op, builder, moduleTranslation);
+ })
.Case([&](omp::SingleOp op) {
return convertOmpSingle(op, builder, moduleTranslation);
})
diff --git a/mlir/test/Target/LLVMIR/openmp-scope.mlir b/mlir/test/Target/LLVMIR/openmp-scope.mlir
new file mode 100644
index 0000000000000..cedd85cfa1b31
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-scope.mlir
@@ -0,0 +1,179 @@
+// RUN: mlir-translate -mlir-to-llvmir -split-input-file %s | FileCheck %s
+
+// Basic scope: body runs in omp.scope.region, barrier emitted after.
+// CHECK-LABEL: define internal void @scope_basic..omp_par
+// CHECK: br label %omp.scope.region
+// CHECK: omp.scope.region:
+// CHECK: br label %omp.region.cont3
+// CHECK: omp_region.finalize:
+// CHECK: call void @__kmpc_barrier(
+
+llvm.func @scope_basic() {
+ omp.parallel {
+ omp.scope {
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
+// Scope nowait: body runs in omp.scope.region, no barrier emitted.
+// CHECK-LABEL: define internal void @scope_nowait..omp_par
+// CHECK: br label %omp.scope.region
+// CHECK: omp.scope.region:
+// CHECK: omp_region.finalize:
+// CHECK-NOT: call void @__kmpc_barrier(
+// CHECK: ret void
+
+llvm.func @scope_nowait() {
+ omp.parallel {
+ omp.scope nowait {
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
+// Scope with reduction: reduction vars initialized before scope body,
+// __kmpc_reduce / __kmpc_end_reduce emitted + scope barrier
+// CHECK-LABEL: define internal void @scope_reduction..omp_par
+// CHECK: omp.reduction.init:
+// CHECK: store float 0.000000e+00, ptr
+// CHECK: omp.scope.region:
+// CHECK: fadd float
+// CHECK: omp_region.finalize:
+// CHECK: call void @__kmpc_barrier(
+// CHECK: call i32 @__kmpc_reduce(
+// CHECK: reduce.switch.nonatomic:
+// CHECK: fadd float
+// CHECK: call void @__kmpc_end_reduce(
+
+omp.declare_reduction @add_f32 : f32 init {
+^bb0(%arg0: f32):
+ %c = llvm.mlir.constant(0.0 : f32) : f32
+ omp.yield(%c : f32)
+} combiner {
+^bb0(%arg0: f32, %arg1: f32):
+ %r = llvm.fadd %arg0, %arg1 : f32
+ omp.yield(%r : f32)
+}
+
+llvm.func @scope_reduction(%ptr: !llvm.ptr) {
+ omp.parallel {
+ omp.scope reduction(@add_f32 %ptr -> %arg0 : !llvm.ptr) {
+ %c = llvm.mlir.constant(1.0 : f32) : f32
+ %v = llvm.load %arg0 : !llvm.ptr -> f32
+ %r = llvm.fadd %v, %c : f32
+ llvm.store %r, %arg0 : f32, !llvm.ptr
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
+// Scope with reduction + nowait: nowait suppresses the scope barrier
+// __kmpc_reduce_nowait / __kmpc_end_reduce_nowait emitted
+// CHECK-LABEL: define internal void @scope_reduction_nowait..omp_par
+// CHECK: omp.reduction.init:
+// CHECK: store float 0.000000e+00, ptr
+// CHECK: omp.scope.region:
+// CHECK: fadd float
+// CHECK: omp_region.finalize:
+// CHECK-NOT: call void @__kmpc_barrier(
+// CHECK: call i32 @__kmpc_reduce_nowait(
+// CHECK: reduce.switch.nonatomic:
+// CHECK: call void @__kmpc_end_reduce_nowait(
+
+omp.declare_reduction @add_f32_2 : f32 init {
+^bb0(%arg0: f32):
+ %c = llvm.mlir.constant(0.0 : f32) : f32
+ omp.yield(%c : f32)
+} combiner {
+^bb0(%arg0: f32, %arg1: f32):
+ %r = llvm.fadd %arg0, %arg1 : f32
+ omp.yield(%r : f32)
+}
+
+llvm.func @scope_reduction_nowait(%ptr: !llvm.ptr) {
+ omp.parallel {
+ omp.scope nowait reduction(@add_f32_2 %ptr -> %arg0 : !llvm.ptr) {
+ %c = llvm.mlir.constant(1.0 : f32) : f32
+ %v = llvm.load %arg0 : !llvm.ptr -> f32
+ %r = llvm.fadd %v, %c : f32
+ llvm.store %r, %arg0 : f32, !llvm.ptr
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
+// Scope with private: a per-thread alloca is created, body uses it,
+// original variable is not touched, barrier emitted after.
+// CHECK-LABEL: define internal void @scope_private..omp_par
+// CHECK: %omp.private.alloc = alloca i32
+// CHECK: omp.private.init:
+// CHECK: omp.scope.region:
+// CHECK: store i32 1, ptr %omp.private.alloc
+// CHECK: omp_region.finalize:
+// CHECK: call void @__kmpc_barrier(
+
+omp.private {type = private} @x_private_i32 : i32
+
+llvm.func @scope_private(%x_ptr: !llvm.ptr) {
+ %c1 = llvm.mlir.constant(1 : i32) : i32
+ omp.parallel {
+ omp.scope private(@x_private_i32 %x_ptr -> %arg0 : !llvm.ptr) {
+ llvm.store %c1, %arg0 : i32, !llvm.ptr
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
+// Scope with firstprivate: per-thread alloca created, original value copied in
+// before the body, barrier emitted after.
+// CHECK-LABEL: define internal void @scope_firstprivate..omp_par
+// CHECK: %omp.private.alloc = alloca i32
+// CHECK: omp.private.copy:
+// CHECK: %[[ORIG:.*]] = load i32, ptr
+// CHECK: store i32 %[[ORIG]], ptr %omp.private.alloc
+// CHECK: omp.scope.region:
+// CHECK: load i32, ptr %omp.private.alloc
+// CHECK: omp_region.finalize:
+// CHECK: call void @__kmpc_barrier(
+
+omp.private {type = firstprivate} @x_firstprivate_i32 : i32 copy {
+^bb0(%arg0: !llvm.ptr, %arg1: !llvm.ptr):
+ %0 = llvm.load %arg0 : !llvm.ptr -> i32
+ llvm.store %0, %arg1 : i32, !llvm.ptr
+ omp.yield(%arg1 : !llvm.ptr)
+}
+
+llvm.func @scope_firstprivate(%x_ptr: !llvm.ptr) {
+ %c1 = llvm.mlir.constant(1 : i32) : i32
+ omp.parallel {
+ omp.scope private(@x_firstprivate_i32 %x_ptr -> %arg0 : !llvm.ptr) {
+ %v = llvm.load %arg0 : !llvm.ptr -> i32
+ %r = llvm.add %v, %c1 : i32
+ llvm.store %r, %arg0 : i32, !llvm.ptr
+ omp.terminator
+ }
+ omp.terminator
+ }
+ llvm.return
+}
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index af5da3dc8c3a4..3e521fb4f9263 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -74,6 +74,17 @@ llvm.func @sections_allocate(%x : !llvm.ptr) {
// -----
+llvm.func @scope_allocate(%x : !llvm.ptr) {
+ // expected-error at below {{not yet implemented: Unhandled clause allocate in omp.scope operation}}
+ // expected-error at below {{LLVM Translation failed for operation: omp.scope}}
+ omp.scope allocate(%x : !llvm.ptr -> %x : !llvm.ptr) {
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
omp.private {type = private} @x.privatizer : i32 init {
^bb0(%mold: !llvm.ptr, %private: !llvm.ptr):
%c0 = llvm.mlir.constant(0 : i32) : i32
More information about the flang-commits
mailing list