Move the check for branching into and out of an OpenMP construct from symbol resolution into semantic checks. Instead of using directive contexts to check for crossing a construct boundary, use construct pointers and source ranges.
822 lines
30 KiB
C++
822 lines
30 KiB
C++
//===-- 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 "flang/Common/idioms.h"
|
|
#include "flang/Common/visit.h"
|
|
#include "flang/Parser/char-block.h"
|
|
#include "flang/Parser/openmp-utils.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/openmp-utils.h"
|
|
#include "flang/Semantics/semantics.h"
|
|
#include "flang/Semantics/symbol.h"
|
|
#include "flang/Semantics/tools.h"
|
|
#include "flang/Semantics/type.h"
|
|
|
|
#include "llvm/ADT/BitVector.h"
|
|
#include "llvm/Frontend/OpenMP/OMP.h"
|
|
|
|
#include <cinttypes>
|
|
#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_);
|
|
}
|
|
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) {
|
|
const parser::OmpDirectiveName &beginName{x.BeginDir().DirName()};
|
|
if (llvm::omp::topDistributeSet.test(beginName.v)) {
|
|
// `distribute` region has to be nested
|
|
if (CurrentDirectiveIsNested()) {
|
|
// `distribute` region has to be strictly nested inside `teams`
|
|
if (!llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
|
|
context_.Say(beginName.source,
|
|
"`DISTRIBUTE` region has to be strictly nested inside `TEAMS` "
|
|
"region."_err_en_US);
|
|
}
|
|
} else {
|
|
// If not lexically nested (orphaned), issue a warning.
|
|
context_.Say(beginName.source,
|
|
"`DISTRIBUTE` must be dynamically enclosed in a `TEAMS` "
|
|
"region."_warn_en_US);
|
|
}
|
|
}
|
|
}
|
|
void OmpStructureChecker::HasInvalidLoopBinding(
|
|
const parser::OpenMPLoopConstruct &x) {
|
|
const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
|
|
const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
|
|
|
|
auto teamsBindingChecker = [&](parser::MessageFixedText msg) {
|
|
if (auto *clause{
|
|
parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_bind)}) {
|
|
auto &bind{std::get<parser::OmpClause::Bind>(clause->u).v};
|
|
if (bind.v != parser::OmpBindClause::Binding::Teams) {
|
|
context_.Say(beginName.source, msg);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (llvm::omp::Directive::OMPD_loop == beginName.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(beginName.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::OmpBlockConstruct &c) {
|
|
const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()};
|
|
if (beginSpec.DirId() == llvm::omp::Directive::OMPD_ordered) {
|
|
if (parser::omp::FindClause(
|
|
beginSpec, llvm::omp::Clause::OMPC_simd)) {
|
|
eligibleSIMD = true;
|
|
}
|
|
}
|
|
},
|
|
[&](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) {
|
|
if (parser::omp::FindClause(
|
|
ssc->v, llvm::omp::Clause::OMPC_simd)) {
|
|
eligibleSIMD = true;
|
|
}
|
|
} else if (dirId == llvm::omp::Directive::OMPD_scan) {
|
|
eligibleSIMD = true;
|
|
}
|
|
}
|
|
},
|
|
// Allowing SIMD and loop construct
|
|
[&](const parser::OpenMPLoopConstruct &c) {
|
|
const auto &beginName{c.BeginDir().DirName()};
|
|
if (beginName.v == llvm::omp::Directive::OMPD_simd ||
|
|
beginName.v == llvm::omp::Directive::OMPD_do_simd ||
|
|
beginName.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::omp::GetOmpDirectiveName(c).source,
|
|
"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::CheckRectangularNest(
|
|
const parser::OmpDirectiveSpecification &spec, const LoopSequence &nest) {
|
|
unsigned version{context_.langOptions().OpenMPVersion};
|
|
auto depth{GetRectangularNestDepthWithReason(spec, version)};
|
|
if (!depth || *depth.value == 0) {
|
|
return;
|
|
}
|
|
|
|
int64_t height{0};
|
|
std::vector<const LoopSequence *> outer;
|
|
for (const LoopSequence *n{&nest}; n;) {
|
|
if (n->owner()) {
|
|
WithReason<bool> rect{n->isRectangular(outer)};
|
|
if (!rect.value.value_or(true)) {
|
|
auto &msg{context_.Say(spec.DirName().source,
|
|
"This construct requires a rectangular loop nest, but the associated nest is not"_err_en_US)};
|
|
depth.reason.AttachTo(msg);
|
|
rect.reason.AttachTo(msg);
|
|
}
|
|
outer.push_back(n);
|
|
}
|
|
height += n->height().value.value_or(1);
|
|
if (height >= *depth.value) {
|
|
break;
|
|
}
|
|
n = n->children().empty() ? nullptr : &n->children().front();
|
|
}
|
|
}
|
|
|
|
void OmpStructureChecker::CheckNestedConstruct(
|
|
const parser::OpenMPLoopConstruct &x) {
|
|
const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
|
|
llvm::omp::Directive dir{beginSpec.DirId()};
|
|
unsigned version{context_.langOptions().OpenMPVersion};
|
|
parser::CharBlock beginSource{beginSpec.DirName().source};
|
|
|
|
// End-directive is not allowed in such cases:
|
|
// do 100 i = ...
|
|
// !$omp do
|
|
// do 100 j = ...
|
|
// 100 continue
|
|
// !$omp end do ! error
|
|
auto &flags{std::get<parser::OmpDirectiveSpecification::Flags>(beginSpec.t)};
|
|
if (flags.test(parser::OmpDirectiveSpecification::Flag::CrossesLabelDo)) {
|
|
if (auto &endSpec{x.EndDir()}) {
|
|
context_
|
|
.Say(endSpec->DirName().source,
|
|
"END %s directive is not allowed when the construct does not contain all loops that share a loop-terminating statement"_err_en_US,
|
|
parser::ToUpperCaseLetters(beginSource.ToString()))
|
|
.Attach(beginSource, "The construct starts here"_en_US);
|
|
}
|
|
}
|
|
|
|
// Check constructs contained in the body of the loop construct.
|
|
auto &body{std::get<parser::Block>(x.t)};
|
|
|
|
for (auto &stmt : BlockRange(body, BlockRange::Step::Over)) {
|
|
if (auto *d{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
|
|
context_.Say(d->source,
|
|
"Compiler directives are not allowed inside OpenMP loop constructs"_warn_en_US);
|
|
}
|
|
}
|
|
|
|
// The loop sequence will correspond to the nest associated with the
|
|
// loop-associated construct being visited.
|
|
LoopSequence sequence(body, version, true);
|
|
auto assoc{llvm::omp::getDirectiveAssociation(dir)};
|
|
auto needRange{GetAffectedLoopRangeWithReason(beginSpec, version)};
|
|
auto haveLength{sequence.length()};
|
|
|
|
const auto MsgShouldContainDoOr{
|
|
"This construct should contain a DO-loop or a loop-%s-generating construct"_err_en_US};
|
|
const auto MsgRequiresCanonical{
|
|
"This construct requires a canonical loop %s"_err_en_US};
|
|
|
|
if (assoc == llvm::omp::Association::LoopNest) {
|
|
if (sequence.children().size() == 0) {
|
|
context_.Say(beginSource, MsgShouldContainDoOr, "nest");
|
|
} else if (haveLength.value > 1) {
|
|
auto &msg{context_.Say(beginSource,
|
|
"This construct applies to a loop nest, but has a loop sequence of "
|
|
"length %" PRId64 ""_err_en_US,
|
|
*haveLength.value)};
|
|
haveLength.reason.AttachTo(msg);
|
|
}
|
|
auto [isWellFormed, whyNot]{sequence.isWellFormedNest()};
|
|
if (isWellFormed && !*isWellFormed) {
|
|
auto &msg{context_.Say(beginSource, MsgRequiresCanonical, "nest")};
|
|
whyNot.AttachTo(msg);
|
|
}
|
|
|
|
// Check requirements on nest depth.
|
|
auto [needDepth, needPerfect]{
|
|
GetAffectedNestDepthWithReason(beginSpec, version)};
|
|
auto &[haveSema, havePerf]{sequence.depth()};
|
|
|
|
auto haveDepth{needPerfect ? havePerf : haveSema};
|
|
std::string_view perfectTxt{needPerfect ? " perfect" : ""};
|
|
|
|
if (needDepth.value > 1 && IsDoConcurrentLegal(version)) {
|
|
if (auto *conc{sequence.getNestedDoConcurrent()}) {
|
|
auto &msg{context_.Say(*parser::GetSource(*conc->owner()),
|
|
"DO CONCURRENT must be the only affected loop in a loop nest"_err_en_US)};
|
|
needDepth.reason.AttachTo(msg);
|
|
}
|
|
}
|
|
|
|
// If the present depth is 0, it's likely that the construct doesn't
|
|
// have any loops in it, which would be diagnosed above.
|
|
if (needDepth && haveDepth.value > 0) {
|
|
if (*needDepth.value > *haveDepth.value) {
|
|
auto &msg{context_.Say(beginSource,
|
|
"This construct requires a%s nest of depth %" PRId64
|
|
", but the associated nest is a%s nest of depth %" PRId64
|
|
""_err_en_US,
|
|
perfectTxt, *needDepth.value, perfectTxt, *haveDepth.value)};
|
|
haveDepth.reason.AttachTo(msg);
|
|
needDepth.reason.AttachTo(msg);
|
|
} else {
|
|
CheckRectangularNest(beginSpec, sequence);
|
|
}
|
|
}
|
|
|
|
} else if (assoc == llvm::omp::Association::LoopSeq) {
|
|
if (haveLength.value == 0) {
|
|
context_.Say(beginSource, MsgShouldContainDoOr, "sequence");
|
|
} else {
|
|
auto [isWellFormed, whyNot]{sequence.isWellFormedSequence()};
|
|
if (isWellFormed && !*isWellFormed) {
|
|
auto &msg{context_.Say(beginSource, MsgRequiresCanonical, "sequence")};
|
|
whyNot.AttachTo(msg);
|
|
}
|
|
if (auto requiredCount{GetMinimumSequenceCount(needRange.value)}) {
|
|
if (*requiredCount > 0 && haveLength.value < *requiredCount) {
|
|
auto &msg{context_.Say(beginSource,
|
|
"This construct requires a sequence of at least %" PRId64
|
|
" loops, but the loop sequence has a length of %" PRId64
|
|
""_err_en_US,
|
|
*requiredCount, *haveLength.value)};
|
|
haveLength.reason.AttachTo(msg);
|
|
needRange.reason.AttachTo(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
|
|
const parser::OmpDirectiveName &beginName{x.BeginDir().DirName()};
|
|
PushContextAndClauseSets(beginName.source, beginName.v);
|
|
|
|
// Check matching, end directive is optional
|
|
if (auto &endSpec{x.EndDir()}) {
|
|
CheckMatching<parser::OmpDirectiveName>(beginName, endSpec->DirName());
|
|
|
|
AddEndDirectiveClauses(endSpec->Clauses());
|
|
}
|
|
|
|
if (llvm::omp::allSimdSet.test(GetContext().directive)) {
|
|
EnterDirectiveNest(SIMDNest);
|
|
}
|
|
|
|
if (CurrentDirectiveIsNested() &&
|
|
llvm::omp::topTeamsSet.test(GetContext().directive) &&
|
|
GetContextParent().directive == llvm::omp::Directive::OMPD_target &&
|
|
!GetDirectiveNest(TargetBlockOnlyTeams)) {
|
|
context_.Say(GetContextParent().directiveSource,
|
|
"TARGET construct with nested TEAMS region contains statements or "
|
|
"directives outside of the TEAMS construct"_err_en_US);
|
|
}
|
|
|
|
// 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(beginName.v)) {
|
|
deviceConstructFound_ = true;
|
|
}
|
|
|
|
if (beginName.v == llvm::omp::Directive::OMPD_do) {
|
|
// nesting check
|
|
HasInvalidWorksharingNesting(beginName, llvm::omp::nestedWorkshareErrSet);
|
|
}
|
|
SetLoopInfo(x);
|
|
|
|
for (auto &construct : std::get<parser::Block>(x.t)) {
|
|
if (const auto *doConstruct{parser::omp::GetDoConstruct(construct)}) {
|
|
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
|
|
CheckNoBranching(doBlock, beginName.v, beginName.source);
|
|
}
|
|
}
|
|
CheckIterationVariableType(x);
|
|
CheckNestedConstruct(x);
|
|
CheckAssociatedLoopConstraints(x);
|
|
HasInvalidDistributeNesting(x);
|
|
HasInvalidLoopBinding(x);
|
|
if (CurrentDirectiveIsNested() &&
|
|
llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
|
|
HasInvalidTeamsNesting(beginName.v, beginName.source);
|
|
}
|
|
if (beginName.v == llvm::omp::Directive::OMPD_distribute_parallel_do_simd ||
|
|
beginName.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) {
|
|
if (const auto *loop{x.GetNestedLoop()}) {
|
|
if (loop->IsDoNormal()) {
|
|
const parser::Name &itrVal{GetLoopIndex(loop)};
|
|
SetLoopIv(itrVal.symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OmpStructureChecker::CheckIterationVariableType(
|
|
const parser::OpenMPLoopConstruct &x) {
|
|
auto &body{std::get<parser::Block>(x.t)};
|
|
for (auto &construct : LoopRange(body, LoopRange::Step::Into)) {
|
|
// 'construct' can also be OpenMPLoopConstruct
|
|
if (auto *loop{parser::Unwrap<parser::DoConstruct>(construct)}) {
|
|
if (loop->IsDoNormal()) {
|
|
if (const parser::Name &iv{GetLoopIndex(loop)}; iv.symbol) {
|
|
const auto *type{iv.symbol->GetType()};
|
|
if (!type->IsNumeric(TypeCategory::Integer)) {
|
|
context_.Say(iv.source,
|
|
"The DO loop iteration variable must be of integer type"_err_en_US,
|
|
iv.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::int64_t OmpStructureChecker::GetOrdCollapseLevel(
|
|
const parser::OpenMPLoopConstruct &x) {
|
|
const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
|
|
std::int64_t orderedCollapseLevel{1};
|
|
std::int64_t orderedLevel{1};
|
|
std::int64_t collapseLevel{1};
|
|
|
|
for (const auto &clause : beginSpec.Clauses().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 parser::OmpClauseList &clauses{x.BeginDir().Clauses()};
|
|
|
|
SymbolSourceMap indexVars;
|
|
|
|
// Collect symbols of all the variables from linear clauses
|
|
for (auto &clause : clauses.v) {
|
|
if (std::get_if<parser::OmpClause::Linear>(&clause.u)) {
|
|
GetSymbolsInObjectList(*parser::omp::GetOmpObjectList(clause), 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.
|
|
for (auto &construct : std::get<parser::Block>(x.t)) {
|
|
std::int64_t curCollapseVal{collapseVal};
|
|
for (const parser::DoConstruct *loop{
|
|
parser::omp::GetDoConstruct(construct)};
|
|
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());
|
|
}
|
|
curCollapseVal--;
|
|
if (curCollapseVal == 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::CheckScanModifier(
|
|
const parser::OmpClause::Reduction &x) {
|
|
using ReductionModifier = parser::OmpReductionModifier;
|
|
|
|
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());
|
|
}
|
|
}
|
|
}};
|
|
|
|
auto &modifiers{OmpGetModifiers(x.v)};
|
|
auto *maybeModifier{OmpGetUniqueModifier<ReductionModifier>(modifiers)};
|
|
if (maybeModifier && maybeModifier->v == ReductionModifier::Value::Inscan) {
|
|
for (const auto &ompObj : parser::omp::GetOmpObjectList(x)->v) {
|
|
common::visit(
|
|
common::visitors{
|
|
[&](const parser::Designator &desg) {
|
|
if (auto *name{parser::GetDesignatorNameIfDataRef(desg)}) {
|
|
checkReductionSymbolInScan(*name);
|
|
}
|
|
},
|
|
[&](const parser::Name &name) {
|
|
checkReductionSymbolInScan(name);
|
|
},
|
|
[&](const parser::OmpObject::Invalid &invalid) {},
|
|
},
|
|
ompObj.u);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
|
|
const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
|
|
|
|
// 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.
|
|
for (const auto &clause : beginSpec.Clauses().v) {
|
|
if (auto *reduction{std::get_if<parser::OmpClause::Reduction>(&clause.u)}) {
|
|
CheckScanModifier(*reduction);
|
|
}
|
|
}
|
|
if (llvm::omp::allSimdSet.test(beginSpec.DirName().v)) {
|
|
ExitDirectiveNest(SIMDNest);
|
|
}
|
|
dirContext_.pop_back();
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::OmpClause::Depth &x) {
|
|
CheckAllowedClause(llvm::omp::Clause::OMPC_depth);
|
|
|
|
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_depth, x.v);
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::OmpClause::Ordered &x) {
|
|
CheckAllowedClause(llvm::omp::Clause::OMPC_ordered);
|
|
|
|
// the parameter of ordered clause is optional
|
|
if (const auto &expr{x.v}) {
|
|
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_ordered, *expr);
|
|
// 2.8.3 Loop SIMD Construct Restriction
|
|
if (llvm::omp::allDoSimdSet.test(GetContext().directive)) {
|
|
context_.Say(GetContext().clauseSource,
|
|
"No ORDERED clause with a parameter can be specified "
|
|
"on the %s directive"_err_en_US,
|
|
ContextDirectiveAsFortran());
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
CheckAssumedSizeArray(symbols, llvm::omp::Clause::OMPC_linear);
|
|
|
|
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) {
|
|
auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()};
|
|
parser::CharBlock modSource{OmpGetModifierSource(modifiers, linearMod)};
|
|
bool valid{true};
|
|
|
|
if (version < 52) {
|
|
// Modifiers on LINEAR are only allowed on DECLARE SIMD
|
|
if (dir != llvm::omp::Directive::OMPD_declare_simd) {
|
|
context_.Say(modSource,
|
|
"A modifier may not be specified in a LINEAR clause on the %s directive"_err_en_US,
|
|
parser::omp::GetUpperName(dir, version));
|
|
valid = false;
|
|
}
|
|
} else {
|
|
if (linearMod->v == parser::OmpLinearModifier::Value::Ref ||
|
|
linearMod->v == parser::OmpLinearModifier::Value::Uval) {
|
|
if (dir != llvm::omp::Directive::OMPD_declare_simd) {
|
|
context_.Say(modSource,
|
|
"A REF or UVAL '%s' may not be specified in a LINEAR clause on the %s directive"_err_en_US,
|
|
desc.name.str(), parser::omp::GetUpperName(dir, version));
|
|
valid = false;
|
|
}
|
|
}
|
|
if (!std::get</*PostModified=*/bool>(x.v.t)) {
|
|
context_.Say(modSource,
|
|
"The 'modifier(<list>)' syntax is deprecated in %s, use '<list> : modifier' instead"_warn_en_US,
|
|
ThisVersion(version));
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Linear clause restrictions.
|
|
for (auto &[symbol, source] : symbols) {
|
|
// Check that the list item is a scalar variable (rank 0)
|
|
// For declare simd with REF modifier, arrays are allowed
|
|
bool isArrayAllowed{dir == llvm::omp::Directive::OMPD_declare_simd &&
|
|
linearMod && linearMod->v == parser::OmpLinearModifier::Value::Ref};
|
|
if (symbol->Rank() != 0 && !isArrayAllowed) {
|
|
context_.Say(source,
|
|
"List item '%s' in LINEAR clause must be a scalar variable"_err_en_US,
|
|
symbol->name());
|
|
}
|
|
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::OmpClause::Sizes &c) {
|
|
CheckAllowedClause(llvm::omp::Clause::OMPC_sizes);
|
|
for (const parser::Cosubscript &v : c.v)
|
|
RequiresPositiveParameter(llvm::omp::Clause::OMPC_sizes, v,
|
|
/*paramName=*/"parameter", /*allowZero=*/false);
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::OmpClause::Permutation &c) {
|
|
unsigned version{context_.langOptions().OpenMPVersion};
|
|
llvm::omp::Clause clause = llvm::omp::Clause::OMPC_permutation;
|
|
CheckAllowedClause(clause);
|
|
if (c.v.size() < 2)
|
|
context_.Say(GetContext().clauseSource,
|
|
"The %s clause must have a length of at least two"_err_en_US,
|
|
parser::omp::GetUpperName(clause, version));
|
|
|
|
llvm::BitVector found(c.v.size(), false);
|
|
bool cont = true;
|
|
for (const auto &val : c.v) {
|
|
if (const auto v{GetIntValue(val)}) {
|
|
if (*v <= 0) {
|
|
cont = false;
|
|
context_.Say(GetContext().clauseSource,
|
|
"The parameter of the %s clause must be a constant positive integer expression"_err_en_US,
|
|
parser::omp::GetUpperName(clause, version));
|
|
} else if ((unsigned)*v - 1 < c.v.size()) {
|
|
found.set(*v - 1);
|
|
}
|
|
} else
|
|
cont = false;
|
|
}
|
|
|
|
if (!cont)
|
|
return;
|
|
if (!found.all()) {
|
|
context_.Say(GetContext().clauseSource,
|
|
"Every integer from 1 must appear in the %s clause"_err_en_US,
|
|
parser::omp::GetUpperName(clause, version));
|
|
}
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
|
|
CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
|
|
auto &[first, count]{x.v.t};
|
|
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
|
|
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
|
|
}
|
|
|
|
void OmpStructureChecker::Enter(const parser::DoConstruct &x) {
|
|
Base::Enter(x);
|
|
constructStack_.push_back(&x);
|
|
}
|
|
|
|
void OmpStructureChecker::Leave(const parser::DoConstruct &x) {
|
|
assert(!constructStack_.empty() && "Expecting non-empty construct stack");
|
|
#ifndef NDEBUG
|
|
const LoopOrConstruct &top = constructStack_.back();
|
|
auto *doc{std::get_if<const parser::DoConstruct *>(&top)};
|
|
assert(doc != nullptr && *doc == &x && "Mismatched constructs");
|
|
#endif
|
|
constructStack_.pop_back();
|
|
Base::Leave(x);
|
|
}
|
|
|
|
} // namespace Fortran::semantics
|