[flang-commits] [flang] [flang][OpenMP] Support run-time user conditions in DECLARE VARIANT (PR #216781)

Abid Qadeer via flang-commits flang-commits at lists.llvm.org
Mon Aug 17 10:57:37 PDT 2026


https://github.com/abidh updated https://github.com/llvm/llvm-project/pull/216781

>From b7a6ca10ab1ae7f0e067fbcc1880b50107107f1f Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Wed, 8 Jul 2026 14:17:43 +0100
Subject: [PATCH 1/5] DECLARE VARIANT: accept run-time user conditions in
 semantics

Remove the "Run-time USER condition in the MATCH clause is not yet
implemented" error and the now-empty CheckDeclareVariantUserConditions
helper. A non-constant user condition is now accepted; it will be
resolved at the call site as an if/else cascade during lowering (added in
subsequent commits). The scalar-LOGICAL validation of the condition is
unaffected (handled by CheckTraitCondition).

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/lib/Semantics/check-omp-structure.h     |  1 -
 flang/lib/Semantics/check-omp-variant.cpp     | 46 -------------------
 .../OpenMP/declare-variant-match.f90          |  3 +-
 3 files changed, 2 insertions(+), 48 deletions(-)

diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 4acec2ad05ba1..26e9b88677da0 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -311,7 +311,6 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckMetadirectiveVariantsWithoutLoop(std::size_t firstVariant = 0);
   void CheckOmpDeclareVariantDirective(
       const parser::OmpDeclareVariantDirective &);
-  void CheckDeclareVariantUserConditions(const parser::OmpContextSelector &);
   const std::list<parser::OmpTraitProperty> &GetTraitPropertyList(
       const parser::OmpTraitSelector &);
   std::optional<llvm::omp::Clause> GetClauseFromProperty(
diff --git a/flang/lib/Semantics/check-omp-variant.cpp b/flang/lib/Semantics/check-omp-variant.cpp
index 0f8daf84d25ec..c057e947985a7 100644
--- a/flang/lib/Semantics/check-omp-variant.cpp
+++ b/flang/lib/Semantics/check-omp-variant.cpp
@@ -911,51 +911,6 @@ getMatchClauseContextSelector(const parser::OmpDirectiveSpecification &spec) {
   return nullptr;
 }
 
-void OmpStructureChecker::CheckDeclareVariantUserConditions(
-    const parser::OmpContextSelector &ctx) {
-  using SetName = parser::OmpTraitSetSelectorName;
-  using TraitName = parser::OmpTraitSelectorName;
-
-  for (const parser::OmpTraitSetSelector &traitSet : ctx.v) {
-    if (std::get<SetName>(traitSet.t).v != SetName::Value::User) {
-      continue;
-    }
-    for (const parser::OmpTraitSelector &trait :
-        std::get<std::list<parser::OmpTraitSelector>>(traitSet.t)) {
-      const auto &traitName{std::get<TraitName>(trait.t)};
-      if (!std::holds_alternative<TraitName::Value>(traitName.u) ||
-          std::get<TraitName::Value>(traitName.u) !=
-              TraitName::Value::Condition) {
-        continue;
-      }
-      const auto &maybeProps{
-          std::get<std::optional<parser::OmpTraitSelector::Properties>>(
-              trait.t)};
-      if (!maybeProps) {
-        continue;
-      }
-      const auto &properties{
-          std::get<std::list<parser::OmpTraitProperty>>(maybeProps->t)};
-      if (properties.size() != 1) {
-        continue;
-      }
-      const parser::OmpTraitProperty &property{properties.front()};
-      const parser::ScalarExpr &scalarExpr{
-          std::get<parser::ScalarExpr>(property.u)};
-      auto maybeType{GetDynamicType(scalarExpr.thing.value())};
-      if (!maybeType || maybeType->category() != TypeCategory::Logical) {
-        continue;
-      }
-      if (const auto *expr{GetExpr(scalarExpr)}) {
-        if (!IsConstantExpr(*expr, &context_.foldingContext())) {
-          context_.Say(property.source,
-              "Run-time USER condition in the MATCH clause is not yet implemented"_err_en_US);
-        }
-      }
-    }
-  }
-}
-
 static bool IsProcedureOrFunction(const Symbol &symbol) {
   return IsProcedure(symbol) || IsFunction(symbol);
 }
@@ -1236,7 +1191,6 @@ void OmpStructureChecker::CheckOmpDeclareVariantDirective(
 
   EnterDirectiveNest(ContextSelectorNest);
   CheckContextSelectorSpecification(*matchSelector);
-  CheckDeclareVariantUserConditions(*matchSelector);
   ExitDirectiveNest(ContextSelectorNest);
 }
 
diff --git a/flang/test/Semantics/OpenMP/declare-variant-match.f90 b/flang/test/Semantics/OpenMP/declare-variant-match.f90
index 73558dc171b69..a7f05651f8a47 100644
--- a/flang/test/Semantics/OpenMP/declare-variant-match.f90
+++ b/flang/test/Semantics/OpenMP/declare-variant-match.f90
@@ -74,9 +74,10 @@ subroutine sub
   end subroutine
 end subroutine
 
+! A run-time (non-constant) USER condition is accepted; it is resolved at the
+! call site as an if/else cascade during lowering.
 subroutine f06(x)
   integer :: x
-!ERROR: Run-time USER condition in the MATCH clause is not yet implemented
   !$omp declare variant (sub:vsub) match (user={condition(x > 0)})
 contains
   subroutine vsub

>From c6f1720cebe9f84157fef47ea2dbae4bac6c1d31 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Wed, 8 Jul 2026 14:28:29 +0100
Subject: [PATCH 2/5] DECLARE VARIANT: resolve variants into ranked candidates

Refactor the call-site resolver into resolveDeclareVariant, which returns
the statically-applicable variants ranked best-first, each with its
optional run-time user condition (captured from MakeVariantMatchInfo).
Run-time conditions are handled with the same static/dynamic match-info
split that METADIRECTIVE uses: the user_condition_unknown trait is dropped
for static applicability while its score and a user_condition_true trait
are kept for ranking.

resolveDeclareVariantCallee is now a thin wrapper preserving today's
behavior: it returns the single best variant when no run-time condition is
involved, and otherwise falls back to the base (the if/else cascade at the
call site is added in a subsequent commit).

A function base with a run-time condition is rejected with a "not yet
implemented" TODO for now; subroutines are handled first.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/lib/Lower/OpenMP/Utils.cpp | 142 +++++++++++++++++++++++--------
 flang/lib/Lower/OpenMP/Utils.h   |  27 ++++++
 2 files changed, 133 insertions(+), 36 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 8f57f00d59c58..8fd295e977ab7 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1392,59 +1392,129 @@ void collectEnclosingConstructTraits(
   std::reverse(constructTraits.begin(), constructTraits.end());
 }
 
-const semantics::Symbol *
-resolveDeclareVariantCallee(const semantics::Symbol &base,
-                            AbstractConverter &converter) {
+DeclareVariantResolution
+resolveDeclareVariant(const semantics::Symbol &base,
+                      AbstractConverter &converter) {
+  DeclareVariantResolution result;
   const semantics::Symbol &ultimate{base.GetUltimate()};
 
   const auto *details{ultimate.detailsIf<semantics::SubprogramDetails>()};
   assert(details && !details->ompDeclareVariants().empty() &&
-         "resolveDeclareVariantCallee called on symbol with no variants");
+         "resolveDeclareVariant called on symbol with no variants");
 
   semantics::SemanticsContext &semaCtx{ultimate.owner().context()};
-  llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
-  llvm::SmallVector<const semantics::Symbol *, 4> variants;
+
+  llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
+  collectEnclosingConstructTraits(
+      converter.getFirOpBuilder().getInsertionBlock()->getParentOp(),
+      constructTraits);
+  semantics::omp::OmpVariantMatchContext ompCtx =
+      makeVariantMatchContext(converter.getModuleOp(), constructTraits);
+
+  // A statically-applicable candidate plus the VMI used to rank it. For a
+  // run-time user condition the VMI is evaluated as if the condition holds, so
+  // ranking honours the user-condition selector (mirrors METADIRECTIVE).
+  struct ApplicableCandidate {
+    const semantics::Symbol *variant;
+    llvm::omp::VariantMatchInfo rankingVMI;
+    std::optional<semantics::omp::DynamicUserCondition> condition;
+  };
+  llvm::SmallVector<ApplicableCandidate, 4> applicable;
+
   for (const semantics::OmpDeclareVariantEntry &entry :
        details->ompDeclareVariants()) {
+    if (!entry.matchSelector)
+      continue;
     // Variant selection cannot yet honour some selector features that the
     // parser/semantics otherwise accept; reject them before building the match
     // info (MakeVariantMatchInfo asserts none are present). This mirrors how
     // METADIRECTIVE lowering rejects the same features.
-    if (entry.matchSelector) {
-      switch (semantics::omp::FindUnsupportedSelectorFeature(
-          *entry.matchSelector, semaCtx)) {
-      case semantics::omp::UnsupportedSelectorFeature::TargetDevice:
-        TODO(converter.getCurrentLocation(),
-             "target_device selector in DECLARE VARIANT");
-        break;
-      case semantics::omp::UnsupportedSelectorFeature::
-          ClauseOrExtensionProperty:
-        TODO(converter.getCurrentLocation(),
-             "clause or extension trait matching in DECLARE VARIANT");
-        break;
-      case semantics::omp::UnsupportedSelectorFeature::None:
-        break;
-      }
+    switch (semantics::omp::FindUnsupportedSelectorFeature(
+        *entry.matchSelector, semaCtx)) {
+    case semantics::omp::UnsupportedSelectorFeature::TargetDevice:
+      TODO(converter.getCurrentLocation(),
+           "target_device selector in DECLARE VARIANT");
+      break;
+    case semantics::omp::UnsupportedSelectorFeature::ClauseOrExtensionProperty:
+      TODO(converter.getCurrentLocation(),
+           "clause or extension trait matching in DECLARE VARIANT");
+      break;
+    case semantics::omp::UnsupportedSelectorFeature::None:
+      break;
     }
-    llvm::omp::VariantMatchInfo &vmi{vmis.emplace_back()};
-    if (entry.matchSelector)
-      semantics::omp::MakeVariantMatchInfo(vmi, *entry.matchSelector, semaCtx);
-    variants.push_back(&entry.variant.get());
+
+    llvm::omp::VariantMatchInfo rawVMI;
+    std::optional<semantics::omp::DynamicUserCondition> dynamicCond{
+        semantics::omp::MakeVariantMatchInfo(rawVMI, *entry.matchSelector,
+                                             semaCtx)};
+
+    if (!dynamicCond) {
+      if (!llvm::omp::isVariantApplicableInContext(rawVMI, ompCtx))
+        continue;
+      applicable.push_back({&entry.variant.get(), rawVMI, std::nullopt});
+      continue;
+    }
+
+    // Run-time user condition: static applicability must ignore the unknown
+    // condition trait. Keep its score for ranking, and add user_condition_true
+    // so ranking honours the user-condition selector.
+    constexpr llvm::omp::TraitProperty dynamicConditionTrait{
+        llvm::omp::TraitProperty::user_condition_unknown};
+    llvm::omp::VariantMatchInfo staticVMI = rawVMI;
+    std::optional<llvm::APInt> conditionScore;
+    auto scoreIt = staticVMI.ScoreMap.find(dynamicConditionTrait);
+    if (scoreIt != staticVMI.ScoreMap.end()) {
+      conditionScore = scoreIt->second;
+      staticVMI.ScoreMap.erase(scoreIt);
+    }
+    staticVMI.RequiredTraits.reset(unsigned(dynamicConditionTrait));
+    if (!llvm::omp::isVariantApplicableInContext(staticVMI, ompCtx))
+      continue;
+    llvm::omp::VariantMatchInfo rankingVMI = staticVMI;
+    rankingVMI.addTrait(llvm::omp::TraitProperty::user_condition_true,
+                        "<condition>",
+                        conditionScore ? &*conditionScore : nullptr);
+    applicable.push_back({&entry.variant.get(), rankingVMI, dynamicCond});
   }
 
-  llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
-  collectEnclosingConstructTraits(
-      converter.getFirOpBuilder().getInsertionBlock()->getParentOp(),
-      constructTraits);
-  semantics::omp::OmpVariantMatchContext ompCtx =
-      makeVariantMatchContext(converter.getModuleOp(), constructTraits);
+  // Rank best-first by repeatedly selecting the best remaining candidate; the
+  // OpenMP context scorer preserves input order for ties.
+  llvm::SmallVector<unsigned, 4> remaining;
+  for (unsigned i = 0, e = applicable.size(); i < e; ++i)
+    remaining.push_back(i);
+  while (!remaining.empty()) {
+    llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
+    for (unsigned idx : remaining)
+      vmis.push_back(applicable[idx].rankingVMI);
+    int bestIdx{llvm::omp::getBestVariantMatchForContext(vmis, ompCtx)};
+    if (bestIdx < 0)
+      break;
+    const ApplicableCandidate &chosen{applicable[remaining[bestIdx]]};
+    if (chosen.condition)
+      result.hasDynamicCondition = true;
+    result.candidates.push_back({chosen.variant, chosen.condition});
+    remaining.erase(remaining.begin() + bestIdx);
+  }
 
-  const int bestIdx{llvm::omp::getBestVariantMatchForContext(vmis, ompCtx)};
-  // Return nullptr when no variant matches the current context; the caller
-  // will fall back to the base symbol.
-  if (bestIdx < 0)
+  // Subroutines only for now: a function base with a run-time condition would
+  // need the if/else cascade to carry a result, which is not yet implemented.
+  if (result.hasDynamicCondition && semantics::IsFunction(ultimate))
+    TODO(converter.getCurrentLocation(),
+         "dynamic user condition on a function in DECLARE VARIANT");
+
+  return result;
+}
+
+const semantics::Symbol *
+resolveDeclareVariantCallee(const semantics::Symbol &base,
+                            AbstractConverter &converter) {
+  DeclareVariantResolution resolution{resolveDeclareVariant(base, converter)};
+  // Static fast path: return the single best variant when no run-time condition
+  // is involved. When a candidate carries a run-time condition, fall back to the
+  // base here; the call site lowers the choice as an if/else cascade.
+  if (resolution.candidates.empty() || resolution.hasDynamicCondition)
     return nullptr;
-  return variants[bestIdx];
+  return resolution.candidates.front().variant;
 }
 
 } // namespace omp
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..9e482bb5754c2 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -11,6 +11,7 @@
 
 #include "flang/Lower/OpenMP/Clauses.h"
 #include "flang/Optimizer/Builder/HLFIRTools.h"
+#include "flang/Semantics/openmp-utils.h"
 #include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
 #include "mlir/IR/Location.h"
@@ -60,6 +61,32 @@ struct DeclareTargetCaptureInfo {
       : clause(c), automap(a), symbol(s) {}
 };
 
+/// One statically-applicable DECLARE VARIANT candidate at a call site, ranked
+/// best-first by resolveDeclareVariant.
+struct DeclareVariantCandidate {
+  const semantics::Symbol *variant;
+  /// When set, this candidate applies only if the run-time user condition
+  /// holds; it is lowered as a guard in an if/else cascade. Unset means the
+  /// candidate is unconditional (a terminal in the cascade).
+  std::optional<semantics::omp::DynamicUserCondition> condition;
+};
+
+/// Ranked DECLARE VARIANT candidates that are statically applicable in the
+/// enclosing OpenMP context at a call site.
+struct DeclareVariantResolution {
+  llvm::SmallVector<DeclareVariantCandidate, 4> candidates;
+  /// True if any candidate carries a run-time user condition.
+  bool hasDynamicCondition = false;
+};
+
+/// Resolve the DECLARE VARIANT candidates recorded on \p base against the
+/// enclosing OpenMP context. Candidates that cannot statically apply are
+/// dropped; the rest are returned ranked best-first, each with its optional
+/// run-time user condition. \p base is expected to carry variant entries.
+DeclareVariantResolution
+resolveDeclareVariant(const semantics::Symbol &base,
+                      lower::AbstractConverter &converter);
+
 // A small helper structure for keeping track of a component members MapInfoOp
 // and index data when lowering OpenMP map clauses. Keeps track of the
 // placement of the component in the derived type hierarchy it rests within,

>From 03484d99a238eb65f9a46dee8beeef86c68286b3 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Wed, 8 Jul 2026 14:40:52 +0100
Subject: [PATCH 3/5] DECLARE VARIANT: lower run-time user conditions as an
 if/else cascade

Add genDeclareVariantCall, which lowers a direct subroutine call to a base
carrying a run-time-guarded variant as a ranked if/else cascade:

    if (cond0) call variant0
    else if (cond1) call variant1
    else call base

The cascade is emitted from genCallOpAndResult at the fir.call site, so the
already-lowered actual arguments are evaluated once and reused in every
branch. The candidate selection and ranking reuse resolveDeclareVariant;
the base is the final fallback (an unconditional applicable variant, if one
outranks the guarded candidates, terminates the cascade in its place).

Only direct subroutine calls are handled; a function base with a run-time
condition is rejected with a not-yet-implemented TODO. The static case is
unchanged (the callee name swap still selects the single best variant).

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Lower/OpenMP.h | 13 +++++++
 flang/lib/Lower/ConvertCall.cpp    | 42 +++++++++++++++++-----
 flang/lib/Lower/OpenMP/Utils.cpp   | 58 ++++++++++++++++++++++++++++++
 3 files changed, 105 insertions(+), 8 deletions(-)

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index a3f35498b9180..f2794400d5264 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -13,6 +13,7 @@
 #ifndef FORTRAN_LOWER_OPENMP_H
 #define FORTRAN_LOWER_OPENMP_H
 
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 
@@ -113,6 +114,18 @@ namespace omp {
 const Fortran::semantics::Symbol *
 resolveDeclareVariantCallee(const Fortran::semantics::Symbol &base,
                             AbstractConverter &converter);
+
+/// Lower a direct call to \p base (which carries OpenMP DECLARE VARIANT entries)
+/// as a run-time if/else cascade when a variant is guarded by a run-time user
+/// condition. \p emitCall is invoked to emit one call with the already-lowered
+/// arguments at the current insertion point: with the chosen variant symbol for
+/// a guarded branch, or with nullptr for the base fallback. Returns true if a
+/// cascade was emitted; false if no run-time condition applies and the caller
+/// should emit the normal single call. Subroutines only.
+bool genDeclareVariantCall(
+    AbstractConverter &converter, mlir::Location loc,
+    const Fortran::semantics::Symbol &base,
+    llvm::function_ref<void(const Fortran::semantics::Symbol *)> emitCall);
 } // namespace omp
 
 // Materialize (idempotently) the omp.declare_reduction op for one already-
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 19678e429249b..220ae04b2b64e 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -18,6 +18,7 @@
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/CustomIntrinsicCall.h"
 #include "flang/Lower/HlfirIntrinsics.h"
+#include "flang/Lower/OpenMP.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/StatementContext.h"
 #include "flang/Lower/SymbolMap.h"
@@ -797,15 +798,40 @@ Fortran::lower::genCallOpAndResult(
     else if (caller.getCallDescription().hasAlwaysInline())
       inlineAttr = fir::FortranInlineEnumAttr::get(
           builder.getContext(), fir::FortranInlineEnum::always_inline);
-    auto call = fir::CallOp::create(
-        builder, loc, funcType.getResults(), funcSymbolAttr, operands,
-        /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs, inlineAttr,
-        /*accessGroups=*/mlir::ArrayAttr{});
-    callOp = call;
+    auto emitCall = [&](mlir::SymbolRefAttr callee) {
+      return fir::CallOp::create(
+          builder, loc, funcType.getResults(), callee, operands,
+          /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs, inlineAttr,
+          /*accessGroups=*/mlir::ArrayAttr{});
+    };
 
-    callNumResults = call.getNumResults();
-    if (callNumResults != 0)
-      callResult = call.getResult(0);
+    // OpenMP DECLARE VARIANT with a run-time user condition: a direct
+    // subroutine call is lowered to an if/else cascade that selects the variant
+    // at run time, reusing the already-lowered arguments. Returns false (and
+    // nothing is emitted) for the common case with no run-time condition.
+    bool emittedVariantCascade = false;
+    if (funcSymbolAttr && funcType.getNumResults() == 0) {
+      if (const Fortran::semantics::Symbol *base =
+              caller.getProcedureSymbol()) {
+        emittedVariantCascade = Fortran::lower::omp::genDeclareVariantCall(
+            converter, loc, *base,
+            [&](const Fortran::semantics::Symbol *callee) {
+              emitCall(callee ? builder.getSymbolRefAttr(
+                                    converter.mangleName(*callee))
+                              : funcSymbolAttr);
+            });
+      }
+    }
+
+    if (!emittedVariantCascade) {
+      auto call = emitCall(funcSymbolAttr);
+      callOp = call;
+      callNumResults = call.getNumResults();
+      if (callNumResults != 0)
+        callResult = call.getResult(0);
+    } else {
+      callNumResults = 0;
+    }
   }
 
   std::optional<Fortran::evaluate::DynamicType> retTy =
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 8fd295e977ab7..02422575f3109 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1517,6 +1517,64 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
   return resolution.candidates.front().variant;
 }
 
+bool genDeclareVariantCall(
+    AbstractConverter &converter, mlir::Location loc,
+    const semantics::Symbol &base,
+    llvm::function_ref<void(const semantics::Symbol *)> emitCall) {
+  const semantics::Symbol &ultimate{base.GetUltimate()};
+  const auto *details{ultimate.detailsIf<semantics::SubprogramDetails>()};
+  if (!details || details->ompDeclareVariants().empty())
+    return false;
+
+  DeclareVariantResolution resolution{resolveDeclareVariant(base, converter)};
+  if (!resolution.hasDynamicCondition)
+    return false;
+
+  fir::FirOpBuilder &builder{converter.getFirOpBuilder()};
+  semantics::SemanticsContext &semaCtx{ultimate.owner().context()};
+  lower::StatementContext stmtCtx;
+
+  // Emit a ranked if/else cascade over the candidates:
+  //   if (cond0) call variant0
+  //   else if (cond1) call variant1
+  //   else call base
+  // An unconditional candidate (should one outrank the guarded ones)
+  // terminates the cascade in place of the base fallback.
+  fir::IfOp outerIf;
+  for (const DeclareVariantCandidate &candidate : resolution.candidates) {
+    if (!candidate.condition) {
+      emitCall(candidate.variant);
+      if (outerIf)
+        builder.setInsertionPointAfter(outerIf);
+      return true;
+    }
+
+    mlir::Location condLoc{converter.genLocation(candidate.condition->source)};
+    const auto *condExpr{
+        semantics::GetExpr(semaCtx, *candidate.condition->expr)};
+    assert(condExpr && "missing expression for user condition");
+    mlir::Value condVal{
+        fir::getBase(converter.genExprValue(*condExpr, stmtCtx, &condLoc))};
+    if (condVal.getType() != builder.getI1Type())
+      condVal = builder.createConvert(condLoc, builder.getI1Type(), condVal);
+    stmtCtx.finalizeAndReset();
+
+    auto ifOp{fir::IfOp::create(builder, condLoc, condVal,
+                                /*withElseRegion=*/true)};
+    if (!outerIf)
+      outerIf = ifOp;
+    builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+    emitCall(candidate.variant);
+    builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
+  }
+
+  // No unconditional terminal: the innermost else calls the base.
+  emitCall(nullptr);
+  if (outerIf)
+    builder.setInsertionPointAfter(outerIf);
+  return true;
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran

>From 50df864863af1c27358cd70a4fb89566098ad2f9 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Wed, 8 Jul 2026 14:44:18 +0100
Subject: [PATCH 4/5] DECLARE VARIANT: tests for run-time user-condition
 lowering

Add lowering tests for the if/else cascade produced by a run-time DECLARE
VARIANT user condition: a single condition, a two-condition ranked chain,
and a variant with both a static selector and a run-time condition. Add a
Todo test asserting that a run-time condition on a function is rejected as
not-yet-implemented.

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 ...are-variant-dynamic-condition-function.f90 | 25 +++++++
 .../declare-variant-dynamic-condition.f90     | 74 +++++++++++++++++++
 2 files changed, 99 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/declare-variant-dynamic-condition-function.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-variant-dynamic-condition.f90

diff --git a/flang/test/Lower/OpenMP/Todo/declare-variant-dynamic-condition-function.f90 b/flang/test/Lower/OpenMP/Todo/declare-variant-dynamic-condition-function.f90
new file mode 100644
index 0000000000000..7d641154c0bce
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/declare-variant-dynamic-condition-function.f90
@@ -0,0 +1,25 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! A run-time DECLARE VARIANT user condition on a function is not yet supported;
+! only subroutines are handled (the if/else cascade cannot yet carry a result).
+
+! CHECK: not yet implemented: dynamic user condition on a function in DECLARE VARIANT
+
+module m
+contains
+  integer function fbase(x)
+    integer :: x
+    !$omp declare variant (fvar) match (user={condition(x > 0)})
+    fbase = 1
+  end function
+  integer function fvar(x)
+    integer :: x
+    fvar = 2
+  end function
+end module
+
+subroutine caller(x)
+  use m
+  integer :: x, r
+  r = fbase(x)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/declare-variant-dynamic-condition.f90 b/flang/test/Lower/OpenMP/declare-variant-dynamic-condition.f90
new file mode 100644
index 0000000000000..839f9c7083f1e
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-variant-dynamic-condition.f90
@@ -0,0 +1,74 @@
+! RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s
+
+! A run-time (non-constant) USER condition in a DECLARE VARIANT match clause is
+! resolved at the call site as an if/else cascade: the variant is called when
+! the condition holds, otherwise the base.
+
+! CHECK-LABEL: func.func @_QPtest_single
+! CHECK: %[[COND:.*]] = arith.cmpi sgt
+! CHECK: fir.if %[[COND]] {
+! CHECK:   fir.call @_QFtest_singlePvsub
+! CHECK: } else {
+! CHECK:   fir.call @_QFtest_singlePbase
+! CHECK: }
+subroutine test_single(x)
+  integer :: x
+  call base()
+contains
+  subroutine base
+    !$omp declare variant (base:vsub) match (user={condition(x > 0)})
+  end subroutine base
+  subroutine vsub
+  end subroutine vsub
+end subroutine test_single
+
+! Two run-time conditions form a ranked if/else-if cascade, with the base as the
+! final fallback.
+
+! CHECK-LABEL: func.func @_QPtest_chain
+! CHECK: fir.if
+! CHECK:   fir.call @_QFtest_chainPv1
+! CHECK: } else {
+! CHECK:   fir.if
+! CHECK:     fir.call @_QFtest_chainPv2
+! CHECK:   } else {
+! CHECK:     fir.call @_QFtest_chainPbase
+subroutine test_chain(a, b)
+  logical :: a, b
+  call base()
+contains
+  subroutine base
+    !$omp declare variant (base:v1) match (user={condition(a)})
+    !$omp declare variant (base:v2) match (user={condition(b)})
+  end subroutine base
+  subroutine v1
+  end subroutine v1
+  subroutine v2
+  end subroutine v2
+end subroutine test_chain
+
+! A variant with both a static selector and a run-time condition: the static
+! part decides applicability at compile time (only inside the parallel region),
+! and the condition guards the call at run time.
+
+! CHECK-LABEL: func.func @_QPtest_mix
+! CHECK: fir.call @_QFtest_mixPbase
+! CHECK: omp.parallel {
+! CHECK:   fir.if
+! CHECK:     fir.call @_QFtest_mixPvsub
+! CHECK:   } else {
+! CHECK:     fir.call @_QFtest_mixPbase
+subroutine test_mix(x)
+  integer :: x
+  call base()
+  !$omp parallel
+  call base()
+  !$omp end parallel
+contains
+  subroutine base
+    !$omp declare variant (base:vsub) match (construct={parallel}, user={condition(x > 0)})
+  end subroutine base
+  subroutine vsub
+  end subroutine vsub
+end subroutine test_mix

>From d18abb2a6264190f427a4b3e2f10b70d878c5bf4 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Mon, 17 Aug 2026 18:56:45 +0100
Subject: [PATCH 5/5] DECLARE VARIANT: NFC clang-format fixes

Co-authored-by: Cursor <cursoragent at cursor.com>
---
 flang/include/flang/Lower/OpenMP.h | 14 +++++++-------
 flang/lib/Lower/OpenMP/Utils.cpp   | 13 ++++++-------
 2 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index f2794400d5264..7261950e2cea0 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -115,13 +115,13 @@ const Fortran::semantics::Symbol *
 resolveDeclareVariantCallee(const Fortran::semantics::Symbol &base,
                             AbstractConverter &converter);
 
-/// Lower a direct call to \p base (which carries OpenMP DECLARE VARIANT entries)
-/// as a run-time if/else cascade when a variant is guarded by a run-time user
-/// condition. \p emitCall is invoked to emit one call with the already-lowered
-/// arguments at the current insertion point: with the chosen variant symbol for
-/// a guarded branch, or with nullptr for the base fallback. Returns true if a
-/// cascade was emitted; false if no run-time condition applies and the caller
-/// should emit the normal single call. Subroutines only.
+/// Lower a direct call to \p base (which carries OpenMP DECLARE VARIANT
+/// entries) as a run-time if/else cascade when a variant is guarded by a
+/// run-time user condition. \p emitCall is invoked to emit one call with the
+/// already-lowered arguments at the current insertion point: with the chosen
+/// variant symbol for a guarded branch, or with nullptr for the base fallback.
+/// Returns true if a cascade was emitted; false if no run-time condition
+/// applies and the caller should emit the normal single call. Subroutines only.
 bool genDeclareVariantCall(
     AbstractConverter &converter, mlir::Location loc,
     const Fortran::semantics::Symbol &base,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 02422575f3109..9af58e45157c8 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1392,9 +1392,8 @@ void collectEnclosingConstructTraits(
   std::reverse(constructTraits.begin(), constructTraits.end());
 }
 
-DeclareVariantResolution
-resolveDeclareVariant(const semantics::Symbol &base,
-                      AbstractConverter &converter) {
+DeclareVariantResolution resolveDeclareVariant(const semantics::Symbol &base,
+                                               AbstractConverter &converter) {
   DeclareVariantResolution result;
   const semantics::Symbol &ultimate{base.GetUltimate()};
 
@@ -1429,8 +1428,8 @@ resolveDeclareVariant(const semantics::Symbol &base,
     // parser/semantics otherwise accept; reject them before building the match
     // info (MakeVariantMatchInfo asserts none are present). This mirrors how
     // METADIRECTIVE lowering rejects the same features.
-    switch (semantics::omp::FindUnsupportedSelectorFeature(
-        *entry.matchSelector, semaCtx)) {
+    switch (semantics::omp::FindUnsupportedSelectorFeature(*entry.matchSelector,
+                                                           semaCtx)) {
     case semantics::omp::UnsupportedSelectorFeature::TargetDevice:
       TODO(converter.getCurrentLocation(),
            "target_device selector in DECLARE VARIANT");
@@ -1510,8 +1509,8 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
                             AbstractConverter &converter) {
   DeclareVariantResolution resolution{resolveDeclareVariant(base, converter)};
   // Static fast path: return the single best variant when no run-time condition
-  // is involved. When a candidate carries a run-time condition, fall back to the
-  // base here; the call site lowers the choice as an if/else cascade.
+  // is involved. When a candidate carries a run-time condition, fall back to
+  // the base here; the call site lowers the choice as an if/else cascade.
   if (resolution.candidates.empty() || resolution.hasDynamicCondition)
     return nullptr;
   return resolution.candidates.front().variant;



More information about the flang-commits mailing list