[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
Mon Apr 27 10:53:54 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-fir-hlfir

Author: Chi-Chun, Chen (chichunchen)

<details>
<summary>Changes</summary>

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 and stacked on top of https://github.com/llvm/llvm-project/pull/193664.

Assisted with copilot and GPT-5.4

---
Full diff: https://github.com/llvm/llvm-project/pull/194424.diff


3 Files Affected:

- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+60-13) 
- (removed) flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90 (-10) 
- (modified) flang/test/Lower/OpenMP/metadirective-user.f90 (+202-1) 


``````````diff
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 0ac0f0071ec3b..4c2ffb723534c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4446,6 +4446,11 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 
   llvm::SmallVector<const parser::OmpDirectiveSpecification *> candidates;
   llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
+  struct DynamicCandidate {
+    const parser::OmpDirectiveSpecification *dirSpec = 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;
@@ -4497,9 +4502,16 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
       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, dynCondExpr});
+        continue;
+      }
 
       if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
         continue;
@@ -4542,18 +4554,53 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    queue.begin());
   };
 
-  const parser::OmpDirectiveSpecification *selected = fallback;
-  if (candidates.size() == 1) {
-    selected = candidates.front();
-  } else if (!candidates.empty()) {
-    int bestIdx = llvm::omp::getBestVariantMatchForContext(vmis, ompCtx);
-    if (bestIdx >= 0) {
-      assert(static_cast<size_t>(bestIdx) < candidates.size() &&
-             "best variant index out of range");
-      selected = candidates[bestIdx];
+  auto selectStaticBest = [&]() -> const parser::OmpDirectiveSpecification * {
+    if (candidates.size() == 1)
+      return candidates.front();
+    if (!candidates.empty()) {
+      int bestIdx = llvm::omp::getBestVariantMatchForContext(vmis, ompCtx);
+      if (bestIdx >= 0) {
+        assert(static_cast<size_t>(bestIdx) < candidates.size() &&
+               "best variant index out of range");
+        return candidates[bestIdx];
+      }
     }
+    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(meta.v.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.dirSpec);
+
+    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 5165268cf7cc2..cc20da2d2b5d4 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
@@ -31,3 +35,200 @@ subroutine test_condition_false()
   !$omp & default(nothing)
 #endif
 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

``````````

</details>


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


More information about the llvm-branch-commits mailing list