[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:24:36 PDT 2026
https://github.com/abidh created https://github.com/llvm/llvm-project/pull/216781
`!$omp declare variant` accepts `match(user={condition(expr)})`, but until now a non-constant `expr` was rejected in semantics with "Run-time USER condition in the MATCH clause is not yet implemented". This series accepts it and resolves the choice at the call site at run time.
The call-site resolver is refactored into `resolveDeclareVariant`, which returns the statically-applicable variants ranked best-first, each with its optional run-time condition. A run-time condition is handled with the same static/dynamic
match-info split that METADIRECTIVE lowering already uses: `user_condition_unknown` is dropped when testing static applicability, so a variant is not discarded for a condition that simply cannot be evaluated yet, while its score is kept and a
`user_condition_true` trait added so ranking still honours the user selector.
A direct call to a base with a run-time-guarded variant is then lowered as a ranked if/else cascade, with the base as the final fallback. The cascade is emitted at the `fir.call` site, so the actual arguments are lowered once and reused in every branch, and each condition is evaluated only if the higher-ranked ones did not hold. An unconditional candidate that outranks the guarded ones terminates the cascade in place of the base.
subroutine base
!$omp declare variant (base:v1) match (user={condition(a)})
!$omp declare variant (base:v2) match (user={condition(b)})
end subroutine
fir.if %a {
fir.call @v1
} else {
fir.if %b {
fir.call @v2
} else {
fir.call @base
}
}
The purely static case is unchanged: `resolveDeclareVariantCallee` still returns the single best variant and the callee name is swapped as before. It returns null as soon as any candidate is guarded, which is what hands the call site over
to the cascade.
Limitations, both to be addressed separately:
- Subroutines only. A function base with a run-time condition is rejected with a not-yet-implemented TODO, since the cascade cannot yet carry a result.
- A variant declared on a module procedure and used from a separate compilation unit is still not seen, because `declare variant` is not written into the module file. That is pre-existing and orthogonal to this change.
>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/4] 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/4] 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/4] 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/4] 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
More information about the flang-commits
mailing list