[flang-commits] [flang] [DRAFT] [flang][OpenMP] Support loop-associated metadirective variants (part 3) (PR #195344)
via flang-commits
flang-commits at lists.llvm.org
Wed Jun 24 12:51:12 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/195344
>From b20b726d3161abb93f469f6a4c68557ca6719361 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 24 Jun 2026 14:48:09 -0500
Subject: [PATCH] [flang][OpenMP] Support lowering of metadirective (part 3)
Enable metadirective lowering for loop-associated variants such as
`do`, `simd`, `parallel do`, and `do simd`.
When a metadirective resolves to a loop-associated directive, the
associated DO construct is made available to the selected variant so
existing OpenMP loop lowering can process it. Standalone metadirectives
use the following sibling DO evaluation, while begin/end metadirectives
use the first substantive nested evaluation from the metadirective block.
Loop induction-variable data-sharing attributes are marked during
lowering because semantic analysis cannot know which variant will be
selected. The temporary attributes are scoped to the selected variant so
DSA state does not leak between alternatives.
Dynamic metadirective selection also preserves the associated DO
evaluation when a runtime condition may choose either a loop-associated
variant or a standalone fallback. This keeps fallback lowering from
dropping the original loop body after another candidate has consumed the
loop.
This patch also uses the normal affected-loop-depth calculation for
selected variants, so `collapse(n)` and `ordered(n)` mark all affected
induction variables. SIMD variants are identified from their leaf
constructs so standalone `simd` preserves its implicit linear
induction-variable semantics.
This patch is part of the feature work for #188820 and builds on
[#194424](https://github.com/llvm/llvm-project/pull/194424).
Assisted with Copilot and GPT-5.
---
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 90 +++++-
flang/lib/Lower/OpenMP/OpenMP.cpp | 175 ++++++++++-
flang/lib/Lower/OpenMP/Utils.cpp | 17 ++
flang/lib/Lower/OpenMP/Utils.h | 2 +
.../Lower/OpenMP/Todo/metadirective-loop.f90 | 12 -
.../OpenMP/Todo/metadirective-no-loop.f90 | 12 +
.../OpenMP/Todo/metadirective-target-loop.f90 | 15 +
.../test/Lower/OpenMP/metadirective-loop.f90 | 289 ++++++++++++++++++
8 files changed, 590 insertions(+), 22 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-no-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..7a28986c7feb0 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 4b83ac68ebf44..575f1df241738 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4802,6 +4802,130 @@ struct MetadirectiveCandidate {
};
} // namespace
+/// If the metadirective evaluation already contains a spliced DO loop (from a
+/// prior call), return it. Otherwise, locate the sibling DO loop that
+/// immediately follows the metadirective in the parent evaluation list, splice
+/// it into the metadirective's own evaluation list, and return a pointer to it.
+/// Returns nullptr if no associated DO loop is found.
+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;
+ }
+
+ auto *parentList = eval.parentConstruct
+ ? eval.parentConstruct->evaluationList.get()
+ : &eval.getOwningProcedure()->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;
+ Symbol::Flags dataSharingAttributeFlags{
+ Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate,
+ Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate,
+ Symbol::Flag::OmpReduction, Symbol::Flag::OmpLinear};
+ sym.flags() &=
+ ~(dataSharingAttributeFlags |
+ Symbol::Flags{Symbol::Flag::OmpExplicit, Symbol::Flag::OmpImplicit,
+ Symbol::Flag::OmpPreDetermined});
+ sym.flags() |= 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 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.
+static void
+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;
+
+ 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) {
+ assert(doEval->getIf<parser::DoConstruct>() &&
+ "expected associated DO construct");
+ if (semantics::Symbol *sym = getIterationVariableSymbol(*doEval))
+ dsaGuard.setSymbolDSA(*sym, ivDSA);
+ if (level + 1 < affectedDepth)
+ doEval = getNestedDoConstruct(*doEval);
+ }
+}
+
static void genMetadirective(lower::AbstractConverter &converter,
lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -4974,23 +5098,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) {
@@ -5002,8 +5144,22 @@ 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;
+ markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval, dsaGuard);
+ 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 =
@@ -5518,7 +5674,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 382292b6c6c13..c145612e6e91c 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -725,6 +725,23 @@ pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
llvm_unreachable("Expected do loop to be in the nested evaluations");
}
+/// 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 efe6c963a3778..4b268d7df4272 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -277,6 +277,8 @@ class FlangOMPContext final : public llvm::omp::OMPContext {
mlir::LLVM::TargetFeaturesAttr targetFeatures;
};
+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-no-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-no-loop.f90
new file mode 100644
index 0000000000000..d10b57c493b6d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-no-loop.f90
@@ -0,0 +1,12 @@
+! Test that a metadirective resolving to a loop-associated variant
+! without an associated DO loop correctly reports a TODO.
+
+! 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
+
+subroutine test_no_associated_do()
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: parallel do) &
+ !$omp & default(nothing)
+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..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..fd35d71a550a7
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,289 @@
+! 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
More information about the flang-commits
mailing list