[llvm-branch-commits] [flang] [flang][OpenMP] Fix variant selector identity and candidate ranking (PR #231676)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Oct 14 11:57:24 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/231676
>From 0576b3074cad2aa517ae296e8192843119e0b801 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 13 Oct 2026 17:00:56 -0500
Subject: [PATCH] [flang][OpenMP] Rank metadirective candidates once
When a runtime guard fails, Flang reranks the remaining METADIRECTIVE
candidates. A selector that was a strict subset of the failed candidate
then gets its raw score back and can overtake a better static candidate.
With vendor(score(100): llvm) for TASKYIELD, vendor(score(1): llvm) plus
condition(score(20): flag) for BARRIER, and condition(score(10): .true.)
for TASKWAIT, a false flag selects TASKYIELD instead of TASKWAIT.
This patch ranks the candidates once against the complete set and keeps
that order on every runtime path. It also scores an implicit NOTHING like
an explicit replacement, so explicitness only breaks ties. Under
match_none, the guarded candidate now carries a false condition instead
of an unknown one, since that's the outcome it's selected on. Construct
selectors now recognize construct={simd}.
Split from #224431.
Assisted-by: GitHub Copilot (Claude Opus 5.5)
---
flang/include/flang/Semantics/openmp-utils.h | 21 +-
flang/lib/Lower/OpenMP/OpenMP.cpp | 29 +-
flang/lib/Semantics/openmp-utils.cpp | 143 +++----
.../declare-variant-condition-identity.f90 | 19 +
.../OpenMP/metadirective-user-static.f90 | 81 ++++
.../test/Lower/OpenMP/metadirective-user.f90 | 364 ++++++++++++++----
6 files changed, 451 insertions(+), 206 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/metadirective-user-static.f90
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index c31459f256f622..e8ace181dff7dc 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -279,6 +279,9 @@ struct MetadirectiveCandidate {
struct MetadirectiveCandidateSet {
llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
+ /// Candidate indices in selection order, including global subset scores.
+ /// Filtering this list must preserve its order without recomputing scores.
+ llvm::SmallVector<unsigned, 4> order;
/// Null represents either an explicit NOTHING fallback or no fallback.
const parser::OmpDirectiveSpecification *fallback{nullptr};
};
@@ -291,11 +294,6 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
const parser::OmpClauseList &clauses, SemanticsContext &context,
const OmpVariantMatchContext &matchContext);
-std::optional<unsigned> SelectBestMetadirectiveCandidate(
- llvm::ArrayRef<unsigned> candidateIndices,
- llvm::ArrayRef<MetadirectiveCandidate> candidates,
- const OmpVariantMatchContext &matchContext);
-
/// Return true when repeated evaluation of \p condition cannot call a
/// procedure or observe asynchronously changing state.
bool IsRepeatableMetadirectiveCondition(
@@ -305,18 +303,19 @@ bool IsRepeatableMetadirectiveCondition(
bool AreSameRepeatableMetadirectiveCondition(const parser::ScalarExpr &left,
const parser::ScalarExpr &right, SemanticsContext &context);
-/// Return candidates reachable after \p selectedIndex fails. Equal repeatable
-/// guards are pruned until a non-repeatable guard is encountered.
+/// Return candidates reachable after the first candidate fails. The nonempty
+/// \p candidateIndices list must be in selection order. Equal repeatable guards
+/// are pruned until an unguarded or non-repeatable candidate is encountered.
llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
- unsigned selectedIndex, llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<unsigned> candidateIndices,
llvm::ArrayRef<MetadirectiveCandidate> candidates,
- const OmpVariantMatchContext &matchContext, SemanticsContext &context);
+ SemanticsContext &context);
/// Return every replacement that can be selected, retaining lower-ranked
/// candidates after a dynamic condition. Null represents NOTHING.
llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
-GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
- const OmpVariantMatchContext &matchContext, SemanticsContext &context);
+GetReachableMetadirectiveVariants(
+ const MetadirectiveCandidateSet &candidateSet, SemanticsContext &context);
/// True if a variant guarded by \p selector may be selected in the current
/// compilation context.
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 57978826c5d014..06d1e0972cd7e4 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -7483,14 +7483,9 @@ static void genMetadirective(lower::AbstractConverter &converter,
auto &candidates = candidateSet->candidates;
const parser::OmpDirectiveSpecification *fallback = candidateSet->fallback;
- llvm::SmallVector<unsigned, 4> allCandidateIndices;
- allCandidateIndices.reserve(candidates.size());
- for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
- allCandidateIndices.push_back(idx);
-
llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
reachableVariantSpecs = semantics::omp::GetReachableMetadirectiveVariants(
- *candidateSet, ompCtx, semaCtx);
+ *candidateSet, semaCtx);
bool hasLoopAssociatedCandidate =
llvm::any_of(reachableVariantSpecs, [](const auto *spec) {
@@ -7694,7 +7689,7 @@ static void genMetadirective(lower::AbstractConverter &converter,
genMetadirectiveBody();
};
- llvm::SmallVector<unsigned, 4> remainingCandidates{allCandidateIndices};
+ llvm::SmallVector<unsigned, 4> remainingCandidates{candidateSet->order};
lower::StatementContext stmtCtx;
@@ -7714,24 +7709,16 @@ static void genMetadirective(lower::AbstractConverter &converter,
// If the else path selects the same unguarded directive, lower it directly.
// Stop when selection reaches an unguarded candidate or the fallback.
while (!remainingCandidates.empty()) {
- std::optional<unsigned> selected =
- semantics::omp::SelectBestMetadirectiveCandidate(remainingCandidates,
- candidates, ompCtx);
- if (!selected) {
- genVariant(fallback);
- return;
- }
-
const semantics::omp::MetadirectiveCandidate &candidate =
- candidates[*selected];
+ candidates[remainingCandidates.front()];
if (!candidate.dynamicCondition) {
genVariant(candidate.spec);
return;
}
llvm::SmallVector<unsigned, 4> elsePathCandidates =
- semantics::omp::GetMetadirectiveElsePathCandidates(
- *selected, remainingCandidates, candidates, ompCtx, semaCtx);
+ semantics::omp::GetMetadirectiveElsePathCandidates(remainingCandidates,
+ candidates, semaCtx);
// match_any may create a guarded condition-true candidate and an unguarded
// static candidate for the same directive. If the else path picks the
@@ -7739,11 +7726,9 @@ static void genMetadirective(lower::AbstractConverter &converter,
//
// if (flag) barrier into just barrier
// else barrier
- if (std::optional<unsigned> selectedInElse =
- semantics::omp::SelectBestMetadirectiveCandidate(
- elsePathCandidates, candidates, ompCtx)) {
+ if (!elsePathCandidates.empty()) {
const semantics::omp::MetadirectiveCandidate &candidateInElse =
- candidates[*selectedInElse];
+ candidates[elsePathCandidates.front()];
if (!candidateInElse.dynamicCondition &&
candidateInElse.spec == candidate.spec) {
genVariant(candidate.spec);
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 49d46c5032c699..b0efa6d163f2ea 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -2623,6 +2623,13 @@ static void AddTraitPropertiesFromSelector(llvm::omp::TraitSet set,
// the selector itself implies the property.
if (const auto *dir{std::get_if<llvm::omp::Directive>(&traitName.u)}) {
AppendConstructTraitsForDirective(*dir, vmi);
+ } else if (const auto *value{std::get_if<parser::OmpTraitSelectorName::Value>(
+ &traitName.u)}) {
+ // SIMD is a predefined selector name because it can take clause
+ // properties, unlike the other construct selectors.
+ if (*value == parser::OmpTraitSelectorName::Value::Simd) {
+ AppendConstructTraitsForDirective(llvm::omp::Directive::OMPD_simd, vmi);
+ }
}
}
@@ -2747,7 +2754,7 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
auto addConditionTraitForRanking =
[&](llvm::omp::VariantMatchInfo &rankingVMI) {
rankingVMI.addTrait(hasMatchNone
- ? dynamicConditionTrait
+ ? llvm::omp::TraitProperty::user_condition_false
: llvm::omp::TraitProperty::user_condition_true,
conditionIdentity, conditionScorePtr);
};
@@ -2755,14 +2762,11 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
if (hasMatchAny && isStaticVMIApplicable) {
// Represent both outcomes: a guarded candidate with the condition's
// score and an unguarded candidate whose false condition preserves
- // selector identity for subset checks. If the WHEN clause omits its
- // directive, only add the unguarded candidate.
- if (isExplicit) {
- llvm::omp::VariantMatchInfo conditionTrueVMI{staticVMI};
- addConditionTraitForRanking(conditionTrueVMI);
- result.candidates.push_back({spec, std::move(conditionTrueVMI),
- isExplicit, dynamicCondition});
- }
+ // selector identity for subset checks.
+ llvm::omp::VariantMatchInfo conditionTrueVMI{staticVMI};
+ addConditionTraitForRanking(conditionTrueVMI);
+ result.candidates.push_back({spec, std::move(conditionTrueVMI),
+ isExplicit, dynamicCondition});
staticVMI.addTrait(llvm::omp::TraitProperty::user_condition_false,
conditionIdentity, conditionScorePtr);
result.candidates.push_back({spec, std::move(staticVMI), isExplicit});
@@ -2770,19 +2774,9 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
}
llvm::omp::VariantMatchInfo rankingVMI{staticVMI};
- if (isExplicit) {
- addConditionTraitForRanking(rankingVMI);
- } else {
- // Preserve the existing lowering behavior for an omitted directive:
- // do not let its runtime condition raise the implicit NOTHING rank,
- // but keep the condition's identity for subset checks.
- if (hasMatchAny && !isStaticVMIApplicable)
- rankingVMI = llvm::omp::VariantMatchInfo();
- rankingVMI.addTrait(hasMatchNone
- ? dynamicConditionTrait
- : llvm::omp::TraitProperty::user_condition_true,
- conditionIdentity);
- }
+ // An implicit NOTHING is scored like an explicit replacement;
+ // explicitness only breaks ties.
+ addConditionTraitForRanking(rankingVMI);
result.candidates.push_back({spec, std::move(rankingVMI), isExplicit,
dynamicCondition, /*conditionShouldBeTrue=*/!hasMatchNone});
continue;
@@ -2802,50 +2796,25 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
result.fallback = getFallbackVariant(defaultVariantClause->v.v.value());
}
}
+ // Rank against the complete set once. Removing a failed runtime guard must
+ // not restore the raw score of a selector that was its strict subset.
+ llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
+ for (const auto &[index, candidate] : llvm::enumerate(result.candidates)) {
+ vmis.push_back(candidate.vmi);
+ result.order.push_back(index);
+ }
+ auto scores{llvm::omp::getVariantMatchScores(vmis, matchContext)};
+ llvm::stable_sort(result.order, [&](unsigned a, unsigned b) {
+ CHECK(scores[a] && scores[b]);
+ const auto &left{*scores[a]}, &right{*scores[b]};
+ unsigned width{std::max(left.getBitWidth(), right.getBitWidth())};
+ if (left.zextOrTrunc(width) != right.zextOrTrunc(width))
+ return left.zextOrTrunc(width).ugt(right.zextOrTrunc(width));
+ return result.candidates[a].isExplicit && !result.candidates[b].isExplicit;
+ });
return result;
}
-std::optional<unsigned> SelectBestMetadirectiveCandidate(
- llvm::ArrayRef<unsigned> candidateIndices,
- llvm::ArrayRef<MetadirectiveCandidate> candidates,
- const OmpVariantMatchContext &matchContext) {
- if (candidateIndices.empty()) {
- return std::nullopt;
- }
- if (candidateIndices.size() == 1) {
- return candidateIndices.front();
- }
-
- // The context scorer preserves input order for ties. Explicit replacements
- // take precedence over an omitted directive's implicit NOTHING.
- llvm::SmallVector<unsigned, 4> candidateOrder;
- candidateOrder.reserve(candidateIndices.size());
- for (unsigned index : candidateIndices) {
- if (candidates[index].isExplicit) {
- candidateOrder.push_back(index);
- }
- }
- for (unsigned index : candidateIndices) {
- if (!candidates[index].isExplicit) {
- candidateOrder.push_back(index);
- }
- }
-
- llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
- orderedVMIs.reserve(candidateOrder.size());
- for (unsigned index : candidateOrder) {
- orderedVMIs.push_back(candidates[index].vmi);
- }
-
- int bestIndex{
- llvm::omp::getBestVariantMatchForContext(orderedVMIs, matchContext)};
- if (bestIndex < 0) {
- return std::nullopt;
- }
- CHECK(static_cast<std::size_t>(bestIndex) < candidateOrder.size());
- return candidateOrder[bestIndex];
-}
-
namespace {
struct MetadirectiveConditionNormalizer : evaluate::rewrite::Identity {
using evaluate::rewrite::Identity::operator();
@@ -2908,28 +2877,23 @@ bool AreSameRepeatableMetadirectiveCondition(const parser::ScalarExpr &left,
}
llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
- unsigned selectedIndex, llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<unsigned> candidateIndices,
llvm::ArrayRef<MetadirectiveCandidate> candidates,
- const OmpVariantMatchContext &matchContext, SemanticsContext &context) {
- CHECK(selectedIndex < candidates.size());
- const MetadirectiveCandidate &selected{candidates[selectedIndex]};
+ SemanticsContext &context) {
+ CHECK(!candidateIndices.empty() &&
+ candidateIndices.front() < candidates.size());
+ const MetadirectiveCandidate &selected{candidates[candidateIndices.front()]};
CHECK(selected.dynamicCondition);
- llvm::SmallVector<unsigned, 4> result;
- result.reserve(candidateIndices.size());
- for (unsigned index : candidateIndices)
- if (index != selectedIndex)
- result.push_back(index);
+ llvm::SmallVector<unsigned, 4> result{candidateIndices.drop_front()};
// Inspect candidates in the order in which selection would evaluate them.
// A distinct repeatable condition cannot modify the selected condition, so
// the failed value remains usable past it. Stop at the first non-repeatable
// condition because it can change state before a lower-ranked occurrence is
// evaluated.
- llvm::SmallVector<unsigned, 4> candidatesToInspect{result};
- while (std::optional<unsigned> next{SelectBestMetadirectiveCandidate(
- candidatesToInspect, candidates, matchContext)}) {
- const MetadirectiveCandidate &candidate{candidates[*next]};
+ for (unsigned index : candidateIndices.drop_front()) {
+ const MetadirectiveCandidate &candidate{candidates[index]};
if (!candidate.dynamicCondition ||
!IsRepeatableMetadirectiveCondition(
*candidate.dynamicCondition->expr, context))
@@ -2941,31 +2905,25 @@ llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
*selected.dynamicCondition->expr, *candidate.dynamicCondition->expr,
context)};
if (hasSameFailedCondition)
- llvm::erase(result, *next);
- llvm::erase(candidatesToInspect, *next);
+ llvm::erase(result, index);
}
return result;
}
llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
-GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
- const OmpVariantMatchContext &matchContext, SemanticsContext &context) {
- llvm::SmallVector<unsigned, 4> candidates;
- candidates.reserve(candidateSet.candidates.size());
- for (unsigned index{0}; index < candidateSet.candidates.size(); ++index) {
- candidates.push_back(index);
- }
+GetReachableMetadirectiveVariants(
+ const MetadirectiveCandidateSet &candidateSet, SemanticsContext &context) {
+ llvm::SmallVector<unsigned, 4> candidates{candidateSet.order};
llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4> reachable;
while (true) {
- std::optional<unsigned> selected{SelectBestMetadirectiveCandidate(
- candidates, candidateSet.candidates, matchContext)};
- if (!selected) {
+ if (candidates.empty()) {
reachable.push_back(candidateSet.fallback);
break;
}
- const MetadirectiveCandidate &candidate{candidateSet.candidates[*selected]};
+ const MetadirectiveCandidate &candidate{
+ candidateSet.candidates[candidates.front()]};
reachable.push_back(candidate.spec);
// An unguarded winner ends selection. A dynamic winner leaves the
// remaining candidates reachable through its false path.
@@ -2974,12 +2932,11 @@ GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
}
candidates = GetMetadirectiveElsePathCandidates(
- *selected, candidates, candidateSet.candidates, matchContext, context);
+ candidates, candidateSet.candidates, context);
- if (std::optional<unsigned> selectedInElse{SelectBestMetadirectiveCandidate(
- candidates, candidateSet.candidates, matchContext)}) {
+ if (!candidates.empty()) {
const MetadirectiveCandidate &elseCandidate{
- candidateSet.candidates[*selectedInElse]};
+ candidateSet.candidates[candidates.front()]};
if (!elseCandidate.dynamicCondition &&
elseCandidate.spec == candidate.spec) {
break;
diff --git a/flang/test/Lower/OpenMP/declare-variant-condition-identity.f90 b/flang/test/Lower/OpenMP/declare-variant-condition-identity.f90
index 3cce9cd3f35091..f4669e8cb15203 100644
--- a/flang/test/Lower/OpenMP/declare-variant-condition-identity.f90
+++ b/flang/test/Lower/OpenMP/declare-variant-condition-identity.f90
@@ -68,6 +68,16 @@
! IMPORTED-NOT: omp.taskyield
! IMPORTED: return
+! Each match_any outcome of the first selector is a strict subset of the same
+! outcome of the second, so TASKWAIT is selected without a runtime branch.
+! IMPORTED-LABEL: func.func @_QPruntime_any(
+! IMPORTED-NOT: fir.if
+! IMPORTED-NOT: omp.taskyield
+! IMPORTED: omp.taskwait
+! IMPORTED-NOT: fir.if
+! IMPORTED-NOT: omp.taskyield
+! IMPORTED: return
+
!--- settings.f90
module settings
logical, parameter :: on = .true., other = .true.
@@ -302,6 +312,15 @@ subroutine false_conditions()
!$omp& implementation={extension(match_none), vendor(score(1): gnu)}: taskwait)
end subroutine
+subroutine runtime_any()
+ use settings, only: flag, alias => flag
+ !$omp metadirective &
+ !$omp& when(user={condition(flag)}, &
+ !$omp& implementation={extension(match_any), vendor(score(100): llvm)}: taskyield) &
+ !$omp& when(user={condition((alias))}, device={kind(cpu)}, &
+ !$omp& implementation={extension(match_any), vendor(score(1): llvm)}: taskwait)
+end subroutine
+
! Relational operator spellings share an identity in runtime conditions.
! IMPORTED-LABEL: func.func @_QPruntime_operators(
! IMPORTED-NOT: omp.taskyield
diff --git a/flang/test/Lower/OpenMP/metadirective-user-static.f90 b/flang/test/Lower/OpenMP/metadirective-user-static.f90
new file mode 100644
index 00000000000000..340b7c25293c87
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-user-static.f90
@@ -0,0 +1,81 @@
+! Test lowering of OpenMP metadirective with static user={condition()}
+! selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | \
+! RUN: FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | \
+! RUN: FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 %s -o - | \
+! RUN: FileCheck %s
+
+! Large non-negative scores must not wrap their sum to zero.
+! CHECK-LABEL: func.func @_QPtest_wide_score()
+! CHECK: omp.parallel
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_wide_score()
+ !$omp parallel
+ !$omp metadirective &
+ !$omp& when(user={condition(score(9223372036854775807_8): .true.)}, &
+ !$omp& implementation={vendor(score(9223372036854775807_8): llvm)}, &
+ !$omp& construct={parallel}: barrier) &
+ !$omp& when(user={condition(.true.)}: taskyield)
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_condition_true()
+! CHECK: omp.taskyield
+! CHECK-NOT: fir.if
+! CHECK: return
+subroutine test_condition_true()
+ !$omp metadirective &
+ !$omp & when(user={condition(.true.)}: taskyield)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_condition_false()
+! CHECK-NOT: omp.taskwait
+! CHECK-NOT: fir.if
+! CHECK: return
+subroutine test_condition_false()
+ !$omp metadirective &
+ !$omp & when(user={condition(.false.)}: taskwait)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_condition_score()
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.taskwait
+! CHECK: return
+subroutine test_condition_score()
+ !$omp metadirective &
+ !$omp & when(user={condition(.true.)}: taskyield) &
+ !$omp & when(user={condition(score(2): .true.)}: taskwait)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_condition_true()
+! CHECK: omp.parallel
+! CHECK: omp.terminator
+! CHECK-NOT: fir.if
+! CHECK: return
+subroutine test_begin_condition_true()
+ integer :: x
+ x = 0
+ !$omp begin metadirective &
+ !$omp & when(user={condition(.true.)}: parallel)
+ x = 1
+ !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_condition_false()
+! CHECK-NOT: omp.parallel
+! CHECK-NOT: fir.if
+! CHECK: return
+subroutine test_begin_condition_false()
+ integer :: x
+ x = 0
+ !$omp begin metadirective &
+ !$omp & when(user={condition(.false.)}: parallel)
+ x = 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 57e429bdd83549..a96b7b624743cb 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -1,93 +1,204 @@
-! Test lowering of OpenMP metadirective with user={condition()} selectors.
+! Test lowering of OpenMP metadirective with dynamic 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
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -cpp -DOMP_50 %s \
+! RUN: -o - | FileCheck --check-prefix=OMP50 %s
+
+#ifdef OMP_50
+! OpenMP 5.0 has no NOTHING directive, so it gets its own runtime cases.
+
+! A strict subset loses to its dynamic superset despite a higher score.
+! OMP50-LABEL: func.func @_QPtest_omp50_dynamic_subset_order(
+! OMP50: fir.if
+! OMP50-NEXT: omp.barrier
+! OMP50-NEXT: } else {
+! OMP50-NEXT: omp.taskwait
+! OMP50-NOT: omp.taskyield
+! OMP50: return
+subroutine test_omp50_dynamic_subset_order(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(100): llvm)}: taskyield) &
+ !$omp& when(implementation={vendor(score(1): llvm)}, &
+ !$omp& user={condition(score(20): flag)}: barrier) &
+ !$omp& when(user={condition(score(10): .true.)}: taskwait)
+end subroutine
+
+! DEFAULT supplies the fallback when the runtime condition is false.
+! OMP50-LABEL: func.func @_QPtest_omp50_default(
+! OMP50: fir.if
+! OMP50-NEXT: omp.barrier
+! OMP50-NEXT: } else {
+! OMP50-NEXT: omp.taskwait
+! OMP50: return
+subroutine test_omp50_default(flag)
+ logical :: flag
+ !$omp metadirective when(user={condition(flag)}: barrier) default(taskwait)
+end subroutine
+#else
!===----------------------------------------------------------------------===!
-! Static (constant-folded) user conditions
+! Unknown ARCH retains its weight even when only the runtime condition matches.
+! CHECK-LABEL: func.func @_QPtest_unknown_arch_weight(
+! CHECK: fir.if
+! CHECK-NEXT: omp.barrier
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskyield
+! CHECK: return
+subroutine test_unknown_arch_weight(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(device={arch(bogus_arch)}, &
+ !$omp& implementation={extension(match_any)}, &
+ !$omp& user={condition(flag)}: barrier) &
+ !$omp& when(user={condition(score(1): .true.)}: taskyield)
+end subroutine
+
+! Selectors with unknown properties retain their separate scores,
+! regardless of selector order.
+! CHECK-LABEL: func.func @_QPtest_unknown_selector_scores(
+! CHECK: fir.if
+! CHECK-NEXT: omp.barrier
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskyield
+! CHECK: return
+subroutine test_unknown_selector_scores(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(10): bogus_vendor), &
+ !$omp& extension(score(1): match_any, bogus_extension)}, &
+ !$omp& user={condition(score(5): flag)}: barrier) &
+ !$omp& when(implementation={vendor(score(10): llvm)}: taskyield)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_unknown_selector_scores_reversed(
+! CHECK: fir.if
+! CHECK-NEXT: omp.barrier
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskyield
+! CHECK: return
+subroutine test_unknown_selector_scores_reversed(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={extension(score(1): match_any, bogus_extension), &
+ !$omp& vendor(score(10): bogus_vendor)}, &
+ !$omp& user={condition(score(5): flag)}: barrier) &
+ !$omp& when(implementation={vendor(score(10): llvm)}: taskyield)
+end subroutine
+
+! An unknown vendor does not veto a runtime MATCH_ANY condition or its score.
+! CHECK-LABEL: func.func @_QPtest_dynamic_unknown_vendor(
+! CHECK: fir.if
+! CHECK-NEXT: omp.barrier
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskyield
+! CHECK: return
+subroutine test_dynamic_unknown_vendor(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(bogus_vendor), extension(match_any)}, &
+ !$omp& user={condition(score(5): flag)}: barrier) &
+ !$omp& when(user={condition(.true.)}: taskyield)
+end subroutine
+
+! The same rule applies to unknown device traits and implicit NOTHING.
+! CHECK-LABEL: func.func @_QPtest_dynamic_unknown_arch_implicit(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_dynamic_unknown_arch_implicit(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(device={arch(bogus_arch)}, &
+ !$omp& implementation={extension(match_any)}, &
+ !$omp& user={condition(score(10): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
+end subroutine
+
+! Scored implicit NOTHING competes with explicit replacements by score.
!===----------------------------------------------------------------------===!
-! CHECK-LABEL: func.func @_QPtest_condition_true()
-! CHECK: omp.taskyield
-! CHECK-NOT: fir.if
-! CHECK: return
-subroutine test_condition_true()
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_score(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_implicit_nothing_score(flag)
+ logical :: flag
!$omp metadirective &
- !$omp & when(user={condition(.true.)}: taskyield) &
-#ifdef OMP_52
- !$omp & otherwise(nothing)
-#else
- !$omp & default(nothing)
-#endif
+ !$omp& when(user={condition(score(10): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
end subroutine
-! CHECK-LABEL: func.func @_QPtest_condition_false()
-! CHECK-NOT: omp.taskwait
-! CHECK-NOT: fir.if
-! CHECK: return
-subroutine test_condition_false()
+! Explicit NOTHING with the same score must produce the same selection.
+! CHECK-LABEL: func.func @_QPtest_explicit_nothing_score(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_explicit_nothing_score(flag)
+ logical :: flag
!$omp metadirective &
- !$omp & when(user={condition(.false.)}: taskwait) &
-#ifdef OMP_52
- !$omp & otherwise(nothing)
-#else
- !$omp & default(nothing)
-#endif
+ !$omp& when(user={condition(score(10): flag)}: nothing) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
end subroutine
-! CHECK-LABEL: func.func @_QPtest_condition_score()
-! CHECK-NOT: omp.taskyield
-! CHECK: omp.taskwait
-! CHECK: return
-subroutine test_condition_score()
+! Equal scores favor the explicit replacement without a runtime branch.
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_equal_score(
+! CHECK-NOT: fir.if
+! CHECK: omp.barrier
+! CHECK-NEXT: return
+subroutine test_implicit_nothing_equal_score(flag)
+ logical :: flag
!$omp metadirective &
- !$omp & when(user={condition(.true.)}: taskyield) &
- !$omp & when(user={condition(score(2): .true.)}: taskwait) &
-#ifdef OMP_52
- !$omp & otherwise(nothing)
-#else
- !$omp & default(nothing)
-#endif
+ !$omp& when(user={condition(score(5): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
end subroutine
-! CHECK-LABEL: func.func @_QPtest_begin_condition_true()
-! CHECK: omp.parallel
-! CHECK: omp.terminator
-! CHECK-NOT: fir.if
-! CHECK: return
-subroutine test_begin_condition_true()
- integer :: x
- x = 0
-#ifdef OMP_52
- !$omp begin metadirective &
- !$omp & when(user={condition(.true.)}: parallel) &
- !$omp & otherwise(nothing)
-#else
- !$omp begin metadirective &
- !$omp & when(user={condition(.true.)}: parallel)
-#endif
- x = 1
- !$omp end metadirective
+! MATCH_ANY still needs a scored runtime candidate when a static trait matches.
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_match_any_static(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_implicit_nothing_match_any_static(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm), extension(match_any)}, &
+ !$omp& user={condition(score(10): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
end subroutine
-! CHECK-LABEL: func.func @_QPtest_begin_condition_false()
-! CHECK-NOT: omp.parallel
-! CHECK-NOT: fir.if
-! CHECK: return
-subroutine test_begin_condition_false()
- integer :: x
- x = 0
-#ifdef OMP_52
- !$omp begin metadirective &
- !$omp & when(user={condition(.false.)}: parallel) &
- !$omp & otherwise(nothing)
-#else
- !$omp begin metadirective &
- !$omp & when(user={condition(.false.)}: parallel)
-#endif
- x = 1
- !$omp end metadirective
+! MATCH_ANY can also depend entirely on the runtime condition.
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_match_any_runtime(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_implicit_nothing_match_any_runtime(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(gnu), extension(match_any)}, &
+ !$omp& user={condition(score(10): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
+end subroutine
+
+! MATCH_NONE retains the score but selects NOTHING when the condition is false.
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_match_none(
+! CHECK: arith.xori
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.barrier
+! CHECK: return
+subroutine test_implicit_nothing_match_none(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={extension(match_none)}, &
+ !$omp& user={condition(score(10): flag)}:) &
+ !$omp& when(user={condition(score(5): .true.)}: barrier)
end subroutine
!===----------------------------------------------------------------------===!
@@ -331,8 +442,21 @@ subroutine test_dynamic_user_score_order(low, high)
!$omp & when(device={kind(host)}: taskwait)
end subroutine
-! Under extension(match_none), the runtime condition selects the variant when it
-! is false. Its score is still available for ranking that false path.
+! A statically false MATCH_NONE condition is the selector that makes the
+! candidate applicable, so its score participates in ranking.
+! CHECK-LABEL: func.func @_QPtest_static_user_score_match_none()
+! CHECK-NOT: fir.if
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskwait
+! CHECK: return
+subroutine test_static_user_score_match_none()
+ !$omp metadirective &
+ !$omp & when(implementation={extension(match_none)}, &
+ !$omp & user={condition(score(100): .false.)}: barrier) &
+ !$omp & when(user={condition(score(10): .true.)}: taskwait)
+end subroutine
+
+! The dynamic form must use the same score on its false path.
! CHECK-LABEL: func.func @_QPtest_dynamic_user_score_match_none(
! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
@@ -349,12 +473,9 @@ subroutine test_dynamic_user_score_order(low, high)
subroutine test_dynamic_user_score_match_none(flag)
logical, intent(in) :: flag
!$omp metadirective &
- !$omp & when(implementation={extension(match_none)}, user={condition(score(5): flag)}: barrier) &
-#ifdef OMP_52
- !$omp & otherwise(taskwait)
-#else
- !$omp & default(taskwait)
-#endif
+ !$omp & when(implementation={extension(match_none)}, &
+ !$omp & user={condition(score(100): flag)}: barrier) &
+ !$omp & when(user={condition(score(10): .true.)}: taskwait)
end subroutine
! Under extension(match_any), a dynamic condition can be the selector that makes
@@ -425,6 +546,53 @@ subroutine test_dynamic_user_match_any_static_score(flag)
#endif
end subroutine
+! A statically false condition follows the dynamic false-path rule: its score
+! does not contribute when another selector satisfies MATCH_ANY.
+! CHECK-LABEL: func.func @_QPtest_static_user_match_any_false_score()
+! CHECK-NOT: fir.if
+! CHECK-NOT: omp.barrier
+! CHECK: omp.taskwait
+! CHECK: return
+subroutine test_static_user_match_any_false_score()
+ !$omp metadirective &
+ !$omp& when(implementation={extension(match_any), vendor(llvm)}, &
+ !$omp& user={condition(score(100): .false.)}: barrier) &
+ !$omp& when(user={condition(score(10): .true.)}: taskwait) &
+#ifdef OMP_52
+ !$omp& otherwise(nothing)
+#else
+ !$omp& default(nothing)
+#endif
+end subroutine
+
+! A false user condition remains part of the selector for specificity. The
+! more-specific MATCH_ANY candidate therefore wins over the vendor-only
+! candidate in both the static and dynamic forms.
+! CHECK-LABEL: func.func @_QPtest_static_user_match_any_false_subset()
+! CHECK-NOT: fir.if
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskwait
+! CHECK: return
+subroutine test_static_user_match_any_false_subset()
+ !$omp metadirective &
+ !$omp& when(implementation={extension(match_any), vendor(llvm)}, &
+ !$omp& user={condition(.false.)}: barrier) &
+ !$omp& when(implementation={vendor(llvm)}: taskwait)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_user_match_any_false_subset(
+! CHECK-NOT: fir.if
+! CHECK: omp.barrier
+! CHECK-NOT: omp.taskwait
+! CHECK: return
+subroutine test_dynamic_user_match_any_false_subset(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={extension(match_any), vendor(llvm)}, &
+ !$omp& user={condition(flag)}: barrier) &
+ !$omp& when(implementation={vendor(llvm)}: taskwait)
+end subroutine
+
! The vendor-only selector is a strict subset of the implicit NOTHING's
! selector. The user condition determines which replacement is selected.
! CHECK-LABEL: func.func @_QPtest_dynamic_implicit_nothing_more_specific(
@@ -546,6 +714,41 @@ subroutine test_multi_dynamic_multi_static(a, b)
#endif
end subroutine
+! Rank once: the vendor-only selector scores zero as a subset of the guarded
+! candidate. If that guard fails, its subset must not regain score 101 and
+! overtake the static candidate (score 11). The original order is 22, 11, 0.
+! CHECK-LABEL: func.func @_QPtest_dynamic_subset_order(
+! CHECK: fir.if
+! CHECK-NEXT: omp.barrier
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskwait
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_dynamic_subset_order(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(100): llvm)}: taskyield) &
+ !$omp& when(implementation={vendor(score(1): llvm)}, &
+ !$omp& user={condition(score(20): flag)}: barrier) &
+ !$omp& when(user={condition(score(10): .true.)}: taskwait)
+end subroutine
+
+! The same ordering applies when the guarded replacement is implicit NOTHING.
+! CHECK-LABEL: func.func @_QPtest_dynamic_subset_nothing_order(
+! CHECK: fir.if
+! CHECK-NEXT: } else {
+! CHECK-NEXT: omp.taskwait
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_dynamic_subset_nothing_order(flag)
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(score(100): llvm)}: taskyield) &
+ !$omp& when(implementation={vendor(score(1): llvm)}, &
+ !$omp& user={condition(score(20): flag)}:) &
+ !$omp& when(user={condition(score(10): .true.)}: taskwait)
+end subroutine
+
!===----------------------------------------------------------------------===!
! Condition identity in strict-subset checks
!===----------------------------------------------------------------------===!
@@ -603,3 +806,4 @@ subroutine test_implicit_nothing_distinct_conditions(a, b)
!$omp & default(taskyield)
#endif
end subroutine
+#endif
More information about the llvm-branch-commits
mailing list