[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