Skip to content

Commit

Permalink
[flang][OpenMP] Change clause modifier representation in parser (llvm…
Browse files Browse the repository at this point in the history
…#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
kparzysz authored Nov 20, 2024
1 parent 4b3b74d commit fb4ecad
Show file tree
Hide file tree
Showing 5 changed files with 545 additions and 0 deletions.
391 changes: 391 additions & 0 deletions flang/include/flang/Semantics/openmp-modifiers.h
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_
1 change: 1 addition & 0 deletions flang/lib/Semantics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit fb4ecad

Please sign in to comment.