[flang-commits] [flang] [flang][OpenMP] Privatize indirectly referenced symbols (PR #187097)
Leandro Lupori via flang-commits
flang-commits at lists.llvm.org
Thu Mar 19 10:12:03 PDT 2026
https://github.com/luporl updated https://github.com/llvm/llvm-project/pull/187097
>From d203780202849fc4cb3927087c99ac6ad3822521 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Fri, 13 Mar 2026 16:24:11 -0300
Subject: [PATCH 1/3] [flang][OpenMP] Privatize indirectly referenced symbols
Symbols that may be referenced indirectly by lastprivate or linear
DSAs in nested constructs must not have their privatization skipped
in the enclosing context. This avoids updates to the wrong symbols.
This is needed to unblock PRs that fix #170784.
Fixes #179345
---
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 178 +++++++++++++-----
flang/lib/Lower/OpenMP/DataSharingProcessor.h | 13 +-
.../Lower/OpenMP/composite_simd_linear.f90 | 35 ++++
.../parallel-lastprivate-clause-scalar.f90 | 43 +++++
...redetermined_only_when_defined_by_eval.f90 | 35 ----
5 files changed, 222 insertions(+), 82 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/privatize_predetermined_only_when_defined_by_eval.f90
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index fcf2ae9337295..ef5d5f86c875c 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -96,6 +96,7 @@ void DataSharingProcessor::processStep1(
collectDefaultSymbols();
collectImplicitSymbols();
collectPreDeterminedSymbols();
+ collectIndirectReferences();
privatize(clauseOps, dir);
@@ -461,55 +462,47 @@ void DataSharingProcessor::collectSymbolsInNestedRegions(
}
}
-// Collect symbols to be default privatized in two steps.
-// In step 1, collect all symbols in `eval` that match `flag` into
-// `defaultSymbols`. In step 2, for nested constructs (if any), if and only if
-// the nested construct is an OpenMP construct, collect those nested
-// symbols skipping host associated symbols into `symbolsInNestedRegions`.
-// Later, in current context, all symbols in the set
-// `defaultSymbols` - `symbolsInNestedRegions` will be privatized.
-void DataSharingProcessor::collectSymbols(
- semantics::Symbol::Flag flag,
- llvm::SetVector<const semantics::Symbol *> &symbols) {
- // Collect all scopes associated with 'eval'.
- llvm::SetVector<const semantics::Scope *> clauseScopes;
- std::function<void(const semantics::Scope *)> collectScopes =
+// Collect all scopes associated with `eval` and return the current scope.
+static const semantics::Scope *
+collectScopes(semantics::SemanticsContext &semaCtx,
+ lower::pft::Evaluation &eval,
+ llvm::SetVector<const semantics::Scope *> &clauseScopes) {
+ std::function<void(const semantics::Scope *)> collect =
[&](const semantics::Scope *scope) {
clauseScopes.insert(scope);
for (const semantics::Scope &child : scope->children())
- collectScopes(&child);
+ collect(&child);
};
parser::CharBlock source = getSource(semaCtx, eval);
const semantics::Scope *curScope = nullptr;
if (!source.empty()) {
curScope = &semaCtx.FindScope(source);
- collectScopes(curScope);
+ collect(curScope);
}
- // Collect all symbols referenced in the evaluation being processed,
- // that matches 'flag'.
- llvm::SetVector<const semantics::Symbol *> allSymbols;
- converter.collectSymbolSet(eval, allSymbols, flag,
- /*collectSymbols=*/true,
- /*collectHostAssociatedSymbols=*/true);
-
- llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
- collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
+ return curScope;
+}
- for (auto *symbol : allSymbols)
- if (visitor.isSymbolDefineBy(symbol, eval))
- symbolsInNestedRegions.remove(symbol);
+static bool isPrivatizable(const semantics::Symbol &sym) {
+ return !semantics::IsProcedure(sym) &&
+ !sym.GetUltimate().has<semantics::DerivedTypeDetails>() &&
+ !sym.GetUltimate().has<semantics::NamelistDetails>() &&
+ !semantics::IsImpliedDoIndex(sym.GetUltimate()) &&
+ !semantics::IsStmtFunction(sym);
+}
+void DataSharingProcessor::collectPrivatizedSymbols(
+ std::optional<semantics::Symbol::Flag> flag,
+ const llvm::SetVector<const semantics::Symbol *> &allSymbols,
+ const llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions,
+ llvm::SetVector<const semantics::Symbol *> *symbols) {
// Filter-out symbols that must not be privatized.
- bool collectImplicit = flag == semantics::Symbol::Flag::OmpImplicit;
- bool collectPreDetermined = flag == semantics::Symbol::Flag::OmpPreDetermined;
-
- auto isPrivatizable = [](const semantics::Symbol &sym) -> bool {
- return !semantics::IsProcedure(sym) &&
- !sym.GetUltimate().has<semantics::DerivedTypeDetails>() &&
- !sym.GetUltimate().has<semantics::NamelistDetails>() &&
- !semantics::IsImpliedDoIndex(sym.GetUltimate()) &&
- !semantics::IsStmtFunction(sym);
- };
+ bool collectImplicit = false;
+ bool collectPreDetermined = false;
+ bool collectIndirectRefs = !flag.has_value();
+ if (!collectIndirectRefs) {
+ collectImplicit = *flag == semantics::Symbol::Flag::OmpImplicit;
+ collectPreDetermined = *flag == semantics::Symbol::Flag::OmpPreDetermined;
+ }
auto shouldCollectSymbol = [&](const semantics::Symbol *sym) {
if (collectImplicit) {
@@ -541,31 +534,65 @@ void DataSharingProcessor::collectSymbols(
sym->test(semantics::Symbol::Flag::OmpPreDetermined);
}
+ if (collectIndirectRefs)
+ return true;
+
return !sym->test(semantics::Symbol::Flag::OmpImplicit) &&
!sym->test(semantics::Symbol::Flag::OmpPreDetermined);
};
+ llvm::SetVector<const semantics::Scope *> clauseScopes;
+ const semantics::Scope *curScope = collectScopes(semaCtx, eval, clauseScopes);
+
for (const auto *sym : allSymbols) {
assert(curScope && "couldn't find current scope");
if (isPrivatizable(*sym) && !symbolsInNestedRegions.contains(sym) &&
!explicitlyPrivatizedSymbols.contains(sym) &&
shouldCollectSymbol(sym) && clauseScopes.contains(&sym->owner())) {
allPrivatizedSymbols.insert(sym);
- symbols.insert(sym);
+ if (symbols)
+ symbols->insert(sym);
}
}
}
+// Collect symbols to be default privatized in two steps.
+// In step 1, collect all symbols in `eval` that match `flag` into
+// `defaultSymbols`. In step 2, for nested constructs (if any), if and only if
+// the nested construct is an OpenMP construct, collect those nested
+// symbols skipping host associated symbols into `symbolsInNestedRegions`.
+// Later, in current context, all symbols in the set
+// `defaultSymbols` - `symbolsInNestedRegions` will be privatized.
+void DataSharingProcessor::collectSymbols(
+ semantics::Symbol::Flag flag,
+ llvm::SetVector<const semantics::Symbol *> *symbols) {
+ // Collect all symbols referenced in the evaluation being processed,
+ // that matches 'flag'.
+ llvm::SetVector<const semantics::Symbol *> allSymbols;
+ converter.collectSymbolSet(eval, allSymbols, flag,
+ /*collectSymbols=*/true,
+ /*collectHostAssociatedSymbols=*/true);
+
+ llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
+ collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
+
+ for (auto *symbol : allSymbols)
+ if (visitor.isSymbolDefineBy(symbol, eval))
+ symbolsInNestedRegions.remove(symbol);
+
+ collectPrivatizedSymbols(flag, allSymbols, symbolsInNestedRegions);
+}
+
void DataSharingProcessor::collectDefaultSymbols() {
using DataSharingAttribute = omp::clause::Default::DataSharingAttribute;
for (const omp::Clause &clause : clauses) {
if (const auto *defaultClause =
std::get_if<omp::clause::Default>(&clause.u)) {
if (defaultClause->v == DataSharingAttribute::Private)
- collectSymbols(semantics::Symbol::Flag::OmpPrivate, defaultSymbols);
+ collectSymbols(semantics::Symbol::Flag::OmpPrivate, &defaultSymbols);
else if (defaultClause->v == DataSharingAttribute::Firstprivate)
collectSymbols(semantics::Symbol::Flag::OmpFirstPrivate,
- defaultSymbols);
+ &defaultSymbols);
}
}
}
@@ -573,13 +600,78 @@ void DataSharingProcessor::collectDefaultSymbols() {
void DataSharingProcessor::collectImplicitSymbols() {
// There will be no implicit symbols when a default clause is present.
if (defaultSymbols.empty())
- collectSymbols(semantics::Symbol::Flag::OmpImplicit, implicitSymbols);
+ collectSymbols(semantics::Symbol::Flag::OmpImplicit);
}
void DataSharingProcessor::collectPreDeterminedSymbols() {
if (shouldCollectPreDeterminedSymbols)
- collectSymbols(semantics::Symbol::Flag::OmpPreDetermined,
- preDeterminedSymbols);
+ collectSymbols(semantics::Symbol::Flag::OmpPreDetermined);
+}
+
+// Collect symbols that may be referenced indirectly by lastprivate or linear
+// DSAs in nested constructs. Their privatization must not be skipped in the
+// enclosing context, to avoid updating the wrong symbol.
+void DataSharingProcessor::collectIndirectReferences() {
+ // For compound constructs, collect the symbols only for the last leaf.
+ if (!shouldCollectPreDeterminedSymbols)
+ return;
+
+ llvm::SetVector<const semantics::Scope *> clauseScopes;
+ const semantics::Scope *curScope = collectScopes(semaCtx, eval, clauseScopes);
+ if (!curScope)
+ return;
+
+ // Collect all linear and lastprivate symbols.
+ llvm::SetVector<const semantics::Symbol *> allSymbols;
+ llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
+
+ auto collect = [&](semantics::Symbol::Flag flag) {
+ converter.collectSymbolSet(eval, allSymbols, flag,
+ /*collectSymbols=*/true,
+ /*collectHostAssociatedSymbols=*/true);
+ collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
+ };
+ collect(semantics::Symbol::Flag::OmpLinear);
+ collect(semantics::Symbol::Flag::OmpLastPrivate);
+
+ for (auto *symbol : allSymbols)
+ if (visitor.isSymbolDefineBy(symbol, eval))
+ symbolsInNestedRegions.remove(symbol);
+
+ auto isPrivate = [](const semantics::Symbol &sym) {
+ using Symbol = semantics::Symbol;
+ Symbol::Flags privateFlags{
+ Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate,
+ Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpLinear};
+ return (sym.flags() & privateFlags).any();
+ };
+
+ // Find indirect references.
+ //
+ // A symbol in the current scope may be indirectly referenced by a DSA in
+ // nested constructs.
+ // To simplify the implementation, any linear/lastprivate symbol in a nested
+ // region is considered as an indirect reference. The produced output is
+ // correct, although it may contain privatizations that could be eliminated.
+ llvm::SetVector<const semantics::Symbol *> indirectReferences;
+ for (auto it = curScope->begin(), end = curScope->end(); it != end; ++it) {
+ const semantics::Symbol &sym = *it->second;
+ if (!isPrivate(sym))
+ continue;
+
+ for (const semantics::Symbol *nestedSym : symbolsInNestedRegions) {
+ if (&sym != nestedSym && sym.name() == nestedSym->name())
+ indirectReferences.insert(&sym);
+ }
+ }
+
+ // Remove indirectly referenced symbols from nested regions, to force them
+ // to be privatized.
+ for (const semantics::Symbol *sym : indirectReferences)
+ symbolsInNestedRegions.remove(sym);
+
+ collectPrivatizedSymbols(std::nullopt, indirectReferences,
+ symbolsInNestedRegions);
}
void DataSharingProcessor::privatize(mlir::omp::PrivateClauseOps *clauseOps,
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.h b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
index f6aa8652e3534..5dd564d4bbb61 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.h
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
@@ -96,8 +96,6 @@ class DataSharingProcessor {
// Symbols in private, firstprivate, and/or lastprivate clauses.
llvm::SetVector<const semantics::Symbol *> explicitlyPrivatizedSymbols;
llvm::SetVector<const semantics::Symbol *> defaultSymbols;
- llvm::SetVector<const semantics::Symbol *> implicitSymbols;
- llvm::SetVector<const semantics::Symbol *> preDeterminedSymbols;
llvm::SetVector<const semantics::Symbol *> allPrivatizedSymbols;
lower::AbstractConverter &converter;
@@ -113,8 +111,14 @@ class DataSharingProcessor {
OMPConstructSymbolVisitor visitor;
bool needBarrier();
- void collectSymbols(semantics::Symbol::Flag flag,
- llvm::SetVector<const semantics::Symbol *> &symbols);
+ void collectPrivatizedSymbols(
+ std::optional<semantics::Symbol::Flag> flag,
+ const llvm::SetVector<const semantics::Symbol *> &allSymbols,
+ const llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions,
+ llvm::SetVector<const semantics::Symbol *> *symbols = nullptr);
+ void
+ collectSymbols(semantics::Symbol::Flag flag,
+ llvm::SetVector<const semantics::Symbol *> *symbols = nullptr);
void collectSymbolsInNestedRegions(
lower::pft::Evaluation &eval, semantics::Symbol::Flag flag,
llvm::SetVector<const semantics::Symbol *> &symbolsInNestedRegions);
@@ -126,6 +130,7 @@ class DataSharingProcessor {
void collectDefaultSymbols();
void collectImplicitSymbols();
void collectPreDeterminedSymbols();
+ void collectIndirectReferences();
void privatize(mlir::omp::PrivateClauseOps *clauseOps,
std::optional<llvm::omp::Directive> dir = std::nullopt);
void copyLastPrivatize(mlir::Operation *op);
diff --git a/flang/test/Lower/OpenMP/composite_simd_linear.f90 b/flang/test/Lower/OpenMP/composite_simd_linear.f90
index ff26f8663b35f..6e6789fc4c6bd 100644
--- a/flang/test/Lower/OpenMP/composite_simd_linear.f90
+++ b/flang/test/Lower/OpenMP/composite_simd_linear.f90
@@ -106,3 +106,38 @@ subroutine teams_distribute_parallel_do
!$omp end teams distribute parallel do simd
!CHECK: } {linear_var_types = [i32, i32], omp.composite}
end subroutine teams_distribute_parallel_do
+
+subroutine parallel_simd
+!CHECK-LABEL: func @_QPparallel_simd
+!CHECK: omp.parallel private(@_QFparallel_simdEk2_private_i32 {{.*}} -> %[[ARG:.*]] : !fir.ref<i32>)
+!CHECK: %[[PRIV_K2:.*]]:2 = hlfir.declare %[[ARG]] {uniq_name = "_QFparallel_simdEk2"}
+!CHECK: omp.simd linear(%[[PRIV_K2]]#0 {{.*}})
+ integer :: k1, k2
+ !$omp parallel default(none)
+ !$omp do
+ do k1 = 1, 10
+ do k2 = 1, 10
+ end do
+ end do
+ !$omp end do
+
+ !$omp simd linear(k2)
+ do k2 = 1, 10
+ end do
+ !$omp end simd
+ !$omp end parallel
+end subroutine parallel_simd
+
+subroutine task_simd
+!CHECK-LABEL: func @_QPtask_simd
+!CHECK: omp.task private(@_QFtask_simdEk_firstprivate_i32 %{{.*}})
+!CHECK: %[[PRIV_K:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtask_simdEk"}
+!CHECK: omp.simd linear(%[[PRIV_K]]#0 : !fir.ref<i32> {{.*}})
+ integer :: k
+ !$omp task
+ !$omp simd linear(k)
+ do k = 1, 10
+ end do
+ !$omp end simd
+ !$omp end task
+end subroutine task_simd
diff --git a/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90 b/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
index a6de35786cf64..72e03aedea2e0 100644
--- a/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
+++ b/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
@@ -260,3 +260,46 @@ subroutine firstpriv_lastpriv_int2(arg1)
!$OMP END PARALLEL
print *, arg1
end subroutine
+
+! Check that LASTPRIVATE updates the private copy of `i` when used inside
+! nested PARALLEL constructs in which `i` is private.
+!CHECK-LABEL: func @_QPlastprivate_nested_parallel()
+!CHECK: %[[I:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFlastprivate_nested_parallelEi"} :
+!CHECK-SAME: (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+!CHECK: omp.parallel private(@_QFlastprivate_nested_parallelEi_private_i32 %[[I]]#0 {{.*}})
+!CHECK: %[[PRIV_I:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFlastprivate_nested_parallelEi"} :
+!CHECK-SAME: (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+!CHECK: omp.parallel {
+!CHECK: omp.wsloop private(@_QFlastprivate_nested_parallelEi_private_i32 %[[PRIV_I]]#0 {{.*}})
+!CHECK: hlfir.assign %{{.*}} to %[[PRIV_I]]#0
+
+subroutine lastprivate_nested_parallel()
+ integer :: i
+
+ !$OMP PARALLEL DEFAULT(PRIVATE)
+ !$OMP PARALLEL
+ !$OMP DO LASTPRIVATE(i)
+ do i = 1, 5
+ end do
+ !$OMP END PARALLEL
+ !$OMP END PARALLEL
+end subroutine
+
+!CHECK-LABEL: func @_QPlastprivate_nested_parallel2()
+!CHECK: omp.parallel {
+!CHECK: omp.wsloop private(@_QFlastprivate_nested_parallel2Ei_private_i32 {{.*}})
+!CHECK: %[[PRIV_I:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFlastprivate_nested_parallel2Ei"}
+!CHECK: omp.parallel {
+!CHECK: omp.wsloop private(@_QFlastprivate_nested_parallel2Ei_private_i32 %[[PRIV_I]]#0 {{.*}})
+!CHECK: hlfir.assign %{{.*}} to %[[PRIV_I]]#0
+
+subroutine lastprivate_nested_parallel2()
+ integer :: i, j, k
+
+ !$omp parallel do lastprivate(i)
+ do j = 1, 10
+ !$omp parallel do lastprivate(i)
+ do k = 2, 20
+ end do
+ end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/privatize_predetermined_only_when_defined_by_eval.f90 b/flang/test/Lower/OpenMP/privatize_predetermined_only_when_defined_by_eval.f90
deleted file mode 100644
index 5e9e622a45ef6..0000000000000
--- a/flang/test/Lower/OpenMP/privatize_predetermined_only_when_defined_by_eval.f90
+++ /dev/null
@@ -1,35 +0,0 @@
-! Fixes a regression uncovered by Fujitsu test 0686_0024.f90. In particular,
-! verifies that a pre-determined symbol is only privatized by its defining
-! evaluation (e.g. the loop for which the symbol was marked as pre-determined).
-
-! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
-
-subroutine privatize_predetermined_when_defined_by_eval
- integer::i,ii
- integer::j
-
- !$omp parallel
- !$omp do lastprivate(ii)
- do i=1,10
- do ii=1,10
- enddo
- enddo
-
- !$omp do
- do j=1,ii
- enddo
- !$omp end parallel
-end subroutine
-
-! Verify that nothing is privatized by the `omp.parallel` op.
-! CHECK: omp.parallel {
-
-! Verify that `i` and `ii` are privatized by the first loop.
-! CHECK: omp.wsloop private(@{{.*}}ii_private_i32 %{{.*}}#0 -> %{{.*}}, @{{.*}}i_private_i32 %2#0 -> %{{.*}} : {{.*}}) {
-! CHECK: }
-
-! Verify that `j` is privatized by the second loop.
-! CHECK: omp.wsloop private(@{{.*}}j_private_i32 %{{.*}}#0 -> %{{.*}} : {{.*}}) {
-! CHECK: }
-
-! CHECK: }
>From d474008432f124a611bb0a012b3ddc978441dab5 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Wed, 18 Mar 2026 18:25:47 -0300
Subject: [PATCH 2/3] Move indirect reference privatization tests to another
file
---
.../Lower/OpenMP/composite_simd_linear.f90 | 35 -----------------
.../indirect-reference-privatization.f90 | 38 +++++++++++++++++++
2 files changed, 38 insertions(+), 35 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/indirect-reference-privatization.f90
diff --git a/flang/test/Lower/OpenMP/composite_simd_linear.f90 b/flang/test/Lower/OpenMP/composite_simd_linear.f90
index 6e6789fc4c6bd..ff26f8663b35f 100644
--- a/flang/test/Lower/OpenMP/composite_simd_linear.f90
+++ b/flang/test/Lower/OpenMP/composite_simd_linear.f90
@@ -106,38 +106,3 @@ subroutine teams_distribute_parallel_do
!$omp end teams distribute parallel do simd
!CHECK: } {linear_var_types = [i32, i32], omp.composite}
end subroutine teams_distribute_parallel_do
-
-subroutine parallel_simd
-!CHECK-LABEL: func @_QPparallel_simd
-!CHECK: omp.parallel private(@_QFparallel_simdEk2_private_i32 {{.*}} -> %[[ARG:.*]] : !fir.ref<i32>)
-!CHECK: %[[PRIV_K2:.*]]:2 = hlfir.declare %[[ARG]] {uniq_name = "_QFparallel_simdEk2"}
-!CHECK: omp.simd linear(%[[PRIV_K2]]#0 {{.*}})
- integer :: k1, k2
- !$omp parallel default(none)
- !$omp do
- do k1 = 1, 10
- do k2 = 1, 10
- end do
- end do
- !$omp end do
-
- !$omp simd linear(k2)
- do k2 = 1, 10
- end do
- !$omp end simd
- !$omp end parallel
-end subroutine parallel_simd
-
-subroutine task_simd
-!CHECK-LABEL: func @_QPtask_simd
-!CHECK: omp.task private(@_QFtask_simdEk_firstprivate_i32 %{{.*}})
-!CHECK: %[[PRIV_K:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtask_simdEk"}
-!CHECK: omp.simd linear(%[[PRIV_K]]#0 : !fir.ref<i32> {{.*}})
- integer :: k
- !$omp task
- !$omp simd linear(k)
- do k = 1, 10
- end do
- !$omp end simd
- !$omp end task
-end subroutine task_simd
diff --git a/flang/test/Lower/OpenMP/indirect-reference-privatization.f90 b/flang/test/Lower/OpenMP/indirect-reference-privatization.f90
new file mode 100644
index 0000000000000..fabc835554b29
--- /dev/null
+++ b/flang/test/Lower/OpenMP/indirect-reference-privatization.f90
@@ -0,0 +1,38 @@
+! RUN: %flang_fc1 -fopenmp -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+!CHECK-LABEL: func @_QPparallel_simd
+!CHECK: omp.parallel private(@_QFparallel_simdEk2_private_i32 {{.*}} -> %[[ARG:.*]] : !fir.ref<i32>)
+!CHECK: %[[PRIV_K2:.*]]:2 = hlfir.declare %[[ARG]] {uniq_name = "_QFparallel_simdEk2"}
+!CHECK: omp.simd linear(%[[PRIV_K2]]#0 {{.*}})
+
+subroutine parallel_simd
+ integer :: k1, k2
+ !$omp parallel default(none)
+ !$omp do
+ do k1 = 1, 10
+ do k2 = 1, 10
+ end do
+ end do
+ !$omp end do
+
+ !$omp simd linear(k2)
+ do k2 = 1, 10
+ end do
+ !$omp end simd
+ !$omp end parallel
+end subroutine parallel_simd
+
+!CHECK-LABEL: func @_QPtask_simd
+!CHECK: omp.task private(@_QFtask_simdEk_firstprivate_i32 %{{.*}})
+!CHECK: %[[PRIV_K:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtask_simdEk"}
+!CHECK: omp.simd linear(%[[PRIV_K]]#0 : !fir.ref<i32> {{.*}})
+
+subroutine task_simd
+ integer :: k
+ !$omp task
+ !$omp simd linear(k)
+ do k = 1, 10
+ end do
+ !$omp end simd
+ !$omp end task
+end subroutine task_simd
>From fa5852f739732e36fa647c0c1378b6095315d543 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Thu, 19 Mar 2026 14:35:53 +0000
Subject: [PATCH 3/3] Unify IsPrivatizable() implementation
---
flang/include/flang/Semantics/openmp-utils.h | 1 +
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 12 +++------
flang/lib/Semantics/openmp-utils.cpp | 22 ++++++++++++++++
flang/lib/Semantics/resolve-directives.cpp | 25 ++-----------------
4 files changed, 28 insertions(+), 32 deletions(-)
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index cd599237e4182..9fd2401dfbc79 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -84,6 +84,7 @@ bool IsExtendedListItem(const Symbol &sym);
bool IsVariableListItem(const Symbol &sym);
bool IsTypeParamInquiry(const Symbol &sym);
bool IsStructureComponent(const Symbol &sym);
+bool IsPrivatizable(const Symbol &sym);
bool IsVarOrFunctionRef(const MaybeExpr &expr);
bool IsWholeAssumedSizeArray(const parser::OmpObject &object);
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index ef5d5f86c875c..074370576e3ca 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -27,6 +27,7 @@
#include "flang/Parser/openmp-utils.h"
#include "flang/Semantics/attr.h"
#include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/openmp-utils.h"
#include "flang/Semantics/tools.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallSet.h"
@@ -482,14 +483,6 @@ collectScopes(semantics::SemanticsContext &semaCtx,
return curScope;
}
-static bool isPrivatizable(const semantics::Symbol &sym) {
- return !semantics::IsProcedure(sym) &&
- !sym.GetUltimate().has<semantics::DerivedTypeDetails>() &&
- !sym.GetUltimate().has<semantics::NamelistDetails>() &&
- !semantics::IsImpliedDoIndex(sym.GetUltimate()) &&
- !semantics::IsStmtFunction(sym);
-}
-
void DataSharingProcessor::collectPrivatizedSymbols(
std::optional<semantics::Symbol::Flag> flag,
const llvm::SetVector<const semantics::Symbol *> &allSymbols,
@@ -546,7 +539,8 @@ void DataSharingProcessor::collectPrivatizedSymbols(
for (const auto *sym : allSymbols) {
assert(curScope && "couldn't find current scope");
- if (isPrivatizable(*sym) && !symbolsInNestedRegions.contains(sym) &&
+ if (semantics::omp::IsPrivatizable(*sym) &&
+ !symbolsInNestedRegions.contains(sym) &&
!explicitlyPrivatizedSymbols.contains(sym) &&
shouldCollectSymbol(sym) && clauseScopes.contains(&sym->owner())) {
allPrivatizedSymbols.insert(sym);
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 8abf008a72147..f8c6a259d4dd7 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -205,6 +205,28 @@ bool IsStructureComponent(const Symbol &sym) {
return sym.owner().kind() == Scope::Kind::DerivedType;
}
+bool IsPrivatizable(const Symbol &sym) {
+ auto *misc{sym.detailsIf<MiscDetails>()};
+ return IsVariableName(sym) && !IsProcedure(sym) && !IsStmtFunction(sym) &&
+ !IsNamedConstant(sym) &&
+ ( // OpenMP 5.2, 5.1.1: Assumed-size arrays are shared
+ !semantics::IsAssumedSizeArray(sym) ||
+ // If CrayPointer is among the DSA list then the
+ // CrayPointee is Privatizable
+ sym.test(Symbol::Flag::CrayPointee)) &&
+ !sym.owner().IsDerivedType() &&
+ sym.owner().kind() != Scope::Kind::ImpliedDos &&
+ sym.owner().kind() != Scope::Kind::Forall &&
+ !sym.detailsIf<semantics::AssocEntityDetails>() &&
+ !sym.detailsIf<semantics::NamelistDetails>() &&
+ (!misc ||
+ (misc->kind() != MiscDetails::Kind::ComplexPartRe &&
+ misc->kind() != MiscDetails::Kind::ComplexPartIm &&
+ misc->kind() != MiscDetails::Kind::KindParamInquiry &&
+ misc->kind() != MiscDetails::Kind::LenParamInquiry &&
+ misc->kind() != MiscDetails::Kind::ConstructName));
+}
+
bool IsVarOrFunctionRef(const MaybeExpr &expr) {
if (expr) {
return evaluate::UnwrapProcedureRef(*expr) != nullptr ||
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 96f66246ac676..6bccaad15ad3e 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2583,27 +2583,6 @@ void OmpAttributeVisitor::Post(const parser::OpenMPAllocatorsConstruct &x) {
PopContext();
}
-static bool IsPrivatizable(const Symbol *sym) {
- auto *misc{sym->detailsIf<MiscDetails>()};
- return IsVariableName(*sym) && !IsProcedure(*sym) && !IsNamedConstant(*sym) &&
- ( // OpenMP 5.2, 5.1.1: Assumed-size arrays are shared
- !semantics::IsAssumedSizeArray(*sym) ||
- // If CrayPointer is among the DSA list then the
- // CrayPointee is Privatizable
- sym->test(Symbol::Flag::CrayPointee)) &&
- !sym->owner().IsDerivedType() &&
- sym->owner().kind() != Scope::Kind::ImpliedDos &&
- sym->owner().kind() != Scope::Kind::Forall &&
- !sym->detailsIf<semantics::AssocEntityDetails>() &&
- !sym->detailsIf<semantics::NamelistDetails>() &&
- (!misc ||
- (misc->kind() != MiscDetails::Kind::ComplexPartRe &&
- misc->kind() != MiscDetails::Kind::ComplexPartIm &&
- misc->kind() != MiscDetails::Kind::KindParamInquiry &&
- misc->kind() != MiscDetails::Kind::LenParamInquiry &&
- misc->kind() != MiscDetails::Kind::ConstructName));
-}
-
static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol,
const Symbol::Flags &dsa, const Symbol::Flags &dataSharingAttributeFlags,
const Symbol::Flags &dataMappingAttributeFlags,
@@ -2671,7 +2650,7 @@ static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol,
void OmpAttributeVisitor::CreateImplicitSymbols(
const parser::Name &name, const Symbol *symbol) {
- if (!IsPrivatizable(symbol)) {
+ if (!omp::IsPrivatizable(*symbol)) {
return;
}
@@ -2947,7 +2926,7 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
auto *symbol{name.symbol};
if (symbol && WithinConstruct()) {
- if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol) &&
+ if (omp::IsPrivatizable(*symbol) && !IsObjectWithDSA(*symbol) &&
!IsLocalInsideScope(*symbol, currScope())) {
// TODO: create a separate function to go through the rules for
// predetermined, explicitly determined, and implicitly
More information about the flang-commits
mailing list