[llvm-branch-commits] [flang] [flang][OpenMP] Identify DO loops affected by loop-associated construct (PR #191719)
Krzysztof Parzyszek via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Apr 12 08:16:09 PDT 2026
https://github.com/kparzysz created https://github.com/llvm/llvm-project/pull/191719
This is to identify iteration variables of DO loops affected by an OpenMP loop construct. These variables are privatized as per data-sharing rules.
>From bc23293b1f78161af44d0e4b04b72df6b30ac8b7 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 9 Apr 2026 14:24:16 -0500
Subject: [PATCH] [flang][OpenMP] Identify DO loops affected by loop-associated
construct
This is to identify iteration variables of DO loops affected by an OpenMP
loop construct. These variables are privatized as per data-sharing rules.
---
flang/include/flang/Semantics/openmp-utils.h | 7 ++
flang/lib/Semantics/openmp-utils.cpp | 91 +++++++++++++++++++
flang/lib/Semantics/resolve-directives.cpp | 69 +++++++-------
.../test/Semantics/OpenMP/affected-loops.f90 | 27 ++++++
4 files changed, 158 insertions(+), 36 deletions(-)
create mode 100644 flang/test/Semantics/OpenMP/affected-loops.f90
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 252c88c967750..1924fbe9b093d 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -242,6 +242,13 @@ std::optional<int64_t> GetMinimumSequenceCount(
std::optional<int64_t> GetMinimumSequenceCount(
std::optional<std::pair<int64_t, int64_t>> range);
+/// Collect the set of DO loops present in the source code that are directly
+/// affected by the given loop construct. This does not include any DO loops
+/// that are affected by any construct nested in `x`.
+std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
+ const parser::OpenMPLoopConstruct &x, unsigned version,
+ SemanticsContext *semaCtx = nullptr);
+
struct LoopSequence {
LoopSequence(const parser::ExecutionPartConstruct &root, unsigned version,
bool allowAllLoops = false, SemanticsContext *semaCtx = nullptr);
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 7a5b31b1c805e..b09d4a2e207b7 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -1202,6 +1202,97 @@ std::optional<int64_t> GetMinimumSequenceCount(
return GetMinimumSequenceCount(std::nullopt, std::nullopt);
}
+/// Collect the DO loops that are affected directly by the given loop
+/// transformation. Not all DO loops nested in the associated nest are
+/// affected by the top-level loop transformation, e.g.
+///
+/// !$omp do collapse(5) | [2]
+/// !$omp tile sizes(2, 2) | [1] | <- nest of 4 loops
+/// do i = 1, 10 | <- affected by TILE | generated by TILE
+/// do j = 1, 10 | <- |
+/// do k = 1, 10 | <- affected by DO
+/// end do
+/// end do
+/// end do
+///
+/// The two DO loops (i and j) in [1] are affected by the TILE construct.
+/// The k DO loop is affected by the DO construct [2].
+/// For the top-level DO COLLAPSE(5) construct, the k loop is the only
+/// directly affected loop.
+std::optional<std::vector<const parser::DoConstruct *>> CollectAffectedDoLoops(
+ const parser::OpenMPLoopConstruct &x, unsigned version,
+ SemanticsContext *semaCtx) {
+ std::vector<const parser::DoConstruct *> result;
+ const parser::OmpDirectiveSpecification &spec{x.BeginDir()};
+
+ auto [depth, _]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+
+ // If the depth is absent, then there is some issue. Leave it alone here,
+ // and let the semantic checks diagnose the problem.
+ if (!depth) {
+ return std::nullopt;
+ }
+ if (*depth.value <= 0) {
+ return result;
+ }
+
+ // The algorithm is to descend down the nest and keep track of intervening
+ // constructs and how many loops they consume and produce. This is similar
+ // to traversing an expression tree to identify the operands to the top-
+ // level operation:
+ //
+ // ... + + x y z w ...
+ // ^ ^ ^
+ // | | |
+ // | | +-- produces 1 value consumed by the first +
+ // | +-- produces 1 value, but first consumes 2
+ // +-- consumes 2 operands
+ //
+ // The analogous result here would be "z" as the operand to the first +.
+
+ int64_t produced{0};
+ int64_t consuming{0};
+ int64_t level{*depth.value};
+
+ auto visit{[&](const LoopSequence &nest, auto &&self) -> void {
+ const parser::ExecutionPartConstruct *owner{nest.owner()};
+
+ if (auto *doLoop{parser::Unwrap<parser::DoConstruct>(owner)}) {
+ if (consuming == 0) {
+ result.push_back(doLoop);
+ ++produced;
+ } else {
+ --consuming;
+ }
+ } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(owner)}) {
+ const parser::OmpDirectiveSpecification &spec{omp->BeginDir()};
+ auto [cons, _1]{GetAffectedNestDepthWithReason(spec, version, semaCtx)};
+ auto [prod, _2]{GetGeneratedNestDepthWithReason(spec, version, semaCtx)};
+ if (!cons || !prod) {
+ return;
+ }
+ if (*prod.value <= consuming) {
+ consuming -= *prod.value;
+ } else {
+ produced += (*prod.value - consuming);
+ consuming = 0;
+ }
+ consuming += *cons.value;
+ }
+
+ if (produced < level) {
+ for (const LoopSequence &child : nest.children()) {
+ self(child, self);
+ }
+ }
+ }};
+
+ LoopSequence sequence(std::get<parser::Block>(x.t), version, true, semaCtx);
+ visit(sequence, visit);
+
+ return result;
+}
+
#ifdef EXPENSIVE_CHECKS
namespace {
/// Check that for every value x of type T, there will be a "source" member
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index c50c76647962c..6b42a7290e260 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1114,8 +1114,7 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
}
// Predetermined DSA rules
- void PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
- const parser::OpenMPLoopConstruct &);
+ void PrivatizeAssociatedLoopIndex(const parser::OpenMPLoopConstruct &);
void ResolveSeqLoopIndexInParallelOrTaskConstruct(const parser::Name &);
bool IsNestedInDirective(llvm::omp::Directive directive);
@@ -2068,7 +2067,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
}
}
- PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
+ PrivatizeAssociatedLoopIndex(x);
return true;
}
@@ -2162,13 +2161,16 @@ bool OmpAttributeVisitor::Pre(const parser::DoConstruct &x) {
// increment of the associated do-loop (only for OpenMP versions <= 4.5)
// - The loop iteration variables in the associated do-loops of a simd
// construct with multiple associated do-loops are lastprivate.
-void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
+void OmpAttributeVisitor::PrivatizeAssociatedLoopIndex(
const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpDirectiveSpecification &spec{x.BeginDir()};
unsigned version{context_.langOptions().OpenMPVersion};
+
auto [depth, _]{
- omp::GetAffectedNestDepthWithReason(x.BeginDir(), version, &context_)};
- // If there was a problem obtaining the depth, it will be diagnosed in
- // the semantic checks.
+ omp::GetAffectedNestDepthWithReason(spec, version, &context_)};
+
+ // If depth is absent, then there is some issue. Leave it alone here,
+ // and let the semantic checks diagnose the problem.
if (!depth || *depth.value <= 0) {
return;
}
@@ -2183,35 +2185,30 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
ivDSA = Symbol::Flag::OmpLastPrivate;
}
- for (auto &construct : std::get<parser::Block>(x.t)) {
- if (const auto *innermostConstruct{parser::omp::GetOmpLoop(construct)}) {
- PrivatizeAssociatedLoopIndexAndCheckLoopLevel(*innermostConstruct);
- } else if (const auto *doConstruct{
- parser::omp::GetDoConstruct(construct)}) {
- for (const parser::DoConstruct *loop{&*doConstruct}; loop && level > 0;
- --level) {
- // go through all the nested do-loops and resolve index variables
- if (const parser::Name *iv{GetLoopIndex(*loop)}) {
- if (!iv->symbol || !IsLocalInsideScope(*iv->symbol, currScope())) {
- if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
- if (const auto *details{
- iv->symbol->detailsIf<HostAssocDetails>()}) {
- const Symbol *tpSymbol = &details->symbol();
- if (tpSymbol->test(Symbol::Flag::OmpThreadprivate)) {
- context_.Say(iv->source,
- "Loop iteration variable %s is not allowed in THREADPRIVATE."_err_en_US,
- iv->ToString());
- }
- }
- SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
- iv->symbol = symbol; // adjust the symbol within region
- AddToContextObjectWithDSA(*symbol, ivDSA);
- }
- }
- const auto &block{std::get<parser::Block>(loop->t)};
- const auto it{block.begin()};
- loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
- }
+ auto checkThreadprivate{[&](const parser::Name &iv) {
+ if (const auto *details{iv.symbol->detailsIf<HostAssocDetails>()}) {
+ if (details->symbol().test(Symbol::Flag::OmpThreadprivate)) {
+ context_.Say(iv.source,
+ "Loop iteration variable %s is not allowed in THREADPRIVATE."_err_en_US,
+ iv.ToString());
+ }
+ }
+ }};
+
+ Scope &scope{currScope()};
+
+ if (auto doLoops{omp::CollectAffectedDoLoops(x, version, &context_)}) {
+ for (const parser::DoConstruct *loop : *doLoops) {
+ const parser::Name *iv{GetLoopIndex(*loop)};
+ if (!iv || (iv->symbol && IsLocalInsideScope(*iv->symbol, scope))) {
+ continue;
+ }
+
+ if (auto *symbol{ResolveOmp(*iv, ivDSA, scope)}) {
+ checkThreadprivate(*iv);
+ SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
+ iv->symbol = symbol; // adjust the symbol within region
+ AddToContextObjectWithDSA(*symbol, ivDSA);
}
}
}
diff --git a/flang/test/Semantics/OpenMP/affected-loops.f90 b/flang/test/Semantics/OpenMP/affected-loops.f90
new file mode 100644
index 0000000000000..01273ec4725ec
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/affected-loops.f90
@@ -0,0 +1,27 @@
+!RUN: %flang_fc1 -fdebug-dump-symbols -fopenmp -fopenmp-version=60 %s | FileCheck %s
+
+subroutine f
+ integer :: i, j, k
+ !$omp do collapse(5)
+ !$omp tile sizes(2, 2)
+ do i = 1, 10
+ do j = 1, 10
+ do k = 1, 10
+ end do
+ end do
+ end do
+end
+
+! Check that k is privatized in the scope of DO, and that i, j are privatized
+! in the scope of TILE.
+
+!CHECK: Subprogram scope: f size=12 alignment=4 sourceRange=139 bytes
+!CHECK: f (Subroutine): HostAssoc => f (Subroutine): Subprogram ()
+!CHECK: i size=4 offset=0: ObjectEntity type: INTEGER(4)
+!CHECK: j size=4 offset=4: ObjectEntity type: INTEGER(4)
+!CHECK: k size=4 offset=8: ObjectEntity type: INTEGER(4)
+!CHECK: OtherConstruct scope: size=0 alignment=1 sourceRange=98 bytes
+!CHECK: k (OmpPrivate, OmpPreDetermined): HostAssoc => k size=4 offset=8: ObjectEntity type: INTEGER(4)
+!CHECK: OtherConstruct scope: size=0 alignment=1 sourceRange=77 bytes
+!CHECK: i (OmpPrivate, OmpPreDetermined): HostAssoc => i size=4 offset=0: ObjectEntity type: INTEGER(4)
+!CHECK: j (OmpPrivate, OmpPreDetermined): HostAssoc => j size=4 offset=4: ObjectEntity type: INTEGER(4)
More information about the llvm-branch-commits
mailing list