[flang-commits] [flang] fb4ecad - [flang][OpenMP] Change clause modifier representation in parser (#116656)
via flang-commits
flang-commits at lists.llvm.org
Wed Nov 20 08:38:11 PST 2024
Author: Krzysztof Parzyszek
Date: 2024-11-20T10:38:06-06:00
New Revision: fb4ecada815ceee37536a26b4ff5ce231226b23e
URL: https://github.com/llvm/llvm-project/commit/fb4ecada815ceee37536a26b4ff5ce231226b23e
DIFF: https://github.com/llvm/llvm-project/commit/fb4ecada815ceee37536a26b4ff5ce231226b23e.diff
LOG: [flang][OpenMP] Change clause modifier representation in parser (#116656)
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
Added:
flang/include/flang/Semantics/openmp-modifiers.h
flang/lib/Semantics/openmp-modifiers.cpp
Modified:
flang/lib/Semantics/CMakeLists.txt
llvm/include/llvm/Frontend/OpenMP/OMP.h
llvm/lib/Frontend/OpenMP/OMP.cpp
Removed:
################################################################################
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
diff erent 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
diff erent 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
diff erent 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 37b778417fc24e..2792dc42810155 100644
--- a/llvm/lib/Frontend/OpenMP/OMP.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMP.cpp
@@ -189,6 +189,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 flang-commits
mailing list