forked from rust-lang/llvm-project
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[flang][OpenMP] Change clause modifier representation in parser (llvm…
…#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: rust-lang#2/3
- Loading branch information
Showing
5 changed files
with
545 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.