[flang-commits] [flang] cf25fb1 - [flang][OpenMP] Add structure checks for DECLARE VARIANT (#198799)

via flang-commits flang-commits at lists.llvm.org
Tue Jun 2 08:57:49 PDT 2026


Author: Abid Qadeer
Date: 2026-06-02T16:57:43+01:00
New Revision: cf25fb1e0809aaf4dbb13f2b837d47e329f595b8

URL: https://github.com/llvm/llvm-project/commit/cf25fb1e0809aaf4dbb13f2b837d47e329f595b8
DIFF: https://github.com/llvm/llvm-project/commit/cf25fb1e0809aaf4dbb13f2b837d47e329f595b8.diff

LOG: [flang][OpenMP] Add structure checks for DECLARE VARIANT (#198799)

This PR adds declare-variant structure checking. Following checks are
added:

- Validate [base:]variant arguments (including implicit base for
single-name form).
- Require exactly one MATCH clause; reject a second MATCH on the same
directive.
- Reject duplicate (base, variant) across multiple declare variant
directives.
- Reject clauses not allowed on declare variant.
- Apply shared context-selector checks to MATCH (reuse metadirective
logic).
- Require constant user conditions in MATCH for declare variant (dynamic
selectors deferred).

Refactor metadirective support:

- Extract CheckContextSelectorSpecification for reuse.
- Reject SCORE on trait sets that do not allow it (also affects
metadirective).

Co-authored-by: Cursor <cursoragent at cursor.com>

Added: 
    flang/test/Semantics/OpenMP/declare-variant-match.f90

Modified: 
    flang/lib/Semantics/check-omp-metadirective.cpp
    flang/lib/Semantics/check-omp-structure.cpp
    flang/lib/Semantics/check-omp-structure.h
    flang/test/Semantics/OpenMP/declare-variant.f90

Removed: 
    


################################################################################
diff  --git a/flang/lib/Semantics/check-omp-metadirective.cpp b/flang/lib/Semantics/check-omp-metadirective.cpp
index c8c19e4ac7dac..d492f7e7e2eb4 100644
--- a/flang/lib/Semantics/check-omp-metadirective.cpp
+++ b/flang/lib/Semantics/check-omp-metadirective.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Semantic checks for METADIRECTIVE and related constructs/clauses.
+// Semantic checks for METADIRECTIVE, DECLARE VARIANT, and related constructs.
 //
 //===----------------------------------------------------------------------===//
 
@@ -15,11 +15,13 @@
 #include "flang/Common/idioms.h"
 #include "flang/Common/indirection.h"
 #include "flang/Common/visit.h"
+#include "flang/Evaluate/check-expression.h"
 #include "flang/Parser/characters.h"
 #include "flang/Parser/message.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/openmp-modifiers.h"
 #include "flang/Semantics/openmp-utils.h"
+#include "flang/Semantics/symbol.h"
 #include "flang/Semantics/tools.h"
 
 #include "llvm/Frontend/OpenMP/OMP.h"
@@ -43,9 +45,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::When &x) {
       x.v, llvm::omp::OMPC_when, GetContext().clauseSource, context_);
 }
 
-void OmpStructureChecker::Enter(const parser::OmpContextSelector &ctx) {
-  EnterDirectiveNest(ContextSelectorNest);
-
+void OmpStructureChecker::CheckContextSelectorSpecification(
+    const parser::OmpContextSelector &ctx) {
   using SetName = parser::OmpTraitSetSelectorName;
   std::map<SetName::Value, const SetName *> visited;
 
@@ -66,6 +67,11 @@ void OmpStructureChecker::Enter(const parser::OmpContextSelector &ctx) {
   }
 }
 
+void OmpStructureChecker::Enter(const parser::OmpContextSelector &ctx) {
+  EnterDirectiveNest(ContextSelectorNest);
+  CheckContextSelectorSpecification(ctx);
+}
+
 void OmpStructureChecker::Leave(const parser::OmpContextSelector &) {
   ExitDirectiveNest(ContextSelectorNest);
 }
@@ -240,7 +246,11 @@ void OmpStructureChecker::CheckTraitSetSelector(
       if (maybeProps) {
         auto &[maybeScore, _]{maybeProps->t};
         if (maybeScore) {
-          CheckTraitScore(*maybeScore);
+          if (!config.allowsScore)
+            context_.Say(maybeScore->source,
+                "SCORE is not allowed for %s trait set"_err_en_US, usn);
+          else
+            CheckTraitScore(*maybeScore);
         }
       }
 
@@ -594,4 +604,151 @@ void OmpStructureChecker::Leave(
   dirContext_.pop_back();
 }
 
+static const parser::traits::OmpContextSelectorSpecification *
+getMatchClauseContextSelector(const parser::OmpDirectiveSpecification &spec) {
+  for (const parser::OmpClause &clause : spec.Clauses().v) {
+    if (clause.Id() == llvm::omp::Clause::OMPC_match)
+      return &std::get<parser::OmpClause::Match>(clause.u).v.v;
+  }
+  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);
+        }
+      }
+    }
+  }
+}
+
+void OmpStructureChecker::CheckOmpDeclareVariantDirective(
+    const parser::OmpDeclareVariantDirective &x) {
+  const parser::OmpDirectiveSpecification &spec{x.v};
+  const parser::OmpArgumentList &args{spec.Arguments()};
+
+  if (args.v.size() != 1) {
+    context_.Say(args.source,
+        "DECLARE_VARIANT directive should have a single argument"_err_en_US);
+    return;
+  }
+
+  auto InvalidArgument{[&](parser::CharBlock source) {
+    context_.Say(source,
+        "The argument to the DECLARE_VARIANT directive should be [base-name:]variant-name"_err_en_US);
+  }};
+
+  auto CheckProcedureSymbol{[&](const Symbol *sym, parser::CharBlock source) {
+    if (sym) {
+      if (!IsProcedure(*sym) && !IsFunction(*sym)) {
+        auto &msg{context_.Say(source,
+            "The name '%s' should refer to a procedure"_err_en_US,
+            sym->name())};
+        if (sym->test(Symbol::Flag::Implicit)) {
+          msg.Attach(source, "The name '%s' has been implicitly declared"_en_US,
+              sym->name());
+        }
+      }
+    } else {
+      InvalidArgument(source);
+    }
+  }};
+
+  const Symbol *base{nullptr};
+  const Symbol *variant{nullptr};
+  const parser::OmpArgument &arg{args.v.front()};
+  common::visit( //
+      common::visitors{
+          [&](const parser::OmpBaseVariantNames &y) {
+            base = GetObjectSymbol(std::get<0>(y.t));
+            variant = GetObjectSymbol(std::get<1>(y.t));
+            CheckProcedureSymbol(base, arg.source);
+            CheckProcedureSymbol(variant, arg.source);
+          },
+          [&](const parser::OmpLocator &y) {
+            variant = GetArgumentSymbol(arg);
+            CheckProcedureSymbol(variant, arg.source);
+            const Scope &containingScope{context_.FindScope(x.source)};
+            if (const Symbol *host{
+                    GetProgramUnitContaining(containingScope).symbol()}) {
+              base = host;
+            }
+          },
+          [&](auto &&y) { InvalidArgument(arg.source); },
+      },
+      arg.u);
+
+  if (base && variant) {
+    base = &base->GetUltimate();
+    variant = &variant->GetUltimate();
+    if (base == variant) {
+      context_.Say(arg.source,
+          "The variant procedure must 
diff er from the base procedure"_err_en_US);
+    } else if (!declareVariantPairs_.emplace(base, variant).second) {
+      context_.Say(arg.source,
+          "Variant '%s' was already specified for '%s' in another DECLARE VARIANT directive"_err_en_US,
+          variant->name(), base->name());
+    }
+  }
+
+  const parser::traits::OmpContextSelectorSpecification *matchSelector{
+      getMatchClauseContextSelector(spec)};
+  if (!matchSelector) {
+    context_.Say(x.source,
+        "DECLARE_VARIANT directive requires a MATCH clause"_err_en_US);
+    return;
+  }
+
+  EnterDirectiveNest(ContextSelectorNest);
+  CheckContextSelectorSpecification(*matchSelector);
+  CheckDeclareVariantUserConditions(*matchSelector);
+  ExitDirectiveNest(ContextSelectorNest);
+}
+
+void OmpStructureChecker::Enter(const parser::OmpDeclareVariantDirective &x) {
+  const parser::OmpDirectiveName &dirName{x.v.DirName()};
+  PushContextAndClauseSets(dirName.source, dirName.v);
+  CheckOmpDeclareVariantDirective(x);
+}
+
+void OmpStructureChecker::Leave(const parser::OmpDeclareVariantDirective &) {
+  dirContext_.pop_back();
+}
+
 } // namespace Fortran::semantics

diff  --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index eff8f5f5858d6..1ac43b666977b 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -71,6 +71,7 @@ OmpStructureChecker::OmpStructureChecker(SemanticsContext &context)
 
 void OmpStructureChecker::Enter(const parser::ProgramUnit &) { //
   ClearLabels();
+  declareVariantPairs_.clear();
 }
 
 void OmpStructureChecker::Enter(const parser::MainProgram &x) {
@@ -1714,59 +1715,6 @@ void OmpStructureChecker::Leave(const parser::OmpDeclareSimdDirective &) {
   dirContext_.pop_back();
 }
 
-void OmpStructureChecker::Enter(const parser::OmpDeclareVariantDirective &x) {
-  const parser::OmpDirectiveName &dirName{x.v.DirName()};
-  PushContextAndClauseSets(dirName.source, dirName.v);
-
-  const parser::OmpArgumentList &args{x.v.Arguments()};
-  if (args.v.size() != 1) {
-    context_.Say(args.source,
-        "DECLARE_VARIANT directive should have a single argument"_err_en_US);
-    return;
-  }
-
-  auto InvalidArgument{[&](parser::CharBlock source) {
-    context_.Say(source,
-        "The argument to the DECLARE_VARIANT directive should be [base-name:]variant-name"_err_en_US);
-  }};
-
-  auto CheckSymbol{[&](const Symbol *sym, parser::CharBlock source) {
-    if (sym) {
-      if (!IsProcedure(*sym) && !IsFunction(*sym)) {
-        auto &msg{context_.Say(source,
-            "The name '%s' should refer to a procedure"_err_en_US,
-            sym->name())};
-        if (sym->test(Symbol::Flag::Implicit)) {
-          msg.Attach(source, "The name '%s' has been implicitly declared"_en_US,
-              sym->name());
-        }
-      }
-    } else {
-      InvalidArgument(source);
-    }
-  }};
-
-  const parser::OmpArgument &arg{args.v.front()};
-  common::visit( //
-      common::visitors{
-          [&](const parser::OmpBaseVariantNames &y) {
-            CheckSymbol(GetObjectSymbol(std::get<0>(y.t), /*ultimate=*/true),
-                arg.source);
-            CheckSymbol(GetObjectSymbol(std::get<1>(y.t), /*ultimate=*/true),
-                arg.source);
-          },
-          [&](const parser::OmpLocator &y) {
-            CheckSymbol(GetArgumentSymbol(arg, /*ultimate=*/true), arg.source);
-          },
-          [&](auto &&y) { InvalidArgument(arg.source); },
-      },
-      arg.u);
-}
-
-void OmpStructureChecker::Leave(const parser::OmpDeclareVariantDirective &) {
-  dirContext_.pop_back();
-}
-
 void OmpStructureChecker::CheckInitOnDepobj(
     const parser::OpenMPDepobjConstruct &depobj,
     const parser::OmpClause &initClause) {

diff  --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 487b7bf732b2f..676442d0bd666 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -259,12 +259,16 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
 
   // check-omp-metadirective.cpp
+  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(
       const parser::OmpTraitProperty &);
 
   void CheckTraitSelectorList(const std::list<parser::OmpTraitSelector> &);
+  void CheckContextSelectorSpecification(const parser::OmpContextSelector &);
   void CheckTraitSetSelector(const parser::OmpTraitSetSelector &);
   void CheckTraitScore(const parser::OmpTraitScore &);
   bool VerifyTraitPropertyLists(
@@ -418,6 +422,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   };
   int directiveNest_[LastType + 1] = {0};
 
+  std::set<std::pair<const Symbol *, const Symbol *>> declareVariantPairs_;
+
   int allocateDirectiveLevel_{0};
   parser::CharBlock visitedAtomicSource_;
 

diff  --git a/flang/test/Semantics/OpenMP/declare-variant-match.f90 b/flang/test/Semantics/OpenMP/declare-variant-match.f90
new file mode 100644
index 0000000000000..199d05f1750ee
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-variant-match.f90
@@ -0,0 +1,118 @@
+! RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=52
+
+! MATCH clause checks for DECLARE VARIANT: required/duplicate clause and
+! context-selector validation (shared with METADIRECTIVE).
+
+subroutine f00
+  !$omp declare variant (sub:vsub) &
+  !$omp & match (implementation={vendor("this")}, &
+!ERROR: Repeated trait set name IMPLEMENTATION in a context specifier
+  !$omp &       implementation={requires(unified_shared_memory)})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f01
+  !$omp declare variant (sub:vsub) &
+!ERROR: Repeated trait name ISA in a trait set
+  !$omp & match (device={isa("this"), isa("that")})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f02
+  !$omp declare variant (sub:vsub) &
+!ERROR: SCORE expression must be a non-negative constant integer expression
+  !$omp & match (user={condition(score(-2): .true.)})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f03(x)
+  integer :: x
+  !$omp declare variant (sub:vsub) &
+!ERROR: SCORE expression must be a non-negative constant integer expression
+  !$omp & match (user={condition(score(x): .true.)})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f04
+  !$omp declare variant (sub:vsub) &
+!ERROR: Trait property should be a scalar expression
+!ERROR: More invalid properties are present
+  !$omp & match (target_device={device_num("device", "foo"(1))})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f05(x)
+  integer :: x
+  !$omp declare variant (sub:vsub) &
+  !$omp & match (user={ &
+!ERROR: CONDITION trait requires a single LOGICAL expression
+  !$omp & condition(score(2): x)})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+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
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f07
+  !$omp declare variant (sub:vsub) &
+!ERROR: SCORE is not allowed for DEVICE trait set
+  !$omp & match (device={kind(score(1): host)})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f08
+!ERROR: DECLARE_VARIANT directive requires a MATCH clause
+  !$omp declare variant (sub:vsub)
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine f09
+  !$omp declare variant (sub:vsub) match (construct={parallel}) &
+!ERROR: At most one MATCH clause can appear on the DECLARE VARIANT directive
+  !$omp & match (construct={teams})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine

diff  --git a/flang/test/Semantics/OpenMP/declare-variant.f90 b/flang/test/Semantics/OpenMP/declare-variant.f90
index 6fc94a4fb837f..443f767f73244 100644
--- a/flang/test/Semantics/OpenMP/declare-variant.f90
+++ b/flang/test/Semantics/OpenMP/declare-variant.f90
@@ -12,3 +12,33 @@ subroutine vsub
   subroutine sub ()
   end subroutine
 end subroutine
+
+subroutine same_base_variant
+!ERROR: The variant procedure must 
diff er from the base procedure
+  !$omp declare variant (sub:sub) match (construct={parallel})
+contains
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine duplicate_variant
+  !$omp declare variant (sub:vsub) match (construct={parallel})
+!ERROR: Variant 'vsub' was already specified for 'sub' in another DECLARE VARIANT directive
+  !$omp declare variant (sub:vsub) match (construct={teams})
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+  end subroutine
+end subroutine
+
+subroutine invalid_clause
+!ERROR: PRIVATE clause is not allowed on the DECLARE VARIANT directive
+  !$omp declare variant (sub:vsub) match (construct={parallel}) private(x)
+contains
+  subroutine vsub
+  end subroutine
+  subroutine sub
+    integer :: x
+  end subroutine
+end subroutine


        


More information about the flang-commits mailing list