[flang-commits] [flang] [flang][OpenMP] Store DECLARE_TARGET information in WithOmpDeclarative (PR #201103)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Wed Jun 3 05:57:49 PDT 2026


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

>From 8f2e7890b13db3e5bb4516d0c2ba9376864b13f0 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 28 May 2026 12:29:06 -0500
Subject: [PATCH 1/5] [flang][OpenMP] Refactor interface of WithOmpDeclarative

The two major changes are that:
1. The clause sets are not optional anymore. In the absence of any
declarative directives (REQURIRES in this case), the set will simply
be empty.
2. The optional memory order member will serve as the value of the
argument to the ATOMIC_DEFAULT_MEM_ORDER clause, and will only be
meaningful (and required) when the clause is a member of the clause set.

Additionally,
- Rename the RequiredClauses type alias to OmpClauseSet, since it will be
reused for other purposes in the future.
- Remove the has_* functions since they are not necessary, and when more
members are added these functions will only add to the clutter.
- Add a version_ member for printing directive/clause names.
---
 flang/include/flang/Semantics/openmp-utils.h  | 20 ++++++
 flang/include/flang/Semantics/symbol.h        | 30 ++++-----
 flang/lib/Lower/OpenMP/Atomic.cpp             |  4 +-
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  5 +-
 flang/lib/Semantics/check-omp-structure.cpp   |  4 +-
 flang/lib/Semantics/mod-file.cpp              | 34 +++++-----
 flang/lib/Semantics/resolve-directives.cpp    | 63 +++++++------------
 flang/lib/Semantics/symbol.cpp                | 44 ++++++++-----
 .../Semantics/OpenMP/requires-modfile.f90     |  6 +-
 9 files changed, 110 insertions(+), 100 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 4001b337193a1..c2e89fe829ce0 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -127,6 +127,26 @@ std::optional<int64_t> GetIntValueFromExpr(
   return std::nullopt;
 }
 
+// There are several clauses that take an optional, compile-time
+// constant bool argument. Those clauses are stored as std::optional, e.g.
+// OmpClause::ReverseOffload -> std::optional<OmpReverseOffloadClause>.
+// Retrieve the logical value if present.
+template <typename ClauseTy>
+std::optional<bool> GetLogicalArgument(
+    const std::optional<ClauseTy> &maybeClause, SemanticsContext &semaCtx) {
+  if (maybeClause) {
+    // Scalar<Logical<Constant<common::Indirection<Expr>>>>
+    auto &parserExpr{parser::UnwrapRef<parser::Expr>(*maybeClause)};
+    evaluate::ExpressionAnalyzer ea{semaCtx};
+    if (auto &&maybeExpr{ea.Analyze(parserExpr)}) {
+      if (auto v{GetLogicalValue(*maybeExpr)}) {
+        return *v;
+      }
+    }
+  }
+  return std::nullopt;
+}
+
 std::optional<bool> IsContiguous(
     SemanticsContext &semaCtx, const parser::OmpObject &object);
 
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 775ac5ca3dcbc..a7647665973d2 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -52,22 +52,15 @@ using MutableSymbolVector = std::vector<MutableSymbolRef>;
 // Mixin for details with OpenMP declarative constructs.
 class WithOmpDeclarative {
 public:
-  // The set of requirements for any program unit include requirements
-  // from any module used in the program unit.
-  using RequiresClauses =
+  using OmpClauseSet =
       common::EnumSet<llvm::omp::Clause, llvm::omp::Clause_enumSize>;
 
-  bool has_ompRequires() const { return ompRequires_.has_value(); }
-  const RequiresClauses *ompRequires() const {
-    return ompRequires_ ? &*ompRequires_ : nullptr;
-  }
-  void set_ompRequires(RequiresClauses clauses) { ompRequires_ = clauses; }
+  const OmpClauseSet &ompRequires() const { return ompRequires_; }
+  void set_ompRequires(OmpClauseSet clauses) { ompRequires_ = clauses; }
 
-  bool has_ompAtomicDefaultMemOrder() const {
-    return ompAtomicDefaultMemOrder_.has_value();
-  }
-  const common::OmpMemoryOrderType *ompAtomicDefaultMemOrder() const {
-    return ompAtomicDefaultMemOrder_ ? &*ompAtomicDefaultMemOrder_ : nullptr;
+  const std::optional<common::OmpMemoryOrderType> &
+  ompAtomicDefaultMemOrder() const {
+    return ompAtomicDefaultMemOrder_;
   }
   void set_ompAtomicDefaultMemOrder(common::OmpMemoryOrderType flags) {
     ompAtomicDefaultMemOrder_ = flags;
@@ -76,8 +69,17 @@ class WithOmpDeclarative {
   friend llvm::raw_ostream &operator<<(
       llvm::raw_ostream &, const WithOmpDeclarative &);
 
+  void set_version(unsigned version) { version_ = version; }
+
 private:
-  std::optional<RequiresClauses> ompRequires_;
+  unsigned version_;
+  // The set of clauses from a REQUIRES directive. Only applicable
+  // to program unit symbols (i.e. scopes of the REQUIRES directive).
+  // The set of requirements for any program unit include requirements
+  // from any module used in the program unit.
+  OmpClauseSet ompRequires_;
+  // The argument to ATOMIC_DEFAULT_MEM_ORDER. Only needed when the ADMO
+  // clause is present in the ompRequires_ set.
   std::optional<common::OmpMemoryOrderType> ompAtomicDefaultMemOrder_;
 };
 
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index b80564fddd943..e7745ee2c8547 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -192,7 +192,9 @@ getMemoryOrderFromRequires(const semantics::Scope &scope) {
           using WithOmpDeclarative = semantics::WithOmpDeclarative;
           if constexpr (std::is_convertible_v<decltype(s),
                                               const WithOmpDeclarative &>) {
-            return s.ompAtomicDefaultMemOrder();
+            if (auto &admo{s.ompAtomicDefaultMemOrder()}) {
+              return &*admo;
+            }
           }
           return static_cast<const common::OmpMemoryOrderType *>(nullptr);
         },
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 620eeadbc0711..6114dfe392145 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -5208,14 +5208,13 @@ void Fortran::lower::genOpenMPRequires(mlir::Operation *mod,
 
   if (auto offloadMod =
           llvm::dyn_cast<mlir::omp::OffloadModuleInterface>(mod)) {
-    semantics::WithOmpDeclarative::RequiresClauses reqs;
+    semantics::WithOmpDeclarative::OmpClauseSet reqs;
     if (symbol) {
       common::visit(
           [&](const auto &details) {
             if constexpr (std::is_base_of_v<semantics::WithOmpDeclarative,
                                             std::decay_t<decltype(details)>>) {
-              if (details.has_ompRequires())
-                reqs = *details.ompRequires();
+              reqs = details.ompRequires();
             }
           },
           symbol->details());
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index c450c3fbfeb43..f44cdfd4e6768 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -614,9 +614,7 @@ bool OmpStructureChecker::HasRequires(llvm::omp::Clause req) {
       [&](const auto &details) {
         if constexpr (std::is_convertible_v<decltype(details),
                           const WithOmpDeclarative &>) {
-          if (auto *reqs{details.ompRequires()}) {
-            return reqs->test(req);
-          }
+          return details.ompRequires().test(req);
         }
         return false;
       },
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index f5e66a04c3f11..6e89ae92b882a 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -364,34 +364,36 @@ void ModFileWriter::PrepareRenamings(const Scope &scope) {
   }
 }
 
-static void PutOpenMPRequirements(llvm::raw_ostream &os, const Symbol &symbol) {
-  using RequiresClauses = WithOmpDeclarative::RequiresClauses;
+static void PutOpenMPRequirements(
+    llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &semaCtx) {
+  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
   using OmpMemoryOrderType = common::OmpMemoryOrderType;
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
 
   const auto [reqs, order]{common::visit(
       [&](auto &&details)
-          -> std::pair<const RequiresClauses *, const OmpMemoryOrderType *> {
+          -> std::pair<const OmpClauseSet *, const OmpMemoryOrderType *> {
         if constexpr (std::is_convertible_v<decltype(details),
                           const WithOmpDeclarative &>) {
-          return {details.ompRequires(), details.ompAtomicDefaultMemOrder()};
+          if (const auto &memOrder{details.ompAtomicDefaultMemOrder()}) {
+            return {&details.ompRequires(), &*memOrder};
+          }
+          return {&details.ompRequires(), nullptr};
         } else {
           return {nullptr, nullptr};
         }
       },
       symbol.details())};
 
-  if (order) {
-    llvm::omp::Clause admo{llvm::omp::Clause::OMPC_atomic_default_mem_order};
-    os << "!$omp requires "
-       << parser::ToLowerCaseLetters(llvm::omp::getOpenMPClauseName(admo))
-       << '(' << parser::ToLowerCaseLetters(EnumToString(*order)) << ")\n";
-  }
-  if (reqs) {
+  if (reqs->count()) {
     os << "!$omp requires";
-    reqs->IterateOverMembers([&](llvm::omp::Clause f) {
-      if (f != llvm::omp::Clause::OMPC_atomic_default_mem_order) {
-        os << ' '
-           << parser::ToLowerCaseLetters(llvm::omp::getOpenMPClauseName(f));
+    reqs->IterateOverMembers([&, order = order](llvm::omp::Clause f) {
+      os << ' '
+         << parser::ToLowerCaseLetters(
+                llvm::omp::getOpenMPClauseName(f, version));
+      if (f == llvm::omp::Clause::OMPC_atomic_default_mem_order) {
+        os << '(' << parser::ToLowerCaseLetters(EnumToString(DEREF(order)))
+           << ')';
       }
     });
     os << "\n";
@@ -435,7 +437,7 @@ void ModFileWriter::PutSymbols(
   for (const Symbol &symbol : uses) {
     PutUse(symbol);
   }
-  PutOpenMPRequirements(decls_, DEREF(scope.symbol()));
+  PutOpenMPRequirements(decls_, DEREF(scope.symbol()), context_);
   for (const auto &set : scope.equivalenceSets()) {
     if (!set.empty() &&
         !set.front().symbol.test(Symbol::Flag::CompilerCreated)) {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 2fa59adf7f3af..b86bf64d18bd3 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -638,34 +638,19 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   void Post(const parser::OpenMPFlushConstruct &) { PopContext(); }
 
   bool Pre(const parser::OmpRequiresDirective &x) {
-    using RequiresClauses = WithOmpDeclarative::RequiresClauses;
+    using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
     PushContext(x.source, llvm::omp::Directive::OMPD_requires);
 
-    auto getArgument{[&](auto &&maybeClause) {
-      if (maybeClause) {
-        // Scalar<Logical<Constant<common::Indirection<Expr>>>>
-        auto &parserExpr{parser::UnwrapRef<parser::Expr>(*maybeClause)};
-        evaluate::ExpressionAnalyzer ea{context_};
-        if (auto &&maybeExpr{ea.Analyze(parserExpr)}) {
-          if (auto v{omp::GetLogicalValue(*maybeExpr)}) {
-            return *v;
-          }
-        }
-      }
-      // If the argument is missing, it is assumed to be true.
-      return true;
-    }};
-
     // Gather information from the clauses.
-    RequiresClauses reqs;
-    const common::OmpMemoryOrderType *memOrder{nullptr};
+    OmpClauseSet reqs;
+    std::optional<common::OmpMemoryOrderType> memOrder;
     for (const parser::OmpClause &clause : x.v.Clauses().v) {
       using OmpClause = parser::OmpClause;
       reqs |= common::visit(
           common::visitors{
-              [&](const OmpClause::AtomicDefaultMemOrder &atomic) {
-                memOrder = &atomic.v.v;
-                return RequiresClauses{};
+              [&](const OmpClause::AtomicDefaultMemOrder &admo) {
+                memOrder = admo.v.v;
+                return OmpClauseSet{clause.Id()};
               },
               [&](auto &&s) {
                 using TypeS = llvm::remove_cvref_t<decltype(s)>;
@@ -676,18 +661,18 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
                     std::is_same_v<TypeS, OmpClause::SelfMaps> ||
                     std::is_same_v<TypeS, OmpClause::UnifiedAddress> ||
                     std::is_same_v<TypeS, OmpClause::UnifiedSharedMemory>) {
-                  if (getArgument(s.v)) {
-                    return RequiresClauses{clause.Id()};
+                  if (omp::GetLogicalArgument(s.v, context_).value_or(true)) {
+                    return OmpClauseSet{clause.Id()};
                   }
                 }
-                return RequiresClauses{};
+                return OmpClauseSet{};
               },
           },
           clause.u);
     }
 
     // Merge clauses into parents' symbols details.
-    AddOmpRequiresToScope(currScope(), &reqs, memOrder);
+    AddOmpRequiresToScope(currScope(), reqs, memOrder);
     return true;
   }
   void Post(const parser::OmpRequiresDirective &) { PopContext(); }
@@ -1055,9 +1040,8 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   void CheckObjectIsPrivatizable(
       const parser::Name &, const Symbol &, Symbol::Flag);
 
-  void AddOmpRequiresToScope(Scope &,
-      const WithOmpDeclarative::RequiresClauses *,
-      const common::OmpMemoryOrderType *);
+  void AddOmpRequiresToScope(Scope &, const WithOmpDeclarative::OmpClauseSet &,
+      const std::optional<common::OmpMemoryOrderType> &);
 
   void CreateImplicitSymbols(const parser::Name &, const Symbol *symbol);
 
@@ -3213,30 +3197,25 @@ void OmpAttributeVisitor::CheckObjectIsPrivatizable(
 }
 
 void OmpAttributeVisitor::AddOmpRequiresToScope(Scope &scope,
-    const WithOmpDeclarative::RequiresClauses *reqs,
-    const common::OmpMemoryOrderType *memOrder) {
+    const WithOmpDeclarative::OmpClauseSet &reqs,
+    const std::optional<common::OmpMemoryOrderType> &memOrder) {
+  unsigned version{context_.langOptions().OpenMPVersion};
   const Scope &programUnit{omp::GetProgramUnit(scope)};
-  using RequiresClauses = WithOmpDeclarative::RequiresClauses;
-  RequiresClauses combinedReqs{reqs ? *reqs : RequiresClauses{}};
 
   if (auto *symbol{const_cast<Symbol *>(programUnit.symbol())}) {
     common::visit(
         [&](auto &details) {
           if constexpr (std::is_convertible_v<decltype(&details),
                             WithOmpDeclarative *>) {
-            if (combinedReqs.any()) {
-              if (const RequiresClauses *otherReqs{details.ompRequires()}) {
-                combinedReqs |= *otherReqs;
-              }
-              details.set_ompRequires(combinedReqs);
+            if (reqs.any()) {
+              details.set_ompRequires(reqs | details.ompRequires());
+              details.set_version(version);
             }
             if (memOrder) {
-              if (details.has_ompAtomicDefaultMemOrder() &&
-                  *details.ompAtomicDefaultMemOrder() != *memOrder) {
-                unsigned version{context_.langOptions().OpenMPVersion};
+              if (auto &admo{details.ompAtomicDefaultMemOrder()};
+                  admo && *admo != *memOrder) {
                 context_.Say(programUnit.sourceRange(),
-                    "Conflicting '%s' REQUIRES clauses found in compilation "
-                    "unit"_err_en_US,
+                    "Conflicting '%s' REQUIRES clauses found in compilation unit"_err_en_US,
                     parser::omp::GetUpperName(
                         llvm::omp::Clause::OMPC_atomic_default_mem_order,
                         version));
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index ed0715a422e78..7712091a03210 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -72,25 +72,35 @@ static void DumpList(llvm::raw_ostream &os, const char *label, const T &list) {
 
 llvm::raw_ostream &operator<<(
     llvm::raw_ostream &os, const WithOmpDeclarative &x) {
-  if (x.has_ompRequires() || x.has_ompAtomicDefaultMemOrder()) {
-    os << " OmpRequirements:(";
-    if (const common::OmpMemoryOrderType *admo{x.ompAtomicDefaultMemOrder()}) {
-      os << parser::ToLowerCaseLetters(llvm::omp::getOpenMPClauseName(
-                llvm::omp::Clause::OMPC_atomic_default_mem_order))
-         << '(' << parser::ToLowerCaseLetters(EnumToString(*admo)) << ')';
-      if (x.has_ompRequires()) {
+  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
+
+  auto toLower = [](std::string_view sv) {
+    return parser::ToLowerCaseLetters(sv);
+  };
+  auto getLowerName = [&](llvm::omp::Clause c) {
+    return toLower(llvm::omp::getOpenMPClauseName(c, x.version_));
+  };
+  auto printClauses = [&](const OmpClauseSet &cs) {
+    size_t idx{0}, size{cs.count()};
+    cs.IterateOverMembers([&](llvm::omp::Clause c) {
+      os << getLowerName(c);
+      switch (c) {
+      case llvm::omp::Clause::OMPC_atomic_default_mem_order:
+        os << '(' << toLower(EnumToString(*x.ompAtomicDefaultMemOrder()))
+           << ')';
+        break;
+      default:
+        break;
+      }
+      if (++idx < size) {
         os << ',';
       }
-    }
-    if (const WithOmpDeclarative::RequiresClauses *reqs{x.ompRequires()}) {
-      size_t num{0}, size{reqs->count()};
-      reqs->IterateOverMembers([&](llvm::omp::Clause f) {
-        os << parser::ToLowerCaseLetters(llvm::omp::getOpenMPClauseName(f));
-        if (++num < size) {
-          os << ',';
-        }
-      });
-    }
+    });
+  };
+
+  if (const OmpClauseSet &reqs{x.ompRequires()}; reqs.count()) {
+    os << " OmpRequirements:(";
+    printClauses(reqs);
     os << ')';
   }
   return os;
diff --git a/flang/test/Semantics/OpenMP/requires-modfile.f90 b/flang/test/Semantics/OpenMP/requires-modfile.f90
index 52a43c2ef37ac..76d83458f4dc2 100644
--- a/flang/test/Semantics/OpenMP/requires-modfile.f90
+++ b/flang/test/Semantics/OpenMP/requires-modfile.f90
@@ -29,8 +29,7 @@ module fold
 
 !Expect: req.mod
 !module req
-!!$omp requires atomic_default_mem_order(seq_cst)
-!!$omp requires reverse_offload
+!!$omp requires atomic_default_mem_order(seq_cst) reverse_offload
 !contains
 !subroutine f00()
 !end
@@ -42,8 +41,7 @@ module fold
 !module user
 !use req,only:f00
 !use req,only:f01
-!!$omp requires atomic_default_mem_order(seq_cst)
-!!$omp requires reverse_offload
+!!$omp requires atomic_default_mem_order(seq_cst) reverse_offload
 !end
 
 !Expect: fold.mod

>From dc09abc584c63f344854abdab33084eb89c4a298 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 28 May 2026 12:29:06 -0500
Subject: [PATCH 2/5] [flang][OpenMP] Store DECLARE_TARGET information in
 WithOmpDeclarative

This will be used to emit DECLARE_TARGET directives into module files.

When a symbol apperars in DECLARE_TARGET (explicitly or explicitly), the
OmpDeclareTarget flag will be set on it. The set of accompanying clauses
will be stored in the associated details, in the WithOmpDeclarative mixin.
The mixin was added to ObjectEntityDetails, ProcEntityDetails, and
CommonBlockDetails.

The design goal was to be able to reconstruct the appropriate DECLARE_
TARGET directive for individual symbols for the purpose of emitting it
in a module file. Simply storing and then unparsing the AST node may
include symbols that should not be emitted.

Additionally, refactor the WithOmpDeclarative printing code for reuse in
symbol dumping for debugging, and for printing clause sets.
---
 .../FlangOmpReport/FlangOmpReportVisitor.cpp  |  3 +-
 flang/include/flang/Parser/dump-parse-tree.h  |  2 +-
 flang/include/flang/Parser/parse-tree.h       |  2 +-
 flang/include/flang/Semantics/symbol.h        | 26 ++++++-
 flang/include/flang/Support/Fortran.h         |  3 +
 flang/lib/Parser/unparse.cpp                  |  3 +-
 flang/lib/Semantics/mod-file.cpp              | 68 +++++++++++--------
 flang/lib/Semantics/resolve-directives.cpp    | 63 +++++++++++++++++
 flang/lib/Semantics/symbol.cpp                | 65 ++++++++++++------
 .../OpenMP/declare_target-device_type.f90     | 18 ++---
 flang/test/Parser/OpenMP/groupprivate.f90     |  2 +-
 .../Semantics/OpenMP/declare-target-flags.f90 | 36 ++++++++++
 .../OpenMP/declare-target-modfile.f90         | 41 +++++++++++
 .../OpenMP/dump-requires-details.f90          |  2 +-
 14 files changed, 265 insertions(+), 69 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/declare-target-flags.f90
 create mode 100644 flang/test/Semantics/OpenMP/declare-target-modfile.f90

diff --git a/flang/examples/FlangOmpReport/FlangOmpReportVisitor.cpp b/flang/examples/FlangOmpReport/FlangOmpReportVisitor.cpp
index d9cb10a352708..c4ca3b22489d7 100644
--- a/flang/examples/FlangOmpReport/FlangOmpReportVisitor.cpp
+++ b/flang/examples/FlangOmpReport/FlangOmpReportVisitor.cpp
@@ -137,8 +137,7 @@ void OpenMPCounterVisitor::Post(
 }
 void OpenMPCounterVisitor::Post(
     const OmpDeviceTypeClause::DeviceTypeDescription &c) {
-  clauseDetails +=
-      "type=" + std::string{OmpDeviceTypeClause::EnumToString(c)} + ";";
+  clauseDetails += "type=" + std::string{common::EnumToString(c)} + ";";
 }
 void OpenMPCounterVisitor::Post(
     const OmpDefaultmapClause::ImplicitBehavior &c) {
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index b4680e1cb656d..ff5c7a8ec01cf 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -66,6 +66,7 @@ class ParseTreeDumper {
   NODE_ENUM(common, CUDASubprogramAttrs)
   NODE_ENUM(common, ImportKind)
   NODE_ENUM(common, OmpDependenceKind)
+  NODE_ENUM(common, OmpDeviceType)
   NODE_ENUM(common, OmpMemoryOrderType)
   NODE_ENUM(common, OpenACCDeviceType)
   NODE(format, ControlEditDesc)
@@ -605,7 +606,6 @@ class ParseTreeDumper {
   NODE_ENUM(OmpDeviceModifier, Value)
   NODE(parser, OmpDeviceSafesyncClause)
   NODE(parser, OmpDeviceTypeClause)
-  NODE_ENUM(OmpDeviceTypeClause, DeviceTypeDescription)
   NODE(parser, OmpDimsModifier)
   NODE(parser, OmpDirectiveName)
   NODE(parser, OmpDirectiveSpecification)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 4efae0c9772b3..b8d622edf2b1a 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -4591,7 +4591,7 @@ struct OmpDeviceSafesyncClause {
 // device-type-clause ->
 //    DEVICE_TYPE(ANY | HOST | NOHOST)              // since 5.0
 struct OmpDeviceTypeClause {
-  ENUM_CLASS(DeviceTypeDescription, Any, Host, Nohost)
+  using DeviceTypeDescription = common::OmpDeviceType;
   WRAPPER_CLASS_BOILERPLATE(OmpDeviceTypeClause, DeviceTypeDescription);
 };
 
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index a7647665973d2..bb5a2dbbba409 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -66,6 +66,18 @@ class WithOmpDeclarative {
     ompAtomicDefaultMemOrder_ = flags;
   }
 
+  const OmpClauseSet &ompDeclTarget() const { return ompDeclTarget_; }
+  void set_ompDeclTarget(OmpClauseSet clauses) { ompDeclTarget_ = clauses; }
+
+  const std::optional<common::OmpDeviceType> &ompDeviceType() const {
+    return ompDeviceType_;
+  }
+  void set_ompDeclTarget(common::OmpDeviceType device) {
+    ompDeviceType_ = device;
+  }
+
+  void printClauseSet(llvm::raw_ostream &os, const OmpClauseSet &clauses,
+      parser::CharBlock name = parser::CharBlock{}) const;
   friend llvm::raw_ostream &operator<<(
       llvm::raw_ostream &, const WithOmpDeclarative &);
 
@@ -81,6 +93,12 @@ class WithOmpDeclarative {
   // The argument to ATOMIC_DEFAULT_MEM_ORDER. Only needed when the ADMO
   // clause is present in the ompRequires_ set.
   std::optional<common::OmpMemoryOrderType> ompAtomicDefaultMemOrder_;
+  // The set of clauses on DECLARE_TARGET directive that apply to this
+  // symbol.
+  OmpClauseSet ompDeclTarget_;
+  // The argument to DEVICE_TYPE clause. Only needed when the clause is
+  // present in the ompDeclTarget_ set.
+  std::optional<common::OmpDeviceType> ompDeviceType_;
 };
 
 // A module or submodule.
@@ -379,7 +397,7 @@ class AssocEntityDetails : public EntityDetails {
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const AssocEntityDetails &);
 
 // An entity known to be an object.
-class ObjectEntityDetails : public EntityDetails {
+class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
 public:
   explicit ObjectEntityDetails(EntityDetails &&);
   ObjectEntityDetails(const ObjectEntityDetails &) = default;
@@ -450,7 +468,9 @@ class WithPassArg {
 // A procedure pointer (other than one defined with POINTER and an
 // INTERFACE block), a dummy procedure (without an INTERFACE but with
 // EXTERNAL or use in a procedure reference), or external procedure.
-class ProcEntityDetails : public EntityDetails, public WithPassArg {
+class ProcEntityDetails : public EntityDetails,
+                          public WithPassArg,
+                          public WithOmpDeclarative {
 public:
   ProcEntityDetails() = default;
   explicit ProcEntityDetails(EntityDetails &&);
@@ -587,7 +607,7 @@ class NamelistDetails {
   SymbolVector objects_;
 };
 
-class CommonBlockDetails : public WithBindName {
+class CommonBlockDetails : public WithBindName, public WithOmpDeclarative {
 public:
   explicit CommonBlockDetails(SourceName location)
       : sourceLocation_{location} {}
diff --git a/flang/include/flang/Support/Fortran.h b/flang/include/flang/Support/Fortran.h
index 057b0c3da8c47..1118b2f8080a8 100644
--- a/flang/include/flang/Support/Fortran.h
+++ b/flang/include/flang/Support/Fortran.h
@@ -78,6 +78,9 @@ ENUM_CLASS(OmpDependenceKind, In, Out, Inout, Inoutset, Mutexinoutset, Depobj)
 // OpenMP memory-order types
 ENUM_CLASS(OmpMemoryOrderType, Acq_Rel, Acquire, Relaxed, Release, Seq_Cst)
 
+// OpenMP device-type
+ENUM_CLASS(OmpDeviceType, Any, Host, Nohost)
+
 // Fortran names may have up to 63 characters (See Fortran 2018 C601).
 static constexpr int maxNameLen{63};
 
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index fd9fcaa0405b2..58ecb365e138a 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2846,6 +2846,7 @@ class UnparseVisitor {
   WALK_NESTED_ENUM(common, CUDADataAttr) // CUDA
   WALK_NESTED_ENUM(common, CUDASubprogramAttrs) // CUDA
   WALK_NESTED_ENUM(common, OmpDependenceKind)
+  WALK_NESTED_ENUM(common, OmpDeviceType)
   WALK_NESTED_ENUM(common, OmpMemoryOrderType)
   WALK_NESTED_ENUM(IntentSpec, Intent) // R826
   WALK_NESTED_ENUM(ImplicitStmt, ImplicitNoneNameSpec) // R866
@@ -2873,8 +2874,6 @@ class UnparseVisitor {
   WALK_NESTED_ENUM(OmpThreadsetClause, ThreadsetPolicy) // OMP threadset
   WALK_NESTED_ENUM(OmpAccessGroup, Value)
   WALK_NESTED_ENUM(OmpDeviceModifier, Value) // OMP device modifier
-  WALK_NESTED_ENUM(
-      OmpDeviceTypeClause, DeviceTypeDescription) // OMP device_type
   WALK_NESTED_ENUM(OmpReductionModifier, Value) // OMP reduction-modifier
   WALK_NESTED_ENUM(OmpExpectation, Value) // OMP motion-expectation
   WALK_NESTED_ENUM(OmpFallbackModifier, Value) // OMP fallback-modifier
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 6e89ae92b882a..4f3d989d35235 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -364,39 +364,51 @@ void ModFileWriter::PrepareRenamings(const Scope &scope) {
   }
 }
 
+static const WithOmpDeclarative *GetOmpDeclarative(const Symbol &symbol) {
+  return common::visit(
+      [&](auto &&details) -> const WithOmpDeclarative * {
+        using TypeD = llvm::remove_cvref_t<decltype(details)>;
+        if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
+          return &static_cast<const WithOmpDeclarative &>(details);
+        } else {
+          return nullptr;
+        }
+      },
+      symbol.details());
+}
+
+
 static void PutOpenMPRequirements(
     llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &semaCtx) {
   using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
-  using OmpMemoryOrderType = common::OmpMemoryOrderType;
   unsigned version{semaCtx.langOptions().OpenMPVersion};
 
-  const auto [reqs, order]{common::visit(
-      [&](auto &&details)
-          -> std::pair<const OmpClauseSet *, const OmpMemoryOrderType *> {
-        if constexpr (std::is_convertible_v<decltype(details),
-                          const WithOmpDeclarative &>) {
-          if (const auto &memOrder{details.ompAtomicDefaultMemOrder()}) {
-            return {&details.ompRequires(), &*memOrder};
-          }
-          return {&details.ompRequires(), nullptr};
-        } else {
-          return {nullptr, nullptr};
-        }
-      },
-      symbol.details())};
-
-  if (reqs->count()) {
-    os << "!$omp requires";
-    reqs->IterateOverMembers([&, order = order](llvm::omp::Clause f) {
-      os << ' '
-         << parser::ToLowerCaseLetters(
-                llvm::omp::getOpenMPClauseName(f, version));
-      if (f == llvm::omp::Clause::OMPC_atomic_default_mem_order) {
-        os << '(' << parser::ToLowerCaseLetters(EnumToString(DEREF(order)))
-           << ')';
+  if (const auto *decls{GetOmpDeclarative(symbol)}) {
+    if (const OmpClauseSet &reqs{decls->ompRequires()}; reqs.count()) {
+      os << "!$omp "
+         << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
+                llvm::omp::Directive::OMPD_requires, version));
+      decls->printClauseSet(os, reqs);
+      os << "\n";
+    }
+  }
+}
+
+static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
+    const SymbolVector &symbols, SemanticsContext &semaCtx) {
+  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
+
+  for (const Symbol &symbol : symbols) {
+    if (const auto *decls{GetOmpDeclarative(symbol)}) {
+      if (const OmpClauseSet &dtgt{decls->ompDeclTarget()}; dtgt.count()) {
+        os << "!$omp "
+           << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
+                  llvm::omp::Directive::OMPD_declare_target, version)) << " ";
+        decls->printClauseSet(os, dtgt, symbol.name());
+        os << "\n";
       }
-    });
-    os << "\n";
+    }
   }
 }
 
@@ -438,6 +450,8 @@ void ModFileWriter::PutSymbols(
     PutUse(symbol);
   }
   PutOpenMPRequirements(decls_, DEREF(scope.symbol()), context_);
+  PutOpenMPDeclarativeDirectives(decls_, sorted, context_);
+
   for (const auto &set : scope.equivalenceSets()) {
     if (!set.empty() &&
         !set.front().symbol.test(Symbol::Flag::CompilerCreated)) {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index b86bf64d18bd3..84d65d87c6a82 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2191,9 +2191,29 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPCriticalConstruct &x) {
 bool OmpAttributeVisitor::Pre(const parser::OmpDeclareTargetDirective &x) {
   PushContext(x.source, llvm::omp::Directive::OMPD_declare_target);
 
+  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
+  std::map<const Symbol *, WithOmpDeclarative> details;
+  std::optional<common::OmpDeviceType> device;
+
+  if (auto *devClause{
+          parser::omp::FindClause(x.v, llvm::omp::Clause::OMPC_device_type)}) {
+    device = parser::UnwrapRef<common::OmpDeviceType>(*devClause);
+  }
+
+  auto addClause{[&](const parser::OmpObject &object,
+                     llvm::omp::Clause clauseId) {
+    if (const Symbol *sym{omp::GetObjectSymbol(object)}) {
+      auto &clauseSet{const_cast<OmpClauseSet &>(details[sym].ompDeclTarget())};
+      clauseSet.set(clauseId);
+    }
+  }};
+
   for (const parser::OmpArgument &arg : x.v.Arguments().v) {
     if (auto *object{parser::omp::GetArgumentObject(arg)}) {
       ResolveOmpObject(*object, Symbol::Flag::OmpDeclareTarget);
+      // Always record this as "enter", even with older OpenMP versions
+      // (as opposed to "to").
+      addClause(*object, llvm::omp::Clause::OMPC_enter);
     }
   }
 
@@ -2201,9 +2221,52 @@ bool OmpAttributeVisitor::Pre(const parser::OmpDeclareTargetDirective &x) {
     if (auto *objects{parser::omp::GetOmpObjectList(clause)}) {
       for (const parser::OmpObject &object : objects->v) {
         ResolveOmpObject(object, Symbol::Flag::OmpDeclareTarget);
+        addClause(object, clause.Id());
       }
     }
   }
+
+  if (x.v.Arguments().v.empty() && x.v.Clauses().v.empty()) {
+    // "!$omp declare_target" alone applies to the associated procedure.
+    const Scope &scope{omp::GetScopingUnit(context_.FindScope(x.source))};
+    if (auto *proc{const_cast<Symbol *>(scope.symbol())}) {
+      proc->flags().set(Symbol::Flag::OmpDeclareTarget);
+      auto &clauseSet{
+          const_cast<OmpClauseSet &>(details[proc].ompDeclTarget())};
+      clauseSet.set(llvm::omp::Clause::OMPC_enter);
+    }
+  }
+
+  for (auto &pair : details) {
+    const Symbol *sym{&pair.first->GetUltimate()};
+    const WithOmpDeclarative &decl{pair.second};
+    common::visit(
+        [&](auto &d) {
+          using TypeD = llvm::remove_cvref_t<decltype(d)>;
+          if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
+            auto &clauseSet{const_cast<OmpClauseSet &>(d.ompDeclTarget())};
+            clauseSet |= decl.ompDeclTarget();
+            if (device) {
+              clauseSet.set(llvm::omp::Clause::OMPC_device_type);
+              auto &deviceType{
+                  const_cast<decltype(device) &>(d.ompDeviceType())};
+              deviceType = device;
+            }
+          } else if constexpr (std::is_same_v<GenericDetails, TypeD> ||
+              std::is_same_v<TypeParamDetails, TypeD> ||
+              std::is_same_v<MiscDetails, TypeD>) { // e.g. KindParamInquiry
+            // These cannot be specified on DECLARE_TARGET. The symbol will
+            // receive the OmpDeclareTarget flag (for a diagnostic message),
+            // but no details will be modified.
+          } else {
+            // Catch any unexpected cases.
+            assert((std::is_base_of_v<WithOmpDeclarative, TypeD>) &&
+                "Unexpected details type");
+          }
+        },
+        const_cast<Symbol *>(sym)->details());
+  }
+
   return true;
 }
 
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index 7712091a03210..b162a61d105ee 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -70,37 +70,55 @@ static void DumpList(llvm::raw_ostream &os, const char *label, const T &list) {
   }
 }
 
-llvm::raw_ostream &operator<<(
-    llvm::raw_ostream &os, const WithOmpDeclarative &x) {
-  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
-
+void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
+    const OmpClauseSet &clauses, parser::CharBlock name) const {
   auto toLower = [](std::string_view sv) {
     return parser::ToLowerCaseLetters(sv);
   };
   auto getLowerName = [&](llvm::omp::Clause c) {
-    return toLower(llvm::omp::getOpenMPClauseName(c, x.version_));
+    return toLower(llvm::omp::getOpenMPClauseName(c, version_));
   };
-  auto printClauses = [&](const OmpClauseSet &cs) {
-    size_t idx{0}, size{cs.count()};
-    cs.IterateOverMembers([&](llvm::omp::Clause c) {
-      os << getLowerName(c);
-      switch (c) {
-      case llvm::omp::Clause::OMPC_atomic_default_mem_order:
-        os << '(' << toLower(EnumToString(*x.ompAtomicDefaultMemOrder()))
-           << ')';
-        break;
-      default:
-        break;
-      }
-      if (++idx < size) {
-        os << ',';
+
+  size_t idx{0}, size{clauses.count()};
+  clauses.IterateOverMembers([&](llvm::omp::Clause c) {
+    os << getLowerName(c);
+    switch (c) {
+    case llvm::omp::Clause::OMPC_atomic_default_mem_order:
+      os << '(' << toLower(EnumToString(*ompAtomicDefaultMemOrder())) << ')';
+      break;
+    case llvm::omp::Clause::OMPC_device_type:
+      os << "(" << toLower(EnumToString(*ompDeviceType())) << ')';
+      break;
+    case llvm::omp::Clause::OMPC_enter:
+    case llvm::omp::Clause::OMPC_link:
+      if (!name.empty()) {
+        os << '(' << name.ToString() << ')';
       }
-    });
-  };
+      break;
+    case llvm::omp::Clause::OMPC_indirect:
+      os << "(true)";
+      break;
+    default:
+      break;
+    }
+    if (++idx < size) {
+      os << ' ';
+    }
+  });
+}
+
+llvm::raw_ostream &operator<<(
+    llvm::raw_ostream &os, const WithOmpDeclarative &x) {
+  using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;
 
   if (const OmpClauseSet &reqs{x.ompRequires()}; reqs.count()) {
     os << " OmpRequirements:(";
-    printClauses(reqs);
+    x.printClauseSet(os, reqs);
+    os << ')';
+  }
+  if (const OmpClauseSet &dtgt{x.ompDeclTarget()}; dtgt.count()) {
+    os << " OmpDeclareTargetFlags:(";
+    x.printClauseSet(os, dtgt);
     os << ')';
   }
   return os;
@@ -545,6 +563,7 @@ llvm::raw_ostream &operator<<(
   if (x.cudaDataAttr()) {
     os << " cudaDataAttr: " << common::EnumToString(*x.cudaDataAttr());
   }
+  os << static_cast<const WithOmpDeclarative &>(x);
   return os;
 }
 
@@ -584,6 +603,7 @@ llvm::raw_ostream &operator<<(
   if (x.isCUDAKernel()) {
     os << " isCUDAKernel";
   }
+  os << static_cast<const WithOmpDeclarative &>(x);
   return os;
 }
 
@@ -679,6 +699,7 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Details &details) {
             for (const auto &object : x.objects()) {
               os << ' ' << object->name();
             }
+            os << static_cast<const WithOmpDeclarative &>(x);
           },
           [&](const TypeParamDetails &x) {
             DumpOptional(os, "type", x.type());
diff --git a/flang/test/Parser/OpenMP/declare_target-device_type.f90 b/flang/test/Parser/OpenMP/declare_target-device_type.f90
index 4b61fb6f16f33..9c2e63c12941c 100644
--- a/flang/test/Parser/OpenMP/declare_target-device_type.f90
+++ b/flang/test/Parser/OpenMP/declare_target-device_type.f90
@@ -7,7 +7,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Host
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Host
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | Flags = {}
@@ -17,7 +17,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Nohost
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Nohost
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | Flags = {}
@@ -27,7 +27,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Any
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Any
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | Flags = {}
@@ -37,7 +37,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Host
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Host
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | | bool = 'true'
@@ -48,7 +48,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Nohost
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Nohost
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | | bool = 'true'
@@ -59,7 +59,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Any
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Any
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | | bool = 'true'
@@ -70,7 +70,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Host
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Host
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'y'
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
@@ -83,7 +83,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Nohost
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Nohost
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'y'
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
@@ -96,7 +96,7 @@ subroutine openmp_declare_target
 
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareTargetDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = declare target
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Any
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Any
 !PARSE-TREE: | OmpClause -> Enter -> OmpEnterClause
 !PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'y'
 !PARSE-TREE: | OmpClause -> To -> OmpToClause
diff --git a/flang/test/Parser/OpenMP/groupprivate.f90 b/flang/test/Parser/OpenMP/groupprivate.f90
index ca4d974f6895c..120af619d3b9b 100644
--- a/flang/test/Parser/OpenMP/groupprivate.f90
+++ b/flang/test/Parser/OpenMP/groupprivate.f90
@@ -21,7 +21,7 @@ module m
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = groupprivate
 !PARSE-TREE: | OmpArgumentList -> OmpArgument -> OmpLocator -> OmpObject -> Designator -> DataRef -> Name = 'x'
 !PARSE-TREE: | OmpArgument -> OmpLocator -> OmpObject -> Designator -> DataRef -> Name = 'y'
-!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> DeviceTypeDescription = Nohost
+!PARSE-TREE: | OmpClauseList -> OmpClause -> DeviceType -> OmpDeviceTypeClause -> OmpDeviceType = Nohost
 !PARSE-TREE: | Flags = {}
 !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpGroupprivateDirective -> OmpDirectiveSpecification
 !PARSE-TREE: | OmpDirectiveName -> llvm::omp::Directive = groupprivate
diff --git a/flang/test/Semantics/OpenMP/declare-target-flags.f90 b/flang/test/Semantics/OpenMP/declare-target-flags.f90
new file mode 100644
index 0000000000000..b17bc61d145f7
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-target-flags.f90
@@ -0,0 +1,36 @@
+!RUN: %flang_fc1 -fdebug-dump-symbols -fopenmp -fopenmp-version=60 %s | FileCheck %s
+
+module m
+integer :: x
+!$omp declare_target link(x) device_type(nohost)
+interface
+  real function g(v)
+    real :: v(10)
+    !$omp declare_target
+  end
+end interface
+contains
+subroutine f
+  !$omp declare_target(f)
+end
+subroutine h
+  integer, save :: a(10)
+  !$omp declare_target enter(h, a)
+  continue
+end
+end module
+
+!CHECK:  Module scope: m size=4 alignment=4 sourceRange=295 bytes
+!CHECK:    f, PUBLIC (Subroutine): Subprogram () OmpDeclareTargetFlags:(enter)
+!CHECK:    g, EXTERNAL, PUBLIC (Function, OmpDeclareTarget): Subprogram isInterface result:REAL(4) g (REAL(4) v) OmpDeclareTargetFlags:(enter)
+!CHECK:    h, PUBLIC (Subroutine): Subprogram () OmpDeclareTargetFlags:(enter)
+!CHECK:    x, PUBLIC (OmpDeclareTarget) size=4 offset=0: ObjectEntity type: INTEGER(4) OmpDeclareTargetFlags:(device_type(nohost) link)
+!CHECK:    Subprogram scope: g size=44 alignment=4 sourceRange=57 bytes
+!CHECK:      g size=4 offset=0: ObjectEntity funcResult type: REAL(4)
+!CHECK:      v size=40 offset=4: ObjectEntity dummy type: REAL(4) shape: 1_8:10_8
+!CHECK:    Subprogram scope: f size=0 alignment=1 sourceRange=40 bytes
+!CHECK:      f (Subroutine, OmpDeclareTarget): HostAssoc => f, PUBLIC (Subroutine): Subprogram () OmpDeclareTargetFlags:(enter)
+!CHECK:    Subprogram scope: h size=40 alignment=4 sourceRange=81 bytes
+!CHECK:      a, SAVE (OmpDeclareTarget) size=40 offset=0: ObjectEntity type: INTEGER(4) shape: 1_8:10_8 OmpDeclareTargetFlags:(enter)
+!CHECK:      h (Subroutine, OmpDeclareTarget): HostAssoc => h, PUBLIC (Subroutine): Subprogram () OmpDeclareTargetFlags:(enter)
+
diff --git a/flang/test/Semantics/OpenMP/declare-target-modfile.f90 b/flang/test/Semantics/OpenMP/declare-target-modfile.f90
new file mode 100644
index 0000000000000..eb4c4c855f597
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-target-modfile.f90
@@ -0,0 +1,41 @@
+!RUN: %python %S/../test_modfile.py %s %flang_fc1 -fopenmp -fopenmp-version=60
+
+module m
+integer :: x
+!$omp declare_target link(x) device_type(nohost)
+interface
+  real function g(v)
+    real :: v(10)
+    !$omp declare_target
+  end
+end interface
+contains
+subroutine f
+  !$omp declare_target(f)
+end
+subroutine h
+  integer, save :: a(10)
+  !$omp declare_target enter(h, a)
+  continue
+end
+end module
+
+!Expect: m.mod
+!module m
+!integer(4)::x
+!interface
+!function g(v)
+!real(4)::v(1_8:10_8)
+!real(4)::g
+!end
+!end interface
+!!$omp declare_target device_type(nohost) link(x)
+!!$omp declare_target enter(g)
+!!$omp declare_target enter(f)
+!!$omp declare_target enter(h)
+!contains
+!subroutine f()
+!end
+!subroutine h()
+!end
+!end
diff --git a/flang/test/Semantics/OpenMP/dump-requires-details.f90 b/flang/test/Semantics/OpenMP/dump-requires-details.f90
index 9c844c092c5e6..dbca9df41c5fb 100644
--- a/flang/test/Semantics/OpenMP/dump-requires-details.f90
+++ b/flang/test/Semantics/OpenMP/dump-requires-details.f90
@@ -11,4 +11,4 @@ subroutine f01
 end
 end module
 
-!CHECK: fred: Module OmpRequirements:(atomic_default_mem_order(relaxed),unified_address,unified_shared_memory)
+!CHECK: fred: Module OmpRequirements:(atomic_default_mem_order(relaxed) unified_address unified_shared_memory)

>From 227e62e6d087f9c976aad91bedb77872100ab613 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 2 Jun 2026 07:33:48 -0500
Subject: [PATCH 3/5] format

---
 flang/lib/Semantics/mod-file.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 4f3d989d35235..e4dc5151abbc2 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -404,7 +404,8 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
       if (const OmpClauseSet &dtgt{decls->ompDeclTarget()}; dtgt.count()) {
         os << "!$omp "
            << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
-                  llvm::omp::Directive::OMPD_declare_target, version)) << " ";
+                  llvm::omp::Directive::OMPD_declare_target, version))
+           << " ";
         decls->printClauseSet(os, dtgt, symbol.name());
         os << "\n";
       }

>From 905afb8313029dd00866a78368d6b19ddcf9a4d2 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 2 Jun 2026 07:36:44 -0500
Subject: [PATCH 4/5] format

---
 flang/lib/Semantics/mod-file.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index e4dc5151abbc2..b81c84572eaa9 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -377,7 +377,6 @@ static const WithOmpDeclarative *GetOmpDeclarative(const Symbol &symbol) {
       symbol.details());
 }
 
-
 static void PutOpenMPRequirements(
     llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &semaCtx) {
   using OmpClauseSet = WithOmpDeclarative::OmpClauseSet;

>From 0cd33ad834c5cd13c2a4c2fb39dac59365baef8e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 3 Jun 2026 07:57:37 -0500
Subject: [PATCH 5/5] Update symbol.cpp

---
 flang/lib/Semantics/symbol.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index aa29f549a2b54..49f27ee1f249f 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -76,7 +76,7 @@ void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
 
   size_t idx{0}, size{clauses.count()};
   clauses.IterateOverMembers([&](llvm::omp::Clause c) {
-    os << toLower(llvm::omp::getOpenMPClauseName(c, x.version_));
+    os << toLower(llvm::omp::getOpenMPClauseName(c, version_));
     switch (c) {
     case llvm::omp::Clause::OMPC_atomic_default_mem_order:
       os << '(' << toLower(EnumToString(*ompAtomicDefaultMemOrder())) << ')';



More information about the flang-commits mailing list