[flang-commits] [flang] [llvm] [flang][OpenMP] Track reachable metadirective replacements (PR #219014)
via flang-commits
flang-commits at lists.llvm.org
Tue Sep 8 10:51:05 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/219014
>From fbe9a26583ec49487d9c0fdce6907280a8104da0 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 1/3] [flang][OpenMP] Track reachable metadirective replacement
paths
The existing semantic checks can validate loop-associated directives in a
METADIRECTIVE against the following loop, but they do not model how the
METADIRECTIVE chooses among its replacements.
Today each WHEN is considered independently: if its selector can match, its
replacement is checked. Selection instead ranks all applicable candidates as
a set. An unguarded higher-ranked candidate makes lower-ranked candidates
unreachable, while a dynamically guarded candidate leaves them reachable
when its condition is false. Treating both cases alike can diagnose loop
requirements on a replacement that can never be selected.
The selected replacement can also affect later selection. Its directive
contributes to the construct context seen by a nested metadirective. The
checker currently retains only syntactic nesting, so nested construct
selectors cannot observe a directive selected by an enclosing
metadirective.
For example, consider an enclosing dynamic choice and a nested construct
selector:
outer: when(user={condition(flag)}: target) default(parallel)
inner: when(construct={parallel}: simd) default(nothing)
The enclosing selection creates two mutually exclusive contexts:
outer metadirective
/ \
flag=true flag=false
| |
TARGET PARALLEL
| |
construct={parallel} construct={parallel}
no match match
|
SIMD
Dropping selected directives misses the match on the PARALLEL path.
Flattening both paths would instead make the nested selector observe TARGET
and PARALLEL together, a construct context that can never occur.
This PR models each possible selection outcome as a reachable directive path.
For each enclosing path, it uses the existing candidate ranking to retain only
replacements that can win after static and dynamic conditions are considered.
It runs loop checks only for directive specifications present on at least one
reachable path, and propagates the same reachability through APPLY.
Each path is extended with the selected directive while walking its associated
loop or begin/end region. This lets nested construct selectors observe both
syntactic and selected enclosing constructs. The selected context is removed
at its association boundary so it cannot affect later constructs.
When a selector cannot be modeled, all replacements are conservatively
retained so error recovery does not suppress required checks. To control path
growth, this PR collects the construct selectors in the program unit and
merges only paths that none of those selectors can distinguish.
Add coverage for ranked static and dynamic candidates, implicit NOTHING,
nested standalone and begin/end metadirectives, non-NOTHING fallbacks,
context lifetime, and APPLY reachability.
Assisted with codex.
---
flang/include/flang/Semantics/openmp-utils.h | 4 +
flang/lib/Semantics/check-omp-structure.cpp | 81 +++-
flang/lib/Semantics/check-omp-structure.h | 50 +-
flang/lib/Semantics/check-omp-variant.cpp | 442 ++++++++++++++----
flang/lib/Semantics/openmp-utils.cpp | 2 +-
...metadirective-loop-applicability-apply.f90 | 23 +
...directive-loop-applicability-openmp-60.f90 | 13 +
.../metadirective-loop-applicability.f90 | 186 ++++++++
8 files changed, 666 insertions(+), 135 deletions(-)
create mode 100644 flang/test/Semantics/OpenMP/metadirective-loop-applicability-apply.f90
create mode 100644 flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index ffde7c6ac20eb..7c3f0e529d048 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -242,6 +242,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 a238313ecf3fd..e8559eab438c2 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -80,18 +80,19 @@ OmpStructureChecker::OmpStructureChecker(SemanticsContext &context)
scopeStack_.push_back(&context.globalScope());
}
-void OmpStructureChecker::Enter(const parser::ProgramUnit &) { //
+void OmpStructureChecker::Enter(const parser::ProgramUnit &x) { //
ClearLabels();
declareVariantPairs_.clear();
+ CollectMetadirectiveConstructSelectors(x);
}
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 +208,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 +253,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 +274,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 +288,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 +652,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 GetUniqueEffectiveDirectivePaths(std::move(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 c3ac73935f4df..d790ba3f1e04c 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -23,6 +23,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Frontend/OpenMP/OMP.h"
+#include "llvm/Frontend/OpenMP/OMPContext.h"
#include "llvm/Frontend/OpenMP/OMPDescriptors.h"
#include <cstddef>
@@ -178,9 +179,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 &);
@@ -257,7 +260,6 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void Enter(const parser::OmpClause::UseDeviceAddr &x);
void Enter(const parser::OmpClause::UseDevicePtr &x);
void Enter(const parser::OmpClause::UsesAllocators &x);
- void Enter(const parser::OmpClause::When &x);
private:
using LoopOrConstruct = std::variant<const parser::DoConstruct *,
@@ -321,11 +323,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 &);
@@ -405,6 +408,25 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
bool HasInvalidWorksharingNesting(
const parser::OmpDirectiveName &name, const llvm::omp::DirectiveSet &);
+ using EffectiveDirectivePath = llvm::SmallVector<llvm::omp::Directive, 8>;
+ using ConstructTraitSequence = llvm::SmallVector<llvm::omp::TraitProperty, 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<EffectiveDirectivePath, 4> GetUniqueEffectiveDirectivePaths(
+ llvm::SmallVector<EffectiveDirectivePath, 4>) const;
+ void CollectMetadirectiveConstructSelectors(const parser::ProgramUnit &);
+ 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;
@@ -571,15 +593,17 @@ 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<ConstructTraitSequence> metadirectiveConstructSelectors_;
+ 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 d4372897b84a4..3f4ca36fb5775 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>
@@ -46,6 +47,67 @@ using namespace Fortran::semantics::omp;
namespace {
+class MetadirectiveConstructSelectorCollector {
+public:
+ using ConstructTraitSequence = llvm::SmallVector<llvm::omp::TraitProperty, 8>;
+
+ explicit MetadirectiveConstructSelectorCollector(
+ std::vector<ConstructTraitSequence> &sequences)
+ : sequences_{sequences} {}
+
+ template <typename T> bool Pre(const T &) { return true; }
+ template <typename T> void Post(const T &) {}
+ bool Pre(const parser::CharBlock &) { return false; }
+
+ bool Pre(const parser::OmpClause::When &when) {
+ const auto &modifiers{std::get<0>(when.v.t)};
+ if (!modifiers || modifiers->size() != 1) {
+ return false;
+ }
+ const auto *contextSelector{
+ std::get_if<parser::modifier::OmpContextSelector>(
+ &modifiers->front().u)};
+ if (!contextSelector) {
+ return false;
+ }
+
+ ConstructTraitSequence sequence;
+ for (const parser::OmpTraitSetSelector &traitSet : contextSelector->v) {
+ using SetName = parser::OmpTraitSetSelectorName;
+ if (std::get<SetName>(traitSet.t).v != SetName::Value::Construct) {
+ continue;
+ }
+ for (const parser::OmpTraitSelector &selector :
+ std::get<std::list<parser::OmpTraitSelector>>(traitSet.t)) {
+ const auto &properties{
+ std::get<std::optional<parser::OmpTraitSelector::Properties>>(
+ selector.t)};
+ if (properties) {
+ // Construct properties are not modelled by variant matching yet.
+ // The recovery path retains every replacement independently of the
+ // construct context, so this selector does not distinguish paths.
+ return false;
+ }
+ const auto &name{std::get<parser::OmpTraitSelectorName>(selector.t)};
+ if (const auto *directive{std::get_if<llvm::omp::Directive>(&name.u)}) {
+ llvm::omp::VariantMatchInfo vmi;
+ AppendConstructTraitsForDirective(*directive, vmi);
+ sequence.append(
+ vmi.ConstructTraits.begin(), vmi.ConstructTraits.end());
+ }
+ }
+ }
+
+ if (!sequence.empty() && !llvm::is_contained(sequences_, sequence)) {
+ sequences_.push_back(std::move(sequence));
+ }
+ return false;
+ }
+
+private:
+ std::vector<ConstructTraitSequence> &sequences_;
+};
+
bool HasDefaultNone(const parser::OmpDirectiveSpecification &spec) {
using DataSharingAttribute = parser::OmpDefaultClause::DataSharingAttribute;
const parser::OmpClause *clause{
@@ -186,23 +248,6 @@ void OmpStructureChecker::CheckDefaultNoneInAssociatedLoop(
parser::Walk(rootLoop, checker);
}
-void OmpStructureChecker::Enter(const parser::OmpClause::When &x) {
- // 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 VerifyModifiers.
- 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(
const parser::OmpContextSelector &ctx) {
using SetName = parser::OmpTraitSetSelectorName;
@@ -702,6 +747,141 @@ void OmpStructureChecker::CheckTraitSimd(
}
}
+void OmpStructureChecker::CollectMetadirectiveConstructSelectors(
+ const parser::ProgramUnit &programUnit) {
+ metadirectiveConstructSelectors_.clear();
+ MetadirectiveConstructSelectorCollector collector{
+ metadirectiveConstructSelectors_};
+ parser::Walk(programUnit, collector);
+}
+
+llvm::SmallVector<OmpStructureChecker::EffectiveDirectivePath, 4>
+OmpStructureChecker::GetUniqueEffectiveDirectivePaths(
+ llvm::SmallVector<EffectiveDirectivePath, 4> paths) const {
+ if (paths.size() < 2) {
+ return paths;
+ }
+
+ // Selected directive paths are observed only by construct selectors on
+ // nested metadirectives. If there are none in this program unit, every path
+ // is equivalent for this analysis.
+ if (metadirectiveConstructSelectors_.empty()) {
+ paths.resize(1);
+ return paths;
+ }
+
+ auto getSignature = [&](const EffectiveDirectivePath &path) {
+ llvm::omp::VariantMatchInfo contextVMI;
+ for (auto directive{path.rbegin()}; directive != path.rend(); ++directive) {
+ AppendConstructTraitsForDirective(*directive, contextVMI);
+ }
+ const auto &contextTraits{contextVMI.ConstructTraits};
+
+ // Matching depends on trait presence and on the positions at which each
+ // ordered construct-selector prefix is matched. This is also enough to
+ // update the match when an inner directive is appended later.
+ std::vector<unsigned> signature;
+ signature.reserve(
+ 1 + 2 * contextTraits.size() * metadirectiveConstructSelectors_.size());
+ signature.push_back(contextTraits.size());
+ for (const ConstructTraitSequence &selector :
+ metadirectiveConstructSelectors_) {
+ for (llvm::omp::TraitProperty property : selector) {
+ signature.push_back(llvm::is_contained(contextTraits, property));
+ }
+
+ std::size_t contextIndex{0};
+ for (llvm::omp::TraitProperty property : selector) {
+ while (contextIndex < contextTraits.size() &&
+ contextTraits[contextIndex] != property) {
+ ++contextIndex;
+ }
+ if (contextIndex == contextTraits.size()) {
+ signature.push_back(0);
+ } else {
+ // Reserve zero for an unmatched property.
+ signature.push_back(++contextIndex);
+ }
+ }
+ }
+ return signature;
+ };
+
+ std::set<std::vector<unsigned>> signatures;
+ llvm::SmallVector<EffectiveDirectivePath, 4> uniquePaths;
+ uniquePaths.reserve(paths.size());
+ for (EffectiveDirectivePath &path : paths) {
+ if (signatures.insert(getSignature(path)).second) {
+ uniquePaths.push_back(std::move(path));
+ }
+ }
+ return uniquePaths;
+}
+
+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.
@@ -712,11 +892,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;
llvm::omp::Version version{context_.langOptions().getOpenMPVersion()};
switch (llvm::omp::getDirectiveAssociation(dirId)) {
case llvm::omp::Association::Block:
@@ -737,21 +914,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);
}
@@ -759,12 +958,36 @@ 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));
+ }
+ paths = GetUniqueEffectiveDirectivePaths(std::move(paths));
+ 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)) {
@@ -775,14 +998,37 @@ 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));
+ }
+ paths = GetUniqueEffectiveDirectivePaths(std::move(paths));
+ activeMetadirectiveReplacements_.push_back(
+ {dirContext_.size(), std::move(paths)});
+ ++executionPartReplacementCounts_.back();
+ }
llvm::omp::Version version{context_.langOptions().getOpenMPVersion()};
LoopSequence sequence(x, version, /*allowAllLoops=*/true, &context_);
@@ -817,87 +1063,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 86fe11ddbc12c..3dbed292f40af 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -2427,7 +2427,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-openmp-60.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
new file mode 100644
index 0000000000000..6729a06fc4faa
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
@@ -0,0 +1,13 @@
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+! An unsupported selector conservatively retains its OTHERWISE replacement.
+subroutine f01(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !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(target_device={kind(host)}: nothing) otherwise(do collapse(2))
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
index 0665ab304aea5..9c59d3ff1f5a7 100644
--- a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
@@ -131,3 +131,189 @@ 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)}: parallel) 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, parallel}: 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 first inner loop variant is unreachable.
+! The second inner variant verifies that the dynamic TARGET path is retained.
+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
+
+ !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 collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+! A non-NOTHING fallback contributes its selected context to nested matching.
+subroutine f17(flag, n, a)
+ logical :: flag
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(user={condition(flag)}: target) default(parallel)
+ !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={parallel}: simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+! A selected context ends with its associated loop and does not affect a
+! subsequent metadirective.
+subroutine f18(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective when(implementation={vendor(llvm)}: do) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+
+ !$omp metadirective when(construct={do}: simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! A selected context ends at END METADIRECTIVE and does not affect a
+! subsequent metadirective.
+subroutine f19(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel) default(nothing)
+ a(1) = 1
+ !$omp end metadirective
+
+ !$omp metadirective when(construct={parallel}: simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Unsupported selectors conservatively retain their explicit replacement.
+subroutine f20(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !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(target_device={kind(host)}: do collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Unsupported selectors also retain the DEFAULT replacement.
+subroutine f21(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !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(target_device={kind(host)}: nothing) default(do collapse(2))
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Recovery retains replacements from siblings of an unsupported selector.
+subroutine f22(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(target_device={kind(host)}: nothing) &
+ !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(implementation={vendor(llvm)}: do collapse(2)) &
+ !$omp& default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! A selected composite directive contributes each of its leaf constructs.
+subroutine f23(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel do) default(nothing)
+ do i = 1, n
+ !$omp metadirective &
+ !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(construct={parallel, do}: simd collapse(2)) default(nothing)
+ do j = 1, n
+ a(j, i) = i
+ end do
+ end do
+end subroutine
>From 2a1e12d105071c878c42a73188d8bccf7a9c2203 Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Thu, 3 Sep 2026 16:25:26 -0500
Subject: [PATCH 2/3] Fix metadirective reachable path boundaries
Stop construct trait paths at the innermost target and retain the implicit
nothing fallback during unsupported-selector recovery.
Add coverage for actual and selected target boundaries and for nested
metadirectives reached through implicit fallback.
---
flang/lib/Semantics/check-omp-structure.h | 2 ++
flang/lib/Semantics/check-omp-variant.cpp | 36 ++++++++++++-------
...directive-loop-applicability-openmp-60.f90 | 16 +++++++++
.../metadirective-loop-applicability.f90 | 32 +++++++++++++++++
4 files changed, 73 insertions(+), 13 deletions(-)
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index d790ba3f1e04c..497d3379242e1 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -422,6 +422,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
llvm::SmallVector<EffectiveDirectivePath, 4>
GetEnclosingDirectivePaths() const;
+ ConstructTraitSequence GetConstructTraitsForPath(
+ const EffectiveDirectivePath &) const;
llvm::SmallVector<EffectiveDirectivePath, 4> GetUniqueEffectiveDirectivePaths(
llvm::SmallVector<EffectiveDirectivePath, 4>) const;
void CollectMetadirectiveConstructSelectors(const parser::ProgramUnit &);
diff --git a/flang/lib/Semantics/check-omp-variant.cpp b/flang/lib/Semantics/check-omp-variant.cpp
index 3f4ca36fb5775..cd9a8c2a76252 100644
--- a/flang/lib/Semantics/check-omp-variant.cpp
+++ b/flang/lib/Semantics/check-omp-variant.cpp
@@ -755,6 +755,23 @@ void OmpStructureChecker::CollectMetadirectiveConstructSelectors(
parser::Walk(programUnit, collector);
}
+OmpStructureChecker::ConstructTraitSequence
+OmpStructureChecker::GetConstructTraitsForPath(
+ const EffectiveDirectivePath &path) const {
+ ConstructTraitSequence constructTraits;
+ for (auto directive{path.rbegin()}; directive != path.rend(); ++directive) {
+ // The construct trait set starts at the innermost target construct.
+ if (llvm::omp::allTargetSet.test(*directive)) {
+ constructTraits.clear();
+ }
+ llvm::omp::VariantMatchInfo directiveVMI;
+ AppendConstructTraitsForDirective(*directive, directiveVMI);
+ constructTraits.append(directiveVMI.ConstructTraits.begin(),
+ directiveVMI.ConstructTraits.end());
+ }
+ return constructTraits;
+}
+
llvm::SmallVector<OmpStructureChecker::EffectiveDirectivePath, 4>
OmpStructureChecker::GetUniqueEffectiveDirectivePaths(
llvm::SmallVector<EffectiveDirectivePath, 4> paths) const {
@@ -771,11 +788,7 @@ OmpStructureChecker::GetUniqueEffectiveDirectivePaths(
}
auto getSignature = [&](const EffectiveDirectivePath &path) {
- llvm::omp::VariantMatchInfo contextVMI;
- for (auto directive{path.rbegin()}; directive != path.rend(); ++directive) {
- AppendConstructTraitsForDirective(*directive, contextVMI);
- }
- const auto &contextTraits{contextVMI.ConstructTraits};
+ ConstructTraitSequence contextTraits{GetConstructTraitsForPath(path)};
// Matching depends on trait presence and on the positions at which each
// ordered construct-selector prefix is matched. This is also enough to
@@ -825,13 +838,7 @@ OmpStructureChecker::GetReachableMetadirectiveReplacements(
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());
+ ConstructTraitSequence constructTraits{GetConstructTraitsForPath(path)};
OmpVariantMatchContext matchContext{context_, constructTraits};
if (auto candidateSet{
BuildMetadirectiveCandidateSet(clauses, context_, matchContext)}) {
@@ -851,6 +858,7 @@ OmpStructureChecker::GetReachableMetadirectiveReplacements(
// Unsupported selectors are diagnosed elsewhere. Retain every explicit
// replacement on each path so recovery cannot miss a loop constraint.
+ bool hasFallback{false};
for (const parser::OmpClause &clause : clauses.v) {
const parser::OmpDirectiveSpecification *spec{nullptr};
if (const auto *when{std::get_if<parser::OmpClause::When>(&clause.u)}) {
@@ -860,12 +868,14 @@ OmpStructureChecker::GetReachableMetadirectiveReplacements(
}
} else if (const auto *otherwise{
std::get_if<parser::OmpClause::Otherwise>(&clause.u)}) {
+ hasFallback = true;
if (otherwise->v && otherwise->v->v) {
spec = &otherwise->v->v->value();
}
} else if (const auto *defaultVariant{
std::get_if<parser::OmpClause::DefaultVariant>(
&clause.u)}) {
+ hasFallback = true;
spec = &defaultVariant->v.v.value();
} else {
continue;
@@ -875,7 +885,7 @@ OmpStructureChecker::GetReachableMetadirectiveReplacements(
}
result.push_back({path, spec});
}
- if (result.size() == firstBranch) {
+ if (!hasFallback) {
result.push_back({path, nullptr});
}
}
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
index 6729a06fc4faa..0b6efa0617e35 100644
--- a/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability-openmp-60.f90
@@ -11,3 +11,19 @@ subroutine f01(n, a)
a(i) = i
end do
end subroutine
+
+! Unsupported-selector recovery retains an implicit NOTHING fallback.
+subroutine f02(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(construct={simd(simdlen(8))}: parallel)
+ !$omp metadirective &
+ !$omp& when(construct={parallel}: nothing) &
+ !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& otherwise(do collapse(2))
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
index 9c59d3ff1f5a7..ccddf604708ea 100644
--- a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
@@ -317,3 +317,35 @@ subroutine f23(n, a)
end do
end do
end subroutine
+
+! An actual TARGET hides enclosing constructs from a nested selector.
+subroutine f24(n, a)
+ integer :: n, a(n), i
+ !$omp parallel
+ !$omp target
+ !$omp metadirective &
+ !$omp& when(construct={parallel, target}: &
+ !$omp& simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end target
+ !$omp end parallel
+end subroutine
+
+! A selected TARGET also hides enclosing constructs from a nested selector.
+subroutine f25(n, a)
+ integer :: n, a(n), i
+ !$omp parallel
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: target) &
+ !$omp& default(nothing)
+ !$omp metadirective &
+ !$omp& when(construct={parallel, target}: &
+ !$omp& simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+ !$omp end parallel
+end subroutine
>From 6f3b7881ac36a65c2b38320e5cb3cfe8b10a57cb Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 8 Sep 2026 12:45:00 -0500
Subject: [PATCH 3/3] Resolve match_any crash during ranking
This commit resolve avoid crash with "Mismatch in the construct traits!"
inside PARALLEL when ranking competing candidates with construct={parallel} and
extension(match_any).
---
flang/lib/Semantics/check-omp-variant.cpp | 77 +++++++++++-------
.../metadirective-loop-applicability.f90 | 65 +++++++++++++++
llvm/lib/Frontend/OpenMP/OMPContext.cpp | 81 +++++++++++--------
llvm/unittests/Frontend/OpenMPContextTest.cpp | 66 ++++++++++++++-
4 files changed, 224 insertions(+), 65 deletions(-)
diff --git a/flang/lib/Semantics/check-omp-variant.cpp b/flang/lib/Semantics/check-omp-variant.cpp
index cd9a8c2a76252..17a434cc5d51f 100644
--- a/flang/lib/Semantics/check-omp-variant.cpp
+++ b/flang/lib/Semantics/check-omp-variant.cpp
@@ -1003,9 +1003,49 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
if (parser::Unwrap<parser::CompilerDirective>(x)) {
return;
}
+
+ const parser::DoConstruct *rootLoop{parser::Unwrap<parser::DoConstruct>(x)};
+ bool isStrictlyStructuredBlock{
+ parser::Unwrap<parser::BlockConstruct>(x) != nullptr};
+
+ // A standalone metadirective's replacement applies to its following
+ // associated construct. Keep block-associated replacements active across a
+ // Fortran BLOCK, just as loop-associated replacements are active across
+ // their DO construct, so nested construct selectors see the selected path.
+ if (rootLoop || isStrictlyStructuredBlock) {
+ for (const PendingLoopDirectiveGroup &group : pendingLoopDirectiveGroups_) {
+ 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())};
+ bool appliesToAssociatedConstruct{
+ (rootLoop &&
+ (association == llvm::omp::Association::LoopNest ||
+ association == llvm::omp::Association::LoopSeq)) ||
+ (isStrictlyStructuredBlock &&
+ association == llvm::omp::Association::Block)};
+ if (appliesToAssociatedConstruct) {
+ path.insert(path.begin(), spec->DirId());
+ }
+ }
+ paths.push_back(std::move(path));
+ }
+ paths = GetUniqueEffectiveDirectivePaths(std::move(paths));
+ activeMetadirectiveReplacements_.push_back(
+ {dirContext_.size(), std::move(paths)});
+ ++executionPartReplacementCounts_.back();
+ }
+ }
+
// This is the first construct after the metadirective. It consumes the
// pending variants, whether or not it is a loop nest.
- if (!parser::Unwrap<parser::DoConstruct>(x)) {
+ if (!rootLoop) {
// A non-loop construct follows, so a loop-associated variant has no loop
// nest to associate with.
CheckPendingLoopDirectivesWithoutLoop();
@@ -1016,33 +1056,9 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
// 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));
- }
- paths = GetUniqueEffectiveDirectivePaths(std::move(paths));
- activeMetadirectiveReplacements_.push_back(
- {dirContext_.size(), std::move(paths)});
- ++executionPartReplacementCounts_.back();
- }
llvm::omp::Version version{context_.langOptions().getOpenMPVersion()};
LoopSequence sequence(x, version, /*allowAllLoops=*/true, &context_);
- const parser::DoConstruct &rootLoop{*parser::Unwrap<parser::DoConstruct>(x)};
const auto &[haveSemantic, havePerfect]{sequence.depth()};
const auto MsgRequiresCanonical{
@@ -1052,13 +1068,14 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
auto checkRootLoopCanonical =
[&](const parser::OmpDirectiveSpecification &spec, bool isSequence) {
- parser::CharBlock source{*parser::GetSource(rootLoop)};
+ parser::CharBlock source{*parser::GetSource(*rootLoop)};
Reason reason;
- if (rootLoop.IsDoWhile()) {
+ if (rootLoop->IsDoWhile()) {
reason.Say(source, MsgNotValidAffectedLoop, "DO WHILE loop");
- } else if (rootLoop.IsDoConcurrent() && !IsDoConcurrentLegal(version)) {
+ } else if (rootLoop->IsDoConcurrent() &&
+ !IsDoConcurrentLegal(version)) {
reason.Say(source, MsgNotValidAffectedLoop, "DO CONCURRENT loop");
- } else if (!rootLoop.GetLoopControl()) {
+ } else if (!rootLoop->GetLoopControl()) {
reason.Say(
source, MsgNotValidAffectedLoop, "DO loop without loop control");
}
@@ -1093,7 +1110,7 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
// the parse tree, so name resolution cannot apply DEFAULT(NONE) to it.
if (group.checkDefaultNoneInAssociatedLoop) {
CheckDefaultNoneInAssociatedLoop(
- *spec, rootLoop, defaultNoneDiagnosed);
+ *spec, *rootLoop, defaultNoneDiagnosed);
}
auto [needDepth, needPerfect]{
diff --git a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90 b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
index ccddf604708ea..9e95518a90056 100644
--- a/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-loop-applicability.f90
@@ -349,3 +349,68 @@ subroutine f25(n, a)
!$omp end metadirective
!$omp end parallel
end subroutine
+
+! MATCH_ANY with construct traits records only the construct traits that match
+! before ranking the reachable variants.
+subroutine f26(n, a)
+ integer :: n, a(n), i
+ !$omp parallel
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: nothing) &
+ !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(construct={target, parallel}, implementation={extension(match_any)}: simd collapse(2)) &
+ !$omp& default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end parallel
+end subroutine
+
+! MATCH_NONE remains applicable when its construct trait is absent, and the
+! first equally ranked candidate is selected.
+subroutine f27(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !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(construct={parallel}, implementation={extension(match_none)}: simd collapse(2)) &
+ !$omp& when(implementation={vendor(llvm)}: nothing) &
+ !$omp& default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! A block-associated directive selected by a standalone metadirective remains
+! active throughout its following strictly structured BLOCK.
+subroutine f28(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel) default(nothing)
+ block
+ !$omp metadirective &
+ !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(construct={parallel}: simd collapse(2)) default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ end block
+end subroutine
+
+! Precise ranking is required for MATCH_ANY with construct traits. A
+! conservative fallback would retain the lower-ranked invalid SIMD variant and
+! diagnose it even though the higher-scored NOTHING is always selected.
+subroutine f29(n, a)
+ integer :: n, a(n), i
+ !$omp parallel
+ !$omp metadirective &
+ !$omp& when(user={condition(score(100): .true.)}: nothing) &
+ !$omp& when(construct={target, parallel}, implementation={extension(match_any)}: simd collapse(2)) &
+ !$omp& default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end parallel
+end subroutine
diff --git a/llvm/lib/Frontend/OpenMP/OMPContext.cpp b/llvm/lib/Frontend/OpenMP/OMPContext.cpp
index f2cdd9bbaf5e4..ff65c866730dc 100644
--- a/llvm/lib/Frontend/OpenMP/OMPContext.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPContext.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/Frontend/OpenMP/OMPContext.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Debug.h"
@@ -177,11 +178,10 @@ static bool isStrictSubset(const VariantMatchInfo &VMI0,
return true;
}
-static int
-isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
- const OMPContext &Ctx,
- SmallVectorImpl<unsigned> *ConstructMatches,
- bool DeviceOrImplementationSetOnly) {
+static int isVariantApplicableInContextHelper(
+ const VariantMatchInfo &VMI, const OMPContext &Ctx,
+ SmallVectorImpl<std::optional<unsigned>> *ConstructMatches,
+ bool DeviceOrImplementationSetOnly) {
// The match kind determines if we need to match all traits, any of the
// traits, or none of the traits for it to be an applicable context.
@@ -197,23 +197,24 @@ isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
unsigned(TraitProperty::implementation_extension_match_none)))
MK = MK_NONE;
- // Helper to deal with a single property that was (not) found in the OpenMP
- // context based on the match kind selected by the user via
- // `implementation={extensions(match_[all,any,none])}'
- auto HandleTrait = [MK](TraitProperty Property,
- bool WasFound) -> std::optional<bool> /* Result */ {
- // For kind "any" a single match is enough but we ignore non-matched
- // properties.
+ bool AnyTraitMatched = false;
+
+ // Handle a single property that was (not) found in the OpenMP context based
+ // on the match kind selected by the user via
+ // `implementation={extensions(match_[all,any,none])}'. Keep inspecting
+ // traits after match_any succeeds so construct match positions needed for
+ // scoring are still recorded.
+ auto HandleTrait = [MK, &AnyTraitMatched](TraitProperty Property,
+ bool WasFound) -> bool {
+ AnyTraitMatched |= WasFound;
if (MK == MK_ANY) {
- if (WasFound)
- return true;
- return std::nullopt;
+ return true;
}
// In "all" or "none" mode we accept a matching or non-matching property
// respectively and move on. We are not done yet!
if ((WasFound && MK == MK_ALL) || (!WasFound && MK == MK_NONE))
- return std::nullopt;
+ return true;
// We missed a property, provide some debug output and indicate failure.
LLVM_DEBUG({
@@ -243,6 +244,10 @@ isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
TraitSelector::implementation_extension)
continue;
+ // Construct traits require ordered matching and are handled below.
+ if (getOpenMPContextTraitSetForProperty(Property) == TraitSet::construct)
+ continue;
+
bool IsActiveTrait = Ctx.ActiveTraits.test(unsigned(Property));
// We overwrite the isa trait as it is actually up to the OMPContext hook to
@@ -256,8 +261,8 @@ isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
return Ctx.matchesISATrait(RawString);
});
- if (std::optional<bool> Result = HandleTrait(Property, IsActiveTrait))
- return *Result;
+ if (!HandleTrait(Property, IsActiveTrait))
+ return false;
}
if (!DeviceOrImplementationSetOnly) {
@@ -269,17 +274,21 @@ isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
TraitSet::construct &&
"Variant context is ill-formed!");
- // Verify the nesting.
+ // Verify the nesting. A failed match in match_any or match_none must not
+ // consume the remaining context, since a later selector property can
+ // still match.
+ unsigned SearchStart = ConstructIdx;
bool FoundInOrder = false;
while (!FoundInOrder && ConstructIdx != NoConstructTraits)
FoundInOrder = (Ctx.ConstructTraits[ConstructIdx++] == Property);
+ if (!FoundInOrder && MK != MK_ALL)
+ ConstructIdx = SearchStart;
if (ConstructMatches)
- ConstructMatches->push_back(ConstructIdx - 1);
-
- if (std::optional<bool> Result = HandleTrait(Property, FoundInOrder))
- return *Result;
+ ConstructMatches->push_back(
+ FoundInOrder ? std::optional<unsigned>{ConstructIdx - 1}
+ : std::nullopt);
- if (!FoundInOrder) {
+ if (!HandleTrait(Property, FoundInOrder)) {
LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE << "] Construct property "
<< getOpenMPContextTraitPropertyName(Property, "")
<< " was not nested properly.\n");
@@ -289,11 +298,13 @@ isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,
// TODO: Verify SIMD
}
- assert(isSubset<TraitProperty>(VMI.ConstructTraits, Ctx.ConstructTraits) &&
- "Broken invariant!");
+ if (MK == MK_ALL)
+ assert(
+ isSubset<TraitProperty>(VMI.ConstructTraits, Ctx.ConstructTraits) &&
+ "Broken invariant!");
}
- if (MK == MK_ANY) {
+ if (MK == MK_ANY && !AnyTraitMatched) {
LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE
<< "] None of the properties was in the OpenMP context "
"but match kind is any.\n");
@@ -310,9 +321,9 @@ bool llvm::omp::isVariantApplicableInContext(
VMI, Ctx, /* ConstructMatches */ nullptr, DeviceOrImplementationSetOnly);
}
-static APInt getVariantMatchScore(const VariantMatchInfo &VMI,
- const OMPContext &Ctx,
- SmallVectorImpl<unsigned> &ConstructMatches) {
+static APInt getVariantMatchScore(
+ const VariantMatchInfo &VMI, const OMPContext &Ctx,
+ SmallVectorImpl<std::optional<unsigned>> &ConstructMatches) {
APInt Score(64, 1);
unsigned NoConstructTraits = VMI.ConstructTraits.size();
@@ -377,16 +388,18 @@ static APInt getVariantMatchScore(const VariantMatchInfo &VMI,
}
}
- unsigned ConstructIdx = 0;
assert(NoConstructTraits == ConstructMatches.size() &&
"Mismatch in the construct traits!");
- for (TraitProperty Property : VMI.ConstructTraits) {
+ for (auto [Property, Match] :
+ llvm::zip_equal(VMI.ConstructTraits, ConstructMatches)) {
assert(getOpenMPContextTraitSetForProperty(Property) ==
TraitSet::construct &&
"Ill-formed variant match info!");
(void)Property;
+ if (!Match)
+ continue;
// ConstructMatches is the position p - 1 and we need 2^(p-1).
- Score += (1ULL << ConstructMatches[ConstructIdx++]);
+ Score += (1ULL << *Match);
}
LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE << "] Variant has a score of " << Score
@@ -404,7 +417,7 @@ int llvm::omp::getBestVariantMatchForContext(
for (unsigned u = 0, e = VMIs.size(); u < e; ++u) {
const VariantMatchInfo &VMI = VMIs[u];
- SmallVector<unsigned, 8> ConstructMatches;
+ SmallVector<std::optional<unsigned>, 8> ConstructMatches;
// If the variant is not applicable its not the best.
if (!isVariantApplicableInContextHelper(
VMI, Ctx, &ConstructMatches,
diff --git a/llvm/unittests/Frontend/OpenMPContextTest.cpp b/llvm/unittests/Frontend/OpenMPContextTest.cpp
index f9683ae56e933..77a29a756c3e0 100644
--- a/llvm/unittests/Frontend/OpenMPContextTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPContextTest.cpp
@@ -314,7 +314,71 @@ TEST_F(OpenMPContextTest, ApplicabilityAllTraits) {
}
TEST_F(OpenMPContextTest, ScoringSimple) {
- // TODO: Add scoring tests (via getBestVariantMatchForContext).
+ OMPContext Parallel(false, Triple("x86_64-unknown-linux"), Triple(), -1);
+ Parallel.addTrait(TraitProperty::construct_parallel_parallel);
+ OMPContext NoConstruct(false, Triple("x86_64-unknown-linux"), Triple(), -1);
+
+ VariantMatchInfo MatchAny;
+ MatchAny.addTrait(TraitProperty::construct_target_target, "");
+ MatchAny.addTrait(TraitProperty::construct_parallel_parallel, "");
+ MatchAny.addTrait(TraitProperty::implementation_extension_match_any, "");
+ EXPECT_TRUE(isVariantApplicableInContext(MatchAny, Parallel));
+ EXPECT_FALSE(isVariantApplicableInContext(MatchAny, NoConstruct));
+
+ VariantMatchInfo VendorLLVM;
+ VendorLLVM.addTrait(TraitProperty::implementation_vendor_llvm, "");
+ // The matching construct must raise the score, not just win a tie by order.
+ SmallVector<VariantMatchInfo, 2> MatchAnyCandidates{VendorLLVM, MatchAny};
+ EXPECT_EQ(getBestVariantMatchForContext(MatchAnyCandidates, Parallel), 1);
+
+ VariantMatchInfo MatchNone;
+ MatchNone.addTrait(TraitProperty::construct_parallel_parallel, "");
+ MatchNone.addTrait(TraitProperty::implementation_extension_match_none, "");
+ EXPECT_TRUE(isVariantApplicableInContext(MatchNone, NoConstruct));
+ EXPECT_FALSE(isVariantApplicableInContext(MatchNone, Parallel));
+
+ VariantMatchInfo Empty;
+ SmallVector<VariantMatchInfo, 2> MatchNoneCandidates{MatchNone, Empty};
+ EXPECT_EQ(getBestVariantMatchForContext(MatchNoneCandidates, NoConstruct), 0);
+}
+
+TEST_F(OpenMPContextTest, ScoringMatchAnyConstructs) {
+ OMPContext TargetParallel(false, Triple("x86_64-unknown-linux"), Triple(),
+ -1);
+ TargetParallel.addTrait(TraitProperty::construct_target_target);
+ TargetParallel.addTrait(TraitProperty::construct_parallel_parallel);
+
+ VariantMatchInfo Parallel;
+ Parallel.addTrait(TraitProperty::construct_parallel_parallel, "");
+
+ VariantMatchInfo MatchAny;
+ MatchAny.addTrait(TraitProperty::construct_target_target, "");
+ MatchAny.addTrait(TraitProperty::construct_parallel_parallel, "");
+ MatchAny.addTrait(TraitProperty::implementation_extension_match_any, "");
+
+ // Both construct matches contribute: 1 + 1 + 2 beats PARALLEL's 1 + 2.
+ // Stopping after the first match must not omit the later match's score.
+ SmallVector<VariantMatchInfo, 2> Candidates{Parallel, MatchAny};
+ EXPECT_EQ(getBestVariantMatchForContext(Candidates, TargetParallel), 1);
+}
+
+TEST_F(OpenMPContextTest, ScoringMatchAnyWithoutMatchingConstructs) {
+ OMPContext NoConstruct(false, Triple("x86_64-unknown-linux"), Triple(), -1);
+
+ VariantMatchInfo MatchAny;
+ MatchAny.addTrait(TraitProperty::construct_parallel_parallel, "");
+ MatchAny.addTrait(TraitProperty::implementation_vendor_llvm, "");
+ MatchAny.addTrait(TraitProperty::implementation_extension_match_any, "");
+ EXPECT_TRUE(isVariantApplicableInContext(MatchAny, NoConstruct));
+
+ APInt Score(64, 1);
+ VariantMatchInfo Scored;
+ Scored.addTrait(TraitProperty::user_condition_true, "", &Score);
+
+ // The vendor match makes MATCH_ANY applicable, but the absent construct
+ // must not add to its score. The scored candidate wins by 2 to 1.
+ SmallVector<VariantMatchInfo, 2> Candidates{MatchAny, Scored};
+ EXPECT_EQ(getBestVariantMatchForContext(Candidates, NoConstruct), 1);
}
} // namespace
More information about the flang-commits
mailing list