[llvm-branch-commits] [flang] [flang][OpenMP] Track reachable metadirective replacements (PR #219014)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Aug 26 11:56:01 PDT 2026


https://github.com/chichunchen created https://github.com/llvm/llvm-project/pull/219014

Semantic checks currently consider each statically applicable metadirective
replacement independently. This can diagnose lower-ranked replacements that
selection can never reach, and nested construct selectors cannot observe a
directive selected by an enclosing metadirective.

Use shared candidate ranking to retain only reachable replacements. Track each
effective directive path separately and propagate reachability to nested APPLY
transformations. This lets nested selectors observe selected contexts without
combining mutually exclusive paths.

Assisted with codex.


>From c79e63ce3f71283f4b0242a39ee77b32d3c7fd3a Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 26 Aug 2026 13:46:09 -0500
Subject: [PATCH] [flang][OpenMP] Track reachable metadirective replacements

Semantic checks currently consider each statically applicable metadirective
replacement independently. This can diagnose lower-ranked replacements that
selection can never reach, and nested construct selectors cannot observe a
directive selected by an enclosing metadirective.

Use shared candidate ranking to retain only reachable replacements. Track each
effective directive path separately and propagate reachability to nested APPLY
transformations. This lets nested selectors observe selected contexts without
combining mutually exclusive paths.

Assisted with codex.
---
 flang/include/flang/Semantics/openmp-utils.h  |   4 +
 flang/lib/Semantics/check-omp-structure.cpp   |  78 +++--
 flang/lib/Semantics/check-omp-structure.h     |  43 ++-
 flang/lib/Semantics/check-omp-variant.cpp     | 305 ++++++++++++------
 flang/lib/Semantics/openmp-utils.cpp          |   2 +-
 ...metadirective-loop-applicability-apply.f90 |  23 ++
 .../metadirective-loop-applicability.f90      |  79 +++++
 7 files changed, 404 insertions(+), 130 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/metadirective-loop-applicability-apply.f90

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index be6cff0bb4f4e..3182dcd7ae4f0 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -257,6 +257,10 @@ class OmpVariantMatchContext : public llvm::omp::OMPContext {
   std::string features_;
 };
 
+/// Add the construct traits implied by an OpenMP directive to \p vmi.
+void AppendConstructTraitsForDirective(
+    llvm::omp::Directive, llvm::omp::VariantMatchInfo &vmi);
+
 struct MetadirectiveCandidate {
   MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
       llvm::omp::VariantMatchInfo vmi, bool isExplicit,
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 1cc5a5d885a0c..9ae243065f98a 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -86,12 +86,12 @@ void OmpStructureChecker::Enter(const parser::ProgramUnit &) { //
 }
 
 void OmpStructureChecker::Leave(const parser::ProgramUnit &) {
-  if (!metadirectiveLoopVariants_.empty()) {
+  if (!pendingLoopDirectiveGroups_.empty()) {
     // A declaration-only unit (module, submodule, or block data) has no
     // execution part to follow the metadirective, so its loop-associated
     // variants were never validated. A subprogram validates them while
     // scanning the execution part, leaving none pending here.
-    CheckMetadirectiveVariantsWithoutLoop();
+    CheckPendingLoopDirectivesWithoutLoop();
   }
 }
 
@@ -207,38 +207,39 @@ void OmpStructureChecker::Enter(const parser::EndMpSubprogramStmt &x) {
   scopeStack_.pop_back();
 }
 
-void OmpStructureChecker::BeginMetadirectiveVariantScope() {
-  metadirectiveVariantScopeStarts_.push_back(metadirectiveLoopVariants_.size());
+void OmpStructureChecker::BeginPendingLoopDirectiveScope() {
+  pendingLoopDirectiveScopeStarts_.push_back(
+      pendingLoopDirectiveGroups_.size());
 }
 
-void OmpStructureChecker::EndMetadirectiveVariantScope() {
-  CHECK(!metadirectiveVariantScopeStarts_.empty());
-  std::size_t firstVariant{metadirectiveVariantScopeStarts_.back()};
-  metadirectiveVariantScopeStarts_.pop_back();
-  if (firstVariant < metadirectiveLoopVariants_.size()) {
-    // Diagnose variants that were recorded in this scope but not consumed by
-    // one of its executable constructs, preserving variants from an enclosing
-    // scope.
-    CheckMetadirectiveVariantsWithoutLoop(firstVariant);
+void OmpStructureChecker::EndPendingLoopDirectiveScope() {
+  CHECK(!pendingLoopDirectiveScopeStarts_.empty());
+  std::size_t firstDirectiveGroup{pendingLoopDirectiveScopeStarts_.back()};
+  pendingLoopDirectiveScopeStarts_.pop_back();
+  if (firstDirectiveGroup < pendingLoopDirectiveGroups_.size()) {
+    // Diagnose directives that were recorded in this scope but not consumed
+    // by one of its executable constructs, preserving directives from an
+    // enclosing scope.
+    CheckPendingLoopDirectivesWithoutLoop(firstDirectiveGroup);
   }
 }
 
 void OmpStructureChecker::Enter(const parser::Block &) {
-  BeginMetadirectiveVariantScope();
+  BeginPendingLoopDirectiveScope();
 }
 
 void OmpStructureChecker::Leave(const parser::Block &) {
-  EndMetadirectiveVariantScope();
+  EndPendingLoopDirectiveScope();
 }
 
 void OmpStructureChecker::Enter(const parser::BlockConstruct &x) {
-  BeginMetadirectiveVariantScope();
+  BeginPendingLoopDirectiveScope();
   auto &endBlockStmt{std::get<parser::Statement<parser::EndBlockStmt>>(x.t)};
   scopeStack_.push_back(&context_.FindScope(endBlockStmt.source));
 }
 
 void OmpStructureChecker::Leave(const parser::BlockConstruct &x) {
-  EndMetadirectiveVariantScope();
+  EndPendingLoopDirectiveScope();
   scopeStack_.pop_back();
 }
 
@@ -251,20 +252,20 @@ void OmpStructureChecker::Enter(const parser::ModuleSubprogram &) {
 }
 
 void OmpStructureChecker::Enter(const parser::ModuleSubprogramPart &) {
-  if (!metadirectiveLoopVariants_.empty()) {
+  if (!pendingLoopDirectiveGroups_.empty()) {
     // The enclosing module or submodule has no execution part. Diagnose its
     // pending loop-associated variants before a contained procedure starts a
     // new specification part and resets the worklist.
-    CheckMetadirectiveVariantsWithoutLoop();
+    CheckPendingLoopDirectivesWithoutLoop();
   }
 }
 
 void OmpStructureChecker::Enter(const parser::InterfaceBody &) {
-  BeginMetadirectiveVariantScope();
+  BeginPendingLoopDirectiveScope();
 }
 
 void OmpStructureChecker::Leave(const parser::InterfaceBody &) {
-  EndMetadirectiveVariantScope();
+  EndPendingLoopDirectiveScope();
 }
 
 void OmpStructureChecker::Enter(const parser::SpecificationPart &) {
@@ -272,7 +273,7 @@ void OmpStructureChecker::Enter(const parser::SpecificationPart &) {
   // An empty partStack_ marks the unit's top-level specification part, so a
   // nested one such as an interface body does not reset them.
   if (partStack_.empty()) {
-    metadirectiveLoopVariants_.clear();
+    pendingLoopDirectiveGroups_.clear();
   }
   partStack_.push_back(PartKind::SpecificationPart);
 }
@@ -286,10 +287,10 @@ void OmpStructureChecker::Enter(const parser::ExecutionPart &) {
 }
 
 void OmpStructureChecker::Leave(const parser::ExecutionPart &) {
-  if (!metadirectiveLoopVariants_.empty()) {
+  if (!pendingLoopDirectiveGroups_.empty()) {
     // No loop nest followed the metadirective in this execution part, so its
     // loop-associated variants were never validated.
-    CheckMetadirectiveVariantsWithoutLoop();
+    CheckPendingLoopDirectivesWithoutLoop();
   }
   partStack_.pop_back();
 }
@@ -650,6 +651,35 @@ void OmpStructureChecker::ClearLabels() {
   targetLabels_.clear();
 }
 
+llvm::SmallVector<OmpStructureChecker::EffectiveDirectivePath, 4>
+OmpStructureChecker::GetEnclosingDirectivePaths() const {
+  // The current metadirective is already on dirContext_; start at its parent.
+  int actualIndex{static_cast<int>(dirContext_.size()) - 2};
+  std::size_t depth{0};
+  llvm::SmallVector<EffectiveDirectivePath, 4> paths(1);
+  if (!activeMetadirectiveReplacements_.empty()) {
+    const MetadirectiveReplacementContext &frame{
+        activeMetadirectiveReplacements_.back()};
+    depth = frame.directiveContextDepth;
+    paths = frame.paths;
+  }
+
+  EffectiveDirectivePath actualContexts;
+  while (actualIndex >= static_cast<int>(depth)) {
+    std::size_t index{static_cast<std::size_t>(actualIndex--)};
+    const DirectiveContext &context{dirContext_[index]};
+    // A metadirective is replaced, so it is transparent in an effective path.
+    if (context.directive == llvm::omp::Directive::OMPD_metadirective) {
+      continue;
+    }
+    actualContexts.push_back(context.directive);
+  }
+  for (EffectiveDirectivePath &path : paths) {
+    path.insert(path.begin(), actualContexts.begin(), actualContexts.end());
+  }
+  return paths;
+}
+
 bool OmpStructureChecker::IsCloselyNestedRegion(
     const llvm::omp::DirectiveSet &set) {
   // Definition of close nesting:
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 8016c82b8496f..b13319d1c6bfa 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -161,9 +161,11 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
 
   void Enter(const parser::OmpMetadirectiveDirective &);
   void Leave(const parser::OmpMetadirectiveDirective &);
+  void Enter(const parser::OmpDelimitedMetadirectiveDirective &);
+  void Leave(const parser::OmpDelimitedMetadirectiveDirective &);
 
   void Enter(const parser::ExecutionPartConstruct &);
-  void Leave(const parser::OmpClause::When &);
+  void Leave(const parser::ExecutionPartConstruct &);
 
   void Enter(const parser::OmpContextSelector &);
   void Leave(const parser::OmpContextSelector &);
@@ -306,11 +308,12 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckUnrollFullTripCount(const parser::OpenMPLoopConstruct &x);
 
-  void BeginMetadirectiveVariantScope();
-  void EndMetadirectiveVariantScope();
+  void BeginPendingLoopDirectiveScope();
+  void EndPendingLoopDirectiveScope();
 
   // check-omp-variant.cpp
-  void CheckMetadirectiveVariantsWithoutLoop(std::size_t firstVariant = 0);
+  void CheckPendingLoopDirectivesWithoutLoop(
+      std::size_t firstDirectiveGroup = 0);
   void CheckOmpDeclareVariantDirective(
       const parser::OmpDeclareVariantDirective &);
   void CheckDeclareVariantUserConditions(const parser::OmpContextSelector &);
@@ -374,6 +377,21 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   bool HasInvalidWorksharingNesting(
       const parser::OmpDirectiveName &name, const llvm::omp::DirectiveSet &);
 
+  using EffectiveDirectivePath = llvm::SmallVector<llvm::omp::Directive, 8>;
+
+  struct MetadirectiveReplacementBranch {
+    EffectiveDirectivePath enclosingPath;
+    const parser::OmpDirectiveSpecification *spec{nullptr};
+  };
+  struct MetadirectiveReplacementContext {
+    std::size_t directiveContextDepth;
+    llvm::SmallVector<EffectiveDirectivePath, 4> paths;
+  };
+
+  llvm::SmallVector<EffectiveDirectivePath, 4>
+  GetEnclosingDirectivePaths() const;
+  llvm::SmallVector<MetadirectiveReplacementBranch, 4>
+  GetReachableMetadirectiveReplacements(const parser::OmpClauseList &);
   bool IsCloselyNestedRegion(const llvm::omp::DirectiveSet &set);
   bool IsNestedInDirective(llvm::omp::Directive directive);
   bool IsCombinedParallelWorksharing(llvm::omp::Directive directive) const;
@@ -540,15 +558,16 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   };
   std::vector<PartKind> partStack_;
 
-  struct MetadirectiveLoopVariant {
-    const parser::traits::OmpContextSelectorSpecification *selector;
-    const parser::OmpDirectiveSpecification *spec;
-    bool checkDefaultNoneInAssociatedLoop;
+  struct PendingLoopDirectiveGroup {
+    llvm::SmallVector<MetadirectiveReplacementBranch, 4> branches;
+    bool activatesReplacementContext{false};
+    bool checkDefaultNoneInAssociatedLoop{false};
   };
-  std::vector<MetadirectiveLoopVariant> metadirectiveLoopVariants_;
-  std::vector<std::size_t> metadirectiveVariantScopeStarts_;
-  const parser::traits::OmpContextSelectorSpecification *currentWhenSelector_{
-      nullptr};
+  std::vector<PendingLoopDirectiveGroup> pendingLoopDirectiveGroups_;
+  std::vector<std::size_t> pendingLoopDirectiveScopeStarts_;
+  std::vector<bool> directiveSpecificationReachability_;
+  std::vector<MetadirectiveReplacementContext> activeMetadirectiveReplacements_;
+  std::vector<std::size_t> executionPartReplacementCounts_;
 
   std::multimap<const parser::Label,
       std::pair<parser::CharBlock, const parser::OpenMPConstruct *>>
diff --git a/flang/lib/Semantics/check-omp-variant.cpp b/flang/lib/Semantics/check-omp-variant.cpp
index 0f8daf84d25ec..11d3a88226608 100644
--- a/flang/lib/Semantics/check-omp-variant.cpp
+++ b/flang/lib/Semantics/check-omp-variant.cpp
@@ -28,6 +28,7 @@
 #include "flang/Semantics/tools.h"
 
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/Frontend/OpenMP/OMP.h"
 
 #include <algorithm>
@@ -189,20 +190,6 @@ void OmpStructureChecker::CheckDefaultNoneInAssociatedLoop(
 void OmpStructureChecker::Enter(const parser::OmpClause::When &x) {
   OmpVerifyModifiers(
       x.v, llvm::omp::OMPC_when, GetContext().clauseSource, context_);
-  // Record this WHEN clause's context selector so the variant directive it
-  // controls can be paired with it for static-applicability matching. A
-  // well-formed WHEN clause has exactly one modifier, its context selector;
-  // pair it only in that case, which also makes front() safe. Any other count
-  // is malformed and already diagnosed by OmpVerifyModifiers above.
-  if (const auto &modifiers{std::get<0>(x.v.t)};
-      modifiers && modifiers->size() == 1) {
-    currentWhenSelector_ =
-        &std::get<parser::modifier::OmpContextSelector>(modifiers->front().u);
-  }
-}
-
-void OmpStructureChecker::Leave(const parser::OmpClause::When &) {
-  currentWhenSelector_ = nullptr;
 }
 
 void OmpStructureChecker::CheckContextSelectorSpecification(
@@ -704,6 +691,70 @@ void OmpStructureChecker::CheckTraitSimd(
   }
 }
 
+llvm::SmallVector<OmpStructureChecker::MetadirectiveReplacementBranch, 4>
+OmpStructureChecker::GetReachableMetadirectiveReplacements(
+    const parser::OmpClauseList &clauses) {
+  llvm::SmallVector<MetadirectiveReplacementBranch, 4> result;
+
+  for (const EffectiveDirectivePath &path : GetEnclosingDirectivePaths()) {
+    std::size_t firstBranch{result.size()};
+    llvm::omp::VariantMatchInfo constructVMI;
+    for (auto context{path.rbegin()}; context != path.rend(); ++context) {
+      AppendConstructTraitsForDirective(*context, constructVMI);
+    }
+    llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits(
+        constructVMI.ConstructTraits.begin(),
+        constructVMI.ConstructTraits.end());
+    OmpVariantMatchContext matchContext{context_, constructTraits};
+    if (auto candidateSet{
+            BuildMetadirectiveCandidateSet(clauses, context_, matchContext)}) {
+      for (const parser::OmpDirectiveSpecification *spec :
+          GetReachableMetadirectiveVariants(
+              *candidateSet, matchContext, context_)) {
+        if (spec && spec->DirId() == llvm::omp::Directive::OMPD_nothing) {
+          spec = nullptr;
+        }
+        result.push_back({path, spec});
+      }
+      if (result.size() == firstBranch) {
+        result.push_back({path, nullptr});
+      }
+      continue;
+    }
+
+    // Unsupported selectors are diagnosed elsewhere. Retain every explicit
+    // replacement on each path so recovery cannot miss a loop constraint.
+    for (const parser::OmpClause &clause : clauses.v) {
+      const parser::OmpDirectiveSpecification *spec{nullptr};
+      if (const auto *when{std::get_if<parser::OmpClause::When>(&clause.u)}) {
+        const auto &optionalSpec{std::get<1>(when->v.t)};
+        if (optionalSpec) {
+          spec = &optionalSpec->value();
+        }
+      } else if (const auto *otherwise{
+                     std::get_if<parser::OmpClause::Otherwise>(&clause.u)}) {
+        if (otherwise->v && otherwise->v->v) {
+          spec = &otherwise->v->v->value();
+        }
+      } else if (const auto *defaultVariant{
+                     std::get_if<parser::OmpClause::DefaultVariant>(
+                         &clause.u)}) {
+        spec = &defaultVariant->v.v.value();
+      } else {
+        continue;
+      }
+      if (spec && spec->DirId() == llvm::omp::Directive::OMPD_nothing) {
+        spec = nullptr;
+      }
+      result.push_back({path, spec});
+    }
+    if (result.size() == firstBranch) {
+      result.push_back({path, nullptr});
+    }
+  }
+  return result;
+}
+
 void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
   // OmpDirectiveSpecification exists on its own only in clauses on
   // METADIRECTIVE.
@@ -714,11 +765,8 @@ void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
   }
 
   llvm::omp::Directive dirId{x.DirId()};
-  bool checkDefaultNoneInAssociatedLoop{
-      GetDirectiveNest(MetadirectiveNest) != 0};
   if (const parser::OpenMPConstruct *meta{GetCurrentConstruct()}) {
     if (parser::Unwrap<parser::OmpDelimitedMetadirectiveDirective>(meta->u)) {
-      checkDefaultNoneInAssociatedLoop = false;
       unsigned version{context_.langOptions().OpenMPVersion};
       switch (llvm::omp::getDirectiveAssociation(dirId)) {
       case llvm::omp::Association::Block:
@@ -739,21 +787,43 @@ void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
   PushContextAndClauseSets(
       std::get<parser::OmpDirectiveName>(x.t).source, dirId);
 
-  // Record each variant directive. A loop-associated one is later validated
-  // against the loop nest that follows the metadirective.
-  if (dirId != llvm::omp::Directive::OMPD_metadirective) {
-    metadirectiveLoopVariants_.push_back(
-        {currentWhenSelector_, &x, checkDefaultNoneInAssociatedLoop});
+  // Each metadirective group already contains its ranked reachable
+  // replacements. APPLY specifications are separate loop transformations;
+  // retain them only when their containing replacement is reachable.
+  bool reachable{true};
+  if (GetDirectiveNest(MetadirectiveNest)) {
+    reachable = llvm::any_of(pendingLoopDirectiveGroups_,
+        [&x](const PendingLoopDirectiveGroup &group) {
+          return llvm::any_of(group.branches,
+              [&x](const MetadirectiveReplacementBranch &branch) {
+                return branch.spec == &x;
+              });
+        });
+    if (!reachable && GetDirectiveNest(ApplyNest) &&
+        !directiveSpecificationReachability_.empty()) {
+      reachable = directiveSpecificationReachability_.back();
+    }
+  }
+  directiveSpecificationReachability_.push_back(reachable);
+
+  if (GetDirectiveNest(ApplyNest) && reachable &&
+      dirId != llvm::omp::Directive::OMPD_metadirective) {
+    pendingLoopDirectiveGroups_.push_back(
+        {{{EffectiveDirectivePath{}, &x}}, false, false});
   }
 }
 
 void OmpStructureChecker::Leave(const parser::OmpDirectiveSpecification &x) {
   if (GetDirectiveNest(MetadirectiveNest) || GetDirectiveNest(ApplyNest)) {
+    CHECK(!directiveSpecificationReachability_.empty());
+    directiveSpecificationReachability_.pop_back();
     dirContext_.pop_back();
   }
 }
 
 void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) {
+  auto branches{GetReachableMetadirectiveReplacements(x.v.Clauses())};
+  pendingLoopDirectiveGroups_.push_back({std::move(branches), true, true});
   EnterDirectiveNest(MetadirectiveNest);
 }
 
@@ -761,12 +831,35 @@ void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) {
   ExitDirectiveNest(MetadirectiveNest);
 }
 
+void OmpStructureChecker::Enter(
+    const parser::OmpDelimitedMetadirectiveDirective &x) {
+  auto branches{GetReachableMetadirectiveReplacements(x.BeginDir().Clauses())};
+  llvm::SmallVector<EffectiveDirectivePath, 4> paths;
+  for (const MetadirectiveReplacementBranch &branch : branches) {
+    EffectiveDirectivePath path{branch.enclosingPath};
+    if (branch.spec) {
+      path.insert(path.begin(), branch.spec->DirId());
+    }
+    paths.push_back(std::move(path));
+  }
+  activeMetadirectiveReplacements_.push_back(
+      {dirContext_.size(), std::move(paths)});
+  pendingLoopDirectiveGroups_.push_back({std::move(branches), false, false});
+}
+
+void OmpStructureChecker::Leave(
+    const parser::OmpDelimitedMetadirectiveDirective &) {
+  CHECK(!activeMetadirectiveReplacements_.empty());
+  activeMetadirectiveReplacements_.pop_back();
+}
+
 // Check a loop-associated metadirective's variants against the loop nest they
 // apply to. The nest is not attached to the directive in the parse tree. It is
 // the next executable construct, either a following sibling or the first
 // execution-part construct for a declarative metadirective.
 void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
-  if (metadirectiveLoopVariants_.empty()) {
+  executionPartReplacementCounts_.push_back(0);
+  if (pendingLoopDirectiveGroups_.empty()) {
     return;
   }
   if (parser::Unwrap<parser::CompilerDirective>(x)) {
@@ -777,14 +870,36 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
   if (!parser::Unwrap<parser::DoConstruct>(x)) {
     // A non-loop construct follows, so a loop-associated variant has no loop
     // nest to associate with.
-    CheckMetadirectiveVariantsWithoutLoop();
+    CheckPendingLoopDirectivesWithoutLoop();
     return;
   }
 
-  // A loop nest follows. Take the pending variants off the worklist and
-  // validate them against it.
-  std::vector<MetadirectiveLoopVariant> variants;
-  variants.swap(metadirectiveLoopVariants_);
+  // A loop nest follows. Take the pending groups off the worklist and validate
+  // their reachable directives against it.
+  std::vector<PendingLoopDirectiveGroup> pending;
+  pending.swap(pendingLoopDirectiveGroups_);
+  for (const PendingLoopDirectiveGroup &group : pending) {
+    if (!group.activatesReplacementContext) {
+      continue;
+    }
+    llvm::SmallVector<EffectiveDirectivePath, 4> paths;
+    for (const MetadirectiveReplacementBranch &branch : group.branches) {
+      EffectiveDirectivePath path{branch.enclosingPath};
+      const parser::OmpDirectiveSpecification *spec{branch.spec};
+      if (spec) {
+        llvm::omp::Association association{
+            llvm::omp::getDirectiveAssociation(spec->DirId())};
+        if (association == llvm::omp::Association::LoopNest ||
+            association == llvm::omp::Association::LoopSeq) {
+          path.insert(path.begin(), spec->DirId());
+        }
+      }
+      paths.push_back(std::move(path));
+    }
+    activeMetadirectiveReplacements_.push_back(
+        {dirContext_.size(), std::move(paths)});
+    ++executionPartReplacementCounts_.back();
+  }
 
   unsigned version{context_.langOptions().OpenMPVersion};
   LoopSequence sequence(x, version, /*allowAllLoops=*/true, &context_);
@@ -819,87 +934,91 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
         return false;
       };
 
-  // Build the matching context once for the static-applicability gate below.
-  OmpVariantMatchContext matchContext{context_};
   UnorderedSymbolSet defaultNoneDiagnosed;
+  llvm::SmallPtrSet<const parser::OmpDirectiveSpecification *, 8>
+      checkedSpecifications;
 
-  for (const MetadirectiveLoopVariant &variant : variants) {
-    const parser::OmpDirectiveSpecification *spec{variant.spec};
-    // Skip variants that can never be selected on this compilation target so
-    // that their associated loop is not diagnosed.
-    if (!MayVariantBeSelected(variant.selector, context_, matchContext)) {
-      continue;
-    }
-    auto assoc{llvm::omp::getDirectiveAssociation(spec->DirId())};
-    if (assoc == llvm::omp::Association::LoopNest) {
-      if (!checkRootLoopCanonical(*spec, /*isSequence=*/false)) {
+  for (const PendingLoopDirectiveGroup &group : pending) {
+    for (const MetadirectiveReplacementBranch &branch : group.branches) {
+      const parser::OmpDirectiveSpecification *spec{branch.spec};
+      if (!spec || !checkedSpecifications.insert(spec).second) {
         continue;
       }
+      auto assoc{llvm::omp::getDirectiveAssociation(spec->DirId())};
+      if (assoc == llvm::omp::Association::LoopNest) {
+        if (!checkRootLoopCanonical(*spec, /*isSequence=*/false)) {
+          continue;
+        }
 
-      // A standalone metadirective does not contain its associated loop in
-      // the parse tree, so name resolution cannot apply DEFAULT(NONE) to it.
-      if (variant.checkDefaultNoneInAssociatedLoop) {
-        CheckDefaultNoneInAssociatedLoop(*spec, rootLoop, defaultNoneDiagnosed);
-      }
+        // A standalone metadirective does not contain its associated loop in
+        // the parse tree, so name resolution cannot apply DEFAULT(NONE) to it.
+        if (group.checkDefaultNoneInAssociatedLoop) {
+          CheckDefaultNoneInAssociatedLoop(
+              *spec, rootLoop, defaultNoneDiagnosed);
+        }
 
-      auto [needDepth, needPerfect]{
-          GetAffectedNestDepthWithReason(*spec, version, &context_)};
-      auto haveDepth{needPerfect ? havePerfect : haveSemantic};
-      if (!needDepth || *needDepth.value <= 0 || !haveDepth ||
-          *haveDepth.value <= 0) {
-        continue;
-      }
-      if (*needDepth.value > *haveDepth.value) {
-        std::string_view perfectTxt{needPerfect ? " perfect" : ""};
-        auto &msg{context_.Say(spec->DirName().source,
-            "This construct requires a%s nest of depth %" PRId64
-            ", but the associated nest is a%s nest of depth %" PRId64
-            ""_err_en_US,
-            perfectTxt, *needDepth.value, perfectTxt, *haveDepth.value)};
-        haveDepth.reason.AttachTo(msg);
-        needDepth.reason.AttachTo(msg);
-      } else {
-        CheckRectangularNest(*spec, sequence);
+        auto [needDepth, needPerfect]{
+            GetAffectedNestDepthWithReason(*spec, version, &context_)};
+        auto haveDepth{needPerfect ? havePerfect : haveSemantic};
+        if (!needDepth || *needDepth.value <= 0 || !haveDepth ||
+            *haveDepth.value <= 0) {
+          continue;
+        }
+        if (*needDepth.value > *haveDepth.value) {
+          std::string_view perfectTxt{needPerfect ? " perfect" : ""};
+          auto &msg{context_.Say(spec->DirName().source,
+              "This construct requires a%s nest of depth %" PRId64
+              ", but the associated nest is a%s nest of depth %" PRId64
+              ""_err_en_US,
+              perfectTxt, *needDepth.value, perfectTxt, *haveDepth.value)};
+          haveDepth.reason.AttachTo(msg);
+          needDepth.reason.AttachTo(msg);
+        } else {
+          CheckRectangularNest(*spec, sequence);
+        }
+      } else if (assoc == llvm::omp::Association::LoopSeq) {
+        (void)checkRootLoopCanonical(*spec, /*isSequence=*/true);
       }
-    } else if (assoc == llvm::omp::Association::LoopSeq) {
-      (void)checkRootLoopCanonical(*spec, /*isSequence=*/true);
     }
   }
 }
 
-// Diagnose loop-associated metadirective variants that are not followed by a
-// loop nest, either because the metadirective is the last construct in the
-// execution part or because a non-loop construct follows it. Variants that
-// cannot be selected on this target are skipped.
-void OmpStructureChecker::CheckMetadirectiveVariantsWithoutLoop(
-    std::size_t firstVariant) {
-  CHECK(firstVariant <= metadirectiveLoopVariants_.size());
-  std::vector<MetadirectiveLoopVariant> variants;
-  if (firstVariant == 0) {
-    variants.swap(metadirectiveLoopVariants_);
-  } else {
-    auto first{metadirectiveLoopVariants_.begin() + firstVariant};
-    variants.assign(first, metadirectiveLoopVariants_.end());
-    metadirectiveLoopVariants_.erase(first, metadirectiveLoopVariants_.end());
-  }
+void OmpStructureChecker::Leave(const parser::ExecutionPartConstruct &) {
+  CHECK(!executionPartReplacementCounts_.empty());
+  std::size_t count{executionPartReplacementCounts_.back()};
+  executionPartReplacementCounts_.pop_back();
+  CHECK(count <= activeMetadirectiveReplacements_.size());
+  activeMetadirectiveReplacements_.resize(
+      activeMetadirectiveReplacements_.size() - count);
+}
 
-  OmpVariantMatchContext matchContext{context_};
+// Diagnose reachable loop-associated directives that are not followed by a
+// loop nest, either because the directive is last in the execution part or
+// because a non-loop construct follows it.
+void OmpStructureChecker::CheckPendingLoopDirectivesWithoutLoop(
+    std::size_t firstDirectiveGroup) {
+  CHECK(firstDirectiveGroup <= pendingLoopDirectiveGroups_.size());
   const auto MsgShouldContainDoOr{
       "This construct should contain a DO-loop or a loop-%s-generating construct"_err_en_US};
-
-  for (const MetadirectiveLoopVariant &variant : variants) {
-    if (!MayVariantBeSelected(variant.selector, context_, matchContext)) {
-      continue;
-    }
-    auto assoc{llvm::omp::getDirectiveAssociation(variant.spec->DirId())};
-    if (assoc == llvm::omp::Association::LoopNest) {
-      context_.Say(
-          variant.spec->DirName().source, MsgShouldContainDoOr, "nest");
-    } else if (assoc == llvm::omp::Association::LoopSeq) {
-      context_.Say(
-          variant.spec->DirName().source, MsgShouldContainDoOr, "sequence");
+  llvm::SmallPtrSet<const parser::OmpDirectiveSpecification *, 8>
+      checkedSpecifications;
+
+  auto first{pendingLoopDirectiveGroups_.begin() + firstDirectiveGroup};
+  for (auto group{first}; group != pendingLoopDirectiveGroups_.end(); ++group) {
+    for (const MetadirectiveReplacementBranch &branch : group->branches) {
+      const parser::OmpDirectiveSpecification *spec{branch.spec};
+      if (!spec || !checkedSpecifications.insert(spec).second) {
+        continue;
+      }
+      auto assoc{llvm::omp::getDirectiveAssociation(spec->DirId())};
+      if (assoc == llvm::omp::Association::LoopNest) {
+        context_.Say(spec->DirName().source, MsgShouldContainDoOr, "nest");
+      } else if (assoc == llvm::omp::Association::LoopSeq) {
+        context_.Say(spec->DirName().source, MsgShouldContainDoOr, "sequence");
+      }
     }
   }
+  pendingLoopDirectiveGroups_.erase(first, pendingLoopDirectiveGroups_.end());
 }
 
 static const parser::traits::OmpContextSelectorSpecification *
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index fa4b8f4f26987..873806c38ad65 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -2426,7 +2426,7 @@ UnsupportedSelectorFeature FindUnsupportedSelectorFeature(
 // `target` adds `construct_target_target`, `target teams` adds both
 // `construct_target_target` and `construct_teams_teams`) to \p vmi. This
 // decomposes combined/composite construct selectors into their leaf traits.
-static void AppendConstructTraitsForDirective(
+void AppendConstructTraitsForDirective(
     llvm::omp::Directive dir, llvm::omp::VariantMatchInfo &vmi) {
   auto add = [&](llvm::omp::TraitProperty prop) {
     vmi.addTrait(prop, llvm::omp::getOpenMPContextTraitPropertyName(prop, ""));
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability-apply.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-apply.f90
new file mode 100644
index 0000000000000..15b01f4375e16
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-apply.f90
@@ -0,0 +1,23 @@
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+! A loop transformation in APPLY inherits the reachability of the
+! metadirective replacement that contains it.
+
+subroutine f01()
+  !$omp metadirective &
+  !$omp& when(user={condition(score(10): .true.)}: nothing) &
+  !$omp& when(user={condition(score(5): .true.)}: &
+  !$omp& tile sizes(2) apply(grid: unroll)) &
+  !$omp& otherwise(nothing)
+end subroutine
+
+subroutine f02(flag)
+  logical :: flag
+  !$omp metadirective &
+  !$omp& when(user={condition(score(10): flag)}: nothing) &
+  !$omp& when(user={condition(score(5): .true.)}: &
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating construct
+  !ERROR: This construct should contain a DO-loop or a loop-nest-generating construct
+  !$omp& tile sizes(2) apply(grid: unroll)) &
+  !$omp& otherwise(nothing)
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
index 0665ab304aea5..a2e2208c38da2 100644
--- a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
@@ -131,3 +131,82 @@ subroutine f11()
   logical, parameter :: use_variant = .true.
   !$omp metadirective when(user={condition(use_variant)}, implementation={extension(match_none)}: do) default(nothing)
 end subroutine
+
+! A higher-scored static implicit NOTHING makes the lower-scored loop variant
+! unreachable, so the latter's loop requirements are not checked.
+subroutine f12(n, a)
+  integer :: n, a(n), i
+  !$omp metadirective &
+  !$omp& when(user={condition(score(10): .true.)}:) &
+  !$omp& when(user={condition(score(5): .true.)}: do collapse(2)) &
+  !$omp& default(nothing)
+  do i = 1, n
+    a(i) = i
+  end do
+end subroutine
+
+! A dynamic implicit NOTHING leaves the lower-scored loop variant reachable
+! when its condition is false, so that variant's requirements are checked.
+subroutine f13(flag, n, a)
+  logical :: flag
+  integer :: n, a(n), i
+  !$omp metadirective &
+  !$omp& when(user={condition(score(10): flag)}:) &
+  !ERROR: This construct requires a nest of depth 2, but the associated nest is a nest of depth 1
+  !BECAUSE: COLLAPSE clause was specified with argument 2
+  !$omp& when(user={condition(score(5): .true.)}: do collapse(2)) &
+  !$omp& default(nothing)
+  do i = 1, n
+    a(i) = i
+  end do
+end subroutine
+
+! A nested construct selector observes the loop directive selected by a
+! standalone metadirective.
+subroutine f14(n, a)
+  integer :: n, a(n, n), i, j
+  !$omp metadirective when(implementation={vendor(llvm)}: do) default(nothing)
+  do i = 1, n
+    !ERROR: This construct requires a nest of depth 2, but the associated nest is a nest of depth 1
+    !BECAUSE: COLLAPSE clause was specified with argument 2
+    !$omp metadirective when(construct={do}: simd collapse(2)) default(nothing)
+    do j = 1, n
+      a(j, i) = i
+    end do
+  end do
+end subroutine
+
+! Actual and selected contexts are both visible inside a begin/end
+! metadirective.
+subroutine f15(n, a)
+  integer :: n, a(n, n), i, j
+  !$omp target
+    !$omp begin metadirective &
+    !$omp& when(implementation={vendor(llvm)}: simd) default(nothing)
+    do i = 1, n
+      !ERROR: This construct requires a nest of depth 2, but the associated nest is a nest of depth 1
+      !BECAUSE: COLLAPSE clause was specified with argument 2
+      !$omp metadirective when(construct={target, simd}: simd collapse(2)) default(nothing)
+      do j = 1, n
+        a(j, i) = i
+      end do
+    end do
+    !$omp end metadirective
+  !$omp end target
+end subroutine
+
+! Mutually exclusive selected contexts are not flattened together. No path has
+! both TARGET and PARALLEL, so the inner loop variant is unreachable.
+subroutine f16(flag, n, a)
+  logical :: flag
+  integer :: n, a(n), i
+  !$omp begin metadirective &
+  !$omp& when(user={condition(flag)}: target) default(parallel)
+    !$omp metadirective &
+    !$omp& when(construct={target, parallel}: simd collapse(2)) &
+    !$omp& default(nothing)
+    do i = 1, n
+      a(i) = i
+    end do
+  !$omp end metadirective
+end subroutine



More information about the llvm-branch-commits mailing list