This patch creates the `OmpRewriteMutator` pass that runs at the end of `RewriteParseTree()`. This pass is intended to make OpenMP-specific mutations to the PFT after name resolution. In the case of the `atomic_default_mem_order` clause of the REQUIRES directive, name resolution results in populating global symbols with information about the REQUIRES clauses that apply to that scope. The new rewrite pass is then able to use this information in order to explicitly set the memory order of ATOMIC constructs for which that is not already specified. Given that this rewrite happens before semantics checks, the check of the order in which ATOMIC constructs without explicit memory order and REQUIRES directives with `atomic_default_mem_order` appear is moved earlier into the rewrite pass. Otherwise, these problems would not be caught by semantics checks, since the PFT would be modified by that stage. This is patch 4/5 of a series splitting D149337 to simplify review. Depends on D157983. Differential Revision: https://reviews.llvm.org/D158096
178 lines
6.3 KiB
C++
178 lines
6.3 KiB
C++
//===-- lib/Semantics/rewrite-directives.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 "rewrite-directives.h"
|
|
#include "flang/Parser/parse-tree-visitor.h"
|
|
#include "flang/Parser/parse-tree.h"
|
|
#include "flang/Semantics/semantics.h"
|
|
#include "flang/Semantics/symbol.h"
|
|
#include "llvm/Frontend/OpenMP/OMP.h.inc"
|
|
#include <list>
|
|
|
|
namespace Fortran::semantics {
|
|
|
|
using namespace parser::literals;
|
|
|
|
class DirectiveRewriteMutator {
|
|
public:
|
|
explicit DirectiveRewriteMutator(SemanticsContext &context)
|
|
: context_{context} {}
|
|
|
|
// Default action for a parse tree node is to visit children.
|
|
template <typename T> bool Pre(T &) { return true; }
|
|
template <typename T> void Post(T &) {}
|
|
|
|
protected:
|
|
SemanticsContext &context_;
|
|
};
|
|
|
|
// Rewrite atomic constructs to add an explicit memory ordering to all that do
|
|
// not specify it, honoring in this way the `atomic_default_mem_order` clause of
|
|
// the REQUIRES directive.
|
|
class OmpRewriteMutator : public DirectiveRewriteMutator {
|
|
public:
|
|
explicit OmpRewriteMutator(SemanticsContext &context)
|
|
: DirectiveRewriteMutator(context) {}
|
|
|
|
template <typename T> bool Pre(T &) { return true; }
|
|
template <typename T> void Post(T &) {}
|
|
|
|
bool Pre(parser::OpenMPAtomicConstruct &);
|
|
bool Pre(parser::OpenMPRequiresConstruct &);
|
|
|
|
private:
|
|
bool atomicDirectiveDefaultOrderFound_{false};
|
|
};
|
|
|
|
bool OmpRewriteMutator::Pre(parser::OpenMPAtomicConstruct &x) {
|
|
// Find top-level parent of the operation.
|
|
Symbol *topLevelParent{common::visit(
|
|
[&](auto &atomic) {
|
|
Symbol *symbol{nullptr};
|
|
Scope *scope{
|
|
&context_.FindScope(std::get<parser::Verbatim>(atomic.t).source)};
|
|
do {
|
|
if (Symbol * parent{scope->symbol()}) {
|
|
symbol = parent;
|
|
}
|
|
scope = &scope->parent();
|
|
} while (!scope->IsGlobal());
|
|
|
|
assert(symbol &&
|
|
"Atomic construct must be within a scope associated with a symbol");
|
|
return symbol;
|
|
},
|
|
x.u)};
|
|
|
|
// Get the `atomic_default_mem_order` clause from the top-level parent.
|
|
std::optional<common::OmpAtomicDefaultMemOrderType> defaultMemOrder;
|
|
common::visit(
|
|
[&](auto &details) {
|
|
if constexpr (std::is_convertible_v<decltype(&details),
|
|
WithOmpDeclarative *>) {
|
|
if (details.has_ompAtomicDefaultMemOrder()) {
|
|
defaultMemOrder = *details.ompAtomicDefaultMemOrder();
|
|
}
|
|
}
|
|
},
|
|
topLevelParent->details());
|
|
|
|
if (!defaultMemOrder) {
|
|
return false;
|
|
}
|
|
|
|
auto findMemOrderClause =
|
|
[](const std::list<parser::OmpAtomicClause> &clauses) {
|
|
return std::find_if(
|
|
clauses.begin(), clauses.end(), [](const auto &clause) {
|
|
return std::get_if<parser::OmpMemoryOrderClause>(
|
|
&clause.u);
|
|
}) != clauses.end();
|
|
};
|
|
|
|
// Get the clause list to which the new memory order clause must be added,
|
|
// only if there are no other memory order clauses present for this atomic
|
|
// directive.
|
|
std::list<parser::OmpAtomicClause> *clauseList = common::visit(
|
|
common::visitors{[&](parser::OmpAtomic &atomicConstruct) {
|
|
// OmpAtomic only has a single list of clauses.
|
|
auto &clauses{std::get<parser::OmpAtomicClauseList>(
|
|
atomicConstruct.t)};
|
|
return !findMemOrderClause(clauses.v) ? &clauses.v
|
|
: nullptr;
|
|
},
|
|
[&](auto &atomicConstruct) {
|
|
// All other atomic constructs have two lists of clauses.
|
|
auto &clausesLhs{std::get<0>(atomicConstruct.t)};
|
|
auto &clausesRhs{std::get<2>(atomicConstruct.t)};
|
|
return !findMemOrderClause(clausesLhs.v) &&
|
|
!findMemOrderClause(clausesRhs.v)
|
|
? &clausesRhs.v
|
|
: nullptr;
|
|
}},
|
|
x.u);
|
|
|
|
// Add a memory order clause to the atomic directive.
|
|
if (clauseList) {
|
|
atomicDirectiveDefaultOrderFound_ = true;
|
|
switch (*defaultMemOrder) {
|
|
case common::OmpAtomicDefaultMemOrderType::AcqRel:
|
|
clauseList->emplace_back<parser::OmpMemoryOrderClause>(common::visit(
|
|
common::visitors{[](parser::OmpAtomicRead &) -> parser::OmpClause {
|
|
return parser::OmpClause::Acquire{};
|
|
},
|
|
[](parser::OmpAtomicCapture &) -> parser::OmpClause {
|
|
return parser::OmpClause::AcqRel{};
|
|
},
|
|
[](auto &) -> parser::OmpClause {
|
|
// parser::{OmpAtomic, OmpAtomicUpdate, OmpAtomicWrite}
|
|
return parser::OmpClause::Release{};
|
|
}},
|
|
x.u));
|
|
break;
|
|
case common::OmpAtomicDefaultMemOrderType::Relaxed:
|
|
clauseList->emplace_back<parser::OmpMemoryOrderClause>(
|
|
parser::OmpClause{parser::OmpClause::Relaxed{}});
|
|
break;
|
|
case common::OmpAtomicDefaultMemOrderType::SeqCst:
|
|
clauseList->emplace_back<parser::OmpMemoryOrderClause>(
|
|
parser::OmpClause{parser::OmpClause::SeqCst{}});
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool OmpRewriteMutator::Pre(parser::OpenMPRequiresConstruct &x) {
|
|
for (parser::OmpClause &clause : std::get<parser::OmpClauseList>(x.t).v) {
|
|
if (std::holds_alternative<parser::OmpClause::AtomicDefaultMemOrder>(
|
|
clause.u) &&
|
|
atomicDirectiveDefaultOrderFound_) {
|
|
context_.Say(clause.source,
|
|
"REQUIRES directive with '%s' clause found lexically after atomic "
|
|
"operation without a memory order clause"_err_en_US,
|
|
parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(
|
|
llvm::omp::OMPC_atomic_default_mem_order)
|
|
.str()));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RewriteOmpParts(SemanticsContext &context, parser::Program &program) {
|
|
if (!context.IsEnabled(common::LanguageFeature::OpenMP)) {
|
|
return true;
|
|
}
|
|
OmpRewriteMutator ompMutator{context};
|
|
parser::Walk(program, ompMutator);
|
|
return !context.AnyFatalError();
|
|
}
|
|
|
|
} // namespace Fortran::semantics
|