Files
clang-p2996/flang/lib/Semantics/assignment.cpp
Tom Eccles ce603a0f16 [flang][openmp]Add UserReductionDetails and use in DECLARE REDUCTION (#140066)
This adds another puzzle piece for the support of OpenMP DECLARE
REDUCTION functionality.

This adds support for operators with derived types, as well as declaring
multiple different types with the same name or operator.

A new detail class for UserReductionDetials is introduced to hold the
list of types supported for a given reduction declaration.

Tests for parsing and symbol generation added.

Declare reduction is still not supported to lowering, it will generate a
"Not yet implemented" fatal error.

Fixes #141306
Fixes #97241
Fixes #92832
Fixes #66453

---------

Co-authored-by: Mats Petersson <mats.petersson@arm.com>
2025-06-09 11:17:03 +01:00

301 lines
11 KiB
C++

//===-- lib/Semantics/assignment.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 "assignment.h"
#include "definable.h"
#include "pointer-assignment.h"
#include "flang/Common/idioms.h"
#include "flang/Common/restorer.h"
#include "flang/Evaluate/characteristics.h"
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
#include "flang/Parser/message.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/expression.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/tools.h"
#include <optional>
#include <set>
#include <string>
#include <type_traits>
using namespace Fortran::parser::literals;
namespace Fortran::semantics {
class AssignmentContext {
public:
explicit AssignmentContext(SemanticsContext &context) : context_{context} {}
AssignmentContext(AssignmentContext &&) = default;
AssignmentContext(const AssignmentContext &) = delete;
bool operator==(const AssignmentContext &x) const { return this == &x; }
template <typename A> void PushWhereContext(const A &);
void PopWhereContext();
void Analyze(const parser::AssignmentStmt &);
void Analyze(const parser::PointerAssignmentStmt &);
void Analyze(const parser::ConcurrentControl &);
int deviceConstructDepth_{0};
SemanticsContext &context() { return context_; }
private:
bool CheckForPureContext(const SomeExpr &rhs, parser::CharBlock rhsSource);
void CheckShape(parser::CharBlock, const SomeExpr *);
template <typename... A>
parser::Message *Say(parser::CharBlock at, A &&...args) {
return &context_.Say(at, std::forward<A>(args)...);
}
evaluate::FoldingContext &foldingContext() {
return context_.foldingContext();
}
SemanticsContext &context_;
int whereDepth_{0}; // number of WHEREs currently nested in
// shape of masks in LHS of assignments in current WHERE:
std::vector<std::optional<std::int64_t>> whereExtents_;
};
void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) {
if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
const SomeExpr &lhs{assignment->lhs};
const SomeExpr &rhs{assignment->rhs};
auto lhsLoc{std::get<parser::Variable>(stmt.t).GetSource()};
const Scope &scope{context_.FindScope(lhsLoc)};
DefinabilityFlags flags{DefinabilityFlag::VectorSubscriptIsOk};
bool isDefinedAssignment{
std::holds_alternative<evaluate::ProcedureRef>(assignment->u)};
if (isDefinedAssignment) {
flags.set(DefinabilityFlag::AllowEventLockOrNotifyType);
} else if (const Symbol *
whole{evaluate::UnwrapWholeSymbolOrComponentDataRef(lhs)}) {
if (IsAllocatable(whole->GetUltimate())) {
flags.set(DefinabilityFlag::PotentialDeallocation);
}
}
if (auto whyNot{WhyNotDefinable(lhsLoc, scope, flags, lhs)}) {
if (whyNot->IsFatal()) {
if (auto *msg{Say(lhsLoc,
"Left-hand side of assignment is not definable"_err_en_US)}) {
msg->Attach(
std::move(whyNot->set_severity(parser::Severity::Because)));
}
} else {
context_.Say(std::move(*whyNot));
}
}
auto rhsLoc{std::get<parser::Expr>(stmt.t).source};
if (!isDefinedAssignment) {
CheckForPureContext(rhs, rhsLoc);
}
if (whereDepth_ > 0) {
CheckShape(lhsLoc, &lhs);
}
if (context_.foldingContext().languageFeatures().IsEnabled(
common::LanguageFeature::CUDA)) {
const auto &scope{context_.FindScope(lhsLoc)};
const Scope &progUnit{GetProgramUnitContaining(scope)};
if (!IsCUDADeviceContext(&progUnit) && deviceConstructDepth_ == 0) {
if (Fortran::evaluate::HasCUDADeviceAttrs(lhs) &&
Fortran::evaluate::HasCUDAImplicitTransfer(rhs)) {
if (GetNbOfCUDAManagedOrUnifiedSymbols(lhs) == 1 &&
GetNbOfCUDAManagedOrUnifiedSymbols(rhs) == 1 &&
GetNbOfCUDADeviceSymbols(rhs) == 1)
return; // This is a special case handled on the host.
context_.Say(lhsLoc, "Unsupported CUDA data transfer"_err_en_US);
}
}
}
}
}
void AssignmentContext::Analyze(const parser::PointerAssignmentStmt &stmt) {
CHECK(whereDepth_ == 0);
if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
parser::CharBlock at{context_.location().value()};
auto restorer{foldingContext().messages().SetLocation(at)};
CheckPointerAssignment(context_, *assignment, context_.FindScope(at));
}
}
static std::optional<std::string> GetPointerComponentDesignatorName(
const SomeExpr &expr) {
if (const auto *derived{
evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(expr))}) {
PotentialAndPointerComponentIterator potentials{*derived};
if (auto pointer{
std::find_if(potentials.begin(), potentials.end(), IsPointer)}) {
return pointer.BuildResultDesignatorName();
}
}
return std::nullopt;
}
// Checks C1594(5,6); false if check fails
bool CheckCopyabilityInPureScope(parser::ContextualMessages &messages,
const SomeExpr &expr, const Scope &scope) {
if (auto pointer{GetPointerComponentDesignatorName(expr)}) {
if (const Symbol * base{GetFirstSymbol(expr)}) {
const char *why{WhyBaseObjectIsSuspicious(base->GetUltimate(), scope)};
if (!why) {
if (auto coarray{evaluate::ExtractCoarrayRef(expr)}) {
base = &coarray->GetLastSymbol();
why = "coindexed";
}
}
if (why) {
evaluate::SayWithDeclaration(messages, *base,
"A pure subprogram may not copy the value of '%s' because it is %s"
" and has the POINTER potential subobject component '%s'"_err_en_US,
base->name(), why, *pointer);
return false;
}
}
}
return true;
}
bool AssignmentContext::CheckForPureContext(
const SomeExpr &rhs, parser::CharBlock rhsSource) {
const Scope &scope{context_.FindScope(rhsSource)};
if (FindPureProcedureContaining(scope)) {
parser::ContextualMessages messages{
context_.location().value(), &context_.messages()};
return CheckCopyabilityInPureScope(messages, rhs, scope);
} else {
return true;
}
}
// 10.2.3.1(2) The masks and LHS of assignments must be arrays of the same shape
void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) {
if (auto shape{evaluate::GetShape(foldingContext(), expr)}) {
std::size_t size{shape->size()};
if (size == 0) {
Say(at, "The mask or variable must not be scalar"_err_en_US);
}
if (whereDepth_ == 0) {
whereExtents_.resize(size);
} else if (whereExtents_.size() != size) {
Say(at,
"Must have rank %zd to match prior mask or assignment of"
" WHERE construct"_err_en_US,
whereExtents_.size());
return;
}
for (std::size_t i{0}; i < size; ++i) {
if (std::optional<std::int64_t> extent{evaluate::ToInt64((*shape)[i])}) {
if (!whereExtents_[i]) {
whereExtents_[i] = *extent;
} else if (*whereExtents_[i] != *extent) {
Say(at,
"Dimension %d must have extent %jd to match prior mask or"
" assignment of WHERE construct"_err_en_US,
i + 1, *whereExtents_[i]);
}
}
}
}
}
template <typename A> void AssignmentContext::PushWhereContext(const A &x) {
const auto &expr{std::get<parser::LogicalExpr>(x.t)};
CheckShape(expr.thing.value().source, GetExpr(context_, expr));
++whereDepth_;
}
void AssignmentContext::PopWhereContext() {
--whereDepth_;
if (whereDepth_ == 0) {
whereExtents_.clear();
}
}
AssignmentChecker::~AssignmentChecker() {}
SemanticsContext &AssignmentChecker::context() {
return context_.value().context();
}
AssignmentChecker::AssignmentChecker(SemanticsContext &context)
: context_{new AssignmentContext{context}} {}
void AssignmentChecker::Enter(
const parser::OpenMPDeclareReductionConstruct &x) {
context().set_location(x.source);
}
void AssignmentChecker::Enter(const parser::AssignmentStmt &x) {
context_.value().Analyze(x);
}
void AssignmentChecker::Enter(const parser::PointerAssignmentStmt &x) {
context_.value().Analyze(x);
}
void AssignmentChecker::Enter(const parser::WhereStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::WhereStmt &) {
context_.value().PopWhereContext();
}
void AssignmentChecker::Enter(const parser::WhereConstructStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::EndWhereStmt &) {
context_.value().PopWhereContext();
}
void AssignmentChecker::Enter(const parser::MaskedElsewhereStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::MaskedElsewhereStmt &) {
context_.value().PopWhereContext();
}
void AssignmentChecker::Enter(const parser::CUFKernelDoConstruct &x) {
++context_.value().deviceConstructDepth_;
}
void AssignmentChecker::Leave(const parser::CUFKernelDoConstruct &) {
--context_.value().deviceConstructDepth_;
}
static bool IsOpenACCComputeConstruct(const parser::OpenACCBlockConstruct &x) {
const auto &beginBlockDirective =
std::get<Fortran::parser::AccBeginBlockDirective>(x.t);
const auto &blockDirective =
std::get<Fortran::parser::AccBlockDirective>(beginBlockDirective.t);
if (blockDirective.v == llvm::acc::ACCD_parallel ||
blockDirective.v == llvm::acc::ACCD_serial ||
blockDirective.v == llvm::acc::ACCD_kernels) {
return true;
}
return false;
}
void AssignmentChecker::Enter(const parser::OpenACCBlockConstruct &x) {
if (IsOpenACCComputeConstruct(x)) {
++context_.value().deviceConstructDepth_;
}
}
void AssignmentChecker::Leave(const parser::OpenACCBlockConstruct &x) {
if (IsOpenACCComputeConstruct(x)) {
--context_.value().deviceConstructDepth_;
}
}
void AssignmentChecker::Enter(const parser::OpenACCCombinedConstruct &) {
++context_.value().deviceConstructDepth_;
}
void AssignmentChecker::Leave(const parser::OpenACCCombinedConstruct &) {
--context_.value().deviceConstructDepth_;
}
void AssignmentChecker::Enter(const parser::OpenACCLoopConstruct &) {
++context_.value().deviceConstructDepth_;
}
void AssignmentChecker::Leave(const parser::OpenACCLoopConstruct &) {
--context_.value().deviceConstructDepth_;
}
} // namespace Fortran::semantics
template class Fortran::common::Indirection<
Fortran::semantics::AssignmentContext>;