//===-- lib/Semantics/openmp-utils.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 // //===----------------------------------------------------------------------===// // // Common utilities used in OpenMP semantic checks. // //===----------------------------------------------------------------------===// #include "openmp-utils.h" #include "flang/Common/indirection.h" #include "flang/Common/reference.h" #include "flang/Common/visit.h" #include "flang/Evaluate/check-expression.h" #include "flang/Evaluate/expression.h" #include "flang/Evaluate/tools.h" #include "flang/Evaluate/traverse.h" #include "flang/Evaluate/type.h" #include "flang/Evaluate/variable.h" #include "flang/Parser/parse-tree.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/semantics.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include #include #include #include #include #include #include namespace Fortran::semantics::omp { SourcedActionStmt GetActionStmt(const parser::ExecutionPartConstruct *x) { if (x == nullptr) { return SourcedActionStmt{}; } if (auto *exec{std::get_if(&x->u)}) { using ActionStmt = parser::Statement; if (auto *stmt{std::get_if(&exec->u)}) { return SourcedActionStmt{&stmt->statement, stmt->source}; } } return SourcedActionStmt{}; } SourcedActionStmt GetActionStmt(const parser::Block &block) { if (block.size() == 1) { return GetActionStmt(&block.front()); } return SourcedActionStmt{}; } std::string ThisVersion(unsigned version) { std::string tv{ std::to_string(version / 10) + "." + std::to_string(version % 10)}; return "OpenMP v" + tv; } std::string TryVersion(unsigned version) { return "try -fopenmp-version=" + std::to_string(version); } const parser::Designator *GetDesignatorFromObj( const parser::OmpObject &object) { return std::get_if(&object.u); } const parser::DataRef *GetDataRefFromObj(const parser::OmpObject &object) { if (auto *desg{GetDesignatorFromObj(object)}) { return std::get_if(&desg->u); } return nullptr; } const parser::ArrayElement *GetArrayElementFromObj( const parser::OmpObject &object) { if (auto *dataRef{GetDataRefFromObj(object)}) { using ElementIndirection = common::Indirection; if (auto *ind{std::get_if(&dataRef->u)}) { return &ind->value(); } } return nullptr; } const Symbol *GetObjectSymbol(const parser::OmpObject &object) { // Some symbols may be missing if the resolution failed, e.g. when an // undeclared name is used with implicit none. if (auto *name{std::get_if(&object.u)}) { return name->symbol ? &name->symbol->GetUltimate() : nullptr; } else if (auto *desg{std::get_if(&object.u)}) { auto &last{GetLastName(*desg)}; return last.symbol ? &GetLastName(*desg).symbol->GetUltimate() : nullptr; } return nullptr; } const Symbol *GetArgumentSymbol(const parser::OmpArgument &argument) { if (auto *locator{std::get_if(&argument.u)}) { if (auto *object{std::get_if(&locator->u)}) { return GetObjectSymbol(*object); } } return nullptr; } std::optional GetObjectSource( const parser::OmpObject &object) { if (auto *name{std::get_if(&object.u)}) { return name->source; } else if (auto *desg{std::get_if(&object.u)}) { return GetLastName(*desg).source; } return std::nullopt; } bool IsCommonBlock(const Symbol &sym) { return sym.detailsIf() != nullptr; } bool IsVariableListItem(const Symbol &sym) { return evaluate::IsVariable(sym) || sym.attrs().test(Attr::POINTER); } bool IsExtendedListItem(const Symbol &sym) { return IsVariableListItem(sym) || sym.IsSubprogram(); } bool IsVarOrFunctionRef(const MaybeExpr &expr) { if (expr) { return evaluate::UnwrapProcedureRef(*expr) != nullptr || evaluate::IsVariable(*expr); } else { return false; } } bool IsMapEnteringType(parser::OmpMapType::Value type) { switch (type) { case parser::OmpMapType::Value::Alloc: case parser::OmpMapType::Value::Storage: case parser::OmpMapType::Value::To: case parser::OmpMapType::Value::Tofrom: return true; default: return false; } } bool IsMapExitingType(parser::OmpMapType::Value type) { switch (type) { case parser::OmpMapType::Value::Delete: case parser::OmpMapType::Value::From: case parser::OmpMapType::Value::Release: case parser::OmpMapType::Value::Storage: case parser::OmpMapType::Value::Tofrom: return true; default: return false; } } std::optional GetEvaluateExpr(const parser::Expr &parserExpr) { const parser::TypedExpr &typedExpr{parserExpr.typedExpr}; // ForwardOwningPointer typedExpr // `- GenericExprWrapper ^.get() // `- std::optional ^->v return typedExpr.get()->v; } std::optional GetDynamicType( const parser::Expr &parserExpr) { if (auto maybeExpr{GetEvaluateExpr(parserExpr)}) { return maybeExpr->GetType(); } else { return std::nullopt; } } namespace { struct ContiguousHelper { ContiguousHelper(SemanticsContext &context) : fctx_(context.foldingContext()) {} template std::optional Visit(const common::Indirection &x) { return Visit(x.value()); } template std::optional Visit(const common::Reference &x) { return Visit(x.get()); } template std::optional Visit(const evaluate::Expr &x) { return common::visit([&](auto &&s) { return Visit(s); }, x.u); } template std::optional Visit(const evaluate::Designator &x) { return common::visit( [this](auto &&s) { return evaluate::IsContiguous(s, fctx_); }, x.u); } template std::optional Visit(const T &) { // Everything else. return std::nullopt; } private: evaluate::FoldingContext &fctx_; }; } // namespace // Return values: // - std::optional{true} if the object is known to be contiguous // - std::optional{false} if the object is known not to be contiguous // - std::nullopt if the object contiguity cannot be determined std::optional IsContiguous( SemanticsContext &semaCtx, const parser::OmpObject &object) { return common::visit( // common::visitors{ [&](const parser::Name &x) { // Any member of a common block must be contiguous. return std::optional{true}; }, [&](const parser::Designator &x) { evaluate::ExpressionAnalyzer ea{semaCtx}; if (MaybeExpr maybeExpr{ea.Analyze(x)}) { return ContiguousHelper{semaCtx}.Visit(*maybeExpr); } return std::optional{}; }, }, object.u); } struct DesignatorCollector : public evaluate::Traverse, false> { using Result = std::vector; using Base = evaluate::Traverse; DesignatorCollector() : Base(*this) {} Result Default() const { return {}; } using Base::operator(); template // Result operator()(const evaluate::Designator &x) const { // Once in a designator, don't traverse it any further (i.e. only // collect top-level designators). auto copy{x}; return Result{AsGenericExpr(std::move(copy))}; } template // Result Combine(Result &&result, Rs &&...results) const { Result v(std::move(result)); auto moveAppend{[](auto &accum, auto &&other) { for (auto &&s : other) { accum.push_back(std::move(s)); } }}; (moveAppend(v, std::move(results)), ...); return v; } }; struct VariableFinder : public evaluate::AnyTraverse { using Base = evaluate::AnyTraverse; VariableFinder(const SomeExpr &v) : Base(*this), var(v) {} using Base::operator(); template bool operator()(const evaluate::Designator &x) const { auto copy{x}; return evaluate::AsGenericExpr(std::move(copy)) == var; } template bool operator()(const evaluate::FunctionRef &x) const { auto copy{x}; return evaluate::AsGenericExpr(std::move(copy)) == var; } private: const SomeExpr &var; }; std::vector GetAllDesignators(const SomeExpr &expr) { return DesignatorCollector{}(expr); } static bool HasCommonDesignatorSymbols( const evaluate::SymbolVector &baseSyms, const SomeExpr &other) { // Compare the designators used in "other" with the designators whose // symbols are given in baseSyms. // This is a part of the check if these two expressions can access the same // storage: if the designators used in them are different enough, then they // will be assumed not to access the same memory. // // Consider an (array element) expression x%y(w%z), the corresponding symbol // vector will be {x, y, w, z} (i.e. the symbols for these names). // Check whether this exact sequence appears anywhere in any the symbol // vector for "other". This will be true for x(y) and x(y+1), so this is // not a sufficient condition, but can be used to eliminate candidates // before doing more exhaustive checks. // // If any of the symbols in this sequence are function names, assume that // there is no storage overlap, mostly because it would be impossible in // general to determine what storage the function will access. // Note: if f is pure, then two calls to f will access the same storage // when called with the same arguments. This check is not done yet. if (llvm::any_of( baseSyms, [](const SymbolRef &s) { return s->IsSubprogram(); })) { // If there is a function symbol in the chain then we can't infer much // about the accessed storage. return false; } auto isSubsequence{// Is u a subsequence of v. [](const evaluate::SymbolVector &u, const evaluate::SymbolVector &v) { size_t us{u.size()}, vs{v.size()}; if (us > vs) { return false; } for (size_t off{0}; off != vs - us + 1; ++off) { bool same{true}; for (size_t i{0}; i != us; ++i) { if (u[i] != v[off + i]) { same = false; break; } } if (same) { return true; } } return false; }}; evaluate::SymbolVector otherSyms{evaluate::GetSymbolVector(other)}; return isSubsequence(baseSyms, otherSyms); } static bool HasCommonTopLevelDesignators( const std::vector &baseDsgs, const SomeExpr &other) { // Compare designators directly as expressions. This will ensure // that x(y) and x(y+1) are not flagged as overlapping, whereas // the symbol vectors for both of these would be identical. std::vector otherDsgs{GetAllDesignators(other)}; for (auto &s : baseDsgs) { if (llvm::any_of(otherDsgs, [&](auto &&t) { return s == t; })) { return true; } } return false; } const SomeExpr *HasStorageOverlap( const SomeExpr &base, llvm::ArrayRef exprs) { evaluate::SymbolVector baseSyms{evaluate::GetSymbolVector(base)}; std::vector baseDsgs{GetAllDesignators(base)}; for (const SomeExpr &expr : exprs) { if (!HasCommonDesignatorSymbols(baseSyms, expr)) { continue; } if (HasCommonTopLevelDesignators(baseDsgs, expr)) { return &expr; } } return nullptr; } bool IsSubexpressionOf(const SomeExpr &sub, const SomeExpr &super) { return VariableFinder{sub}(super); } // Check if the ActionStmt is actually a [Pointer]AssignmentStmt. This is // to separate cases where the source has something that looks like an // assignment, but is semantically wrong (diagnosed by general semantic // checks), and where the source has some other statement (which we want // to report as "should be an assignment"). bool IsAssignment(const parser::ActionStmt *x) { if (x == nullptr) { return false; } using AssignmentStmt = common::Indirection; using PointerAssignmentStmt = common::Indirection; return common::visit( [](auto &&s) -> bool { using BareS = llvm::remove_cvref_t; return std::is_same_v || std::is_same_v; }, x->u); } bool IsPointerAssignment(const evaluate::Assignment &x) { return std::holds_alternative(x.u) || std::holds_alternative(x.u); } /// parser::Block is a list of executable constructs, parser::BlockConstruct /// is Fortran's BLOCK/ENDBLOCK construct. /// Strip the outermost BlockConstructs, return the reference to the Block /// in the executable part of the innermost of the stripped constructs. /// Specifically, if the given `block` has a single entry (it's a list), and /// the entry is a BlockConstruct, get the Block contained within. Repeat /// this step as many times as possible. const parser::Block &GetInnermostExecPart(const parser::Block &block) { const parser::Block *iter{&block}; while (iter->size() == 1) { const parser::ExecutionPartConstruct &ep{iter->front()}; if (auto *exec{std::get_if(&ep.u)}) { using BlockConstruct = common::Indirection; if (auto *bc{std::get_if(&exec->u)}) { iter = &std::get(bc->value().t); continue; } } break; } return *iter; } } // namespace Fortran::semantics::omp