[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