[llvm-branch-commits] [flang] [flang][OpenMP] Support lowering of metadirective (part 2) (PR #194424)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri May 1 12:48:28 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/194424
>From 241c7bb83ca47317c7d10bfac428ec6d266e85c4 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 27 Apr 2026 02:45:02 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Support lowering of metadirective (part
2)
Lower non-constant user={condition(expr)} selectors in metadirectives
to a fir.if/else chain.
Only statically applicable when-clauses participate in dynamic
selection. Dynamic conditions are evaluated at runtime in declaration
order, with the best static match, an explicit otherwise/default
clause, or implicit nothing as the final fallback.
This patch is part of the feature work for #188820.
Assisted with copilot and GPT-5.4
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 108 +++++++---
.../OpenMP/Todo/metadirective-dynamic.f90 | 10 -
.../test/Lower/OpenMP/metadirective-user.f90 | 203 +++++++++++++++++-
3 files changed, 280 insertions(+), 41 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index fe62db07e7f14..b25e5971ea0a3 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4490,6 +4490,11 @@ static void genMetadirective(lower::AbstractConverter &converter,
TargetOMPContext ompCtx(builder.getModule(), constructTraits);
llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
+ struct DynamicCandidate {
+ const parser::OmpDirectiveSpecification *spec = nullptr;
+ const parser::ScalarExpr *condExpr = nullptr;
+ };
+ llvm::SmallVector<DynamicCandidate> dynamicCandidates;
// A null directive specification represents either the implicit `nothing`
// variant or the absence of an explicit otherwise/default clause.
const parser::OmpDirectiveSpecification *fallback = nullptr;
@@ -4545,9 +4550,16 @@ static void genMetadirective(lower::AbstractConverter &converter,
makeVariantMatchInfo(vmi, *ctxSel, semaCtx,
converter.genLocation(clause.source), dynCondExpr);
- if (dynCondExpr)
- TODO(converter.genLocation(clause.source),
- "dynamic user condition in METADIRECTIVE");
+ // Check if this variant has a dynamic user condition.
+ if (dynCondExpr) {
+ // For dynamic candidates, verify that the non-user parts (device,
+ // implementation) still match statically before keeping the candidate.
+ if (!llvm::omp::isVariantApplicableInContext(
+ vmi, ompCtx, /*DeviceOrImplementationSetOnly=*/true))
+ continue;
+ dynamicCandidates.push_back({variant.spec, dynCondExpr});
+ continue;
+ }
if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
continue;
@@ -4589,35 +4601,71 @@ static void genMetadirective(lower::AbstractConverter &converter,
queue.begin());
};
- const parser::OmpDirectiveSpecification *selected = fallback;
- if (candidates.size() == 1) {
- selected = candidates.front().spec;
- } else if (!candidates.empty()) {
- // The OpenMP context scorer preserves input order for tied candidates.
- // Put explicit variants first so they take precedence over implicit
- // `nothing`, as required by metadirective selection.
- llvm::SmallVector<unsigned, 4> candidateOrder;
- candidateOrder.reserve(candidates.size());
- for (auto [idx, candidate] : llvm::enumerate(candidates))
- if (candidate.isExplicit)
- candidateOrder.push_back(idx);
- for (auto [idx, candidate] : llvm::enumerate(candidates))
- if (!candidate.isExplicit)
- candidateOrder.push_back(idx);
-
- llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
- orderedVMIs.reserve(candidates.size());
- for (unsigned idx : candidateOrder)
- orderedVMIs.push_back(candidates[idx].vmi);
-
- int bestIdx = llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
- if (bestIdx >= 0) {
- assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
- "best variant index out of range");
- selected = candidates[candidateOrder[bestIdx]].spec;
+ auto selectStaticBest = [&]() -> const parser::OmpDirectiveSpecification * {
+ if (candidates.size() == 1)
+ return candidates.front().spec;
+ if (!candidates.empty()) {
+ // The OpenMP context scorer preserves input order for tied candidates.
+ // Put explicit variants first so they take precedence over implicit
+ // `nothing`, as required by metadirective selection.
+ llvm::SmallVector<unsigned, 4> candidateOrder;
+ candidateOrder.reserve(candidates.size());
+ for (auto [idx, candidate] : llvm::enumerate(candidates))
+ if (candidate.isExplicit)
+ candidateOrder.push_back(idx);
+ for (auto [idx, candidate] : llvm::enumerate(candidates))
+ if (!candidate.isExplicit)
+ candidateOrder.push_back(idx);
+
+ llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+ orderedVMIs.reserve(candidates.size());
+ for (unsigned idx : candidateOrder)
+ orderedVMIs.push_back(candidates[idx].vmi);
+
+ int bestIdx =
+ llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
+ if (bestIdx >= 0) {
+ assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
+ "best variant index out of range");
+ return candidates[candidateOrder[bestIdx]].spec;
+ }
}
+ return fallback;
+ };
+
+ // If no dynamic candidates, do pure static resolution.
+ if (dynamicCandidates.empty()) {
+ genVariant(selectStaticBest());
+ return;
+ }
+
+ // Dynamic resolution: build a fir.if chain for user conditions.
+ // Candidates are tested in declaration order. The final else branch
+ // falls back to the best static match, an explicit otherwise/default
+ // clause, or an implicit no-op.
+ mlir::Location loc = converter.genLocation(clauseList.source);
+ lower::StatementContext stmtCtx;
+ const parser::OmpDirectiveSpecification *staticFallback = selectStaticBest();
+
+ for (auto [i, cand] : llvm::enumerate(dynamicCandidates)) {
+ assert(cand.condExpr && "dynamic candidate must have condition expr");
+ const auto *typedExpr = semantics::GetExpr(semaCtx, *cand.condExpr);
+ assert(typedExpr && "missing typed expression for user condition");
+ mlir::Value condVal =
+ fir::getBase(converter.genExprValue(*typedExpr, stmtCtx, &loc));
+
+ if (condVal.getType() != builder.getI1Type())
+ condVal = builder.createConvert(loc, builder.getI1Type(), condVal);
+
+ auto ifOp =
+ fir::IfOp::create(builder, loc, condVal, /*withElseRegion=*/true);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+ genVariant(cand.spec);
+
+ builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
+ if (i == dynamicCandidates.size() - 1)
+ genVariant(staticFallback);
}
- genVariant(selected);
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
deleted file mode 100644
index a9a02ece1db4b..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
+++ /dev/null
@@ -1,10 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: dynamic user condition in METADIRECTIVE
-
-subroutine test_dynamic_user_condition(flag)
- logical, intent(in) :: flag
- !$omp metadirective &
- !$omp & when(user={condition(flag)}: taskyield) &
- !$omp & default(nothing)
-end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 623443b2c6c3c..e17e5dfc4968c 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -1,9 +1,13 @@
-! Test lowering of OpenMP metadirective with constant-folded user selectors.
+! Test lowering of OpenMP metadirective with user={condition()} selectors.
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck %s
+!===----------------------------------------------------------------------===!
+! Static (constant-folded) user conditions
+!===----------------------------------------------------------------------===!
+
! CHECK-LABEL: func.func @_QPtest_condition_true()
! CHECK: omp.taskyield
! CHECK-NOT: fir.if
@@ -70,3 +74,200 @@ subroutine test_begin_condition_false()
x = 1
!$omp end metadirective
end subroutine
+
+!===----------------------------------------------------------------------===!
+! Dynamic (runtime) user conditions
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_condition(
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK: %[[COND:.*]] = fir.convert %[[LOAD]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[COND]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_condition(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: barrier) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_condition_expr(
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32>
+! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK: %[[C1000:.*]] = arith.constant 1000 : i32
+! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[LOAD]], %[[C1000]] : i32
+! CHECK: fir.if %[[CMP]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_condition_expr(n)
+ integer, intent(in) :: n
+ !$omp metadirective &
+ !$omp & when(user={condition(n > 1000)}: barrier) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! Both when clauses pass vendor(llvm) statically. The first has a dynamic
+! condition so becomes a runtime branch; the second is fully static and
+! becomes the fallback.
+! CHECK-LABEL: func.func @_QPtest_mixed_static_dynamic(
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32>
+! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK: %[[C100:.*]] = arith.constant 100 : i32
+! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[LOAD]], %[[C100]] : i32
+! CHECK: fir.if %[[CMP]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: omp.taskwait
+! CHECK: }
+! CHECK: return
+subroutine test_mixed_static_dynamic(n)
+ integer, intent(in) :: n
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}, user={condition(n > 100)}: barrier) &
+ !$omp & when(implementation={vendor(llvm)}: taskwait) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! Dynamic candidate whose static traits don't match is skipped entirely.
+! CHECK-LABEL: func.func @_QPtest_dynamic_static_mismatch(
+! CHECK-NOT: fir.if
+! CHECK: omp.taskyield
+! CHECK: return
+subroutine test_dynamic_static_mismatch(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(implementation={vendor("unknown")}, user={condition(flag)}: barrier) &
+#ifdef OMP_52
+ !$omp & otherwise(taskyield)
+#else
+ !$omp & default(taskyield)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_two_dynamic(
+! CHECK-SAME: %[[ARG0:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME: %[[ARG1:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DECLA:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[DECLB:.*]]:2 = hlfir.declare %[[ARG1]]
+! CHECK: %[[LOADA:.*]] = fir.load %[[DECLA]]#0
+! CHECK: %[[CONDA:.*]] = fir.convert %[[LOADA]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CONDA]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: %[[LOADB:.*]] = fir.load %[[DECLB]]#0
+! CHECK: %[[CONDB:.*]] = fir.convert %[[LOADB]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CONDB]] {
+! CHECK: omp.taskwait
+! CHECK: } else {
+! CHECK: omp.taskyield
+! CHECK: }
+! CHECK: }
+! CHECK: return
+subroutine test_two_dynamic(a, b)
+ logical, intent(in) :: a, b
+ !$omp metadirective &
+ !$omp & when(user={condition(a)}: barrier) &
+ !$omp & when(user={condition(b)}: taskwait) &
+#ifdef OMP_52
+ !$omp & otherwise(taskyield)
+#else
+ !$omp & default(taskyield)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_three_dynamic(
+! CHECK-SAME: %[[A:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME: %[[B:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME: %[[C:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DA:.*]]:2 = hlfir.declare %[[A]]
+! CHECK: %[[DB:.*]]:2 = hlfir.declare %[[B]]
+! CHECK: %[[DC:.*]]:2 = hlfir.declare %[[C]]
+! CHECK: %[[LA:.*]] = fir.load %[[DA]]#0
+! CHECK: %[[CA:.*]] = fir.convert %[[LA]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CA]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: %[[LB:.*]] = fir.load %[[DB]]#0
+! CHECK: %[[CB:.*]] = fir.convert %[[LB]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CB]] {
+! CHECK: omp.taskwait
+! CHECK: } else {
+! CHECK: %[[LC:.*]] = fir.load %[[DC]]#0
+! CHECK: %[[CC:.*]] = fir.convert %[[LC]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CC]] {
+! CHECK: omp.taskyield
+! CHECK: } else {
+! CHECK: }
+! CHECK: }
+! CHECK: }
+! CHECK: return
+subroutine test_three_dynamic(a, b, c)
+ logical, intent(in) :: a, b, c
+ !$omp metadirective &
+ !$omp & when(user={condition(a)}: barrier) &
+ !$omp & when(user={condition(b)}: taskwait) &
+ !$omp & when(user={condition(c)}: taskyield) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_multi_dynamic_multi_static(
+! CHECK-SAME: %[[A:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME: %[[B:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DA:.*]]:2 = hlfir.declare %[[A]]
+! CHECK: %[[DB:.*]]:2 = hlfir.declare %[[B]]
+! CHECK: %[[LA:.*]] = fir.load %[[DA]]#0
+! CHECK: %[[CA:.*]] = fir.convert %[[LA]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CA]] {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: %[[LB:.*]] = fir.load %[[DB]]#0
+! CHECK: %[[CB:.*]] = fir.convert %[[LB]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CB]] {
+! CHECK: omp.taskyield
+! CHECK: } else {
+! CHECK: omp.taskwait
+! CHECK: }
+! CHECK: }
+! CHECK: return
+subroutine test_multi_dynamic_multi_static(a, b)
+ logical, intent(in) :: a, b
+ ! dynamic + vendor(llvm) -> kept
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}, user={condition(a)}: barrier) &
+ ! dynamic + vendor("unknown") -> skipped (static mismatch)
+ !$omp & when(implementation={vendor("unknown")}, user={condition(.true.)}: taskwait) &
+ ! dynamic + vendor(llvm) -> kept
+ !$omp & when(implementation={vendor(llvm)}, user={condition(b)}: taskyield) &
+ ! static + vendor(llvm) -> best static fallback
+ !$omp & when(implementation={vendor(llvm)}: taskwait) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
>From c36eec79da089d45d1ca15bca2f2109b81e005d2 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Wed, 29 Apr 2026 02:45:38 -0500
Subject: [PATCH 2/2] Fix dynamic metadirective candidate selection
- Use one scored candidate path for static and dynamic metadirective variants.
- Dynamic user conditions are statically filtered and scored using their
non-user traits, then guarded at runtime with fir.if.
- Keeps construct/device/implementation traits enforced for dynamic
candidates and lets higher-scored static candidates beat lower-scored dynamic
candidates.
- Add regressions for construct mismatch, score ordering, and
implicit-nothing tie-breaking.
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 127 ++++++++++--------
.../test/Lower/OpenMP/metadirective-user.f90 | 50 +++++++
2 files changed, 118 insertions(+), 59 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index b25e5971ea0a3..bc7575547311a 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4446,12 +4446,14 @@ struct TargetOMPContext final : public llvm::omp::OMPContext {
struct MetadirectiveCandidate {
MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
- llvm::omp::VariantMatchInfo vmi, bool isExplicit)
- : spec(spec), vmi(vmi), isExplicit(isExplicit) {}
+ llvm::omp::VariantMatchInfo vmi, bool isExplicit,
+ const parser::ScalarExpr *condExpr = nullptr)
+ : spec(spec), vmi(vmi), isExplicit(isExplicit), condExpr(condExpr) {}
const parser::OmpDirectiveSpecification *spec = nullptr;
llvm::omp::VariantMatchInfo vmi;
bool isExplicit = false;
+ const parser::ScalarExpr *condExpr = nullptr;
};
} // namespace
@@ -4490,11 +4492,6 @@ static void genMetadirective(lower::AbstractConverter &converter,
TargetOMPContext ompCtx(builder.getModule(), constructTraits);
llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
- struct DynamicCandidate {
- const parser::OmpDirectiveSpecification *spec = nullptr;
- const parser::ScalarExpr *condExpr = nullptr;
- };
- llvm::SmallVector<DynamicCandidate> dynamicCandidates;
// A null directive specification represents either the implicit `nothing`
// variant or the absence of an explicit otherwise/default clause.
const parser::OmpDirectiveSpecification *fallback = nullptr;
@@ -4552,12 +4549,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
// Check if this variant has a dynamic user condition.
if (dynCondExpr) {
- // For dynamic candidates, verify that the non-user parts (device,
- // implementation) still match statically before keeping the candidate.
- if (!llvm::omp::isVariantApplicableInContext(
- vmi, ompCtx, /*DeviceOrImplementationSetOnly=*/true))
+ llvm::omp::VariantMatchInfo dynamicVMI = vmi;
+ dynamicVMI.RequiredTraits.reset(
+ unsigned(llvm::omp::TraitProperty::user_condition_unknown));
+ if (!llvm::omp::isVariantApplicableInContext(dynamicVMI, ompCtx))
continue;
- dynamicCandidates.push_back({variant.spec, dynCondExpr});
+ candidates.emplace_back(spec, dynamicVMI, isExplicit, dynCondExpr);
continue;
}
@@ -4601,58 +4598,67 @@ static void genMetadirective(lower::AbstractConverter &converter,
queue.begin());
};
- auto selectStaticBest = [&]() -> const parser::OmpDirectiveSpecification * {
- if (candidates.size() == 1)
- return candidates.front().spec;
- if (!candidates.empty()) {
- // The OpenMP context scorer preserves input order for tied candidates.
- // Put explicit variants first so they take precedence over implicit
- // `nothing`, as required by metadirective selection.
- llvm::SmallVector<unsigned, 4> candidateOrder;
- candidateOrder.reserve(candidates.size());
- for (auto [idx, candidate] : llvm::enumerate(candidates))
- if (candidate.isExplicit)
- candidateOrder.push_back(idx);
- for (auto [idx, candidate] : llvm::enumerate(candidates))
- if (!candidate.isExplicit)
- candidateOrder.push_back(idx);
-
- llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
- orderedVMIs.reserve(candidates.size());
- for (unsigned idx : candidateOrder)
- orderedVMIs.push_back(candidates[idx].vmi);
-
- int bestIdx =
- llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
- if (bestIdx >= 0) {
- assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
- "best variant index out of range");
- return candidates[candidateOrder[bestIdx]].spec;
- }
+ auto selectBestCandidate =
+ [](llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<MetadirectiveCandidate> candidates,
+ const TargetOMPContext &ompCtx) -> std::optional<unsigned> {
+ if (candidateIndices.empty())
+ return std::nullopt;
+ if (candidateIndices.size() == 1)
+ return candidateIndices.front();
+
+ // The OpenMP context scorer preserves input order for tied candidates.
+ // Put explicit variants first so they take precedence over implicit
+ // `nothing`, as required by metadirective selection.
+ llvm::SmallVector<unsigned, 4> candidateOrder;
+ candidateOrder.reserve(candidateIndices.size());
+ for (unsigned idx : candidateIndices)
+ if (candidates[idx].isExplicit)
+ candidateOrder.push_back(idx);
+ for (unsigned idx : candidateIndices)
+ if (!candidates[idx].isExplicit)
+ candidateOrder.push_back(idx);
+
+ llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+ orderedVMIs.reserve(candidateOrder.size());
+ for (unsigned idx : candidateOrder)
+ orderedVMIs.push_back(candidates[idx].vmi);
+
+ int bestIdx = llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
+ if (bestIdx >= 0) {
+ assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
+ "best variant index out of range");
+ return candidateOrder[bestIdx];
}
- return fallback;
+ return std::nullopt;
};
- // If no dynamic candidates, do pure static resolution.
- if (dynamicCandidates.empty()) {
- genVariant(selectStaticBest());
- return;
- }
+ llvm::SmallVector<unsigned, 4> remainingCandidates;
+ remainingCandidates.reserve(candidates.size());
+ for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
+ remainingCandidates.push_back(idx);
- // Dynamic resolution: build a fir.if chain for user conditions.
- // Candidates are tested in declaration order. The final else branch
- // falls back to the best static match, an explicit otherwise/default
- // clause, or an implicit no-op.
mlir::Location loc = converter.genLocation(clauseList.source);
lower::StatementContext stmtCtx;
- const parser::OmpDirectiveSpecification *staticFallback = selectStaticBest();
- for (auto [i, cand] : llvm::enumerate(dynamicCandidates)) {
- assert(cand.condExpr && "dynamic candidate must have condition expr");
- const auto *typedExpr = semantics::GetExpr(semaCtx, *cand.condExpr);
- assert(typedExpr && "missing typed expression for user condition");
+ while (!remainingCandidates.empty()) {
+ std::optional<unsigned> selected =
+ selectBestCandidate(remainingCandidates, candidates, ompCtx);
+ if (!selected) {
+ genVariant(fallback);
+ return;
+ }
+
+ const MetadirectiveCandidate &candidate = candidates[*selected];
+ if (!candidate.condExpr) {
+ genVariant(candidate.spec);
+ return;
+ }
+
+ const auto *condExpr = semantics::GetExpr(semaCtx, *candidate.condExpr);
+ assert(condExpr && "missing expression for user condition");
mlir::Value condVal =
- fir::getBase(converter.genExprValue(*typedExpr, stmtCtx, &loc));
+ fir::getBase(converter.genExprValue(*condExpr, stmtCtx, &loc));
if (condVal.getType() != builder.getI1Type())
condVal = builder.createConvert(loc, builder.getI1Type(), condVal);
@@ -4660,12 +4666,15 @@ static void genMetadirective(lower::AbstractConverter &converter,
auto ifOp =
fir::IfOp::create(builder, loc, condVal, /*withElseRegion=*/true);
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
- genVariant(cand.spec);
+ genVariant(candidate.spec);
builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
- if (i == dynamicCandidates.size() - 1)
- genVariant(staticFallback);
+ auto *remainingIt = llvm::find(remainingCandidates, *selected);
+ assert(remainingIt != remainingCandidates.end() &&
+ "selected candidate missing from remaining candidates");
+ remainingCandidates.erase(remainingIt);
}
+ genVariant(fallback);
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index e17e5dfc4968c..b0cec034466eb 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -165,6 +165,56 @@ subroutine test_dynamic_static_mismatch(flag)
#endif
end subroutine
+! Dynamic candidates must still satisfy non-user static traits. This construct
+! selector does not match outside a parallel construct, so the fallback wins.
+! CHECK-LABEL: func.func @_QPtest_dynamic_construct_mismatch(
+! CHECK-NOT: fir.if
+! CHECK-NOT: omp.barrier
+! CHECK: omp.taskwait
+! CHECK: return
+subroutine test_dynamic_construct_mismatch(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(construct={parallel}, user={condition(flag)}: barrier) &
+#ifdef OMP_52
+ !$omp & otherwise(taskwait)
+#else
+ !$omp & default(taskwait)
+#endif
+end subroutine
+
+! A higher-scored static candidate is selected before a lower-scored dynamic
+! candidate, even when the dynamic condition could be true at runtime.
+! CHECK-LABEL: func.func @_QPtest_dynamic_static_score_order(
+! CHECK-NOT: fir.if
+! CHECK-NOT: omp.barrier
+! CHECK: omp.taskwait
+! CHECK: return
+subroutine test_dynamic_static_score_order(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: barrier) &
+ !$omp & when(device={kind(host)}: taskwait) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! The explicit directive variant wins this tie over the earlier implicit
+! nothing candidate.
+! CHECK-LABEL: func.func @_QPtest_dynamic_implicit_nothing_tie_break(
+! CHECK-NOT: fir.if
+! CHECK: omp.barrier
+! CHECK: return
+subroutine test_dynamic_implicit_nothing_tie_break(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}, user={condition(flag)}:) &
+ !$omp & when(implementation={vendor(llvm)}: barrier)
+end subroutine
+
! CHECK-LABEL: func.func @_QPtest_two_dynamic(
! CHECK-SAME: %[[ARG0:[^,]*]]: !fir.ref<!fir.logical<4>>
! CHECK-SAME: %[[ARG1:.*]]: !fir.ref<!fir.logical<4>>
More information about the llvm-branch-commits
mailing list