[flang-commits] [flang] [llvm] [flang][OpenMP] Don't treat different runtime conditions as subsets (PR #233493)
via flang-commits
flang-commits at lists.llvm.org
Mon Oct 19 11:56:12 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/233493
>From 1e6dd889f4b71f3bff4a90ba3701e4305e86da22 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Sat, 17 Oct 2026 22:32:07 -0500
Subject: [PATCH] [flang][OpenMP] Don't treat different runtime conditions as
subsets
When two METADIRECTIVE candidates have the same score and one selector is
a strict subset of the other, the superset wins. Flang ranks every
runtime condition with the same property, so isStrictSubset saw
different conditions as the same:
!$omp metadirective &
!$omp& when(user={condition(a)}, &
!$omp& implementation={vendor(score(4): llvm)}: taskyield) &
!$omp& when(user={condition(b)}, device={kind(host)}, &
!$omp& implementation={vendor(score(3): llvm)}: taskwait)
Both candidates score 5, and `b` was tested first, so TASKWAIT ran when
both conditions were true. Neither selector is a subset of the other, so
clause order should decide.
This patch adds a UserCondition key to VariantMatchInfo, and
isStrictSubset requires equal keys when the first selector has a
condition. Flang gives each distinct runtime condition of a METADIRECTIVE
its own key. It compares the conditions as expressions, normalized as in
the existing check for repeated conditions, so condition(a) and
condition((a)) still form a strict subset. Like that check, it treats
different names for the same entity as different conditions. Conditions
that fold to a constant of any kind, including all of Clang's, leave the
key empty and are still compared by truth value. A constant condition
and a runtime condition are now different.
Fixes #233038.
Assisted-by: GitHub Copilot (Claude Opus 5.5)
---
flang/lib/Semantics/openmp-utils.cpp | 31 +++-
.../test/Lower/OpenMP/metadirective-user.f90 | 152 ++++++++++++++++++
.../include/llvm/Frontend/OpenMP/OMPContext.h | 4 +
llvm/lib/Frontend/OpenMP/OMPContext.cpp | 7 +
llvm/unittests/Frontend/OpenMPContextTest.cpp | 37 +++++
5 files changed, 228 insertions(+), 3 deletions(-)
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 46fef418fe18b..ffabc147c5dc2 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -2473,6 +2473,8 @@ static void AppendConstructTraitsForDirective(
add(llvm::omp::TraitProperty::construct_dispatch_dispatch);
}
+static SomeExpr NormalizeMetadirectiveCondition(const SomeExpr &expr);
+
static void AddTraitPropertiesFromSelector(llvm::omp::TraitSet set,
const parser::OmpTraitSelector &selector, llvm::omp::VariantMatchInfo &vmi,
SemanticsContext &semaCtx,
@@ -2579,6 +2581,22 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
return spec.DirId() == llvm::omp::Directive::OMPD_nothing ? nullptr : &spec;
};
+ // Identify each distinct runtime condition, so that different conditions
+ // are not strict subsets of each other.
+ llvm::SmallVector<SomeExpr, 4> runtimeConditions;
+ auto getConditionKey = [&](const DynamicUserCondition &condition) {
+ const SomeExpr *expr{GetExpr(context, *condition.expr)};
+ if (!expr ||
+ evaluate::IsActuallyConstant(
+ evaluate::Fold(context.foldingContext(), common::Clone(*expr))))
+ return std::string{};
+ SomeExpr normalized{NormalizeMetadirectiveCondition(*expr)};
+ auto *it{llvm::find(runtimeConditions, normalized)};
+ if (it == runtimeConditions.end())
+ it = &runtimeConditions.emplace_back(std::move(normalized));
+ return std::to_string(it - runtimeConditions.begin() + 1);
+ };
+
for (const parser::OmpClause &clause : clauses.v) {
if (const auto *whenClause{
std::get_if<parser::OmpClause::When>(&clause.u)}) {
@@ -2593,6 +2611,8 @@ std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
llvm::omp::VariantMatchInfo rawVMI;
std::optional<DynamicUserCondition> dynamicCondition{
MakeVariantMatchInfo(rawVMI, *ctxSel, context)};
+ if (dynamicCondition)
+ rawVMI.UserCondition = getConditionKey(*dynamicCondition);
if (llvm::any_of(
rawVMI.ConstructTraits, [](llvm::omp::TraitProperty property) {
return llvm::omp::getOpenMPContextTraitSetForProperty(
@@ -2780,6 +2800,12 @@ bool isRepeatableMetadirectiveCondition(const SomeExpr &expr) {
}
} // namespace
+static SomeExpr NormalizeMetadirectiveCondition(const SomeExpr &expr) {
+ MetadirectiveConditionNormalizer normalizer;
+ evaluate::rewrite::Mutator normalize{normalizer};
+ return normalize(expr);
+}
+
bool IsRepeatableMetadirectiveCondition(
const parser::ScalarExpr &condition, SemanticsContext &context) {
const SomeExpr *expr{GetExpr(context, condition)};
@@ -2795,9 +2821,8 @@ bool AreSameRepeatableMetadirectiveCondition(const parser::ScalarExpr &left,
!isRepeatableMetadirectiveCondition(*rightExpr))
return false;
- MetadirectiveConditionNormalizer normalizer;
- evaluate::rewrite::Mutator normalize{normalizer};
- return normalize(*leftExpr) == normalize(*rightExpr);
+ return NormalizeMetadirectiveCondition(*leftExpr) ==
+ NormalizeMetadirectiveCondition(*rightExpr);
}
llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index cdfbddd4151a0..be066a8e94557 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -544,3 +544,155 @@ subroutine test_multi_dynamic_multi_static(a, b)
!$omp & default(nothing)
#endif
end subroutine
+
+! Both candidates score 5. The conditions differ, so neither selector is a
+! subset of the other and `a` is tested first.
+! CHECK-LABEL: func.func @_QPtest_distinct_conditions(
+! 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-NEXT: omp.taskyield
+! CHECK-NEXT: } else {
+! CHECK: %[[LOADB:.*]] = fir.load %[[DECLB]]#0
+! CHECK: %[[CONDB:.*]] = fir.convert %[[LOADB]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[CONDB]] {
+! CHECK-NEXT: omp.taskwait
+! CHECK: return
+subroutine test_distinct_conditions(a, b)
+ logical, intent(in) :: a, b
+ !$omp metadirective &
+ !$omp & when(user={condition(a)}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(b)}, device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
+
+! With the same condition, the first selector is a strict subset of the second.
+! CHECK-LABEL: func.func @_QPtest_same_condition(
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskwait
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_same_condition(a)
+ logical, intent(in) :: a
+ !$omp metadirective &
+ !$omp & when(user={condition(a)}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(a)}, device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
+
+! Outer parentheses don't change the condition.
+! CHECK-LABEL: func.func @_QPtest_parenthesized_condition(
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskwait
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_parenthesized_condition(a)
+ logical, intent(in) :: a
+ !$omp metadirective &
+ !$omp & when(user={condition((a))}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(a)}, device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
+
+! Under match_none, both candidates score 3 and `a` is tested first.
+! CHECK-LABEL: func.func @_QPtest_distinct_conditions_match_none(
+! CHECK-SAME: %[[ARG0:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DECLA:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[LOADA:.*]] = fir.load %[[DECLA]]#0
+! CHECK: %[[CONDA:.*]] = fir.convert %[[LOADA]] : (!fir.logical<4>) -> i1
+! CHECK: %[[NOTA:.*]] = arith.xori %[[CONDA]]
+! CHECK: fir.if %[[NOTA]] {
+! CHECK-NEXT: omp.taskyield
+! CHECK-NEXT: } else {
+! CHECK: omp.taskwait
+! CHECK: return
+subroutine test_distinct_conditions_match_none(a, b)
+ logical, intent(in) :: a, b
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): a)}, &
+ !$omp & implementation={extension(match_none)}: taskyield) &
+ !$omp & when(user={condition(b)}, device={arch(nvptx)}, &
+ !$omp & implementation={extension(match_none)}: taskwait)
+end subroutine
+
+! Constant conditions are compared by truth value, whatever their kind.
+! CHECK-LABEL: func.func @_QPtest_constant_condition_kinds(
+! CHECK-NOT: omp.taskyield
+! CHECK: omp.taskwait
+! CHECK-NOT: omp.taskyield
+! CHECK: return
+subroutine test_constant_condition_kinds()
+ !$omp metadirective &
+ !$omp & when(user={condition(.true._8)}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(.true._4)}, device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
+
+! Calls to intrinsics without arguments are evaluated at runtime.
+! CHECK-LABEL: func.func @_QPtest_intrinsic_call_conditions(
+! CHECK: arith.cmpi sgt, %{{.*}}, %c0
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskyield
+! CHECK-NEXT: } else {
+! CHECK: arith.cmpi sgt, %{{.*}}, %c1
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskwait
+! CHECK: return
+subroutine test_intrinsic_call_conditions()
+ !$omp metadirective &
+ !$omp & when(user={condition(command_argument_count() > 0)}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(command_argument_count() > 1)}, &
+ !$omp & device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
+
+module metadirective_user_a
+contains
+ logical function predicate()
+ predicate = .true.
+ end function
+ logical function invoke(p)
+ interface
+ logical function p()
+ end function
+ end interface
+ invoke = p()
+ end function
+end module
+
+module metadirective_user_b
+contains
+ logical function predicate()
+ predicate = .false.
+ end function
+end module
+
+! The conditions pass different procedures with the same name, so `a` is
+! tested first.
+! CHECK-LABEL: func.func @_QPtest_same_name_conditions(
+! CHECK: fir.address_of(@_QMmetadirective_user_aPpredicate)
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskyield
+! CHECK-NEXT: } else {
+! CHECK: fir.address_of(@_QMmetadirective_user_bPpredicate)
+! CHECK: fir.if
+! CHECK-NEXT: omp.taskwait
+! CHECK: return
+subroutine test_same_name_conditions()
+ use metadirective_user_a, only: a => predicate, invoke
+ use metadirective_user_b, only: b => predicate
+ !$omp metadirective &
+ !$omp & when(user={condition(invoke(a))}, &
+ !$omp & implementation={vendor(score(4): llvm)}: taskyield) &
+ !$omp & when(user={condition(invoke(b))}, device={kind(host)}, &
+ !$omp & implementation={vendor(score(3): llvm)}: taskwait)
+end subroutine
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPContext.h b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h
index 7849d32665994..c8c69823d59ed 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPContext.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h
@@ -22,6 +22,8 @@
#include "llvm/Frontend/OpenMP/OMPConstants.h"
#include "llvm/Support/Compiler.h"
+#include <string>
+
namespace llvm {
class Triple;
namespace omp {
@@ -156,6 +158,8 @@ struct VariantMatchInfo {
SmallVector<StringRef, 8> ISATraits;
SmallVector<TraitProperty, 8> ConstructTraits;
SmallDenseMap<TraitProperty, APInt> ScoreMap;
+ /// Identifies a runtime user condition; empty for a constant condition.
+ std::string UserCondition;
};
/// The context for a source location is made up of active property traits,
diff --git a/llvm/lib/Frontend/OpenMP/OMPContext.cpp b/llvm/lib/Frontend/OpenMP/OMPContext.cpp
index f2cdd9bbaf5e4..dca1b6c63af39 100644
--- a/llvm/lib/Frontend/OpenMP/OMPContext.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPContext.cpp
@@ -172,6 +172,13 @@ static bool isStrictSubset(const VariantMatchInfo &VMI0,
for (unsigned Bit : VMI0.RequiredTraits.set_bits())
if (!VMI1.RequiredTraits.test(Bit))
return false;
+ // Different runtime conditions share a trait, so compare them as well.
+ for (TraitProperty Property :
+ {TraitProperty::user_condition_true, TraitProperty::user_condition_false,
+ TraitProperty::user_condition_unknown})
+ if (VMI0.RequiredTraits.test(unsigned(Property)) &&
+ VMI0.UserCondition != VMI1.UserCondition)
+ return false;
if (!isSubset<TraitProperty>(VMI0.ConstructTraits, VMI1.ConstructTraits))
return false;
return true;
diff --git a/llvm/unittests/Frontend/OpenMPContextTest.cpp b/llvm/unittests/Frontend/OpenMPContextTest.cpp
index f9683ae56e933..cdf39813fe0f1 100644
--- a/llvm/unittests/Frontend/OpenMPContextTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPContextTest.cpp
@@ -317,4 +317,41 @@ TEST_F(OpenMPContextTest, ScoringSimple) {
// TODO: Add scoring tests (via getBestVariantMatchForContext).
}
+TEST_F(OpenMPContextTest, UserConditionInStrictSubset) {
+ OMPContext Context(false, Triple("x86_64-unknown-linux"), Triple(), -1);
+ APInt Three(64, 3), Four(64, 4), Five(64, 5);
+ VariantMatchInfo A, B;
+ A.addTrait(TraitProperty::user_condition_true, "");
+ A.addTrait(TraitProperty::implementation_vendor_llvm, "", &Four);
+ B.addTrait(TraitProperty::user_condition_true, "");
+ B.addTrait(TraitProperty::implementation_vendor_llvm, "", &Three);
+ B.addTrait(TraitProperty::device_kind_host, "");
+ auto Best = [&](const VariantMatchInfo &First,
+ const VariantMatchInfo &Second) {
+ return getBestVariantMatchForContext(
+ SmallVector<VariantMatchInfo, 2>{First, Second}, Context);
+ };
+
+ // Both score 5. Constant conditions are compared by their trait alone.
+ EXPECT_EQ(Best(A, B), 1);
+ EXPECT_EQ(Best(B, A), 0);
+
+ // Different runtime conditions keep clause order.
+ A.UserCondition = "a";
+ B.UserCondition = "b";
+ EXPECT_EQ(Best(A, B), 0);
+ EXPECT_EQ(Best(B, A), 0);
+
+ // The same runtime condition still makes A a strict subset of B.
+ B.UserCondition = "a";
+ EXPECT_EQ(Best(A, B), 1);
+ EXPECT_EQ(Best(B, A), 0);
+
+ // A higher score wins regardless of the conditions.
+ B.UserCondition = "b";
+ A.ScoreMap[TraitProperty::implementation_vendor_llvm] = Five;
+ EXPECT_EQ(Best(A, B), 0);
+ EXPECT_EQ(Best(B, A), 1);
+}
+
} // namespace
More information about the flang-commits
mailing list