[flang-commits] [flang] [flang][OpenMP] Check DEFAULT(NONE) on metadirective loop variants (PR #210172)
via flang-commits
flang-commits at lists.llvm.org
Thu Jul 16 20:46:18 PDT 2026
https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/210172
>From e5f3ebe2e8a21877d8b91c4690ffd24c6f16ca04 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Thu, 16 Jul 2026 22:42:51 -0500
Subject: [PATCH] [flang][OpenMP] Check DEFAULT(NONE) on metadirective loop
variants
Standalone metadirectives and their associated loops are separate parse-tree
nodes. For example:
```fortran
!$omp metadirective &
!$omp& when(implementation={vendor(llvm)}: &
!$omp& parallel do default(none) shared(n, a)) default(nothing)
do i = 1, n
a(i) = x
end do
```
Unlike an ordinary PARALLEL DO, the loop is not nested under the directive:
```text
METADIRECTIVE
`-- WHEN
`-- PARALLEL DO DEFAULT(NONE) SHARED(n, a)
DO
`-- a(i) = x
```
Name resolution therefore leaves the variant context before visiting the loop
and does not diagnose the missing data-sharing attribute for `x`.
After associating a metadirective with its loop, validate DEFAULT(NONE) for
each potentially selectable loop variant. Preserve the existing rules for
predetermined and explicit data-sharing attributes, static locals, nested
constructs, specification expressions, and statically inapplicable variants.
Add semantic tests for variant selection and data-sharing behavior.
---
flang/lib/Semantics/check-omp-structure.h | 4 +
flang/lib/Semantics/check-omp-variant.cpp | 167 ++++++++++++-
.../OpenMP/metadirective-default-none.f90 | 224 ++++++++++++++++++
3 files changed, 394 insertions(+), 1 deletion(-)
create mode 100644 flang/test/Semantics/OpenMP/metadirective-default-none.f90
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 7248ca79f061e..83ad79c432471 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -393,6 +393,9 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void CheckCrayPointee(const parser::OmpObjectList &objectList,
llvm::StringRef clause, bool suggestToUseCrayPointer = true);
void GetSymbolsInObjectList(const parser::OmpObjectList &, SymbolSourceMap &);
+ void CheckDefaultNoneInAssociatedLoop(
+ const parser::OmpDirectiveSpecification &, const parser::DoConstruct &,
+ UnorderedSymbolSet &diagnosed);
void CheckDefinableObjects(SymbolSourceMap &, const llvm::omp::Clause);
void CheckCopyingPolymorphicAllocatable(
SymbolSourceMap &, const llvm::omp::Clause);
@@ -504,6 +507,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
struct MetadirectiveLoopVariant {
const parser::traits::OmpContextSelectorSpecification *selector;
const parser::OmpDirectiveSpecification *spec;
+ bool checkDefaultNoneInAssociatedLoop;
};
std::vector<MetadirectiveLoopVariant> metadirectiveLoopVariants_;
std::vector<std::size_t> metadirectiveVariantScopeStarts_;
diff --git a/flang/lib/Semantics/check-omp-variant.cpp b/flang/lib/Semantics/check-omp-variant.cpp
index 994a49163914d..c05a9be661852 100644
--- a/flang/lib/Semantics/check-omp-variant.cpp
+++ b/flang/lib/Semantics/check-omp-variant.cpp
@@ -17,6 +17,7 @@
#include "flang/Common/visit.h"
#include "flang/Evaluate/characteristics.h"
#include "flang/Evaluate/check-expression.h"
+#include "flang/Evaluate/tools.h"
#include "flang/Parser/characters.h"
#include "flang/Parser/message.h"
#include "flang/Parser/parse-tree.h"
@@ -41,6 +42,159 @@ namespace Fortran::semantics {
using namespace Fortran::semantics::omp;
+namespace {
+
+bool HasDefaultNone(const parser::OmpDirectiveSpecification &spec) {
+ using DataSharingAttribute = parser::OmpDefaultClause::DataSharingAttribute;
+ const parser::OmpClause *clause{
+ parser::omp::FindClause(spec, llvm::omp::Clause::OMPC_default)};
+ if (!clause) {
+ return false;
+ }
+ const auto &defaultClause{std::get<parser::OmpClause::Default>(clause->u)};
+ const auto *dsa{std::get_if<DataSharingAttribute>(&defaultClause.v.u)};
+ return dsa && *dsa == DataSharingAttribute::None;
+}
+
+bool HasStaticStorageDuration(const Symbol &symbol) {
+ const Symbol &ultimate{symbol.GetUltimate()};
+ return semantics::IsSaved(ultimate) ||
+ ultimate.test(Symbol::Flag::InCommonBlock);
+}
+
+bool IsLocalInsideScope(const Symbol &symbol, const Scope &scope) {
+ const Symbol &ultimate{symbol.GetUltimate()};
+ return ultimate.owner() != scope && scope.Contains(ultimate.owner()) &&
+ !HasStaticStorageDuration(ultimate);
+}
+
+bool HasNestedPrivateDSA(const Symbol &symbol, const Scope &scope) {
+ if (symbol.owner() == scope || !scope.Contains(symbol.owner())) {
+ return false;
+ }
+ if (symbol.test(Symbol::Flag::OmpPreDetermined)) {
+ return true;
+ }
+ static const Symbol::Flags privatizingFlags{Symbol::Flag::OmpPrivate,
+ Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate,
+ Symbol::Flag::OmpLinear, Symbol::Flag::OmpReduction,
+ Symbol::Flag::OmpInReduction};
+ return symbol.test(Symbol::Flag::OmpExplicit) &&
+ (symbol.flags() & privatizingFlags).any();
+}
+
+class MetadirectiveDefaultNoneChecker {
+public:
+ MetadirectiveDefaultNoneChecker(SemanticsContext &context, const Scope &scope,
+ const SymbolSourceMap &explicitDSA, UnorderedSymbolSet &diagnosed)
+ : context_{context}, scope_{scope}, explicitDSA_{explicitDSA},
+ diagnosed_{diagnosed} {}
+
+ template <typename T> bool Pre(const T &) { return true; }
+ template <typename T> void Post(const T &) {}
+
+ bool Pre(const parser::DoConstruct &loop) {
+ for (const omp::LoopControl &control : omp::GetLoopControls(loop)) {
+ if (const Symbol *symbol{control.iv.symbol}) {
+ loopIndices_.insert(symbol->GetUltimate());
+ }
+ }
+ return true;
+ }
+
+ bool Pre(const parser::SpecificationPart &) {
+ ++declarativeNesting_;
+ return true;
+ }
+ void Post(const parser::SpecificationPart &) { --declarativeNesting_; }
+
+ bool Pre(const parser::DataStmt &) {
+ ++declarativeNesting_;
+ return true;
+ }
+ void Post(const parser::DataStmt &) { --declarativeNesting_; }
+
+ bool Pre(const parser::Expr &) {
+ ++expressionNesting_;
+ return true;
+ }
+ void Post(const parser::Expr &) { --expressionNesting_; }
+
+ bool Pre(const parser::Name &name) {
+ // A declarative name is not a reference, but a name in a specification
+ // expression can require an explicit DSA.
+ if ((declarativeNesting_ > 0 && expressionNesting_ == 0) || !name.symbol) {
+ return true;
+ }
+
+ const Symbol &symbol{*name.symbol};
+ const Symbol &ultimate{symbol.GetUltimate()};
+ if (!omp::IsPrivatizable(ultimate) ||
+ ultimate.test(Symbol::Flag::OmpThreadprivate) ||
+ loopIndices_.count(ultimate) != 0 ||
+ HasNestedPrivateDSA(symbol, scope_) ||
+ IsLocalInsideScope(symbol, scope_)) {
+ return true;
+ }
+
+ const Symbol *requiredSymbol{&ultimate};
+ if (ultimate.test(Symbol::Flag::CrayPointee)) {
+ requiredSymbol = &semantics::GetCrayPointer(ultimate).GetUltimate();
+ }
+ if (explicitDSA_.count(&ultimate) != 0 ||
+ explicitDSA_.count(requiredSymbol) != 0 ||
+ !diagnosed_.insert(*requiredSymbol).second) {
+ return true;
+ }
+
+ if (ultimate.test(Symbol::Flag::CrayPointee)) {
+ context_.Say(name.source,
+ "The DEFAULT(NONE) clause requires that the Cray Pointer '%s' must be listed in a data-sharing attribute clause"_err_en_US,
+ requiredSymbol->name());
+ } else {
+ context_.Say(name.source,
+ "The DEFAULT(NONE) clause requires that '%s' must be listed in a data-sharing attribute clause"_err_en_US,
+ ultimate.name());
+ }
+ return true;
+ }
+
+private:
+ SemanticsContext &context_;
+ const Scope &scope_;
+ const SymbolSourceMap &explicitDSA_;
+ UnorderedSymbolSet &diagnosed_;
+ UnorderedSymbolSet loopIndices_;
+ int declarativeNesting_{0};
+ int expressionNesting_{0};
+};
+
+} // namespace
+
+void OmpStructureChecker::CheckDefaultNoneInAssociatedLoop(
+ const parser::OmpDirectiveSpecification &spec,
+ const parser::DoConstruct &rootLoop, UnorderedSymbolSet &diagnosed) {
+ if (!HasDefaultNone(spec)) {
+ return;
+ }
+
+ SymbolSourceMap explicitDSA;
+ unsigned version{context_.langOptions().OpenMPVersion};
+ for (const parser::OmpClause &clause : spec.Clauses().v) {
+ if (llvm::omp::isDataSharingAttributeClause(clause.Id(), version)) {
+ if (const parser::OmpObjectList *objects{
+ parser::omp::GetOmpObjectList(clause)}) {
+ GetSymbolsInObjectList(*objects, explicitDSA);
+ }
+ }
+ }
+
+ const Scope &scope{context_.FindScope(*parser::GetSource(rootLoop))};
+ MetadirectiveDefaultNoneChecker checker{
+ context_, scope, explicitDSA, diagnosed};
+ parser::Walk(rootLoop, checker);
+}
+
void OmpStructureChecker::Enter(const parser::OmpClause::When &x) {
OmpVerifyModifiers(
x.v, llvm::omp::OMPC_when, GetContext().clauseSource, context_);
@@ -569,8 +723,11 @@ void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
}
llvm::omp::Directive dirId{x.DirId()};
+ bool checkDefaultNoneInAssociatedLoop{
+ GetDirectiveNest(MetadirectiveNest) != 0};
if (const parser::OpenMPConstruct *meta{GetCurrentConstruct()}) {
if (parser::Unwrap<parser::OmpDelimitedMetadirectiveDirective>(meta->u)) {
+ checkDefaultNoneInAssociatedLoop = false;
unsigned version{context_.langOptions().OpenMPVersion};
switch (llvm::omp::getDirectiveAssociation(dirId)) {
case llvm::omp::Association::Block:
@@ -594,7 +751,8 @@ void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
// Record each variant directive. A loop-associated one is later validated
// against the loop nest that follows the metadirective.
if (dirId != llvm::omp::Directive::OMPD_metadirective) {
- metadirectiveLoopVariants_.push_back({currentWhenSelector_, &x});
+ metadirectiveLoopVariants_.push_back(
+ {currentWhenSelector_, &x, checkDefaultNoneInAssociatedLoop});
}
}
@@ -672,6 +830,7 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
// Build the matching context once for the static-applicability gate below.
OmpVariantMatchContext matchContext{context_};
+ UnorderedSymbolSet defaultNoneDiagnosed;
for (const MetadirectiveLoopVariant &variant : variants) {
const parser::OmpDirectiveSpecification *spec{variant.spec};
@@ -686,6 +845,12 @@ void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
continue;
}
+ // A standalone metadirective does not contain its associated loop in
+ // the parse tree, so name resolution cannot apply DEFAULT(NONE) to it.
+ if (variant.checkDefaultNoneInAssociatedLoop) {
+ CheckDefaultNoneInAssociatedLoop(*spec, rootLoop, defaultNoneDiagnosed);
+ }
+
auto [needDepth, needPerfect]{
GetAffectedNestDepthWithReason(*spec, version, &context_)};
auto haveDepth{needPerfect ? havePerfect : haveSemantic};
diff --git a/flang/test/Semantics/OpenMP/metadirective-default-none.f90 b/flang/test/Semantics/OpenMP/metadirective-default-none.f90
new file mode 100644
index 0000000000000..cde9839991356
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/metadirective-default-none.f90
@@ -0,0 +1,224 @@
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=51
+
+subroutine missing_dsa(n, a, x)
+ integer :: n, a(n), x, i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ !ERROR: The DEFAULT(NONE) clause requires that 'x' must be listed in a data-sharing attribute clause
+ a(i) = x
+ end do
+end subroutine
+
+subroutine explicit_dsa(n, a, x)
+ integer :: n, a(n), x, i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a, x)) default(nothing)
+ do i = 1, n
+ a(i) = x
+ end do
+end subroutine
+
+subroutine common_block_dsa(n)
+ integer :: n, i, x
+ common /block/ x
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, /block/)) default(nothing)
+ do i = 1, n
+ x = i
+ end do
+end subroutine
+
+! A DSA from another variant does not apply to this one.
+subroutine variant_dsa(n, a, x)
+ integer :: n, a(n), x, i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) &
+ !$omp& default(parallel do shared(x))
+ do i = 1, n
+ !ERROR: The DEFAULT(NONE) clause requires that 'x' must be listed in a data-sharing attribute clause
+ a(i) = x
+ end do
+end subroutine
+
+! A DSA on a host-associated symbol still belongs only to its own variant.
+subroutine host_dsa(x)
+ integer :: x
+contains
+ subroutine inner(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) &
+ !$omp& default(parallel do private(x))
+ do i = 1, n
+ !ERROR: The DEFAULT(NONE) clause requires that 'x' must be listed in a data-sharing attribute clause
+ a(i) = x
+ end do
+ end subroutine
+end subroutine
+
+! A statically inapplicable variant does not constrain the loop.
+subroutine inapplicable_variant(n, a, x)
+ integer :: n, a(n), x, i
+ !$omp metadirective &
+ !$omp& when(device={kind(nohost)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ a(i) = x
+ end do
+end subroutine
+
+! A run-time condition may select the variant, so its loop is checked.
+subroutine dynamic_variant(n, a, x, flag)
+ integer :: n, a(n), x, i
+ logical :: flag
+ !$omp metadirective &
+ !$omp& when(user={condition(flag)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ !ERROR: The DEFAULT(NONE) clause requires that 'x' must be listed in a data-sharing attribute clause
+ a(i) = x
+ end do
+end subroutine
+
+! Sequential loop indices and automatic variables declared in the region have
+! predetermined data-sharing attributes.
+subroutine predetermined_dsa(n, a)
+ integer :: n, a(n), i, j
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ do j = 1, n
+ block
+ integer :: local
+ local = i + j
+ a(j) = local
+ end block
+ end do
+ end do
+end subroutine
+
+subroutine static_local(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ block
+ integer, save :: saved
+ !ERROR: The DEFAULT(NONE) clause requires that 'saved' must be listed in a data-sharing attribute clause
+ saved = i
+ a(i) = saved
+ end block
+ end do
+end subroutine
+
+subroutine implicit_saved_local(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ block
+ integer :: saved = 1
+ !ERROR: The DEFAULT(NONE) clause requires that 'saved' must be listed in a data-sharing attribute clause
+ saved = i
+ a(i) = saved
+ end block
+ end do
+end subroutine
+
+subroutine unused_static_locals(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ block
+ integer, save :: explicit_saved
+ integer :: implicit_saved = 1
+ integer :: saved, datum
+ integer, save :: volatile_saved
+ save saved
+ data datum /1/
+ volatile volatile_saved
+ a(i) = i
+ end block
+ end do
+end subroutine
+
+subroutine saved_statement_locals(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ block
+ integer :: saved, datum
+ save saved
+ data datum /1/
+ !ERROR: The DEFAULT(NONE) clause requires that 'saved' must be listed in a data-sharing attribute clause
+ saved = i
+ !ERROR: The DEFAULT(NONE) clause requires that 'datum' must be listed in a data-sharing attribute clause
+ a(i) = datum
+ end block
+ end do
+end subroutine
+
+subroutine declaration_bound(n, a, extent)
+ integer :: n, a(n), extent, i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n, a)) default(nothing)
+ do i = 1, n
+ block
+ !ERROR: The DEFAULT(NONE) clause requires that 'extent' must be listed in a data-sharing attribute clause
+ integer :: local(extent)
+ a(i) = size(local)
+ end block
+ end do
+end subroutine
+
+subroutine nested_private(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n)) default(nothing)
+ do i = 1, n
+ !$omp task private(a)
+ a(i) = i
+ !$omp end task
+ end do
+end subroutine
+
+subroutine nested_implicit(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n)) default(nothing)
+ do i = 1, n
+ !$omp task
+ !ERROR: The DEFAULT(NONE) clause requires that 'a' must be listed in a data-sharing attribute clause
+ a(i) = i
+ !$omp end task
+ end do
+end subroutine
+
+subroutine nested_shared(n, a)
+ integer :: n, a(n), i
+ !$omp metadirective &
+ !$omp& when(implementation={vendor(llvm)}: &
+ !$omp& parallel do default(none) shared(n)) default(nothing)
+ do i = 1, n
+ !ERROR: The DEFAULT(NONE) clause requires that 'a' must be listed in a data-sharing attribute clause
+ !$omp task shared(a)
+ a(i) = i
+ !$omp end task
+ end do
+end subroutine
More information about the flang-commits
mailing list