[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 3 21:14:45 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/195344
>From 1476d8bcd29956b03560e79d950e871963fe7259 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 28 Apr 2026 11:54:13 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Support loop-associated metadirective
variants (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
sibling DO evaluation is spliced into the metadirective's evaluation
list so existing loop lowering finds it. Loop IV data-sharing
attributes are marked at lowering time since semantic analysis cannot
know which variant will be selected. The DataSharingProcessor is also
extended to handle spliced evaluations.
This patch is part of the feature work for #188820 and stacked on top
of #194424.
Assisted with copilot and GPT-5.4
---
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 85 +++++++-
flang/lib/Lower/OpenMP/OpenMP.cpp | 101 ++++++++-
flang/lib/Lower/OpenMP/Utils.cpp | 14 ++
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 | 203 ++++++++++++++++++
8 files changed, 429 insertions(+), 15 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 6525383766b29..2ce02f746d475 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -41,8 +41,41 @@ 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 constructs, the construct itself
+ // does not introduce privatization. As a result, symbols from nested
+ // (spliced) DO evaluations may be associated with a null
+ // OpenMPConstruct pointer. This is expected and should be accepted.
+ if (const auto *standalone =
+ std::get_if<parser::OpenMPStandaloneConstruct>(
+ &functionParserNode.u)) {
+ if (std::holds_alternative<parser::OmpMetadirectiveDirective>(
+ standalone->u)) {
+ 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 +110,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 +261,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(
@@ -531,6 +591,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) &&
// Linear symbols are privatized by OpenMP IRBuilder. See comments
@@ -563,6 +636,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 395746a60af7d..038fc78216b10 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4721,6 +4721,95 @@ static void appendConstructTraits(
llvm::omp::TraitProperty::construct_target_target);
}
+/// 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()) {
+ lower::pft::Evaluation &nested = eval.getNestedEvaluations().back();
+ return nested.getIf<parser::DoConstruct>() ? &nested : 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();
+}
+
+/// Clear all existing DSA flags on \p sym, then set PreDetermined + \p dsa.
+static void setSymbolDSA(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};
+}
+
+/// Extract the induction variable symbol from a DO construct.
+static semantics::Symbol *
+getDoConstructIVSymbol(const parser::DoConstruct &doConstruct) {
+ if (const auto &loopCtrl = doConstruct.GetLoopControl())
+ if (auto *bounds = std::get_if<parser::LoopControl::Bounds>(&loopCtrl->u))
+ return bounds->Name().thing.symbol;
+ return nullptr;
+}
+
+/// 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) {
+ 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;
+ Symbol::Flag ivDSA;
+ if (!llvm::omp::allSimdSet.test(spec.DirId()))
+ 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) {
+ auto *doConstruct = doEval->getIf<parser::DoConstruct>();
+ assert(doConstruct && "expected associated DO construct");
+ if (semantics::Symbol *sym = getDoConstructIVSymbol(*doConstruct))
+ setSymbolDSA(*sym, ivDSA);
+ if (level + 1 < affectedDepth)
+ doEval = getNestedDoConstruct(*doEval);
+ }
+}
+
static void genMetadirective(lower::AbstractConverter &converter,
lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -4900,7 +4989,17 @@ static void genMetadirective(lower::AbstractConverter &converter,
return llvm::omp::getDirectiveAssociation(item.id) ==
llvm::omp::Association::LoopNest;
})) {
- TODO(variantLoc, "loop-associated METADIRECTIVE variant");
+ lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
+ if (!loopEval)
+ TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
+ markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval);
+ }
+
+ if (llvm::any_of(queue, [](const auto &item) {
+ return llvm::omp::allTargetSet.test(item.id);
+ })) {
+ TODO(variantLoc,
+ "TARGET construct selected by METADIRECTIVE (host-eval)");
}
if (llvm::any_of(queue, [](const auto &item) {
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 4f6bc3610ad32..35a7df358f1c2 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -722,6 +722,20 @@ 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 (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 36fde727535d8..017dbae2229b2 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -255,6 +255,8 @@ makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
const parser::modifier::OmpContextSelector &ctxSel,
semantics::SemanticsContext &semaCtx, mlir::Location loc);
+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..80c826a722496
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,203 @@
+! 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-NOT: omp.wsloop
+! CHECK: omp.simd
+! CHECK: omp.loop_nest
+! CHECK: omp.yield
+! CHECK: return
+subroutine test_simd()
+ integer :: i
+ !$omp metadirective &
+ !$omp & when(implementation={vendor(llvm)}: simd) &
+ !$omp & default(nothing)
+ do i = 1, 100
+ end do
+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
+
+!===----------------------------------------------------------------------===!
+! 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
+
+!===----------------------------------------------------------------------===!
+! 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
>From 9e7053b4300b50d30d9e1c012ac6d0d67c69e7f7 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Wed, 6 May 2026 00:14:29 -0500
Subject: [PATCH 2/2] Fix metadirective loop variant lowering
Preserve the associated DO evaluation when a dynamic metadirective can
select either a loop-associated directive or a standalone fallback, so
the fallback still lowers the original loop body.
Scope temporary loop-IV data-sharing attributes to the selected variant.
Use the selected variant's collapse clause to determine how many loop IVs
to mark, avoiding DSA state leaking between alternatives.
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 107 ++++++++++++++----
.../test/Lower/OpenMP/metadirective-loop.f90 | 50 +++++++-
2 files changed, 133 insertions(+), 24 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 038fc78216b10..debccf8f9b758 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4753,8 +4753,23 @@ spliceAssociatedDoEval(lower::pft::Evaluation &eval) {
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 setSymbolDSA(semantics::Symbol &sym, semantics::Symbol::Flag dsa) {
+static void applySymbolDSA(semantics::Symbol &sym,
+ semantics::Symbol::Flag dsa) {
using Symbol = semantics::Symbol;
Symbol::Flags dataSharingAttributeFlags{
Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate,
@@ -4767,6 +4782,25 @@ static void setSymbolDSA(semantics::Symbol &sym, semantics::Symbol::Flag dsa) {
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;
+};
+
/// Extract the induction variable symbol from a DO construct.
static semantics::Symbol *
getDoConstructIVSymbol(const parser::DoConstruct &doConstruct) {
@@ -4776,21 +4810,26 @@ getDoConstructIVSymbol(const parser::DoConstruct &doConstruct) {
return nullptr;
}
+static int64_t getCollapseValue(const parser::OmpDirectiveSpecification &spec) {
+ for (const parser::OmpClause &clause : spec.Clauses().v)
+ if (const auto *collapse =
+ std::get_if<parser::OmpClause::Collapse>(&clause.u))
+ if (const auto value = semantics::GetIntValue(collapse->v))
+ return *value;
+ return 1;
+}
+
/// 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) {
+ 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;
+ int64_t affectedDepth = std::max<int64_t>(getCollapseValue(spec), 1);
Symbol::Flag ivDSA;
if (!llvm::omp::allSimdSet.test(spec.DirId()))
ivDSA = Symbol::Flag::OmpPrivate;
@@ -4804,7 +4843,7 @@ markMetadirectiveLoopIVs(semantics::SemanticsContext &semaCtx,
auto *doConstruct = doEval->getIf<parser::DoConstruct>();
assert(doConstruct && "expected associated DO construct");
if (semantics::Symbol *sym = getDoConstructIVSymbol(*doConstruct))
- setSymbolDSA(*sym, ivDSA);
+ dsaGuard.setSymbolDSA(*sym, ivDSA);
if (level + 1 < affectedDepth)
doEval = getNestedDoConstruct(*doEval);
}
@@ -4973,27 +5012,35 @@ 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)};
-
- if (llvm::any_of(queue, [](const auto &item) {
- return llvm::omp::getDirectiveAssociation(item.id) ==
- llvm::omp::Association::LoopNest;
- })) {
- lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
- if (!loopEval)
- TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
- markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval);
- }
+ ConstructQueue queue = makeVariantQueue(*spec);
if (llvm::any_of(queue, [](const auto &item) {
return llvm::omp::allTargetSet.test(item.id);
@@ -5011,8 +5058,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 =
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
index 80c826a722496..fdea60a2616f4 100644
--- a/flang/test/Lower/OpenMP/metadirective-loop.f90
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -114,6 +114,54 @@ subroutine test_dynamic_loop(flag)
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
+! CHECK: omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK: } else {
+! CHECK: omp.simd {{.*}}private(@{{[^,]*}}Ei_private_i32 {{[^:]*}} : !fir.ref<i32>)
+! CHECK-NOT: @_QFtest_dynamic_loop_dsa_isolationEj_private_i32
+! 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
+
!===----------------------------------------------------------------------===!
! Loop-associated variants with clauses
!===----------------------------------------------------------------------===!
@@ -176,7 +224,7 @@ subroutine test_collapse()
end subroutine
! CHECK-LABEL: func.func @_QPtest_safelen()
-! CHECK: omp.simd {{.*}} safelen(4)
+! CHECK: omp.simd {{.*}}safelen(4)
! CHECK: omp.loop_nest
! CHECK: return
subroutine test_safelen()
More information about the flang-commits
mailing list