[llvm-branch-commits] [flang] [flang][OpenMP] Match variant selectors in the source construct context (PR #231690)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Oct 13 15:55:40 PDT 2026
https://github.com/chichunchen created https://github.com/llvm/llvm-project/pull/231690
The construct context for METADIRECTIVE and DECLARE VARIANT came from
enclosing MLIR operations. It recognized only a few operations, followed
operation nesting instead of source nesting, kept constructs outside an
enclosing TARGET, and used the body context for clause expressions and
loop bounds.
Build the context from the evaluation's source ancestors and from frames
for construct regions entered during lowering:
- A selected METADIRECTIVE replacement stands in for its source construct.
- TARGET discards the outer context.
- Constructs without a selector property still add a nesting level.
- Clause expressions see only enclosing constructs.
- Loop bounds see the leaves that precede the first loop-associated leaf.
- Specification expressions see no construct.
Composite DISTRIBUTE PARALLEL DO [SIMD] now evaluates DISTRIBUTE clauses
before PARALLEL, as in source nesting, and destroys their temporaries
once, after omp.parallel.
Split from #224431.
Assisted-by: GitHub Copilot (Claude Opus 5.5)
>From 98dc5e0cd143c5e4a45ee4463e49b373e0aaf966 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 13 Oct 2026 15:45:06 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Destroy composite num_threads temporaries
once
DISTRIBUTE PARALLEL DO and DISTRIBUTE PARALLEL DO SIMD evaluate the
PARALLEL clause operands before creating omp.parallel, using the
statement context of the dispatch. That context is finalized after the
returned omp.distribute, which is inside the parallel region, so every
thread destroyed the same num_threads temporary, for example for
num_threads(size(f())) where f returns an allocatable.
Evaluate these operands in a separate statement context and finalize it
after omp.parallel.
Fixes #230661
Assisted with Copilot and Claude Opus 5.5
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 36 ++++++++++++++-----
.../OpenMP/distribute-parallel-do-simd.f90 | 32 +++++++++++++++++
.../Lower/OpenMP/distribute-parallel-do.f90 | 32 +++++++++++++++++
3 files changed, 91 insertions(+), 9 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bdc6bb0073cff..57978826c5d01 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -5242,11 +5242,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
ConstructQueue::const_iterator parallelItem = std::next(distributeItem);
ConstructQueue::const_iterator doItem = std::next(parallelItem);
+ // Operands evaluated outside omp.parallel are destroyed once, after it.
+ lower::StatementContext parallelStmtCtx;
+
// Create parent omp.parallel first.
mlir::omp::ParallelOperands parallelClauseOps;
llvm::SmallVector<Object> parallelReductionObjects;
- genParallelClauses(converter, semaCtx, stmtCtx, parallelItem->clauses, loc,
- parallelClauseOps, parallelReductionObjects);
+ genParallelClauses(converter, semaCtx, parallelStmtCtx, parallelItem->clauses,
+ loc, parallelClauseOps, parallelReductionObjects);
DataSharingProcessor dsp(converter, semaCtx, doItem->clauses, eval,
/*shouldCollectPreDeterminedSymbols=*/true,
@@ -5259,8 +5262,10 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
parallelArgs.priv.vars = parallelClauseOps.privateVars;
parallelArgs.reduction.objects = parallelReductionObjects;
parallelArgs.reduction.vars = parallelClauseOps.reductionVars;
- genParallelOp(converter, symTable, semaCtx, eval, loc, queue, parallelItem,
- parallelClauseOps, parallelArgs, &dsp, /*isComposite=*/true);
+ mlir::omp::ParallelOp parallelOp =
+ genParallelOp(converter, symTable, semaCtx, eval, loc, queue,
+ parallelItem, parallelClauseOps, parallelArgs, &dsp,
+ /*isComposite=*/true);
// Clause processing.
mlir::omp::DistributeOperands distributeClauseOps;
@@ -5296,6 +5301,11 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
loopNestClauseOps, iv,
{{distributeOp, distributeArgs}, {wsloopOp, wsloopArgs}},
llvm::omp::Directive::OMPD_distribute_parallel_do, dsp);
+
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ fir::FirOpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointAfter(parallelOp);
+ parallelStmtCtx.finalizeAndPop();
return distributeOp;
}
@@ -5310,11 +5320,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
ConstructQueue::const_iterator doItem = std::next(parallelItem);
ConstructQueue::const_iterator simdItem = std::next(doItem);
+ // Operands evaluated outside omp.parallel are destroyed once, after it.
+ lower::StatementContext parallelStmtCtx;
+
// Create parent omp.parallel first.
mlir::omp::ParallelOperands parallelClauseOps;
llvm::SmallVector<Object> parallelReductionObjects;
- genParallelClauses(converter, semaCtx, stmtCtx, parallelItem->clauses, loc,
- parallelClauseOps, parallelReductionObjects);
+ genParallelClauses(converter, semaCtx, parallelStmtCtx, parallelItem->clauses,
+ loc, parallelClauseOps, parallelReductionObjects);
DataSharingProcessor parallelItemDSP(
converter, semaCtx, parallelItem->clauses, eval,
@@ -5328,9 +5341,9 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
parallelArgs.priv.vars = parallelClauseOps.privateVars;
parallelArgs.reduction.objects = parallelReductionObjects;
parallelArgs.reduction.vars = parallelClauseOps.reductionVars;
- genParallelOp(converter, symTable, semaCtx, eval, loc, queue, parallelItem,
- parallelClauseOps, parallelArgs, ¶llelItemDSP,
- /*isComposite=*/true);
+ mlir::omp::ParallelOp parallelOp = genParallelOp(
+ converter, symTable, semaCtx, eval, loc, queue, parallelItem,
+ parallelClauseOps, parallelArgs, ¶llelItemDSP, /*isComposite=*/true);
// Clause processing.
// Use a shared cache so that both wsloop and simd produce the same SSA
@@ -5405,6 +5418,11 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
{simdOp, simdArgs}},
llvm::omp::Directive::OMPD_distribute_parallel_do_simd,
simdItemDSP);
+
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ fir::FirOpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointAfter(parallelOp);
+ parallelStmtCtx.finalizeAndPop();
return distributeOp;
}
diff --git a/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90 b/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
index 266e5482e9756..44b6e342b99f0 100644
--- a/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
+++ b/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
@@ -42,6 +42,38 @@ subroutine distribute_parallel_do_simd_dist_schedule()
!$omp end teams
end subroutine distribute_parallel_do_simd_dist_schedule
+! A num_threads temporary is created outside omp.parallel and must be
+! destroyed once, after it.
+! CHECK-LABEL: func.func @_QPdistribute_parallel_do_simd_temp_cleanup(
+subroutine distribute_parallel_do_simd_temp_cleanup(n)
+ integer :: n
+ interface
+ function get(n) result(r)
+ integer, intent(in) :: n
+ integer, allocatable :: r(:)
+ end function
+ end interface
+ !$omp teams
+
+ ! CHECK: omp.teams {
+ ! CHECK: fir.call @_QPget(
+ ! CHECK: %[[NT_TMP:.*]] = hlfir.as_expr
+ ! CHECK: omp.parallel num_threads({{.*}}) {
+ ! CHECK-NOT: hlfir.destroy
+ ! CHECK: omp.distribute {
+ ! CHECK-NOT: hlfir.destroy
+ ! CHECK: omp.terminator
+ ! CHECK-NEXT: } {omp.composite}
+ ! CHECK: hlfir.destroy %[[NT_TMP]] :
+ ! CHECK-NEXT: omp.terminator
+ !$omp distribute parallel do simd num_threads(size(get(n)))
+ do index_ = 1, 10
+ end do
+ !$omp end distribute parallel do simd
+
+ !$omp end teams
+end subroutine distribute_parallel_do_simd_temp_cleanup
+
! CHECK-LABEL: func.func @_QPdistribute_parallel_do_simd_schedule(
subroutine distribute_parallel_do_simd_schedule()
!$omp teams
diff --git a/flang/test/Lower/OpenMP/distribute-parallel-do.f90 b/flang/test/Lower/OpenMP/distribute-parallel-do.f90
index cddf61647ead3..203e3c014faec 100644
--- a/flang/test/Lower/OpenMP/distribute-parallel-do.f90
+++ b/flang/test/Lower/OpenMP/distribute-parallel-do.f90
@@ -36,6 +36,38 @@ subroutine distribute_parallel_do_dist_schedule()
!$omp end teams
end subroutine distribute_parallel_do_dist_schedule
+! A num_threads temporary is created outside omp.parallel and must be
+! destroyed once, after it.
+! CHECK-LABEL: func.func @_QPdistribute_parallel_do_temp_cleanup(
+subroutine distribute_parallel_do_temp_cleanup(n)
+ integer :: n
+ interface
+ function get(n) result(r)
+ integer, intent(in) :: n
+ integer, allocatable :: r(:)
+ end function
+ end interface
+ !$omp teams
+
+ ! CHECK: omp.teams {
+ ! CHECK: fir.call @_QPget(
+ ! CHECK: %[[NT_TMP:.*]] = hlfir.as_expr
+ ! CHECK: omp.parallel num_threads({{.*}}) private({{.*}}) {
+ ! CHECK-NOT: hlfir.destroy
+ ! CHECK: omp.distribute {
+ ! CHECK-NOT: hlfir.destroy
+ ! CHECK: omp.terminator
+ ! CHECK-NEXT: } {omp.composite}
+ ! CHECK: hlfir.destroy %[[NT_TMP]] :
+ ! CHECK-NEXT: omp.terminator
+ !$omp distribute parallel do num_threads(size(get(n)))
+ do index_ = 1, 10
+ end do
+ !$omp end distribute parallel do
+
+ !$omp end teams
+end subroutine distribute_parallel_do_temp_cleanup
+
! CHECK-LABEL: func.func @_QPdistribute_parallel_do_schedule(
subroutine distribute_parallel_do_schedule()
!$omp teams
>From c3d1b637fab1bf96054f56a557b927171a3aac92 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 13 Oct 2026 17:31:24 -0500
Subject: [PATCH 2/2] [flang][OpenMP] Match variant selectors in the source
construct context
The construct context for METADIRECTIVE and DECLARE VARIANT came from
enclosing MLIR operations. It recognized only a few operations, followed
operation nesting instead of source nesting, kept constructs outside an
enclosing TARGET, and used the body context for clause expressions and
loop bounds.
Build the context from the evaluation's source ancestors and from frames
for construct regions entered during lowering:
- A selected METADIRECTIVE replacement stands in for its source construct.
- TARGET discards the outer context.
- Constructs without a selector property still add a nesting level.
- Clause expressions see only enclosing constructs.
- Loop bounds see the leaves that precede the first loop-associated leaf.
- Specification expressions see no construct.
Composite DISTRIBUTE PARALLEL DO [SIMD] now evaluates DISTRIBUTE clauses
before PARALLEL, as in source nesting, and destroys their temporaries
once, after omp.parallel.
Split from #224431.
Assisted-by: GitHub Copilot (Claude Opus 5.5)
---
flang/include/flang/Lower/AbstractConverter.h | 3 +
flang/include/flang/Semantics/openmp-utils.h | 9 +
flang/lib/Lower/Bridge.cpp | 8 +
flang/lib/Lower/OpenMP/Atomic.cpp | 3 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 89 ++-
flang/lib/Lower/OpenMP/Utils.cpp | 108 +++-
flang/lib/Lower/OpenMP/Utils.h | 41 +-
flang/lib/Semantics/openmp-utils.cpp | 15 +-
.../OpenMP/declare-variant-construct.f90 | 114 +++-
.../OpenMP/declare-variant-loop-bounds.f90 | 580 ++++++++++++++++++
.../declare-variant-metadirective-context.f90 | 303 +++++++++
.../OpenMP/declare-variant-spec-expr.f90 | 56 ++
.../OpenMP/distribute-parallel-do-simd.f90 | 10 +-
.../Lower/OpenMP/distribute-parallel-do.f90 | 10 +-
.../OpenMP/metadirective-device-kind.f90 | 17 +
.../OpenMP/metadirective-target-boundary.f90 | 76 +++
.../OpenMP/variant-scoring-any-sections.f90 | 81 +++
.../Lower/OpenMP/variant-source-context.f90 | 211 +++++++
18 files changed, 1678 insertions(+), 56 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/declare-variant-loop-bounds.f90
create mode 100644 flang/test/Lower/OpenMP/declare-variant-metadirective-context.f90
create mode 100644 flang/test/Lower/OpenMP/declare-variant-spec-expr.f90
create mode 100644 flang/test/Lower/OpenMP/metadirective-target-boundary.f90
create mode 100644 flang/test/Lower/OpenMP/variant-scoring-any-sections.f90
create mode 100644 flang/test/Lower/OpenMP/variant-source-context.f90
diff --git a/flang/include/flang/Lower/AbstractConverter.h b/flang/include/flang/Lower/AbstractConverter.h
index 40d94dc9f5bfd..25337bdb9e35d 100644
--- a/flang/include/flang/Lower/AbstractConverter.h
+++ b/flang/include/flang/Lower/AbstractConverter.h
@@ -337,6 +337,9 @@ class AbstractConverter {
/// Get the converter's current scope
virtual const Fortran::semantics::Scope &getCurrentScope() = 0;
+ /// Get the current PFT evaluation, or null when lowering a function prologue.
+ virtual pft::Evaluation *getCurrentEvaluation() = 0;
+
//===--------------------------------------------------------------------===//
// FIR/MLIR
//===--------------------------------------------------------------------===//
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index c31459f256f62..326d97714f64f 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -261,6 +261,15 @@ class OmpVariantMatchContext : public llvm::omp::OMPContext {
std::string features_;
};
+/// Append the construct context contributed by \p directive.
+///
+/// Combined and composite directives contribute their leaf constructs in
+/// source order. Informational directives do not contribute. Entering a TARGET
+/// construct discards the outer context, as required by the OpenMP
+/// construct-set definition.
+void AppendDirectiveContextTraits(llvm::omp::Directive directive,
+ llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits);
+
struct MetadirectiveCandidate {
MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
llvm::omp::VariantMatchInfo vmi, bool isExplicit,
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index e52e690ab4b64..2ceaacd374a74 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1165,6 +1165,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
void genEval(Fortran::lower::pft::Evaluation &eval,
bool unstructuredContext) override final {
+ Fortran::lower::pft::Evaluation *previousEval{evalPtr};
+ llvm::scope_exit restoreEval([&]() { evalPtr = previousEval; });
genFIR(eval, unstructuredContext);
}
@@ -1275,6 +1277,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
return bridge.getSemanticsContext().FindScope(currentPosition);
}
+ Fortran::lower::pft::Evaluation *getCurrentEvaluation() override final {
+ return evalPtr;
+ }
+
fir::FirOpBuilder &getFirOpBuilder() override final {
CHECK(builder && "builder is not set before calling getFirOpBuilder");
return *builder;
@@ -6485,6 +6491,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
/// Start translation of a function.
void startNewFunction(Fortran::lower::pft::FunctionLikeUnit &funit) {
assert(!builder && "expected nullptr");
+ // Specification expressions have no current executable evaluation.
+ evalPtr = nullptr;
bridge.fctCtx().pushScope();
bridge.cudaCleanupCtx().pushScope();
bridge.openAccCtx().pushScope();
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 5a38c97ee77a2..4a73fae32b934 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Atomic.h"
+#include "Utils.h"
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
@@ -559,6 +560,8 @@ void Fortran::lower::omp::lowerAtomic(
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
const parser::OmpDirectiveSpecification &dirSpec = construct.BeginDir();
+ mlir::SaveStateStack<OpenMPContextFrame> context{converter.getStateStack(),
+ eval, dirSpec.DirId()};
omp::List<omp::Clause> clauses = makeClauses(dirSpec.Clauses(), semaCtx);
lower::StatementContext stmtCtx;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 57978826c5d01..b58ee17e85e87 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1896,6 +1896,13 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
return {};
}();
+ // A loop body uses the collapsed DO evaluation, but its context belongs to
+ // the owning directive. This also covers intervening code between loops.
+ auto &contextEval =
+ info.outerCollapseEval ? *info.outerCollapseEval : info.eval;
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ info.converter.getStateStack(), contextEval, info.dir};
+
// Mark the earliest insertion point.
mlir::Operation *marker = insertMarker(firOpBuilder);
@@ -2046,6 +2053,8 @@ static void genBodyOfTargetDataOp(
fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
genEntryBlock(firOpBuilder, args.asEntryBlockArgs(), dataOp.getRegion());
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_target_data};
bindEntryBlockArgs(converter, dataOp, args);
auto argIface = llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(*dataOp);
llvm::SmallVector<const semantics::Symbol *> sourceUseDeviceAddrSyms{
@@ -2132,6 +2141,8 @@ static void genBodyOfTargetOp(
mlir::Region ®ion = targetOp.getRegion();
genEntryBlock(firOpBuilder, args.asEntryBlockArgs(), region);
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_target};
bindEntryBlockArgs(converter, targetOp, args);
if (HostEvalInfo *hostEvalInfo = getHostEvalInfoStackTop(converter))
hostEvalInfo->bindOperands(argIface.getHostEvalBlockArgs());
@@ -3019,6 +3030,9 @@ static void genCanonicalLoopNest(
// Computing the trip count must happen before entering the outermost loop
lower::pft::Evaluation *innermostEval = nestedEval;
for (std::size_t i = 0; i < ivs.size(); ++i) {
+ // Otherwise a later loop's bounds would see the previous loop's body.
+ mlir::SaveStateStack<LoopControlContext> loopControlContext{
+ converter.getStateStack(), eval};
if (innermostEval->getIf<parser::DoConstruct>()->IsDoConcurrent()) {
// OpenMP specifies DO CONCURRENT only with the `!omp loop` construct.
// Will need to add special cases for this combination.
@@ -5245,6 +5259,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
// Operands evaluated outside omp.parallel are destroyed once, after it.
lower::StatementContext parallelStmtCtx;
+ // Clause expressions follow source nesting even though the composite
+ // operations place PARALLEL outside DISTRIBUTE.
+ mlir::omp::DistributeOperands distributeClauseOps;
+ genDistributeClauses(converter, semaCtx, parallelStmtCtx,
+ distributeItem->clauses, loc, distributeClauseOps);
+ mlir::SaveStateStack<OpenMPContextFrame> distributeContext{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_distribute};
+
// Create parent omp.parallel first.
mlir::omp::ParallelOperands parallelClauseOps;
llvm::SmallVector<Object> parallelReductionObjects;
@@ -5266,12 +5288,10 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
genParallelOp(converter, symTable, semaCtx, eval, loc, queue,
parallelItem, parallelClauseOps, parallelArgs, &dsp,
/*isComposite=*/true);
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_parallel};
// Clause processing.
- mlir::omp::DistributeOperands distributeClauseOps;
- genDistributeClauses(converter, semaCtx, stmtCtx, distributeItem->clauses,
- loc, distributeClauseOps);
-
mlir::omp::WsloopOperands wsloopClauseOps;
llvm::SmallVector<Object> wsloopReductionObjects;
genWsloopClauses(converter, semaCtx, stmtCtx, doItem->clauses, loc,
@@ -5323,6 +5343,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
// Operands evaluated outside omp.parallel are destroyed once, after it.
lower::StatementContext parallelStmtCtx;
+ // Evaluate DISTRIBUTE clauses before entering its source context and then
+ // PARALLEL, as in the explicit nesting.
+ mlir::omp::DistributeOperands distributeClauseOps;
+ genDistributeClauses(converter, semaCtx, parallelStmtCtx,
+ distributeItem->clauses, loc, distributeClauseOps);
+ mlir::SaveStateStack<OpenMPContextFrame> distributeContext{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_distribute};
+
// Create parent omp.parallel first.
mlir::omp::ParallelOperands parallelClauseOps;
llvm::SmallVector<Object> parallelReductionObjects;
@@ -5344,16 +5372,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
mlir::omp::ParallelOp parallelOp = genParallelOp(
converter, symTable, semaCtx, eval, loc, queue, parallelItem,
parallelClauseOps, parallelArgs, ¶llelItemDSP, /*isComposite=*/true);
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_parallel};
// Clause processing.
// Use a shared cache so that both wsloop and simd produce the same SSA
// values for array/box reduction variables. See genCompositeDoSimd.
llvm::DenseMap<const semantics::Symbol *, mlir::Value> reductionVarCache;
- mlir::omp::DistributeOperands distributeClauseOps;
- genDistributeClauses(converter, semaCtx, stmtCtx, distributeItem->clauses,
- loc, distributeClauseOps);
-
mlir::omp::WsloopOperands wsloopClauseOps;
llvm::SmallVector<Object> wsloopReductionObjects;
genWsloopClauses(converter, semaCtx, stmtCtx, doItem->clauses, loc,
@@ -5361,8 +5387,14 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
mlir::omp::SimdOperands simdClauseOps;
llvm::SmallVector<Object> simdReductionObjects;
- genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
- simdReductionObjects, &reductionVarCache);
+ {
+ // SIMD clause expressions are inside DO, but loop control remains outside
+ // the loop constructs.
+ mlir::SaveStateStack<OpenMPContextFrame> doContext{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_do};
+ genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
+ simdReductionObjects, &reductionVarCache);
+ }
// Same as genCompositeDoSimd.
if (!simdClauseOps.linearVars.empty()) {
@@ -5442,8 +5474,13 @@ static mlir::omp::DistributeOp genCompositeDistributeSimd(
mlir::omp::SimdOperands simdClauseOps;
llvm::SmallVector<Object> simdReductionObjects;
- genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
- simdReductionObjects);
+ {
+ // SIMD clauses see DISTRIBUTE, but loop bounds are evaluated before it.
+ mlir::SaveStateStack<OpenMPContextFrame> distributeContext{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_distribute};
+ genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
+ simdReductionObjects);
+ }
DataSharingProcessor distributeItemDSP(
converter, semaCtx, distributeItem->clauses, eval,
@@ -5514,8 +5551,14 @@ static mlir::omp::WsloopOp genCompositeDoSimd(
mlir::omp::SimdOperands simdClauseOps;
llvm::SmallVector<Object> simdReductionObjects;
- genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
- simdReductionObjects, &reductionVarCache);
+ {
+ // SIMD clause expressions are inside DO, but loop control remains outside
+ // the loop constructs.
+ mlir::SaveStateStack<OpenMPContextFrame> doContext{
+ converter.getStateStack(), eval, llvm::omp::Directive::OMPD_do};
+ genSimdClauses(converter, semaCtx, simdItem->clauses, loc, simdClauseOps,
+ simdReductionObjects, &reductionVarCache);
+ }
// omp.simd writes back linear vars unconditionally, causing a race when
// inside a parallel region. Move them to wsloop which has proper last-iter
@@ -7445,8 +7488,7 @@ static void genMetadirective(lower::AbstractConverter &converter,
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
- collectEnclosingConstructTraits(builder.getInsertionBlock()->getParentOp(),
- constructTraits);
+ collectEnclosingConstructTraits(converter, &eval, constructTraits);
semantics::omp::OmpVariantMatchContext ompCtx =
makeVariantMatchContext(builder.getModule(), constructTraits);
@@ -7673,6 +7715,9 @@ static void genMetadirective(lower::AbstractConverter &converter,
if (marking == MetadirectiveLoopIVMarking::ThreadprivateIV)
TODO(variantLoc, "THREADPRIVATE loop iteration variable in "
"loop-associated METADIRECTIVE variant");
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, spec->DirId(),
+ /*isReplacement=*/true};
genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
queue.begin(), dsaGuard.getMarkedSymbols());
return;
@@ -7686,8 +7731,16 @@ static void genMetadirective(lower::AbstractConverter &converter,
TODO(variantLoc,
"METADIRECTIVE with both block- and loop-associated variants");
- genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
- queue.begin());
+ if (consumesBody) {
+ mlir::SaveStateStack<OpenMPContextFrame> context{
+ converter.getStateStack(), eval, spec->DirId(),
+ /*isReplacement=*/true};
+ genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+ queue.begin());
+ } else {
+ genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+ queue.begin());
+ }
// A standalone variant (Association::None, e.g. barrier/taskwait/nothing)
// does not consume the metadirective's nested block, so lower it here.
if (!consumesBody && eval.hasNestedEvaluations())
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index e05a3eab6fad6..ebfcf5ef158a6 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -823,6 +823,8 @@ void collectLoopRelatedInfo(
int64_t numCollapse, mlir::omp::LoopRelatedClauseOps &result,
llvm::SmallVectorImpl<const semantics::Symbol *> &iv) {
+ mlir::SaveStateStack<LoopControlContext> context{converter.getStateStack(),
+ eval};
fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
// Collect the loops to collapse.
@@ -1471,27 +1473,90 @@ semantics::omp::OmpVariantMatchContext makeVariantMatchContext(
}
void collectEnclosingConstructTraits(
- mlir::Operation *op,
+ AbstractConverter &converter, const pft::Evaluation *evaluation,
llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
- // Collect enclosing OpenMP operations so variants chosen by an outer
- // metadirective are part of this metadirective's context. For example, an
- // inner metadirective inside `target` and an outer-selected `parallel` must
- // be able to match construct={target, parallel}. The final reverse yields
- // outermost-to-innermost order as required by OMPContext.
- for (; op; op = op->getParentOp()) {
- if (mlir::isa<mlir::omp::WsloopOp>(op))
- constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for);
- if (mlir::isa<mlir::omp::ParallelOp>(op))
- constructTraits.push_back(
- llvm::omp::TraitProperty::construct_parallel_parallel);
- if (mlir::isa<mlir::omp::TeamsOp>(op))
- constructTraits.push_back(
- llvm::omp::TraitProperty::construct_teams_teams);
- if (mlir::isa<mlir::omp::TargetOp>(op))
- constructTraits.push_back(
- llvm::omp::TraitProperty::construct_target_target);
+ const auto *loopControl =
+ converter.getStateStack().getStackTop<LoopControlContext>();
+ // Use the loop owner's ancestors: host evaluation may have TARGET current
+ // while evaluating a nested loop's bounds.
+ if (loopControl)
+ evaluation = &loopControl->evaluation;
+
+ llvm::SmallVector<const OpenMPContextFrame *, 4> frames;
+ converter.getStateStack().stackWalk<OpenMPContextFrame>(
+ [&](OpenMPContextFrame &frame) {
+ frames.push_back(&frame);
+ return mlir::WalkResult::advance();
+ });
+ std::reverse(frames.begin(), frames.end());
+ llvm::SmallVector<bool, 4> usedFrames(frames.size(), false);
+
+ llvm::SmallVector<const pft::Evaluation *, 8> ancestors;
+ for (const pft::Evaluation *parent = evaluation ? evaluation->parentConstruct
+ : nullptr;
+ parent; parent = parent->parentConstruct) {
+ ancestors.push_back(parent);
+ }
+ std::reverse(ancestors.begin(), ancestors.end());
+
+ auto append = [&](llvm::omp::Directive directive) {
+ semantics::omp::AppendDirectiveContextTraits(directive, constructTraits);
+ };
+ auto getDirective =
+ [&](const pft::Evaluation &eval) -> std::optional<llvm::omp::Directive> {
+ std::optional<llvm::omp::Directive> directive;
+ if (const auto *omp = eval.getIf<parser::OpenMPConstruct>()) {
+ directive = parser::omp::GetOmpDirectiveName(*omp).v;
+ } else if (const auto *decl =
+ eval.getIf<parser::OpenMPDeclarativeConstruct>()) {
+ // A metadirective before the first executable statement is declarative.
+ if (std::holds_alternative<parser::OmpMetadirectiveDirective>(decl->u))
+ directive = llvm::omp::Directive::OMPD_metadirective;
+ }
+ if (directive == llvm::omp::Directive::OMPD_metadirective)
+ for (const OpenMPContextFrame *frame : frames)
+ if (&frame->evaluation == &eval && frame->isReplacement)
+ return frame->directive;
+ return directive;
+ };
+ for (const pft::Evaluation *ancestor : ancestors) {
+ std::optional<llvm::omp::Directive> directive = getDirective(*ancestor);
+ if (!directive)
+ continue;
+ // An ancestor supplies the full source context, including constituents
+ // whose bodies also have active frames. Count each construct only once.
+ for (auto [index, frame] : llvm::enumerate(frames))
+ if (&frame->evaluation == ancestor)
+ usedFrames[index] = true;
+ append(*directive);
+ }
+
+ // Use entered frames for clauses. Loop bounds use the source prefix below,
+ // which is available even before the owner's frames are entered.
+ for (auto [index, frame] : llvm::enumerate(frames)) {
+ if (usedFrames[index] || frame->isReplacement)
+ continue;
+ if (loopControl && &frame->evaluation == evaluation)
+ continue;
+ append(frame->directive);
+ }
+
+ if (!loopControl)
+ return;
+ std::optional<llvm::omp::Directive> directive = getDirective(*evaluation);
+ if (!directive)
+ return;
+ // Bounds precede the first loop-associated constituent in source order.
+ // TARGET TEAMS DISTRIBUTE PARALLEL DO therefore retains TARGET and TEAMS.
+ for (llvm::omp::Directive leaf :
+ llvm::omp::getLeafConstructsOrSelf(*directive)) {
+ llvm::omp::Association association =
+ llvm::omp::getDirectiveAssociation(leaf);
+ if (association == llvm::omp::Association::LoopNest ||
+ association == llvm::omp::Association::LoopSequence)
+ break;
+ append(leaf);
}
- std::reverse(constructTraits.begin(), constructTraits.end());
}
const semantics::Symbol *
@@ -1535,9 +1600,8 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
}
llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
- collectEnclosingConstructTraits(
- converter.getFirOpBuilder().getInsertionBlock()->getParentOp(),
- constructTraits);
+ collectEnclosingConstructTraits(converter, converter.getCurrentEvaluation(),
+ constructTraits);
semantics::omp::OmpVariantMatchContext ompCtx =
makeVariantMatchContext(converter.getModuleOp(), constructTraits);
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 4560c9df349b2..36ea143c5e133 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -15,6 +15,7 @@
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/Value.h"
+#include "mlir/Support/StateStack.h"
#include "llvm/Frontend/OpenMP/OMPContext.h"
#include "llvm/Support/CommandLine.h"
#include <cstdint>
@@ -51,6 +52,36 @@ class AbstractConverter;
namespace omp {
+class OpenMPContextFrame
+ : public mlir::StateStackFrameBase<OpenMPContextFrame> {
+public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(OpenMPContextFrame)
+
+ OpenMPContextFrame(const pft::Evaluation &evaluation,
+ llvm::omp::Directive directive, bool isReplacement = false)
+ : evaluation{evaluation}, directive{directive},
+ isReplacement{isReplacement} {}
+
+ const pft::Evaluation &evaluation;
+ llvm::omp::Directive directive;
+ // A replacement records the selected directive for nested evaluations.
+ // Other frames record constructs whose regions have already been entered.
+ bool isReplacement;
+};
+
+// Original loop control belongs outside the outermost loop-associated
+// constituent, including for composites that emit inner regions first.
+class LoopControlContext
+ : public mlir::StateStackFrameBase<LoopControlContext> {
+public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LoopControlContext)
+
+ explicit LoopControlContext(const pft::Evaluation &evaluation)
+ : evaluation(evaluation) {}
+
+ const pft::Evaluation &evaluation;
+};
+
struct DeclareTargetCaptureInfo {
mlir::omp::DeclareTargetCaptureClause clause;
bool automap = false;
@@ -258,12 +289,12 @@ std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices(
Fortran::lower::AbstractConverter &converter, const omp::Object &object,
Fortran::lower::StatementContext &stmtCtx, mlir::Location loc);
-/// Walk the already-emitted MLIR parent operations starting from \p op and
-/// collect the implied OpenMP construct traits in outermost-to-innermost
-/// order. Used by metadirective lowering and declare-variant call resolution
-/// to build the `ConstructTraits` of an `OMPContext`.
+/// Collect the source OpenMP constructs enclosing \p evaluation in
+/// outermost-to-innermost order. Active metadirective replacements substitute
+/// for their METADIRECTIVE source constructs. A null evaluation uses only
+/// active context frames.
void collectEnclosingConstructTraits(
- mlir::Operation *op,
+ AbstractConverter &converter, const pft::Evaluation *evaluation,
llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits);
/// Return true when \p module is being compiled for an AMDGPU device or all of
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 044be0be2d4b9..f8d5a38cc077c 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -2975,7 +2975,10 @@ OmpVariantMatchContext::OmpVariantMatchContext(bool isDeviceCompilation,
std::move(targetOffloadTriple), /*DeviceNum=*/-1),
features_(std::move(targetFeatures)) {
for (llvm::omp::TraitProperty trait : constructTraits) {
- addTrait(trait);
+ if (trait == llvm::omp::TraitProperty::invalid)
+ addUnknownConstruct();
+ else
+ addTrait(trait);
}
}
@@ -2988,6 +2991,16 @@ OmpVariantMatchContext::OmpVariantMatchContext(const SemanticsContext &context,
: context.langOptions().OMPTargetTriples.front(),
context.targetFeatures(), constructTraits) {}
+void AppendDirectiveContextTraits(llvm::omp::Directive directive,
+ llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
+ for (llvm::omp::TraitProperty trait :
+ llvm::omp::getConstructTraits(directive)) {
+ if (trait == llvm::omp::TraitProperty::construct_target_target)
+ constructTraits.clear();
+ constructTraits.push_back(trait);
+ }
+}
+
bool OmpVariantMatchContext::matchesISATrait(llvm::StringRef rawString) const {
// The target feature list is a comma-separated string such as
// "+sse,+avx2,-foo"; an ISA trait matches when its "+" form is present.
diff --git a/flang/test/Lower/OpenMP/declare-variant-construct.f90 b/flang/test/Lower/OpenMP/declare-variant-construct.f90
index ca72b5c8e47af..06cd2b581b789 100644
--- a/flang/test/Lower/OpenMP/declare-variant-construct.f90
+++ b/flang/test/Lower/OpenMP/declare-variant-construct.f90
@@ -1,8 +1,8 @@
! RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s
-! DECLARE VARIANT callee resolution with combined/composite construct
-! selectors. The bases and their variants are sibling module procedures, so
+! DECLARE VARIANT callee resolution with construct selectors, including
+! combined/composite selectors. Bases and variants are sibling procedures, so
! each variant is accessible at every reference to its base.
module m
@@ -10,6 +10,79 @@ module m
subroutine base_tt
!$omp declare variant (base_tt:vsub_tt) match (construct={target teams})
end subroutine base_tt
+
+ subroutine base_device_weight
+ !$omp declare variant (vsub_cpu) match(device={kind(cpu)})
+ !$omp declare variant (vsub_parallel) match(construct={parallel})
+ end subroutine
+ subroutine vsub_cpu
+ end subroutine
+ subroutine vsub_parallel
+ end subroutine
+
+ ! CPU scores 5 in this context, beating PARALLEL's score of 3.
+ ! CHECK-LABEL: func.func @_QMmPtest_device_weight()
+ ! CHECK: omp.parallel
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMmPvsub_parallel
+ ! CHECK: fir.call @_QMmPvsub_cpu()
+ ! CHECK-NOT: fir.call @_QMmPvsub_parallel
+ ! CHECK: return
+ subroutine test_device_weight
+ !$omp parallel
+ !$omp parallel
+ call base_device_weight()
+ !$omp end parallel
+ !$omp end parallel
+ end subroutine
+
+ subroutine base_task_weight
+ !$omp declare variant (vsub_cpu) match(device={kind(cpu)})
+ !$omp declare variant (vsub_score3) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ end subroutine
+ subroutine vsub_score3
+ end subroutine
+
+ ! TASK has no construct-selector property, but contributes to the device
+ ! weight. In PARALLEL > TASK, CPU scores 5 and beats score(3)'s total of 4.
+ ! CHECK-LABEL: func.func @_QMmPtest_task_weight()
+ ! CHECK: omp.parallel
+ ! CHECK: omp.task
+ ! CHECK-NOT: fir.call @_QMmPvsub_score3
+ ! CHECK: fir.call @_QMmPvsub_cpu()
+ ! CHECK-NOT: fir.call @_QMmPvsub_score3
+ ! CHECK: return
+ subroutine test_task_weight
+ !$omp parallel
+ !$omp task
+ call base_task_weight()
+ !$omp end task
+ !$omp end parallel
+ end subroutine
+
+ subroutine base_subset
+ !$omp declare variant (vsub_subset) &
+ !$omp& match(implementation={vendor(score(100): llvm)})
+ !$omp declare variant (vsub_superset) &
+ !$omp& match(implementation={vendor(score(1): llvm)}, &
+ !$omp& user={condition(.true.)})
+ end subroutine
+ subroutine vsub_subset
+ end subroutine
+ subroutine vsub_superset
+ end subroutine
+
+ ! A strict subset has score zero before candidates are ranked, even when its
+ ! explicit score would otherwise be higher.
+ ! CHECK-LABEL: func.func @_QMmPtest_strict_subset()
+ ! CHECK-NOT: fir.call @_QMmPvsub_subset
+ ! CHECK: fir.call @_QMmPvsub_superset()
+ ! CHECK-NOT: fir.call @_QMmPvsub_subset
+ subroutine test_strict_subset
+ call base_subset()
+ end subroutine
+
subroutine vsub_tt
end subroutine vsub_tt
@@ -49,6 +122,26 @@ end subroutine vsub_lo
subroutine vsub_hi
end subroutine vsub_hi
+ subroutine base_repeated
+ !$omp declare variant (vsub_lo) &
+ !$omp& match(user={condition(score(1): .true.)})
+ !$omp declare variant (vsub_par) match(construct={parallel})
+ end subroutine base_repeated
+
+ ! The inner PARALLEL raises the construct score above the user score.
+ ! CHECK-LABEL: func.func @_QMmPtest_repeated_parallel(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMmPvsub_lo
+ ! CHECK: fir.call @_QMmPvsub_par()
+ subroutine test_repeated_parallel
+ !$omp parallel
+ !$omp parallel
+ call base_repeated()
+ !$omp end parallel
+ !$omp end parallel
+ end subroutine test_repeated_parallel
+
! The combined directive selector decomposes to {target, teams}; it matches
! only when both constructs enclose the call.
@@ -157,4 +250,21 @@ end subroutine test_rank_parallel_only
subroutine test_score_ranking
call base_score()
end subroutine test_score_ranking
+
+ ! TARGET hides the outer PARALLEL during callee selection as well.
+
+ ! CHECK-LABEL: func.func @_QMmPtest_target_boundary(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.target
+ ! CHECK: fir.call @_QMmPbase_tp2()
+ ! CHECK-NEXT: fir.call @_QMmPbase_rank()
+ ! CHECK: return
+ subroutine test_target_boundary
+ !$omp parallel
+ !$omp target
+ call base_tp2()
+ call base_rank()
+ !$omp end target
+ !$omp end parallel
+ end subroutine test_target_boundary
end module m
diff --git a/flang/test/Lower/OpenMP/declare-variant-loop-bounds.f90 b/flang/test/Lower/OpenMP/declare-variant-loop-bounds.f90
new file mode 100644
index 0000000000000..5f80a3815cf6e
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-variant-loop-bounds.f90
@@ -0,0 +1,580 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | \
+! RUN: FileCheck %s --check-prefixes=CHECK,TARGET,HOST \
+! RUN: --implicit-check-not='fir.call @_QMloop_contextPparallel_count'
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-fir %s -o - | \
+! RUN: FileCheck %s --check-prefixes=CHECK,TARGET,HOST \
+! RUN: --implicit-check-not='fir.call @_QMloop_contextPparallel_count'
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-hlfir \
+! RUN: -fopenmp-is-target-device %s -o - | \
+! RUN: FileCheck %s --check-prefixes=TARGET,DEVICE
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-fir \
+! RUN: -fopenmp-is-target-device %s -o - | \
+! RUN: FileCheck %s --check-prefixes=TARGET,DEVICE
+
+module loop_context
+contains
+ pure integer function teams_bound(n)
+ integer, intent(in) :: n
+ teams_bound = n
+ end function
+
+ pure integer function parallel_bound(n)
+ integer, intent(in) :: n
+ parallel_bound = n
+ end function
+
+ pure integer function do_bound(n)
+ integer, intent(in) :: n
+ do_bound = n
+ end function
+
+ pure integer function bound(n)
+ integer, intent(in) :: n
+ !$omp declare variant(teams_bound) match(construct={teams})
+ !$omp declare variant(parallel_bound) match(construct={parallel})
+ !$omp declare variant(do_bound) match(construct={do})
+ bound = n
+ end function
+
+ ! NUM_THREADS is evaluated outside PARALLEL. The DO bound is inside
+ ! PARALLEL but outside DO, and the loop body is inside both constructs.
+ ! CHECK-LABEL: func.func @_QMloop_contextPcombined(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine combined(n, a)
+ integer, intent(in) :: n
+ integer :: a(n), i
+ !$omp parallel do num_threads(bound(n))
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ end subroutine
+
+ ! Explicit nesting must select the same variants as the combined spelling.
+ ! CHECK-LABEL: func.func @_QMloop_contextPexplicit_nest(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine explicit_nest(n, a)
+ integer, intent(in) :: n
+ integer :: a(n), i
+ !$omp parallel num_threads(bound(n))
+ !$omp do
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ !$omp end parallel
+ end subroutine
+
+ ! Original bounds are evaluated in the context before DISTRIBUTE.
+ ! Emitting the composite PARALLEL region first must not affect selection.
+ ! CHECK-LABEL: func.func @_QMloop_contextPcomposite(
+ ! CHECK: omp.teams
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: omp.distribute
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine composite(n, a)
+ integer, intent(in) :: n
+ integer :: a(n), i
+ !$omp teams distribute parallel do
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ end subroutine
+
+ ! The SIMD composite follows a separate lowering path.
+ ! CHECK-LABEL: func.func @_QMloop_contextPcomposite_simd(
+ ! CHECK: omp.teams
+ ! CHECK: omp.parallel
+ ! CHECK-NOT: fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: omp.distribute
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine composite_simd(n, a)
+ integer, intent(in) :: n
+ integer :: a(n), i
+ !$omp teams distribute parallel do simd
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ end subroutine
+
+ ! A standalone DO contributes to its body, but not its bounds or later calls.
+ ! CHECK-LABEL: func.func @_QMloop_contextPstandalone_do(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine standalone_do(n, a)
+ integer, intent(in) :: n
+ integer :: a(n), i
+ !$omp do
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ a(n) = bound(n)
+ end subroutine
+
+ pure integer function cpu_count(n)
+ integer, intent(in) :: n
+ cpu_count = n
+ end function
+
+ pure integer function scored_count(n)
+ integer, intent(in) :: n
+ scored_count = n + 1
+ end function
+
+ pure integer function parallel_count(n)
+ integer, intent(in) :: n
+ parallel_count = n + 2
+ end function
+
+ pure logical function do_pred(n)
+ integer, intent(in) :: n
+ do_pred = n > 0
+ end function
+
+ pure logical function pred(n)
+ integer, intent(in) :: n
+ !$omp declare variant(do_pred) match(construct={do})
+ pred = n > 0
+ end function
+
+ pure integer function thread_count(n)
+ integer, intent(in) :: n
+ !$omp declare variant(cpu_count) match(device={kind(cpu)})
+ !$omp declare variant(parallel_count) &
+ !$omp& match(construct={parallel}, device={kind(cpu)})
+ !$omp declare variant(scored_count) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ thread_count = n
+ end function
+
+ ! DIST_SCHEDULE sees only TEAMS. NUM_THREADS sees TEAMS, DISTRIBUTE, so
+ ! CPU scores 5 and beats the vendor score of 4. A leaked PARALLEL would
+ ! make parallel_count a strict superset of cpu_count. Bounds see only TEAMS.
+ ! CHECK-LABEL: func.func @_QMloop_contextPcomposite_clauses(
+ ! CHECK: omp.teams
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: fir.call @_QMloop_contextPcpu_count(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: omp.distribute
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine composite_clauses(n, a)
+ integer :: n, a(n), i
+ !$omp teams distribute parallel do dist_schedule(static, bound(n)) &
+ !$omp& num_threads(thread_count(n))
+ do i = bound(1), bound(n), bound(1)
+ a(i) = bound(n)
+ end do
+ end subroutine
+
+ ! The SIMD composite must evaluate its clauses in the same source contexts.
+ ! CHECK-LABEL: func.func @_QMloop_contextPcomposite_simd_clauses(
+ ! CHECK: omp.teams
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: fir.call @_QMloop_contextPcpu_count(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMloop_contextPdo_pred(
+ ! CHECK: fir.call @_QMloop_contextPscored_count(
+ ! CHECK: omp.distribute
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine composite_simd_clauses(n, a)
+ integer :: n, a(n), i
+ !$omp teams distribute parallel do simd dist_schedule(static, bound(n)) &
+ !$omp& num_threads(thread_count(n)) if(simd: pred(n))
+ do i = 1, thread_count(n)
+ a(i) = bound(n)
+ end do
+ end subroutine
+
+ ! SIMD clauses see DO, but the following loop bound and post-loop call do not.
+ ! CHECK-LABEL: func.func @_QMloop_contextPdo_simd_clauses(
+ ! CHECK: fir.call @_QMloop_contextPdo_pred(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPpred(
+ ! CHECK: return
+ subroutine do_simd_clauses(n, a, flag)
+ integer :: n, a(n), i
+ logical :: flag
+ !$omp do simd if(simd: pred(n))
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ flag = pred(n)
+ end subroutine
+
+ ! The explicit DO/SIMD nesting also exposes DO to the SIMD clause.
+ ! CHECK-LABEL: func.func @_QMloop_contextPnested_simd_clauses(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_pred(
+ ! CHECK: omp.simd
+ ! CHECK: return
+ subroutine nested_simd_clauses(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp do
+ do i = 1, n
+ !$omp simd if(simd: pred(n))
+ do j = 1, n
+ a(i, j) = i + j
+ end do
+ end do
+ end subroutine
+ ! SIMD clauses see TEAMS and DISTRIBUTE (CPU scores 5). The bound and
+ ! post-loop call see only TEAMS (CPU scores 3, losing to vendor score 4).
+ ! CHECK-LABEL: func.func @_QMloop_contextPdistribute_simd_clauses(
+ ! CHECK: omp.teams
+ ! CHECK: fir.call @_QMloop_contextPcpu_count(
+ ! CHECK: fir.call @_QMloop_contextPscored_count(
+ ! CHECK: omp.distribute
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPscored_count(
+ ! CHECK: return
+ subroutine distribute_simd_clauses(n, a)
+ integer :: n, a(n), i
+ !$omp teams
+ !$omp distribute simd if(simd: thread_count(n) > 0)
+ do i = 1, thread_count(n)
+ a(i) = i
+ end do
+ i = thread_count(n)
+ !$omp end teams
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMloop_contextPnested_distribute_simd_clauses(
+ ! CHECK: omp.teams
+ ! CHECK: omp.distribute
+ ! CHECK: fir.call @_QMloop_contextPcpu_count(
+ ! CHECK: omp.simd
+ ! CHECK: return
+ subroutine nested_distribute_simd_clauses(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp teams
+ !$omp distribute
+ do i = 1, n
+ !$omp simd if(simd: thread_count(n) > 0)
+ do j = 1, n
+ a(i, j) = i + j
+ end do
+ end do
+ !$omp end teams
+ end subroutine
+
+ ! The same original-bound rule applies without PARALLEL in the composite.
+ ! CHECK-LABEL: func.func @_QMloop_contextPteams_distribute(
+ ! CHECK: omp.teams
+ ! CHECK: fir.call @_QMloop_contextPteams_bound(
+ ! CHECK: fir.call @_QMloop_contextPscored_count(
+ ! CHECK: omp.distribute
+ ! CHECK: fir.call @_QMloop_contextPcpu_count(
+ ! CHECK: fir.call @_QMloop_contextPscored_count(
+ ! CHECK: return
+ subroutine teams_distribute(n, a)
+ integer :: n, a(n), i
+ !$omp teams
+ !$omp distribute
+ do i = bound(1), thread_count(n)
+ a(i) = thread_count(n)
+ end do
+ i = thread_count(n)
+ !$omp end teams
+ end subroutine
+
+ ! Retain PARALLEL preceding DO in the same combined directive.
+ ! CHECK-LABEL: func.func @_QMloop_contextPparallel_do_simd(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMloop_contextPdo_pred(
+ ! CHECK: fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: return
+ subroutine parallel_do_simd(n, a)
+ integer :: n, a(n), i
+ !$omp parallel do simd if(simd: pred(n))
+ do i = 1, bound(n)
+ a(i) = bound(n)
+ end do
+ i = bound(n)
+ end subroutine
+
+ ! An enclosing DO belongs to a different source loop and must remain active
+ ! for the inner SIMD bounds. Only the inner directive's frames are filtered.
+ ! CHECK-LABEL: func.func @_QMloop_contextPenclosing_do(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: return
+ subroutine enclosing_do(n, a)
+ integer :: n, a(n,n), i, j
+ !$omp do
+ do i = 1, n
+ !$omp simd
+ do j = 1, bound(n)
+ a(i,j) = bound(n)
+ end do
+ end do
+ i = bound(n)
+ end subroutine
+ ! Lastprivate re-evaluates the bound inside WSLOOP for the copy-back test.
+ ! Both evaluations belong to the original loop control, outside DO.
+ ! CHECK-LABEL: func.func @_QMloop_contextPlastprivate_bound(
+ ! CHECK: fir.call @_QMloop_contextPbound(
+ ! CHECK: omp.wsloop
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: %[[COPY_UB:.*]] = fir.call @_QMloop_contextPbound(
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: arith.cmpi sgt, %{{[^,]+}}, %[[COPY_UB]]
+ ! CHECK: fir.if
+ ! CHECK: {{hlfir.assign|fir.store}}
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine lastprivate_bound(n, x)
+ integer :: n, x, i
+ !$omp do lastprivate(x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ end subroutine
+
+ ! Keep the entered PARALLEL constituent when re-evaluating the bound.
+ ! CHECK-LABEL: func.func @_QMloop_contextPlastprivate_combined(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: omp.wsloop
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: %[[PAR_UB:.*]] = fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: arith.cmpi sgt, %{{[^,]+}}, %[[PAR_UB]]
+ ! CHECK: fir.if
+ ! CHECK: {{hlfir.assign|fir.store}}
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine lastprivate_combined(n, x)
+ integer :: n, x, i
+ !$omp parallel do lastprivate(x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ end subroutine
+
+ ! A separately enclosing PARALLEL must also remain in the bound context.
+ ! CHECK-LABEL: func.func @_QMloop_contextPlastprivate_nested(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: omp.wsloop
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: %[[NESTED_UB:.*]] = fir.call @_QMloop_contextPparallel_bound(
+ ! CHECK: arith.cmpi sgt, %{{[^,]+}}, %[[NESTED_UB]]
+ ! CHECK: fir.if
+ ! CHECK: {{hlfir.assign|fir.store}}
+ ! CHECK-NOT: fir.call @_QMloop_contextPdo_bound(
+ ! CHECK: return
+ subroutine lastprivate_nested(n, x)
+ integer :: n, x, i
+ !$omp parallel
+ !$omp do lastprivate(x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ !$omp end parallel
+ end subroutine
+
+end module
+
+! Host-evaluated bounds and lastprivate copy-back must use the same source
+! context, before the first loop-associated constituent.
+module host_bounds
+contains
+ pure integer function target_bound(n)
+ integer, intent(in) :: n
+ target_bound = n + 2
+ end function
+
+ pure integer function parallel_bound(n)
+ integer, intent(in) :: n
+ parallel_bound = n + 1
+ end function
+
+ pure integer function teams_bound(n)
+ integer, intent(in) :: n
+ teams_bound = n + 3
+ end function
+
+ pure integer function do_bound(n)
+ integer, intent(in) :: n
+ do_bound = n + 10
+ end function
+
+ pure integer function bound(n)
+ integer, intent(in) :: n
+ !$omp declare variant(target_bound) match(construct={target})
+ !$omp declare variant(parallel_bound) match(construct={target, parallel})
+ !$omp declare variant(teams_bound) match(construct={target, teams})
+ !$omp declare variant(do_bound) match(construct={target, parallel, do})
+ bound = n
+ end function
+
+ pure integer function parallel_depth(n)
+ integer, intent(in) :: n
+ parallel_depth = n + 1
+ end function
+
+ pure integer function scored_depth(n)
+ integer, intent(in) :: n
+ scored_depth = n
+ end function
+
+ pure integer function depth_bound(n)
+ integer, intent(in) :: n
+ !$omp declare variant(parallel_depth) match(construct={parallel})
+ !$omp declare variant(scored_depth) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ depth_bound = n
+ end function
+
+ ! Bounds see TARGET and PARALLEL before DO.
+ ! TARGET-LABEL: func.func @_QMhost_boundsPcombined(
+ ! HOST: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.target
+ ! TARGET: omp.parallel
+ ! DEVICE: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.wsloop
+ ! TARGET: %[[UB:.*]] = fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: arith.cmpi sgt, %{{[^,]+}}, %[[UB]]
+ ! TARGET: fir.if
+ ! TARGET: {{hlfir.assign|fir.store}}
+ ! TARGET: return
+ subroutine combined(n, x)
+ integer :: n, x, i
+ !$omp target parallel do lastprivate(x) map(tofrom: x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ end subroutine
+
+ ! Bounds see the enclosing TARGET and the combined directive's PARALLEL.
+ ! TARGET-LABEL: func.func @_QMhost_boundsPpartial_nest(
+ ! HOST: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.target
+ ! TARGET: omp.parallel
+ ! DEVICE: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.wsloop
+ ! TARGET: %[[UB:.*]] = fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: arith.cmpi sgt, %{{[^,]+}}, %[[UB]]
+ ! TARGET: fir.if
+ ! TARGET: {{hlfir.assign|fir.store}}
+ ! TARGET: return
+ subroutine partial_nest(n, x)
+ integer :: n, x, i
+ !$omp target map(tofrom: x)
+ !$omp parallel do lastprivate(x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ !$omp end target
+ end subroutine
+
+ ! Bounds see both source ancestors while host evaluation has TARGET current.
+ ! TARGET-LABEL: func.func @_QMhost_boundsPexplicit_nest(
+ ! HOST: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.target
+ ! TARGET: omp.parallel
+ ! DEVICE: fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: omp.wsloop
+ ! TARGET: %[[UB:.*]] = fir.call @_QMhost_boundsPparallel_bound(
+ ! TARGET: arith.cmpi sgt, %{{[^,]+}}, %[[UB]]
+ ! TARGET: fir.if
+ ! TARGET: {{hlfir.assign|fir.store}}
+ ! TARGET: return
+ subroutine explicit_nest(n, x)
+ integer :: n, x, i
+ !$omp target map(tofrom: x)
+ !$omp parallel
+ !$omp do lastprivate(x)
+ do i = 1, bound(n)
+ x = i
+ end do
+ !$omp end parallel
+ !$omp end target
+ end subroutine
+
+ ! Bounds see TARGET and TEAMS before DISTRIBUTE.
+ ! TARGET-LABEL: func.func @_QMhost_boundsPcomposite(
+ ! HOST: fir.call @_QMhost_boundsPteams_bound(
+ ! TARGET: omp.target
+ ! TARGET: omp.teams
+ ! TARGET: omp.parallel
+ ! DEVICE: fir.call @_QMhost_boundsPteams_bound(
+ ! TARGET: omp.distribute
+ ! TARGET: omp.wsloop
+ ! TARGET: return
+ subroutine composite(n, a)
+ integer :: n, a(:), i
+ !$omp target teams distribute parallel do map(tofrom: a)
+ do i = 1, bound(n)
+ a(i) = i
+ end do
+ end subroutine
+
+ ! PARALLEL scores 3 and the vendor scores 4 in TARGET, PARALLEL.
+ ! Counting PARALLEL twice raises its score to at least 5, so it wins.
+ ! TARGET-LABEL: func.func @_QMhost_boundsPprefix_depth(
+ ! HOST: fir.call @_QMhost_boundsPscored_depth(
+ ! TARGET: omp.target
+ ! TARGET: omp.parallel
+ ! DEVICE: fir.call @_QMhost_boundsPscored_depth(
+ ! TARGET: omp.wsloop
+ ! TARGET: %[[UB:.*]] = fir.call @_QMhost_boundsPscored_depth(
+ ! TARGET: arith.cmpi sgt, %{{[^,]+}}, %[[UB]]
+ ! TARGET: fir.if
+ ! TARGET: {{hlfir.assign|fir.store}}
+ ! TARGET: return
+ subroutine prefix_depth(n, x)
+ integer :: n, x, i
+ !$omp target map(tofrom: x)
+ !$omp parallel do lastprivate(x)
+ do i = 1, depth_bound(n)
+ x = i
+ end do
+ !$omp end target
+ end subroutine
+end module
diff --git a/flang/test/Lower/OpenMP/declare-variant-metadirective-context.f90 b/flang/test/Lower/OpenMP/declare-variant-metadirective-context.f90
new file mode 100644
index 0000000000000..030aa1cd1ac4d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-variant-metadirective-context.f90
@@ -0,0 +1,303 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | \
+! RUN: FileCheck %s --implicit-check-not=omp.taskyield \
+! RUN: --implicit-check-not='fir.call @_QMloop_depthPdo_value'
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-fir %s -o - | \
+! RUN: FileCheck %s --implicit-check-not=omp.taskyield \
+! RUN: --implicit-check-not='fir.call @_QMloop_depthPdo_value'
+
+module selected_context
+contains
+ pure integer function in_parallel(n)
+ integer, intent(in) :: n
+ in_parallel = n + 1
+ end function
+
+ pure integer function in_do(n)
+ integer, intent(in) :: n
+ in_do = n + 2
+ end function
+
+ pure integer function value(n)
+ integer, intent(in) :: n
+ !$omp declare variant(in_parallel) match(construct={parallel})
+ !$omp declare variant(in_do) match(construct={do})
+ value = n
+ end function
+
+ ! NUM_THREADS is evaluated outside PARALLEL for either spelling. The body
+ ! sees PARALLEL, and leaving the region restores the enclosing context.
+ ! CHECK-LABEL: func.func @_QMselected_contextPdirect_parallel(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMselected_contextPin_parallel(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine direct_parallel(n, a)
+ integer :: n, a(n)
+ !$omp parallel num_threads(value(n))
+ a(1) = value(n)
+ !$omp end parallel
+ a(n) = value(n)
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMselected_contextPstatic_parallel(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMselected_contextPin_parallel(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine static_parallel(n, a)
+ integer :: n, a(n)
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel num_threads(value(n))) &
+ !$omp& otherwise(nothing)
+ a(1) = value(n)
+ !$omp end metadirective
+ a(n) = value(n)
+ end subroutine
+
+ ! Each runtime arm uses its own context. NOTHING adds no construct.
+ ! CHECK-LABEL: func.func @_QMselected_contextPruntime_parallel(
+ ! CHECK: fir.if
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.parallel
+ ! CHECK: fir.call @_QMselected_contextPin_parallel(
+ ! CHECK: } else {
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine runtime_parallel(flag, n, a)
+ logical :: flag
+ integer :: n, a(n)
+ !$omp begin metadirective &
+ !$omp& when(user={condition(flag)}: parallel num_threads(value(n))) &
+ !$omp& otherwise(nothing)
+ a(1) = value(n)
+ !$omp end metadirective
+ a(n) = value(n)
+ end subroutine
+
+ ! The following DO remains a sibling of the standalone metadirective in
+ ! the source tree, but its body must still see the selected construct.
+ ! CHECK-LABEL: func.func @_QMselected_contextPstatic_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine static_do(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective when(implementation={vendor(llvm)}: do) &
+ !$omp& otherwise(nothing)
+ do i = 1, value(n)
+ a(i) = value(n)
+ end do
+ a(n) = value(n)
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMselected_contextPruntime_do(
+ ! CHECK: fir.if
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: } else {
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine runtime_do(flag, n, a)
+ logical :: flag
+ integer :: n, a(n), i
+ !$omp metadirective when(user={condition(flag)}: do) otherwise(nothing)
+ do i = 1, value(n)
+ a(i) = value(n)
+ end do
+ a(n) = value(n)
+ end subroutine
+
+ ! The delimited form owns the loop in the source tree.
+ ! CHECK-LABEL: func.func @_QMselected_contextPdelimited_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine delimited_do(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: do) otherwise(nothing)
+ do i = 1, value(n)
+ a(i) = value(n)
+ end do
+ !$omp end metadirective
+ a(n) = value(n)
+ end subroutine
+
+ ! SIMD clauses see DO; original bounds exclude both loop constituents.
+ ! CHECK-LABEL: func.func @_QMselected_contextPstatic_do_simd(
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine static_do_simd(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: do simd if(simd: value(n) > 0)) &
+ !$omp& otherwise(nothing)
+ do i = 1, value(n)
+ a(i) = value(n)
+ end do
+ !$omp end metadirective
+ a(n) = value(n)
+ end subroutine
+
+ ! Both runtime arms and the subsequent call restore the outer context.
+ ! CHECK-LABEL: func.func @_QMselected_contextPruntime_do_simd(
+ ! CHECK: fir.if
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.simd
+ ! CHECK: fir.call @_QMselected_contextPin_do(
+ ! CHECK: } else {
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: fir.call @_QMselected_contextPvalue(
+ ! CHECK: return
+ subroutine runtime_do_simd(flag, n, a)
+ logical :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(user={condition(flag)}: do simd if(simd: value(n) > 0)) &
+ !$omp& otherwise(nothing)
+ do i = 1, value(n)
+ a(i) = value(n)
+ end do
+ a(n) = value(n)
+ end subroutine
+end module
+
+! Construct depth must agree for calls and metadirective replacements.
+module loop_depth
+contains
+ integer function do_value()
+ do_value = 1
+ end function
+
+ integer function scored_value()
+ scored_value = 2
+ end function
+
+ integer function value()
+ !$omp declare variant(do_value) match(construct={do})
+ !$omp declare variant(scored_value) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ value = 0
+ end function
+
+ integer function requiring_do()
+ !$omp declare variant(do_value) match(construct={do})
+ requiring_do = 0
+ end function
+
+ ! At depth one CPU scores 3, below the user selector's score of 4.
+ ! Counting the DO twice would select TASKYIELD instead.
+ ! CHECK-LABEL: func.func @_QMloop_depthPbare_do(
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.taskwait
+ ! CHECK: return
+ subroutine bare_do(n)
+ integer :: n, i
+ !$omp do
+ do i = 1, n
+ !$omp metadirective when(device={kind(cpu)}: taskyield) &
+ !$omp& when(user={condition(score(3): .true.)}: taskwait)
+ end do
+ end subroutine
+
+ ! DO at position two scores 3, below the vendor selector's score of 4.
+ ! The same context must be used for both kinds of variant selection.
+ ! CHECK-LABEL: func.func @_QMloop_depthPcombined(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.taskwait
+ ! CHECK: fir.call @_QMloop_depthPscored_value()
+ ! CHECK: return
+ subroutine combined(n, a)
+ integer :: n, i, a(n)
+ !$omp parallel do
+ do i = 1, n
+ !$omp metadirective when(construct={do}: taskyield) &
+ !$omp& when(implementation={vendor(score(3): llvm)}: taskwait)
+ a(i) = value()
+ end do
+ end subroutine
+
+ ! Intervening code is outside the innermost DO evaluation, but shares the
+ ! same OpenMP construct context as the collapsed loop body. The competing
+ ! call rejects a duplicated DO; the DO-only call rejects a missing DO.
+ ! CHECK-LABEL: func.func @_QMloop_depthPcollapsed(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.wsloop
+ ! CHECK: fir.call @_QMloop_depthPscored_value()
+ ! CHECK: fir.call @_QMloop_depthPdo_value()
+ ! CHECK: fir.call @_QMloop_depthPscored_value()
+ ! CHECK: fir.call @_QMloop_depthPdo_value()
+ ! CHECK: fir.call @_QMloop_depthPscored_value()
+ ! CHECK: fir.call @_QMloop_depthPdo_value()
+ ! CHECK: return
+ subroutine collapsed(n, a)
+ integer :: n, i, j, a(n, n)
+ !$omp parallel do collapse(2)
+ do i = 1, n
+ a(i, 1) = value() + requiring_do()
+ do j = 1, n
+ a(i, j) = value() + requiring_do()
+ end do
+ a(i, n) = value() + requiring_do()
+ end do
+ end subroutine
+
+ ! A nested TASK contributes a position, but must not move the enclosing DO.
+ ! CHECK-LABEL: func.func @_QMloop_depthPnested_task(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.task
+ ! CHECK: omp.taskwait
+ ! CHECK: return
+ subroutine nested_task(n)
+ integer :: n, i
+ !$omp parallel do
+ do i = 1, n
+ !$omp task
+ !$omp metadirective when(construct={do}: taskyield) &
+ !$omp& when(implementation={vendor(score(3): llvm)}: taskwait)
+ !$omp end task
+ end do
+ end subroutine
+
+ ! TARGET hides the outer PARALLEL. TARGET, PARALLEL, DO give depth three:
+ ! CPU scores 9 and loses to the user selector's score of 10.
+ ! CHECK-LABEL: func.func @_QMloop_depthPtarget_depth(
+ ! CHECK: omp.parallel
+ ! CHECK: omp.target
+ ! CHECK: omp.parallel
+ ! CHECK: omp.wsloop
+ ! CHECK: omp.taskwait
+ ! CHECK: return
+ subroutine target_depth(n)
+ integer :: n, i
+ !$omp parallel
+ !$omp target
+ !$omp parallel do
+ do i = 1, n
+ !$omp metadirective when(device={kind(cpu)}: taskyield) &
+ !$omp& when(user={condition(score(9): .true.)}: taskwait)
+ end do
+ !$omp end target
+ !$omp end parallel
+ end subroutine
+end module
diff --git a/flang/test/Lower/OpenMP/declare-variant-spec-expr.f90 b/flang/test/Lower/OpenMP/declare-variant-spec-expr.f90
new file mode 100644
index 0000000000000..704314e68696c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-variant-spec-expr.f90
@@ -0,0 +1,56 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -emit-hlfir %s -o - | \
+! RUN: FileCheck %s --implicit-check-not='fir.call @_QMspec_contextPparallel'
+
+module spec_context
+contains
+ ! Specification expressions are lowered before any current evaluation.
+ ! The caller comes first to avoid reusing a previous procedure's evaluation.
+ ! CHECK-LABEL: func.func @_QMspec_contextPfirst(
+ ! CHECK-NOT: fir.call @_QMspec_contextPbound(
+ ! CHECK: fir.call @_QMspec_contextPreplacement(
+ ! CHECK-NOT: fir.call @_QMspec_contextPreplacement(
+ ! CHECK: fir.call @_QMspec_contextPunmatched(
+ ! CHECK-NOT: fir.call @_QMspec_contextPbound(
+ ! CHECK: return
+ subroutine first(n)
+ integer, intent(in) :: n
+ integer :: a(bound(n)), b(unmatched(n))
+ print *, size(a), size(b)
+ end subroutine
+
+ pure integer function bound(n)
+ integer, intent(in) :: n
+ !$omp declare variant(replacement) match(implementation={vendor(llvm)})
+ bound = n
+ end function
+
+ pure integer function unmatched(n)
+ integer, intent(in) :: n
+ !$omp declare variant(parallel_replacement) match(construct={parallel})
+ unmatched = n
+ end function
+
+ pure integer function replacement(n)
+ integer, intent(in) :: n
+ replacement = n
+ end function
+
+ pure integer function parallel_replacement(n)
+ integer, intent(in) :: n
+ parallel_replacement = n
+ end function
+
+ ! A later procedure's prologue also starts with an empty construct context.
+ ! CHECK-LABEL: func.func @_QMspec_contextPlast(
+ ! CHECK-NOT: fir.call @_QMspec_contextPbound(
+ ! CHECK: fir.call @_QMspec_contextPreplacement(
+ ! CHECK-NOT: fir.call @_QMspec_contextPreplacement(
+ ! CHECK: fir.call @_QMspec_contextPunmatched(
+ ! CHECK-NOT: fir.call @_QMspec_contextPbound(
+ ! CHECK: return
+ subroutine last(n)
+ integer, intent(in) :: n
+ integer :: a(bound(n)), b(unmatched(n))
+ print *, size(a), size(b)
+ end subroutine
+end module
diff --git a/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90 b/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
index 44b6e342b99f0..0506781d2a650 100644
--- a/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
+++ b/flang/test/Lower/OpenMP/distribute-parallel-do-simd.f90
@@ -42,8 +42,7 @@ subroutine distribute_parallel_do_simd_dist_schedule()
!$omp end teams
end subroutine distribute_parallel_do_simd_dist_schedule
-! A num_threads temporary is created outside omp.parallel and must be
-! destroyed once, after it.
+! Temporaries evaluated outside omp.parallel must be destroyed once, after it.
! CHECK-LABEL: func.func @_QPdistribute_parallel_do_simd_temp_cleanup(
subroutine distribute_parallel_do_simd_temp_cleanup(n)
integer :: n
@@ -57,16 +56,19 @@ function get(n) result(r)
! CHECK: omp.teams {
! CHECK: fir.call @_QPget(
+ ! CHECK: %[[DIST_TMP:.*]] = hlfir.as_expr
+ ! CHECK: fir.call @_QPget(
! CHECK: %[[NT_TMP:.*]] = hlfir.as_expr
! CHECK: omp.parallel num_threads({{.*}}) {
! CHECK-NOT: hlfir.destroy
- ! CHECK: omp.distribute {
+ ! CHECK: omp.distribute dist_schedule_static dist_schedule_chunk_size({{.*}}) {
! CHECK-NOT: hlfir.destroy
! CHECK: omp.terminator
! CHECK-NEXT: } {omp.composite}
! CHECK: hlfir.destroy %[[NT_TMP]] :
+ ! CHECK: hlfir.destroy %[[DIST_TMP]] :
! CHECK-NEXT: omp.terminator
- !$omp distribute parallel do simd num_threads(size(get(n)))
+ !$omp distribute parallel do simd num_threads(size(get(n))) dist_schedule(static, size(get(n)))
do index_ = 1, 10
end do
!$omp end distribute parallel do simd
diff --git a/flang/test/Lower/OpenMP/distribute-parallel-do.f90 b/flang/test/Lower/OpenMP/distribute-parallel-do.f90
index 203e3c014faec..b69a45086f6a1 100644
--- a/flang/test/Lower/OpenMP/distribute-parallel-do.f90
+++ b/flang/test/Lower/OpenMP/distribute-parallel-do.f90
@@ -36,8 +36,7 @@ subroutine distribute_parallel_do_dist_schedule()
!$omp end teams
end subroutine distribute_parallel_do_dist_schedule
-! A num_threads temporary is created outside omp.parallel and must be
-! destroyed once, after it.
+! Temporaries evaluated outside omp.parallel must be destroyed once, after it.
! CHECK-LABEL: func.func @_QPdistribute_parallel_do_temp_cleanup(
subroutine distribute_parallel_do_temp_cleanup(n)
integer :: n
@@ -51,16 +50,19 @@ function get(n) result(r)
! CHECK: omp.teams {
! CHECK: fir.call @_QPget(
+ ! CHECK: %[[DIST_TMP:.*]] = hlfir.as_expr
+ ! CHECK: fir.call @_QPget(
! CHECK: %[[NT_TMP:.*]] = hlfir.as_expr
! CHECK: omp.parallel num_threads({{.*}}) private({{.*}}) {
! CHECK-NOT: hlfir.destroy
- ! CHECK: omp.distribute {
+ ! CHECK: omp.distribute dist_schedule_static dist_schedule_chunk_size({{.*}}) {
! CHECK-NOT: hlfir.destroy
! CHECK: omp.terminator
! CHECK-NEXT: } {omp.composite}
! CHECK: hlfir.destroy %[[NT_TMP]] :
+ ! CHECK: hlfir.destroy %[[DIST_TMP]] :
! CHECK-NEXT: omp.terminator
- !$omp distribute parallel do num_threads(size(get(n)))
+ !$omp distribute parallel do num_threads(size(get(n))) dist_schedule(static, size(get(n)))
do index_ = 1, 10
end do
!$omp end distribute parallel do
diff --git a/flang/test/Lower/OpenMP/metadirective-device-kind.f90 b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
index ae7613d40c5d9..d96be7739ac98 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-kind.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
@@ -1,5 +1,22 @@
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
+! Device weights depend on context depth, not the candidate's selector count.
+! CHECK-LABEL: func.func @_QPtest_device_kind_nested_parallel()
+! CHECK: omp.parallel
+! CHECK: omp.parallel
+! CHECK-NOT: omp.barrier
+! CHECK: omp.taskyield
+! CHECK-NOT: omp.barrier
+! CHECK: return
+subroutine test_device_kind_nested_parallel()
+ !$omp parallel
+ !$omp parallel
+ !$omp metadirective when(device={kind(cpu)}: taskyield) &
+ !$omp& when(construct={parallel}: barrier)
+ !$omp end parallel
+ !$omp end parallel
+end subroutine
+
! CHECK-LABEL: func.func @_QPtest_device_kind_host()
! CHECK: omp.taskyield
! CHECK: return
diff --git a/flang/test/Lower/OpenMP/metadirective-target-boundary.f90 b/flang/test/Lower/OpenMP/metadirective-target-boundary.f90
new file mode 100644
index 0000000000000..00adbc0ffdba5
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-target-boundary.f90
@@ -0,0 +1,76 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=51 -emit-fir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=51 -emit-hlfir %s -o - | \
+! RUN: FileCheck %s
+
+! TARGET hides the outer PARALLEL, so the SIMD replacement is not lowered.
+! CHECK-LABEL: func.func @_QPactual_target(
+! CHECK: omp.parallel
+! CHECK: omp.target
+! CHECK-NOT: omp.simd
+! CHECK: return
+subroutine actual_target(n, a)
+ integer :: n, i, a(n)
+ !$omp parallel
+ !$omp target
+ !$omp metadirective &
+ !$omp& when(construct={parallel}: simd) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end target
+ !$omp end parallel
+end subroutine
+
+! A selected TARGET contributes its own construct trait.
+! CHECK-LABEL: func.func @_QPselected_target_present()
+! CHECK: omp.parallel
+! CHECK: omp.target
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine selected_target_present()
+ !$omp parallel
+ !$omp begin metadirective default(target)
+ !$omp metadirective &
+ !$omp& when(construct={target}: barrier) default(taskyield)
+ !$omp end metadirective
+ !$omp end parallel
+end subroutine
+
+! The same selected TARGET hides the enclosing PARALLEL trait.
+! CHECK-LABEL: func.func @_QPselected_target_hides_parallel()
+! CHECK: omp.parallel
+! CHECK: omp.target
+! CHECK-NOT: omp.barrier
+! CHECK: omp.taskyield
+! CHECK-NOT: omp.barrier
+! CHECK: return
+subroutine selected_target_hides_parallel()
+ !$omp parallel
+ !$omp begin metadirective default(target)
+ !$omp metadirective &
+ !$omp& when(construct={parallel}: barrier) default(taskyield)
+ !$omp end metadirective
+ !$omp end parallel
+end subroutine
+
+! The boundary includes TARGET itself and constructs nested inside it.
+! CHECK-LABEL: func.func @_QPtarget_inner_parallel()
+! CHECK: omp.parallel
+! CHECK: omp.target
+! CHECK: omp.parallel
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine target_inner_parallel()
+ !$omp parallel
+ !$omp target
+ !$omp parallel
+ !$omp metadirective when(construct={target, parallel}: barrier) &
+ !$omp& default(taskyield)
+ !$omp end parallel
+ !$omp end target
+ !$omp end parallel
+end subroutine
diff --git a/flang/test/Lower/OpenMP/variant-scoring-any-sections.f90 b/flang/test/Lower/OpenMP/variant-scoring-any-sections.f90
new file mode 100644
index 0000000000000..e51df9bc37746
--- /dev/null
+++ b/flang/test/Lower/OpenMP/variant-scoring-any-sections.f90
@@ -0,0 +1,81 @@
+! RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 %s -o - \
+! RUN: | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 %s -o - \
+! RUN: | FileCheck %s
+
+module scoring
+contains
+ subroutine high()
+ end subroutine
+ subroutine low()
+ end subroutine
+ subroutine cpu()
+ end subroutine
+ subroutine vendor()
+ end subroutine
+
+ subroutine base_any()
+ !$omp declare variant(high) &
+ !$omp& match(implementation={vendor(score(100): llvm)})
+ !$omp declare variant(low) &
+ !$omp& match(implementation={vendor(score(1): llvm)}, device={kind(any)})
+ end subroutine
+
+ ! kind(any) does not make the lower-scored selector a strict superset.
+ ! CHECK-LABEL: func.func @_QMscoringPtest_any()
+ ! CHECK-NOT: fir.call @_QMscoringPlow
+ ! CHECK: fir.call @_QMscoringPhigh()
+ ! CHECK-NOT: fir.call @_QMscoringPlow
+ ! CHECK: return
+ subroutine test_any()
+ call base_any()
+ end subroutine
+
+ subroutine base_sections()
+ !$omp declare variant(cpu) match(device={kind(cpu)})
+ !$omp declare variant(vendor) &
+ !$omp& match(implementation={vendor(score(5): llvm)})
+ end subroutine
+
+ ! SECTION is a separator: the context depth is two, CPU scores 5, and the
+ ! vendor scores 6 whether the optional first SECTION is present or absent.
+ ! CHECK-LABEL: func.func @_QMscoringPtest_explicit_section()
+ ! CHECK: omp.parallel
+ ! CHECK: omp.sections
+ ! CHECK: omp.section
+ ! CHECK-NOT: fir.call @_QMscoringPcpu
+ ! CHECK: fir.call @_QMscoringPvendor()
+ ! CHECK-NOT: fir.call @_QMscoringPcpu
+ ! CHECK: return
+ subroutine test_explicit_section()
+ !$omp parallel sections
+ !$omp section
+ call base_sections()
+ !$omp end parallel sections
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMscoringPtest_implicit_section()
+ ! CHECK: omp.parallel
+ ! CHECK: omp.sections
+ ! CHECK: omp.section
+ ! CHECK-NOT: fir.call @_QMscoringPcpu
+ ! CHECK: fir.call @_QMscoringPvendor()
+ ! CHECK-NOT: fir.call @_QMscoringPcpu
+ ! CHECK: return
+ subroutine test_implicit_section()
+ !$omp parallel sections
+ call base_sections()
+ !$omp end parallel sections
+ end subroutine
+end module
+
+! CHECK-LABEL: func.func @_QPtest_metadirective_any()
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.barrier
+! CHECK-NEXT: return
+subroutine test_metadirective_any()
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(100): llvm)}: barrier) &
+ !$omp& when(implementation={vendor(score(1): llvm)}, &
+ !$omp& device={kind(any)}: taskyield)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/variant-source-context.f90 b/flang/test/Lower/OpenMP/variant-source-context.f90
new file mode 100644
index 0000000000000..798e2549531d8
--- /dev/null
+++ b/flang/test/Lower/OpenMP/variant-source-context.f90
@@ -0,0 +1,211 @@
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp \
+! RUN: -fopenmp-version=52 -triple x86_64-unknown-linux-gnu \
+! RUN: -emit-hlfir %s -o - | FileCheck %s \
+! RUN: --implicit-check-not='fir.call @_QMsource_contextPbound_arch' %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp \
+! RUN: -fopenmp-version=60 -triple x86_64-unknown-linux-gnu \
+! RUN: -emit-hlfir %s -o - | FileCheck %s \
+! RUN: --implicit-check-not='fir.call @_QMsource_contextPbound_arch' %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp \
+! RUN: -fopenmp-version=52 -triple x86_64-unknown-linux-gnu \
+! RUN: -emit-fir %s -o - | FileCheck %s \
+! RUN: --implicit-check-not='fir.call @_QMsource_contextPbound_arch' %}
+
+! DECLARE VARIANT and METADIRECTIVE use the same source construct context.
+
+module source_context
+contains
+ subroutine depth_base
+ !$omp declare variant(depth_vendor) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ !$omp declare variant(depth_arch) match(device={arch(x86_64)})
+ end subroutine
+ subroutine depth_vendor
+ end subroutine
+ subroutine depth_arch
+ end subroutine
+
+ subroutine order_base
+ !$omp declare variant(order_vendor) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ !$omp declare variant(order_parallel) match(construct={parallel})
+ end subroutine
+ subroutine order_vendor
+ end subroutine
+ subroutine order_parallel
+ end subroutine
+
+ integer function value_base()
+ !$omp declare variant(value_vendor) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ !$omp declare variant(value_arch) match(device={arch(x86_64)})
+ value_base = 0
+ end function
+ integer function value_vendor()
+ value_vendor = 1
+ end function
+ integer function value_arch()
+ value_arch = 2
+ end function
+
+ integer function bound_base(n)
+ integer, intent(in) :: n
+ !$omp declare variant(bound_vendor) &
+ !$omp& match(implementation={vendor(score(3): llvm)})
+ !$omp declare variant(bound_arch) match(device={arch(x86_64)})
+ bound_base = n
+ end function
+ integer function bound_vendor(n)
+ integer, intent(in) :: n
+ bound_vendor = n
+ end function
+ integer function bound_arch(n)
+ integer, intent(in) :: n
+ bound_arch = n
+ end function
+
+! Bounds exclude TILE in both evaluations; the body includes it.
+! CHECK-LABEL: func.func @_QMsource_contextPtile_context(
+! CHECK-COUNT-2: fir.call @_QMsource_contextPbound_vendor(
+! CHECK: fir.call @_QMsource_contextPdepth_arch()
+! CHECK-NOT: fir.call @_QMsource_contextPdepth_vendor
+! CHECK: return
+ subroutine tile_context(n)
+ integer :: n, i
+ !$omp tile sizes(2)
+ do i = 1, bound_base(n)
+ call depth_base()
+ end do
+ end subroutine
+
+! CHECK-LABEL: func.func @_QMsource_contextPunroll_context(
+! CHECK: fir.call @_QMsource_contextPdepth_arch()
+! CHECK-NOT: fir.call @_QMsource_contextPdepth_vendor
+! CHECK: return
+ subroutine unroll_context(n)
+ integer :: n, i
+ !$omp unroll partial(2)
+ do i = 1, n
+ call depth_base()
+ end do
+ end subroutine
+
+! Bounds exclude FUSE in both evaluations of each loop; the bodies include it.
+! CHECK-LABEL: func.func @_QMsource_contextPfuse_context(
+! CHECK-NOT: fir.call @_QMsource_contextPdepth_vendor
+! CHECK-COUNT-2: fir.call @_QMsource_contextPbound_vendor(
+! CHECK: fir.call @_QMsource_contextPdepth_arch()
+! CHECK-COUNT-2: fir.call @_QMsource_contextPbound_vendor(
+! CHECK: fir.call @_QMsource_contextPdepth_arch()
+! CHECK-NOT: fir.call @_QMsource_contextPdepth_vendor
+! CHECK: return
+ subroutine fuse_context(n)
+ integer :: n, i, j
+ !$omp fuse
+ do i = 1, bound_base(n)
+ call depth_base()
+ end do
+ do j = 1, bound_base(n)
+ call depth_base()
+ end do
+ !$omp end fuse
+ end subroutine
+
+! CHECK-LABEL: func.func @_QMsource_contextPatomic_context(
+! CHECK: fir.call @_QMsource_contextPvalue_arch()
+! CHECK-NOT: fir.call @_QMsource_contextPvalue_vendor
+! CHECK: omp.atomic.update
+! CHECK: return
+ subroutine atomic_context(x)
+ integer :: x
+ !$omp atomic update
+ x = x + value_base()
+ end subroutine
+
+! CHECK-LABEL: func.func @_QMsource_contextPassume_context()
+! CHECK: fir.call @_QMsource_contextPdepth_vendor()
+! CHECK-NOT: fir.call @_QMsource_contextPdepth_arch
+! CHECK: return
+ subroutine assume_context
+ !$omp assume holds(.true.)
+ call depth_base()
+ !$omp end assume
+ end subroutine
+
+! CHECK-LABEL: func.func @_QMsource_contextPcombined_context(
+! CHECK: fir.call @_QMsource_contextPorder_parallel()
+! CHECK-NOT: fir.call @_QMsource_contextPorder_vendor
+! CHECK: return
+ subroutine combined_context(n)
+ integer :: n, i
+ !$omp teams distribute parallel do
+ do i = 1, n
+ call order_base()
+ end do
+ end subroutine
+end module
+
+! Executable loop transformations contribute to the construct context.
+! CHECK-LABEL: func.func @_QPtile_context(
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine tile_context(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp tile sizes(2)
+ do i = 1, n
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(3): llvm)}: taskyield) &
+ !$omp& when(device={arch(x86_64)}: barrier)
+ do j = 1, n
+ a(j, i) = j
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_context(
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine unroll_context(n, a)
+ integer :: n, a(n), i
+ !$omp unroll partial(2)
+ do i = 1, n
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(3): llvm)}: taskyield) &
+ !$omp& when(device={arch(x86_64)}: barrier)
+ a(i) = i
+ end do
+end subroutine
+
+! Informational directives do not contribute to the construct context.
+! CHECK-LABEL: func.func @_QPassume_context()
+! CHECK: omp.taskyield
+! CHECK-NOT: omp.barrier
+! CHECK: return
+subroutine assume_context()
+ !$omp assume holds(.true.)
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(3): llvm)}: taskyield) &
+ !$omp& when(device={arch(x86_64)}: barrier)
+ !$omp end assume
+end subroutine
+
+! Combined and composite constructs retain their source nesting order.
+! CHECK-LABEL: func.func @_QPcombined_context(
+! CHECK: omp.teams
+! CHECK: omp.parallel
+! CHECK: omp.distribute
+! CHECK: omp.wsloop
+! CHECK: omp.taskyield
+! CHECK-NOT: omp.barrier
+! CHECK: return
+subroutine combined_context(n)
+ integer :: n, i
+ !$omp teams distribute parallel do
+ do i = 1, n
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(3): llvm)}: barrier) &
+ !$omp& when(construct={parallel}: taskyield)
+ end do
+end subroutine
More information about the llvm-branch-commits
mailing list