[flang][OpenMP] Split check-omp-structure.cpp into smaller files, NFC (#146359)

Create these new files in flang/lib/Semantics:
  openmp-utils.cpp/.h         - Common utilities
  check-omp-atomic.cpp        - Atomic-related checks
  check-omp-loop.cpp          - Loop constructs/clauses
  check-omp-metadirective.cpp - Metadirective-related checks

Update lists of included headers, std in particular.

---------

Co-authored-by: Jack Styles <jack.styles@arm.com>
This commit is contained in:
Krzysztof Parzyszek
2025-07-01 11:12:00 -05:00
committed by GitHub
parent 1a7b7e24bc
commit ba116a8bed
8 changed files with 3016 additions and 2747 deletions

View File

@@ -20,6 +20,9 @@ add_flang_library(FortranSemantics
check-io.cpp
check-namelist.cpp
check-nullify.cpp
check-omp-atomic.cpp
check-omp-loop.cpp
check-omp-metadirective.cpp
check-omp-structure.cpp
check-purity.cpp
check-return.cpp
@@ -34,12 +37,13 @@ add_flang_library(FortranSemantics
mod-file.cpp
openmp-dsa.cpp
openmp-modifiers.cpp
openmp-utils.cpp
pointer-assignment.cpp
program-tree.cpp
resolve-labels.cpp
resolve-directives.cpp
resolve-names-utils.cpp
resolve-labels.cpp
resolve-names.cpp
resolve-names-utils.cpp
rewrite-parse-tree.cpp
runtime-type-info.cpp
scope.cpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,671 @@
//===-- lib/Semantics/check-omp-loop.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 for constructs and clauses related to loops.
//
//===----------------------------------------------------------------------===//
#include "check-omp-structure.h"
#include "check-directive-structure.h"
#include "openmp-utils.h"
#include "flang/Common/idioms.h"
#include "flang/Common/visit.h"
#include "flang/Parser/char-block.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Parser/tools.h"
#include "flang/Semantics/openmp-modifiers.h"
#include "flang/Semantics/semantics.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/tools.h"
#include "flang/Semantics/type.h"
#include "llvm/Frontend/OpenMP/OMP.h"
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <tuple>
#include <variant>
namespace {
using namespace Fortran;
class AssociatedLoopChecker {
public:
AssociatedLoopChecker(
semantics::SemanticsContext &context, std::int64_t level)
: context_{context}, level_{level} {}
template <typename T> bool Pre(const T &) { return true; }
template <typename T> void Post(const T &) {}
bool Pre(const parser::DoConstruct &dc) {
level_--;
const auto &doStmt{
std::get<parser::Statement<parser::NonLabelDoStmt>>(dc.t)};
const auto &constructName{
std::get<std::optional<parser::Name>>(doStmt.statement.t)};
if (constructName) {
constructNamesAndLevels_.emplace(
constructName.value().ToString(), level_);
}
if (level_ >= 0) {
if (dc.IsDoWhile()) {
context_.Say(doStmt.source,
"The associated loop of a loop-associated directive cannot be a DO WHILE."_err_en_US);
}
if (!dc.GetLoopControl()) {
context_.Say(doStmt.source,
"The associated loop of a loop-associated directive cannot be a DO without control."_err_en_US);
}
}
return true;
}
void Post(const parser::DoConstruct &dc) { level_++; }
bool Pre(const parser::CycleStmt &cyclestmt) {
std::map<std::string, std::int64_t>::iterator it;
bool err{false};
if (cyclestmt.v) {
it = constructNamesAndLevels_.find(cyclestmt.v->source.ToString());
err = (it != constructNamesAndLevels_.end() && it->second > 0);
} else { // If there is no label then use the level of the last enclosing DO
err = level_ > 0;
}
if (err) {
context_.Say(*source_,
"CYCLE statement to non-innermost associated loop of an OpenMP DO "
"construct"_err_en_US);
}
return true;
}
bool Pre(const parser::ExitStmt &exitStmt) {
std::map<std::string, std::int64_t>::iterator it;
bool err{false};
if (exitStmt.v) {
it = constructNamesAndLevels_.find(exitStmt.v->source.ToString());
err = (it != constructNamesAndLevels_.end() && it->second >= 0);
} else { // If there is no label then use the level of the last enclosing DO
err = level_ >= 0;
}
if (err) {
context_.Say(*source_,
"EXIT statement terminates associated loop of an OpenMP DO "
"construct"_err_en_US);
}
return true;
}
bool Pre(const parser::Statement<parser::ActionStmt> &actionstmt) {
source_ = &actionstmt.source;
return true;
}
private:
semantics::SemanticsContext &context_;
const parser::CharBlock *source_;
std::int64_t level_;
std::map<std::string, std::int64_t> constructNamesAndLevels_;
};
} // namespace
namespace Fortran::semantics {
using namespace Fortran::semantics::omp;
void OmpStructureChecker::HasInvalidDistributeNesting(
const parser::OpenMPLoopConstruct &x) {
bool violation{false};
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
if (llvm::omp::topDistributeSet.test(beginDir.v)) {
// `distribute` region has to be nested
if (!CurrentDirectiveIsNested()) {
violation = true;
} else {
// `distribute` region has to be strictly nested inside `teams`
if (!llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
violation = true;
}
}
}
if (violation) {
context_.Say(beginDir.source,
"`DISTRIBUTE` region has to be strictly nested inside `TEAMS` "
"region."_err_en_US);
}
}
void OmpStructureChecker::HasInvalidLoopBinding(
const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
auto teamsBindingChecker = [&](parser::MessageFixedText msg) {
const auto &clauseList{std::get<parser::OmpClauseList>(beginLoopDir.t)};
for (const auto &clause : clauseList.v) {
if (const auto *bindClause{
std::get_if<parser::OmpClause::Bind>(&clause.u)}) {
if (bindClause->v.v != parser::OmpBindClause::Binding::Teams) {
context_.Say(beginDir.source, msg);
}
}
}
};
if (llvm::omp::Directive::OMPD_loop == beginDir.v &&
CurrentDirectiveIsNested() &&
llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
teamsBindingChecker(
"`BIND(TEAMS)` must be specified since the `LOOP` region is "
"strictly nested inside a `TEAMS` region."_err_en_US);
}
if (OmpDirectiveSet{
llvm::omp::OMPD_teams_loop, llvm::omp::OMPD_target_teams_loop}
.test(beginDir.v)) {
teamsBindingChecker(
"`BIND(TEAMS)` must be specified since the `LOOP` directive is "
"combined with a `TEAMS` construct."_err_en_US);
}
}
void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
// Check the following:
// The only OpenMP constructs that can be encountered during execution of
// a simd region are the `atomic` construct, the `loop` construct, the `simd`
// construct and the `ordered` construct with the `simd` clause.
// Check if the parent context has the SIMD clause
// Please note that we use GetContext() instead of GetContextParent()
// because PushContextAndClauseSets() has not been called on the
// current context yet.
// TODO: Check for declare simd regions.
bool eligibleSIMD{false};
common::visit(
common::visitors{
// Allow `!$OMP ORDERED SIMD`
[&](const parser::OpenMPBlockConstruct &c) {
const auto &beginBlockDir{
std::get<parser::OmpBeginBlockDirective>(c.t)};
const auto &beginDir{
std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
if (beginDir.v == llvm::omp::Directive::OMPD_ordered) {
const auto &clauses{
std::get<parser::OmpClauseList>(beginBlockDir.t)};
for (const auto &clause : clauses.v) {
if (std::get_if<parser::OmpClause::Simd>(&clause.u)) {
eligibleSIMD = true;
break;
}
}
}
},
[&](const parser::OpenMPStandaloneConstruct &c) {
if (auto *ssc{std::get_if<parser::OpenMPSimpleStandaloneConstruct>(
&c.u)}) {
llvm::omp::Directive dirId{ssc->v.DirId()};
if (dirId == llvm::omp::Directive::OMPD_ordered) {
for (const parser::OmpClause &x : ssc->v.Clauses().v) {
if (x.Id() == llvm::omp::Clause::OMPC_simd) {
eligibleSIMD = true;
break;
}
}
} else if (dirId == llvm::omp::Directive::OMPD_scan) {
eligibleSIMD = true;
}
}
},
// Allowing SIMD and loop construct
[&](const parser::OpenMPLoopConstruct &c) {
const auto &beginLoopDir{
std::get<parser::OmpBeginLoopDirective>(c.t)};
const auto &beginDir{
std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
if ((beginDir.v == llvm::omp::Directive::OMPD_simd) ||
(beginDir.v == llvm::omp::Directive::OMPD_do_simd) ||
(beginDir.v == llvm::omp::Directive::OMPD_loop)) {
eligibleSIMD = true;
}
},
[&](const parser::OpenMPAtomicConstruct &c) {
// Allow `!$OMP ATOMIC`
eligibleSIMD = true;
},
[&](const auto &c) {},
},
c.u);
if (!eligibleSIMD) {
context_.Say(parser::FindSourceLocation(c),
"The only OpenMP constructs that can be encountered during execution "
"of a 'SIMD' region are the `ATOMIC` construct, the `LOOP` construct, "
"the `SIMD` construct, the `SCAN` construct and the `ORDERED` "
"construct with the `SIMD` clause."_err_en_US);
}
}
void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
loopStack_.push_back(&x);
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
PushContextAndClauseSets(beginDir.source, beginDir.v);
// check matching, End directive is optional
if (const auto &endLoopDir{
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t)}) {
const auto &endDir{
std::get<parser::OmpLoopDirective>(endLoopDir.value().t)};
CheckMatching<parser::OmpLoopDirective>(beginDir, endDir);
AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endLoopDir->t));
}
if (llvm::omp::allSimdSet.test(GetContext().directive)) {
EnterDirectiveNest(SIMDNest);
}
// Combined target loop constructs are target device constructs. Keep track of
// whether any such construct has been visited to later check that REQUIRES
// directives for target-related options don't appear after them.
if (llvm::omp::allTargetSet.test(beginDir.v)) {
deviceConstructFound_ = true;
}
if (beginDir.v == llvm::omp::Directive::OMPD_do) {
// 2.7.1 do-clause -> private-clause |
// firstprivate-clause |
// lastprivate-clause |
// linear-clause |
// reduction-clause |
// schedule-clause |
// collapse-clause |
// ordered-clause
// nesting check
HasInvalidWorksharingNesting(
beginDir.source, llvm::omp::nestedWorkshareErrSet);
}
SetLoopInfo(x);
auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
if (optLoopCons.has_value()) {
if (const auto &doConstruct{
std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginDir.v, beginDir.source);
}
}
CheckLoopItrVariableIsInt(x);
CheckAssociatedLoopConstraints(x);
HasInvalidDistributeNesting(x);
HasInvalidLoopBinding(x);
if (CurrentDirectiveIsNested() &&
llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
HasInvalidTeamsNesting(beginDir.v, beginDir.source);
}
if ((beginDir.v == llvm::omp::Directive::OMPD_distribute_parallel_do_simd) ||
(beginDir.v == llvm::omp::Directive::OMPD_distribute_simd)) {
CheckDistLinear(x);
}
}
const parser::Name OmpStructureChecker::GetLoopIndex(
const parser::DoConstruct *x) {
using Bounds = parser::LoopControl::Bounds;
return std::get<Bounds>(x->GetLoopControl()->u).name.thing;
}
void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
if (optLoopCons.has_value()) {
if (const auto &loopConstruct{
std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
const parser::DoConstruct *loop{&*loopConstruct};
if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
SetLoopIv(itrVal.symbol);
}
}
}
}
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
if (optLoopCons.has_value()) {
if (const auto &loopConstruct{
std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
if (itrVal.symbol) {
const auto *type{itrVal.symbol->GetType()};
if (!type->IsNumeric(TypeCategory::Integer)) {
context_.Say(itrVal.source,
"The DO loop iteration"
" variable must be of the type integer."_err_en_US,
itrVal.ToString());
}
}
}
// Get the next DoConstruct if block is not empty.
const auto &block{std::get<parser::Block>(loop->t)};
const auto it{block.begin()};
loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
: nullptr;
}
}
}
}
std::int64_t OmpStructureChecker::GetOrdCollapseLevel(
const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &clauseList{std::get<parser::OmpClauseList>(beginLoopDir.t)};
std::int64_t orderedCollapseLevel{1};
std::int64_t orderedLevel{1};
std::int64_t collapseLevel{1};
for (const auto &clause : clauseList.v) {
if (const auto *collapseClause{
std::get_if<parser::OmpClause::Collapse>(&clause.u)}) {
if (const auto v{GetIntValue(collapseClause->v)}) {
collapseLevel = *v;
}
}
if (const auto *orderedClause{
std::get_if<parser::OmpClause::Ordered>(&clause.u)}) {
if (const auto v{GetIntValue(orderedClause->v)}) {
orderedLevel = *v;
}
}
}
if (orderedLevel >= collapseLevel) {
orderedCollapseLevel = orderedLevel;
} else {
orderedCollapseLevel = collapseLevel;
}
return orderedCollapseLevel;
}
void OmpStructureChecker::CheckAssociatedLoopConstraints(
const parser::OpenMPLoopConstruct &x) {
std::int64_t ordCollapseLevel{GetOrdCollapseLevel(x)};
AssociatedLoopChecker checker{context_, ordCollapseLevel};
parser::Walk(x, checker);
}
void OmpStructureChecker::CheckDistLinear(
const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &clauses{std::get<parser::OmpClauseList>(beginLoopDir.t)};
SymbolSourceMap indexVars;
// Collect symbols of all the variables from linear clauses
for (auto &clause : clauses.v) {
if (auto *linearClause{std::get_if<parser::OmpClause::Linear>(&clause.u)}) {
auto &objects{std::get<parser::OmpObjectList>(linearClause->v.t)};
GetSymbolsInObjectList(objects, indexVars);
}
}
if (!indexVars.empty()) {
// Get collapse level, if given, to find which loops are "associated."
std::int64_t collapseVal{GetOrdCollapseLevel(x)};
// Include the top loop if no collapse is specified
if (collapseVal == 0) {
collapseVal = 1;
}
// Match the loop index variables with the collected symbols from linear
// clauses.
auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
if (optLoopCons.has_value()) {
if (const auto &loopConstruct{
std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
if (itrVal.symbol) {
// Remove the symbol from the collected set
indexVars.erase(&itrVal.symbol->GetUltimate());
}
collapseVal--;
if (collapseVal == 0) {
break;
}
}
// Get the next DoConstruct if block is not empty.
const auto &block{std::get<parser::Block>(loop->t)};
const auto it{block.begin()};
loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
: nullptr;
}
}
}
// Show error for the remaining variables
for (auto &[symbol, source] : indexVars) {
const Symbol &root{GetAssociationRoot(*symbol)};
context_.Say(source,
"Variable '%s' not allowed in LINEAR clause, only loop iterator can be specified in LINEAR clause of a construct combined with DISTRIBUTE"_err_en_US,
root.name());
}
}
}
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &clauseList{std::get<parser::OmpClauseList>(beginLoopDir.t)};
// A few semantic checks for InScan reduction are performed below as SCAN
// constructs inside LOOP may add the relevant information. Scan reduction is
// supported only in loop constructs, so same checks are not applicable to
// other directives.
using ReductionModifier = parser::OmpReductionModifier;
for (const auto &clause : clauseList.v) {
if (const auto *reductionClause{
std::get_if<parser::OmpClause::Reduction>(&clause.u)}) {
auto &modifiers{OmpGetModifiers(reductionClause->v)};
auto *maybeModifier{OmpGetUniqueModifier<ReductionModifier>(modifiers)};
if (maybeModifier &&
maybeModifier->v == ReductionModifier::Value::Inscan) {
const auto &objectList{
std::get<parser::OmpObjectList>(reductionClause->v.t)};
auto checkReductionSymbolInScan = [&](const parser::Name *name) {
if (auto &symbol = name->symbol) {
if (!symbol->test(Symbol::Flag::OmpInclusiveScan) &&
!symbol->test(Symbol::Flag::OmpExclusiveScan)) {
context_.Say(name->source,
"List item %s must appear in EXCLUSIVE or "
"INCLUSIVE clause of an "
"enclosed SCAN directive"_err_en_US,
name->ToString());
}
}
};
for (const auto &ompObj : objectList.v) {
common::visit(
common::visitors{
[&](const parser::Designator &designator) {
if (const auto *name{semantics::getDesignatorNameIfDataRef(
designator)}) {
checkReductionSymbolInScan(name);
}
},
[&](const auto &name) { checkReductionSymbolInScan(&name); },
},
ompObj.u);
}
}
}
}
if (llvm::omp::allSimdSet.test(GetContext().directive)) {
ExitDirectiveNest(SIMDNest);
}
dirContext_.pop_back();
assert(!loopStack_.empty() && "Expecting non-empty loop stack");
#ifndef NDEBUG
const LoopConstruct &top{loopStack_.back()};
auto *loopc{std::get_if<const parser::OpenMPLoopConstruct *>(&top)};
assert(loopc != nullptr && *loopc == &x && "Mismatched loop constructs");
#endif
loopStack_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpEndLoopDirective &x) {
const auto &dir{std::get<parser::OmpLoopDirective>(x.t)};
ResetPartialContext(dir.source);
switch (dir.v) {
// 2.7.1 end-do -> END DO [nowait-clause]
// 2.8.3 end-do-simd -> END DO SIMD [nowait-clause]
case llvm::omp::Directive::OMPD_do:
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_end_do);
break;
case llvm::omp::Directive::OMPD_do_simd:
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_end_do_simd);
break;
default:
// no clauses are allowed
break;
}
}
void OmpStructureChecker::Leave(const parser::OmpEndLoopDirective &x) {
if ((GetContext().directive == llvm::omp::Directive::OMPD_end_do) ||
(GetContext().directive == llvm::omp::Directive::OMPD_end_do_simd)) {
dirContext_.pop_back();
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Linear &x) {
CheckAllowedClause(llvm::omp::Clause::OMPC_linear);
unsigned version{context_.langOptions().OpenMPVersion};
llvm::omp::Directive dir{GetContext().directive};
parser::CharBlock clauseSource{GetContext().clauseSource};
const parser::OmpLinearModifier *linearMod{nullptr};
SymbolSourceMap symbols;
auto &objects{std::get<parser::OmpObjectList>(x.v.t)};
CheckCrayPointee(objects, "LINEAR", false);
GetSymbolsInObjectList(objects, symbols);
auto CheckIntegerNoRef{[&](const Symbol *symbol, parser::CharBlock source) {
if (!symbol->GetType()->IsNumeric(TypeCategory::Integer)) {
auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()};
context_.Say(source,
"The list item '%s' specified without the REF '%s' must be of INTEGER type"_err_en_US,
symbol->name(), desc.name.str());
}
}};
if (OmpVerifyModifiers(x.v, llvm::omp::OMPC_linear, clauseSource, context_)) {
auto &modifiers{OmpGetModifiers(x.v)};
linearMod = OmpGetUniqueModifier<parser::OmpLinearModifier>(modifiers);
if (linearMod) {
// 2.7 Loop Construct Restriction
if ((llvm::omp::allDoSet | llvm::omp::allSimdSet).test(dir)) {
context_.Say(clauseSource,
"A modifier may not be specified in a LINEAR clause on the %s directive"_err_en_US,
ContextDirectiveAsFortran());
return;
}
auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()};
for (auto &[symbol, source] : symbols) {
if (linearMod->v != parser::OmpLinearModifier::Value::Ref) {
CheckIntegerNoRef(symbol, source);
} else {
if (!IsAllocatable(*symbol) && !IsAssumedShape(*symbol) &&
!IsPolymorphic(*symbol)) {
context_.Say(source,
"The list item `%s` specified with the REF '%s' must be polymorphic variable, assumed-shape array, or a variable with the `ALLOCATABLE` attribute"_err_en_US,
symbol->name(), desc.name.str());
}
}
if (linearMod->v == parser::OmpLinearModifier::Value::Ref ||
linearMod->v == parser::OmpLinearModifier::Value::Uval) {
if (!IsDummy(*symbol) || IsValue(*symbol)) {
context_.Say(source,
"If the `%s` is REF or UVAL, the list item '%s' must be a dummy argument without the VALUE attribute"_err_en_US,
desc.name.str(), symbol->name());
}
}
} // for (symbol, source)
if (version >= 52 && !std::get</*PostModified=*/bool>(x.v.t)) {
context_.Say(OmpGetModifierSource(modifiers, linearMod),
"The 'modifier(<list>)' syntax is deprecated in %s, use '<list> : modifier' instead"_warn_en_US,
ThisVersion(version));
}
}
}
// OpenMP 5.2: Ordered clause restriction
if (const auto *clause{
FindClause(GetContext(), llvm::omp::Clause::OMPC_ordered)}) {
const auto &orderedClause{std::get<parser::OmpClause::Ordered>(clause->u)};
if (orderedClause.v) {
return;
}
}
// OpenMP 5.2: Linear clause Restrictions
for (auto &[symbol, source] : symbols) {
if (!linearMod) {
// Already checked this with the modifier present.
CheckIntegerNoRef(symbol, source);
}
if (dir == llvm::omp::Directive::OMPD_declare_simd && !IsDummy(*symbol)) {
context_.Say(source,
"The list item `%s` must be a dummy argument"_err_en_US,
symbol->name());
}
if (IsPointer(*symbol) || symbol->test(Symbol::Flag::CrayPointer)) {
context_.Say(source,
"The list item `%s` in a LINEAR clause must not be Cray Pointer or a variable with POINTER attribute"_err_en_US,
symbol->name());
}
if (FindCommonBlockContaining(*symbol)) {
context_.Say(source,
"'%s' is a common block name and must not appear in an LINEAR clause"_err_en_US,
symbol->name());
}
}
}
void OmpStructureChecker::Enter(const parser::DoConstruct &x) {
Base::Enter(x);
loopStack_.push_back(&x);
}
void OmpStructureChecker::Leave(const parser::DoConstruct &x) {
assert(!loopStack_.empty() && "Expecting non-empty loop stack");
#ifndef NDEBUG
const LoopConstruct &top = loopStack_.back();
auto *doc{std::get_if<const parser::DoConstruct *>(&top)};
assert(doc != nullptr && *doc == &x && "Mismatched loop constructs");
#endif
loopStack_.pop_back();
Base::Leave(x);
}
} // namespace Fortran::semantics

View File

@@ -0,0 +1,548 @@
//===-- lib/Semantics/check-omp-metadirective.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 for METADIRECTIVE and related constructs/clauses.
//
//===----------------------------------------------------------------------===//
#include "check-omp-structure.h"
#include "openmp-utils.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "flang/Common/visit.h"
#include "flang/Parser/characters.h"
#include "flang/Parser/message.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/openmp-modifiers.h"
#include "flang/Semantics/tools.h"
#include "llvm/Frontend/OpenMP/OMP.h"
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
namespace Fortran::semantics {
using namespace Fortran::semantics::omp;
void OmpStructureChecker::Enter(const parser::OmpClause::When &x) {
CheckAllowedClause(llvm::omp::Clause::OMPC_when);
OmpVerifyModifiers(
x.v, llvm::omp::OMPC_when, GetContext().clauseSource, context_);
}
void OmpStructureChecker::Enter(const parser::OmpContextSelector &ctx) {
EnterDirectiveNest(ContextSelectorNest);
using SetName = parser::OmpTraitSetSelectorName;
std::map<SetName::Value, const SetName *> visited;
for (const parser::OmpTraitSetSelector &traitSet : ctx.v) {
auto &name{std::get<SetName>(traitSet.t)};
auto [prev, unique]{visited.insert(std::make_pair(name.v, &name))};
if (!unique) {
std::string showName{parser::ToUpperCaseLetters(name.ToString())};
parser::MessageFormattedText txt(
"Repeated trait set name %s in a context specifier"_err_en_US,
showName);
parser::Message message(name.source, txt);
message.Attach(prev->second->source,
"Previous trait set %s provided here"_en_US, showName);
context_.Say(std::move(message));
}
CheckTraitSetSelector(traitSet);
}
}
void OmpStructureChecker::Leave(const parser::OmpContextSelector &) {
ExitDirectiveNest(ContextSelectorNest);
}
const std::list<parser::OmpTraitProperty> &
OmpStructureChecker::GetTraitPropertyList(
const parser::OmpTraitSelector &trait) {
static const std::list<parser::OmpTraitProperty> empty{};
auto &[_, maybeProps]{trait.t};
if (maybeProps) {
using PropertyList = std::list<parser::OmpTraitProperty>;
return std::get<PropertyList>(maybeProps->t);
} else {
return empty;
}
}
std::optional<llvm::omp::Clause> OmpStructureChecker::GetClauseFromProperty(
const parser::OmpTraitProperty &property) {
using MaybeClause = std::optional<llvm::omp::Clause>;
// The parser for OmpClause will only succeed if the clause was
// given with all required arguments.
// If this is a string or complex extension with a clause name,
// treat it as a clause and let the trait checker deal with it.
auto getClauseFromString{[&](const std::string &s) -> MaybeClause {
auto id{llvm::omp::getOpenMPClauseKind(parser::ToLowerCaseLetters(s))};
if (id != llvm::omp::Clause::OMPC_unknown) {
return id;
} else {
return std::nullopt;
}
}};
return common::visit( //
common::visitors{
[&](const parser::OmpTraitPropertyName &x) -> MaybeClause {
return getClauseFromString(x.v);
},
[&](const common::Indirection<parser::OmpClause> &x) -> MaybeClause {
return x.value().Id();
},
[&](const parser::ScalarExpr &x) -> MaybeClause {
return std::nullopt;
},
[&](const parser::OmpTraitPropertyExtension &x) -> MaybeClause {
using ExtProperty = parser::OmpTraitPropertyExtension;
if (auto *name{std::get_if<parser::OmpTraitPropertyName>(&x.u)}) {
return getClauseFromString(name->v);
} else if (auto *cpx{std::get_if<ExtProperty::Complex>(&x.u)}) {
return getClauseFromString(
std::get<parser::OmpTraitPropertyName>(cpx->t).v);
}
return std::nullopt;
},
},
property.u);
}
void OmpStructureChecker::CheckTraitSelectorList(
const std::list<parser::OmpTraitSelector> &traits) {
// [6.0:322:20]
// Each trait-selector-name may only be specified once in a trait selector
// set.
// Cannot store OmpTraitSelectorName directly, because it's not copyable.
using TraitName = parser::OmpTraitSelectorName;
using BareName = decltype(TraitName::u);
std::map<BareName, const TraitName *> visited;
for (const parser::OmpTraitSelector &trait : traits) {
auto &name{std::get<TraitName>(trait.t)};
auto [prev, unique]{visited.insert(std::make_pair(name.u, &name))};
if (!unique) {
std::string showName{parser::ToUpperCaseLetters(name.ToString())};
parser::MessageFormattedText txt(
"Repeated trait name %s in a trait set"_err_en_US, showName);
parser::Message message(name.source, txt);
message.Attach(prev->second->source,
"Previous trait %s provided here"_en_US, showName);
context_.Say(std::move(message));
}
}
}
void OmpStructureChecker::CheckTraitSetSelector(
const parser::OmpTraitSetSelector &traitSet) {
// Trait Set | Allowed traits | D-traits | X-traits | Score |
//
// Construct | Simd, directive-name | Yes | No | No |
// Device | Arch, Isa, Kind | No | Yes | No |
// Implementation | Atomic_Default_Mem_Order | No | Yes | Yes |
// | Extension, Requires | | | |
// | Vendor | | | |
// Target_Device | Arch, Device_Num, Isa | No | Yes | No |
// | Kind, Uid | | | |
// User | Condition | No | No | Yes |
struct TraitSetConfig {
std::set<parser::OmpTraitSelectorName::Value> allowed;
bool allowsDirectiveTraits;
bool allowsExtensionTraits;
bool allowsScore;
};
using SName = parser::OmpTraitSetSelectorName::Value;
using TName = parser::OmpTraitSelectorName::Value;
static const std::map<SName, TraitSetConfig> configs{
{SName::Construct, //
{{TName::Simd}, true, false, false}},
{SName::Device, //
{{TName::Arch, TName::Isa, TName::Kind}, false, true, false}},
{SName::Implementation, //
{{TName::Atomic_Default_Mem_Order, TName::Extension, TName::Requires,
TName::Vendor},
false, true, true}},
{SName::Target_Device, //
{{TName::Arch, TName::Device_Num, TName::Isa, TName::Kind,
TName::Uid},
false, true, false}},
{SName::User, //
{{TName::Condition}, false, false, true}},
};
auto checkTraitSet{[&](const TraitSetConfig &config) {
auto &[setName, traits]{traitSet.t};
auto usn{parser::ToUpperCaseLetters(setName.ToString())};
// Check if there are any duplicate traits.
CheckTraitSelectorList(traits);
for (const parser::OmpTraitSelector &trait : traits) {
// Don't use structured bindings here, because they cannot be captured
// before C++20.
auto &traitName = std::get<parser::OmpTraitSelectorName>(trait.t);
auto &maybeProps =
std::get<std::optional<parser::OmpTraitSelector::Properties>>(
trait.t);
// Check allowed traits
common::visit( //
common::visitors{
[&](parser::OmpTraitSelectorName::Value v) {
if (!config.allowed.count(v)) {
context_.Say(traitName.source,
"%s is not a valid trait for %s trait set"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()), usn);
}
},
[&](llvm::omp::Directive) {
if (!config.allowsDirectiveTraits) {
context_.Say(traitName.source,
"Directive name is not a valid trait for %s trait set"_err_en_US,
usn);
}
},
[&](const std::string &) {
if (!config.allowsExtensionTraits) {
context_.Say(traitName.source,
"Extension traits are not valid for %s trait set"_err_en_US,
usn);
}
},
},
traitName.u);
// Check score
if (maybeProps) {
auto &[maybeScore, _]{maybeProps->t};
if (maybeScore) {
CheckTraitScore(*maybeScore);
}
}
// Check the properties of the individual traits
CheckTraitSelector(traitSet, trait);
}
}};
checkTraitSet(
configs.at(std::get<parser::OmpTraitSetSelectorName>(traitSet.t).v));
}
void OmpStructureChecker::CheckTraitScore(const parser::OmpTraitScore &score) {
// [6.0:322:23]
// A score-expression must be a non-negative constant integer expression.
if (auto value{GetIntValue(score)}; !value || value < 0) {
context_.Say(score.source,
"SCORE expression must be a non-negative constant integer expression"_err_en_US);
}
}
bool OmpStructureChecker::VerifyTraitPropertyLists(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
using TraitName = parser::OmpTraitSelectorName;
using PropertyList = std::list<parser::OmpTraitProperty>;
auto &[traitName, maybeProps]{trait.t};
auto checkPropertyList{[&](const PropertyList &properties, auto isValid,
const std::string &message) {
bool foundInvalid{false};
for (const parser::OmpTraitProperty &prop : properties) {
if (!isValid(prop)) {
if (foundInvalid) {
context_.Say(
prop.source, "More invalid properties are present"_err_en_US);
break;
}
context_.Say(prop.source, "%s"_err_en_US, message);
foundInvalid = true;
}
}
return !foundInvalid;
}};
bool invalid{false};
if (std::holds_alternative<llvm::omp::Directive>(traitName.u)) {
// Directive-name traits don't have properties.
if (maybeProps) {
context_.Say(trait.source,
"Directive-name traits cannot have properties"_err_en_US);
invalid = true;
}
}
// Ignore properties on extension traits.
// See `TraitSelectorParser` in openmp-parser.cpp
if (auto *v{std::get_if<TraitName::Value>(&traitName.u)}) {
switch (*v) {
// name-list properties
case parser::OmpTraitSelectorName::Value::Arch:
case parser::OmpTraitSelectorName::Value::Extension:
case parser::OmpTraitSelectorName::Value::Isa:
case parser::OmpTraitSelectorName::Value::Kind:
case parser::OmpTraitSelectorName::Value::Uid:
case parser::OmpTraitSelectorName::Value::Vendor:
if (maybeProps) {
auto isName{[](const parser::OmpTraitProperty &prop) {
return std::holds_alternative<parser::OmpTraitPropertyName>(prop.u);
}};
invalid = !checkPropertyList(std::get<PropertyList>(maybeProps->t),
isName, "Trait property should be a name");
}
break;
// clause-list
case parser::OmpTraitSelectorName::Value::Atomic_Default_Mem_Order:
case parser::OmpTraitSelectorName::Value::Requires:
case parser::OmpTraitSelectorName::Value::Simd:
if (maybeProps) {
auto isClause{[&](const parser::OmpTraitProperty &prop) {
return GetClauseFromProperty(prop).has_value();
}};
invalid = !checkPropertyList(std::get<PropertyList>(maybeProps->t),
isClause, "Trait property should be a clause");
}
break;
// expr-list
case parser::OmpTraitSelectorName::Value::Condition:
case parser::OmpTraitSelectorName::Value::Device_Num:
if (maybeProps) {
auto isExpr{[](const parser::OmpTraitProperty &prop) {
return std::holds_alternative<parser::ScalarExpr>(prop.u);
}};
invalid = !checkPropertyList(std::get<PropertyList>(maybeProps->t),
isExpr, "Trait property should be a scalar expression");
}
break;
} // switch
}
return !invalid;
}
void OmpStructureChecker::CheckTraitSelector(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
using TraitName = parser::OmpTraitSelectorName;
auto &[traitName, maybeProps]{trait.t};
// Only do the detailed checks if the property lists are valid.
if (VerifyTraitPropertyLists(traitSet, trait)) {
if (std::holds_alternative<llvm::omp::Directive>(traitName.u) ||
std::holds_alternative<std::string>(traitName.u)) {
// No properties here: directives don't have properties, and
// we don't implement any extension traits now.
return;
}
// Specific traits we want to check.
// Limitations:
// (1) The properties for these traits are defined in "Additional
// Definitions for the OpenMP API Specification". It's not clear how
// to define them in a portable way, and how to verify their validity,
// especially if they get replaced by their integer values (in case
// they are defined as enums).
// (2) These are entirely implementation-defined, and at the moment
// there is no known schema to validate these values.
auto v{std::get<TraitName::Value>(traitName.u)};
switch (v) {
case TraitName::Value::Arch:
// Unchecked, TBD(1)
break;
case TraitName::Value::Atomic_Default_Mem_Order:
CheckTraitADMO(traitSet, trait);
break;
case TraitName::Value::Condition:
CheckTraitCondition(traitSet, trait);
break;
case TraitName::Value::Device_Num:
CheckTraitDeviceNum(traitSet, trait);
break;
case TraitName::Value::Extension:
// Ignore
break;
case TraitName::Value::Isa:
// Unchecked, TBD(1)
break;
case TraitName::Value::Kind:
// Unchecked, TBD(1)
break;
case TraitName::Value::Requires:
CheckTraitRequires(traitSet, trait);
break;
case TraitName::Value::Simd:
CheckTraitSimd(traitSet, trait);
break;
case TraitName::Value::Uid:
// Unchecked, TBD(2)
break;
case TraitName::Value::Vendor:
// Unchecked, TBD(1)
break;
}
}
}
void OmpStructureChecker::CheckTraitADMO(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
auto &traitName{std::get<parser::OmpTraitSelectorName>(trait.t)};
auto &properties{GetTraitPropertyList(trait)};
if (properties.size() != 1) {
context_.Say(trait.source,
"%s trait requires a single clause property"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
} else {
const parser::OmpTraitProperty &property{properties.front()};
auto clauseId{*GetClauseFromProperty(property)};
// Check that the clause belongs to the memory-order clause-set.
// Clause sets will hopefully be autogenerated at some point.
switch (clauseId) {
case llvm::omp::Clause::OMPC_acq_rel:
case llvm::omp::Clause::OMPC_acquire:
case llvm::omp::Clause::OMPC_relaxed:
case llvm::omp::Clause::OMPC_release:
case llvm::omp::Clause::OMPC_seq_cst:
break;
default:
context_.Say(property.source,
"%s trait requires a clause from the memory-order clause set"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
}
using ClauseProperty = common::Indirection<parser::OmpClause>;
if (!std::holds_alternative<ClauseProperty>(property.u)) {
context_.Say(property.source,
"Invalid clause specification for %s"_err_en_US,
parser::ToUpperCaseLetters(getClauseName(clauseId)));
}
}
}
void OmpStructureChecker::CheckTraitCondition(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
auto &traitName{std::get<parser::OmpTraitSelectorName>(trait.t)};
auto &properties{GetTraitPropertyList(trait)};
if (properties.size() != 1) {
context_.Say(trait.source,
"%s trait requires a single expression property"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
} else {
const parser::OmpTraitProperty &property{properties.front()};
auto &scalarExpr{std::get<parser::ScalarExpr>(property.u)};
auto maybeType{GetDynamicType(scalarExpr.thing.value())};
if (!maybeType || maybeType->category() != TypeCategory::Logical) {
context_.Say(property.source,
"%s trait requires a single LOGICAL expression"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
}
}
}
void OmpStructureChecker::CheckTraitDeviceNum(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
auto &traitName{std::get<parser::OmpTraitSelectorName>(trait.t)};
auto &properties{GetTraitPropertyList(trait)};
if (properties.size() != 1) {
context_.Say(trait.source,
"%s trait requires a single expression property"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
}
// No other checks at the moment.
}
void OmpStructureChecker::CheckTraitRequires(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
unsigned version{context_.langOptions().OpenMPVersion};
auto &traitName{std::get<parser::OmpTraitSelectorName>(trait.t)};
auto &properties{GetTraitPropertyList(trait)};
for (const parser::OmpTraitProperty &property : properties) {
auto clauseId{*GetClauseFromProperty(property)};
if (!llvm::omp::isAllowedClauseForDirective(
llvm::omp::OMPD_requires, clauseId, version)) {
context_.Say(property.source,
"%s trait requires a clause from the requirement clause set"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()));
}
using ClauseProperty = common::Indirection<parser::OmpClause>;
if (!std::holds_alternative<ClauseProperty>(property.u)) {
context_.Say(property.source,
"Invalid clause specification for %s"_err_en_US,
parser::ToUpperCaseLetters(getClauseName(clauseId)));
}
}
}
void OmpStructureChecker::CheckTraitSimd(
const parser::OmpTraitSetSelector &traitSet,
const parser::OmpTraitSelector &trait) {
unsigned version{context_.langOptions().OpenMPVersion};
auto &traitName{std::get<parser::OmpTraitSelectorName>(trait.t)};
auto &properties{GetTraitPropertyList(trait)};
for (const parser::OmpTraitProperty &property : properties) {
auto clauseId{*GetClauseFromProperty(property)};
if (!llvm::omp::isAllowedClauseForDirective(
llvm::omp::OMPD_declare_simd, clauseId, version)) {
context_.Say(property.source,
"%s trait requires a clause that is allowed on the %s directive"_err_en_US,
parser::ToUpperCaseLetters(traitName.ToString()),
parser::ToUpperCaseLetters(
getDirectiveName(llvm::omp::OMPD_declare_simd)));
}
using ClauseProperty = common::Indirection<parser::OmpClause>;
if (!std::holds_alternative<ClauseProperty>(property.u)) {
context_.Say(property.source,
"Invalid clause specification for %s"_err_en_US,
parser::ToUpperCaseLetters(getClauseName(clauseId)));
}
}
}
void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) {
EnterDirectiveNest(MetadirectiveNest);
PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_metadirective);
}
void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) {
ExitDirectiveNest(MetadirectiveNest);
dirContext_.pop_back();
}
} // namespace Fortran::semantics

File diff suppressed because it is too large Load Diff

View File

@@ -162,10 +162,6 @@ public:
private:
bool CheckAllowedClause(llvmOmpClause clause);
bool IsVariableListItem(const Symbol &sym);
bool IsExtendedListItem(const Symbol &sym);
bool IsCommonBlock(const Symbol &sym);
std::optional<bool> IsContiguous(const parser::OmpObject &object);
void CheckVariableListItem(const SymbolSourceMap &symbols);
void CheckMultipleOccurrence(semantics::UnorderedSymbolSet &listVars,
const std::list<parser::Name> &nameList, const parser::CharBlock &item,
@@ -215,10 +211,6 @@ private:
typename IterTy = decltype(std::declval<RangeTy>().begin())>
std::optional<IterTy> FindDuplicate(RangeTy &&);
const Symbol *GetObjectSymbol(const parser::OmpObject &object);
const Symbol *GetArgumentSymbol(const parser::OmpArgument &argument);
std::optional<parser::CharBlock> GetObjectSource(
const parser::OmpObject &object);
void CheckDependList(const parser::DataRef &);
void CheckDependArraySection(
const common::Indirection<parser::ArrayElement> &, const parser::Name &);

View File

@@ -0,0 +1,393 @@
//===-- 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 <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace Fortran::semantics::omp {
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<parser::Designator>(&object.u);
}
const parser::DataRef *GetDataRefFromObj(const parser::OmpObject &object) {
if (auto *desg{GetDesignatorFromObj(object)}) {
return std::get_if<parser::DataRef>(&desg->u);
}
return nullptr;
}
const parser::ArrayElement *GetArrayElementFromObj(
const parser::OmpObject &object) {
if (auto *dataRef{GetDataRefFromObj(object)}) {
using ElementIndirection = common::Indirection<parser::ArrayElement>;
if (auto *ind{std::get_if<ElementIndirection>(&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<parser::Name>(&object.u)}) {
return name->symbol ? &name->symbol->GetUltimate() : nullptr;
} else if (auto *desg{std::get_if<parser::Designator>(&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<parser::OmpLocator>(&argument.u)}) {
if (auto *object{std::get_if<parser::OmpObject>(&locator->u)}) {
return GetObjectSymbol(*object);
}
}
return nullptr;
}
std::optional<parser::CharBlock> GetObjectSource(
const parser::OmpObject &object) {
if (auto *name{std::get_if<parser::Name>(&object.u)}) {
return name->source;
} else if (auto *desg{std::get_if<parser::Designator>(&object.u)}) {
return GetLastName(*desg).source;
}
return std::nullopt;
}
bool IsCommonBlock(const Symbol &sym) {
return sym.detailsIf<CommonBlockDetails>() != 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;
}
}
std::optional<SomeExpr> GetEvaluateExpr(const parser::Expr &parserExpr) {
const parser::TypedExpr &typedExpr{parserExpr.typedExpr};
// ForwardOwningPointer typedExpr
// `- GenericExprWrapper ^.get()
// `- std::optional<Expr> ^->v
return typedExpr.get()->v;
}
std::optional<evaluate::DynamicType> 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 <typename Contained>
std::optional<bool> Visit(const common::Indirection<Contained> &x) {
return Visit(x.value());
}
template <typename Contained>
std::optional<bool> Visit(const common::Reference<Contained> &x) {
return Visit(x.get());
}
template <typename T> std::optional<bool> Visit(const evaluate::Expr<T> &x) {
return common::visit([&](auto &&s) { return Visit(s); }, x.u);
}
template <typename T>
std::optional<bool> Visit(const evaluate::Designator<T> &x) {
return common::visit(
[this](auto &&s) { return evaluate::IsContiguous(s, fctx_); }, x.u);
}
template <typename T> std::optional<bool> Visit(const T &) {
// Everything else.
return std::nullopt;
}
private:
evaluate::FoldingContext &fctx_;
};
} // namespace
// Return values:
// - std::optional<bool>{true} if the object is known to be contiguous
// - std::optional<bool>{false} if the object is known not to be contiguous
// - std::nullopt if the object contiguity cannot be determined
std::optional<bool> 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<bool>{true};
},
[&](const parser::Designator &x) {
evaluate::ExpressionAnalyzer ea{semaCtx};
if (MaybeExpr maybeExpr{ea.Analyze(x)}) {
return ContiguousHelper{semaCtx}.Visit(*maybeExpr);
}
return std::optional<bool>{};
},
},
object.u);
}
struct DesignatorCollector : public evaluate::Traverse<DesignatorCollector,
std::vector<SomeExpr>, false> {
using Result = std::vector<SomeExpr>;
using Base = evaluate::Traverse<DesignatorCollector, Result, false>;
DesignatorCollector() : Base(*this) {}
Result Default() const { return {}; }
using Base::operator();
template <typename T> //
Result operator()(const evaluate::Designator<T> &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 <typename... Rs> //
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<VariableFinder> {
using Base = evaluate::AnyTraverse<VariableFinder>;
VariableFinder(const SomeExpr &v) : Base(*this), var(v) {}
using Base::operator();
template <typename T>
bool operator()(const evaluate::Designator<T> &x) const {
auto copy{x};
return evaluate::AsGenericExpr(std::move(copy)) == var;
}
template <typename T>
bool operator()(const evaluate::FunctionRef<T> &x) const {
auto copy{x};
return evaluate::AsGenericExpr(std::move(copy)) == var;
}
private:
const SomeExpr &var;
};
std::vector<SomeExpr> 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<SomeExpr> &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<SomeExpr> 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<SomeExpr> exprs) {
evaluate::SymbolVector baseSyms{evaluate::GetSymbolVector(base)};
std::vector<SomeExpr> 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<parser::AssignmentStmt>;
using PointerAssignmentStmt =
common::Indirection<parser::PointerAssignmentStmt>;
return common::visit(
[](auto &&s) -> bool {
using BareS = llvm::remove_cvref_t<decltype(s)>;
return std::is_same_v<BareS, AssignmentStmt> ||
std::is_same_v<BareS, PointerAssignmentStmt>;
},
x->u);
}
bool IsPointerAssignment(const evaluate::Assignment &x) {
return std::holds_alternative<evaluate::Assignment::BoundsSpec>(x.u) ||
std::holds_alternative<evaluate::Assignment::BoundsRemapping>(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<parser::ExecutableConstruct>(&ep.u)}) {
using BlockConstruct = common::Indirection<parser::BlockConstruct>;
if (auto *bc{std::get_if<BlockConstruct>(&exec->u)}) {
iter = &std::get<parser::Block>(bc->value().t);
continue;
}
}
break;
}
return *iter;
}
} // namespace Fortran::semantics::omp

View File

@@ -0,0 +1,66 @@
//===-- lib/Semantics/openmp-utils.h --------------------------------------===//
//
// 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.
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_SEMANTICS_OPENMP_UTILS_H
#define FORTRAN_SEMANTICS_OPENMP_UTILS_H
#include "flang/Evaluate/type.h"
#include "flang/Parser/char-block.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/tools.h"
#include "llvm/ADT/ArrayRef.h"
#include <optional>
#include <string>
namespace Fortran::semantics {
class SemanticsContext;
class Symbol;
// Add this namespace to avoid potential conflicts
namespace omp {
std::string ThisVersion(unsigned version);
std::string TryVersion(unsigned version);
const parser::Designator *GetDesignatorFromObj(const parser::OmpObject &object);
const parser::DataRef *GetDataRefFromObj(const parser::OmpObject &object);
const parser::ArrayElement *GetArrayElementFromObj(
const parser::OmpObject &object);
const Symbol *GetObjectSymbol(const parser::OmpObject &object);
const Symbol *GetArgumentSymbol(const parser::OmpArgument &argument);
std::optional<parser::CharBlock> GetObjectSource(
const parser::OmpObject &object);
bool IsCommonBlock(const Symbol &sym);
bool IsExtendedListItem(const Symbol &sym);
bool IsVariableListItem(const Symbol &sym);
bool IsVarOrFunctionRef(const MaybeExpr &expr);
std::optional<SomeExpr> GetEvaluateExpr(const parser::Expr &parserExpr);
std::optional<evaluate::DynamicType> GetDynamicType(
const parser::Expr &parserExpr);
std::optional<bool> IsContiguous(
SemanticsContext &semaCtx, const parser::OmpObject &object);
std::vector<SomeExpr> GetAllDesignators(const SomeExpr &expr);
const SomeExpr *HasStorageOverlap(
const SomeExpr &base, llvm::ArrayRef<SomeExpr> exprs);
bool IsSubexpressionOf(const SomeExpr &sub, const SomeExpr &super);
bool IsAssignment(const parser::ActionStmt *x);
bool IsPointerAssignment(const evaluate::Assignment &x);
const parser::Block &GetInnermostExecPart(const parser::Block &block);
} // namespace omp
} // namespace Fortran::semantics
#endif // FORTRAN_SEMANTICS_OPENMP_UTILS_H