Krzysztof Parzyszek e00ef602a8 [flang][OpenMP] Semantic checks for DYN_GROUPPRIVATE
Add checks for non-STRICT values of the prescriptiveness modifier on
clauses that had accepted it prior to the addition of FALLBACK value
(GRAINSIZE and NUM_TASKS).
2025-08-20 08:55:19 -05:00

520 lines
19 KiB
C++

//===-- 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/characters.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 <memory>
#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;
unsigned since(llvm::omp::Clause id) 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();
#define DECLARE_DESCRIPTOR(name) \
template <> const OmpModifierDescriptor &OmpGetDescriptor<name>()
DECLARE_DESCRIPTOR(parser::OmpAccessGroup);
DECLARE_DESCRIPTOR(parser::OmpAlignment);
DECLARE_DESCRIPTOR(parser::OmpAlignModifier);
DECLARE_DESCRIPTOR(parser::OmpAllocatorComplexModifier);
DECLARE_DESCRIPTOR(parser::OmpAllocatorSimpleModifier);
DECLARE_DESCRIPTOR(parser::OmpAlwaysModifier);
DECLARE_DESCRIPTOR(parser::OmpAutomapModifier);
DECLARE_DESCRIPTOR(parser::OmpChunkModifier);
DECLARE_DESCRIPTOR(parser::OmpCloseModifier);
DECLARE_DESCRIPTOR(parser::OmpContextSelector);
DECLARE_DESCRIPTOR(parser::OmpDeleteModifier);
DECLARE_DESCRIPTOR(parser::OmpDependenceType);
DECLARE_DESCRIPTOR(parser::OmpDeviceModifier);
DECLARE_DESCRIPTOR(parser::OmpDirectiveNameModifier);
DECLARE_DESCRIPTOR(parser::OmpExpectation);
DECLARE_DESCRIPTOR(parser::OmpInteropPreference);
DECLARE_DESCRIPTOR(parser::OmpInteropType);
DECLARE_DESCRIPTOR(parser::OmpIterator);
DECLARE_DESCRIPTOR(parser::OmpLastprivateModifier);
DECLARE_DESCRIPTOR(parser::OmpLinearModifier);
DECLARE_DESCRIPTOR(parser::OmpMapper);
DECLARE_DESCRIPTOR(parser::OmpMapType);
DECLARE_DESCRIPTOR(parser::OmpMapTypeModifier);
DECLARE_DESCRIPTOR(parser::OmpOrderModifier);
DECLARE_DESCRIPTOR(parser::OmpOrderingModifier);
DECLARE_DESCRIPTOR(parser::OmpPrescriptiveness);
DECLARE_DESCRIPTOR(parser::OmpPresentModifier);
DECLARE_DESCRIPTOR(parser::OmpReductionIdentifier);
DECLARE_DESCRIPTOR(parser::OmpReductionModifier);
DECLARE_DESCRIPTOR(parser::OmpRefModifier);
DECLARE_DESCRIPTOR(parser::OmpSelfModifier);
DECLARE_DESCRIPTOR(parser::OmpStepComplexModifier);
DECLARE_DESCRIPTOR(parser::OmpStepSimpleModifier);
DECLARE_DESCRIPTOR(parser::OmpTaskDependenceType);
DECLARE_DESCRIPTOR(parser::OmpVariableCategory);
DECLARE_DESCRIPTOR(parser::OmpxHoldModifier);
#undef DECLARE_DESCRIPTOR
// 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 Specific1, 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 first 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.
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()};
auto at{detail::findInRange<SpecificTy, UnionTy>(modifiers->cbegin(), end)};
if (at != end) {
found = &std::get<SpecificTy>(at->u);
}
}
return found;
}
template <typename SpecificTy> struct OmpSpecificModifierIterator {
using VectorTy = std::vector<const SpecificTy *>;
OmpSpecificModifierIterator(
std::shared_ptr<VectorTy> list, typename VectorTy::const_iterator where)
: specificList(list), at(where) {}
OmpSpecificModifierIterator &operator++() {
++at;
return *this;
}
// OmpSpecificModifierIterator &operator++(int);
OmpSpecificModifierIterator &operator--() {
--at;
return *this;
}
// OmpSpecificModifierIterator &operator--(int);
const SpecificTy *operator*() const { return *at; }
bool operator==(const OmpSpecificModifierIterator &other) const {
assert(specificList.get() == other.specificList.get() &&
"comparing unrelated iterators");
return at == other.at;
}
bool operator!=(const OmpSpecificModifierIterator &other) const {
return !(*this == other);
}
private:
std::shared_ptr<VectorTy> specificList;
typename VectorTy::const_iterator at;
};
template <typename SpecificTy, typename UnionTy>
llvm::iterator_range<OmpSpecificModifierIterator<SpecificTy>>
OmpGetRepeatableModifier(const std::optional<std::list<UnionTy>> &modifiers) {
using VectorTy = std::vector<const SpecificTy *>;
std::shared_ptr<VectorTy> items(new VectorTy);
if (modifiers) {
for (auto &m : *modifiers) {
if (auto *s = std::get_if<SpecificTy>(&m.u)) {
items->push_back(s);
}
}
}
return llvm::iterator_range(
OmpSpecificModifierIterator(items, items->begin()),
OmpSpecificModifierIterator(items, items->end()));
}
// Attempt to prevent creating a range based on an expiring modifier list.
template <typename SpecificTy, typename UnionTy>
llvm::iterator_range<OmpSpecificModifierIterator<SpecificTy>>
OmpGetRepeatableModifier(std::optional<std::list<UnionTy>> &&) = delete;
template <typename SpecificTy, typename UnionTy>
Fortran::parser::CharBlock OmpGetModifierSource(
const std::optional<std::list<UnionTy>> &modifiers,
const SpecificTy *specific) {
if (!modifiers || !specific) {
return Fortran::parser::CharBlock{};
}
for (auto &m : *modifiers) {
if (std::get_if<SpecificTy>(&m.u) == specific) {
return m.source;
}
}
llvm_unreachable("`specific` must be a member of `modifiers`");
}
namespace detail {
template <typename T> constexpr const T *make_nullptr() {
return static_cast<const T *>(nullptr);
}
/// Verify that all modifiers are allowed in the given OpenMP version.
template <typename UnionTy>
bool verifyVersions(const std::optional<std::list<UnionTy>> &modifiers,
llvm::omp::Clause id, parser::CharBlock clauseSource,
SemanticsContext &semaCtx) {
if (!modifiers) {
return true;
}
unsigned version{semaCtx.langOptions().OpenMPVersion};
bool result{true};
for (auto &m : *modifiers) {
const OmpModifierDescriptor &desc{OmpGetDescriptor(m)};
unsigned since{desc.since(id)};
if (since == ~0u) {
// This shouldn't really happen, but have it just in case.
semaCtx.Say(m.source,
"'%s' modifier is not supported on %s clause"_err_en_US,
desc.name.str(),
parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(id)));
} else if (version < since) {
semaCtx.Say(m.source,
"'%s' modifier is not supported in OpenMP v%d.%d, try -fopenmp-version=%d"_warn_en_US,
desc.name.str(), version / 10, version % 10, since);
result = false;
}
}
return result;
}
/// 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, "'%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,
llvm::omp::Clause id, 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,
"'%s' modifier 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,
llvm::omp::Clause id, 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,
llvm::omp::Clause id, 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,
"'%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,
llvm::omp::Clause id, 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' modifier 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, llvm::omp::Clause id,
parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
auto &modifiers{OmpGetModifiers(clause)};
bool results[]{//
detail::verifyVersions(modifiers, id, clauseSource, semaCtx),
detail::verifyRequired(modifiers, id, clauseSource, semaCtx),
detail::verifyUnique(modifiers, id, clauseSource, semaCtx),
detail::verifyUltimate(modifiers, id, clauseSource, semaCtx),
detail::verifyExclusive(modifiers, id, clauseSource, semaCtx)};
return llvm::all_of(results, [](bool x) { return x; });
}
} // namespace Fortran::semantics
#endif // FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_