[llvm-branch-commits] [flang] [llvm] [flang][OpenMP] Change clause modifier representation in parser (PR #116656)

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Nov 18 14:14:31 PST 2024


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

>From e8bbc26e136993758c3a3197eed6b1924c6531d0 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 18 Nov 2024 08:47:24 -0600
Subject: [PATCH] [flang][OpenMP] Change clause modifier representation in
 parser

The main issue to solve is that OpenMP modifiers can be specified
in any order, so the parser cannot expect any specific modifier at
a given position. To solve that, define modifier to be a union of
all allowable specific modifiers for a given clause.

Additionally, implement modifier descriptors: for each modifier the
corresponding descriptor contains a set of properties of the modifier
that allow a common set of semantic checks. Start with the syntactic
properties defined in the spec: Required, Unique, Exclusive, Ultimate,
and implement common checks to verify each of them.

OpenMP modifier overhaul: #2/3
---
 .../flang/Semantics/openmp-modifiers.h        | 391 ++++++++++++++++++
 flang/lib/Semantics/CMakeLists.txt            |   1 +
 flang/lib/Semantics/openmp-modifiers.cpp      | 146 +++++++
 llvm/include/llvm/Frontend/OpenMP/OMP.h       |   2 +
 llvm/lib/Frontend/OpenMP/OMP.cpp              |   5 +
 5 files changed, 545 insertions(+)
 create mode 100644 flang/include/flang/Semantics/openmp-modifiers.h
 create mode 100644 flang/lib/Semantics/openmp-modifiers.cpp

diff --git a/flang/include/flang/Semantics/openmp-modifiers.h b/flang/include/flang/Semantics/openmp-modifiers.h
new file mode 100644
index 00000000000000..6be582761ed687
--- /dev/null
+++ b/flang/include/flang/Semantics/openmp-modifiers.h
@@ -0,0 +1,391 @@
+//===-- flang/lib/Semantics/openmp-modifiers.h ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_
+#define FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_
+
+#include "flang/Common/enum-set.h"
+#include "flang/Parser/parse-tree.h"
+#include "flang/Semantics/semantics.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Frontend/OpenMP/OMP.h"
+
+#include <cassert>
+#include <map>
+#include <optional>
+#include <variant>
+
+namespace Fortran::semantics {
+
+// Ref: [5.2:58]
+//
+// Syntactic properties for Clauses, Arguments and Modifiers
+//
+// Inverse properties:
+//   not Required  -> Optional
+//   not Unique    -> Repeatable
+//   not Exclusive -> Compatible
+//   not Ultimate  -> Free
+//
+// Clause defaults:   Optional, Repeatable, Compatible, Free
+// Argument defaults: Required,     Unique, Compatible, Free
+// Modifier defaults: Optional,     Unique, Compatible, Free
+//
+// ---
+// Each modifier is used as either pre-modifier (i.e. modifier: item),
+// or post-modifier (i.e. item: modifier). The default is pre-.
+// Add an additional property that reflects the type of modifier.
+
+ENUM_CLASS(OmpProperty, Required, Unique, Exclusive, Ultimate, Post);
+using OmpProperties = common::EnumSet<OmpProperty, OmpProperty_enumSize>;
+using OmpClauses =
+    common::EnumSet<llvm::omp::Clause, llvm::omp::Clause_enumSize>;
+
+struct OmpModifierDescriptor {
+  // Modifier name for use in diagnostic messages.
+  const OmpProperties &props(unsigned version) const;
+  const OmpClauses &clauses(unsigned version) const;
+
+  const llvm::StringRef name;
+  // Version-dependent properties of the modifier.
+  const std::map<unsigned, OmpProperties> props_;
+  // Version-dependent set of clauses to which the modifier can apply.
+  const std::map<unsigned, OmpClauses> clauses_;
+};
+
+template <typename SpecificTy> const OmpModifierDescriptor &OmpGetDescriptor();
+
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpDependenceType>();
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpIterator>();
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpLinearModifier>();
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpReductionIdentifier>();
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpTaskDependenceType>();
+
+// Explanation of terminology:
+//
+// A typical clause with modifier[s] looks like this (with parts that are
+// not relevant here removed):
+//   struct OmpSomeClause {
+//     struct Modifier {
+//       using Variant = std::variant<Specific1, Specific2...>;
+//       Variant u;
+//     };
+//     std::tuple<std::optional<std::list<Modifier>>, ...> t;
+//   };
+//
+// The Speficic1, etc. refer to parser classes that represent modifiers,
+// e.g. OmpIterator or OmpTaskDependenceType. The Variant type contains
+// all modifiers that are allowed for a given clause. The Modifier class
+// is there to wrap the variant into the form that the parse tree visitor
+// expects, i.e. with traits, member "u", etc.
+//
+// To avoid ambiguities with the word "modifier" (e.g. is it "any modifier",
+// or "this specific modifier"?), the following code uses different terms:
+//
+// - UnionTy:    refers to the nested "Modifier" class, i.e.
+//               "OmpSomeClause::Modifier" in the example above.
+// - SpecificTy: refers to any of the alternatives, i.e. "Specific1" or
+//               "Specific2".
+
+template <typename UnionTy>
+const OmpModifierDescriptor &OmpGetDescriptor(const UnionTy &modifier) {
+  return common::visit(
+      [](auto &&m) -> decltype(auto) {
+        using SpecificTy = llvm::remove_cvref_t<decltype(m)>;
+        return OmpGetDescriptor<SpecificTy>();
+      },
+      modifier.u);
+}
+
+/// Return the optional list of modifiers for a given `Omp[...]Clause`.
+/// Specifically, the parameter type `ClauseTy` is the class that OmpClause::v
+/// holds.
+template <typename ClauseTy>
+const std::optional<std::list<typename ClauseTy::Modifier>> &OmpGetModifiers(
+    const ClauseTy &clause) {
+  using UnionTy = typename ClauseTy::Modifier;
+  return std::get<std::optional<std::list<UnionTy>>>(clause.t);
+}
+
+namespace detail {
+/// Finds the first entry in the iterator range that holds the `SpecificTy`
+/// alternative, or the end iterator if it does not exist.
+/// The `SpecificTy` should be provided, the `UnionTy` is expected to be
+/// auto-deduced, e.g.
+///   const std::optional<std::list<X>> &modifiers = ...
+///   ... = findInRange<OmpIterator>(modifiers->begin(), modifiers->end());
+template <typename SpecificTy, typename UnionTy>
+typename std::list<UnionTy>::const_iterator findInRange(
+    typename std::list<UnionTy>::const_iterator begin,
+    typename std::list<UnionTy>::const_iterator end) {
+  for (auto it{begin}; it != end; ++it) {
+    if (std::holds_alternative<SpecificTy>(it->u)) {
+      return it;
+    }
+  }
+  return end;
+}
+} // namespace detail
+
+/// Finds the entry in the list that holds the `SpecificTy` alternative,
+/// and returns the pointer to that alternative. If such an entry does not
+/// exist, it returns nullptr.
+/// The list is assumed to contain at most one such item, with a check
+/// whether the condition is met.
+/// This function should only be called after the verification of modifier
+/// properties has been performed, since it will assert if multiple items
+/// are found.
+template <typename SpecificTy, typename UnionTy>
+const SpecificTy *OmpGetUniqueModifier(
+    const std::optional<std::list<UnionTy>> &modifiers) {
+  const SpecificTy *found{nullptr};
+  if (modifiers) {
+    auto end{modifiers->cend()};
+    // typename std::list<UnionTy>::iterator end{modifiers->end()};
+    auto at{detail::findInRange<SpecificTy, UnionTy>(modifiers->cbegin(), end)};
+    if (at != end) {
+      found = &std::get<SpecificTy>(at->u);
+#ifndef NDEBUG
+      auto another{
+          detail::findInRange<SpecificTy, UnionTy>(std::next(at), end)};
+      assert(another == end && "repeated modifier");
+#endif
+    }
+  }
+  return found;
+}
+
+namespace detail {
+template <typename T> constexpr const T *make_nullptr() {
+  return static_cast<const T *>(nullptr);
+}
+
+/// Helper function for verifying the Required property:
+/// For a specific SpecificTy, if SpecificTy is has the Required property,
+/// check if the list has an item that holds SpecificTy as an alternative.
+/// If SpecificTy does not have the Required property, ignore it.
+template <typename SpecificTy, typename UnionTy>
+bool verifyIfRequired(const SpecificTy *,
+    const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
+  const OmpModifierDescriptor &desc{OmpGetDescriptor<SpecificTy>()};
+  if (!desc.props(version).test(OmpProperty::Required)) {
+    // If the modifier is not required, there is nothing to do.
+    return true;
+  }
+  bool present{modifiers.has_value()};
+  present = present && llvm::any_of(*modifiers, [](auto &&m) {
+    return std::holds_alternative<SpecificTy>(m.u);
+  });
+  if (!present) {
+    semaCtx.Say(
+        clauseSource, "A %s modifier is required"_err_en_US, desc.name.str());
+  }
+  return present;
+}
+
+/// Helper function for verifying the Required property:
+/// Visit all specific types in UnionTy, and verify the Required property
+/// for each one of them.
+template <typename UnionTy, size_t... Idxs>
+bool verifyRequiredPack(const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx,
+    std::integer_sequence<size_t, Idxs...>) {
+  using VariantTy = typename UnionTy::Variant;
+  return (verifyIfRequired(
+              make_nullptr<std::variant_alternative_t<Idxs, VariantTy>>(),
+              modifiers, clauseSource, semaCtx) &&
+      ...);
+}
+
+/// Verify the Required property for the given list. Return true if the
+/// list is valid, or false otherwise.
+template <typename UnionTy>
+bool verifyRequired(const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
+  using VariantTy = typename UnionTy::Variant;
+  return verifyRequiredPack(modifiers, clauseSource, semaCtx,
+      std::make_index_sequence<std::variant_size_v<VariantTy>>{});
+}
+
+/// Helper function to verify the Unique property.
+/// If SpecificTy has the Unique property, and an item is found holding
+/// it as the alternative, verify that none of the elements that follow
+/// hold SpecificTy as the alternative.
+template <typename UnionTy, typename SpecificTy>
+bool verifyIfUnique(const SpecificTy *,
+    typename std::list<UnionTy>::const_iterator specific,
+    typename std::list<UnionTy>::const_iterator end,
+    SemanticsContext &semaCtx) {
+  // `specific` is the location of the modifier of type SpecificTy.
+  assert(specific != end && "`specific` must be a valid location");
+
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
+  const OmpModifierDescriptor &desc{OmpGetDescriptor<SpecificTy>()};
+  // Ultimate implies Unique.
+  if (!desc.props(version).test(OmpProperty::Unique) &&
+      !desc.props(version).test(OmpProperty::Ultimate)) {
+    return true;
+  }
+  if (std::next(specific) != end) {
+    auto next{
+        detail::findInRange<SpecificTy, UnionTy>(std::next(specific), end)};
+    if (next != end) {
+      semaCtx.Say(next->source, "A %s cannot occur multiple times"_err_en_US,
+          desc.name.str());
+    }
+  }
+  return true;
+}
+
+/// Verify the Unique property for the given list. Return true if the
+/// list is valid, or false otherwise.
+template <typename UnionTy>
+bool verifyUnique(const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
+  if (!modifiers) {
+    return true;
+  }
+  bool result{true};
+  for (auto it{modifiers->cbegin()}, end{modifiers->cend()}; it != end; ++it) {
+    result = common::visit(
+                 [&](auto &&m) {
+                   return verifyIfUnique<UnionTy>(&m, it, end, semaCtx);
+                 },
+                 it->u) &&
+        result;
+  }
+  return result;
+}
+
+/// Verify the Ultimate property for the given list. Return true if the
+/// list is valid, or false otherwise.
+template <typename UnionTy>
+bool verifyUltimate(const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
+  if (!modifiers || modifiers->size() <= 1) {
+    return true;
+  }
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
+  bool result{true};
+  auto first{modifiers->cbegin()};
+  auto last{std::prev(modifiers->cend())};
+
+  // Any item that has the Ultimate property has to be either at the back
+  // or at the front of the list (depending on whether it's a pre- or a post-
+  // modifier).
+  // Walk over the list, and if a given item has the Ultimate property but is
+  // not at the right position, mark it as an error.
+  for (auto it{first}, end{modifiers->cend()}; it != end; ++it) {
+    result =
+        common::visit(
+            [&](auto &&m) {
+              using SpecificTy = llvm::remove_cvref_t<decltype(m)>;
+              const OmpModifierDescriptor &desc{OmpGetDescriptor<SpecificTy>()};
+              auto &props{desc.props(version)};
+
+              if (props.test(OmpProperty::Ultimate)) {
+                bool isPre = !props.test(OmpProperty::Post);
+                if (it == (isPre ? last : first)) {
+                  // Skip, since this is the correct place for this modifier.
+                  return true;
+                }
+                llvm::StringRef where{isPre ? "last" : "first"};
+                semaCtx.Say(it->source,
+                    "The %s should be the %s modifier"_err_en_US,
+                    desc.name.str(), where.str());
+                return false;
+              }
+              return true;
+            },
+            it->u) &&
+        result;
+  }
+  return result;
+}
+
+/// Verify the Exclusive property for the given list. Return true if the
+/// list is valid, or false otherwise.
+template <typename UnionTy>
+bool verifyExclusive(const std::optional<std::list<UnionTy>> &modifiers,
+    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
+  if (!modifiers || modifiers->size() <= 1) {
+    return true;
+  }
+  unsigned version{semaCtx.langOptions().OpenMPVersion};
+  const UnionTy &front{modifiers->front()};
+  const OmpModifierDescriptor &frontDesc{OmpGetDescriptor(front)};
+
+  auto second{std::next(modifiers->cbegin())};
+  auto end{modifiers->end()};
+
+  auto emitErrorMessage{[&](const UnionTy &excl, const UnionTy &other) {
+    const OmpModifierDescriptor &descExcl{OmpGetDescriptor(excl)};
+    const OmpModifierDescriptor &descOther{OmpGetDescriptor(other)};
+    parser::MessageFormattedText txt(
+        "An exclusive %s cannot be specified together with a modifier of a different type"_err_en_US,
+        descExcl.name.str());
+    parser::Message message(excl.source, txt);
+    message.Attach(
+        other.source, "%s provided here"_en_US, descOther.name.str());
+    semaCtx.Say(std::move(message));
+  }};
+
+  if (frontDesc.props(version).test(OmpProperty::Exclusive)) {
+    // If the first item has the Exclusive property, then check if there is
+    // another item in the rest of the list with a different SpecificTy as
+    // the alternative, and mark it as an error. This allows multiple Exclusive
+    // items to coexist as long as they hold the same SpecificTy.
+    bool result{true};
+    size_t frontIndex{front.u.index()};
+    for (auto it{second}; it != end; ++it) {
+      if (it->u.index() != frontIndex) {
+        emitErrorMessage(front, *it);
+        result = false;
+        break;
+      }
+    }
+    return result;
+  } else {
+    // If the first item does not have the Exclusive property, then check
+    // if there is an item in the rest of the list that is Exclusive, and
+    // mark it as an error if so.
+    bool result{true};
+    for (auto it{second}; it != end; ++it) {
+      const OmpModifierDescriptor &desc{OmpGetDescriptor(*it)};
+      if (desc.props(version).test(OmpProperty::Exclusive)) {
+        emitErrorMessage(*it, front);
+        result = false;
+        break;
+      }
+    }
+    return result;
+  }
+}
+} // namespace detail
+
+template <typename ClauseTy>
+bool OmpVerifyModifiers(const ClauseTy &clause, parser::CharBlock clauseSource,
+    SemanticsContext &semaCtx) {
+  auto &modifiers{OmpGetModifiers(clause)};
+  bool result{detail::verifyRequired(modifiers, clauseSource, semaCtx)};
+  result = detail::verifyUnique(modifiers, clauseSource, semaCtx) && result;
+  result = detail::verifyUltimate(modifiers, clauseSource, semaCtx) && result;
+  result = detail::verifyExclusive(modifiers, clauseSource, semaCtx) && result;
+  return result;
+}
+} // namespace Fortran::semantics
+
+#endif // FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 41406ecf50e004..7855ae7eed1387 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -31,6 +31,7 @@ add_flang_library(FortranSemantics
   definable.cpp
   expression.cpp
   mod-file.cpp
+  openmp-modifiers.cpp
   pointer-assignment.cpp
   program-tree.cpp
   resolve-labels.cpp
diff --git a/flang/lib/Semantics/openmp-modifiers.cpp b/flang/lib/Semantics/openmp-modifiers.cpp
new file mode 100644
index 00000000000000..70ca988cddce59
--- /dev/null
+++ b/flang/lib/Semantics/openmp-modifiers.cpp
@@ -0,0 +1,146 @@
+//===-- flang/lib/Semantics/openmp-modifiers.cpp --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Semantics/openmp-modifiers.h"
+
+#include "flang/Parser/parse-tree.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Frontend/OpenMP/OMP.h"
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+
+namespace Fortran::semantics {
+using namespace llvm::omp;
+
+/// Find the highest version that exists as a key in the given map,
+/// and is less than or equal to `version`.
+/// Account for "version" not being a value from getOpenMPVersions().
+template <typename ValueTy>
+static unsigned findVersion(
+    unsigned version, const std::map<unsigned, ValueTy> &map) {
+  llvm::ArrayRef<unsigned> versions{llvm::omp::getOpenMPVersions()};
+  assert(!versions.empty() && "getOpenMPVersions returned empty list");
+  version = std::clamp(version, versions.front(), versions.back());
+
+  // std::map is sorted with respect to keys, by default in the ascending
+  // order.
+  unsigned found{0};
+  for (auto &[v, _] : map) {
+    if (v <= version) {
+      found = v;
+    } else {
+      break;
+    }
+  }
+
+  assert(found != 0 && "cannot locate entry for version in map");
+  return found;
+}
+
+const OmpProperties &OmpModifierDescriptor::props(unsigned version) const {
+  return props_.at(findVersion(version, props_));
+}
+
+const OmpClauses &OmpModifierDescriptor::clauses(unsigned version) const {
+  return clauses_.at(findVersion(version, clauses_));
+}
+
+// Note: The intent for these functions is to have them be automatically-
+// generated in the future.
+
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpDependenceType>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"dependence-type",
+      /*props=*/
+      {
+          {45, {OmpProperty::Required, OmpProperty::Ultimate}},
+      },
+      /*clauses=*/
+      {
+          {45, {Clause::OMPC_depend}},
+          {51, {Clause::OMPC_depend, Clause::OMPC_update}},
+          {52, {Clause::OMPC_doacross}},
+      },
+  };
+  return desc;
+}
+
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpIterator>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"iterator",
+      /*props=*/
+      {
+          {50, {OmpProperty::Unique}},
+      },
+      /*clauses=*/
+      {
+          {50, {Clause::OMPC_affinity, Clause::OMPC_depend}},
+          {51,
+              {Clause::OMPC_affinity, Clause::OMPC_depend, Clause::OMPC_from,
+                  Clause::OMPC_map, Clause::OMPC_to}},
+      },
+  };
+  return desc;
+}
+
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpLinearModifier>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"linear-modifier",
+      /*props=*/
+      {
+          {45, {OmpProperty::Unique}},
+      },
+      /*clauses=*/
+      {
+          {45, {Clause::OMPC_linear}},
+      },
+  };
+  return desc;
+}
+
+template <>
+const OmpModifierDescriptor &
+OmpGetDescriptor<parser::OmpReductionIdentifier>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"reduction-identifier",
+      /*props=*/
+      {
+          {45, {OmpProperty::Required, OmpProperty::Ultimate}},
+      },
+      /*clauses=*/
+      {
+          {45, {Clause::OMPC_reduction}},
+          {50,
+              {Clause::OMPC_in_reduction, Clause::OMPC_reduction,
+                  Clause::OMPC_task_reduction}},
+      },
+  };
+  return desc;
+}
+
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpTaskDependenceType>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"task-dependence-type",
+      /*props=*/
+      {
+          {52, {OmpProperty::Required, OmpProperty::Ultimate}},
+      },
+      /*clauses=*/
+      {
+          {52, {Clause::OMPC_depend, Clause::OMPC_update}},
+      },
+  };
+  return desc;
+}
+} // namespace Fortran::semantics
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.h b/llvm/include/llvm/Frontend/OpenMP/OMP.h
index 0d79c071ecd30d..dd771ac3b416fb 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.h
@@ -47,6 +47,8 @@ static constexpr inline bool canHaveIterator(Clause C) {
   }
 }
 
+ArrayRef<unsigned> getOpenMPVersions();
+
 /// Create a nicer version of a function name for humans to look at.
 std::string prettifyFunctionName(StringRef FunctionName);
 
diff --git a/llvm/lib/Frontend/OpenMP/OMP.cpp b/llvm/lib/Frontend/OpenMP/OMP.cpp
index fdb09678d7a4cc..66e02b6ab58591 100644
--- a/llvm/lib/Frontend/OpenMP/OMP.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMP.cpp
@@ -193,6 +193,11 @@ bool isCombinedConstruct(Directive D) {
   return !getLeafConstructs(D).empty() && !isCompositeConstruct(D);
 }
 
+ArrayRef<unsigned> getOpenMPVersions() {
+  static unsigned Versions[]{45, 50, 51, 52, 60};
+  return Versions;
+}
+
 std::string prettifyFunctionName(StringRef FunctionName) {
   // Internalized functions have the right name, but simply a suffix.
   if (FunctionName.ends_with(".internalized"))



More information about the llvm-branch-commits mailing list