The need for that has already happened once with SourcedActionStmt, and will happen again in upcoming PRs. Issue: https://github.com/llvm/llvm-project/issues/185287
1724 lines
61 KiB
C++
1724 lines
61 KiB
C++
//===-- lib/Semantics/check-omp-atomic.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
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
//
|
||
// Semantic checks related to the ATOMIC construct.
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "check-omp-structure.h"
|
||
|
||
#include "flang/Common/indirection.h"
|
||
#include "flang/Common/template.h"
|
||
#include "flang/Evaluate/expression.h"
|
||
#include "flang/Evaluate/match.h"
|
||
#include "flang/Evaluate/rewrite.h"
|
||
#include "flang/Evaluate/tools.h"
|
||
#include "flang/Parser/char-block.h"
|
||
#include "flang/Parser/openmp-utils.h"
|
||
#include "flang/Parser/parse-tree.h"
|
||
#include "flang/Semantics/openmp-utils.h"
|
||
#include "flang/Semantics/symbol.h"
|
||
#include "flang/Semantics/tools.h"
|
||
#include "flang/Semantics/type.h"
|
||
|
||
#include "llvm/ADT/ArrayRef.h"
|
||
#include "llvm/ADT/STLExtras.h"
|
||
#include "llvm/Frontend/OpenMP/OMP.h"
|
||
#include "llvm/Support/ErrorHandling.h"
|
||
|
||
#include <cassert>
|
||
#include <list>
|
||
#include <optional>
|
||
#include <string_view>
|
||
#include <tuple>
|
||
#include <utility>
|
||
#include <variant>
|
||
#include <vector>
|
||
|
||
namespace Fortran::semantics {
|
||
|
||
using namespace Fortran::parser::omp;
|
||
using namespace Fortran::semantics::omp;
|
||
|
||
namespace operation = Fortran::evaluate::operation;
|
||
|
||
static MaybeExpr PostSemaRewrite(const SomeExpr &atom, const SomeExpr &expr);
|
||
|
||
template <typename T, typename U>
|
||
static bool operator!=(const evaluate::Expr<T> &e, const evaluate::Expr<U> &f) {
|
||
return !(e == f);
|
||
}
|
||
|
||
namespace {
|
||
template <typename...> struct IsIntegral {
|
||
static constexpr bool value{false};
|
||
};
|
||
|
||
template <common::TypeCategory C, int K>
|
||
struct IsIntegral<evaluate::Type<C, K>> {
|
||
static constexpr bool value{//
|
||
C == common::TypeCategory::Integer ||
|
||
C == common::TypeCategory::Unsigned};
|
||
};
|
||
|
||
template <typename T> constexpr bool is_integral_v{IsIntegral<T>::value};
|
||
|
||
template <typename...> struct IsFloatingPoint {
|
||
static constexpr bool value{false};
|
||
};
|
||
|
||
template <common::TypeCategory C, int K>
|
||
struct IsFloatingPoint<evaluate::Type<C, K>> {
|
||
static constexpr bool value{//
|
||
C == common::TypeCategory::Real || C == common::TypeCategory::Complex};
|
||
};
|
||
|
||
template <typename T>
|
||
constexpr bool is_floating_point_v{IsFloatingPoint<T>::value};
|
||
|
||
template <typename T>
|
||
constexpr bool is_numeric_v{is_integral_v<T> || is_floating_point_v<T>};
|
||
|
||
template <typename...> struct IsLogical {
|
||
static constexpr bool value{false};
|
||
};
|
||
|
||
template <common::TypeCategory C, int K>
|
||
struct IsLogical<evaluate::Type<C, K>> {
|
||
static constexpr bool value{C == common::TypeCategory::Logical};
|
||
};
|
||
|
||
template <typename T> constexpr bool is_logical_v{IsLogical<T>::value};
|
||
|
||
template <typename T, typename Op0, typename Op1>
|
||
using ReassocOpBase = evaluate::match::AnyOfPattern< //
|
||
evaluate::match::Add<T, Op0, Op1>, //
|
||
evaluate::match::Mul<T, Op0, Op1>, //
|
||
evaluate::match::LogicalOp<common::LogicalOperator::And, T, Op0, Op1>,
|
||
evaluate::match::LogicalOp<common::LogicalOperator::Or, T, Op0, Op1>,
|
||
evaluate::match::LogicalOp<common::LogicalOperator::Eqv, T, Op0, Op1>,
|
||
evaluate::match::LogicalOp<common::LogicalOperator::Neqv, T, Op0, Op1>>;
|
||
|
||
template <typename T, typename Op0, typename Op1>
|
||
struct ReassocOp : public ReassocOpBase<T, Op0, Op1> {
|
||
using Base = ReassocOpBase<T, Op0, Op1>;
|
||
using Base::Base;
|
||
};
|
||
|
||
template <typename T, typename Op0, typename Op1>
|
||
ReassocOp<T, Op0, Op1> reassocOp(const Op0 &op0, const Op1 &op1) {
|
||
return ReassocOp<T, Op0, Op1>(op0, op1);
|
||
}
|
||
} // namespace
|
||
|
||
struct ReassocRewriter : public evaluate::rewrite::Identity {
|
||
using Id = evaluate::rewrite::Identity;
|
||
struct NonIntegralTag {};
|
||
|
||
ReassocRewriter(const SomeExpr &atom, const SemanticsContext &context)
|
||
: atom_(atom), context_(context) {}
|
||
|
||
// Try to find cases where the input expression is of the form
|
||
// (1) (a . b) . c, or
|
||
// (2) a . (b . c),
|
||
// where . denotes an associative operation, and a, b, c are some
|
||
// subexpresions.
|
||
// If one of the operands in the nested operation is the atomic variable
|
||
// (with some possible type conversions applied to it), bring it to the
|
||
// top-level operation, and move the top-level operand into the nested
|
||
// operation.
|
||
// For example, assuming x is the atomic variable:
|
||
// (a + x) + b -> (a + b) + x, i.e. (conceptually) swap x and b.
|
||
template <typename T, typename U,
|
||
typename = std::enable_if_t<is_numeric_v<T> || is_logical_v<T>>>
|
||
evaluate::Expr<T> operator()(evaluate::Expr<T> &&x, const U &u) {
|
||
if constexpr (is_floating_point_v<T>) {
|
||
if (!context_.langOptions().AssociativeMath) {
|
||
return Id::operator()(std::move(x), u);
|
||
}
|
||
}
|
||
// As per the above comment, there are 3 subexpressions involved in this
|
||
// transformation. A match::Expr<T> will match evaluate::Expr<U> when T is
|
||
// same as U, plus it will store a pointer (ref) to the matched expression.
|
||
// When the match is successful, the sub[i].ref will point to a, b, x (in
|
||
// some order) from the example above.
|
||
evaluate::match::Expr<T> sub[3];
|
||
auto inner{reassocOp<T>(sub[0], sub[1])};
|
||
auto outer1{reassocOp<T>(inner, sub[2])}; // inner . something
|
||
auto outer2{reassocOp<T>(sub[2], inner)}; // something . inner
|
||
#if !defined(__clang__) && !defined(_MSC_VER) && \
|
||
(__GNUC__ < 8 || (__GNUC__ == 8 && __GNUC_MINOR__ < 5))
|
||
// If GCC version < 8.5, use this definition. For the other definition
|
||
// (which is equivalent), GCC 7.5 emits a somewhat cryptic error:
|
||
// use of ‘outer1’ before deduction of ‘auto’
|
||
// inside of the visitor function in common::visit.
|
||
// Since this works with clang, MSVC and at least GCC 8.5, I'm assuming
|
||
// that this is some kind of a GCC issue.
|
||
using MatchTypes = std::tuple<evaluate::Add<T>, evaluate::Multiply<T>,
|
||
evaluate::LogicalOperation<T::kind>>;
|
||
#else
|
||
using MatchTypes = typename decltype(outer1)::MatchTypes;
|
||
#endif
|
||
// There is no way to ensure that the outer operation is the same as
|
||
// the inner one. They are matched independently, so we need to compare
|
||
// the index in the member variant that represents the matched type.
|
||
if ((match(outer1, x) && outer1.ref.index() == inner.ref.index()) ||
|
||
(match(outer2, x) && outer2.ref.index() == inner.ref.index())) {
|
||
size_t atomIdx{[&]() { // sub[atomIdx] will be the atom.
|
||
size_t idx;
|
||
for (idx = 0; idx != 3; ++idx) {
|
||
if (IsAtom(*sub[idx].ref)) {
|
||
break;
|
||
}
|
||
}
|
||
return idx;
|
||
}()};
|
||
|
||
if (atomIdx > 2) {
|
||
return Id::operator()(std::move(x), u);
|
||
}
|
||
return common::visit(
|
||
[&](auto &&s) {
|
||
// Build the new expression from the matched components.
|
||
return Reconstruct<T, MatchTypes>(s, *sub[atomIdx].ref,
|
||
*sub[(atomIdx + 1) % 3].ref, *sub[(atomIdx + 2) % 3].ref);
|
||
},
|
||
evaluate::match::deparen(x).u);
|
||
}
|
||
return Id::operator()(std::move(x), u);
|
||
}
|
||
|
||
template <typename T, typename U,
|
||
typename = std::enable_if_t<!is_numeric_v<T> && !is_logical_v<T>>>
|
||
evaluate::Expr<T> operator()(
|
||
evaluate::Expr<T> &&x, const U &u, NonIntegralTag = {}) {
|
||
return Id::operator()(std::move(x), u);
|
||
}
|
||
|
||
private:
|
||
template <typename T, typename MatchTypes, typename S>
|
||
evaluate::Expr<T> Reconstruct(const S &op, evaluate::Expr<T> atom,
|
||
evaluate::Expr<T> op1, evaluate::Expr<T> op2) {
|
||
using TypeS = llvm::remove_cvref_t<decltype(op)>;
|
||
// This function has to be semantically correct for all possible types
|
||
// of S even though at runtime s will only be one of the matched types.
|
||
// Limit the construction to the operation types that we tried to match
|
||
// (otherwise TypeS(op1, op2) would fail for non-binary operations).
|
||
if constexpr (!common::HasMember<TypeS, MatchTypes>) {
|
||
return evaluate::Expr<T>(TypeS(op));
|
||
} else if constexpr (is_logical_v<T>) {
|
||
constexpr int K{T::kind};
|
||
if constexpr (std::is_same_v<TypeS, evaluate::LogicalOperation<K>>) {
|
||
// Logical operators take an extra argument in their constructor,
|
||
// so they need their own reconstruction code.
|
||
common::LogicalOperator opCode{op.logicalOperator};
|
||
return evaluate::Expr<T>(TypeS( //
|
||
opCode, std::move(atom),
|
||
evaluate::Expr<T>(TypeS( //
|
||
opCode, std::move(op1), std::move(op2)))));
|
||
}
|
||
} else {
|
||
// Generic reconstruction.
|
||
return evaluate::Expr<T>(TypeS( //
|
||
std::move(atom),
|
||
evaluate::Expr<T>(TypeS( //
|
||
std::move(op1), std::move(op2)))));
|
||
}
|
||
}
|
||
|
||
template <typename T> bool IsAtom(const evaluate::Expr<T> &x) const {
|
||
return IsSameOrConvertOf(evaluate::AsGenericExpr(AsRvalue(x)), atom_);
|
||
}
|
||
|
||
const SomeExpr &atom_;
|
||
const SemanticsContext &context_;
|
||
};
|
||
|
||
struct AnalyzedCondStmt {
|
||
SomeExpr cond{evaluate::NullPointer{}}; // Default ctor is deleted
|
||
parser::CharBlock source;
|
||
SourcedActionStmt ift, iff;
|
||
};
|
||
|
||
// Compute the `evaluate::Assignment` from parser::ActionStmt. The assumption
|
||
// is that the ActionStmt will be either an assignment or a pointer-assignment,
|
||
// otherwise return std::nullopt.
|
||
// Note: This function can return std::nullopt on [Pointer]AssignmentStmt where
|
||
// the "typedAssignment" is unset. This can happen if there are semantic errors
|
||
// in the purported assignment.
|
||
static std::optional<evaluate::Assignment> GetEvaluateAssignment(
|
||
const parser::ActionStmt *x) {
|
||
if (x == nullptr) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
using AssignmentStmt = common::Indirection<parser::AssignmentStmt>;
|
||
using PointerAssignmentStmt =
|
||
common::Indirection<parser::PointerAssignmentStmt>;
|
||
using TypedAssignment = parser::TypedAssignment;
|
||
|
||
return common::visit(
|
||
[](auto &&s) -> std::optional<evaluate::Assignment> {
|
||
using BareS = llvm::remove_cvref_t<decltype(s)>;
|
||
if constexpr (std::is_same_v<BareS, AssignmentStmt> ||
|
||
std::is_same_v<BareS, PointerAssignmentStmt>) {
|
||
const TypedAssignment &typed{s.value().typedAssignment};
|
||
// ForwardOwningPointer typedAssignment
|
||
// `- GenericAssignmentWrapper ^.get()
|
||
// `- std::optional<Assignment> ^->v
|
||
return typed.get()->v;
|
||
} else {
|
||
return std::nullopt;
|
||
}
|
||
},
|
||
x->u);
|
||
}
|
||
|
||
static std::optional<AnalyzedCondStmt> AnalyzeConditionalStmt(
|
||
const parser::ExecutionPartConstruct *x) {
|
||
if (x == nullptr) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
// Extract the evaluate::Expr from ScalarLogicalExpr.
|
||
auto getFromLogical{[](const parser::ScalarLogicalExpr &logical) {
|
||
// ScalarLogicalExpr is Scalar<Logical<common::Indirection<Expr>>>
|
||
auto &expr{parser::UnwrapRef<parser::Expr>(logical)};
|
||
return GetEvaluateExpr(expr);
|
||
}};
|
||
|
||
// Recognize either
|
||
// ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> IfStmt, or
|
||
// ExecutionPartConstruct -> ExecutableConstruct -> IfConstruct.
|
||
|
||
if (auto &&action{GetActionStmt(x)}) {
|
||
if (auto *ifs{std::get_if<common::Indirection<parser::IfStmt>>(
|
||
&action.stmt()->u)}) {
|
||
const parser::IfStmt &s{ifs->value()};
|
||
auto &&maybeCond{
|
||
getFromLogical(std::get<parser::ScalarLogicalExpr>(s.t))};
|
||
auto &thenStmt{
|
||
std::get<parser::UnlabeledStatement<parser::ActionStmt>>(s.t)};
|
||
if (maybeCond) {
|
||
return AnalyzedCondStmt{std::move(*maybeCond), action.source,
|
||
SourcedActionStmt{&thenStmt.statement, thenStmt.source},
|
||
SourcedActionStmt{}};
|
||
}
|
||
}
|
||
return std::nullopt;
|
||
}
|
||
|
||
if (auto *exec{std::get_if<parser::ExecutableConstruct>(&x->u)}) {
|
||
if (auto *ifc{
|
||
std::get_if<common::Indirection<parser::IfConstruct>>(&exec->u)}) {
|
||
using ElseBlock = parser::IfConstruct::ElseBlock;
|
||
using ElseIfBlock = parser::IfConstruct::ElseIfBlock;
|
||
const parser::IfConstruct &s{ifc->value()};
|
||
|
||
if (!std::get<std::list<ElseIfBlock>>(s.t).empty()) {
|
||
// Not expecting any else-if statements.
|
||
return std::nullopt;
|
||
}
|
||
auto &stmt{std::get<parser::Statement<parser::IfThenStmt>>(s.t)};
|
||
auto &&maybeCond{getFromLogical(
|
||
std::get<parser::ScalarLogicalExpr>(stmt.statement.t))};
|
||
if (!maybeCond) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
if (auto &maybeElse{std::get<std::optional<ElseBlock>>(s.t)}) {
|
||
AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
|
||
GetActionStmt(std::get<parser::Block>(s.t)),
|
||
GetActionStmt(std::get<parser::Block>(maybeElse->t))};
|
||
if (result.ift.stmt() && result.iff.stmt()) {
|
||
return result;
|
||
}
|
||
} else {
|
||
AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
|
||
GetActionStmt(std::get<parser::Block>(s.t)), SourcedActionStmt{}};
|
||
if (result.ift.stmt()) {
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
return std::nullopt;
|
||
}
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
static std::pair<parser::CharBlock, parser::CharBlock> SplitAssignmentSource(
|
||
parser::CharBlock source) {
|
||
// Find => in the range, if not found, find = that is not a part of
|
||
// <=, >=, ==, or /=.
|
||
auto trim{[](std::string_view v) {
|
||
const char *begin{v.data()};
|
||
const char *end{begin + v.size()};
|
||
while (*begin == ' ' && begin != end) {
|
||
++begin;
|
||
}
|
||
while (begin != end && end[-1] == ' ') {
|
||
--end;
|
||
}
|
||
assert(begin != end && "Source should not be empty");
|
||
return parser::CharBlock(begin, end - begin);
|
||
}};
|
||
|
||
std::string_view sv(source.begin(), source.size());
|
||
|
||
if (auto where{sv.find("=>")}; where != sv.npos) {
|
||
std::string_view lhs(sv.data(), where);
|
||
std::string_view rhs(sv.data() + where + 2, sv.size() - where - 2);
|
||
return std::make_pair(trim(lhs), trim(rhs));
|
||
}
|
||
|
||
// Go backwards, since all the exclusions above end with a '='.
|
||
for (size_t next{source.size()}; next > 1; --next) {
|
||
if (sv[next - 1] == '=' && !llvm::is_contained("<>=/", sv[next - 2])) {
|
||
std::string_view lhs(sv.data(), next - 1);
|
||
std::string_view rhs(sv.data() + next, sv.size() - next);
|
||
return std::make_pair(trim(lhs), trim(rhs));
|
||
}
|
||
}
|
||
llvm_unreachable("Could not find assignment operator");
|
||
}
|
||
|
||
static std::vector<SomeExpr> GetNonAtomExpressions(
|
||
const SomeExpr &atom, const std::vector<SomeExpr> &exprs) {
|
||
std::vector<SomeExpr> nonAtom;
|
||
for (const SomeExpr &e : exprs) {
|
||
if (!IsSameOrConvertOf(e, atom)) {
|
||
nonAtom.push_back(e);
|
||
}
|
||
}
|
||
return nonAtom;
|
||
}
|
||
|
||
static std::vector<SomeExpr> GetNonAtomArguments(
|
||
const SomeExpr &atom, const SomeExpr &expr) {
|
||
if (auto &&maybe{GetConvertInput(expr)}) {
|
||
return GetNonAtomExpressions(
|
||
atom, GetTopLevelOperationIgnoreResizing(*maybe).second);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
static bool IsCheckForAssociated(const SomeExpr &cond) {
|
||
return GetTopLevelOperationIgnoreResizing(cond).first ==
|
||
operation::Operator::Associated;
|
||
}
|
||
|
||
static bool IsMaybeAtomicWrite(const evaluate::Assignment &assign) {
|
||
// This ignores function calls, so it will accept "f(x) = f(x) + 1"
|
||
// for example.
|
||
return HasStorageOverlap(assign.lhs, assign.rhs) == nullptr;
|
||
}
|
||
|
||
static void SetExpr(parser::TypedExpr &expr, MaybeExpr value) {
|
||
if (value) {
|
||
expr.Reset(new evaluate::GenericExprWrapper(std::move(value)),
|
||
evaluate::GenericExprWrapper::Deleter);
|
||
}
|
||
}
|
||
|
||
static void SetAssignment(parser::TypedAssignment &assign,
|
||
std::optional<evaluate::Assignment> value) {
|
||
if (value) {
|
||
assign.Reset(new evaluate::GenericAssignmentWrapper(std::move(value)),
|
||
evaluate::GenericAssignmentWrapper::Deleter);
|
||
}
|
||
}
|
||
|
||
namespace {
|
||
struct AtomicAnalysis {
|
||
AtomicAnalysis(const SomeExpr &atom, const MaybeExpr &cond = std::nullopt)
|
||
: atom_(atom), cond_(cond) {}
|
||
|
||
AtomicAnalysis &addOp0(int what,
|
||
const std::optional<evaluate::Assignment> &maybeAssign = std::nullopt) {
|
||
return addOp(op0_, what, maybeAssign);
|
||
}
|
||
AtomicAnalysis &addOp1(int what,
|
||
const std::optional<evaluate::Assignment> &maybeAssign = std::nullopt) {
|
||
return addOp(op1_, what, maybeAssign);
|
||
}
|
||
|
||
operator parser::OpenMPAtomicConstruct::Analysis() const {
|
||
// Defined in flang/include/flang/Parser/parse-tree.h
|
||
//
|
||
// struct Analysis {
|
||
// struct Kind {
|
||
// static constexpr int None = 0;
|
||
// static constexpr int Read = 1;
|
||
// static constexpr int Write = 2;
|
||
// static constexpr int Update = Read | Write;
|
||
// static constexpr int Action = 3; // Bits containing None, Read,
|
||
// // Write, Update
|
||
// static constexpr int IfTrue = 4;
|
||
// static constexpr int IfFalse = 8;
|
||
// static constexpr int Condition = 12; // Bits containing IfTrue,
|
||
// // IfFalse
|
||
// };
|
||
// struct Op {
|
||
// int what;
|
||
// TypedAssignment assign;
|
||
// };
|
||
// TypedExpr atom, cond;
|
||
// Op op0, op1;
|
||
// };
|
||
|
||
parser::OpenMPAtomicConstruct::Analysis an;
|
||
SetExpr(an.atom, atom_);
|
||
SetExpr(an.cond, cond_);
|
||
an.op0 = std::move(op0_);
|
||
an.op1 = std::move(op1_);
|
||
return an;
|
||
}
|
||
|
||
private:
|
||
struct Op {
|
||
operator parser::OpenMPAtomicConstruct::Analysis::Op() const {
|
||
parser::OpenMPAtomicConstruct::Analysis::Op op;
|
||
op.what = what;
|
||
SetAssignment(op.assign, assign);
|
||
return op;
|
||
}
|
||
|
||
int what;
|
||
std::optional<evaluate::Assignment> assign;
|
||
};
|
||
|
||
AtomicAnalysis &addOp(Op &op, int what,
|
||
const std::optional<evaluate::Assignment> &maybeAssign) {
|
||
op.what = what;
|
||
if (maybeAssign) {
|
||
if (MaybeExpr rewritten{PostSemaRewrite(atom_, maybeAssign->rhs)}) {
|
||
op.assign = evaluate::Assignment(
|
||
AsRvalue(maybeAssign->lhs), std::move(*rewritten));
|
||
op.assign->u = std::move(maybeAssign->u);
|
||
} else {
|
||
op.assign = *maybeAssign;
|
||
}
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
const SomeExpr &atom_;
|
||
const MaybeExpr &cond_;
|
||
Op op0_, op1_;
|
||
};
|
||
} // namespace
|
||
|
||
/// Check if `expr` satisfies the following conditions for x and v:
|
||
///
|
||
/// [6.0:189:10-12]
|
||
/// - x and v (as applicable) are either scalar variables or
|
||
/// function references with scalar data pointer result of non-character
|
||
/// intrinsic type or variables that are non-polymorphic scalar pointers
|
||
/// and any length type parameter must be constant.
|
||
void OmpStructureChecker::CheckAtomicType(SymbolRef sym,
|
||
parser::CharBlock source, std::string_view name, bool checkTypeOnPointer) {
|
||
const DeclTypeSpec *typeSpec{sym->GetType()};
|
||
if (!typeSpec) {
|
||
return;
|
||
}
|
||
|
||
if (!IsPointer(sym)) {
|
||
using Category = DeclTypeSpec::Category;
|
||
Category cat{typeSpec->category()};
|
||
if (cat == Category::Character) {
|
||
context_.Say(source,
|
||
"Atomic variable %s cannot have CHARACTER type"_err_en_US, name);
|
||
} else if (cat != Category::Numeric && cat != Category::Logical) {
|
||
context_.Say(source,
|
||
"Atomic variable %s should have an intrinsic type"_err_en_US, name);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Variable is a pointer.
|
||
if (typeSpec->IsPolymorphic()) {
|
||
context_.Say(source,
|
||
"Atomic variable %s cannot be a pointer to a polymorphic type"_err_en_US,
|
||
name);
|
||
return;
|
||
}
|
||
|
||
// Apply pointer-to-non-intrinsic rule only for intrinsic-assignment paths.
|
||
if (checkTypeOnPointer) {
|
||
using Category = DeclTypeSpec::Category;
|
||
Category cat{typeSpec->category()};
|
||
if (cat != Category::Numeric && cat != Category::Logical) {
|
||
std::string details = " has the POINTER attribute";
|
||
if (const auto *derived{typeSpec->AsDerived()}) {
|
||
details += " and derived type '"s + derived->name().ToString() + "'";
|
||
}
|
||
context_.Say(source,
|
||
"ATOMIC operation requires an intrinsic scalar variable; '%s'%s"_err_en_US,
|
||
sym->name(), details);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Go over all length parameters, if any, and check if they are
|
||
// explicit.
|
||
if (const DerivedTypeSpec *derived{typeSpec->AsDerived()}) {
|
||
if (llvm::any_of(derived->parameters(), [](auto &&entry) {
|
||
// "entry" is a map entry
|
||
return entry.second.isLen() && !entry.second.isExplicit();
|
||
})) {
|
||
context_.Say(source,
|
||
"Atomic variable %s is a pointer to a type with non-constant length parameter"_err_en_US,
|
||
name);
|
||
}
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicVariable(
|
||
const SomeExpr &atom, parser::CharBlock source, bool checkTypeOnPointer) {
|
||
if (atom.Rank() != 0) {
|
||
context_.Say(source, "Atomic variable %s should be a scalar"_err_en_US,
|
||
atom.AsFortran());
|
||
}
|
||
|
||
std::vector<SomeExpr> dsgs{GetTopLevelDesignators(atom)};
|
||
|
||
// Procedure references are valid if they return a pointer to a scalar.
|
||
// Just return if we don't have exactly one designator - other checks will
|
||
// diagnose any actual errors.
|
||
if (dsgs.size() != 1) {
|
||
return;
|
||
}
|
||
|
||
SymbolVector syms{evaluate::GetSymbolVector(dsgs.front())};
|
||
if (syms.empty()) {
|
||
return;
|
||
}
|
||
|
||
CheckAtomicType(syms.back(), source, atom.AsFortran(), checkTypeOnPointer);
|
||
|
||
if (!IsArrayElement(atom) && !ExtractComplexPart(atom)) {
|
||
if (IsAllocatable(syms.back())) {
|
||
context_.Say(source, "Atomic variable %s cannot be ALLOCATABLE"_err_en_US,
|
||
atom.AsFortran());
|
||
}
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckStorageOverlap(const SomeExpr &base,
|
||
llvm::ArrayRef<evaluate::Expr<evaluate::SomeType>> exprs,
|
||
parser::CharBlock source) {
|
||
if (auto *expr{HasStorageOverlap(base, exprs)}) {
|
||
context_.Say(source,
|
||
"Within atomic operation %s and %s access the same storage"_warn_en_US,
|
||
base.AsFortran(), expr->AsFortran());
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::ErrorShouldBeVariable(
|
||
const MaybeExpr &expr, parser::CharBlock source) {
|
||
if (expr) {
|
||
context_.Say(source, "Atomic expression %s should be a variable"_err_en_US,
|
||
expr->AsFortran());
|
||
} else {
|
||
context_.Say(source, "Atomic expression should be a variable"_err_en_US);
|
||
}
|
||
}
|
||
|
||
std::pair<const parser::ExecutionPartConstruct *,
|
||
const parser::ExecutionPartConstruct *>
|
||
OmpStructureChecker::CheckUpdateCapture(
|
||
const parser::ExecutionPartConstruct *ec1,
|
||
const parser::ExecutionPartConstruct *ec2, parser::CharBlock source) {
|
||
// Decide which statement is the atomic update and which is the capture.
|
||
//
|
||
// The two allowed cases are:
|
||
// x = ... atomic-var = ...
|
||
// ... = x capture-var = atomic-var (with optional converts)
|
||
// or
|
||
// ... = x capture-var = atomic-var (with optional converts)
|
||
// x = ... atomic-var = ...
|
||
//
|
||
// The case of 'a = b; b = a' is ambiguous, so pick the first one as capture
|
||
// (which makes more sense, as it captures the original value of the atomic
|
||
// variable).
|
||
//
|
||
// If the two statements don't fit these criteria, return a pair of default-
|
||
// constructed values.
|
||
using ReturnTy = std::pair<const parser::ExecutionPartConstruct *,
|
||
const parser::ExecutionPartConstruct *>;
|
||
|
||
SourcedActionStmt act1{GetActionStmt(ec1)};
|
||
SourcedActionStmt act2{GetActionStmt(ec2)};
|
||
auto maybeAssign1{GetEvaluateAssignment(act1.stmt())};
|
||
auto maybeAssign2{GetEvaluateAssignment(act2.stmt())};
|
||
if (!maybeAssign1 || !maybeAssign2) {
|
||
if (!IsAssignment(act1.stmt()) || !IsAssignment(act2.stmt())) {
|
||
context_.Say(source,
|
||
"ATOMIC UPDATE operation with CAPTURE should contain two assignments"_err_en_US);
|
||
}
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
|
||
auto as1{*maybeAssign1}, as2{*maybeAssign2};
|
||
|
||
auto isUpdateCapture{
|
||
[](const evaluate::Assignment &u, const evaluate::Assignment &c) {
|
||
return IsSameOrConvertOf(c.rhs, u.lhs);
|
||
}};
|
||
|
||
// Do some checks that narrow down the possible choices for the update
|
||
// and the capture statements. This will help to emit better diagnostics.
|
||
// 1. An assignment could be an update (cbu) if the left-hand side is a
|
||
// subexpression of the right-hand side.
|
||
// 2. An assignment could be a capture (cbc) if the right-hand side is
|
||
// a variable (or a function ref), with potential type conversions.
|
||
bool cbu1{IsVarSubexpressionOf(as1.lhs, as1.rhs)}; // Can as1 be an update?
|
||
bool cbu2{IsVarSubexpressionOf(as2.lhs, as2.rhs)}; // Can as2 be an update?
|
||
bool cbc1{IsVarOrFunctionRef(GetConvertInput(as1.rhs))}; // Can 1 be capture?
|
||
bool cbc2{IsVarOrFunctionRef(GetConvertInput(as2.rhs))}; // Can 2 be capture?
|
||
|
||
// We want to diagnose cases where both assignments cannot be an update,
|
||
// or both cannot be a capture, as well as cases where either assignment
|
||
// cannot be any of these two.
|
||
//
|
||
// If we organize these boolean values into a matrix
|
||
// |cbu1 cbu2|
|
||
// |cbc1 cbc2|
|
||
// then we want to diagnose cases where the matrix has a zero (i.e. "false")
|
||
// row or column, including the case where everything is zero. All these
|
||
// cases correspond to the determinant of the matrix being 0, which suggests
|
||
// that checking the det may be a convenient diagnostic check. There is only
|
||
// one additional case where the det is 0, which is when the matrix is all 1
|
||
// ("true"). The "all true" case represents the situation where both
|
||
// assignments could be an update as well as a capture. On the other hand,
|
||
// whenever det != 0, the roles of the update and the capture can be
|
||
// unambiguously assigned to as1 and as2 [1].
|
||
//
|
||
// [1] This can be easily verified by hand: there are 10 2x2 matrices with
|
||
// det = 0, leaving 6 cases where det != 0:
|
||
// 0 1 0 1 1 0 1 0 1 1 1 1
|
||
// 1 0 1 1 0 1 1 1 0 1 1 0
|
||
// In each case the classification is unambiguous.
|
||
|
||
// |cbu1 cbu2|
|
||
// det |cbc1 cbc2| = cbu1*cbc2 - cbu2*cbc1
|
||
int det{int(cbu1) * int(cbc2) - int(cbu2) * int(cbc1)};
|
||
|
||
auto errorCaptureShouldRead{[&](const parser::CharBlock &source,
|
||
const std::string &expr) {
|
||
context_.Say(source,
|
||
"In ATOMIC UPDATE operation with CAPTURE the right-hand side of the capture assignment should read %s"_err_en_US,
|
||
expr);
|
||
}};
|
||
|
||
auto errorNeitherWorks{[&]() {
|
||
context_.Say(source,
|
||
"In ATOMIC UPDATE operation with CAPTURE neither statement could be the update or the capture"_err_en_US);
|
||
}};
|
||
|
||
auto makeSelectionFromDet{[&](int det) -> ReturnTy {
|
||
// If det != 0, then the checks unambiguously suggest a specific
|
||
// categorization.
|
||
// If det == 0, then this function should be called only if the
|
||
// checks haven't ruled out any possibility, i.e. when both assignments
|
||
// could still be either updates or captures.
|
||
if (det > 0) {
|
||
// as1 is update, as2 is capture
|
||
if (isUpdateCapture(as1, as2)) {
|
||
return std::make_pair(/*Update=*/ec1, /*Capture=*/ec2);
|
||
} else {
|
||
errorCaptureShouldRead(act2.source, as1.lhs.AsFortran());
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
} else if (det < 0) {
|
||
// as2 is update, as1 is capture
|
||
if (isUpdateCapture(as2, as1)) {
|
||
return std::make_pair(/*Update=*/ec2, /*Capture=*/ec1);
|
||
} else {
|
||
errorCaptureShouldRead(act1.source, as2.lhs.AsFortran());
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
} else {
|
||
bool updateFirst{isUpdateCapture(as1, as2)};
|
||
bool captureFirst{isUpdateCapture(as2, as1)};
|
||
if (updateFirst && captureFirst) {
|
||
// If both assignment could be the update and both could be the
|
||
// capture, emit a warning about the ambiguity.
|
||
context_.Say(act1.source,
|
||
"In ATOMIC UPDATE operation with CAPTURE either statement could be the update and the capture, assuming the first one is the capture statement"_warn_en_US);
|
||
return std::make_pair(/*Update=*/ec2, /*Capture=*/ec1);
|
||
}
|
||
if (updateFirst != captureFirst) {
|
||
const parser::ExecutionPartConstruct *upd{updateFirst ? ec1 : ec2};
|
||
const parser::ExecutionPartConstruct *cap{captureFirst ? ec1 : ec2};
|
||
return std::make_pair(upd, cap);
|
||
}
|
||
assert(!updateFirst && !captureFirst);
|
||
errorNeitherWorks();
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
}};
|
||
|
||
if (det != 0 || (cbu1 && cbu2 && cbc1 && cbc2)) {
|
||
return makeSelectionFromDet(det);
|
||
}
|
||
assert(det == 0 && "Prior checks should have covered det != 0");
|
||
|
||
// If neither of the statements is an RMW update, it could still be a
|
||
// "write" update. Pretty much any assignment can be a write update, so
|
||
// recompute det with cbu1 = cbu2 = true.
|
||
if (int writeDet{int(cbc2) - int(cbc1)}; writeDet || (cbc1 && cbc2)) {
|
||
return makeSelectionFromDet(writeDet);
|
||
}
|
||
|
||
// It's only errors from here on.
|
||
|
||
if (!cbu1 && !cbu2 && !cbc1 && !cbc2) {
|
||
errorNeitherWorks();
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
|
||
// The remaining cases are that
|
||
// - no candidate for update, or for capture,
|
||
// - one of the assignments cannot be anything.
|
||
|
||
if (!cbu1 && !cbu2) {
|
||
context_.Say(source,
|
||
"In ATOMIC UPDATE operation with CAPTURE neither statement could be the update"_err_en_US);
|
||
return std::make_pair(nullptr, nullptr);
|
||
} else if (!cbc1 && !cbc2) {
|
||
context_.Say(source,
|
||
"In ATOMIC UPDATE operation with CAPTURE neither statement could be the capture"_err_en_US);
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
|
||
if ((!cbu1 && !cbc1) || (!cbu2 && !cbc2)) {
|
||
auto &src = (!cbu1 && !cbc1) ? act1.source : act2.source;
|
||
context_.Say(src,
|
||
"In ATOMIC UPDATE operation with CAPTURE the statement could be neither the update nor the capture"_err_en_US);
|
||
return std::make_pair(nullptr, nullptr);
|
||
}
|
||
|
||
// All cases should have been covered.
|
||
llvm_unreachable("Unchecked condition");
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicCaptureAssignment(
|
||
const evaluate::Assignment &capture, const SomeExpr &atom,
|
||
parser::CharBlock source) {
|
||
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
|
||
(void)lsrc;
|
||
const SomeExpr &cap{capture.lhs};
|
||
|
||
if (!IsVarOrFunctionRef(atom)) {
|
||
ErrorShouldBeVariable(atom, rsrc);
|
||
} else {
|
||
CheckAtomicVariable(
|
||
atom, rsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(capture));
|
||
// This part should have been checked prior to calling this function.
|
||
assert(*GetConvertInput(capture.rhs) == atom &&
|
||
"This cannot be a capture assignment");
|
||
CheckStorageOverlap(atom, {cap}, source);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicReadAssignment(
|
||
const evaluate::Assignment &read, parser::CharBlock source) {
|
||
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
|
||
(void)lsrc;
|
||
|
||
if (auto maybe{GetConvertInput(read.rhs)}) {
|
||
const SomeExpr &atom{*maybe};
|
||
|
||
if (!IsVarOrFunctionRef(atom)) {
|
||
ErrorShouldBeVariable(atom, rsrc);
|
||
} else {
|
||
CheckAtomicVariable(
|
||
atom, rsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(read));
|
||
CheckStorageOverlap(atom, {read.lhs}, source);
|
||
}
|
||
} else {
|
||
ErrorShouldBeVariable(read.rhs, rsrc);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicWriteAssignment(
|
||
const evaluate::Assignment &write, parser::CharBlock source) {
|
||
// [6.0:190:13-15]
|
||
// A write structured block is write-statement, a write statement that has
|
||
// one of the following forms:
|
||
// x = expr
|
||
// x => expr
|
||
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
|
||
const SomeExpr &atom{write.lhs};
|
||
|
||
if (!IsVarOrFunctionRef(atom)) {
|
||
ErrorShouldBeVariable(atom, rsrc);
|
||
} else {
|
||
CheckAtomicVariable(
|
||
atom, lsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(write));
|
||
CheckStorageOverlap(atom, {write.rhs}, source);
|
||
}
|
||
}
|
||
|
||
std::optional<evaluate::Assignment>
|
||
OmpStructureChecker::CheckAtomicUpdateAssignment(
|
||
const evaluate::Assignment &update, parser::CharBlock source) {
|
||
// [6.0:191:1-7]
|
||
// An update structured block is update-statement, an update statement
|
||
// that has one of the following forms:
|
||
// x = x operator expr
|
||
// x = expr operator x
|
||
// x = intrinsic-procedure-name (x)
|
||
// x = intrinsic-procedure-name (x, expr-list)
|
||
// x = intrinsic-procedure-name (expr-list, x)
|
||
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
|
||
const SomeExpr &atom{update.lhs};
|
||
|
||
if (!IsVarOrFunctionRef(atom)) {
|
||
ErrorShouldBeVariable(atom, rsrc);
|
||
// Skip other checks.
|
||
return std::nullopt;
|
||
}
|
||
|
||
CheckAtomicVariable(
|
||
atom, lsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(update));
|
||
|
||
auto [hasErrors, tryReassoc]{CheckAtomicUpdateAssignmentRhs(
|
||
atom, update.rhs, source, /*suppressDiagnostics=*/true)};
|
||
|
||
if (!hasErrors) {
|
||
CheckStorageOverlap(atom, GetNonAtomArguments(atom, update.rhs), source);
|
||
return std::nullopt;
|
||
} else if (tryReassoc) {
|
||
ReassocRewriter ra(atom, context_);
|
||
SomeExpr raRhs{evaluate::rewrite::Mutator(ra)(update.rhs)};
|
||
|
||
std::tie(hasErrors, tryReassoc) = CheckAtomicUpdateAssignmentRhs(
|
||
atom, raRhs, source, /*suppressDiagnostics=*/true);
|
||
if (!hasErrors) {
|
||
CheckStorageOverlap(atom, GetNonAtomArguments(atom, raRhs), source);
|
||
|
||
evaluate::Assignment raAssign(update);
|
||
raAssign.rhs = raRhs;
|
||
return raAssign;
|
||
}
|
||
}
|
||
|
||
// This is guaranteed to report errors.
|
||
CheckAtomicUpdateAssignmentRhs(
|
||
atom, update.rhs, source, /*suppressDiagnostics=*/false);
|
||
return std::nullopt;
|
||
}
|
||
|
||
std::pair<bool, bool> OmpStructureChecker::CheckAtomicUpdateAssignmentRhs(
|
||
const SomeExpr &atom, const SomeExpr &rhs, parser::CharBlock source,
|
||
bool suppressDiagnostics) {
|
||
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
|
||
(void)lsrc;
|
||
|
||
std::pair<operation::Operator, std::vector<SomeExpr>> top{
|
||
operation::Operator::Unknown, {}};
|
||
if (auto &&maybeInput{GetConvertInput(rhs)}) {
|
||
top = GetTopLevelOperationIgnoreResizing(*maybeInput);
|
||
}
|
||
switch (top.first) {
|
||
case operation::Operator::Add:
|
||
case operation::Operator::Sub:
|
||
case operation::Operator::Mul:
|
||
case operation::Operator::Div:
|
||
case operation::Operator::And:
|
||
case operation::Operator::Or:
|
||
case operation::Operator::Eqv:
|
||
case operation::Operator::Neqv:
|
||
case operation::Operator::Min:
|
||
case operation::Operator::Max:
|
||
case operation::Operator::Identity:
|
||
break;
|
||
case operation::Operator::Call:
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(source,
|
||
"A call to this function is not a valid ATOMIC UPDATE operation"_err_en_US);
|
||
}
|
||
return std::make_pair(true, false);
|
||
case operation::Operator::Convert:
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(source,
|
||
"An implicit or explicit type conversion is not a valid ATOMIC UPDATE operation"_err_en_US);
|
||
}
|
||
return std::make_pair(true, false);
|
||
case operation::Operator::Intrinsic:
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(source,
|
||
"This intrinsic function is not a valid ATOMIC UPDATE operation"_err_en_US);
|
||
}
|
||
return std::make_pair(true, false);
|
||
case operation::Operator::Constant:
|
||
case operation::Operator::Unknown:
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(
|
||
source, "This is not a valid ATOMIC UPDATE operation"_err_en_US);
|
||
}
|
||
return std::make_pair(true, false);
|
||
default:
|
||
assert(
|
||
top.first != operation::Operator::Identity && "Handle this separately");
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(source,
|
||
"The %s operator is not a valid ATOMIC UPDATE operation"_err_en_US,
|
||
operation::ToString(top.first));
|
||
}
|
||
return std::make_pair(true, false);
|
||
}
|
||
// Check how many times `atom` occurs as an argument, if it's a subexpression
|
||
// of an argument, and collect the non-atom arguments.
|
||
std::vector<SomeExpr> nonAtom;
|
||
MaybeExpr subExpr;
|
||
auto atomCount{[&]() {
|
||
int count{0};
|
||
for (const SomeExpr &arg : top.second) {
|
||
if (IsSameOrConvertOf(arg, atom)) {
|
||
++count;
|
||
} else {
|
||
if (!subExpr && evaluate::IsVarSubexpressionOf(atom, arg)) {
|
||
subExpr = arg;
|
||
}
|
||
nonAtom.push_back(arg);
|
||
}
|
||
}
|
||
return count;
|
||
}()};
|
||
|
||
bool hasError{false}, tryReassoc{false};
|
||
if (subExpr) {
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(rsrc,
|
||
"The atomic variable %s cannot be a proper subexpression of an argument (here: %s) in the update operation"_err_en_US,
|
||
atom.AsFortran(), subExpr->AsFortran());
|
||
}
|
||
hasError = true;
|
||
}
|
||
if (top.first == operation::Operator::Identity) {
|
||
// This is "x = y".
|
||
assert((atomCount == 0 || atomCount == 1) && "Unexpected count");
|
||
if (atomCount == 0) {
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(rsrc,
|
||
"The atomic variable %s should appear as an argument in the update operation"_err_en_US,
|
||
atom.AsFortran());
|
||
}
|
||
hasError = true;
|
||
}
|
||
} else {
|
||
if (atomCount == 0) {
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(rsrc,
|
||
"The atomic variable %s should appear as an argument of the top-level %s operator"_err_en_US,
|
||
atom.AsFortran(), operation::ToString(top.first));
|
||
}
|
||
// If `atom` is a proper subexpression, and it not present as an
|
||
// argument on its own, reassociation may be able to help.
|
||
tryReassoc = subExpr.has_value();
|
||
hasError = true;
|
||
} else if (atomCount > 1) {
|
||
if (!suppressDiagnostics) {
|
||
context_.Say(rsrc,
|
||
"The atomic variable %s should be exactly one of the arguments of the top-level %s operator"_err_en_US,
|
||
atom.AsFortran(), operation::ToString(top.first));
|
||
}
|
||
hasError = true;
|
||
}
|
||
}
|
||
|
||
return std::make_pair(hasError, tryReassoc);
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicConditionalUpdateAssignment(
|
||
const SomeExpr &cond, parser::CharBlock condSource,
|
||
const evaluate::Assignment &assign, parser::CharBlock assignSource) {
|
||
auto [alsrc, arsrc]{SplitAssignmentSource(assignSource)};
|
||
const SomeExpr &atom{assign.lhs};
|
||
|
||
if (!IsVarOrFunctionRef(atom)) {
|
||
ErrorShouldBeVariable(atom, arsrc);
|
||
// Skip other checks.
|
||
return;
|
||
}
|
||
|
||
CheckAtomicVariable(
|
||
atom, alsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(assign));
|
||
|
||
auto top{GetTopLevelOperationIgnoreResizing(cond)};
|
||
// Missing arguments to operations would have been diagnosed by now.
|
||
|
||
switch (top.first) {
|
||
case operation::Operator::Associated:
|
||
if (atom != top.second.front()) {
|
||
context_.Say(assignSource,
|
||
"The pointer argument to ASSOCIATED must be same as the target of the assignment"_err_en_US);
|
||
}
|
||
break;
|
||
// x equalop e | e equalop x (allowing "e equalop x" is an extension)
|
||
case operation::Operator::Eq:
|
||
case operation::Operator::Eqv:
|
||
// x ordop expr | expr ordop x
|
||
case operation::Operator::Lt:
|
||
case operation::Operator::Gt: {
|
||
const SomeExpr &arg0{top.second[0]};
|
||
const SomeExpr &arg1{top.second[1]};
|
||
if (IsSameOrConvertOf(arg0, atom)) {
|
||
CheckStorageOverlap(atom, {arg1}, condSource);
|
||
} else if (IsSameOrConvertOf(arg1, atom)) {
|
||
CheckStorageOverlap(atom, {arg0}, condSource);
|
||
} else {
|
||
assert(top.first != operation::Operator::Identity &&
|
||
"Handle this separately");
|
||
context_.Say(assignSource,
|
||
"An argument of the %s operator should be the target of the assignment"_err_en_US,
|
||
operation::ToString(top.first));
|
||
}
|
||
break;
|
||
}
|
||
case operation::Operator::Identity:
|
||
case operation::Operator::True:
|
||
case operation::Operator::False:
|
||
break;
|
||
default:
|
||
assert(
|
||
top.first != operation::Operator::Identity && "Handle this separately");
|
||
context_.Say(condSource,
|
||
"The %s operator is not a valid condition for ATOMIC operation"_err_en_US,
|
||
operation::ToString(top.first));
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicConditionalUpdateStmt(
|
||
const AnalyzedCondStmt &update, parser::CharBlock source) {
|
||
// The condition/statements must be:
|
||
// - cond: x equalop e ift: x = d iff: -
|
||
// - cond: x ordop expr ift: x = expr iff: - (+ commute ordop)
|
||
// - cond: associated(x) ift: x => expr iff: -
|
||
// - cond: associated(x, e) ift: x => expr iff: -
|
||
|
||
// The if-true statement must be present, and must be an assignment.
|
||
auto maybeAssign{GetEvaluateAssignment(update.ift.stmt())};
|
||
if (!maybeAssign) {
|
||
if (update.ift.stmt() && !IsAssignment(update.ift.stmt())) {
|
||
context_.Say(update.ift.source,
|
||
"In ATOMIC UPDATE COMPARE the update statement should be an assignment"_err_en_US);
|
||
} else {
|
||
context_.Say(
|
||
source, "Invalid body of ATOMIC UPDATE COMPARE operation"_err_en_US);
|
||
}
|
||
return;
|
||
}
|
||
const evaluate::Assignment assign{*maybeAssign};
|
||
const SomeExpr &atom{assign.lhs};
|
||
|
||
CheckAtomicConditionalUpdateAssignment(
|
||
update.cond, update.source, assign, update.ift.source);
|
||
|
||
CheckStorageOverlap(atom, {assign.rhs}, update.ift.source);
|
||
|
||
if (update.iff) {
|
||
context_.Say(update.iff.source,
|
||
"In ATOMIC UPDATE COMPARE the update statement should not have an ELSE branch"_err_en_US);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicUpdateOnly(
|
||
const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
|
||
parser::CharBlock source) {
|
||
if (body.size() == 1) {
|
||
SourcedActionStmt action{GetActionStmt(&body.front())};
|
||
if (auto maybeUpdate{GetEvaluateAssignment(action.stmt())}) {
|
||
const SomeExpr &atom{maybeUpdate->lhs};
|
||
auto maybeAssign{
|
||
CheckAtomicUpdateAssignment(*maybeUpdate, action.source)};
|
||
auto &updateAssign{maybeAssign.has_value() ? maybeAssign : maybeUpdate};
|
||
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
x.analysis = AtomicAnalysis(atom)
|
||
.addOp0(Analysis::Update, updateAssign)
|
||
.addOp1(Analysis::None);
|
||
} else if (!IsAssignment(action.stmt())) {
|
||
context_.Say(
|
||
source, "ATOMIC UPDATE operation should be an assignment"_err_en_US);
|
||
}
|
||
} else {
|
||
context_.Say(x.source,
|
||
"ATOMIC UPDATE operation should have a single statement"_err_en_US);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicConditionalUpdate(
|
||
const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
|
||
parser::CharBlock source) {
|
||
// Allowable forms are (single-statement):
|
||
// - if ...
|
||
// - x = (... ? ... : x)
|
||
// and two-statement:
|
||
// - r = cond ; if (r) ...
|
||
|
||
const parser::ExecutionPartConstruct *ust{nullptr}; // update
|
||
const parser::ExecutionPartConstruct *cst{nullptr}; // condition
|
||
|
||
if (body.size() == 1) {
|
||
ust = &body.front();
|
||
} else if (body.size() == 2) {
|
||
cst = &body.front();
|
||
ust = &body.back();
|
||
} else {
|
||
context_.Say(source,
|
||
"ATOMIC UPDATE COMPARE operation should contain one or two statements"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
// Flang doesn't support conditional-expr yet, so all update statements
|
||
// are if-statements.
|
||
|
||
// IfStmt: if (...) ...
|
||
// IfConstruct: if (...) then ... endif
|
||
auto maybeUpdate{AnalyzeConditionalStmt(ust)};
|
||
if (!maybeUpdate) {
|
||
context_.Say(source,
|
||
"In ATOMIC UPDATE COMPARE the update statement should be a conditional statement"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
AnalyzedCondStmt &update{*maybeUpdate};
|
||
|
||
if (SourcedActionStmt action{GetActionStmt(cst)}) {
|
||
// The "condition" statement must be `r = cond`.
|
||
if (auto maybeCond{GetEvaluateAssignment(action.stmt())}) {
|
||
if (maybeCond->lhs != update.cond) {
|
||
context_.Say(update.source,
|
||
"In ATOMIC UPDATE COMPARE the conditional statement must use %s as the condition"_err_en_US,
|
||
maybeCond->lhs.AsFortran());
|
||
} else {
|
||
// If it's "r = ...; if (r) ..." then put the original condition
|
||
// in `update`.
|
||
update.cond = maybeCond->rhs;
|
||
}
|
||
} else {
|
||
context_.Say(action.source,
|
||
"In ATOMIC UPDATE COMPARE with two statements the first statement should compute the condition"_err_en_US);
|
||
}
|
||
}
|
||
|
||
evaluate::Assignment assign{*GetEvaluateAssignment(update.ift.stmt())};
|
||
|
||
CheckAtomicConditionalUpdateStmt(update, source);
|
||
if (IsCheckForAssociated(update.cond)) {
|
||
if (!IsPointerAssignment(assign)) {
|
||
context_.Say(source,
|
||
"The assignment should be a pointer-assignment when the condition is ASSOCIATED"_err_en_US);
|
||
}
|
||
} else {
|
||
if (IsPointerAssignment(assign)) {
|
||
context_.Say(source,
|
||
"The assignment cannot be a pointer-assignment except when the condition is ASSOCIATED"_err_en_US);
|
||
}
|
||
}
|
||
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
const SomeExpr &atom{assign.lhs};
|
||
|
||
x.analysis = AtomicAnalysis(atom, update.cond)
|
||
.addOp0(Analysis::Update | Analysis::IfTrue, assign)
|
||
.addOp1(Analysis::None);
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicUpdateCapture(
|
||
const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
|
||
parser::CharBlock source) {
|
||
if (body.size() != 2) {
|
||
context_.Say(source,
|
||
"ATOMIC UPDATE operation with CAPTURE should contain two statements"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
auto [uec, cec]{CheckUpdateCapture(&body.front(), &body.back(), source)};
|
||
if (!uec || !cec) {
|
||
// Diagnostics already emitted.
|
||
return;
|
||
}
|
||
SourcedActionStmt uact{GetActionStmt(uec)};
|
||
SourcedActionStmt cact{GetActionStmt(cec)};
|
||
// The "dereferences" of std::optional are guaranteed to be valid after
|
||
// CheckUpdateCapture.
|
||
evaluate::Assignment update{*GetEvaluateAssignment(uact.stmt())};
|
||
evaluate::Assignment capture{*GetEvaluateAssignment(cact.stmt())};
|
||
|
||
const SomeExpr &atom{update.lhs};
|
||
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
int action;
|
||
|
||
std::optional<evaluate::Assignment> updateAssign{update};
|
||
if (IsMaybeAtomicWrite(update)) {
|
||
action = Analysis::Write;
|
||
CheckAtomicWriteAssignment(update, uact.source);
|
||
} else {
|
||
action = Analysis::Update;
|
||
if (auto &&maybe{CheckAtomicUpdateAssignment(update, uact.source)}) {
|
||
updateAssign = maybe;
|
||
}
|
||
}
|
||
CheckAtomicCaptureAssignment(capture, atom, cact.source);
|
||
|
||
if (IsPointerAssignment(*updateAssign) != IsPointerAssignment(capture)) {
|
||
context_.Say(cact.source,
|
||
"The update and capture assignments should both be pointer-assignments or both be non-pointer-assignments"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
if (GetActionStmt(&body.front()).stmt() == uact.stmt()) {
|
||
x.analysis = AtomicAnalysis(atom)
|
||
.addOp0(action, updateAssign)
|
||
.addOp1(Analysis::Read, capture);
|
||
} else {
|
||
x.analysis = AtomicAnalysis(atom)
|
||
.addOp0(Analysis::Read, capture)
|
||
.addOp1(action, updateAssign);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicConditionalUpdateCapture(
|
||
const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
|
||
parser::CharBlock source) {
|
||
// There are two different variants of this:
|
||
// (1) conditional-update and capture separately:
|
||
// This form only allows single-statement updates, i.e. the update
|
||
// form "r = cond; if (r) ..." is not allowed.
|
||
// (2) conditional-update combined with capture in a single statement:
|
||
// This form does allow the condition to be calculated separately,
|
||
// i.e. "r = cond; if (r) ...".
|
||
// Regardless of what form it is, the actual update assignment is a
|
||
// proper write, i.e. "x = d", where d does not depend on x.
|
||
|
||
AnalyzedCondStmt update;
|
||
SourcedActionStmt capture;
|
||
bool captureAlways{true}, captureFirst{true};
|
||
|
||
auto extractCapture{[&]() {
|
||
capture = update.iff;
|
||
captureAlways = false;
|
||
update.iff = SourcedActionStmt{};
|
||
}};
|
||
|
||
auto classifyNonUpdate{[&](const SourcedActionStmt &action) {
|
||
// The non-update statement is either "r = cond" or the capture.
|
||
if (auto maybeAssign{GetEvaluateAssignment(action.stmt())}) {
|
||
if (update.cond == maybeAssign->lhs) {
|
||
// If this is "r = cond; if (r) ...", then update the condition.
|
||
update.cond = maybeAssign->rhs;
|
||
update.source = action.source;
|
||
// In this form, the update and the capture are combined into
|
||
// an IF-THEN-ELSE statement.
|
||
extractCapture();
|
||
} else {
|
||
// Assume this is the capture-statement.
|
||
capture = action;
|
||
}
|
||
}
|
||
}};
|
||
|
||
if (body.size() == 2) {
|
||
// This could be
|
||
// - capture; conditional-update (in any order), or
|
||
// - r = cond; if (r) capture-update
|
||
const parser::ExecutionPartConstruct *st1{&body.front()};
|
||
const parser::ExecutionPartConstruct *st2{&body.back()};
|
||
// In either case, the conditional statement can be analyzed by
|
||
// AnalyzeConditionalStmt, whereas the other statement cannot.
|
||
if (auto maybeUpdate1{AnalyzeConditionalStmt(st1)}) {
|
||
update = *maybeUpdate1;
|
||
classifyNonUpdate(GetActionStmt(st2));
|
||
captureFirst = false;
|
||
} else if (auto maybeUpdate2{AnalyzeConditionalStmt(st2)}) {
|
||
update = *maybeUpdate2;
|
||
classifyNonUpdate(GetActionStmt(st1));
|
||
} else {
|
||
// None of the statements are conditional, this rules out the
|
||
// "r = cond; if (r) ..." and the "capture + conditional-update"
|
||
// variants. This could still be capture + write (which is classified
|
||
// as conditional-update-capture in the spec).
|
||
auto [uec, cec]{CheckUpdateCapture(st1, st2, source)};
|
||
if (!uec || !cec) {
|
||
// Diagnostics already emitted.
|
||
return;
|
||
}
|
||
SourcedActionStmt uact{GetActionStmt(uec)};
|
||
SourcedActionStmt cact{GetActionStmt(cec)};
|
||
update.ift = uact;
|
||
capture = cact;
|
||
if (uec == st1) {
|
||
captureFirst = false;
|
||
}
|
||
}
|
||
} else if (body.size() == 1) {
|
||
if (auto maybeUpdate{AnalyzeConditionalStmt(&body.front())}) {
|
||
update = *maybeUpdate;
|
||
// This is the form with update and capture combined into an IF-THEN-ELSE
|
||
// statement. The capture-statement is always the ELSE branch.
|
||
extractCapture();
|
||
} else {
|
||
goto invalid;
|
||
}
|
||
} else {
|
||
context_.Say(source,
|
||
"ATOMIC UPDATE COMPARE CAPTURE operation should contain one or two statements"_err_en_US);
|
||
return;
|
||
invalid:
|
||
context_.Say(source,
|
||
"Invalid body of ATOMIC UPDATE COMPARE CAPTURE operation"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
// The update must have a form `x = d` or `x => d`.
|
||
if (auto maybeWrite{GetEvaluateAssignment(update.ift.stmt())}) {
|
||
const SomeExpr &atom{maybeWrite->lhs};
|
||
CheckAtomicWriteAssignment(*maybeWrite, update.ift.source);
|
||
if (auto maybeCapture{GetEvaluateAssignment(capture.stmt())}) {
|
||
CheckAtomicCaptureAssignment(*maybeCapture, atom, capture.source);
|
||
|
||
if (IsPointerAssignment(*maybeWrite) !=
|
||
IsPointerAssignment(*maybeCapture)) {
|
||
context_.Say(capture.source,
|
||
"The update and capture assignments should both be pointer-assignments or both be non-pointer-assignments"_err_en_US);
|
||
return;
|
||
}
|
||
} else {
|
||
if (!IsAssignment(capture.stmt())) {
|
||
context_.Say(capture.source,
|
||
"In ATOMIC UPDATE COMPARE CAPTURE the capture statement should be an assignment"_err_en_US);
|
||
}
|
||
return;
|
||
}
|
||
} else {
|
||
if (!IsAssignment(update.ift.stmt())) {
|
||
context_.Say(update.ift.source,
|
||
"In ATOMIC UPDATE COMPARE CAPTURE the update statement should be an assignment"_err_en_US);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// update.iff should be empty here, the capture statement should be
|
||
// stored in "capture".
|
||
|
||
// Fill out the analysis in the AST node.
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
bool condUnused{std::visit(
|
||
[](auto &&s) {
|
||
using BareS = llvm::remove_cvref_t<decltype(s)>;
|
||
if constexpr (std::is_same_v<BareS, evaluate::NullPointer>) {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
},
|
||
update.cond.u)};
|
||
|
||
int updateWhen{!condUnused ? Analysis::IfTrue : 0};
|
||
int captureWhen{!captureAlways ? Analysis::IfFalse : 0};
|
||
|
||
evaluate::Assignment updAssign{*GetEvaluateAssignment(update.ift.stmt())};
|
||
evaluate::Assignment capAssign{*GetEvaluateAssignment(capture.stmt())};
|
||
const SomeExpr &atom{updAssign.lhs};
|
||
|
||
if (captureFirst) {
|
||
x.analysis = AtomicAnalysis(atom, update.cond)
|
||
.addOp0(Analysis::Read | captureWhen, capAssign)
|
||
.addOp1(Analysis::Write | updateWhen, updAssign);
|
||
} else {
|
||
x.analysis = AtomicAnalysis(atom, update.cond)
|
||
.addOp0(Analysis::Write | updateWhen, updAssign)
|
||
.addOp1(Analysis::Read | captureWhen, capAssign);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicRead(
|
||
const parser::OpenMPAtomicConstruct &x) {
|
||
// [6.0:190:5-7]
|
||
// A read structured block is read-statement, a read statement that has one
|
||
// of the following forms:
|
||
// v = x
|
||
// v => x
|
||
auto &block{std::get<parser::Block>(x.t)};
|
||
|
||
// Read cannot be conditional or have a capture statement.
|
||
if (x.IsCompare() || x.IsCapture()) {
|
||
context_.Say(x.BeginDir().source,
|
||
"ATOMIC READ cannot have COMPARE or CAPTURE clauses"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
const parser::Block &body{GetInnermostExecPart(block)};
|
||
|
||
if (body.size() == 1) {
|
||
SourcedActionStmt action{GetActionStmt(&body.front())};
|
||
if (auto maybeRead{GetEvaluateAssignment(action.stmt())}) {
|
||
CheckAtomicReadAssignment(*maybeRead, action.source);
|
||
|
||
if (auto maybe{GetConvertInput(maybeRead->rhs)}) {
|
||
const SomeExpr &atom{*maybe};
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
x.analysis = AtomicAnalysis(atom)
|
||
.addOp0(Analysis::Read, maybeRead)
|
||
.addOp1(Analysis::None);
|
||
}
|
||
} else if (!IsAssignment(action.stmt())) {
|
||
context_.Say(
|
||
x.source, "ATOMIC READ operation should be an assignment"_err_en_US);
|
||
}
|
||
} else {
|
||
context_.Say(x.source,
|
||
"ATOMIC READ operation should have a single statement"_err_en_US);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicWrite(
|
||
const parser::OpenMPAtomicConstruct &x) {
|
||
auto &block{std::get<parser::Block>(x.t)};
|
||
|
||
// Write cannot be conditional or have a capture statement.
|
||
if (x.IsCompare() || x.IsCapture()) {
|
||
context_.Say(x.BeginDir().source,
|
||
"ATOMIC WRITE cannot have COMPARE or CAPTURE clauses"_err_en_US);
|
||
return;
|
||
}
|
||
|
||
const parser::Block &body{GetInnermostExecPart(block)};
|
||
|
||
if (body.size() == 1) {
|
||
SourcedActionStmt action{GetActionStmt(&body.front())};
|
||
if (auto maybeWrite{GetEvaluateAssignment(action.stmt())}) {
|
||
const SomeExpr &atom{maybeWrite->lhs};
|
||
CheckAtomicWriteAssignment(*maybeWrite, action.source);
|
||
|
||
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
|
||
x.analysis = AtomicAnalysis(atom)
|
||
.addOp0(Analysis::Write, maybeWrite)
|
||
.addOp1(Analysis::None);
|
||
} else if (!IsAssignment(action.stmt())) {
|
||
context_.Say(
|
||
x.source, "ATOMIC WRITE operation should be an assignment"_err_en_US);
|
||
}
|
||
} else {
|
||
context_.Say(x.source,
|
||
"ATOMIC WRITE operation should have a single statement"_err_en_US);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::CheckAtomicUpdate(
|
||
const parser::OpenMPAtomicConstruct &x) {
|
||
auto &block{std::get<parser::Block>(x.t)};
|
||
|
||
bool isConditional{x.IsCompare()};
|
||
bool isCapture{x.IsCapture()};
|
||
const parser::Block &body{GetInnermostExecPart(block)};
|
||
|
||
if (isConditional && isCapture) {
|
||
CheckAtomicConditionalUpdateCapture(x, body, x.source);
|
||
} else if (isConditional) {
|
||
CheckAtomicConditionalUpdate(x, body, x.source);
|
||
} else if (isCapture) {
|
||
CheckAtomicUpdateCapture(x, body, x.source);
|
||
} else { // update-only
|
||
CheckAtomicUpdateOnly(x, body, x.source);
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::Enter(const parser::OpenMPAtomicConstruct &x) {
|
||
if (visitedAtomicSource_.empty())
|
||
visitedAtomicSource_ = x.source;
|
||
|
||
// All of the following groups have the "exclusive" property, i.e. at
|
||
// most one clause from each group is allowed.
|
||
// The exclusivity-checking code should eventually be unified for all
|
||
// clauses, with clause groups defined in OMP.td.
|
||
std::array atomic{llvm::omp::Clause::OMPC_read,
|
||
llvm::omp::Clause::OMPC_update, llvm::omp::Clause::OMPC_write};
|
||
std::array memoryOrder{llvm::omp::Clause::OMPC_acq_rel,
|
||
llvm::omp::Clause::OMPC_acquire, llvm::omp::Clause::OMPC_relaxed,
|
||
llvm::omp::Clause::OMPC_release, llvm::omp::Clause::OMPC_seq_cst};
|
||
|
||
auto checkExclusive{[&](llvm::ArrayRef<llvm::omp::Clause> group,
|
||
std::string_view name,
|
||
const parser::OmpClauseList &clauses) {
|
||
const parser::OmpClause *present{nullptr};
|
||
for (const parser::OmpClause &clause : clauses.v) {
|
||
llvm::omp::Clause id{clause.Id()};
|
||
if (!llvm::is_contained(group, id)) {
|
||
continue;
|
||
}
|
||
if (present == nullptr) {
|
||
present = &clause;
|
||
continue;
|
||
} else if (id == present->Id()) {
|
||
// Ignore repetitions of the same clause, those will be diagnosed
|
||
// separately.
|
||
continue;
|
||
}
|
||
parser::MessageFormattedText txt(
|
||
"At most one clause from the '%s' group is allowed on ATOMIC construct"_err_en_US,
|
||
name.data());
|
||
parser::Message message(clause.source, txt);
|
||
message.Attach(present->source,
|
||
"Previous clause from this group provided here"_en_US);
|
||
context_.Say(std::move(message));
|
||
return;
|
||
}
|
||
}};
|
||
|
||
const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()};
|
||
auto &dir{std::get<parser::OmpDirectiveName>(dirSpec.t)};
|
||
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_atomic);
|
||
llvm::omp::Clause kind{x.GetKind()};
|
||
|
||
checkExclusive(atomic, "atomic", dirSpec.Clauses());
|
||
checkExclusive(memoryOrder, "memory-order", dirSpec.Clauses());
|
||
|
||
switch (kind) {
|
||
case llvm::omp::Clause::OMPC_read:
|
||
CheckAtomicRead(x);
|
||
break;
|
||
case llvm::omp::Clause::OMPC_write:
|
||
CheckAtomicWrite(x);
|
||
break;
|
||
case llvm::omp::Clause::OMPC_update:
|
||
CheckAtomicUpdate(x);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OmpStructureChecker::Leave(const parser::OpenMPAtomicConstruct &) {
|
||
dirContext_.pop_back();
|
||
}
|
||
|
||
// Rewrite min/max:
|
||
// Min and max intrinsics in Fortran take an arbitrary number of arguments
|
||
// (two or more). The first two are mandatory, the rest is optional. That
|
||
// means that arguments beyond the first two may be optional dummy argument
|
||
// from the caller. In that case, a reference to such an argument will
|
||
// cause presence test to be emitted, which cannot go inside of the atomic
|
||
// operation. Since the atom operand must be present, rewrite the min/max
|
||
// operation in a way that avoid the presence tests in the atomic code.
|
||
// For example, in
|
||
// subroutine f(atom, x, y, z)
|
||
// integer :: atom, x
|
||
// integer, optional :: y, z
|
||
// !$omp atomic update
|
||
// atom = min(atom, x, y, z)
|
||
// end
|
||
// the min operation will become
|
||
// atom = min(atom, min(x, y, z))
|
||
// and in the final code
|
||
// // Presence check is fine here.
|
||
// tmp = min(x, y, z)
|
||
// atomic update {
|
||
// // Both operands are mandatory, no presence check needed.
|
||
// atom = min(atom, tmp)
|
||
// }
|
||
struct MinMaxRewriter : public evaluate::rewrite::Identity {
|
||
using Id = evaluate::rewrite::Identity;
|
||
using Id::operator();
|
||
|
||
MinMaxRewriter(const SomeExpr &atom) : atom_(atom) {}
|
||
|
||
static bool IsMinMax(const evaluate::ProcedureDesignator &p) {
|
||
if (auto *intrin{p.GetSpecificIntrinsic()}) {
|
||
return intrin->name == "min" || intrin->name == "max";
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Take a list of arguments to a min/max operation, e.g. [a0, a1, ...]
|
||
// One of the a_i's, say a_t, must be the atom.
|
||
// Generate
|
||
// min/max(a_t, min/max(a0, a1, ... [except a_t]))
|
||
template <typename T>
|
||
evaluate::Expr<T> operator()(
|
||
evaluate::Expr<T> &&x, const evaluate::FunctionRef<T> &f) {
|
||
const evaluate::ProcedureDesignator &proc = f.proc();
|
||
if (!IsMinMax(proc) || f.arguments().size() <= 2) {
|
||
return Id::operator()(std::move(x), f);
|
||
}
|
||
|
||
// Collect arguments as SomeExpr's and find out which argument
|
||
// corresponds to atom.
|
||
const SomeExpr *atomArg{nullptr};
|
||
std::vector<const SomeExpr *> args;
|
||
for (const std::optional<evaluate::ActualArgument> &a : f.arguments()) {
|
||
if (!a) {
|
||
continue;
|
||
}
|
||
if (const SomeExpr *e{a->UnwrapExpr()}) {
|
||
if (evaluate::IsSameOrConvertOf(*e, atom_)) {
|
||
atomArg = e;
|
||
}
|
||
args.push_back(e);
|
||
}
|
||
}
|
||
if (!atomArg) {
|
||
return Id::operator()(std::move(x), f);
|
||
}
|
||
|
||
evaluate::ActualArguments nonAtoms;
|
||
|
||
auto AsActual = [](const SomeExpr &z) {
|
||
SomeExpr copy = z;
|
||
return evaluate::ActualArgument(std::move(copy));
|
||
};
|
||
// Semantic checks guarantee that the "atom" shows exactly once in the
|
||
// argument list (with potential conversions around it).
|
||
// For the first two (non-optional) arguments, if "atom" is among them,
|
||
// replace it with another occurrence of the other non-optional argument.
|
||
if (atomArg == args[0]) {
|
||
// (atom, x, y...) -> (x, x, y...)
|
||
nonAtoms.push_back(AsActual(*args[1]));
|
||
nonAtoms.push_back(AsActual(*args[1]));
|
||
} else if (atomArg == args[1]) {
|
||
// (x, atom, y...) -> (x, x, y...)
|
||
nonAtoms.push_back(AsActual(*args[0]));
|
||
nonAtoms.push_back(AsActual(*args[0]));
|
||
} else {
|
||
// (x, y, z...) -> unchanged
|
||
nonAtoms.push_back(AsActual(*args[0]));
|
||
nonAtoms.push_back(AsActual(*args[1]));
|
||
}
|
||
|
||
// The rest of arguments are optional, so we can just skip "atom".
|
||
for (size_t i = 2, e = args.size(); i != e; ++i) {
|
||
if (atomArg != args[i])
|
||
nonAtoms.push_back(AsActual(*args[i]));
|
||
}
|
||
|
||
SomeExpr tmp = evaluate::AsGenericExpr(
|
||
evaluate::FunctionRef<T>(AsRvalue(proc), AsRvalue(nonAtoms)));
|
||
|
||
return evaluate::Expr<T>(evaluate::FunctionRef<T>(
|
||
AsRvalue(proc), {AsActual(*atomArg), AsActual(tmp)}));
|
||
}
|
||
|
||
private:
|
||
const SomeExpr &atom_;
|
||
};
|
||
|
||
static MaybeExpr PostSemaRewrite(const SomeExpr &atom, const SomeExpr &expr) {
|
||
MinMaxRewriter rewriter(atom);
|
||
return evaluate::rewrite::Mutator(rewriter)(expr);
|
||
}
|
||
|
||
} // namespace Fortran::semantics
|