[flang-commits] [flang] [flang][OpenMP] Support lowering of metadirective (part 3) (PR #208081)
via flang-commits
flang-commits at lists.llvm.org
Tue Jul 7 12:51:34 PDT 2026
https://github.com/chichunchen created https://github.com/llvm/llvm-project/pull/208081
Enable lowering of metadirectives that resolve to a loop-associated
variant such as `do`, `simd`, `parallel do`, and `do simd`. The
associated DO construct is made available to the selected variant so the
existing OpenMP loop lowering can process it. A standalone metadirective
uses the following sibling DO, and a begin/end metadirective uses the
first substantive evaluation nested in its block.
For example, when the `vendor(llvm)` selector matches, the `parallel do`
variant is lowered together with the DO loop that follows it:
```fortran
!$omp metadirective when(implementation={vendor(llvm)}: parallel do)
do i = 1, n
y(i) = a*x(i) + y(i)
end do
```
```mlir
omp.parallel {
omp.wsloop {
omp.loop_nest (%iv) = (%lb) to (%ub) ... { ... }
}
}
```
Because semantic analysis cannot know which variant is selected,
induction-variable data-sharing attributes are marked during lowering and
scoped to the selected variant, so DSA state does not leak between
alternatives. A variant that opens a parallel, task, or teams region also
privatizes the indices of its nested sequential loops. `collapse(n)` and
`ordered(n)` use the normal affected-loop-depth calculation, and SIMD
variants are recognized from their leaf constructs.
Dynamic selection preserves the associated DO evaluation in every arm, so
a runtime `condition(flag)` that may pick a loop-associated variant or a
standalone fallback does not drop the loop body from the fallback arm.
Lowering assumes the associated loop nest is a canonical DO nest that is
deep enough for any `collapse(n)` or `ordered(n)`, and asserts these
invariants (for example in markMetadirectiveLoopIVs) rather than
re-diagnosing them. The companion metadirective loop-nest semantic checks
reject a DO WHILE, a missing or non-canonical loop, and an oversized
`collapse`/`ordered`, so a malformed nest does not reach lowering in a
well-formed program.
Part of the feature work for #188820. The metadirective loop-nest
semantic checks are a companion patch
([#207088](https://github.com/llvm/llvm-project/pull/207088)).
Assisted with Copilot and GPT-5.
>From 136b8b574ffb72b779c11837557e62845e3e897f Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 29 Jun 2026 14:49:44 -0500
Subject: [PATCH] [flang][OpenMP] Support lowering of metadirective (part 3)
Enable lowering of metadirectives that resolve to a loop-associated
variant such as `do`, `simd`, `parallel do`, and `do simd`. The
associated DO construct is made available to the selected variant so the
existing OpenMP loop lowering can process it. A standalone metadirective
uses the following sibling DO, and a begin/end metadirective uses the
first substantive evaluation nested in its block.
For example, when the `vendor(llvm)` selector matches, the `parallel do`
variant is lowered together with the DO loop that follows it:
```fortran
!$omp metadirective when(implementation={vendor(llvm)}: parallel do)
do i = 1, n
y(i) = a*x(i) + y(i)
end do
```
```mlir
omp.parallel {
omp.wsloop {
omp.loop_nest (%iv) = (%lb) to (%ub) ... { ... }
}
}
```
Because semantic analysis cannot know which variant is selected,
induction-variable data-sharing attributes are marked during lowering and
scoped to the selected variant, so DSA state does not leak between
alternatives. A variant that opens a parallel, task, or teams region also
privatizes the indices of its nested sequential loops. `collapse(n)` and
`ordered(n)` use the normal affected-loop-depth calculation, and SIMD
variants are recognized from their leaf constructs.
Dynamic selection preserves the associated DO evaluation in every arm, so
a runtime `condition(flag)` that may pick a loop-associated variant or a
standalone fallback does not drop the loop body from the fallback arm.
Lowering assumes the associated loop nest is a canonical DO nest that is
deep enough for any `collapse(n)` or `ordered(n)`, and asserts these
invariants (for example in markMetadirectiveLoopIVs) rather than
re-diagnosing them. The companion metadirective loop-nest semantic checks
reject a DO WHILE, a missing or non-canonical loop, and an oversized
`collapse`/`ordered`, so a malformed nest does not reach lowering in a
well-formed program.
Part of the feature work for #188820. The metadirective loop-nest
semantic checks are a companion patch
([#207088](https://github.com/llvm/llvm-project/pull/207088)).
Assisted with Copilot and GPT-5.
---
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 90 +++-
flang/lib/Lower/OpenMP/OpenMP.cpp | 251 ++++++++-
flang/lib/Lower/OpenMP/Utils.cpp | 32 +-
flang/lib/Lower/OpenMP/Utils.h | 6 +
.../Lower/OpenMP/Todo/metadirective-loop.f90 | 12 -
.../OpenMP/Todo/metadirective-module-loop.f90 | 7 +
.../OpenMP/Todo/metadirective-target-loop.f90 | 15 +
.../test/Lower/OpenMP/metadirective-loop.f90 | 476 ++++++++++++++++++
8 files changed, 863 insertions(+), 26 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
create mode 100644 flang/test/Lower/OpenMP/metadirective-loop.f90
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index c97d054e1210b..31f832ca29095 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -41,8 +41,46 @@ bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
const semantics::Symbol *symbol, lower::pft::Evaluation &eval) const {
return eval.visit(common::visitors{
[&](const parser::OpenMPConstruct &functionParserNode) {
+ if (symDefMap.count(symbol) &&
+ symDefMap.at(symbol) == ConstructPtr(&functionParserNode))
+ return true;
+
+ // For metadirectives on standalone or delimited constructs, the
+ // construct itself does not introduce privatization. As a result,
+ // symbols from nested (spliced or block-owned) DO evaluations may be
+ // associated with a null `OpenMPConstruct` pointer. This is expected
+ // and should be accepted.
+ bool isMetadirective =
+ std::holds_alternative<parser::OmpDelimitedMetadirectiveDirective>(
+ functionParserNode.u);
+ if (const auto *standalone =
+ std::get_if<parser::OpenMPStandaloneConstruct>(
+ &functionParserNode.u)) {
+ isMetadirective =
+ isMetadirective ||
+ std::holds_alternative<parser::OmpMetadirectiveDirective>(
+ standalone->u);
+ }
+ if (isMetadirective)
+ return symDefMap.count(symbol) &&
+ symDefMap.at(symbol) ==
+ ConstructPtr(
+ static_cast<const parser::OpenMPConstruct *>(nullptr));
+ return false;
+ },
+ [&](const parser::OpenMPDeclarativeConstruct &functionParserNode) {
+ // Metadirective variants are resolved during lowering, so the eval
+ // is an `OpenMPDeclarativeConstruct` rather than an `OpenMPConstruct`.
+ // The visitor does not track declarative constructs, so symbols
+ // referenced at the top level of the eval (e.g. loop IVs from a
+ // spliced DO) are mapped to a null `OpenMPConstruct` pointer.
+ if (!std::holds_alternative<parser::OmpMetadirectiveDirective>(
+ functionParserNode.u))
+ return false;
return symDefMap.count(symbol) &&
- symDefMap.at(symbol) == ConstructPtr(&functionParserNode);
+ symDefMap.at(symbol) ==
+ ConstructPtr(
+ static_cast<const parser::OpenMPConstruct *>(nullptr));
},
[](const auto &functionParserNode) { return false; }});
}
@@ -77,6 +115,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(lower::AbstractConverter &converter,
@@ -220,8 +266,27 @@ void DataSharingProcessor::copyFirstPrivateSymbol(
void DataSharingProcessor::copyLastPrivateSymbol(
const semantics::Symbol *sym, mlir::OpBuilder::InsertPoint *lastPrivIP) {
- if (sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+ if (!sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+ return;
+
+ if (sym->has<semantics::HostAssocDetails>()) {
converter.copyHostAssociateVar(*sym, lastPrivIP, /*hostIsSource=*/false);
+ return;
+ }
+
+ // 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(
@@ -542,6 +607,19 @@ void DataSharingProcessor::collectPrivatizedSymbols(
llvm::SetVector<const semantics::Scope *> clauseScopes;
(void)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) && !clauseScopes.empty()) {
+ const semantics::Scope *scope = *clauseScopes.begin();
+ while (scope->kind() != semantics::Scope::Kind::Global) {
+ clauseScopes.insert(scope);
+ scope = &scope->parent();
+ }
+ }
+
for (const auto *sym : allSymbols) {
if (semantics::omp::IsPrivatizable(*sym) &&
!symbolsInNestedRegions.contains(sym) &&
@@ -571,6 +649,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 bfb63c36991be..5264e5b9a2790 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -43,6 +43,7 @@
#include "flang/Parser/parse-tree.h"
#include "flang/Parser/tools.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"
@@ -5088,6 +5089,202 @@ struct MetadirectiveCandidate {
};
} // 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 lower::pft::Evaluation *
+spliceAssociatedDoEval(lower::pft::Evaluation &eval) {
+ if (eval.hasNestedEvaluations()) {
+ auto nestedIt =
+ llvm::find_if(eval.getNestedEvaluations(), [](auto &nested) {
+ return !nested.isEndStmt() &&
+ !nested.template getIf<parser::CompilerDirective>();
+ });
+ 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 loopIt = std::next(metaIt);
+ while (loopIt != parentList->end() &&
+ (loopIt->isEndStmt() || loopIt->getIf<parser::CompilerDirective>()))
+ ++loopIt;
+
+ if (loopIt == parentList->end() || !loopIt->getIf<parser::DoConstruct>())
+ return nullptr;
+
+ eval.evaluationList->splice(eval.evaluationList->end(), *parentList, loopIt);
+ return &eval.getNestedEvaluations().back();
+}
+
+static bool hasLoopAssociatedDirective(const ConstructQueue &queue) {
+ return llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::getDirectiveAssociation(item.id) ==
+ llvm::omp::Association::LoopNest;
+ });
+}
+
+static bool consumesAssociatedEvaluation(const ConstructQueue &queue) {
+ return llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::getDirectiveAssociation(item.id) !=
+ llvm::omp::Association::None;
+ });
+}
+
+/// Clear all existing DSA flags on \p sym, then set PreDetermined + \p dsa.
+static void applySymbolDSA(semantics::Symbol &sym,
+ semantics::Symbol::Flag dsa) {
+ using Symbol = semantics::Symbol;
+ semantics::SetSymbolDSA(sym,
+ Symbol::Flags{Symbol::Flag::OmpPreDetermined, dsa});
+}
+
+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());
+ applySymbolDSA(sym, dsa);
+ }
+
+private:
+ llvm::SmallVector<std::pair<semantics::Symbol *, semantics::Symbol::Flags>, 4>
+ savedFlags;
+};
+
+/// Mark the iteration variable of every sequential DO loop nested in a
+/// metadirective variant's generated region as private, mirroring
+/// `OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct`, which
+/// cannot run because the variant is only resolved during lowering.
+static void markSequentialLoopIVs(
+ lower::pft::Evaluation &eval, SymbolDSAGuard &dsaGuard,
+ const llvm::SmallPtrSetImpl<const semantics::Symbol *> &clauseSyms,
+ unsigned version) {
+ using Symbol = semantics::Symbol;
+ if (!eval.hasNestedEvaluations())
+ return;
+ for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) {
+ // A nested construct with its own data environment (parallel, task, teams,
+ // or target) owns the loops it contains, so do not descend into it. A
+ // nested unresolved metadirective is not special-cased: privatizing its
+ // sibling loop index here too is benign for a loop index.
+ 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;
+ }
+ // Skip an index that already has a DSA: predetermined (the variant's own
+ // associated loops and nested loop-construct indices) or clause-named.
+ if (nested.getIf<parser::DoConstruct>())
+ if (Symbol *sym = getIterationVariableSymbol(nested))
+ if (!sym->test(Symbol::Flag::OmpPreDetermined) &&
+ !clauseSyms.contains(sym))
+ dsaGuard.setSymbolDSA(*sym, Symbol::Flag::OmpPrivate);
+ markSequentialLoopIVs(nested, dsaGuard, clauseSyms, version);
+ }
+}
+
+/// 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 false if the
+/// associated loop nest is shallower than the variant's COLLAPSE/ORDERED
+/// requires, leaving the diagnostic to the caller.
+static bool
+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 true;
+
+ int64_t affectedDepth = *depth.value;
+ bool isSimdVariant =
+ llvm::is_contained(llvm::omp::getLeafConstructsOrSelf(spec.DirId()),
+ llvm::omp::Directive::OMPD_simd);
+ 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,
+ // returning false so the caller handles it instead of asserting in
+ // `getNestedDoConstruct`.
+ const parser::DoConstruct *doConstruct =
+ doEval ? doEval->getIf<parser::DoConstruct>() : nullptr;
+ if (!doConstruct)
+ return false;
+ // 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 by the metadirective loop-nest semantic checks
+ // (check-omp-variant.cpp), so it never reaches lowering in a well-formed
+ // program.
+ assert((doConstruct->IsDoNormal() || doConstruct->IsDoConcurrent()) &&
+ "metadirective affected loop must be a canonical DO loop");
+ if (semantics::Symbol *sym = getIterationVariableSymbol(*doEval))
+ dsaGuard.setSymbolDSA(*sym, ivDSA);
+ if (level + 1 < affectedDepth)
+ doEval = tryGetNestedDoConstruct(*doEval);
+ }
+
+ // A variant that generates a parallel, task, or (>= 5.2) teams region also
+ // privatizes the index of every nested sequential loop. `doEval` is the
+ // innermost associated loop, so its body holds those loops.
+ bool variantGeneratesParallelRegion =
+ llvm::omp::allParallelSet.test(spec.DirId()) ||
+ llvm::omp::taskGeneratingSet.test(spec.DirId()) ||
+ (semaCtx.langOptions().OpenMPVersion >= 52 &&
+ llvm::omp::allTeamsSet.test(spec.DirId()));
+ if (variantGeneratesParallelRegion) {
+ // Indices named in the variant's own clauses already have an explicit DSA;
+ // exclude them so the marking below does not override it.
+ llvm::SmallPtrSet<const semantics::Symbol *, 4> clauseSyms;
+ for (const parser::OmpClause &clause : spec.Clauses().v)
+ if (const parser::OmpObjectList *objects =
+ parser::omp::GetOmpObjectList(clause))
+ for (const parser::OmpObject &object : objects->v)
+ if (const semantics::Symbol *sym = makeObject(object, semaCtx).sym())
+ clauseSyms.insert(sym);
+ markSequentialLoopIVs(*doEval, dsaGuard, clauseSyms,
+ semaCtx.langOptions().OpenMPVersion);
+ }
+ return true;
+}
+
static void genMetadirective(lower::AbstractConverter &converter,
lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -5261,23 +5458,41 @@ static void genMetadirective(lower::AbstractConverter &converter,
}
}
+ auto makeVariantQueue = [&](const parser::OmpDirectiveSpecification &spec) {
+ List<Clause> variantClauses = makeClauses(spec.Clauses(), semaCtx);
+ return ConstructQueue{
+ buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
+ eval, spec.source, spec.DirId(), variantClauses)};
+ };
+
+ bool hasLoopAssociatedCandidate = false;
+ for (const MetadirectiveCandidate &candidate : candidates) {
+ if (candidate.spec &&
+ hasLoopAssociatedDirective(makeVariantQueue(*candidate.spec))) {
+ hasLoopAssociatedCandidate = true;
+ break;
+ }
+ }
+ if (!hasLoopAssociatedCandidate && fallback)
+ hasLoopAssociatedCandidate =
+ hasLoopAssociatedDirective(makeVariantQueue(*fallback));
+ if (hasLoopAssociatedCandidate)
+ (void)spliceAssociatedDoEval(eval);
+
// Lower a single resolved candidate.
auto genVariant = [&](const parser::OmpDirectiveSpecification *spec) {
if (!spec) {
genNestedEvaluations(converter, eval);
return;
}
- List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
mlir::Location variantLoc = converter.genLocation(spec->source);
- ConstructQueue queue{
- buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
- eval, spec->source, spec->DirId(), variantClauses)};
+ ConstructQueue queue = makeVariantQueue(*spec);
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) {
@@ -5289,8 +5504,25 @@ static void genMetadirective(lower::AbstractConverter &converter,
TODO(variantLoc, "declarative METADIRECTIVE variant");
}
+ bool hasLoopAssociation = hasLoopAssociatedDirective(queue);
+ if (hasLoopAssociation) {
+ lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
+ if (!loopEval)
+ TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
+ SymbolDSAGuard dsaGuard;
+ if (!markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval, dsaGuard))
+ TODO(variantLoc, "METADIRECTIVE variant with COLLAPSE or ORDERED "
+ "requires a deeper perfectly-nested loop nest than "
+ "is present");
+ genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+ queue.begin());
+ return;
+ }
+
genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
queue.begin());
+ if (!consumesAssociatedEvaluation(queue) && eval.hasNestedEvaluations())
+ genNestedEvaluations(converter, eval);
};
auto selectBestCandidate =
@@ -5806,7 +6038,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 e0d843ef4eca5..699a7c675d4a0 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -699,7 +699,7 @@ static void processTileSizesFromOpenMPConstruct(
}
}
-pft::Evaluation *getNestedDoConstruct(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>>
@@ -719,10 +719,34 @@ pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
// Loop transformations can introduce nested OpenMP
// constructs between the directive and the actual do-loop nest.
if (nested.getIf<parser::OpenMPConstruct>())
- return getNestedDoConstruct(nested);
- assert(false && "Unexpected construct in the nested evaluations");
+ return tryGetNestedDoConstruct(nested);
+ // Anything else means the perfectly-nested do-loop nest ends here.
+ return nullptr;
}
- llvm_unreachable("Expected do loop to be in the nested evaluations");
+ return nullptr;
+}
+
+pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
+ pft::Evaluation *doEval = tryGetNestedDoConstruct(eval);
+ assert(doEval && "Expected do loop to be in the nested evaluations");
+ return doEval;
+}
+
+/// 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
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..09ba77ba95055 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -170,6 +170,10 @@ void genObjectList(const ObjectList &objects,
void lastprivateModifierNotSupported(const omp::clause::Lastprivate &lastp,
mlir::Location loc);
+/// Return the perfectly-nested DO construct directly contained in \p eval, or
+/// null when there is none (e.g. the loop nest is shallower than required).
+pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval);
+
pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval);
int64_t collectLoopRelatedInfo(
@@ -274,6 +278,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/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-module-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
new file mode 100644
index 0000000000000..be08d91a95c01
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
@@ -0,0 +1,7 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE without associated DO
+
+module m
+ !$omp metadirective when(implementation={vendor(llvm)}: do) default(nothing)
+end module
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..f271cbeffb21f
--- /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=51 -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 & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
new file mode 100644
index 0000000000000..559949d2ef7bf
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,476 @@
+! Test lowering of OpenMP metadirective with loop-associated variants.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+
+!===----------------------------------------------------------------------===!
+! Basic loop-associated variants via static selection
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do()
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: omp.yield
+! CHECK: omp.terminator
+! CHECK: return
+subroutine test_parallel_do()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do()
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_do()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_simd()
+! CHECK: %[[I:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFtest_simdEi"}
+! CHECK-NOT: omp.wsloop
+! CHECK: omp.simd linear(%[[I]]#0
+! CHECK: omp.loop_nest
+! CHECK: omp.yield
+! CHECK-NOT: fir.do_loop
+! CHECK: fir.load %[[I]]#0
+! CHECK: return
+subroutine test_simd()
+ integer :: i, i_after
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ i_after = i
+ end do
+ i_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: omp.yield
+! CHECK: return
+subroutine test_do_simd()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do simd) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_do()
+! CHECK-NOT: omp.parallel
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_begin_do()
+ integer :: i
+ !$omp begin metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+ !$omp end metadirective
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Static mismatch falls through to standalone fallback
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_loop_static_mismatch()
+! CHECK-NOT: omp.wsloop
+! CHECK-NOT: omp.loop_nest
+! CHECK: omp.barrier
+! CHECK: return
+subroutine test_loop_static_mismatch()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor("unknown")}: parallel do) &
+ !$omp & default(barrier)
+ do i = 1, 100
+ end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Dynamic user condition with loop-associated variant
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop(
+! 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]] : (!fir.logical<4>) -> i1
+! CHECK: fir.if %[[COND]] {
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: } else {
+! CHECK: omp.simd
+! CHECK: omp.loop_nest
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_loop(flag)
+ logical, intent(in) :: flag
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: parallel do) &
+ !$omp & default(simd)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop_standalone_fallback(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: } else {
+! CHECK: omp.barrier
+! CHECK: fir.do_loop
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_loop_standalone_fallback(flag, a)
+ logical, intent(in) :: flag
+ integer :: i, a
+ a = 0
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: parallel do) &
+ !$omp & default(barrier)
+ do i = 1, 100
+ a = a + i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop_dsa_isolation(
+! CHECK: fir.if {{.*}} {
+! CHECK: omp.simd {{.*}}private(@{{[^,]*}}Ei_private_i32
+! CHECK-SAME: @_QFtest_dynamic_loop_dsa_isolationEj_private_i32
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: } else {
+! CHECK: omp.simd linear({{[^)]*}}) {
+! CHECK: omp.loop_nest ({{.*}}) : i32
+! CHECK: }
+! CHECK: return
+subroutine test_dynamic_loop_dsa_isolation(flag, n, sink)
+ logical, intent(in) :: flag
+ integer :: n, sink
+ integer :: i, j
+ sink = 0
+ !$omp metadirective &
+ !$omp & when(user={condition(flag)}: simd collapse(2)) &
+ !$omp & default(simd)
+ do i = 1, n
+ do j = 1, n
+ sink = sink + i + j
+ end do
+ end do
+ sink = sink + j
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_ordered_depth()
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop {{.*}}private(@{{[^,]*}}Ei_private_i32
+! CHECK-SAME: @_QFtest_ordered_depthEj_private_i32
+! CHECK: omp.loop_nest ({{.*}}) : i32
+! CHECK: return
+subroutine test_ordered_depth()
+ integer :: i, j
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do ordered(2)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ do j = 1, 100
+ end do
+ end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Loop-associated variants with clauses
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_schedule()
+! CHECK: omp.wsloop schedule(static)
+! CHECK: omp.loop_nest
+! CHECK: return
+subroutine test_schedule()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do schedule(static)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_reduction()
+! CHECK: omp.wsloop {{.*}} reduction(@add_reduction_i32
+! CHECK: omp.loop_nest
+! CHECK: return
+subroutine test_reduction()
+ integer :: i, s
+ s = 0
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do reduction(+:s)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ s = s + i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_private()
+! CHECK: omp.wsloop private(
+! CHECK: omp.loop_nest
+! CHECK: return
+subroutine test_private()
+ integer :: i, x
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do private(x)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ x = i
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_collapse()
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: return
+subroutine test_collapse()
+ integer :: i, j
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do collapse(2)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ do j = 1, 100
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_safelen()
+! CHECK: omp.simd {{.*}}safelen(4)
+! CHECK: omp.loop_nest
+! CHECK: return
+subroutine test_safelen()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd safelen(4)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_num_threads()
+! CHECK: omp.parallel num_threads({{.*}}) {
+! CHECK: omp.wsloop
+! CHECK: omp.loop_nest
+! CHECK: return
+subroutine test_num_threads()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do num_threads(4)) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Lastprivate copy-back for metadirective-selected loop induction variables
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_simd_collapse_lastprivate(
+! CHECK: %[[I_ORIG:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEi"}
+! CHECK: %[[J_ORIG:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEj"}
+! CHECK: omp.simd {{.*}}private({{.*}}_QFtest_simd_collapse_lastprivateEi_private_i32{{.*}}_QFtest_simd_collapse_lastprivateEj_private_i32{{.*}}) {
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2) {
+! CHECK: fir.if
+! CHECK: hlfir.assign %{{.*}} to %[[I_ORIG]]#0
+! CHECK: hlfir.assign %{{.*}} to %[[J_ORIG]]#0
+! CHECK: omp.yield
+subroutine test_simd_collapse_lastprivate(n, sink)
+ integer :: n, sink
+ integer :: i, j
+ sink = 0
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
+ !$omp & default(nothing)
+ do i = 1, n
+ do j = 1, n
+ sink = sink + i + j
+ end do
+ end do
+ sink = sink + j
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Metadirective nested inside a BLOCK construct
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_block_nested_parallel_do(
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop private(@_QFtest_block_nested_parallel_doEi_private_i32 {{.*}}) {
+! CHECK: omp.loop_nest
+subroutine test_block_nested_parallel_do(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i
+ block
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ a(i) = i
+ end do
+ end block
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Inner sequential loop induction-variable privatization
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_seq_inner(
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop private({{.*}}_QFtest_parallel_do_seq_innerEi_private_i32{{.*}}_QFtest_parallel_do_seq_innerEk_private_i32{{.*}}) {
+! CHECK: omp.loop_nest
+subroutine test_parallel_do_seq_inner(n, a)
+ integer :: n
+ integer :: a(n, n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ do k = 1, n
+ a(k, i) = k
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do_seq_inner_shared(
+! CHECK: omp.wsloop private(
+! CHECK-NOT: _QFtest_do_seq_inner_sharedEk_private
+! CHECK: omp.loop_nest
+subroutine test_do_seq_inner_shared(n, a)
+ integer :: n
+ integer :: a(n, n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ do k = 1, n
+ a(k, i) = k
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_critical_inner(
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop private({{.*}}_QFtest_parallel_do_critical_innerEi_private_i32{{.*}}_QFtest_parallel_do_critical_innerEk_private_i32{{.*}}) {
+! CHECK: omp.loop_nest
+! CHECK: omp.critical
+subroutine test_parallel_do_critical_inner(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ !$omp critical
+ do k = 1, n
+ a(k) = k
+ end do
+ !$omp end critical
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_inner_shared(
+! CHECK: omp.wsloop private(
+! CHECK: omp.loop_nest
+! CHECK: omp.parallel {
+! CHECK-NOT: _QFtest_parallel_do_inner_sharedEk_private
+subroutine test_parallel_do_inner_shared(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ !$omp parallel shared(k)
+ do k = 1, n
+ a(k) = k
+ end do
+ !$omp end parallel
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_reduction_iv(
+! CHECK: omp.wsloop private(@_QFtest_parallel_do_reduction_ivEi_private_i32 {{[^,]*}}) reduction(@add_reduction_i32 {{.*}}) {
+subroutine test_parallel_do_reduction_iv(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do reduction(+:k)) &
+ !$omp & default(nothing)
+ do i = 1, n
+ do k = 1, n
+ a(k) = k
+ end do
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_enclosing_shared(
+! CHECK: omp.wsloop private({{.*}}_QFtest_parallel_do_enclosing_sharedEi_private_i32{{.*}}_QFtest_parallel_do_enclosing_sharedEk_private_i32{{.*}}) {
+! CHECK: omp.loop_nest
+subroutine test_parallel_do_enclosing_shared(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i, k
+ !$omp parallel shared(k)
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ do k = 1, n
+ a(k) = k
+ end do
+ end do
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_target_inner(
+! CHECK: omp.wsloop private(@_QFtest_parallel_do_target_innerEi_private_i32 {{[^,]*}}) {
+! CHECK: omp.target
+! CHECK-NOT: _QFtest_parallel_do_target_innerEk_private_i32
+subroutine test_parallel_do_target_inner(n, a)
+ integer :: n
+ integer :: a(n)
+ integer :: i, k
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+ do i = 1, n
+ !$omp target map(tofrom: a)
+ do k = 1, n
+ a(k) = k
+ end do
+ !$omp end target
+ end do
+end subroutine
More information about the flang-commits
mailing list