diff --git a/flang/include/flang/Semantics/openmp-modifiers.h b/flang/include/flang/Semantics/openmp-modifiers.h new file mode 100644 index 0000000000000..6be582761ed68 --- /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 +#include +#include +#include + +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; +using OmpClauses = + common::EnumSet; + +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 props_; + // Version-dependent set of clauses to which the modifier can apply. + const std::map clauses_; +}; + +template const OmpModifierDescriptor &OmpGetDescriptor(); + +template <> +const OmpModifierDescriptor &OmpGetDescriptor(); +template <> +const OmpModifierDescriptor &OmpGetDescriptor(); +template <> +const OmpModifierDescriptor &OmpGetDescriptor(); +template <> +const OmpModifierDescriptor &OmpGetDescriptor(); +template <> +const OmpModifierDescriptor &OmpGetDescriptor(); + +// 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; +// Variant u; +// }; +// std::tuple>, ...> 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 +const OmpModifierDescriptor &OmpGetDescriptor(const UnionTy &modifier) { + return common::visit( + [](auto &&m) -> decltype(auto) { + using SpecificTy = llvm::remove_cvref_t; + return OmpGetDescriptor(); + }, + 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 +const std::optional> &OmpGetModifiers( + const ClauseTy &clause) { + using UnionTy = typename ClauseTy::Modifier; + return std::get>>(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> &modifiers = ... +/// ... = findInRange(modifiers->begin(), modifiers->end()); +template +typename std::list::const_iterator findInRange( + typename std::list::const_iterator begin, + typename std::list::const_iterator end) { + for (auto it{begin}; it != end; ++it) { + if (std::holds_alternative(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 +const SpecificTy *OmpGetUniqueModifier( + const std::optional> &modifiers) { + const SpecificTy *found{nullptr}; + if (modifiers) { + auto end{modifiers->cend()}; + // typename std::list::iterator end{modifiers->end()}; + auto at{detail::findInRange(modifiers->cbegin(), end)}; + if (at != end) { + found = &std::get(at->u); +#ifndef NDEBUG + auto another{ + detail::findInRange(std::next(at), end)}; + assert(another == end && "repeated modifier"); +#endif + } + } + return found; +} + +namespace detail { +template constexpr const T *make_nullptr() { + return static_cast(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 +bool verifyIfRequired(const SpecificTy *, + const std::optional> &modifiers, + parser::CharBlock clauseSource, SemanticsContext &semaCtx) { + unsigned version{semaCtx.langOptions().OpenMPVersion}; + const OmpModifierDescriptor &desc{OmpGetDescriptor()}; + 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(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 +bool verifyRequiredPack(const std::optional> &modifiers, + parser::CharBlock clauseSource, SemanticsContext &semaCtx, + std::integer_sequence) { + using VariantTy = typename UnionTy::Variant; + return (verifyIfRequired( + make_nullptr>(), + modifiers, clauseSource, semaCtx) && + ...); +} + +/// Verify the Required property for the given list. Return true if the +/// list is valid, or false otherwise. +template +bool verifyRequired(const std::optional> &modifiers, + parser::CharBlock clauseSource, SemanticsContext &semaCtx) { + using VariantTy = typename UnionTy::Variant; + return verifyRequiredPack(modifiers, clauseSource, semaCtx, + std::make_index_sequence>{}); +} + +/// 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 +bool verifyIfUnique(const SpecificTy *, + typename std::list::const_iterator specific, + typename std::list::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()}; + // 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(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 +bool verifyUnique(const std::optional> &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(&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 +bool verifyUltimate(const std::optional> &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; + const OmpModifierDescriptor &desc{OmpGetDescriptor()}; + 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 +bool verifyExclusive(const std::optional> &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 +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 41406ecf50e00..7855ae7eed138 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 0000000000000..70ca988cddce5 --- /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 +#include +#include + +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 +static unsigned findVersion( + unsigned version, const std::map &map) { + llvm::ArrayRef 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() { + 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() { + 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() { + static const OmpModifierDescriptor desc{ + /*name=*/"linear-modifier", + /*props=*/ + { + {45, {OmpProperty::Unique}}, + }, + /*clauses=*/ + { + {45, {Clause::OMPC_linear}}, + }, + }; + return desc; +} + +template <> +const OmpModifierDescriptor & +OmpGetDescriptor() { + 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() { + 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 0d79c071ecd30..dd771ac3b416f 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 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 fdb09678d7a4c..66e02b6ab5859 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 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"))