[flang-commits] [flang] [flang][OpenMP] Lower DO and SIMD variants in metadirectives (PR #210810)
via flang-commits
flang-commits at lists.llvm.org
Wed Aug 12 11:53:04 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/210810
>From 6f99daeaf92feab7f0c82819bfd7d4d753613b6b Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 3 Aug 2026 16:34:55 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Lower DO and SIMD variants in
metadirectives
A standalone metadirective and its associated DO are sibling PFT evaluations,
so a selected loop replacement cannot directly reuse ordinary OpenMP loop
lowering. Runtime selection must also preserve exactly one copy of the loop in
each reachable branch. Temporarily associate the evaluations while lowering
to support DO, SIMD, and DO SIMD replacements without losing or duplicating the
ordinary fallback loop.
For example:
```fortran
!$omp metadirective &
!$omp& when(user={condition(flag)}: do) &
!$omp& otherwise(nothing)
do i = 1, n
a(i) = i
end do
```
is represented as sibling evaluations:
```text
METADIRECTIVE
DO
`-- a(i) = i
```
and runtime selection lowers conceptually as:
```text
if (flag)
omp.wsloop
omp.loop_nest
else
fir.do_loop
```
Build the statically applicable candidate set before lowering and use shared
selection utilities to preserve OpenMP ranking. Keep runtime guards separate
from static applicability, retain candidates reachable through a failed
condition, and apply loop-specific restrictions only after the reachable
replacements are known.
This change:
- Lowers DO, SIMD, and DO SIMD replacements through the existing loop-lowering
path for standalone and begin/end metadirectives.
- Supports static selection and runtime user conditions while preserving a
Fortran loop for NOTHING and other non-consuming replacements.
- Preserves compiler loop directives, statements following the associated
loop, and evaluation ownership across alternate ENTRY lowering.
- Applies COLLAPSE and ORDERED to the required depth without leaking temporary
loop-index attributes between replacement arms.
- Emits focused diagnostics when a reachable replacement requires unsupported
data-environment, host-association, capture, or eager-privatization handling.
Initial loop support remains limited to DO, SIMD, and DO SIMD. Diagnose
combined data-environment loops such as PARALLEL DO, explicit data-sharing and
DEFAULT clauses, loops nested in an existing OpenMP data environment, TARGET
and mixed block/loop replacements, non-canonical or unstructured loops,
insufficient COLLAPSE or ORDERED depth, and unsupported loop-index entities.
Add lowering coverage for candidate ranking, runtime fallback paths, DO and
SIMD composition, affected-loop depth, compiler directives, ENTRY handling,
and the unsupported boundaries above.
Assisted with codex.
---
flang/include/flang/Lower/PFTBuilder.h | 3 +
flang/include/flang/Semantics/openmp-utils.h | 57 ++
flang/lib/Lower/Bridge.cpp | 5 +
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 79 +-
flang/lib/Lower/OpenMP/OpenMP.cpp | 785 +++++++++++++-----
flang/lib/Lower/OpenMP/Utils.cpp | 19 +-
flang/lib/Lower/OpenMP/Utils.h | 4 +
flang/lib/Semantics/openmp-utils.cpp | 326 ++++++++
.../metadirective-block-data-environment.f90 | 132 +++
...adirective-block-eager-default-private.f90 | 17 +
...etadirective-block-eager-privatization.f90 | 17 +
...irective-block-host-association-clause.f90 | 37 +
.../Todo/metadirective-loop-associate-iv.f90 | 36 +
.../Todo/metadirective-loop-body-variant.f90 | 18 +
.../metadirective-loop-data-environment.f90 | 16 +
...metadirective-loop-data-sharing-clause.f90 | 17 +
...metadirective-loop-eager-privatization.f90 | 18 +
...ective-loop-enclosing-data-environment.f90 | 60 ++
...adirective-loop-pointer-allocatable-iv.f90 | 35 +
.../Todo/metadirective-loop-unstructured.f90 | 74 ++
...tadirective-loop-unsupported-directive.f90 | 15 +
.../Lower/OpenMP/Todo/metadirective-loop.f90 | 12 -
.../OpenMP/Todo/metadirective-target-loop.f90 | 15 +
.../metadirective-block-eager-shared.f90 | 19 +
.../OpenMP/metadirective-device-arch.f90 | 2 +-
.../OpenMP/metadirective-implementation.f90 | 97 ++-
.../test/Lower/OpenMP/metadirective-loop.f90 | 673 +++++++++++++++
.../test/Lower/OpenMP/metadirective-user.f90 | 40 +-
28 files changed, 2393 insertions(+), 235 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-unstructured.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
create mode 100644 flang/test/Lower/OpenMP/metadirective-block-eager-shared.f90
create mode 100644 flang/test/Lower/OpenMP/metadirective-loop.f90
diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index 490b2a46c18bf..015c7c68aa35f 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -377,6 +377,9 @@ struct Evaluation : EvaluationVariant {
bool isUnstructured{false}; // evaluation has unstructured control flow
bool negateCondition{false}; // If[Then]Stmt condition must be negated
bool activeConstruct{false}; // temporarily set for some constructs
+ // The enclosing evaluation-list traversal should skip this evaluation once
+ // because directive lowering already consumed it.
+ bool skipNextLowering{false};
mlir::Block *block{nullptr}; // isNewBlock block (ActionStmt, ConstructStmt)
int printIndex{0}; // (ActionStmt, ConstructStmt) evaluation index for dumps
};
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 031926d4a7cc2..be6cff0bb4f4e 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -257,6 +257,63 @@ class OmpVariantMatchContext : public llvm::omp::OMPContext {
std::string features_;
};
+struct MetadirectiveCandidate {
+ MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
+ llvm::omp::VariantMatchInfo vmi, bool isExplicit,
+ std::optional<DynamicUserCondition> dynamicCondition = std::nullopt,
+ bool conditionShouldBeTrue = true)
+ : spec{spec}, vmi{std::move(vmi)}, isExplicit{isExplicit},
+ dynamicCondition{dynamicCondition},
+ conditionShouldBeTrue{conditionShouldBeTrue} {}
+
+ const parser::OmpDirectiveSpecification *spec{nullptr};
+ llvm::omp::VariantMatchInfo vmi;
+ bool isExplicit{false};
+ std::optional<DynamicUserCondition> dynamicCondition;
+ bool conditionShouldBeTrue{true};
+};
+
+struct MetadirectiveCandidateSet {
+ llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
+ /// Null represents either an explicit NOTHING fallback or no fallback.
+ const parser::OmpDirectiveSpecification *fallback{nullptr};
+};
+
+/// Build the statically applicable candidates for a METADIRECTIVE.
+///
+/// Returns std::nullopt when a selector is malformed or uses a feature that
+/// variant matching cannot yet model.
+std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
+ const parser::OmpClauseList &clauses, SemanticsContext &context,
+ const OmpVariantMatchContext &matchContext);
+
+std::optional<unsigned> SelectBestMetadirectiveCandidate(
+ llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<MetadirectiveCandidate> candidates,
+ const OmpVariantMatchContext &matchContext);
+
+/// Return true when repeated evaluation of \p condition cannot call a
+/// procedure or observe asynchronously changing state.
+bool IsRepeatableMetadirectiveCondition(
+ const parser::ScalarExpr &condition, SemanticsContext &context);
+
+/// Return true when two repeatable conditions normalize to the same expression.
+bool AreSameRepeatableMetadirectiveCondition(const parser::ScalarExpr &left,
+ const parser::ScalarExpr &right, SemanticsContext &context);
+
+/// Return candidates reachable after \p selectedIndex fails. Equal repeatable
+/// guards are pruned until a non-repeatable guard is encountered.
+llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
+ unsigned selectedIndex, llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<MetadirectiveCandidate> candidates,
+ const OmpVariantMatchContext &matchContext, SemanticsContext &context);
+
+/// Return every replacement that can be selected, retaining lower-ranked
+/// candidates after a dynamic condition. Null represents NOTHING.
+llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
+GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
+ const OmpVariantMatchContext &matchContext, SemanticsContext &context);
+
/// True if a variant guarded by \p selector may be selected in the current
/// compilation context.
///
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index a8e3e4a0aea1a..43d7f384ae90b 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6017,6 +6017,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
? eval.getFirstNestedEvaluation().block
: eval.block);
+ if (eval.skipNextLowering) {
+ eval.skipNextLowering = false;
+ return;
+ }
+
// Add scope for constructs inside acc.loop to properly contain symbol
// bindings (e.g., from cache directive) within the construct.
bool needsAccScope =
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 6b57934a087f6..823d63a20593e 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -37,19 +37,29 @@ namespace lower {
namespace omp {
bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
const semantics::Symbol *symbol, lower::pft::Evaluation &eval) const {
+ auto definition = symDefMap.find(symbol);
+ if (definition == symDefMap.end())
+ return false;
+
+ // A metadirective does not introduce privatization itself, so symbols from
+ // its spliced evaluations are associated with a null construct.
+ if (isMetadirectiveEval(eval))
+ return definition->second ==
+ ConstructPtr(static_cast<const parser::OpenMPConstruct *>(nullptr));
+
return eval.visit(common::visitors{
- [&](const parser::OpenMPConstruct &functionParserNode) {
- return symDefMap.count(symbol) &&
- symDefMap.at(symbol) == ConstructPtr(&functionParserNode);
+ [&](const parser::OpenMPConstruct &ompConstruct) {
+ return definition->second == ConstructPtr(&ompConstruct);
},
- [](const auto &functionParserNode) { return false; }});
+ [](const auto &) { return false; }});
}
bool DataSharingProcessor::OMPConstructSymbolVisitor::
isSymbolDefineByNestedDeclaration(const semantics::Symbol *symbol) const {
- return symDefMap.count(symbol) &&
+ auto definition = symDefMap.find(symbol);
+ return definition != symDefMap.end() &&
std::holds_alternative<const parser::DeclarationConstruct *>(
- symDefMap.at(symbol));
+ definition->second);
}
static bool isConstructWithTopLevelTarget(lower::pft::Evaluation &eval) {
@@ -79,6 +89,14 @@ DataSharingProcessor::DataSharingProcessor(
eval.visit([&](const auto &functionParserNode) {
parser::Walk(functionParserNode, visitor);
});
+ // For metadirective evaluations, the associated DO loop is spliced into the
+ // evaluation tree but is not part of the metadirective's parse tree. Walk
+ // nested evaluations' parse trees so the visitor can track their symbols
+ // (e.g. loop iteration variables).
+ if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+ for (auto &nestedEval : eval.getNestedEvaluations())
+ nestedEval.visit([&](const auto &node) { parser::Walk(node, visitor); });
+ }
}
DataSharingProcessor::DataSharingProcessor(
@@ -223,9 +241,31 @@ void DataSharingProcessor::copyLastPrivateSymbol(
const semantics::Symbol *sym, mlir::OpBuilder::InsertPoint *lastPrivIP) {
// Conditional-lastprivate symbols use their own guarded copy-back (from the
// reduction accumulator), not the standard "last iteration wins" copy-back.
- if (sym->test(semantics::Symbol::Flag::OmpLastPrivate) &&
- !conditionalLastPrivatizedSymbols.contains(sym))
+ if (!sym->test(semantics::Symbol::Flag::OmpLastPrivate) ||
+ conditionalLastPrivatizedSymbols.contains(sym))
+ return;
+
+ if (sym->has<semantics::HostAssocDetails>()) {
converter.copyHostAssociateVar(*sym, lastPrivIP, /*hostIsSource=*/false);
+ return;
+ }
+
+ assert(isMetadirectiveEval(eval) &&
+ "unexpected lastprivate symbol without host association");
+
+ // Metadirective loop IVs can be marked lastprivate during lowering, after
+ // semantic host-association symbols would normally be created. Copy from the
+ // private binding back to the one-level-up binding directly.
+ mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
+ if (lastPrivIP)
+ firOpBuilder.restoreInsertionPoint(*lastPrivIP);
+ lower::SymbolBox hostBox = converter.lookupOneLevelUpSymbol(*sym);
+ lower::SymbolBox privBox = converter.shallowLookupSymbol(*sym);
+ assert(hostBox && privBox &&
+ "expected symbol bindings for lastprivate loop IV");
+ if (hostBox.getAddr() != privBox.getAddr())
+ converter.copyVar(converter.getCurrentLocation(), hostBox.getAddr(),
+ privBox.getAddr(), fir::FortranVariableFlagsEnum::None);
}
void DataSharingProcessor::collectOmpObjectListSymbol(
@@ -596,7 +636,20 @@ void DataSharingProcessor::collectPrivatizedSymbols(
};
llvm::SetVector<const semantics::Scope *> clauseScopes;
- (void)collectScopes(semaCtx, eval, clauseScopes);
+ const semantics::Scope *curScope = collectScopes(semaCtx, eval, clauseScopes);
+
+ // For metadirective evaluations, the source range only covers the directive
+ // clauses, not the spliced DO loop. The scope found from that narrow range
+ // may not include parent scopes where the loop IV is declared (e.g. the
+ // function scope when the metadirective is inside a target region). Walk up
+ // the scope chain to include all ancestor scopes.
+ if (isMetadirectiveEval(eval) && curScope) {
+ const semantics::Scope *scope = curScope;
+ while (scope->kind() != semantics::Scope::Kind::Global) {
+ clauseScopes.insert(scope);
+ scope = &scope->parent();
+ }
+ }
for (const auto *sym : allSymbols) {
if (semantics::omp::IsPrivatizable(*sym) &&
@@ -627,6 +680,14 @@ void DataSharingProcessor::collectSymbols(
/*collectSymbols=*/true,
/*collectHostAssociatedSymbols=*/true);
+ // Collect symbols from spliced nested evaluations for metadirectives.
+ if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+ for (auto &nestedEval : eval.getNestedEvaluations())
+ converter.collectSymbolSet(nestedEval, allSymbols, flag,
+ /*collectSymbols=*/true,
+ /*collectHostAssociatedSymbols=*/true);
+ }
+
llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index d36aa97b00cad..39575cd846a85 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -45,6 +45,7 @@
#include "flang/Parser/tools.h"
#include "flang/Semantics/expression.h"
#include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/openmp-dsa.h"
#include "flang/Semantics/openmp-utils.h"
#include "flang/Semantics/tools.h"
#include "flang/Support/Flags.h"
@@ -60,6 +61,7 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSwitch.h"
@@ -7000,23 +7002,405 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
}
namespace {
-struct MetadirectiveCandidate {
- MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
- llvm::omp::VariantMatchInfo vmi, bool isExplicit,
- std::optional<semantics::omp::DynamicUserCondition>
- dynamicCond = std::nullopt,
- bool conditionShouldBeTrue = true)
- : spec(spec), vmi(vmi), isExplicit(isExplicit), dynamicCond(dynamicCond),
- conditionShouldBeTrue(conditionShouldBeTrue) {}
-
- const parser::OmpDirectiveSpecification *spec = nullptr;
- llvm::omp::VariantMatchInfo vmi;
- bool isExplicit = false;
- std::optional<semantics::omp::DynamicUserCondition> dynamicCond;
- bool conditionShouldBeTrue = true;
+struct SplicedAssociatedEvaluations {
+ using Iterator = lower::pft::EvaluationList::iterator;
+
+ void record(lower::pft::EvaluationList &parent, Iterator evaluation) {
+ assert((!parentList || parentList == &parent) &&
+ "associated evaluations have different parents");
+ parentList = &parent;
+ evaluations.emplace_back(evaluation, std::next(evaluation));
+ }
+
+ void restore(lower::pft::EvaluationList &nested) {
+ if (evaluations.empty())
+ return;
+ assert(parentList && "missing parent evaluation list");
+ // A saved successor may also have been spliced. Restore in reverse order
+ // so every insertion point is back in the parent list before it is used.
+ for (auto &entry : llvm::reverse(evaluations)) {
+ entry.first->skipNextLowering = true;
+ parentList->splice(entry.second, nested, entry.first);
+ }
+ if (entryEvaluation) {
+ entryEvaluation->isNewBlock = true;
+ entryEvaluation->block = entryBlock;
+ }
+ }
+
+ void suppressEntryBlock(lower::pft::Evaluation &evaluation) {
+ assert(!entryEvaluation && evaluation.isNewBlock && evaluation.block &&
+ "invalid associated entry evaluation");
+ // Do not let either cloned loop arm enter a function-region block. The
+ // metadirective selection will be placed in this block for an active ENTRY.
+ entryEvaluation = &evaluation;
+ entryBlock = evaluation.block;
+ evaluation.isNewBlock = false;
+ evaluation.block = nullptr;
+ }
+
+ mlir::Block *getEntryBlock() const { return entryBlock; }
+
+private:
+ lower::pft::EvaluationList *parentList = nullptr;
+ llvm::SmallVector<std::pair<Iterator, Iterator>, 4> evaluations;
+ lower::pft::Evaluation *entryEvaluation = nullptr;
+ mlir::Block *entryBlock = nullptr;
};
} // namespace
+/// A loop-associated metadirective is lowered like a real loop construct, but
+/// the PFT leaves its associated loop nest as the following sibling instead of
+/// nesting it underneath. Splice that sibling into the metadirective's own
+/// nested evaluations so the shared loop-lowering path can find it. Return
+/// nullptr if no associated DO loop follows.
+static bool
+isIgnorableMetadirectiveLoopAssociationEval(lower::pft::Evaluation &eval) {
+ return eval.isEndStmt() || eval.getIf<parser::CompilerDirective>();
+}
+
+static lower::pft::Evaluation *spliceAssociatedDoEval(
+ lower::pft::Evaluation &eval,
+ SplicedAssociatedEvaluations *splicedEvaluations = nullptr) {
+ if (eval.hasNestedEvaluations()) {
+ auto nestedIt =
+ llvm::find_if(eval.getNestedEvaluations(), [](auto &nested) {
+ return !isIgnorableMetadirectiveLoopAssociationEval(nested);
+ });
+ if (nestedIt != eval.getNestedEvaluations().end())
+ return nestedIt->getIf<parser::DoConstruct>() ? &*nestedIt : nullptr;
+ return nullptr;
+ }
+
+ // A metadirective in a specification part (e.g. at module scope) has no
+ // parent construct and no owning procedure, so there is no sibling list.
+ lower::pft::FunctionLikeUnit *owningProc = eval.getOwningProcedure();
+ if (!eval.parentConstruct && !owningProc)
+ return nullptr;
+ auto *parentList = eval.parentConstruct
+ ? eval.parentConstruct->evaluationList.get()
+ : &owningProc->evaluationList;
+ auto metaIt = llvm::find_if(
+ *parentList, [&](lower::pft::Evaluation &e) { return &e == &eval; });
+ assert(metaIt != parentList->end() &&
+ "metadirective eval not found in parent list");
+
+ auto firstAssociatedIt = std::next(metaIt);
+ auto loopIt = firstAssociatedIt;
+ while (loopIt != parentList->end() &&
+ isIgnorableMetadirectiveLoopAssociationEval(*loopIt))
+ ++loopIt;
+
+ if (loopIt == parentList->end() || !loopIt->getIf<parser::DoConstruct>())
+ return nullptr;
+
+ if (splicedEvaluations) {
+ auto entryIt =
+ llvm::find_if(llvm::make_range(firstAssociatedIt, loopIt),
+ [](lower::pft::Evaluation &candidate) {
+ return candidate.isNewBlock && candidate.block;
+ });
+ if (entryIt != loopIt) {
+ splicedEvaluations->suppressEntryBlock(*entryIt);
+ } else {
+ lower::pft::Evaluation &doStmt = loopIt->getFirstNestedEvaluation();
+ if (doStmt.isNewBlock && doStmt.block)
+ splicedEvaluations->suppressEntryBlock(doStmt);
+ }
+ }
+
+ // Compiler directives between the metadirective and its associated loop
+ // must be processed before the loop is lowered. Move them with the loop so
+ // they are not visited later as siblings of the metadirective.
+ for (auto it = firstAssociatedIt; it != loopIt;) {
+ auto current = it++;
+ if (current->getIf<parser::CompilerDirective>()) {
+ if (splicedEvaluations)
+ splicedEvaluations->record(*parentList, current);
+ eval.evaluationList->splice(eval.evaluationList->end(), *parentList,
+ current);
+ }
+ }
+ if (splicedEvaluations)
+ splicedEvaluations->record(*parentList, loopIt);
+ eval.evaluationList->splice(eval.evaluationList->end(), *parentList, loopIt);
+ return &eval.getNestedEvaluations().back();
+}
+
+static bool hasDirectiveAssociation(llvm::omp::Directive directive,
+ llvm::omp::Association association) {
+ return llvm::any_of(llvm::omp::getLeafConstructsOrSelf(directive),
+ [association](llvm::omp::Directive leaf) {
+ return llvm::omp::getDirectiveAssociation(leaf) ==
+ association;
+ });
+}
+
+static bool hasDirectiveAssociation(const ConstructQueue &queue,
+ llvm::omp::Association association) {
+ return llvm::any_of(queue, [association](const auto &item) {
+ return llvm::omp::getDirectiveAssociation(item.id) == association;
+ });
+}
+
+static bool isSupportedMetadirectiveLoopQueue(const ConstructQueue &queue) {
+ using llvm::omp::Directive;
+ using lower::omp::matchLeafSequence;
+ return matchLeafSequence(queue.begin(), queue, Directive::OMPD_do) ||
+ matchLeafSequence(queue.begin(), queue, Directive::OMPD_simd) ||
+ matchLeafSequence(queue.begin(), queue, Directive::OMPD_do_simd);
+}
+
+static bool isNestedInOpenMPDataEnvironment(lower::pft::Evaluation &eval,
+ mlir::Operation *currentOp) {
+ for (lower::pft::Evaluation *parent = eval.parentConstruct; parent;
+ parent = parent->parentConstruct) {
+ if (const auto *omp = parent->getIf<parser::OpenMPConstruct>()) {
+ llvm::omp::Directive directive = parser::omp::GetOmpDirectiveName(*omp).v;
+ if (semantics::omp::HasDataEnvironment(directive))
+ return true;
+ }
+ }
+
+ // A PFT ancestor can itself be a metadirective, so its source directive does
+ // not reveal the data environment selected during lowering. Check the
+ // already-emitted OpenMP operation ancestry as well.
+ for (mlir::Operation *op = currentOp; op; op = op->getParentOp()) {
+ if (mlir::isa<mlir::omp::DistributeOp, mlir::omp::LoopNestOp,
+ mlir::omp::ParallelOp, mlir::omp::ScopeOp,
+ mlir::omp::SectionsOp, mlir::omp::SimdOp, mlir::omp::SingleOp,
+ mlir::omp::TargetDataOp, mlir::omp::TargetOp,
+ mlir::omp::TaskgroupOp, mlir::omp::TaskloopContextOp,
+ mlir::omp::TaskOp, mlir::omp::TeamsOp, mlir::omp::WsloopOp>(
+ op))
+ return true;
+ }
+ return false;
+}
+
+static bool
+hasUnsupportedDataEnvironmentDirective(const ConstructQueue &queue) {
+ return llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::allParallelSet.test(item.id) ||
+ llvm::omp::taskGeneratingSet.test(item.id) ||
+ llvm::omp::allTeamsSet.test(item.id);
+ });
+}
+
+static bool
+hasMetadirectiveStaticStorageDuration(const semantics::Symbol &sym) {
+ const semantics::Symbol &ultimate{sym.GetUltimate()};
+ return semantics::IsSaved(ultimate) ||
+ ultimate.test(semantics::Symbol::Flag::InCommonBlock);
+}
+
+static bool isMetadirectiveRegionLocal(const semantics::Symbol &sym,
+ const semantics::Scope &scope) {
+ const semantics::Symbol &ultimate{sym.GetUltimate()};
+ return ultimate.owner() != scope && scope.Contains(ultimate.owner()) &&
+ !hasMetadirectiveStaticStorageDuration(ultimate);
+}
+
+static bool isSharedBySelectedMetadirective(const semantics::Symbol &sym,
+ const ConstructQueue &queue) {
+ const semantics::Symbol &ultimate{sym.GetUltimate()};
+ for (const auto &item : queue) {
+ for (const Clause &ompClause : item.clauses) {
+ if (const auto *defaultClause =
+ std::get_if<clause::Default>(&ompClause.u)) {
+ if (defaultClause->v == clause::Default::DataSharingAttribute::Shared)
+ return true;
+ } else if (const auto *sharedClause =
+ std::get_if<clause::Shared>(&ompClause.u)) {
+ if (llvm::any_of(sharedClause->v, [&](const Object &object) {
+ return object.sym() && &object.sym()->GetUltimate() == &ultimate;
+ }))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool
+hasSequentialLoopIVRequiringMetadirectiveDSA(lower::pft::Evaluation &eval,
+ unsigned version) {
+ for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) {
+ if (const auto *ompConstruct = nested.getIf<parser::OpenMPConstruct>()) {
+ llvm::omp::Directive dir{
+ parser::omp::GetOmpDirectiveName(*ompConstruct).v};
+ if (llvm::omp::allParallelSet.test(dir) ||
+ llvm::omp::taskGeneratingSet.test(dir) ||
+ llvm::omp::allTargetSet.test(dir) ||
+ (version >= 52 && llvm::omp::allTeamsSet.test(dir)))
+ continue;
+ }
+ if (nested.getIf<parser::DoConstruct>() &&
+ getIterationVariableSymbol(nested))
+ return true;
+ if (nested.hasNestedEvaluations() &&
+ hasSequentialLoopIVRequiringMetadirectiveDSA(nested, version))
+ return true;
+ }
+ return false;
+}
+
+static bool hasUnsupportedBlockDataEnvironment(const ConstructQueue &queue,
+ lower::pft::Evaluation &eval,
+ const semantics::Scope &scope,
+ unsigned version) {
+ if (hasUnsupportedDataEnvironmentDirective(queue) &&
+ hasSequentialLoopIVRequiringMetadirectiveDSA(eval, version))
+ return true;
+
+ bool hasTask{llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::taskGeneratingSet.test(item.id);
+ })};
+ if (!hasTask)
+ return false;
+
+ bool hasUnsupportedCapture{false};
+ for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) {
+ lower::pft::visitAllSymbols(nested, [&](const semantics::Symbol &symbol) {
+ if (hasUnsupportedCapture || !semantics::omp::IsPrivatizable(symbol) ||
+ isMetadirectiveRegionLocal(symbol, scope))
+ return;
+ // DSA flags are symbol-wide and may come from an unselected variant or a
+ // nested construct. Trust only sharing stated by the selected queue.
+ if (!hasMetadirectiveStaticStorageDuration(symbol) &&
+ !isSharedBySelectedMetadirective(symbol, queue))
+ hasUnsupportedCapture = true;
+ });
+ }
+ return hasUnsupportedCapture;
+}
+
+static bool hasUnsupportedDataSharingClause(const ConstructQueue &queue,
+ unsigned version) {
+ return llvm::any_of(queue, [version](const auto &item) {
+ return llvm::any_of(item.clauses, [version](const Clause &ompClause) {
+ return std::holds_alternative<clause::Default>(ompClause.u) ||
+ llvm::omp::isDataSharingAttributeClause(ompClause.id, version);
+ });
+ });
+}
+
+static bool
+hasUnsupportedMetadirectiveHostAssociationClause(const ConstructQueue &queue,
+ unsigned version) {
+ return llvm::any_of(queue, [version](const auto &item) {
+ return llvm::any_of(item.clauses, [version](const Clause &ompClause) {
+ if (const auto *defaultClause =
+ std::get_if<clause::Default>(&ompClause.u)) {
+ using DataSharingAttribute = clause::Default::DataSharingAttribute;
+ return defaultClause->v == DataSharingAttribute::Private ||
+ defaultClause->v == DataSharingAttribute::Firstprivate;
+ }
+ return llvm::omp::isPrivatizingClause(ompClause.id, version) ||
+ ompClause.id == llvm::omp::Clause::OMPC_copyin ||
+ ompClause.id == llvm::omp::Clause::OMPC_copyprivate;
+ });
+ });
+}
+
+class SymbolDSAGuard {
+public:
+ ~SymbolDSAGuard() {
+ for (auto &[sym, flags] : llvm::reverse(savedFlags))
+ sym->flags() = flags;
+ }
+
+ void setSymbolDSA(semantics::Symbol &sym, semantics::Symbol::Flag dsa) {
+ if (!llvm::any_of(savedFlags,
+ [&](const auto &entry) { return entry.first == &sym; }))
+ savedFlags.emplace_back(&sym, sym.flags());
+ using Symbol = semantics::Symbol;
+ semantics::SetSymbolDSA(sym,
+ Symbol::Flags{Symbol::Flag::OmpPreDetermined, dsa});
+ }
+
+private:
+ llvm::SmallVector<std::pair<semantics::Symbol *, semantics::Symbol::Flags>, 4>
+ savedFlags;
+};
+
+enum class MetadirectiveLoopIVMarking {
+ Marked, // Induction variables marked (or there was nothing to do).
+ NestTooShallow, // Fewer DO loops than the variant's COLLAPSE/ORDERED needs.
+ NonCanonicalLoop, // An affected loop is a DO WHILE or has no loop control.
+ IndirectIV, // An affected induction variable is POINTER or ALLOCATABLE.
+ AssociateIV, // An affected induction variable is an ASSOCIATE name.
+};
+
+/// Mark loop induction variable data-sharing attributes for a
+/// metadirective-selected loop variant. Semantic analysis cannot mark these
+/// because the variant is resolved at lowering time. Return a non-`Marked`
+/// result, leaving the diagnostic to the caller, when the associated loop nest
+/// is shallower than the variant's COLLAPSE/ORDERED requires or an affected
+/// loop is not a canonical DO loop or an affected induction variable requires
+/// construct-scoped name resolution that metadirective lowering cannot yet
+/// reproduce.
+static MetadirectiveLoopIVMarking
+markMetadirectiveLoopIVs(semantics::SemanticsContext &semaCtx,
+ const parser::OmpDirectiveSpecification &spec,
+ lower::pft::Evaluation &loopEval,
+ SymbolDSAGuard &dsaGuard) {
+ using Symbol = semantics::Symbol;
+
+ auto [depth, _] = semantics::omp::GetAffectedNestDepthWithReason(
+ spec, semaCtx.langOptions().OpenMPVersion, &semaCtx);
+ if (!depth || !depth.value || *depth.value <= 0)
+ return MetadirectiveLoopIVMarking::Marked;
+
+ int64_t affectedDepth = *depth.value;
+ bool isSimdVariant = llvm::omp::allSimdSet.test(spec.DirId());
+ Symbol::Flag ivDSA;
+ if (!isSimdVariant)
+ ivDSA = Symbol::Flag::OmpPrivate;
+ else if (affectedDepth == 1 && semaCtx.langOptions().OpenMPVersion < 60)
+ ivDSA = Symbol::Flag::OmpLinear;
+ else
+ ivDSA = Symbol::Flag::OmpLastPrivate;
+
+ lower::pft::Evaluation *doEval = &loopEval;
+ for (int64_t level = 0; level < affectedDepth; ++level) {
+ // A nest shallower than COLLAPSE/ORDERED requires is diagnosed during
+ // semantic analysis in check-omp-variant. Guard against it here too so the
+ // caller handles it instead of descending into a missing loop.
+ const parser::DoConstruct *doConstruct =
+ doEval ? doEval->getIf<parser::DoConstruct>() : nullptr;
+ if (!doConstruct)
+ return MetadirectiveLoopIVMarking::NestTooShallow;
+ // The affected loop must be a canonical DO loop (or a DO CONCURRENT, which
+ // lowering rejects further down). A DO WHILE or a loop without loop control
+ // is rejected earlier by the merged metadirective loop-nest semantic checks
+ // (check-omp-variant.cpp), so it should not reach lowering. This guard is
+ // defense-in-depth: bail out for the caller to emit a TODO rather than
+ // crash if that invariant is ever violated.
+ if (!doConstruct->IsDoNormal() && !doConstruct->IsDoConcurrent())
+ return MetadirectiveLoopIVMarking::NonCanonicalLoop;
+ if (semantics::Symbol *sym = getIterationVariableSymbol(*doEval)) {
+ // Ordinary OpenMP name resolution creates a construct-scoped symbol for
+ // an ASSOCIATE-name induction variable. Marking the associate name after
+ // name resolution cannot create the private or lastprivate binding that
+ // loop lowering requires.
+ if (sym->GetUltimate().has<semantics::AssocEntityDetails>())
+ return MetadirectiveLoopIVMarking::AssociateIV;
+ // Ordinary OpenMP semantic resolution creates a construct-scoped symbol
+ // for a POINTER or ALLOCATABLE induction variable. A metadirective
+ // variant is selected too late for that name-resolution step, and marking
+ // the descriptor-backed source symbol cannot recreate it.
+ if (semantics::IsAllocatableOrObjectPointer(sym))
+ return MetadirectiveLoopIVMarking::IndirectIV;
+ dsaGuard.setSymbolDSA(*sym, ivDSA);
+ }
+ if (level + 1 < affectedDepth)
+ doEval = tryGetNestedDoConstruct(*doEval);
+ }
+
+ return MetadirectiveLoopIVMarking::Marked;
+}
+
static void genMetadirective(lower::AbstractConverter &converter,
lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -7030,49 +7414,16 @@ static void genMetadirective(lower::AbstractConverter &converter,
semantics::omp::OmpVariantMatchContext ompCtx =
makeVariantMatchContext(builder.getModule(), constructTraits);
- llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
- // A null directive specification represents either the implicit `nothing`
- // variant or the absence of an explicit otherwise/default clause.
- const parser::OmpDirectiveSpecification *fallback = nullptr;
-
- // Extract the context-selector that controls whether a WHEN variant is
- // applicable. Modifier validation requires exactly one selector per clause.
- auto getContextSelector = [](const parser::OmpClause::When &whenClause)
- -> const parser::modifier::OmpContextSelector & {
- const auto &modifiers = std::get<0>(whenClause.v.t);
- assert(modifiers && modifiers->size() == 1 &&
- "WHEN clause should contain one context-selector");
- return std::get<parser::modifier::OmpContextSelector>(modifiers->front().u);
- };
-
- // Extract the directive variant spec from a when clause.
- // Returns {spec_ptr, isExplicit}. A null spec means "nothing".
- auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
- -> std::pair<const parser::OmpDirectiveSpecification *, bool> {
- const auto &opt = std::get<1>(whenClause.v.t);
- if (!opt)
- return {nullptr, false};
- if (opt->value().DirId() == llvm::omp::Directive::OMPD_nothing)
- return {nullptr, true};
- return {&opt->value(), true};
- };
-
- // Return the directive spec pointer, or nullptr for "nothing".
- auto getFallbackVariant = [](const parser::OmpDirectiveSpecification &spec)
- -> const parser::OmpDirectiveSpecification * {
- if (spec.DirId() == llvm::omp::Directive::OMPD_nothing)
- return nullptr;
- return &spec;
- };
-
+ // Lowering does not yet support every selector feature accepted by
+ // semantics. Diagnose those before building the shared selection plan.
for (const auto &clause : clauseList.v) {
if (const auto *whenClause =
std::get_if<parser::OmpClause::When>(&clause.u)) {
- const auto &ctxSel = getContextSelector(*whenClause);
- auto [spec, isExplicit] = getDirectiveVariant(*whenClause);
-
- // METADIRECTIVE cannot yet honour some selector features that are
- // otherwise accepted; reject them before building the match info.
+ const auto &modifiers = std::get<0>(whenClause->v.t);
+ assert(modifiers && modifiers->size() == 1 &&
+ "WHEN clause should contain one context-selector");
+ const auto &ctxSel =
+ std::get<parser::modifier::OmpContextSelector>(modifiers->front().u);
switch (semantics::omp::FindUnsupportedSelectorFeature(ctxSel, semaCtx)) {
case semantics::omp::UnsupportedSelectorFeature::TargetDevice:
TODO(converter.genLocation(clause.source),
@@ -7086,125 +7437,93 @@ static void genMetadirective(lower::AbstractConverter &converter,
case semantics::omp::UnsupportedSelectorFeature::None:
break;
}
+ }
+ }
- llvm::omp::VariantMatchInfo rawVMI;
- std::optional<semantics::omp::DynamicUserCondition> dynamicCond =
- semantics::omp::MakeVariantMatchInfo(rawVMI, ctxSel, semaCtx);
-
- if (dynamicCond) {
- constexpr llvm::omp::TraitProperty dynamicConditionTrait =
- llvm::omp::TraitProperty::user_condition_unknown;
- constexpr llvm::omp::TraitProperty matchAnyTrait =
- llvm::omp::TraitProperty::implementation_extension_match_any;
- constexpr llvm::omp::TraitProperty matchNoneTrait =
- llvm::omp::TraitProperty::implementation_extension_match_none;
-
- // Static applicability must only use traits known at lowering time.
- // For example, in
- // when(implementation={vendor(llvm)},
- // user={condition(score(5): flag)}: barrier)
- // vendor(llvm) can be checked now, but flag cannot. Drop the
- // runtime-only user_condition_unknown for applicability, while keeping
- // score(5) so ranking can still honor the user-condition selector.
- llvm::omp::VariantMatchInfo staticVMI = rawVMI;
- std::optional<llvm::APInt> conditionScore;
- auto scoreIt = staticVMI.ScoreMap.find(dynamicConditionTrait);
- if (scoreIt != staticVMI.ScoreMap.end()) {
- conditionScore = scoreIt->second;
- staticVMI.ScoreMap.erase(scoreIt);
- }
- staticVMI.RequiredTraits.reset(unsigned(dynamicConditionTrait));
- llvm::APInt *conditionScorePtr =
- conditionScore ? &*conditionScore : nullptr;
-
- bool hasMatchAny = rawVMI.RequiredTraits.test(unsigned(matchAnyTrait));
- bool hasMatchNone =
- rawVMI.RequiredTraits.test(unsigned(matchNoneTrait));
- bool isStaticVMIApplicable =
- llvm::omp::isVariantApplicableInContext(staticVMI, ompCtx);
- // If staticVMI does not match, only match_any can still apply. Check
- // conditionTrueVMI because the runtime condition may satisfy match_any.
- if (!isStaticVMIApplicable) {
- if (!hasMatchAny || staticVMI.RequiredTraits.test(
- unsigned(llvm::omp::TraitProperty::invalid)))
- continue;
-
- llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI;
- conditionTrueVMI.addTrait(
- llvm::omp::TraitProperty::user_condition_true, "<condition>",
- conditionScorePtr);
- if (!llvm::omp::isVariantApplicableInContext(conditionTrueVMI,
- ompCtx))
- continue;
- }
+ std::optional<semantics::omp::MetadirectiveCandidateSet> candidateSet =
+ semantics::omp::BuildMetadirectiveCandidateSet(clauseList, semaCtx,
+ ompCtx);
+ assert(candidateSet && "unsupported selector reached candidate planning");
+ auto &candidates = candidateSet->candidates;
+ const parser::OmpDirectiveSpecification *fallback = candidateSet->fallback;
- auto addConditionTraitForRanking =
- [&](llvm::omp::VariantMatchInfo &rankingVMI) {
- rankingVMI.addTrait(
- hasMatchNone ? dynamicConditionTrait
- : llvm::omp::TraitProperty::user_condition_true,
- "<condition>", conditionScorePtr);
- };
-
- if (hasMatchAny && isStaticVMIApplicable) {
- // A statically matched match_any selector needs two candidates: a
- // guarded candidate with the user condition and score, and an
- // unguarded candidate with only the statically matched traits. If the
- // when clause omits its directive, only add the unguarded candidate.
- if (isExplicit) {
- llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI;
- addConditionTraitForRanking(conditionTrueVMI);
- candidates.emplace_back(spec, conditionTrueVMI, isExplicit,
- dynamicCond);
- }
- candidates.emplace_back(spec, staticVMI, isExplicit);
- continue;
- }
+ llvm::SmallVector<unsigned, 4> allCandidateIndices;
+ allCandidateIndices.reserve(candidates.size());
+ for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
+ allCandidateIndices.push_back(idx);
- llvm::omp::VariantMatchInfo rankingVMI = staticVMI;
- // An omitted directive is implicit nothing, so do not let the runtime
- // condition raise its rank. Explicit `nothing` is still a variant.
- if (!isExplicit && hasMatchAny && !isStaticVMIApplicable)
- rankingVMI = llvm::omp::VariantMatchInfo();
- else if (isExplicit)
- addConditionTraitForRanking(rankingVMI);
- candidates.emplace_back(spec, rankingVMI, isExplicit, dynamicCond,
- /*conditionShouldBeTrue=*/!hasMatchNone);
- continue;
- }
+ llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
+ reachableVariantSpecs = semantics::omp::GetReachableMetadirectiveVariants(
+ *candidateSet, ompCtx, semaCtx);
- if (!llvm::omp::isVariantApplicableInContext(rawVMI, ompCtx))
- continue;
+ bool hasLoopAssociatedCandidate =
+ llvm::any_of(reachableVariantSpecs, [](const auto *spec) {
+ return spec && hasDirectiveAssociation(
+ spec->DirId(), llvm::omp::Association::LoopNest);
+ });
+ SplicedAssociatedEvaluations splicedAssociatedEvaluations;
+ lower::pft::Evaluation *associatedLoopEval = nullptr;
+ llvm::scope_exit restoreEvaluationOwnership([&]() {
+ if (eval.hasNestedEvaluations())
+ splicedAssociatedEvaluations.restore(eval.getNestedEvaluations());
+ });
+ if (hasLoopAssociatedCandidate) {
+ if (lower::pft::Evaluation *loopEval =
+ spliceAssociatedDoEval(eval, &splicedAssociatedEvaluations)) {
+ associatedLoopEval = loopEval;
+ if (lower::pft::FunctionLikeUnit *owningProc =
+ eval.getOwningProcedure()) {
+ if (owningProc->getEntryEval() &&
+ splicedAssociatedEvaluations.getEntryBlock()) {
+ // Alternate ENTRY lowering starts with a branch. Emit selection in
+ // the detached associated block, which is either that branch's
+ // destination or unreachable for an ENTRY after the metadirective.
+ builder.setInsertionPointToStart(
+ splicedAssociatedEvaluations.getEntryBlock());
+ }
+ }
- candidates.emplace_back(spec, rawVMI, isExplicit);
- } else if (const auto *otherwiseClause =
- std::get_if<parser::OmpClause::Otherwise>(&clause.u)) {
- if (otherwiseClause->v && otherwiseClause->v->v)
- fallback = getFallbackVariant(otherwiseClause->v->v->value());
- } else if (const auto *defaultVariantClause =
- std::get_if<parser::OmpClause::DefaultVariant>(&clause.u)) {
- const auto &dirSpec = defaultVariantClause->v.v;
- fallback = getFallbackVariant(dirSpec.value());
+ auto &nested = eval.getNestedEvaluations();
+ auto loopIt =
+ llvm::find_if(nested, [loopEval](lower::pft::Evaluation &e) {
+ return &e == loopEval;
+ });
+ assert(loopIt != nested.end() && "associated loop not nested");
+
+ // Attach compiler directives to the loop before any selected variant
+ // lowers it. Variant bodies skip them below to avoid processing them a
+ // second time.
+ for (auto it = nested.begin(); it != loopIt; ++it)
+ if (it->getIf<parser::CompilerDirective>())
+ converter.genEval(*it);
}
}
+ auto genMetadirectiveBody = [&]() {
+ for (lower::pft::Evaluation &nested : eval.getNestedEvaluations())
+ if (!hasLoopAssociatedCandidate ||
+ !nested.getIf<parser::CompilerDirective>())
+ converter.genEval(nested);
+ };
+
// Lower a single resolved candidate.
auto genVariant = [&](const parser::OmpDirectiveSpecification *spec) {
if (!spec) {
- genNestedEvaluations(converter, eval);
+ genMetadirectiveBody();
return;
}
- List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
mlir::Location variantLoc = converter.genLocation(spec->source);
+ List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
ConstructQueue queue{
buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
eval, spec->source, spec->DirId(), variantClauses)};
+ unsigned ompVersion{semaCtx.langOptions().OpenMPVersion};
if (llvm::any_of(queue, [](const auto &item) {
- return llvm::omp::getDirectiveAssociation(item.id) ==
- llvm::omp::Association::LoopNest;
+ return llvm::omp::allTargetSet.test(item.id);
})) {
- TODO(variantLoc, "loop-associated METADIRECTIVE variant");
+ TODO(variantLoc,
+ "TARGET construct selected by METADIRECTIVE (host-eval)");
}
if (llvm::any_of(queue, [](const auto &item) {
@@ -7216,50 +7535,90 @@ static void genMetadirective(lower::AbstractConverter &converter,
TODO(variantLoc, "declarative METADIRECTIVE variant");
}
+ bool hasLoopAssociation =
+ hasDirectiveAssociation(queue, llvm::omp::Association::LoopNest);
+ if (!hasLoopAssociation &&
+ hasUnsupportedMetadirectiveHostAssociationClause(queue, ompVersion))
+ TODO(variantLoc, "METADIRECTIVE block variant with a clause requiring "
+ "variant-local host association");
+
+ if (hasLoopAssociation && hasUnsupportedDataEnvironmentDirective(queue))
+ TODO(variantLoc,
+ "data-environment construct in loop-associated METADIRECTIVE "
+ "variant");
+ if (!hasLoopAssociation &&
+ hasUnsupportedBlockDataEnvironment(
+ queue, eval, semaCtx.FindScope(spec->source), ompVersion))
+ TODO(variantLoc, "data-environment construct in METADIRECTIVE variant");
+
+ if (hasLoopAssociation) {
+ // Name resolution cannot give a metadirective variant its own DSA
+ // scope, so marking its loop IV can otherwise contaminate an enclosing
+ // data environment.
+ if (isNestedInOpenMPDataEnvironment(
+ eval, builder.getInsertionBlock()->getParentOp()))
+ TODO(variantLoc, "loop-associated METADIRECTIVE nested in an OpenMP "
+ "data environment");
+ if (hasUnsupportedDataSharingClause(queue, ompVersion))
+ TODO(variantLoc,
+ "data-sharing clause in loop-associated METADIRECTIVE variant");
+ if (!isSupportedMetadirectiveLoopQueue(queue))
+ TODO(variantLoc,
+ "loop-associated METADIRECTIVE variant other than DO, SIMD, or "
+ "DO SIMD");
+ // Eager privatization requires a construct-scoped IV symbol with a host
+ // association, which name resolution cannot create for a
+ // metadirective-selected loop.
+ if (!enableDelayedPrivatization)
+ TODO(variantLoc,
+ "loop-associated METADIRECTIVE with eager privatization");
+ lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
+ if (!loopEval)
+ TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
+ // Unstructured loops own PFT blocks that cannot be reused by begin/end
+ // metadirectives or alternate ENTRY lowering without independent block
+ // mappings. Keep Part 2 conservative for all such loops.
+ if (loopEval->lowerAsUnstructured())
+ TODO(variantLoc, "unstructured associated DO in loop-associated "
+ "METADIRECTIVE variant");
+ SymbolDSAGuard dsaGuard;
+ MetadirectiveLoopIVMarking marking =
+ markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval, dsaGuard);
+ if (marking == MetadirectiveLoopIVMarking::NestTooShallow)
+ TODO(variantLoc, "METADIRECTIVE variant with COLLAPSE or ORDERED "
+ "requires a deeper perfectly-nested loop nest than "
+ "is present");
+ if (marking == MetadirectiveLoopIVMarking::NonCanonicalLoop)
+ TODO(variantLoc, "METADIRECTIVE variant with a non-canonical affected "
+ "loop (a DO WHILE or a DO without loop control)");
+ if (marking == MetadirectiveLoopIVMarking::IndirectIV)
+ TODO(variantLoc, "POINTER or ALLOCATABLE loop iteration variable in "
+ "loop-associated METADIRECTIVE variant");
+ if (marking == MetadirectiveLoopIVMarking::AssociateIV)
+ TODO(variantLoc, "ASSOCIATE name loop iteration variable in "
+ "loop-associated METADIRECTIVE variant");
+ genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+ queue.begin());
+ return;
+ }
+
+ bool consumesBody = llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::getDirectiveAssociation(item.id) !=
+ llvm::omp::Association::None;
+ });
+ if (hasLoopAssociatedCandidate && consumesBody)
+ TODO(variantLoc,
+ "METADIRECTIVE with both block- and loop-associated variants");
+
genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
queue.begin());
+ // A standalone variant (Association::None, e.g. barrier/taskwait/nothing)
+ // does not consume the metadirective's nested block, so lower it here.
+ if (!consumesBody && eval.hasNestedEvaluations())
+ genMetadirectiveBody();
};
- auto selectBestCandidate =
- [](llvm::ArrayRef<unsigned> candidateIndices,
- llvm::ArrayRef<MetadirectiveCandidate> candidates,
- const semantics::omp::OmpVariantMatchContext &ompCtx)
- -> std::optional<unsigned> {
- if (candidateIndices.empty())
- return std::nullopt;
- if (candidateIndices.size() == 1)
- return candidateIndices.front();
-
- // The OpenMP context scorer preserves input order for tied candidates.
- // Put explicit variants first so they take precedence over implicit
- // `nothing`, as required by metadirective selection.
- llvm::SmallVector<unsigned, 4> candidateOrder;
- candidateOrder.reserve(candidateIndices.size());
- for (unsigned idx : candidateIndices)
- if (candidates[idx].isExplicit)
- candidateOrder.push_back(idx);
- for (unsigned idx : candidateIndices)
- if (!candidates[idx].isExplicit)
- candidateOrder.push_back(idx);
-
- llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
- orderedVMIs.reserve(candidateOrder.size());
- for (unsigned idx : candidateOrder)
- orderedVMIs.push_back(candidates[idx].vmi);
-
- int bestIdx = llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
- if (bestIdx >= 0) {
- assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
- "best variant index out of range");
- return candidateOrder[bestIdx];
- }
- return std::nullopt;
- };
-
- llvm::SmallVector<unsigned, 4> remainingCandidates;
- remainingCandidates.reserve(candidates.size());
- for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
- remainingCandidates.push_back(idx);
+ llvm::SmallVector<unsigned, 4> remainingCandidates{allCandidateIndices};
lower::StatementContext stmtCtx;
@@ -7280,23 +7639,23 @@ static void genMetadirective(lower::AbstractConverter &converter,
// Stop when selection reaches an unguarded candidate or the fallback.
while (!remainingCandidates.empty()) {
std::optional<unsigned> selected =
- selectBestCandidate(remainingCandidates, candidates, ompCtx);
+ semantics::omp::SelectBestMetadirectiveCandidate(remainingCandidates,
+ candidates, ompCtx);
if (!selected) {
genVariant(fallback);
return;
}
- const MetadirectiveCandidate &candidate = candidates[*selected];
- if (!candidate.dynamicCond) {
+ const semantics::omp::MetadirectiveCandidate &candidate =
+ candidates[*selected];
+ if (!candidate.dynamicCondition) {
genVariant(candidate.spec);
return;
}
- llvm::SmallVector<unsigned, 4> elsePathCandidates(remainingCandidates);
- auto *remainingIt = llvm::find(elsePathCandidates, *selected);
- assert(remainingIt != elsePathCandidates.end() &&
- "selected candidate missing from remaining candidates");
- elsePathCandidates.erase(remainingIt);
+ llvm::SmallVector<unsigned, 4> elsePathCandidates =
+ semantics::omp::GetMetadirectiveElsePathCandidates(
+ *selected, remainingCandidates, candidates, ompCtx, semaCtx);
// match_any may create a guarded condition-true candidate and an unguarded
// static candidate for the same directive. If the else path picks the
@@ -7305,20 +7664,29 @@ static void genMetadirective(lower::AbstractConverter &converter,
// if (flag) barrier into just barrier
// else barrier
if (std::optional<unsigned> selectedInElse =
- selectBestCandidate(elsePathCandidates, candidates, ompCtx)) {
- const MetadirectiveCandidate &candidateInElse =
+ semantics::omp::SelectBestMetadirectiveCandidate(
+ elsePathCandidates, candidates, ompCtx)) {
+ const semantics::omp::MetadirectiveCandidate &candidateInElse =
candidates[*selectedInElse];
- if (!candidateInElse.dynamicCond &&
+ if (!candidateInElse.dynamicCondition &&
candidateInElse.spec == candidate.spec) {
genVariant(candidate.spec);
return;
}
}
+ // Unstructured evaluations own PFT blocks that lowering reparents into the
+ // generated region. They cannot be reused for both sides of a runtime
+ // selection until each arm can receive an independent block mapping.
+ if (associatedLoopEval && associatedLoopEval->lowerAsUnstructured())
+ TODO(converter.genLocation(candidate.dynamicCondition->source),
+ "unstructured associated DO in loop-associated METADIRECTIVE "
+ "variant");
+
mlir::Location condLoc =
- converter.genLocation(candidate.dynamicCond->source);
+ converter.genLocation(candidate.dynamicCondition->source);
const auto *condExpr =
- semantics::GetExpr(semaCtx, *candidate.dynamicCond->expr);
+ semantics::GetExpr(semaCtx, *candidate.dynamicCondition->expr);
assert(condExpr && "missing expression for user condition");
mlir::Value condVal =
fir::getBase(converter.genExprValue(*condExpr, stmtCtx, &condLoc));
@@ -7994,7 +8362,10 @@ void Fortran::lower::genOpenMPDeclarativeConstruct(
semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
const parser::OpenMPDeclarativeConstruct &omp) {
genOMP(converter, symTable, semaCtx, eval, omp);
- genNestedEvaluations(converter, eval);
+ // Metadirective lowering selects a variant and consumes its associated
+ // evaluations itself.
+ if (!isMetadirectiveEval(eval))
+ genNestedEvaluations(converter, eval);
}
void Fortran::lower::genOpenMPSymbolProperties(
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 8f57f00d59c58..aa523cca95e4f 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -707,7 +707,7 @@ static void processTileSizesFromOpenMPConstruct(
// can happen when COLLAPSE counts loops that a transforming construct such as
// TILE generates from the source DO loops. getNestedDoConstruct wraps this for
// callers that require a DO construct and asserts when none is found.
-static pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval) {
+pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval) {
for (pft::Evaluation &nested : eval.getNestedEvaluations()) {
// In an OpenMPConstruct there can be compiler directives:
// 1 <<OpenMPConstruct>>
@@ -741,6 +741,23 @@ pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
return doConstruct;
}
+/// Return true if \p eval holds a metadirective.
+bool isMetadirectiveEval(lower::pft::Evaluation &eval) {
+ if (const auto *decl = eval.getIf<parser::OpenMPDeclarativeConstruct>())
+ return std::holds_alternative<parser::OmpMetadirectiveDirective>(decl->u);
+ if (const auto *ompConstruct = eval.getIf<parser::OpenMPConstruct>()) {
+ if (std::holds_alternative<parser::OmpDelimitedMetadirectiveDirective>(
+ ompConstruct->u))
+ return true;
+ if (const auto *standalone =
+ std::get_if<parser::OpenMPStandaloneConstruct>(&ompConstruct->u)) {
+ return std::holds_alternative<parser::OmpMetadirectiveDirective>(
+ standalone->u);
+ }
+ }
+ return false;
+}
+
/// Populates the sizes vector with values if the given OpenMPConstruct
/// contains a loop construct with an inner tiling construct.
void collectTileSizesFromOpenMPConstruct(
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..ec50b9db801f9 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -170,6 +170,8 @@ void genObjectList(const ObjectList &objects,
void lastprivateModifierNotSupported(const omp::clause::Lastprivate &lastp,
mlir::Location loc);
+pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval);
+
pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval);
int64_t collectLoopRelatedInfo(
@@ -274,6 +276,8 @@ semantics::omp::OmpVariantMatchContext makeVariantMatchContext(
mlir::ModuleOp module,
llvm::ArrayRef<llvm::omp::TraitProperty> constructTraits);
+bool isMetadirectiveEval(lower::pft::Evaluation &eval);
+
} // namespace omp
} // namespace lower
} // namespace Fortran
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index dc3d5a302e841..fb7df56c5beb9 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -22,6 +22,7 @@
#include "flang/Evaluate/check-expression.h"
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/match.h"
+#include "flang/Evaluate/rewrite.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/traverse.h"
#include "flang/Evaluate/type.h"
@@ -2521,6 +2522,331 @@ std::optional<DynamicUserCondition> MakeVariantMatchInfo(
return dynamicCond;
}
+std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
+ const parser::OmpClauseList &clauses, SemanticsContext &context,
+ const OmpVariantMatchContext &matchContext) {
+ MetadirectiveCandidateSet result;
+
+ auto getContextSelector = [](const parser::OmpClause::When &whenClause)
+ -> const parser::modifier::OmpContextSelector * {
+ const auto &modifiers{std::get<0>(whenClause.v.t)};
+ if (!modifiers || modifiers->size() != 1) {
+ return nullptr;
+ }
+ return std::get_if<parser::modifier::OmpContextSelector>(
+ &modifiers->front().u);
+ };
+
+ auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
+ -> std::pair<const parser::OmpDirectiveSpecification *, bool> {
+ const auto &optionalSpec{std::get<1>(whenClause.v.t)};
+ if (!optionalSpec) {
+ return {nullptr, false};
+ }
+ if (optionalSpec->value().DirId() == llvm::omp::Directive::OMPD_nothing) {
+ return {nullptr, true};
+ }
+ return {&optionalSpec->value(), true};
+ };
+
+ auto getFallbackVariant = [](const parser::OmpDirectiveSpecification &spec) {
+ return spec.DirId() == llvm::omp::Directive::OMPD_nothing ? nullptr : &spec;
+ };
+
+ for (const parser::OmpClause &clause : clauses.v) {
+ if (const auto *whenClause{
+ std::get_if<parser::OmpClause::When>(&clause.u)}) {
+ const auto *ctxSel{getContextSelector(*whenClause)};
+ if (!ctxSel ||
+ FindUnsupportedSelectorFeature(*ctxSel, context) !=
+ UnsupportedSelectorFeature::None) {
+ return std::nullopt;
+ }
+
+ auto [spec, isExplicit]{getDirectiveVariant(*whenClause)};
+ llvm::omp::VariantMatchInfo rawVMI;
+ std::optional<DynamicUserCondition> dynamicCondition{
+ MakeVariantMatchInfo(rawVMI, *ctxSel, context)};
+ if (llvm::any_of(
+ rawVMI.ConstructTraits, [](llvm::omp::TraitProperty property) {
+ return llvm::omp::getOpenMPContextTraitSetForProperty(
+ property) != llvm::omp::TraitSet::construct;
+ })) {
+ return std::nullopt;
+ }
+
+ if (dynamicCondition) {
+ constexpr llvm::omp::TraitProperty dynamicConditionTrait{
+ llvm::omp::TraitProperty::user_condition_unknown};
+ constexpr llvm::omp::TraitProperty matchAnyTrait{
+ llvm::omp::TraitProperty::implementation_extension_match_any};
+ constexpr llvm::omp::TraitProperty matchNoneTrait{
+ llvm::omp::TraitProperty::implementation_extension_match_none};
+
+ // Static applicability uses only traits known at compile time. Keep
+ // the condition's score so a true runtime condition is still ranked
+ // correctly.
+ llvm::omp::VariantMatchInfo staticVMI{rawVMI};
+ std::optional<llvm::APInt> conditionScore;
+ auto scoreIt{staticVMI.ScoreMap.find(dynamicConditionTrait)};
+ if (scoreIt != staticVMI.ScoreMap.end()) {
+ conditionScore = scoreIt->second;
+ staticVMI.ScoreMap.erase(scoreIt);
+ }
+ staticVMI.RequiredTraits.reset(unsigned(dynamicConditionTrait));
+ llvm::APInt *conditionScorePtr{
+ conditionScore ? &*conditionScore : nullptr};
+
+ bool hasMatchAny{rawVMI.RequiredTraits.test(unsigned(matchAnyTrait))};
+ bool hasMatchNone{rawVMI.RequiredTraits.test(unsigned(matchNoneTrait))};
+ bool isStaticVMIApplicable{
+ llvm::omp::isVariantApplicableInContext(staticVMI, matchContext)};
+ // Only match_any can remain applicable when the static traits do not
+ // match, because a true runtime condition may satisfy the selector.
+ if (!isStaticVMIApplicable) {
+ if (!hasMatchAny ||
+ staticVMI.RequiredTraits.test(
+ unsigned(llvm::omp::TraitProperty::invalid))) {
+ continue;
+ }
+
+ llvm::omp::VariantMatchInfo conditionTrueVMI{staticVMI};
+ conditionTrueVMI.addTrait(
+ llvm::omp::TraitProperty::user_condition_true, "<condition>",
+ conditionScorePtr);
+ if (!llvm::omp::isVariantApplicableInContext(
+ conditionTrueVMI, matchContext)) {
+ continue;
+ }
+ }
+
+ auto addConditionTraitForRanking =
+ [&](llvm::omp::VariantMatchInfo &rankingVMI) {
+ rankingVMI.addTrait(hasMatchNone
+ ? dynamicConditionTrait
+ : llvm::omp::TraitProperty::user_condition_true,
+ "<condition>", conditionScorePtr);
+ };
+
+ if (hasMatchAny && isStaticVMIApplicable) {
+ // Represent both outcomes: a guarded candidate with the condition's
+ // score and an unguarded candidate with only the static traits.
+ llvm::omp::VariantMatchInfo conditionTrueVMI{staticVMI};
+ addConditionTraitForRanking(conditionTrueVMI);
+ result.candidates.push_back({spec, std::move(conditionTrueVMI),
+ isExplicit, dynamicCondition});
+ result.candidates.push_back({spec, std::move(staticVMI), isExplicit});
+ continue;
+ }
+
+ llvm::omp::VariantMatchInfo rankingVMI{staticVMI};
+ addConditionTraitForRanking(rankingVMI);
+ result.candidates.push_back({spec, std::move(rankingVMI), isExplicit,
+ dynamicCondition, /*conditionShouldBeTrue=*/!hasMatchNone});
+ continue;
+ }
+
+ if (!llvm::omp::isVariantApplicableInContext(rawVMI, matchContext)) {
+ continue;
+ }
+ result.candidates.push_back({spec, std::move(rawVMI), isExplicit});
+ } else if (const auto *otherwiseClause{
+ std::get_if<parser::OmpClause::Otherwise>(&clause.u)}) {
+ if (otherwiseClause->v && otherwiseClause->v->v) {
+ result.fallback = getFallbackVariant(otherwiseClause->v->v->value());
+ }
+ } else if (const auto *defaultVariantClause{
+ std::get_if<parser::OmpClause::DefaultVariant>(&clause.u)}) {
+ result.fallback = getFallbackVariant(defaultVariantClause->v.v.value());
+ }
+ }
+ return result;
+}
+
+std::optional<unsigned> SelectBestMetadirectiveCandidate(
+ llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<MetadirectiveCandidate> candidates,
+ const OmpVariantMatchContext &matchContext) {
+ if (candidateIndices.empty()) {
+ return std::nullopt;
+ }
+ if (candidateIndices.size() == 1) {
+ return candidateIndices.front();
+ }
+
+ // The context scorer preserves input order for ties. Explicit replacements
+ // take precedence over an omitted directive's implicit NOTHING.
+ llvm::SmallVector<unsigned, 4> candidateOrder;
+ candidateOrder.reserve(candidateIndices.size());
+ for (unsigned index : candidateIndices) {
+ if (candidates[index].isExplicit) {
+ candidateOrder.push_back(index);
+ }
+ }
+ for (unsigned index : candidateIndices) {
+ if (!candidates[index].isExplicit) {
+ candidateOrder.push_back(index);
+ }
+ }
+
+ llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+ orderedVMIs.reserve(candidateOrder.size());
+ for (unsigned index : candidateOrder) {
+ orderedVMIs.push_back(candidates[index].vmi);
+ }
+
+ int bestIndex{
+ llvm::omp::getBestVariantMatchForContext(orderedVMIs, matchContext)};
+ if (bestIndex < 0) {
+ return std::nullopt;
+ }
+ CHECK(static_cast<std::size_t>(bestIndex) < candidateOrder.size());
+ return candidateOrder[bestIndex];
+}
+
+namespace {
+struct MetadirectiveConditionNormalizer : evaluate::rewrite::Identity {
+ using evaluate::rewrite::Identity::operator();
+
+ template <typename T>
+ evaluate::Expr<T> operator()(
+ evaluate::Expr<T> &&, const evaluate::Parentheses<T> &parentheses) {
+ return common::Clone(parentheses.left());
+ }
+
+ template <int KIND>
+ evaluate::Expr<evaluate::Type<common::TypeCategory::Logical, KIND>>
+ operator()(evaluate::Expr<evaluate::Type<common::TypeCategory::Logical, KIND>>
+ &&expr,
+ const evaluate::LogicalOperation<KIND> &operation) {
+ if ((operation.logicalOperator == evaluate::LogicalOperator::And ||
+ operation.logicalOperator == evaluate::LogicalOperator::Or) &&
+ operation.left() == operation.right())
+ return common::Clone(operation.left());
+ return std::move(expr);
+ }
+};
+
+bool isRepeatableMetadirectiveCondition(const SomeExpr &expr) {
+ // A procedure call can depend on state that is not represented in the
+ // expression tree, so conservatively do not correlate calls, even if the
+ // procedure is pure. This also rejects coarray references and other
+ // expression nodes that are unsafe to copy.
+ if (!evaluate::IsSafelyCopyable(expr))
+ return false;
+
+ for (const Symbol &symbol : evaluate::CollectSymbols(expr)) {
+ const Symbol &ultimate{symbol.GetUltimate()};
+ if (ultimate.attrs().HasAny({Attr::ASYNCHRONOUS, Attr::VOLATILE}) ||
+ evaluate::IsCoarray(ultimate))
+ return false;
+ }
+ return true;
+}
+} // namespace
+
+bool IsRepeatableMetadirectiveCondition(
+ const parser::ScalarExpr &condition, SemanticsContext &context) {
+ const SomeExpr *expr{GetExpr(context, condition)};
+ return expr && isRepeatableMetadirectiveCondition(*expr);
+}
+
+bool AreSameRepeatableMetadirectiveCondition(const parser::ScalarExpr &left,
+ const parser::ScalarExpr &right, SemanticsContext &context) {
+ const SomeExpr *leftExpr{GetExpr(context, left)};
+ const SomeExpr *rightExpr{GetExpr(context, right)};
+ if (!leftExpr || !rightExpr ||
+ !isRepeatableMetadirectiveCondition(*leftExpr) ||
+ !isRepeatableMetadirectiveCondition(*rightExpr))
+ return false;
+
+ MetadirectiveConditionNormalizer normalizer;
+ evaluate::rewrite::Mutator normalize{normalizer};
+ return normalize(*leftExpr) == normalize(*rightExpr);
+}
+
+llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
+ unsigned selectedIndex, llvm::ArrayRef<unsigned> candidateIndices,
+ llvm::ArrayRef<MetadirectiveCandidate> candidates,
+ const OmpVariantMatchContext &matchContext, SemanticsContext &context) {
+ CHECK(selectedIndex < candidates.size());
+ const MetadirectiveCandidate &selected{candidates[selectedIndex]};
+ CHECK(selected.dynamicCondition);
+
+ llvm::SmallVector<unsigned, 4> result;
+ result.reserve(candidateIndices.size());
+ for (unsigned index : candidateIndices)
+ if (index != selectedIndex)
+ result.push_back(index);
+
+ // Inspect candidates in the order in which selection would evaluate them.
+ // A distinct repeatable condition cannot modify the selected condition, so
+ // the failed value remains usable past it. Stop at the first non-repeatable
+ // condition because it can change state before a lower-ranked occurrence is
+ // evaluated.
+ llvm::SmallVector<unsigned, 4> candidatesToInspect{result};
+ while (std::optional<unsigned> next{SelectBestMetadirectiveCandidate(
+ candidatesToInspect, candidates, matchContext)}) {
+ const MetadirectiveCandidate &candidate{candidates[*next]};
+ if (!candidate.dynamicCondition ||
+ !IsRepeatableMetadirectiveCondition(
+ *candidate.dynamicCondition->expr, context))
+ break;
+
+ bool hasSameFailedCondition{
+ candidate.conditionShouldBeTrue == selected.conditionShouldBeTrue &&
+ AreSameRepeatableMetadirectiveCondition(
+ *selected.dynamicCondition->expr, *candidate.dynamicCondition->expr,
+ context)};
+ if (hasSameFailedCondition)
+ llvm::erase(result, *next);
+ llvm::erase(candidatesToInspect, *next);
+ }
+ return result;
+}
+
+llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
+GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
+ const OmpVariantMatchContext &matchContext, SemanticsContext &context) {
+ llvm::SmallVector<unsigned, 4> candidates;
+ candidates.reserve(candidateSet.candidates.size());
+ for (unsigned index{0}; index < candidateSet.candidates.size(); ++index) {
+ candidates.push_back(index);
+ }
+
+ llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4> reachable;
+ while (true) {
+ std::optional<unsigned> selected{SelectBestMetadirectiveCandidate(
+ candidates, candidateSet.candidates, matchContext)};
+ if (!selected) {
+ reachable.push_back(candidateSet.fallback);
+ break;
+ }
+
+ const MetadirectiveCandidate &candidate{candidateSet.candidates[*selected]};
+ reachable.push_back(candidate.spec);
+ // An unguarded winner ends selection. A dynamic winner leaves the
+ // remaining candidates reachable through its false path.
+ if (!candidate.dynamicCondition) {
+ break;
+ }
+
+ candidates = GetMetadirectiveElsePathCandidates(
+ *selected, candidates, candidateSet.candidates, matchContext, context);
+
+ if (std::optional<unsigned> selectedInElse{SelectBestMetadirectiveCandidate(
+ candidates, candidateSet.candidates, matchContext)}) {
+ const MetadirectiveCandidate &elseCandidate{
+ candidateSet.candidates[*selectedInElse]};
+ if (!elseCandidate.dynamicCondition &&
+ elseCandidate.spec == candidate.spec) {
+ break;
+ }
+ }
+ }
+ return reachable;
+}
+
bool MayVariantBeSelected(
const parser::traits::OmpContextSelectorSpecification *selector,
SemanticsContext &context, OmpVariantMatchContext &matchContext) {
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
new file mode 100644
index 0000000000000..b422d298241c4
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
@@ -0,0 +1,132 @@
+! A metadirective block replacement cannot yet reconstruct implicit
+! data-sharing attributes. Reject regions that would otherwise lose a
+! sequential loop IV or an implicit task capture.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/parallel.f90 2>&1 | FileCheck --check-prefix=TODO %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task.f90 2>&1 | FileCheck --check-prefix=TODO %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task-unselected-shared.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=TODO %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task-nested-shared.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=TODO %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task-default-shared-sequential-loop.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=TODO %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task-local.f90 | FileCheck --check-prefix=LOCAL %s
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/task-selected-shared.f90 \
+! RUN: | FileCheck --check-prefix=SHARED %s
+
+! TODO: not yet implemented: data-environment construct in METADIRECTIVE variant
+
+! LOCAL-LABEL: func.func @_QPtask_block_variant_with_local_storage
+! LOCAL: omp.task {
+! LOCAL: fir.alloca i32 {{.*}}bindc_name = "local"
+! LOCAL: hlfir.assign
+! LOCAL: omp.terminator
+
+! SHARED-LABEL: func.func @_QPtask_block_variant_with_selected_shared
+! SHARED: %[[EXPLICIT:.*]]:2 = hlfir.declare
+! SHARED: omp.task {
+! SHARED: fir.load %[[EXPLICIT]]#0
+! SHARED: hlfir.assign {{.*}} to %[[EXPLICIT]]#0
+! SHARED: omp.terminator
+
+! SHARED-LABEL: func.func @_QPtask_block_variant_with_default_shared
+! SHARED: %[[DEFAULT:.*]]:2 = hlfir.declare
+! SHARED: omp.task {
+! SHARED: fir.load %[[DEFAULT]]#0
+! SHARED: hlfir.assign {{.*}} to %[[DEFAULT]]#0
+! SHARED: omp.terminator
+
+!--- parallel.f90
+subroutine parallel_block_variant(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel) &
+ !$omp& otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+!--- task.f90
+subroutine task_block_variant(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+!--- task-unselected-shared.f90
+subroutine task_block_variant_with_unselected_shared(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(user={condition(score(2): .true.)}: task) &
+ !$omp& when(user={condition(score(1): .true.)}: task shared(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+!--- task-nested-shared.f90
+subroutine task_block_variant_with_nested_shared(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task) &
+ !$omp& otherwise(nothing)
+ !$omp parallel shared(x)
+ x = x + 1
+ !$omp end parallel
+ !$omp end metadirective
+end subroutine
+
+!--- task-default-shared-sequential-loop.f90
+subroutine task_block_variant_with_default_shared_sequential_loop(n)
+ integer :: n, i
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task default(shared)) &
+ !$omp& otherwise(nothing)
+ do i = 1, n
+ end do
+ !$omp end metadirective
+end subroutine
+
+!--- task-local.f90
+subroutine task_block_variant_with_local_storage()
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task) &
+ !$omp& otherwise(nothing)
+ block
+ integer :: local
+ local = 1
+ end block
+ !$omp end metadirective
+end subroutine
+
+!--- task-selected-shared.f90
+subroutine task_block_variant_with_selected_shared(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task shared(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+subroutine task_block_variant_with_default_shared(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: task default(shared)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
new file mode 100644
index 0000000000000..5843f5293c95b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
@@ -0,0 +1,17 @@
+! DEFAULT(PRIVATE) still requires variant-local host associations before a
+! metadirective block variant can use eager privatization.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
+! RUN: | FileCheck %s
+
+! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
+
+subroutine test_block_eager_default_private(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel default(private)) &
+ !$omp & otherwise(nothing)
+ x = 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
new file mode 100644
index 0000000000000..73ad5ca292ff8
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
@@ -0,0 +1,17 @@
+! Metadirective replacements with privatizing clauses need variant-local host
+! associations before they can use eager privatization.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
+! RUN: | FileCheck %s
+
+! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
+
+subroutine test_block_eager_privatization(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel private(x)) &
+ !$omp & otherwise(nothing)
+ x = 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
new file mode 100644
index 0000000000000..8922274ef9727
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
@@ -0,0 +1,37 @@
+! Metadirective block variants do not have variant-local host-association
+! symbols. Reject clauses that require them in both delayed and eager modes.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/firstprivate.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - \
+! RUN: %t/firstprivate.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/copyin.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - \
+! RUN: %t/copyin.f90 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
+
+!--- firstprivate.f90
+subroutine firstprivate_block_variant(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel firstprivate(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+!--- copyin.f90
+subroutine copyin_block_variant()
+ integer, save :: x
+ !$omp threadprivate(x)
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel copyin(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
new file mode 100644
index 0000000000000..e2da85c14179c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
@@ -0,0 +1,36 @@
+! ASSOCIATE-name loop iteration variables require construct-scoped name
+! resolution for private and lastprivate bindings.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/simd-collapse.f90 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: ASSOCIATE name loop iteration variable in loop-associated METADIRECTIVE variant
+
+!--- do.f90
+subroutine test_do(n, a)
+ integer :: n, a(n), source_i
+ associate(i => source_i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ end associate
+end subroutine
+
+!--- simd-collapse.f90
+subroutine test_simd_collapse(n, a)
+ integer :: n, a(n, n), source_i, j
+ associate(i => source_i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+ end associate
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
new file mode 100644
index 0000000000000..484fddde82739
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
@@ -0,0 +1,18 @@
+! A block-associated fallback consumes the begin/end region differently from an
+! ordinary loop variant and is not supported yet.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: METADIRECTIVE with both block- and loop-associated variants
+
+subroutine test_single_fallback(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(single)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
new file mode 100644
index 0000000000000..1b4d073e478cc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
@@ -0,0 +1,16 @@
+! A selected variant that creates a data environment needs its implicit data
+! sharing attributes reconstructed during lowering.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: data-environment construct in loop-associated METADIRECTIVE variant
+
+subroutine test_parallel_do(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
new file mode 100644
index 0000000000000..dea5f1c28757c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
@@ -0,0 +1,17 @@
+! Data-sharing clauses on selected loop variants need their symbol attributes
+! reconstructed during lowering.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: data-sharing clause in loop-associated METADIRECTIVE variant
+
+subroutine test_private(n, a)
+ integer :: n, a(n), i, x
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do private(x)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ x = i
+ a(i) = x
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
new file mode 100644
index 0000000000000..e1a6acec74bf3
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
@@ -0,0 +1,18 @@
+! Loop-associated metadirective variants currently require delayed
+! privatization.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
+! RUN: | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE with eager privatization
+
+subroutine test_eager_privatization(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
new file mode 100644
index 0000000000000..76ebca8b3fd0f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
@@ -0,0 +1,60 @@
+! A selectable loop variant needs a variant-local loop-IV binding before it can
+! be nested in another OpenMP data environment.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/static.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/dynamic.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/selected-metadirective.f90 2>&1 | FileCheck --check-prefix=SELECTED %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE nested in an OpenMP data environment
+! SELECTED: not yet implemented: data-environment construct in METADIRECTIVE variant
+
+!--- static.f90
+subroutine test_static_do_in_parallel(n, a, after)
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp parallel num_threads(1) shared(n, a, after, i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end parallel
+end subroutine
+
+!--- dynamic.f90
+subroutine test_do_in_parallel(flag, n, a, after)
+ logical, intent(in) :: flag
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp parallel num_threads(1) shared(flag, n, a, after, i)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end parallel
+end subroutine
+
+!--- selected-metadirective.f90
+subroutine test_do_in_selected_parallel(flag, n, a, after)
+ logical, intent(in) :: flag
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: &
+ !$omp & parallel num_threads(1) shared(flag, n, a, after, i)) &
+ !$omp & otherwise(nothing)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
new file mode 100644
index 0000000000000..6b7aafd9283f8
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
@@ -0,0 +1,35 @@
+! POINTER and ALLOCATABLE loop iteration variables require construct-scoped
+! name resolution.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/allocatable.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/pointer.f90 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: POINTER or ALLOCATABLE loop iteration variable in loop-associated METADIRECTIVE variant
+
+!--- allocatable.f90
+subroutine test_allocatable_iv(n, a)
+ integer :: n, a(n)
+ integer, allocatable :: i
+ allocate(i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+!--- pointer.f90
+subroutine test_pointer_iv(n, a)
+ integer :: n, a(n)
+ integer, target :: target
+ integer, pointer :: i
+ i => target
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-unstructured.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unstructured.f90
new file mode 100644
index 0000000000000..91f4c7fe8da66
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unstructured.f90
@@ -0,0 +1,74 @@
+! Defer unstructured associated loops until every selection and alternate
+! ENTRY path can give their PFT blocks an independent mapping.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/static.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/begin.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/entry.f90 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/runtime.f90 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: unstructured associated DO in loop-associated METADIRECTIVE variant
+
+!--- static.f90
+subroutine test_static(n, a, selector)
+ integer :: n, a(n), selector, i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ go to (10, 20), selector
+10 a(i) = 1
+ go to 30
+20 a(i) = 2
+30 continue
+ end do
+end subroutine
+
+!--- begin.f90
+subroutine test_begin(n, a, selector)
+ integer :: n, a(n), selector, i
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ go to (10, 20), selector
+10 a(i) = 1
+ go to 30
+20 a(i) = 2
+30 continue
+ end do
+ !$omp end metadirective
+end subroutine
+
+!--- entry.f90
+subroutine test_entry(n, a, selector)
+ integer :: n, a(n), selector, i
+ entry test_alt_entry(n, a, selector)
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ go to (10, 20), selector
+10 a(i) = 1
+ go to 30
+20 a(i) = 2
+30 continue
+ end do
+ !$omp end metadirective
+end subroutine
+
+!--- runtime.f90
+subroutine test_runtime(flag, n, a, selector)
+ logical :: flag
+ integer :: n, a(n), selector, i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ go to (10, 20), selector
+10 a(i) = 1
+ go to 30
+20 a(i) = 2
+30 continue
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
new file mode 100644
index 0000000000000..235eba0ac5c16
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
@@ -0,0 +1,15 @@
+! Part 2 supports DO, SIMD, and DO SIMD loop variants only.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE variant other than DO, SIMD, or DO SIMD
+
+subroutine test_loop(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: loop bind(thread)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
deleted file mode 100644
index 999a8c0839d15..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: loop-associated METADIRECTIVE variant
-
-subroutine test_loop_variant()
- integer :: i
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: parallel do) &
- !$omp & default(nothing)
- do i = 1, 100
- end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
new file mode 100644
index 0000000000000..010864eb14f35
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
@@ -0,0 +1,15 @@
+! Test that a metadirective variant resolving to a target construct
+! correctly reports a TODO (host-eval support needed).
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: TARGET construct selected by METADIRECTIVE (host-eval)
+
+subroutine test_target_loop()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: target teams distribute parallel do) &
+ !$omp & otherwise(nothing)
+ do i = 1, 100
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-block-eager-shared.f90 b/flang/test/Lower/OpenMP/metadirective-block-eager-shared.f90
new file mode 100644
index 0000000000000..41f644cfd4f42
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-block-eager-shared.f90
@@ -0,0 +1,19 @@
+! A DEFAULT(SHARED) clause does not require variant-local host associations and
+! can be lowered with eager privatization.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false -o - %s | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_block_eager_default_shared(
+! CHECK: omp.parallel {
+! CHECK: hlfir.assign
+! CHECK: omp.terminator
+! CHECK: return
+subroutine test_block_eager_default_shared(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel default(shared)) &
+ !$omp & otherwise(nothing)
+ x = 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-arch.f90 b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
index ed2d8a687f48a..30e2e26cfa872 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-arch.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
@@ -95,7 +95,7 @@ subroutine test_begin_arch_unknown()
! X86_64-NOT: omp.parallel
! X86_64: return
subroutine test_begin_arch_multi_when()
- integer :: x
+ integer, save :: x
x = 0
!$omp begin metadirective &
!$omp & when(device={arch(aarch64)}: parallel) &
diff --git a/flang/test/Lower/OpenMP/metadirective-implementation.f90 b/flang/test/Lower/OpenMP/metadirective-implementation.f90
index d85a8ecda2466..6c944c16e22c3 100644
--- a/flang/test/Lower/OpenMP/metadirective-implementation.f90
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -1,6 +1,5 @@
! Test lowering of OpenMP metadirective with implementation selectors.
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck %s
@@ -30,6 +29,102 @@ subroutine test_vendor_no_match()
#endif
end subroutine
+! A selected variant provides a positive control: its clause expression is
+! lowered and attached to the replacement directive.
+! CHECK-LABEL: func.func @_QPtest_selected_clause(
+! CHECK: %[[NUM_THREADS:.*]] = fir.call @_QPmetadirective_num_threads()
+! CHECK: omp.parallel num_threads(%[[NUM_THREADS]] : i32)
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_selected_clause(x)
+ integer :: x, metadirective_num_threads
+ external :: metadirective_num_threads
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: &
+ !$omp & parallel num_threads(metadirective_num_threads())) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+ x = 1
+ !$omp end metadirective
+end subroutine
+
+! An inapplicable variant must not have its clause expression or directive
+! lowered.
+! CHECK-LABEL: func.func @_QPtest_inapplicable_clause(
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK-NOT: omp.parallel
+! CHECK: fir.call @_FortranAioOutputInteger32
+! CHECK-NOT: fir.call @_FortranAioOutputInteger32
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK-NOT: omp.parallel
+! CHECK: return
+subroutine test_inapplicable_clause()
+ integer :: metadirective_num_threads
+ external :: metadirective_num_threads
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor("unknown")}: &
+ !$omp & parallel num_threads(metadirective_num_threads())) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+ print *, 1
+ !$omp end metadirective
+end subroutine
+
+! An unselected fallback must not have its clauses lowered.
+! CHECK-LABEL: func.func @_QPtest_unselected_fallback_clause(
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: omp.parallel
+! CHECK-NOT: num_threads
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: return
+subroutine test_unselected_fallback_clause(x)
+ integer :: x, metadirective_num_threads
+ external :: metadirective_num_threads
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel) &
+#ifdef OMP_52
+ !$omp & otherwise(parallel num_threads(metadirective_num_threads()))
+#else
+ !$omp & default(parallel num_threads(metadirective_num_threads()))
+#endif
+ x = 1
+ !$omp end metadirective
+end subroutine
+
+! A statically applicable but lower-ranked candidate must not have its clauses
+! lowered either.
+! CHECK-LABEL: func.func @_QPtest_unselected_ranked_clause(
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: omp.parallel
+! CHECK-NOT: num_threads
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.call @_QPmetadirective_num_threads
+! CHECK: return
+subroutine test_unselected_ranked_clause(x)
+ integer :: x, metadirective_num_threads
+ external :: metadirective_num_threads
+ !$omp begin metadirective &
+ !$omp & when(user={condition(score(1): .true.)}: &
+ !$omp & parallel num_threads(metadirective_num_threads())) &
+ !$omp & when(user={condition(score(2): .true.)}: parallel) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+ x = 1
+ !$omp end metadirective
+end subroutine
+
! CHECK-LABEL: func.func @_QPtest_standalone_barrier_match()
! CHECK: omp.barrier
! CHECK: return
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
new file mode 100644
index 0000000000000..f1bb3fc6adde3
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,673 @@
+! Test lowering of metadirectives with ordinary loop-associated variants.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 %s -o - | FileCheck %s
+
+! CHECK: #loop_unroll = #llvm.loop_unroll<disable = false, count = 4 : i64>
+! CHECK: #loop_annotation = #llvm.loop_annotation<unroll = #loop_unroll>
+
+! CHECK-LABEL: func.func @_QPtest_do(
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_do(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! The score is compared before explicitness, so a higher-scored implicit
+! NOTHING is selected over an explicit DO when its condition is true.
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_score(
+! CHECK: %[[FLAG:.*]] = fir.load {{.*}} : !fir.ref<!fir.logical<4>>
+! CHECK: %[[COND:.*]] = fir.convert %[[FLAG]]
+! CHECK: fir.if %[[COND]] {
+! CHECK-NOT: omp.
+! CHECK: fir.do_loop
+! CHECK: } else {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: }
+! CHECK: return
+subroutine test_implicit_nothing_score(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(score(10): flag)}:) &
+ !$omp & when(user={condition(score(5): .true.)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_simd(
+! CHECK-NOT: omp.wsloop
+! CHECK: omp.simd linear(
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: omp.yield
+! CHECK-NOT: fir.do_loop
+! CHECK: fir.load
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_simd(n, a, after)
+ integer :: n, a(n), after, i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do_simd(
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop
+! CHECK: omp.simd
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_do_simd(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do simd) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_do(
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_begin_do(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+! The following loop must remain available when the PFT is reused for ENTRY.
+! CHECK-LABEL: func.func @_QPtest_standalone_entry_no_directive(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: } else {
+! CHECK: fir.do_loop
+! CHECK: }
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+! CHECK-LABEL: func.func @_QPtest_alt_standalone_entry_no_directive(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: } else {
+! CHECK: fir.do_loop
+! CHECK: }
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+! CHECK-LABEL: func.func @_QPtest_after_standalone_entry_no_directive(
+! CHECK-NOT: fir.if
+! CHECK-NOT: omp.
+! CHECK-NOT: fir.do_loop
+! CHECK: %[[AFTER_ENTRY_C77:.*]] = arith.constant 77 : i32
+! CHECK: hlfir.assign %[[AFTER_ENTRY_C77]]
+! CHECK: return
+subroutine test_standalone_entry_no_directive(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ entry test_alt_standalone_entry_no_directive(flag, n, a)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ entry test_after_standalone_entry_no_directive(n, a)
+ a(1) = 77
+end subroutine
+
+! Intervening compiler directives have the same ownership across ENTRY.
+! CHECK-LABEL: func.func @_QPtest_standalone_entry(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: } else {
+! CHECK: fir.do_loop {{.*}} attributes {loopAnnotation = #loop_annotation}
+! CHECK: }
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+! CHECK-LABEL: func.func @_QPtest_alt_standalone_entry(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: } else {
+! CHECK: fir.do_loop {{.*}} attributes {loopAnnotation = #loop_annotation}
+! CHECK: }
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+subroutine test_standalone_entry(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ entry test_alt_standalone_entry(flag, n, a)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ !dir$ unroll 4
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! A statically inapplicable loop variant leaves the following loop after the
+! selected standalone variant, so it is lowered sequentially.
+! CHECK-LABEL: func.func @_QPtest_static_standalone_fallback(
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: omp.barrier
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+subroutine test_static_standalone_fallback(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor("unknown")}: do) &
+ !$omp & otherwise(barrier)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! A statically inapplicable loop variant nested in a parallel region leaves the
+! following loop sequential.
+! CHECK-LABEL: func.func @_QPtest_inapplicable_do_in_parallel(
+! CHECK: omp.parallel
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.do_loop
+! CHECK: omp.terminator
+! CHECK: return
+subroutine test_inapplicable_do_in_parallel(n, a, after)
+ integer :: n, a(n), after, i
+ !$omp parallel num_threads(1) shared(n, a, after)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor("unknown")}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end parallel
+end subroutine
+
+! A lower-ranked loop variant is unreachable after an unguarded standalone
+! variant is selected, so it does not impose loop-only lowering restrictions.
+! CHECK-LABEL: func.func @_QPtest_unselected_do_in_parallel(
+! CHECK: omp.parallel
+! CHECK: omp.barrier
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.do_loop
+! CHECK: omp.terminator
+! CHECK: return
+subroutine test_unselected_do_in_parallel(n, a)
+ integer :: n, a(n), i
+ !$omp parallel num_threads(1) shared(n, a)
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): .true.)}: barrier) &
+ !$omp & when(user={condition(score(1): .true.)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end parallel
+end subroutine
+
+! An unreachable loop variant likewise does not turn a statically selected
+! block variant into a mixed-association metadirective.
+! CHECK-LABEL: func.func @_QPtest_unselected_do_with_block_variant(
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: omp.masked
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK-NOT: fir.do_loop
+! CHECK: omp.terminator
+! CHECK: return
+subroutine test_unselected_do_with_block_variant(n, a)
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp & when(user={condition(score(2): .true.)}: masked) &
+ !$omp & when(user={condition(score(1): .true.)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+! A lower-ranked candidate guarded by the same runtime expression is
+! unreachable: when FLAG is true the higher-ranked BARRIER wins, and when it
+! is false neither guarded candidate matches. Do not emit a dead OpenMP loop.
+! CHECK-LABEL: func.func @_QPtest_unreachable_same_runtime_condition(
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.barrier
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: } else {
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: }
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: return
+subroutine test_unreachable_same_runtime_condition(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): flag)}: barrier) &
+ !$omp & when(user={condition(score(1): flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Parentheses do not make a repeatable condition distinct. The lower-ranked
+! loop remains unreachable and must not be emitted.
+! CHECK-LABEL: func.func @_QPtest_unreachable_parenthesized_condition(
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: }
+! CHECK: fir.do_loop
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: return
+subroutine test_unreachable_parenthesized_condition(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): flag)}: barrier) &
+ !$omp & when(user={condition(score(1): (flag))}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Idempotent AND/OR spelling is normalized after proving that the condition is
+! repeatable.
+! CHECK-LABEL: func.func @_QPtest_unreachable_idempotent_condition(
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: }
+! CHECK: fir.do_loop
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: return
+subroutine test_unreachable_idempotent_condition(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): flag)}: barrier) &
+ !$omp & when(user={condition(score(1): flag .or. flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Calls to an opaque procedure are independent runtime conditions even when
+! their source expressions are identical. Preserve both candidates without
+! relying on clause-expression side effects.
+! CHECK-LABEL: func.func @_QPtest_opaque_runtime_conditions(
+! CHECK: fir.call @_QPmetadirective_runtime_condition
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.barrier
+! CHECK: } else {
+! CHECK: fir.call @_QPmetadirective_runtime_condition
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: } else {
+! CHECK: fir.do_loop
+! CHECK: }
+! CHECK: }
+! CHECK: return
+subroutine test_opaque_runtime_conditions(n, a)
+ integer :: n, a(n), i
+ logical :: metadirective_runtime_condition
+ external :: metadirective_runtime_condition
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): metadirective_runtime_condition())}: barrier) &
+ !$omp & when(user={condition(score(1): metadirective_runtime_condition())}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+module metadirective_condition_helpers
+contains
+ pure logical function metadirective_identity(value)
+ logical, intent(in) :: value
+ metadirective_identity = value
+ end function
+end module
+
+! Procedure calls are conservatively kept as independent runtime conditions
+! because the expression tree does not describe the callee's state.
+! CHECK-LABEL: func.func @_QPtest_pure_runtime_conditions(
+! CHECK: fir.call @_QMmetadirective_condition_helpersPmetadirective_identity
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: } else {
+! CHECK: fir.call @_QMmetadirective_condition_helpersPmetadirective_identity
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.simd
+! CHECK: omp.loop_nest
+! CHECK: } else {
+! CHECK: fir.do_loop
+! CHECK: }
+! CHECK: }
+! CHECK: return
+subroutine test_pure_runtime_conditions(flag, n, a)
+ use metadirective_condition_helpers, only : metadirective_identity
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(score(2): &
+ !$omp & metadirective_identity(flag))}: do) &
+ !$omp & when(user={condition(score(1): &
+ !$omp & metadirective_identity(flag))}: simd) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: } else {
+! CHECK: omp.simd
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_loop(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(simd)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! When the standalone fallback is selected at runtime, the following loop is
+! lowered sequentially in that arm.
+! CHECK-LABEL: func.func @_QPtest_dynamic_standalone_fallback(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: } else {
+! CHECK: omp.barrier
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_standalone_fallback(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(barrier)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! When NOTHING is selected, the following loop is lowered normally.
+! CHECK-LABEL: func.func @_QPtest_dynamic_nothing_fallback(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: } else {
+! CHECK-NOT: omp.
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_nothing_fallback(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Compiler directives preceding the associated loop are processed before it.
+! CHECK-LABEL: func.func @_QPtest_dynamic_unroll_fallback(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.wsloop
+! CHECK: } else {
+! CHECK: fir.do_loop {{.*}} attributes {loopAnnotation = #loop_annotation}
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_unroll_fallback(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ !dir$ unroll 4
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! Each runtime arm must compute its own affected depth and restore temporary
+! loop-index attributes before lowering the next arm.
+! CHECK-LABEL: func.func @_QPtest_dynamic_collapse(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.simd {{.*}}private({{.*}}Ei_private_i32{{.*}}Ej_private_i32
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: hlfir.assign
+! CHECK: } else {
+! CHECK: omp.simd linear(
+! CHECK: omp.loop_nest ({{.*}}) : i32
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_collapse(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n, n), i, j
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: simd collapse(2)) &
+ !$omp & otherwise(simd)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_ordered_depth(
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop {{.*}}private({{.*}}Ei_private_i32{{.*}}Ej_private_i32
+! CHECK: omp.loop_nest ({{.*}}) : i32
+! CHECK: fir.do_loop
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_ordered_depth(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do ordered(2)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_schedule(
+! CHECK: omp.wsloop schedule(static)
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_schedule(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do schedule(static)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_collapse(
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_collapse(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do collapse(2)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_safelen(
+! CHECK: omp.simd {{.*}}safelen(4)
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_safelen(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd safelen(4)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+! SIMD collapse makes every affected index lastprivate in OpenMP 5.2. Check
+! that lowering copies both private values back to their source bindings.
+! CHECK-LABEL: func.func @_QPtest_simd_collapse_lastprivate(
+! CHECK: %[[I:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEi"}
+! CHECK: %[[J:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEj"}
+! CHECK: omp.simd {{.*}}private({{.*}}Ei_private_i32{{.*}}Ej_private_i32
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: fir.if
+! CHECK: hlfir.assign {{.*}} to %[[I]]#0
+! CHECK: hlfir.assign {{.*}} to %[[J]]#0
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_simd_collapse_lastprivate(n, a)
+ integer :: n, a(n, n), i, j
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_block_nested_do(
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop {{.*}}private({{.*}}Ei_private_i32
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: return
+subroutine test_block_nested_do(n, a)
+ integer :: n, a(n), i
+ block
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ end block
+end subroutine
+
+! A standalone metadirective's selected DO trait remains active while its
+! sibling loop is traversed. The inner construct selector therefore chooses
+! NOTHING, leaving the inner loop sequential.
+! CHECK-LABEL: func.func @_QPtest_standalone_selected_construct_context(
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK-NOT: omp.wsloop
+! CHECK: fir.do_loop
+! CHECK-NOT: omp.wsloop
+! CHECK: hlfir.assign
+! CHECK-NOT: omp.wsloop
+! CHECK: return
+subroutine test_standalone_selected_construct_context(n, a)
+ integer :: n, a(n), i, j
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: do) &
+ !$omp& otherwise(nothing)
+ do i = 1, n
+ !$omp metadirective &
+ !$omp& when(construct={do}: nothing) &
+ !$omp& otherwise(do)
+ do j = 1, 2
+ a(i) = j
+ end do
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index cdfbddd4151a0..67311133097a0 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -425,11 +425,43 @@ subroutine test_dynamic_user_match_any_static_score(flag)
#endif
end subroutine
-! The explicit directive variant wins this tie over the earlier implicit
-! nothing candidate.
+! An implicit NOTHING keeps its dynamic condition score under match_any as
+! well. The unguarded match_any path has only the static score, so TASKWAIT wins
+! when FLAG is false.
+! CHECK-LABEL: func.func @_QPtest_dynamic_match_any_implicit_nothing_score(
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK: %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK: %[[COND:.*]] = fir.convert %[[LOAD]]
+! CHECK: fir.if %[[COND]] {
+! CHECK-NOT: omp.
+! CHECK: } else {
+! CHECK: omp.taskwait
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_match_any_implicit_nothing_score(flag)
+ logical, intent(in) :: flag
+ !$omp metadirective &
+ !$omp & when(implementation={extension(match_any), vendor(llvm)}, user={condition(score(100): flag)}:) &
+ !$omp & when(user={condition(score(10): .true.)}: taskwait) &
+#ifdef OMP_52
+ !$omp & otherwise(nothing)
+#else
+ !$omp & default(nothing)
+#endif
+end subroutine
+
+! When FLAG is true, the explicit candidate's selector is a strict subset of
+! the implicit candidate's selector and therefore has a lower score. When FLAG
+! is false, only the explicit candidate is compatible.
! CHECK-LABEL: func.func @_QPtest_dynamic_implicit_nothing_tie_break(
-! CHECK-NOT: fir.if
-! CHECK: omp.barrier
+! CHECK: %[[LOAD:.*]] = fir.load {{.*}} : !fir.ref<!fir.logical<4>>
+! CHECK: %[[COND:.*]] = fir.convert %[[LOAD]]
+! CHECK: fir.if %[[COND]] {
+! CHECK-NOT: omp.
+! CHECK: } else {
+! CHECK: omp.barrier
+! CHECK: }
! CHECK: return
subroutine test_dynamic_implicit_nothing_tie_break(flag)
logical, intent(in) :: flag
>From 47149099ab117d0b08db07551e596cd72595334b Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 3 Aug 2026 17:03:10 -0500
Subject: [PATCH 2/2] Consolidate metadirective lowering TODO tests
---
.../metadirective-block-data-environment.f90 | 71 +++++++++++-
...adirective-block-eager-default-private.f90 | 17 ---
...etadirective-block-eager-privatization.f90 | 17 ---
...irective-block-host-association-clause.f90 | 37 ------
.../Todo/metadirective-loop-associate-iv.f90 | 36 ------
.../Todo/metadirective-loop-body-variant.f90 | 18 ---
.../metadirective-loop-data-environment.f90 | 105 +++++++++++++++++-
...metadirective-loop-data-sharing-clause.f90 | 17 ---
...metadirective-loop-eager-privatization.f90 | 18 ---
...ective-loop-enclosing-data-environment.f90 | 60 ----------
.../metadirective-loop-iteration-variable.f90 | 74 ++++++++++++
...adirective-loop-pointer-allocatable-iv.f90 | 35 ------
...tadirective-loop-unsupported-directive.f90 | 15 ---
...irective-loop-unsupported-replacements.f90 | 52 +++++++++
.../OpenMP/Todo/metadirective-target-loop.f90 | 15 ---
15 files changed, 295 insertions(+), 292 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-iteration-variable.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-replacements.f90
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
index b422d298241c4..4c5124add9fec 100644
--- a/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-block-data-environment.f90
@@ -1,6 +1,7 @@
-! A metadirective block replacement cannot yet reconstruct implicit
-! data-sharing attributes. Reject regions that would otherwise lose a
-! sequential loop IV or an implicit task capture.
+! Metadirective block replacements cannot yet reconstruct every implicit or
+! explicit data-sharing relationship. Reject unsupported data environments and
+! clauses requiring variant-local host associations in both delayed and eager
+! privatization modes.
! RUN: split-file %s %t
! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
@@ -21,8 +22,31 @@
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
! RUN: -o - %t/task-selected-shared.f90 \
! RUN: | FileCheck --check-prefix=SHARED %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/firstprivate.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false \
+! RUN: -o - %t/firstprivate.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/copyin.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false \
+! RUN: -o - %t/copyin.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false \
+! RUN: -o - %t/eager-default-private.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false \
+! RUN: -o - %t/eager-private.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=HOST-ASSOC %s
! TODO: not yet implemented: data-environment construct in METADIRECTIVE variant
+! HOST-ASSOC: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
! LOCAL-LABEL: func.func @_QPtask_block_variant_with_local_storage
! LOCAL: omp.task {
@@ -130,3 +154,44 @@ subroutine task_block_variant_with_default_shared(x)
x = x + 1
!$omp end metadirective
end subroutine
+
+!--- firstprivate.f90
+subroutine firstprivate_block_variant(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel firstprivate(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+!--- copyin.f90
+subroutine copyin_block_variant()
+ integer, save :: x
+ !$omp threadprivate(x)
+ !$omp begin metadirective &
+ !$omp& when(implementation={vendor(llvm)}: parallel copyin(x)) &
+ !$omp& otherwise(nothing)
+ x = x + 1
+ !$omp end metadirective
+end subroutine
+
+!--- eager-default-private.f90
+subroutine test_block_eager_default_private(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel default(private)) &
+ !$omp & otherwise(nothing)
+ x = 1
+ !$omp end metadirective
+end subroutine
+
+!--- eager-private.f90
+subroutine test_block_eager_privatization(x)
+ integer :: x
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel private(x)) &
+ !$omp & otherwise(nothing)
+ x = 1
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
deleted file mode 100644
index 5843f5293c95b..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-default-private.f90
+++ /dev/null
@@ -1,17 +0,0 @@
-! DEFAULT(PRIVATE) still requires variant-local host associations before a
-! metadirective block variant can use eager privatization.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
-! RUN: | FileCheck %s
-
-! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
-
-subroutine test_block_eager_default_private(x)
- integer :: x
- !$omp begin metadirective &
- !$omp & when(implementation={vendor(llvm)}: parallel default(private)) &
- !$omp & otherwise(nothing)
- x = 1
- !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
deleted file mode 100644
index 73ad5ca292ff8..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-block-eager-privatization.f90
+++ /dev/null
@@ -1,17 +0,0 @@
-! Metadirective replacements with privatizing clauses need variant-local host
-! associations before they can use eager privatization.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
-! RUN: | FileCheck %s
-
-! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
-
-subroutine test_block_eager_privatization(x)
- integer :: x
- !$omp begin metadirective &
- !$omp & when(implementation={vendor(llvm)}: parallel private(x)) &
- !$omp & otherwise(nothing)
- x = 1
- !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
deleted file mode 100644
index 8922274ef9727..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-block-host-association-clause.f90
+++ /dev/null
@@ -1,37 +0,0 @@
-! Metadirective block variants do not have variant-local host-association
-! symbols. Reject clauses that require them in both delayed and eager modes.
-
-! RUN: split-file %s %t
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -o - %t/firstprivate.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -mmlir --enable-delayed-privatization=false -o - \
-! RUN: %t/firstprivate.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -o - %t/copyin.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -mmlir --enable-delayed-privatization=false -o - \
-! RUN: %t/copyin.f90 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: METADIRECTIVE block variant with a clause requiring variant-local host association
-
-!--- firstprivate.f90
-subroutine firstprivate_block_variant(x)
- integer :: x
- !$omp begin metadirective &
- !$omp& when(implementation={vendor(llvm)}: parallel firstprivate(x)) &
- !$omp& otherwise(nothing)
- x = x + 1
- !$omp end metadirective
-end subroutine
-
-!--- copyin.f90
-subroutine copyin_block_variant()
- integer, save :: x
- !$omp threadprivate(x)
- !$omp begin metadirective &
- !$omp& when(implementation={vendor(llvm)}: parallel copyin(x)) &
- !$omp& otherwise(nothing)
- x = x + 1
- !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
deleted file mode 100644
index e2da85c14179c..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-associate-iv.f90
+++ /dev/null
@@ -1,36 +0,0 @@
-! ASSOCIATE-name loop iteration variables require construct-scoped name
-! resolution for private and lastprivate bindings.
-
-! RUN: split-file %s %t
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/do.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/simd-collapse.f90 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: ASSOCIATE name loop iteration variable in loop-associated METADIRECTIVE variant
-
-!--- do.f90
-subroutine test_do(n, a)
- integer :: n, a(n), source_i
- associate(i => source_i)
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
- end associate
-end subroutine
-
-!--- simd-collapse.f90
-subroutine test_simd_collapse(n, a)
- integer :: n, a(n, n), source_i, j
- associate(i => source_i)
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
- !$omp & otherwise(nothing)
- do i = 1, n
- do j = 1, n
- a(j, i) = i + j
- end do
- end do
- end associate
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
deleted file mode 100644
index 484fddde82739..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-body-variant.f90
+++ /dev/null
@@ -1,18 +0,0 @@
-! A block-associated fallback consumes the begin/end region differently from an
-! ordinary loop variant and is not supported yet.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: METADIRECTIVE with both block- and loop-associated variants
-
-subroutine test_single_fallback(flag, n, a)
- logical, intent(in) :: flag
- integer :: n, a(n), i
- !$omp begin metadirective &
- !$omp & when(user={condition(flag)}: do) &
- !$omp & otherwise(single)
- do i = 1, n
- a(i) = i
- end do
- !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
index 1b4d073e478cc..91e019eb0a054 100644
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-environment.f90
@@ -1,10 +1,34 @@
-! A selected variant that creates a data environment needs its implicit data
-! sharing attributes reconstructed during lowering.
+! Loop-associated metadirective variants cannot yet reconstruct variant-local
+! data-sharing relationships. Cover selected data environments, explicit
+! data-sharing clauses, enclosing data environments, and eager privatization.
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/parallel-do.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=DATA-ENV %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/private.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=DATA-SHARING %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -mmlir --enable-delayed-privatization=false \
+! RUN: -o - %t/eager.f90 2>&1 | FileCheck --check-prefix=EAGER %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/enclosing-static.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=ENCLOSING %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/enclosing-dynamic.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=ENCLOSING %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/selected-metadirective.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=SELECTED %s
-! CHECK: not yet implemented: data-environment construct in loop-associated METADIRECTIVE variant
+! DATA-ENV: not yet implemented: data-environment construct in loop-associated METADIRECTIVE variant
+! DATA-SHARING: not yet implemented: data-sharing clause in loop-associated METADIRECTIVE variant
+! EAGER: not yet implemented: loop-associated METADIRECTIVE with eager privatization
+! ENCLOSING: not yet implemented: loop-associated METADIRECTIVE nested in an OpenMP data environment
+! SELECTED: not yet implemented: data-environment construct in METADIRECTIVE variant
+!--- parallel-do.f90
subroutine test_parallel_do(n, a)
integer :: n, a(n), i
!$omp metadirective &
@@ -14,3 +38,76 @@ subroutine test_parallel_do(n, a)
a(i) = i
end do
end subroutine
+
+!--- private.f90
+subroutine test_private(n, a)
+ integer :: n, a(n), i, x
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do private(x)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ x = i
+ a(i) = x
+ end do
+end subroutine
+
+!--- eager.f90
+subroutine test_eager_privatization(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+!--- enclosing-static.f90
+subroutine test_static_do_in_parallel(n, a, after)
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp parallel num_threads(1) shared(n, a, after, i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end parallel
+end subroutine
+
+!--- enclosing-dynamic.f90
+subroutine test_do_in_parallel(flag, n, a, after)
+ logical, intent(in) :: flag
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp parallel num_threads(1) shared(flag, n, a, after, i)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end parallel
+end subroutine
+
+!--- selected-metadirective.f90
+subroutine test_do_in_selected_parallel(flag, n, a, after)
+ logical, intent(in) :: flag
+ integer :: n, a(n), after, i
+ i = 0
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: &
+ !$omp & parallel num_threads(1) shared(flag, n, a, after, i)) &
+ !$omp & otherwise(nothing)
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ after = i
+ !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
deleted file mode 100644
index dea5f1c28757c..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-data-sharing-clause.f90
+++ /dev/null
@@ -1,17 +0,0 @@
-! Data-sharing clauses on selected loop variants need their symbol attributes
-! reconstructed during lowering.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: data-sharing clause in loop-associated METADIRECTIVE variant
-
-subroutine test_private(n, a)
- integer :: n, a(n), i, x
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do private(x)) &
- !$omp & otherwise(nothing)
- do i = 1, n
- x = i
- a(i) = x
- end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
deleted file mode 100644
index e1a6acec74bf3..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-eager-privatization.f90
+++ /dev/null
@@ -1,18 +0,0 @@
-! Loop-associated metadirective variants currently require delayed
-! privatization.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
-! RUN: -mmlir --enable-delayed-privatization=false -o - %s 2>&1 \
-! RUN: | FileCheck %s
-
-! CHECK: not yet implemented: loop-associated METADIRECTIVE with eager privatization
-
-subroutine test_eager_privatization(n, a)
- integer :: n, a(n), i
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
deleted file mode 100644
index 76ebca8b3fd0f..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-enclosing-data-environment.f90
+++ /dev/null
@@ -1,60 +0,0 @@
-! A selectable loop variant needs a variant-local loop-IV binding before it can
-! be nested in another OpenMP data environment.
-
-! RUN: split-file %s %t
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/static.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/dynamic.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/selected-metadirective.f90 2>&1 | FileCheck --check-prefix=SELECTED %s
-
-! CHECK: not yet implemented: loop-associated METADIRECTIVE nested in an OpenMP data environment
-! SELECTED: not yet implemented: data-environment construct in METADIRECTIVE variant
-
-!--- static.f90
-subroutine test_static_do_in_parallel(n, a, after)
- integer :: n, a(n), after, i
- i = 0
- !$omp parallel num_threads(1) shared(n, a, after, i)
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
- after = i
- !$omp end parallel
-end subroutine
-
-!--- dynamic.f90
-subroutine test_do_in_parallel(flag, n, a, after)
- logical, intent(in) :: flag
- integer :: n, a(n), after, i
- i = 0
- !$omp parallel num_threads(1) shared(flag, n, a, after, i)
- !$omp metadirective &
- !$omp & when(user={condition(flag)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
- after = i
- !$omp end parallel
-end subroutine
-
-!--- selected-metadirective.f90
-subroutine test_do_in_selected_parallel(flag, n, a, after)
- logical, intent(in) :: flag
- integer :: n, a(n), after, i
- i = 0
- !$omp begin metadirective &
- !$omp & when(implementation={vendor(llvm)}: &
- !$omp & parallel num_threads(1) shared(flag, n, a, after, i)) &
- !$omp & otherwise(nothing)
- !$omp metadirective &
- !$omp & when(user={condition(flag)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
- after = i
- !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-iteration-variable.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-iteration-variable.f90
new file mode 100644
index 0000000000000..836ef89026c40
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-iteration-variable.f90
@@ -0,0 +1,74 @@
+! Some loop iteration variables require construct-scoped name resolution for
+! the private, linear, or lastprivate bindings of a selected loop variant.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/associate-do.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=ASSOCIATE %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/associate-simd-collapse.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=ASSOCIATE %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/allocatable.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=INDIRECT %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/pointer.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=INDIRECT %s
+
+! ASSOCIATE: not yet implemented: ASSOCIATE name loop iteration variable in loop-associated METADIRECTIVE variant
+! INDIRECT: not yet implemented: POINTER or ALLOCATABLE loop iteration variable in loop-associated METADIRECTIVE variant
+
+!--- associate-do.f90
+subroutine test_do(n, a)
+ integer :: n, a(n), source_i
+ associate(i => source_i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ end associate
+end subroutine
+
+!--- associate-simd-collapse.f90
+subroutine test_simd_collapse(n, a)
+ integer :: n, a(n, n), source_i, j
+ associate(i => source_i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ do j = 1, n
+ a(j, i) = i + j
+ end do
+ end do
+ end associate
+end subroutine
+
+!--- allocatable.f90
+subroutine test_allocatable_iv(n, a)
+ integer :: n, a(n)
+ integer, allocatable :: i
+ allocate(i)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+!--- pointer.f90
+subroutine test_pointer_iv(n, a)
+ integer :: n, a(n)
+ integer, target :: target
+ integer, pointer :: i
+ i => target
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
deleted file mode 100644
index 6b7aafd9283f8..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-pointer-allocatable-iv.f90
+++ /dev/null
@@ -1,35 +0,0 @@
-! POINTER and ALLOCATABLE loop iteration variables require construct-scoped
-! name resolution.
-
-! RUN: split-file %s %t
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/allocatable.f90 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %t/pointer.f90 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: POINTER or ALLOCATABLE loop iteration variable in loop-associated METADIRECTIVE variant
-
-!--- allocatable.f90
-subroutine test_allocatable_iv(n, a)
- integer :: n, a(n)
- integer, allocatable :: i
- allocate(i)
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
-end subroutine
-
-!--- pointer.f90
-subroutine test_pointer_iv(n, a)
- integer :: n, a(n)
- integer, target :: target
- integer, pointer :: i
- i => target
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: do) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
deleted file mode 100644
index 235eba0ac5c16..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-directive.f90
+++ /dev/null
@@ -1,15 +0,0 @@
-! Part 2 supports DO, SIMD, and DO SIMD loop variants only.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: loop-associated METADIRECTIVE variant other than DO, SIMD, or DO SIMD
-
-subroutine test_loop(n, a)
- integer :: n, a(n), i
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: loop bind(thread)) &
- !$omp & otherwise(nothing)
- do i = 1, n
- a(i) = i
- end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-replacements.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-replacements.f90
new file mode 100644
index 0000000000000..a8439b8b4bbfe
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop-unsupported-replacements.f90
@@ -0,0 +1,52 @@
+! Part 2 supports ordinary DO, SIMD, and DO SIMD loop replacement arms. Keep
+! unsupported association mixes, directives, and target host-evaluation paths
+! diagnosed until their lowering is implemented.
+
+! RUN: split-file %s %t
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/mixed-association.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=MIXED %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/loop-directive.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=DIRECTIVE %s
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 \
+! RUN: -o - %t/target-loop.f90 2>&1 \
+! RUN: | FileCheck --check-prefix=TARGET %s
+
+! MIXED: not yet implemented: METADIRECTIVE with both block- and loop-associated variants
+! DIRECTIVE: not yet implemented: loop-associated METADIRECTIVE variant other than DO, SIMD, or DO SIMD
+! TARGET: not yet implemented: TARGET construct selected by METADIRECTIVE (host-eval)
+
+!--- mixed-association.f90
+subroutine test_single_fallback(flag, n, a)
+ logical, intent(in) :: flag
+ integer :: n, a(n), i
+ !$omp begin metadirective &
+ !$omp & when(user={condition(flag)}: do) &
+ !$omp & otherwise(single)
+ do i = 1, n
+ a(i) = i
+ end do
+ !$omp end metadirective
+end subroutine
+
+!--- loop-directive.f90
+subroutine test_loop(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: loop bind(thread)) &
+ !$omp & otherwise(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+end subroutine
+
+!--- target-loop.f90
+subroutine test_target_loop()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: target teams distribute parallel do) &
+ !$omp & otherwise(nothing)
+ do i = 1, 100
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
deleted file mode 100644
index 010864eb14f35..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
+++ /dev/null
@@ -1,15 +0,0 @@
-! Test that a metadirective variant resolving to a target construct
-! correctly reports a TODO (host-eval support needed).
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: TARGET construct selected by METADIRECTIVE (host-eval)
-
-subroutine test_target_loop()
- integer :: i
- !$omp metadirective &
- !$omp & when(implementation={vendor(llvm)}: target teams distribute parallel do) &
- !$omp & otherwise(nothing)
- do i = 1, 100
- end do
-end subroutine
More information about the flang-commits
mailing list