[flang-commits] [flang] [flang][OpenMP] Leave local automatic variables alone (PR #178739)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Tue Feb 3 12:01:01 PST 2026


https://github.com/kparzysz updated https://github.com/llvm/llvm-project/pull/178739

>From 29e43fbf66146f94ef3bcd64a4101e3bbdb808a2 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 29 Jan 2026 13:29:26 -0600
Subject: [PATCH 1/2] [flang][OpenMP] Leave local automatic variables alone

There is code in resolve-directives.cpp that tries to apply DSA flags
to symbols encountered inside constructs. This code was written with
the assumption that all such symbols will be declared outside of the
construct.
When a symbol declared in a BLOCK construct nested in a construct was
found, the code would attempt to either privatize or share it in the
enclosing construct (where the symbol didn't exist) leading to trouble.

BLOCK constructs (and thus the possibility of having local variables)
was introduced in F2008.
The first OpenMP spec that considered F2008 was 5.0, where the behavior
of the BLOCK construct was explicitly left unspecified.
>From OpenMP 5.1 onwards, all local non-static variables are private
in the construct enclosing the declaration. This PR extends this behavior
retroactively to all prior OpenMP versions.

Fixes https://github.com/llvm/llvm-project/issues/178613
---
 flang/lib/Semantics/resolve-directives.cpp    | 12 +++++-
 .../test/Semantics/OpenMP/local-variables.f90 | 41 +++++++++++++++++++
 2 files changed, 52 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Semantics/OpenMP/local-variables.f90

diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 13cce518aae94..d887fb2c2c7fe 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -3077,7 +3077,17 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
         return;
     }
 
-    CreateImplicitSymbols(symbol);
+    // We should only create any additional symbols, if the one mentioned
+    // in the source code was declared outside of the construct. This was
+    // always the case before Fortran 2008. F2008 introduced the BLOCK
+    // construct, and allowed local variable declarations.
+    // In OpenMP local (non-static) variables are always private in a given
+    // construct, if they are declared inside the construct. In those cases
+    // we don't need to do anything here (i.e. no flags are needed or
+    // anything else).
+    if (symbol->owner().Contains(currScope())) {
+      CreateImplicitSymbols(symbol);
+    }
   } // within OpenMP construct
 }
 
diff --git a/flang/test/Semantics/OpenMP/local-variables.f90 b/flang/test/Semantics/OpenMP/local-variables.f90
new file mode 100644
index 0000000000000..9a605d8b94edd
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/local-variables.f90
@@ -0,0 +1,41 @@
+!RUN: %flang_fc1 -fdebug-unparse-with-symbols -fopenmp %s | FileCheck %s
+
+!Make sure that the local `bbb`s are their own entities.
+
+!CHECK:      !DEF: /foo (Subroutine) Subprogram
+!CHECK-NEXT: subroutine foo
+!CHECK-NEXT:  !DEF: /foo/i ObjectEntity INTEGER(4)
+!CHECK-NEXT:  integer i
+!CHECK-NEXT: !$omp parallel
+!CHECK-NEXT:  block
+!CHECK-NEXT:   block
+!CHECK-NEXT:    !DEF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:    integer bbb
+!CHECK-NEXT:    !REF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb
+!CHECK-NEXT:    bbb = 1
+!CHECK-NEXT:   end block
+!CHECK-NEXT:   block
+!CHECK-NEXT:    !DEF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:    integer bbb
+!CHECK-NEXT:    !REF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb
+!CHECK-NEXT:    bbb = 2
+!CHECK-NEXT:   end block
+!CHECK-NEXT:  end block
+!CHECK-NEXT: !$omp end parallel
+!CHECK-NEXT: end subroutine
+
+subroutine foo()
+  integer :: i
+  !$omp parallel
+  block
+    block
+      integer :: bbb
+      bbb = 1
+    end block
+    block
+      integer :: bbb
+      bbb = 2
+    end block
+  end block
+  !$omp end parallel
+end subroutine

>From 90ce6aa00fbebbc6955e5c8c3488c07473cca18e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 3 Feb 2026 11:02:17 -0600
Subject: [PATCH 2/2] Add more checks for local variables

---
 flang/lib/Semantics/resolve-directives.cpp    | 63 +++++++-----
 .../test/Semantics/OpenMP/local-variables.f90 | 98 ++++++++++++++-----
 2 files changed, 115 insertions(+), 46 deletions(-)

diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index d887fb2c2c7fe..3f0294e287fed 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -409,6 +409,29 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   explicit OmpAttributeVisitor(SemanticsContext &context)
       : DirectiveAttributeVisitor(context) {}
 
+  static bool HasStaticStorageDuration(const Symbol &symbol) {
+    auto &ultSym = symbol.GetUltimate();
+    // Module-scope variable
+    return ultSym.owner().kind() == Scope::Kind::Module ||
+        // Data statement variable
+        ultSym.flags().test(Symbol::Flag::InDataStmt) ||
+        // Save attribute variable
+        ultSym.attrs().test(Attr::SAVE) ||
+        // Referenced in a common block
+        ultSym.flags().test(Symbol::Flag::InCommonBlock);
+  }
+
+  // Recognize symbols that are not created as a part of the OpenMP data-
+  // sharing processing, and that are declared inside of the construct.
+  // These symbols are predetermined private, but they shouldn't be marked
+  // in any special way, because there is nothing to be done for them.
+  // They are not symbols for which private copies need to be created,
+  // they are already themselves private.
+  static bool IsLocalInsideScope(const Symbol &symbol, const Scope &scope) {
+    return symbol.owner() != scope && scope.Contains(symbol.owner()) &&
+        !HasStaticStorageDuration(symbol);
+  }
+
   template <typename A> void Walk(const A &x) { parser::Walk(x, *this); }
   template <typename A> bool Pre(const A &) { return true; }
   template <typename A> void Post(const A &) {}
@@ -2107,6 +2130,9 @@ void OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct(
       break;
     }
   }
+  if (IsLocalInsideScope(*iv.symbol, targetIt->scope)) {
+    return;
+  }
   // If this symbol already has a data-sharing attribute then there is nothing
   // to do here.
   if (const Symbol * symbol{iv.symbol}) {
@@ -2431,14 +2457,14 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
           }
         }
         // go through all the nested do-loops and resolve index variables
-        const parser::Name *iv{GetLoopIndex(*loop)};
-        if (iv) {
-          if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
-            SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
-            iv->symbol = symbol; // adjust the symbol within region
-            AddToContextObjectWithDSA(*symbol, ivDSA);
+        if (const parser::Name *iv{GetLoopIndex(*loop)}) {
+          if (!iv->symbol || !IsLocalInsideScope(*iv->symbol, currScope())) {
+            if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
+              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;
@@ -2682,20 +2708,6 @@ static bool IsPrivatizable(const Symbol *sym) {
               misc->kind() != MiscDetails::Kind::ConstructName));
 }
 
-static bool IsSymbolStaticStorageDuration(const Symbol &symbol) {
-  LLVM_DEBUG(llvm::dbgs() << "IsSymbolStaticStorageDuration(" << symbol.name()
-                          << "):\n");
-  auto ultSym = symbol.GetUltimate();
-  // Module-scope variable
-  return (ultSym.owner().kind() == Scope::Kind::Module) ||
-      // Data statement variable
-      (ultSym.flags().test(Symbol::Flag::InDataStmt)) ||
-      // Save attribute variable
-      (ultSym.attrs().test(Attr::SAVE)) ||
-      // Referenced in a common block
-      (ultSym.flags().test(Symbol::Flag::InCommonBlock));
-}
-
 static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol,
     const Symbol::Flags &dsa, const Symbol::Flags &dataSharingAttributeFlags,
     const Symbol::Flags &dataMappingAttributeFlags,
@@ -2854,7 +2866,9 @@ void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) {
     bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
     bool parallelDir = llvm::omp::topParallelSet.test(dirContext.directive);
     bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive);
-    bool isStaticStorageDuration = IsSymbolStaticStorageDuration(*symbol);
+    bool isStaticStorageDuration = HasStaticStorageDuration(*symbol);
+    LLVM_DEBUG(llvm::dbgs()
+        << "HasStaticStorageDuration(" << symbol->name() << "):\n");
 
     if (dsa.any()) {
       if (parallelDir || taskGenDir || teamsDir) {
@@ -3008,7 +3022,8 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
   auto *symbol{name.symbol};
 
   if (symbol && WithinConstruct()) {
-    if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol)) {
+    if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol) &&
+        !IsLocalInsideScope(*symbol, currScope())) {
       // TODO: create a separate function to go through the rules for
       //       predetermined, explicitly determined, and implicitly
       //       determined data-sharing attributes (2.15.1.1).
@@ -3085,7 +3100,7 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
     // construct, if they are declared inside the construct. In those cases
     // we don't need to do anything here (i.e. no flags are needed or
     // anything else).
-    if (symbol->owner().Contains(currScope())) {
+    if (!IsLocalInsideScope(*symbol, currScope())) {
       CreateImplicitSymbols(symbol);
     }
   } // within OpenMP construct
diff --git a/flang/test/Semantics/OpenMP/local-variables.f90 b/flang/test/Semantics/OpenMP/local-variables.f90
index 9a605d8b94edd..8e7a220319605 100644
--- a/flang/test/Semantics/OpenMP/local-variables.f90
+++ b/flang/test/Semantics/OpenMP/local-variables.f90
@@ -2,32 +2,84 @@
 
 !Make sure that the local `bbb`s are their own entities.
 
-!CHECK:      !DEF: /foo (Subroutine) Subprogram
-!CHECK-NEXT: subroutine foo
-!CHECK-NEXT:  !DEF: /foo/i ObjectEntity INTEGER(4)
-!CHECK-NEXT:  integer i
-!CHECK-NEXT: !$omp parallel
-!CHECK-NEXT:  block
-!CHECK-NEXT:   block
-!CHECK-NEXT:    !DEF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb ObjectEntity INTEGER(4)
-!CHECK-NEXT:    integer bbb
-!CHECK-NEXT:    !REF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb
-!CHECK-NEXT:    bbb = 1
-!CHECK-NEXT:   end block
-!CHECK-NEXT:   block
-!CHECK-NEXT:    !DEF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb ObjectEntity INTEGER(4)
+!CHECK-LABEL:  !DEF: /f00 (Subroutine) Subprogram
+!CHECK-NEXT:   subroutine f00
+!CHECK-NEXT:    !DEF: /f00/i ObjectEntity INTEGER(4)
+!CHECK-NEXT:    integer i
+!CHECK-NEXT:   !$omp parallel
+!CHECK-NEXT:    block
+!CHECK-NEXT:     block
+!CHECK-NEXT:      !DEF: /f00/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:      integer bbb
+!CHECK-NEXT:      !REF: /f00/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb
+!CHECK-NEXT:      bbb = 1
+!CHECK-NEXT:     end block
+!CHECK-NEXT:     block
+!CHECK-NEXT:      !DEF: /f00/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:      integer bbb
+!CHECK-NEXT:      !REF: /f00/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb
+!CHECK-NEXT:      bbb = 2
+!CHECK-NEXT:     end block
+!CHECK-NEXT:    end block
+!CHECK-NEXT:   !$omp end parallel
+!CHECK-NEXT:   end subroutine
+
+subroutine f00()
+  integer :: i
+  !$omp parallel
+  block
+    block
+      integer :: bbb
+      bbb = 1
+    end block
+    block
+      integer :: bbb
+      bbb = 2
+    end block
+  end block
+  !$omp end parallel
+end subroutine
+
+
+!CHECK-LABEL:  !DEF: /f01 (Subroutine) Subprogram
+!CHECK-NEXT:   subroutine f01
+!CHECK-NEXT:    !DEF: /f01/i ObjectEntity INTEGER(4)
+!CHECK-NEXT:    integer i
+!CHECK-NEXT:    !DEF: /f01/bbb ObjectEntity INTEGER(4)
 !CHECK-NEXT:    integer bbb
-!CHECK-NEXT:    !REF: /foo/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb
-!CHECK-NEXT:    bbb = 2
-!CHECK-NEXT:   end block
-!CHECK-NEXT:  end block
-!CHECK-NEXT: !$omp end parallel
-!CHECK-NEXT: end subroutine
-
-subroutine foo()
+!CHECK-NEXT:    !REF: /f01/bbb
+!CHECK-NEXT:    bbb = 0
+!CHECK-NEXT:   !$omp parallel
+!CHECK-NEXT:    block
+!CHECK-NEXT:     !DEF: /f01/OtherConstruct1/bbb (OmpShared) HostAssoc INTEGER(4)
+!CHECK-NEXT:     bbb = 1234
+!CHECK-NEXT:     block
+!CHECK-NEXT:      !DEF: /f01/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:      integer bbb
+!CHECK-NEXT:      !REF: /f01/OtherConstruct1/BlockConstruct1/BlockConstruct1/bbb
+!CHECK-NEXT:      bbb = 1
+!CHECK-NEXT:     end block
+!CHECK-NEXT:     block
+!CHECK-NEXT:      !DEF: /f01/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb ObjectEntity INTEGER(4)
+!CHECK-NEXT:      integer bbb
+!CHECK-NEXT:      !REF: /f01/OtherConstruct1/BlockConstruct1/BlockConstruct2/bbb
+!CHECK-NEXT:      bbb = 2
+!CHECK-NEXT:     end block
+!CHECK-NEXT:    end block
+!CHECK-NEXT:   !$omp end parallel
+!CHECK-NEXT:    !REF: /f01/bbb
+!CHECK-NEXT:    print *, bbb
+!CHECK-NEXT:   end subroutine
+
+subroutine f01()
   integer :: i
+  integer :: bbb
+
+  bbb = 0
+
   !$omp parallel
   block
+    bbb = 1234
     block
       integer :: bbb
       bbb = 1
@@ -38,4 +90,6 @@ subroutine foo()
     end block
   end block
   !$omp end parallel
+
+  print *, bbb
 end subroutine



More information about the flang-commits mailing list