[flang-commits] [flang] [flang][OpenMP] Implicit declarations of procedures in DECLARE_TARGET (PR #201935)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Sat Jun 6 06:18:34 PDT 2026


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

>From 350a72b4b38ee72ab11f9c60e19d4a696dddbc33 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 5 Jun 2026 10:36:56 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Implicit declarations of procedures in
 DECLARE_TARGET

This replaces commit 8f5df8891840b, since it was rejecting the following
case:
```
  function baz(a)
    !$omp declare target to(baz)
    real, intent(in) :: a
    baz = a
  end

  program main
    real :: a
    !$omp declare target(baz)
    integer, save :: baz        ! error: 'baz' is already declared
  end
```
Instead of flagging an error, the 'baz' in the directive should be
resolved to the explicitly declared variable.

The original motivating example was to allow the case where the main
program (from the above snippet) looked like the following:
```
  program main
    real :: a
    !$omp declare target(baz)   ! 'baz' should be resolved to the
    !$omp target                ! external function
      a = baz(a)                ! <- because of this call
    !$omp end target
  end
```

The problem is that "declare_target(baz)" despite being the same in both
cases, should lead to two different outcomes in symbol resolution.

This fix will treat declarations introduced by a DECLARE_TARGET as
eligible for overriding with a potentially conflicting declaration
stemming from the use of that name in a language construct or expression.
Since a mere mention of a name alone declares an object, such conflict
occurs when the name would have been otherwise resolved to a procedure.

The function HandleProcedureName was modified to "undeclare" names
implicitly declared due to their appearance in a DECLARE_TARGET.
---
 flang/lib/Semantics/resolve-names.cpp         | 180 ++++++++++--------
 .../OpenMP/declare-target-symbols.f90         |  35 ++++
 .../Semantics/OpenMP/declare-target08.f90     |   8 +-
 3 files changed, 137 insertions(+), 86 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/declare-target-symbols.f90

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index a92d73ff1bc80..bb9001e46c9a7 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -598,6 +598,7 @@ class ScopeHandler : public ImplicitRulesVisitor {
   Symbol &MakeSymbol(const SourceName &, Attrs = Attrs{});
   Symbol &MakeSymbol(const parser::Name &, Attrs = Attrs{});
   Symbol &MakeHostAssocSymbol(const parser::Name &, const Symbol &);
+  Symbol &MakeHostAssocSymbol(Scope &, const parser::Name &, const Symbol &);
 
   template <typename D>
   common::IfNoLvalue<Symbol &, D> MakeSymbol(
@@ -763,8 +764,6 @@ class ScopeHandler : public ImplicitRulesVisitor {
     std::vector<const std::list<parser::EquivalenceObject> *> equivalenceSets;
     // Names of all common block objects in the scope
     std::set<SourceName> commonBlockObjects;
-    // Names of all names that show in a declare target declaration
-    std::set<SourceName> declareTargetNames;
     // Info about SAVE statements and attributes in current scope
     struct {
       std::optional<SourceName> saveAll; // "SAVE" without entity list
@@ -1272,7 +1271,6 @@ class DeclarationVisitor : public ArraySpecVisitor,
   const parser::Name *FindComponent(const parser::Name *, const parser::Name &);
   void Initialization(const parser::Name &, const parser::Initialization &,
       bool inComponentDecl);
-  bool FindAndMarkDeclareTargetSymbol(const parser::Name &);
   bool PassesLocalityChecks(
       const parser::Name &name, Symbol &symbol, Symbol::Flag flag);
   bool CheckForHostAssociatedImplicit(const parser::Name &);
@@ -1763,49 +1761,73 @@ class OmpVisitor : public virtual DeclarationVisitor {
   bool Pre(const parser::OmpClause::To &);
   bool Pre(const parser::OmpClause::From &);
 
+  // Make sure that the following code is properly handled:
+  //
+  // function baz(a)
+  //   !$omp declare target to(baz)
+  //   real, intent(in) :: a
+  //   baz = a
+  // end
+  //
+  // subroutine foo
+  //   !$omp declare target(baz)   ! 'baz' should resolve to the array
+  //   integer, save :: baz(10)
+  // end
+  //
+  // subroutine bar
+  //   real :: a
+  //   !$omp declare target(baz)   ! 'baz' should resolve to the external
+  //   !$omp target                ! function
+  //     a = baz(a)
+  //   !$omp end target
+  // end
+  //
+  // The problem is that "declare_target(baz)" despite being the same
+  // in both foo and bar, should lead to two different outcomes in symbol
+  // resolution.
+
+  // The approach is to treat declarations introduced by a DECLARE_TARGET
+  // as eligible for overriding with a potentially conflicting declaration
+  // stemming from the use of that name in a language construct or expression.
+  // Since a mere mention of a name alone declares an object, such conflict
+  // occurs when the name would have been otherwise resolved to a procedure.
+  // The overriding happens in HandleProcedureName.
+  //
+  // Here in Pre/Post we'll record the symbols that were created specifically
+  // for names appearing in a DECLARE_TARGET.
   bool Pre(const parser::OmpDeclareTargetDirective &x) {
-    auto addObjectName{[&](const parser::OmpObject &object) {
-      common::visit(
-          common::visitors{
-              [&](const parser::Designator &designator) {
-                if (const auto *name{
-                        parser::GetDesignatorNameIfDataRef(designator)}) {
-                  specPartState_.declareTargetNames.insert(name->source);
-                }
-              },
-              [&](const parser::Name &name) {
-                specPartState_.declareTargetNames.insert(name.source);
-              },
-              [&](const parser::OmpObject::Invalid &invalid) {
-                switch (invalid.v) {
-                  SWITCH_COVERS_ALL_CASES
-                case parser::OmpObject::Invalid::Kind::BlankCommonBlock:
-                  context().Say(invalid.source,
-                      "Blank common blocks are not allowed as directive or clause arguments"_err_en_US);
-                  break;
-                }
-              },
-          },
-          object.u);
-    }};
-
+    // Save names appearing in this DECLARE_TARGET.
+    declareTargetNames_.clear();
     for (const parser::OmpArgument &arg : x.v.Arguments().v) {
       if (auto *object{parser::omp::GetArgumentObject(arg)}) {
-        addObjectName(*object);
+        if (auto *name = parser::Unwrap<parser::Name>(*object)) {
+          declareTargetNames_.insert(name);
+        }
       }
     }
-
     for (const parser::OmpClause &clause : x.v.Clauses().v) {
       if (auto *objects{parser::omp::GetOmpObjectList(clause)}) {
         for (const parser::OmpObject &object : objects->v) {
-          addObjectName(object);
+          if (auto *name = parser::Unwrap<parser::Name>(object)) {
+            declareTargetNames_.insert(name);
+          }
         }
       }
     }
-
-    SkipImplicitTyping(true);
     return true;
   }
+
+  void Post(const parser::OmpDeclareTargetDirective &x) {
+    for (const parser::Name *name : declareTargetNames_) {
+      // The source location of the symbol's name() is the location of the
+      // declaration.
+      if (name->symbol &&
+          name->source.begin() == name->symbol->name().begin()) {
+        declaredByDeclareTarget_.insert(name->symbol);
+      }
+    }
+  }
+
   bool Pre(const parser::OmpClause &x) {
     if (NeedsScope(x)) {
       PushScopeWithSource(Scope::Kind::OtherClause, x.source);
@@ -1874,6 +1896,10 @@ class OmpVisitor : public virtual DeclarationVisitor {
     messageHandler().set_currStmtSource(std::nullopt);
   }
 
+  bool WasDeclaredByOmpDeclareTarget(const Symbol *sym) {
+    return declaredByDeclareTarget_.erase(sym);
+  }
+
 private:
   void ResolveMapperModifier(const parser::OmpMapper &mapper);
   void ProcessMapperSpecifier(const parser::OmpMapperSpecifier &spec,
@@ -1884,6 +1910,8 @@ class OmpVisitor : public virtual DeclarationVisitor {
   void ResolveCriticalName(const parser::OmpArgument &arg);
 
   std::vector<const parser::OpenMPDeclarativeConstruct *> declaratives_;
+  std::set<const parser::Name *> declareTargetNames_;
+  std::set<const Symbol *> declaredByDeclareTarget_;
 };
 
 bool OmpVisitor::NeedsScope(const parser::OmpClause &x) {
@@ -3245,9 +3273,12 @@ Symbol &ScopeHandler::MakeSymbol(const parser::Name &name, Attrs attrs) {
 }
 Symbol &ScopeHandler::MakeHostAssocSymbol(
     const parser::Name &name, const Symbol &hostSymbol) {
-  Symbol &symbol{*NonDerivedTypeScope()
-                      .try_emplace(name.source, HostAssocDetails{hostSymbol})
-                      .first->second};
+  return MakeHostAssocSymbol(NonDerivedTypeScope(), name, hostSymbol);
+}
+Symbol &ScopeHandler::MakeHostAssocSymbol(
+    Scope &scope, const parser::Name &name, const Symbol &hostSymbol) {
+  Symbol &symbol{*scope.try_emplace(name.source, HostAssocDetails{hostSymbol})
+          .first->second};
   name.symbol = &symbol;
   symbol.attrs() = hostSymbol.attrs(); // TODO: except PRIVATE, PUBLIC?
   // These attributes can be redundantly reapplied without error
@@ -9213,11 +9244,7 @@ const parser::Name *DeclarationVisitor::ResolveDataRef(
 // If implicit types are allowed, ensure name is in the symbol table.
 // Otherwise, report an error if it hasn't been declared.
 const parser::Name *DeclarationVisitor::ResolveName(const parser::Name &name) {
-  if (!FindSymbol(name)) {
-    if (FindAndMarkDeclareTargetSymbol(name)) {
-      return &name;
-    }
-  }
+  FindSymbol(name);
   if (CheckForHostAssociatedImplicit(name)) {
     NotePossibleBadForwardRef(name);
     return &name;
@@ -9421,47 +9448,6 @@ const parser::Name *DeclarationVisitor::FindComponent(
   return nullptr;
 }
 
-bool DeclarationVisitor::FindAndMarkDeclareTargetSymbol(
-    const parser::Name &name) {
-  if (!specPartState_.declareTargetNames.empty()) {
-    if (specPartState_.declareTargetNames.count(name.source)) {
-      if (!currScope().IsTopLevel()) {
-        // Search preceding scopes until we find a matching symbol or run out
-        // of scopes to search, we skip the current scope as it's already been
-        // designated as implicit here.
-        for (auto *scope = &currScope().parent();; scope = &scope->parent()) {
-          if (Symbol * symbol{scope->FindSymbol(name.source)}) {
-            if (symbol->test(Symbol::Flag::Subroutine) ||
-                symbol->test(Symbol::Flag::Function)) {
-              const auto [sym, success]{currScope().try_emplace(
-                  symbol->name(), Attrs{}, HostAssocDetails{*symbol})};
-              assert(success &&
-                  "FindAndMarkDeclareTargetSymbol could not emplace new "
-                  "subroutine/function symbol");
-              name.symbol = &*sym->second;
-              symbol->test(Symbol::Flag::Subroutine)
-                  ? name.symbol->set(Symbol::Flag::Subroutine)
-                  : name.symbol->set(Symbol::Flag::Function);
-              return true;
-            }
-            // if we find a symbol that is not a function or subroutine, we
-            // currently escape without doing anything.
-            break;
-          }
-
-          // This is our loop exit condition, as parent() has an inbuilt assert
-          // if you call it on a top level scope, rather than returning a null
-          // value.
-          if (scope->IsTopLevel()) {
-            return false;
-          }
-        }
-      }
-    }
-  }
-  return false;
-}
-
 void DeclarationVisitor::Initialization(const parser::Name &name,
     const parser::Initialization &init, bool inComponentDecl) {
   // Traversal of the initializer was deferred to here so that the
@@ -9756,6 +9742,28 @@ void ResolveNamesVisitor::HandleProcedureName(
     Symbol::Flag flag, const parser::Name &name) {
   CHECK(flag == Symbol::Flag::Function || flag == Symbol::Flag::Subroutine);
   auto *symbol{FindSymbol(NonDerivedTypeScope(), name)};
+  // A symbol listed on OpenMP declare_target directive may be a variable
+  // or a procedure. If the directive is the first occurrence of the name,
+  // it will create an implicit declaration of an object (since the name
+  // is not used in a call at that location). If the name turns out to be
+  // that of a procedure, this is going to create a problem.
+  // If a symbol was created because of its appearance in a declare_target,
+  // a use in a call should override it with the procedure symbol.
+  Scope *ompDTScope{nullptr};
+  // A name implicitly declared by a DECLARE_TARGET may have been followed
+  // by an explcit declaration. Make sure the symbol is still implicit
+  // before doing anything.
+  if (WasDeclaredByOmpDeclareTarget(symbol) &&
+      symbol->flags().test(Symbol::Flag::Implicit)) {
+    // Implicit declaration of a symbol caused by being on a declare_target
+    // should only declare it as an object, not a procedure. This is because
+    // the 'x' in declare_target(x) looks like a use of a variable, not a
+    // procedure.
+    assert(!IsProcedure(*symbol) && "Should not be a procedure");
+    ompDTScope = const_cast<Scope *>(&symbol->owner());
+    ompDTScope->erase(symbol->name());
+    symbol = nullptr;
+  }
   if (!symbol) {
     if (IsIntrinsic(name.source, flag)) {
       symbol = &MakeSymbol(InclusiveScope(), name.source, Attrs{});
@@ -9778,6 +9786,14 @@ void ResolveNamesVisitor::HandleProcedureName(
         // Create a place-holder HostAssocDetails symbol to preclude later
         // use of this name as a local symbol; but don't actually use this new
         // HostAssocDetails symbol in expressions.
+        if (ompDTScope && &currScope() != ompDTScope &&
+            ompDTScope->Contains(currScope())) {
+          // If we're recreating a symbol previously declared due to an
+          // OpenMP declare_target, we need to create one in the scope
+          // where the declare_target was located. The use in a call that
+          // we're handling here may be in a nested scope.
+          MakeHostAssocSymbol(*ompDTScope, name, *symbol);
+        }
         MakeHostAssocSymbol(name, *symbol);
         name.symbol = symbol;
       }
diff --git a/flang/test/Semantics/OpenMP/declare-target-symbols.f90 b/flang/test/Semantics/OpenMP/declare-target-symbols.f90
new file mode 100644
index 0000000000000..8ca8c2786f230
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-target-symbols.f90
@@ -0,0 +1,35 @@
+!RUN: %flang_fc1 -fdebug-dump-symbols %openmp_flags %s | FileCheck %s
+
+function baz(a)
+  !$omp declare target to(baz)
+  real, intent(in) :: a
+  baz = a
+end
+
+subroutine foo
+  !$omp declare target(baz)
+  integer, save :: baz(10)
+end
+
+subroutine bar
+  real :: a
+  !$omp declare target(baz)
+  a = 1.0
+  !$omp target
+    a = baz(a)
+  !$omp end target
+end
+
+!CHECK: Subprogram scope: baz size=8 alignment=4
+!CHECK:   a, INTENT(IN) size=4 offset=4: ObjectEntity dummy type: REAL(4)
+!CHECK:   baz (Implicit, OmpDeclareTarget) size=4 offset=0: ObjectEntity funcResult type: REAL(4)
+!CHECK:   OtherClause scope: size=0 alignment=1
+!CHECK: Subprogram scope: foo size=40 alignment=4 sourceRange=69 bytes
+!CHECK:   baz, SAVE (OmpDeclareTarget) size=40 offset=0: ObjectEntity type: INTEGER(4) shape: 1_8:10_8 OmpDeclareTargetFlags:(enter)
+!CHECK:   foo (Subroutine): HostAssoc => foo (Subroutine): Subprogram ()
+!CHECK: Subprogram scope: bar size=4 alignment=4
+!CHECK:   a size=4 offset=0: ObjectEntity type: REAL(4)
+!CHECK:   bar (Subroutine): HostAssoc => bar (Subroutine): Subprogram ()
+!CHECK:   baz, EXTERNAL (Function, OmpDeclareTarget): HostAssoc => baz, EXTERNAL (Function, OmpDeclareTarget): Subprogram result:REAL(4) baz (REAL(4) a) OmpDeclareTargetFlags:(enter to)
+!CHECK:   OtherConstruct scope: size=0 alignment=1
+!CHECK:     baz, EXTERNAL (Function, OmpDeclareTarget): HostAssoc => baz, EXTERNAL (Function, OmpDeclareTarget): Subprogram result:REAL(4) baz (REAL(4) a) OmpDeclareTargetFlags:(enter to)
diff --git a/flang/test/Semantics/OpenMP/declare-target08.f90 b/flang/test/Semantics/OpenMP/declare-target08.f90
index 91598d9528ae9..7cf1a9c3d9382 100644
--- a/flang/test/Semantics/OpenMP/declare-target08.f90
+++ b/flang/test/Semantics/OpenMP/declare-target08.f90
@@ -28,8 +28,8 @@ program main
 external ext_routine
 integer ext_function
 external ext_function
-!CHECK: bar (Subroutine, OmpDeclareTarget): HostAssoc
-!CHECK: baz (Function, OmpDeclareTarget): HostAssoc
+!CHECK: bar, EXTERNAL (Subroutine, OmpDeclareTarget): HostAssoc
+!CHECK: baz, EXTERNAL (Function, OmpDeclareTarget): HostAssoc
 !CHECK: ext_function, EXTERNAL (Function, OmpDeclareTarget): ProcEntity {{.*}}
 !CHECK: ext_routine, EXTERNAL (OmpDeclareTarget): ProcEntity
 !$omp declare target(bar)
@@ -46,8 +46,8 @@ program main
 subroutine foo(a)
 real a
 integer i
-!CHECK: bar (Subroutine, OmpDeclareTarget): HostAssoc
-!CHECK: baz (Function, OmpDeclareTarget): HostAssoc
+!CHECK: bar, EXTERNAL (Subroutine, OmpDeclareTarget): HostAssoc
+!CHECK: baz, EXTERNAL (Function, OmpDeclareTarget): HostAssoc
 !$omp declare target(bar)
 !$omp declare target(baz)
 !$omp target

>From c72e280ea1d8bc82291630cb9173a6b8fa27f715 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 6 Jun 2026 08:16:46 -0500
Subject: [PATCH 2/2] Don't replace symbols that have been equivalenced

---
 flang/lib/Semantics/resolve-names.cpp         | 23 +++++++++++--------
 .../OpenMP/declare-target-equivalence.f90     | 23 +++++++++++++++++++
 2 files changed, 36 insertions(+), 10 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/declare-target-equivalence.f90

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index bb9001e46c9a7..0689a117d8f8a 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -9753,16 +9753,19 @@ void ResolveNamesVisitor::HandleProcedureName(
   // A name implicitly declared by a DECLARE_TARGET may have been followed
   // by an explcit declaration. Make sure the symbol is still implicit
   // before doing anything.
-  if (WasDeclaredByOmpDeclareTarget(symbol) &&
-      symbol->flags().test(Symbol::Flag::Implicit)) {
-    // Implicit declaration of a symbol caused by being on a declare_target
-    // should only declare it as an object, not a procedure. This is because
-    // the 'x' in declare_target(x) looks like a use of a variable, not a
-    // procedure.
-    assert(!IsProcedure(*symbol) && "Should not be a procedure");
-    ompDTScope = const_cast<Scope *>(&symbol->owner());
-    ompDTScope->erase(symbol->name());
-    symbol = nullptr;
+  if (WasDeclaredByOmpDeclareTarget(symbol)) {
+    bool isImplicit{symbol->flags().test(Symbol::Flag::Implicit)};
+    bool inEquivalence{FindEquivalenceSet(*symbol) != nullptr};
+    if (isImplicit && !inEquivalence) {
+      // Implicit declaration of a symbol caused by being on a declare_target
+      // should only declare it as an object, not a procedure. This is because
+      // the 'x' in declare_target(x) looks like a use of a variable, not a
+      // procedure.
+      assert(!IsProcedure(*symbol) && "Should not be a procedure");
+      ompDTScope = const_cast<Scope *>(&symbol->owner());
+      ompDTScope->erase(symbol->name());
+      symbol = nullptr;
+    }
   }
   if (!symbol) {
     if (IsIntrinsic(name.source, flag)) {
diff --git a/flang/test/Semantics/OpenMP/declare-target-equivalence.f90 b/flang/test/Semantics/OpenMP/declare-target-equivalence.f90
new file mode 100644
index 0000000000000..9c4e15e77e7eb
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-target-equivalence.f90
@@ -0,0 +1,23 @@
+!RUN: %python %S/../test_errors.py %s %flang_fc1 -fopenmp
+
+subroutine baz(x)
+  integer :: x
+end
+
+subroutine f
+!ERROR: A variable in a DECLARE TARGET directive cannot appear in an EQUIVALENCE statement
+  !$omp declare target(baz)
+  integer :: p
+  equivalence(baz, p)
+!ERROR: Cannot call function 'baz' like a subroutine
+  call baz(p)
+end
+
+subroutine g
+!ERROR: A variable in a DECLARE TARGET directive cannot appear in an EQUIVALENCE statement
+  !$omp declare target(baz)
+  integer :: p, q
+  equivalence(baz, p)
+!ERROR: 'baz' is not a callable procedure
+  q = baz(p)
+end



More information about the flang-commits mailing list