[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 11:14:06 PDT 2026


https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/194424

>From 0c4dbe20ed529eddc379bcc133001c7c1d9e3c25 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 954cb0554f211..1c0beda9ae054 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4495,6 +4495,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;
@@ -4548,9 +4553,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;
@@ -4592,35 +4604,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 dbd02979e474767b26c648a4ac5f7a44adb70f11 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             | 128 ++++++++++--------
 .../test/Lower/OpenMP/metadirective-user.f90  |  50 +++++++
 2 files changed, 119 insertions(+), 59 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 1c0beda9ae054..f9880faa165e9 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4451,12 +4451,14 @@ struct MetadirectiveVariant {
 
 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
 
@@ -4495,11 +4497,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;
@@ -4555,12 +4552,13 @@ 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(variant.spec, dynamicVMI, variant.isExplicit,
+                                dynCondExpr);
         continue;
       }
 
@@ -4604,58 +4602,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);
@@ -4663,12 +4670,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