Files
clang-p2996/flang/lib/Semantics/check-omp-structure.cpp
Diana Picus 45cd405dc0 [flang] Add clang-tidy check for braces around if
Flang diverges from the llvm coding style in that it requires braces
around the bodies of if/while/etc statements, even when the body is
a single statement.

This commit adds the readability-braces-around-statements check to
flang's clang-tidy config file. Hopefully the premerge bots will pick it
up and report violations in Phabricator.

We also explicitly disable the check in the directories corresponding to
the Lower and Optimizer libraries, which rely heavily on mlir and llvm
and therefore follow their coding style. Likewise for the tools
directory.

We also fix any outstanding violations in the runtime and in
lib/Semantics.

Differential Revision: https://reviews.llvm.org/D104100
2021-06-16 09:13:53 +00:00

1888 lines
74 KiB
C++

//===-- lib/Semantics/check-omp-structure.cpp -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "check-omp-structure.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/tools.h"
#include <algorithm>
namespace Fortran::semantics {
// Use when clause falls under 'struct OmpClause' in 'parse-tree.h'.
#define CHECK_SIMPLE_CLAUSE(X, Y) \
void OmpStructureChecker::Enter(const parser::OmpClause::X &) { \
CheckAllowed(llvm::omp::Clause::Y); \
}
#define CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(X, Y) \
void OmpStructureChecker::Enter(const parser::OmpClause::X &c) { \
CheckAllowed(llvm::omp::Clause::Y); \
RequiresConstantPositiveParameter(llvm::omp::Clause::Y, c.v); \
}
#define CHECK_REQ_SCALAR_INT_CLAUSE(X, Y) \
void OmpStructureChecker::Enter(const parser::OmpClause::X &c) { \
CheckAllowed(llvm::omp::Clause::Y); \
RequiresPositiveParameter(llvm::omp::Clause::Y, c.v); \
}
// Use when clause don't falls under 'struct OmpClause' in 'parse-tree.h'.
#define CHECK_SIMPLE_PARSER_CLAUSE(X, Y) \
void OmpStructureChecker::Enter(const parser::X &) { \
CheckAllowed(llvm::omp::Y); \
}
// 'OmpWorkshareBlockChecker' is used to check the validity of the assignment
// statements and the expressions enclosed in an OpenMP Workshare construct
class OmpWorkshareBlockChecker {
public:
OmpWorkshareBlockChecker(SemanticsContext &context, parser::CharBlock source)
: context_{context}, source_{source} {}
template <typename T> bool Pre(const T &) { return true; }
template <typename T> void Post(const T &) {}
bool Pre(const parser::AssignmentStmt &assignment) {
const auto &var{std::get<parser::Variable>(assignment.t)};
const auto &expr{std::get<parser::Expr>(assignment.t)};
const auto *lhs{GetExpr(var)};
const auto *rhs{GetExpr(expr)};
Tristate isDefined{semantics::IsDefinedAssignment(
lhs->GetType(), lhs->Rank(), rhs->GetType(), rhs->Rank())};
if (isDefined == Tristate::Yes) {
context_.Say(expr.source,
"Defined assignment statement is not "
"allowed in a WORKSHARE construct"_err_en_US);
}
return true;
}
bool Pre(const parser::Expr &expr) {
if (const auto *e{GetExpr(expr)}) {
for (const Symbol &symbol : evaluate::CollectSymbols(*e)) {
const Symbol &root{GetAssociationRoot(symbol)};
if (IsFunction(root) &&
!(root.attrs().test(Attr::ELEMENTAL) ||
root.attrs().test(Attr::INTRINSIC))) {
context_.Say(expr.source,
"User defined non-ELEMENTAL function "
"'%s' is not allowed in a WORKSHARE construct"_err_en_US,
root.name());
}
}
}
return false;
}
private:
SemanticsContext &context_;
parser::CharBlock source_;
};
class OmpCycleChecker {
public:
OmpCycleChecker(SemanticsContext &context, std::int64_t cycleLevel)
: context_{context}, cycleLevel_{cycleLevel} {}
template <typename T> bool Pre(const T &) { return true; }
template <typename T> void Post(const T &) {}
bool Pre(const parser::DoConstruct &dc) {
cycleLevel_--;
const auto &labelName{std::get<0>(std::get<0>(dc.t).statement.t)};
if (labelName) {
labelNamesandLevels_.emplace(labelName.value().ToString(), cycleLevel_);
}
return true;
}
bool Pre(const parser::CycleStmt &cyclestmt) {
std::map<std::string, std::int64_t>::iterator it;
bool err{false};
if (cyclestmt.v) {
it = labelNamesandLevels_.find(cyclestmt.v->source.ToString());
err = (it != labelNamesandLevels_.end() && it->second > 0);
}
if (cycleLevel_ > 0 || err) {
context_.Say(*cycleSource_,
"CYCLE statement to non-innermost associated loop of an OpenMP DO construct"_err_en_US);
}
return true;
}
bool Pre(const parser::Statement<parser::ActionStmt> &actionstmt) {
cycleSource_ = &actionstmt.source;
return true;
}
private:
SemanticsContext &context_;
const parser::CharBlock *cycleSource_;
std::int64_t cycleLevel_;
std::map<std::string, std::int64_t> labelNamesandLevels_;
};
bool OmpStructureChecker::IsCloselyNestedRegion(const OmpDirectiveSet &set) {
// Definition of close nesting:
//
// `A region nested inside another region with no parallel region nested
// between them`
//
// Examples:
// non-parallel construct 1
// non-parallel construct 2
// parallel construct
// construct 3
// In the above example, construct 3 is NOT closely nested inside construct 1
// or 2
//
// non-parallel construct 1
// non-parallel construct 2
// construct 3
// In the above example, construct 3 is closely nested inside BOTH construct 1
// and 2
//
// Algorithm:
// Starting from the parent context, Check in a bottom-up fashion, each level
// of the context stack. If we have a match for one of the (supplied)
// violating directives, `close nesting` is satisfied. If no match is there in
// the entire stack, `close nesting` is not satisfied. If at any level, a
// `parallel` region is found, `close nesting` is not satisfied.
if (CurrentDirectiveIsNested()) {
int index = dirContext_.size() - 2;
while (index != -1) {
if (set.test(dirContext_[index].directive)) {
return true;
} else if (llvm::omp::parallelSet.test(dirContext_[index].directive)) {
return false;
}
index--;
}
}
return false;
}
bool OmpStructureChecker::HasInvalidWorksharingNesting(
const parser::CharBlock &source, const OmpDirectiveSet &set) {
// set contains all the invalid closely nested directives
// for the given directive (`source` here)
if (IsCloselyNestedRegion(set)) {
context_.Say(source,
"A worksharing region may not be closely nested inside a "
"worksharing, explicit task, taskloop, critical, ordered, atomic, or "
"master region"_err_en_US);
return true;
}
return false;
}
void OmpStructureChecker::HasInvalidDistributeNesting(
const parser::OpenMPLoopConstruct &x) {
bool violation{false};
OmpDirectiveSet distributeSet{llvm::omp::Directive::OMPD_distribute,
llvm::omp::Directive::OMPD_distribute_parallel_do,
llvm::omp::Directive::OMPD_distribute_parallel_do_simd,
llvm::omp::Directive::OMPD_distribute_parallel_for,
llvm::omp::Directive::OMPD_distribute_parallel_for_simd,
llvm::omp::Directive::OMPD_distribute_simd};
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
if (distributeSet.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::teamSet.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::HasInvalidTeamsNesting(
const llvm::omp::Directive &dir, const parser::CharBlock &source) {
OmpDirectiveSet allowedSet{llvm::omp::Directive::OMPD_parallel,
llvm::omp::Directive::OMPD_parallel_do,
llvm::omp::Directive::OMPD_parallel_do_simd,
llvm::omp::Directive::OMPD_parallel_for,
llvm::omp::Directive::OMPD_parallel_for_simd,
llvm::omp::Directive::OMPD_parallel_master,
llvm::omp::Directive::OMPD_parallel_master_taskloop,
llvm::omp::Directive::OMPD_parallel_master_taskloop_simd,
llvm::omp::Directive::OMPD_parallel_sections,
llvm::omp::Directive::OMPD_parallel_workshare,
llvm::omp::Directive::OMPD_distribute,
llvm::omp::Directive::OMPD_distribute_parallel_do,
llvm::omp::Directive::OMPD_distribute_parallel_do_simd,
llvm::omp::Directive::OMPD_distribute_parallel_for,
llvm::omp::Directive::OMPD_distribute_parallel_for_simd,
llvm::omp::Directive::OMPD_distribute_simd};
if (!allowedSet.test(dir)) {
context_.Say(source,
"Only `DISTRIBUTE` or `PARALLEL` regions are allowed to be strictly nested inside `TEAMS` region."_err_en_US);
}
}
void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
const parser::CharBlock &source, const parser::Name &name) {
if (const auto *symbol{name.symbol}) {
const auto *commonBlock{FindCommonBlockContaining(*symbol)};
const auto &scope{context_.FindScope(symbol->name())};
const Scope &containingScope{GetProgramUnitContaining(scope)};
if (!isPredefinedAllocator &&
(IsSave(*symbol) || commonBlock ||
containingScope.kind() == Scope::Kind::Module)) {
context_.Say(source,
"If list items within the ALLOCATE directive have the "
"SAVE attribute, are a common block name, or are "
"declared in the scope of a module, then only "
"predefined memory allocator parameters can be used "
"in the allocator clause"_err_en_US);
}
}
}
void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
const parser::CharBlock &source,
const parser::OmpObjectList &ompObjectList) {
for (const auto &ompObject : ompObjectList.v) {
std::visit(
common::visitors{
[&](const parser::Designator &designator) {
if (const auto *dataRef{
std::get_if<parser::DataRef>(&designator.u)}) {
if (const auto *name{std::get_if<parser::Name>(&dataRef->u)}) {
CheckPredefinedAllocatorRestriction(source, *name);
}
}
},
[&](const parser::Name &name) {
CheckPredefinedAllocatorRestriction(source, name);
},
},
ompObject.u);
}
}
void OmpStructureChecker::Enter(const parser::OpenMPConstruct &x) {
// Simd Construct with Ordered Construct Nesting check
// We cannot use CurrentDirectiveIsNested() here because
// PushContextAndClauseSets() has not been called yet, it is
// called individually for each construct. Therefore a
// dirContext_ size `1` means the current construct is nested
if (dirContext_.size() >= 1) {
if (GetSIMDNest() > 0) {
CheckSIMDNest(x);
}
}
}
void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
// 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);
}
PushContextAndClauseSets(beginDir.source, beginDir.v);
if (llvm::omp::simdSet.test(GetContext().directive)) {
EnterSIMDNest();
}
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);
if (const auto &doConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginDir.v, beginDir.source);
}
CheckDoWhile(x);
CheckLoopItrVariableIsInt(x);
CheckCycleConstraints(x);
HasInvalidDistributeNesting(x);
if (CurrentDirectiveIsNested() &&
llvm::omp::teamSet.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) {
if (const auto &loopConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
const parser::DoConstruct *loop{&*loopConstruct};
if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
SetLoopIv(itrVal.symbol);
}
}
}
void OmpStructureChecker::CheckDoWhile(const parser::OpenMPLoopConstruct &x) {
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
if (beginDir.v == llvm::omp::Directive::OMPD_do) {
if (const auto &doConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
if (doConstruct.value().IsDoWhile()) {
const auto &doStmt{std::get<parser::Statement<parser::NonLabelDoStmt>>(
doConstruct.value().t)};
context_.Say(doStmt.source,
"The DO loop cannot be a DO WHILE with DO directive."_err_en_US);
}
}
}
}
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
if (const auto &loopConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
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;
}
}
}
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.
// TODO: Expand the check to include `LOOP` construct as well when it is
// supported.
// 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};
std::visit(Fortran::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::OpenMPSimpleStandaloneConstruct &c) {
const auto &dir{
std::get<parser::OmpSimpleStandaloneDirective>(c.t)};
if (dir.v == llvm::omp::Directive::OMPD_ordered) {
const auto &clauses{std::get<parser::OmpClauseList>(c.t)};
for (const auto &clause : clauses.v) {
if (std::get_if<parser::OmpClause::Simd>(&clause.u)) {
eligibleSIMD = true;
break;
}
}
}
},
// Allowing SIMD 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)) {
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 and the `ORDERED` construct with the `SIMD` clause."_err_en_US);
}
}
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{0};
std::int64_t collapseLevel{0};
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::CheckCycleConstraints(
const parser::OpenMPLoopConstruct &x) {
std::int64_t ordCollapseLevel{GetOrdCollapseLevel(x)};
OmpCycleChecker ompCycleChecker{context_, ordCollapseLevel};
parser::Walk(x, ompCycleChecker);
}
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)};
semantics::UnorderedSymbolSet indexVars;
// Collect symbols of all the variables from linear clauses
for (const auto &clause : clauses.v) {
if (const auto *linearClause{
std::get_if<parser::OmpClause::Linear>(&clause.u)}) {
std::list<parser::Name> values;
// Get the variant type
if (std::holds_alternative<parser::OmpLinearClause::WithModifier>(
linearClause->v.u)) {
const auto &withM{
std::get<parser::OmpLinearClause::WithModifier>(linearClause->v.u)};
values = withM.names;
} else {
const auto &withOutM{std::get<parser::OmpLinearClause::WithoutModifier>(
linearClause->v.u)};
values = withOutM.names;
}
for (auto const &v : values) {
indexVars.insert(*(v.symbol));
}
}
}
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.
if (const auto &loopConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
if (itrVal.symbol) {
// Remove the symbol from the collcted set
indexVars.erase(*(itrVal.symbol));
}
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 var : indexVars) {
const Symbol &root{GetAssociationRoot(var)};
context_.Say(parser::FindSourceLocation(x),
"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 &) {
if (llvm::omp::simdSet.test(GetContext().directive)) {
ExitSIMDNest();
}
dirContext_.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:
case llvm::omp::Directive::OMPD_do_simd:
SetClauseSets(dir.v);
break;
default:
// no clauses are allowed
break;
}
}
void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
const auto &beginBlockDir{std::get<parser::OmpBeginBlockDirective>(x.t)};
const auto &endBlockDir{std::get<parser::OmpEndBlockDirective>(x.t)};
const auto &beginDir{std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
const auto &endDir{std::get<parser::OmpBlockDirective>(endBlockDir.t)};
const parser::Block &block{std::get<parser::Block>(x.t)};
CheckMatching<parser::OmpBlockDirective>(beginDir, endDir);
PushContextAndClauseSets(beginDir.source, beginDir.v);
if (CurrentDirectiveIsNested()) {
CheckIfDoOrderedClause(beginDir);
if (llvm::omp::teamSet.test(GetContextParent().directive)) {
HasInvalidTeamsNesting(beginDir.v, beginDir.source);
}
if (GetContext().directive == llvm::omp::Directive::OMPD_master) {
CheckMasterNesting(x);
}
}
CheckNoBranching(block, beginDir.v, beginDir.source);
switch (beginDir.v) {
case llvm::omp::OMPD_workshare:
case llvm::omp::OMPD_parallel_workshare:
CheckWorkshareBlockStmts(block, beginDir.source);
HasInvalidWorksharingNesting(
beginDir.source, llvm::omp::nestedWorkshareErrSet);
break;
case llvm::omp::Directive::OMPD_single:
// TODO: This check needs to be extended while implementing nesting of
// regions checks.
HasInvalidWorksharingNesting(
beginDir.source, llvm::omp::nestedWorkshareErrSet);
break;
default:
break;
}
}
void OmpStructureChecker::CheckMasterNesting(
const parser::OpenMPBlockConstruct &x) {
// A MASTER region may not be `closely nested` inside a worksharing, loop,
// task, taskloop, or atomic region.
// TODO: Expand the check to include `LOOP` construct as well when it is
// supported.
if (IsCloselyNestedRegion(llvm::omp::nestedMasterErrSet)) {
context_.Say(parser::FindSourceLocation(x),
"`MASTER` region may not be closely nested inside of `WORKSHARING`, "
"`LOOP`, `TASK`, `TASKLOOP`,"
" or `ATOMIC` region."_err_en_US);
}
}
void OmpStructureChecker::CheckIfDoOrderedClause(
const parser::OmpBlockDirective &blkDirective) {
if (blkDirective.v == llvm::omp::OMPD_ordered) {
// Loops
if (llvm::omp::doSet.test(GetContextParent().directive) &&
!FindClauseParent(llvm::omp::Clause::OMPC_ordered)) {
context_.Say(blkDirective.source,
"The ORDERED clause must be present on the loop"
" construct if any ORDERED region ever binds"
" to a loop region arising from the loop construct."_err_en_US);
}
// Other disallowed nestings, these directives do not support
// ordered clause in them, so no need to check
else if (IsCloselyNestedRegion(llvm::omp::nestedOrderedErrSet)) {
context_.Say(blkDirective.source,
"`ORDERED` region may not be closely nested inside of "
"`CRITICAL`, `ORDERED`, explicit `TASK` or `TASKLOOP` region."_err_en_US);
}
}
}
void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) {
const auto &beginSectionsDir{
std::get<parser::OmpBeginSectionsDirective>(x.t)};
const auto &endSectionsDir{std::get<parser::OmpEndSectionsDirective>(x.t)};
const auto &beginDir{
std::get<parser::OmpSectionsDirective>(beginSectionsDir.t)};
const auto &endDir{std::get<parser::OmpSectionsDirective>(endSectionsDir.t)};
CheckMatching<parser::OmpSectionsDirective>(beginDir, endDir);
PushContextAndClauseSets(beginDir.source, beginDir.v);
const auto &sectionBlocks{std::get<parser::OmpSectionBlocks>(x.t)};
for (const auto &block : sectionBlocks.v) {
CheckNoBranching(block, beginDir.v, beginDir.source);
}
HasInvalidWorksharingNesting(
beginDir.source, llvm::omp::nestedWorkshareErrSet);
}
void OmpStructureChecker::Leave(const parser::OpenMPSectionsConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpEndSectionsDirective &x) {
const auto &dir{std::get<parser::OmpSectionsDirective>(x.t)};
ResetPartialContext(dir.source);
switch (dir.v) {
// 2.7.2 end-sections -> END SECTIONS [nowait-clause]
case llvm::omp::Directive::OMPD_sections:
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_end_sections);
break;
default:
// no clauses are allowed
break;
}
}
// TODO: Verify the popping of dirContext requirement after nowait
// implementation, as there is an implicit barrier at the end of the worksharing
// constructs unless a nowait clause is specified. Only OMPD_end_sections is
// popped becuase it is pushed while entering the EndSectionsDirective.
void OmpStructureChecker::Leave(const parser::OmpEndSectionsDirective &x) {
if (GetContext().directive == llvm::omp::Directive::OMPD_end_sections) {
dirContext_.pop_back();
}
}
void OmpStructureChecker::Enter(const parser::OpenMPDeclareSimdConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_declare_simd);
}
void OmpStructureChecker::Leave(const parser::OpenMPDeclareSimdConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPDeclarativeAllocate &x) {
isPredefinedAllocator = true;
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &objectList{std::get<parser::OmpObjectList>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_allocate);
CheckIsVarPartOfAnotherVar(dir.source, objectList);
}
void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAllocate &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &objectList{std::get<parser::OmpObjectList>(x.t)};
CheckPredefinedAllocatorRestriction(dir.source, objectList);
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpClause::Allocator &x) {
CheckAllowed(llvm::omp::Clause::OMPC_allocator);
// Note: Predefined allocators are stored in ScalarExpr as numbers
// whereas custom allocators are stored as strings, so if the ScalarExpr
// actually has an int value, then it must be a predefined allocator
isPredefinedAllocator = GetIntValue(x.v).has_value();
RequiresPositiveParameter(llvm::omp::Clause::OMPC_allocator, x.v);
}
void OmpStructureChecker::Enter(const parser::OpenMPDeclareTargetConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContext(dir.source, llvm::omp::Directive::OMPD_declare_target);
const auto &spec{std::get<parser::OmpDeclareTargetSpecifier>(x.t)};
if (std::holds_alternative<parser::OmpDeclareTargetWithClause>(spec.u)) {
SetClauseSets(llvm::omp::Directive::OMPD_declare_target);
}
}
void OmpStructureChecker::Leave(const parser::OpenMPDeclareTargetConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPExecutableAllocate &x) {
isPredefinedAllocator = true;
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &objectList{std::get<std::optional<parser::OmpObjectList>>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_allocate);
if (objectList) {
CheckIsVarPartOfAnotherVar(dir.source, *objectList);
}
}
void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &objectList{std::get<std::optional<parser::OmpObjectList>>(x.t)};
if (objectList)
CheckPredefinedAllocatorRestriction(dir.source, *objectList);
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(
const parser::OpenMPSimpleStandaloneConstruct &x) {
const auto &dir{std::get<parser::OmpSimpleStandaloneDirective>(x.t)};
PushContextAndClauseSets(dir.source, dir.v);
}
void OmpStructureChecker::Leave(
const parser::OpenMPSimpleStandaloneConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPFlushConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_flush);
}
void OmpStructureChecker::Leave(const parser::OpenMPFlushConstruct &x) {
if (FindClause(llvm::omp::Clause::OMPC_acquire) ||
FindClause(llvm::omp::Clause::OMPC_release) ||
FindClause(llvm::omp::Clause::OMPC_acq_rel)) {
if (const auto &flushList{
std::get<std::optional<parser::OmpObjectList>>(x.t)}) {
context_.Say(parser::FindSourceLocation(flushList),
"If memory-order-clause is RELEASE, ACQUIRE, or ACQ_REL, list items "
"must not be specified on the FLUSH directive"_err_en_US);
}
}
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPCancelConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_cancel);
}
void OmpStructureChecker::Leave(const parser::OpenMPCancelConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPCriticalConstruct &x) {
const auto &dir{std::get<parser::OmpCriticalDirective>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_critical);
const auto &block{std::get<parser::Block>(x.t)};
CheckNoBranching(block, llvm::omp::Directive::OMPD_critical, dir.source);
}
void OmpStructureChecker::Leave(const parser::OpenMPCriticalConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(
const parser::OpenMPCancellationPointConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_cancellation_point);
}
void OmpStructureChecker::Leave(
const parser::OpenMPCancellationPointConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpEndBlockDirective &x) {
const auto &dir{std::get<parser::OmpBlockDirective>(x.t)};
ResetPartialContext(dir.source);
switch (dir.v) {
// 2.7.3 end-single-clause -> copyprivate-clause |
// nowait-clause
case llvm::omp::Directive::OMPD_single:
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_end_single);
break;
// 2.7.4 end-workshare -> END WORKSHARE [nowait-clause]
case llvm::omp::Directive::OMPD_workshare:
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_end_workshare);
break;
default:
// no clauses are allowed
break;
}
}
// TODO: Verify the popping of dirContext requirement after nowait
// implementation, as there is an implicit barrier at the end of the worksharing
// constructs unless a nowait clause is specified. Only OMPD_end_single and
// end_workshareare popped as they are pushed while entering the
// EndBlockDirective.
void OmpStructureChecker::Leave(const parser::OmpEndBlockDirective &x) {
if ((GetContext().directive == llvm::omp::Directive::OMPD_end_single) ||
(GetContext().directive == llvm::omp::Directive::OMPD_end_workshare)) {
dirContext_.pop_back();
}
}
void OmpStructureChecker::Enter(const parser::OpenMPAtomicConstruct &x) {
std::visit(
common::visitors{
[&](const auto &someAtomicConstruct) {
const auto &dir{std::get<parser::Verbatim>(someAtomicConstruct.t)};
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_atomic);
},
},
x.u);
}
void OmpStructureChecker::Leave(const parser::OpenMPAtomicConstruct &) {
dirContext_.pop_back();
}
// Clauses
// Mainly categorized as
// 1. Checks on 'OmpClauseList' from 'parse-tree.h'.
// 2. Checks on clauses which fall under 'struct OmpClause' from parse-tree.h.
// 3. Checks on clauses which are not in 'struct OmpClause' from parse-tree.h.
void OmpStructureChecker::Leave(const parser::OmpClauseList &) {
// 2.7 Loop Construct Restriction
if (llvm::omp::doSet.test(GetContext().directive)) {
if (auto *clause{FindClause(llvm::omp::Clause::OMPC_schedule)}) {
// only one schedule clause is allowed
const auto &schedClause{std::get<parser::OmpClause::Schedule>(clause->u)};
if (ScheduleModifierHasType(schedClause.v,
parser::OmpScheduleModifierType::ModType::Nonmonotonic)) {
if (FindClause(llvm::omp::Clause::OMPC_ordered)) {
context_.Say(clause->source,
"The NONMONOTONIC modifier cannot be specified "
"if an ORDERED clause is specified"_err_en_US);
}
if (ScheduleModifierHasType(schedClause.v,
parser::OmpScheduleModifierType::ModType::Monotonic)) {
context_.Say(clause->source,
"The MONOTONIC and NONMONOTONIC modifiers "
"cannot be both specified"_err_en_US);
}
}
}
if (auto *clause{FindClause(llvm::omp::Clause::OMPC_ordered)}) {
// only one ordered clause is allowed
const auto &orderedClause{
std::get<parser::OmpClause::Ordered>(clause->u)};
if (orderedClause.v) {
CheckNotAllowedIfClause(
llvm::omp::Clause::OMPC_ordered, {llvm::omp::Clause::OMPC_linear});
if (auto *clause2{FindClause(llvm::omp::Clause::OMPC_collapse)}) {
const auto &collapseClause{
std::get<parser::OmpClause::Collapse>(clause2->u)};
// ordered and collapse both have parameters
if (const auto orderedValue{GetIntValue(orderedClause.v)}) {
if (const auto collapseValue{GetIntValue(collapseClause.v)}) {
if (*orderedValue > 0 && *orderedValue < *collapseValue) {
context_.Say(clause->source,
"The parameter of the ORDERED clause must be "
"greater than or equal to "
"the parameter of the COLLAPSE clause"_err_en_US);
}
}
}
}
}
// TODO: ordered region binding check (requires nesting implementation)
}
} // doSet
// 2.8.1 Simd Construct Restriction
if (llvm::omp::simdSet.test(GetContext().directive)) {
if (auto *clause{FindClause(llvm::omp::Clause::OMPC_simdlen)}) {
if (auto *clause2{FindClause(llvm::omp::Clause::OMPC_safelen)}) {
const auto &simdlenClause{
std::get<parser::OmpClause::Simdlen>(clause->u)};
const auto &safelenClause{
std::get<parser::OmpClause::Safelen>(clause2->u)};
// simdlen and safelen both have parameters
if (const auto simdlenValue{GetIntValue(simdlenClause.v)}) {
if (const auto safelenValue{GetIntValue(safelenClause.v)}) {
if (*safelenValue > 0 && *simdlenValue > *safelenValue) {
context_.Say(clause->source,
"The parameter of the SIMDLEN clause must be less than or "
"equal to the parameter of the SAFELEN clause"_err_en_US);
}
}
}
}
}
// A list-item cannot appear in more than one aligned clause
semantics::UnorderedSymbolSet alignedVars;
auto clauseAll = FindClauses(llvm::omp::Clause::OMPC_aligned);
for (auto itr = clauseAll.first; itr != clauseAll.second; ++itr) {
const auto &alignedClause{
std::get<parser::OmpClause::Aligned>(itr->second->u)};
const auto &alignedNameList{
std::get<std::list<parser::Name>>(alignedClause.v.t)};
for (auto const &var : alignedNameList) {
if (alignedVars.count(*(var.symbol)) == 1) {
context_.Say(itr->second->source,
"List item '%s' present at multiple ALIGNED clauses"_err_en_US,
var.ToString());
break;
}
alignedVars.insert(*(var.symbol));
}
}
} // SIMD
// 2.7.3 Single Construct Restriction
if (GetContext().directive == llvm::omp::Directive::OMPD_end_single) {
CheckNotAllowedIfClause(
llvm::omp::Clause::OMPC_copyprivate, {llvm::omp::Clause::OMPC_nowait});
}
CheckRequireAtLeastOneOf();
}
void OmpStructureChecker::Enter(const parser::OmpClause &x) {
SetContextClause(x);
}
// Following clauses do not have a separate node in parse-tree.h.
CHECK_SIMPLE_CLAUSE(AcqRel, OMPC_acq_rel)
CHECK_SIMPLE_CLAUSE(Acquire, OMPC_acquire)
CHECK_SIMPLE_CLAUSE(AtomicDefaultMemOrder, OMPC_atomic_default_mem_order)
CHECK_SIMPLE_CLAUSE(Affinity, OMPC_affinity)
CHECK_SIMPLE_CLAUSE(Allocate, OMPC_allocate)
CHECK_SIMPLE_CLAUSE(Capture, OMPC_capture)
CHECK_SIMPLE_CLAUSE(Copyin, OMPC_copyin)
CHECK_SIMPLE_CLAUSE(Default, OMPC_default)
CHECK_SIMPLE_CLAUSE(Depobj, OMPC_depobj)
CHECK_SIMPLE_CLAUSE(Destroy, OMPC_destroy)
CHECK_SIMPLE_CLAUSE(Detach, OMPC_detach)
CHECK_SIMPLE_CLAUSE(Device, OMPC_device)
CHECK_SIMPLE_CLAUSE(DeviceType, OMPC_device_type)
CHECK_SIMPLE_CLAUSE(DistSchedule, OMPC_dist_schedule)
CHECK_SIMPLE_CLAUSE(DynamicAllocators, OMPC_dynamic_allocators)
CHECK_SIMPLE_CLAUSE(Exclusive, OMPC_exclusive)
CHECK_SIMPLE_CLAUSE(Final, OMPC_final)
CHECK_SIMPLE_CLAUSE(Flush, OMPC_flush)
CHECK_SIMPLE_CLAUSE(From, OMPC_from)
CHECK_SIMPLE_CLAUSE(Full, OMPC_full)
CHECK_SIMPLE_CLAUSE(Hint, OMPC_hint)
CHECK_SIMPLE_CLAUSE(InReduction, OMPC_in_reduction)
CHECK_SIMPLE_CLAUSE(Inclusive, OMPC_inclusive)
CHECK_SIMPLE_CLAUSE(Match, OMPC_match)
CHECK_SIMPLE_CLAUSE(Nontemporal, OMPC_nontemporal)
CHECK_SIMPLE_CLAUSE(Order, OMPC_order)
CHECK_SIMPLE_CLAUSE(Read, OMPC_read)
CHECK_SIMPLE_CLAUSE(ReverseOffload, OMPC_reverse_offload)
CHECK_SIMPLE_CLAUSE(Threadprivate, OMPC_threadprivate)
CHECK_SIMPLE_CLAUSE(Threads, OMPC_threads)
CHECK_SIMPLE_CLAUSE(Inbranch, OMPC_inbranch)
CHECK_SIMPLE_CLAUSE(IsDevicePtr, OMPC_is_device_ptr)
CHECK_SIMPLE_CLAUSE(Link, OMPC_link)
CHECK_SIMPLE_CLAUSE(Mergeable, OMPC_mergeable)
CHECK_SIMPLE_CLAUSE(Nogroup, OMPC_nogroup)
CHECK_SIMPLE_CLAUSE(Notinbranch, OMPC_notinbranch)
CHECK_SIMPLE_CLAUSE(Nowait, OMPC_nowait)
CHECK_SIMPLE_CLAUSE(Partial, OMPC_partial)
CHECK_SIMPLE_CLAUSE(ProcBind, OMPC_proc_bind)
CHECK_SIMPLE_CLAUSE(Release, OMPC_release)
CHECK_SIMPLE_CLAUSE(Relaxed, OMPC_relaxed)
CHECK_SIMPLE_CLAUSE(SeqCst, OMPC_seq_cst)
CHECK_SIMPLE_CLAUSE(Simd, OMPC_simd)
CHECK_SIMPLE_CLAUSE(Sizes, OMPC_sizes)
CHECK_SIMPLE_CLAUSE(TaskReduction, OMPC_task_reduction)
CHECK_SIMPLE_CLAUSE(To, OMPC_to)
CHECK_SIMPLE_CLAUSE(UnifiedAddress, OMPC_unified_address)
CHECK_SIMPLE_CLAUSE(UnifiedSharedMemory, OMPC_unified_shared_memory)
CHECK_SIMPLE_CLAUSE(Uniform, OMPC_uniform)
CHECK_SIMPLE_CLAUSE(Unknown, OMPC_unknown)
CHECK_SIMPLE_CLAUSE(Untied, OMPC_untied)
CHECK_SIMPLE_CLAUSE(UseDevicePtr, OMPC_use_device_ptr)
CHECK_SIMPLE_CLAUSE(UsesAllocators, OMPC_uses_allocators)
CHECK_SIMPLE_CLAUSE(Update, OMPC_update)
CHECK_SIMPLE_CLAUSE(UseDeviceAddr, OMPC_use_device_addr)
CHECK_SIMPLE_CLAUSE(Write, OMPC_write)
CHECK_SIMPLE_CLAUSE(Init, OMPC_init)
CHECK_SIMPLE_CLAUSE(Use, OMPC_use)
CHECK_SIMPLE_CLAUSE(Novariants, OMPC_novariants)
CHECK_SIMPLE_CLAUSE(Nocontext, OMPC_nocontext)
CHECK_SIMPLE_CLAUSE(Filter, OMPC_filter)
CHECK_REQ_SCALAR_INT_CLAUSE(Grainsize, OMPC_grainsize)
CHECK_REQ_SCALAR_INT_CLAUSE(NumTasks, OMPC_num_tasks)
CHECK_REQ_SCALAR_INT_CLAUSE(NumTeams, OMPC_num_teams)
CHECK_REQ_SCALAR_INT_CLAUSE(NumThreads, OMPC_num_threads)
CHECK_REQ_SCALAR_INT_CLAUSE(Priority, OMPC_priority)
CHECK_REQ_SCALAR_INT_CLAUSE(ThreadLimit, OMPC_thread_limit)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Collapse, OMPC_collapse)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Safelen, OMPC_safelen)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Simdlen, OMPC_simdlen)
// Restrictions specific to each clause are implemented apart from the
// generalized restrictions.
void OmpStructureChecker::Enter(const parser::OmpClause::Reduction &x) {
CheckAllowed(llvm::omp::Clause::OMPC_reduction);
if (CheckReductionOperators(x)) {
CheckReductionTypeList(x);
}
}
bool OmpStructureChecker::CheckReductionOperators(
const parser::OmpClause::Reduction &x) {
const auto &definedOp{std::get<0>(x.v.t)};
bool ok = false;
std::visit(
common::visitors{
[&](const parser::DefinedOperator &dOpr) {
const auto &intrinsicOp{
std::get<parser::DefinedOperator::IntrinsicOperator>(dOpr.u)};
ok = CheckIntrinsicOperator(intrinsicOp);
},
[&](const parser::ProcedureDesignator &procD) {
const parser::Name *name{std::get_if<parser::Name>(&procD.u)};
if (name) {
if (name->source == "max" || name->source == "min" ||
name->source == "iand" || name->source == "ior" ||
name->source == "ieor") {
ok = true;
} else {
context_.Say(GetContext().clauseSource,
"Invalid reduction identifier in REDUCTION clause."_err_en_US,
ContextDirectiveAsFortran());
}
}
},
},
definedOp.u);
return ok;
}
bool OmpStructureChecker::CheckIntrinsicOperator(
const parser::DefinedOperator::IntrinsicOperator &op) {
switch (op) {
case parser::DefinedOperator::IntrinsicOperator::Add:
case parser::DefinedOperator::IntrinsicOperator::Subtract:
case parser::DefinedOperator::IntrinsicOperator::Multiply:
case parser::DefinedOperator::IntrinsicOperator::AND:
case parser::DefinedOperator::IntrinsicOperator::OR:
case parser::DefinedOperator::IntrinsicOperator::EQV:
case parser::DefinedOperator::IntrinsicOperator::NEQV:
return true;
default:
context_.Say(GetContext().clauseSource,
"Invalid reduction operator in REDUCTION clause."_err_en_US,
ContextDirectiveAsFortran());
}
return false;
}
void OmpStructureChecker::CheckReductionTypeList(
const parser::OmpClause::Reduction &x) {
const auto &ompObjectList{std::get<parser::OmpObjectList>(x.v.t)};
CheckIntentInPointerAndDefinable(
ompObjectList, llvm::omp::Clause::OMPC_reduction);
CheckReductionArraySection(ompObjectList);
CheckMultipleAppearanceAcrossContext(ompObjectList);
}
void OmpStructureChecker::CheckIntentInPointerAndDefinable(
const parser::OmpObjectList &objectList, const llvm::omp::Clause clause) {
for (const auto &ompObject : objectList.v) {
if (const auto *name{parser::Unwrap<parser::Name>(ompObject)}) {
if (const auto *symbol{name->symbol}) {
if (IsPointer(symbol->GetUltimate()) &&
IsIntentIn(symbol->GetUltimate())) {
context_.Say(GetContext().clauseSource,
"Pointer '%s' with the INTENT(IN) attribute may not appear "
"in a %s clause"_err_en_US,
symbol->name(),
parser::ToUpperCaseLetters(getClauseName(clause).str()));
}
if (auto msg{
WhyNotModifiable(*symbol, context_.FindScope(name->source))}) {
context_.Say(GetContext().clauseSource,
"Variable '%s' on the %s clause is not definable"_err_en_US,
symbol->name(),
parser::ToUpperCaseLetters(getClauseName(clause).str()));
}
}
}
}
}
void OmpStructureChecker::CheckReductionArraySection(
const parser::OmpObjectList &ompObjectList) {
for (const auto &ompObject : ompObjectList.v) {
if (const auto *dataRef{parser::Unwrap<parser::DataRef>(ompObject)}) {
if (const auto *arrayElement{
parser::Unwrap<parser::ArrayElement>(ompObject)}) {
if (arrayElement) {
CheckArraySection(*arrayElement, GetLastName(*dataRef),
llvm::omp::Clause::OMPC_reduction);
}
}
}
}
}
void OmpStructureChecker::CheckMultipleAppearanceAcrossContext(
const parser::OmpObjectList &redObjectList) {
// TODO: Verify the assumption here that the immediately enclosing region is
// the parallel region to which the worksharing construct having reduction
// binds to.
if (auto *enclosingContext{GetEnclosingDirContext()}) {
for (auto it : enclosingContext->clauseInfo) {
llvmOmpClause type = it.first;
const auto *clause = it.second;
if (llvm::omp::privateReductionSet.test(type)) {
if (const auto *objList{GetOmpObjectList(*clause)}) {
for (const auto &ompObject : objList->v) {
if (const auto *name{parser::Unwrap<parser::Name>(ompObject)}) {
if (const auto *symbol{name->symbol}) {
for (const auto &redOmpObject : redObjectList.v) {
if (const auto *rname{
parser::Unwrap<parser::Name>(redOmpObject)}) {
if (const auto *rsymbol{rname->symbol}) {
if (rsymbol->name() == symbol->name()) {
context_.Say(GetContext().clauseSource,
"%s variable '%s' is %s in outer context must"
" be shared in the parallel regions to which any"
" of the worksharing regions arising from the "
"worksharing"
" construct bind."_err_en_US,
parser::ToUpperCaseLetters(
getClauseName(llvm::omp::Clause::OMPC_reduction)
.str()),
symbol->name(),
parser::ToUpperCaseLetters(
getClauseName(type).str()));
}
}
}
}
}
}
}
}
}
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Ordered &x) {
CheckAllowed(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::doSimdSet.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::Shared &x) {
CheckAllowed(llvm::omp::Clause::OMPC_shared);
CheckIsVarPartOfAnotherVar(GetContext().clauseSource, x.v);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Private &x) {
CheckAllowed(llvm::omp::Clause::OMPC_private);
CheckIsVarPartOfAnotherVar(GetContext().clauseSource, x.v);
CheckIntentInPointer(x.v, llvm::omp::Clause::OMPC_private);
}
bool OmpStructureChecker::IsDataRefTypeParamInquiry(
const parser::DataRef *dataRef) {
bool dataRefIsTypeParamInquiry{false};
if (const auto *structComp{
parser::Unwrap<parser::StructureComponent>(dataRef)}) {
if (const auto *compSymbol{structComp->component.symbol}) {
if (const auto *compSymbolMiscDetails{
std::get_if<MiscDetails>(&compSymbol->details())}) {
const auto detailsKind = compSymbolMiscDetails->kind();
dataRefIsTypeParamInquiry =
(detailsKind == MiscDetails::Kind::KindParamInquiry ||
detailsKind == MiscDetails::Kind::LenParamInquiry);
} else if (compSymbol->has<TypeParamDetails>()) {
dataRefIsTypeParamInquiry = true;
}
}
}
return dataRefIsTypeParamInquiry;
}
void OmpStructureChecker::CheckIsVarPartOfAnotherVar(
const parser::CharBlock &source, const parser::OmpObjectList &objList) {
for (const auto &ompObject : objList.v) {
std::visit(
common::visitors{
[&](const parser::Designator &designator) {
if (const auto *dataRef{
std::get_if<parser::DataRef>(&designator.u)}) {
if (IsDataRefTypeParamInquiry(dataRef)) {
context_.Say(source,
"A type parameter inquiry cannot appear in an ALLOCATE directive"_err_en_US);
} else if (parser::Unwrap<parser::StructureComponent>(
ompObject) ||
parser::Unwrap<parser::ArrayElement>(ompObject)) {
context_.Say(source,
"A variable that is part of another variable (as an "
"array or structure element)"
" cannot appear in a PRIVATE or SHARED clause or on the ALLOCATE directive."_err_en_US);
}
}
},
[&](const parser::Name &name) {},
},
ompObject.u);
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Firstprivate &x) {
CheckAllowed(llvm::omp::Clause::OMPC_firstprivate);
CheckIsLoopIvPartOfClause(llvmOmpClause::OMPC_firstprivate, x.v);
SymbolSourceMap currSymbols;
GetSymbolsInObjectList(x.v, currSymbols);
DirectivesClauseTriple dirClauseTriple;
// Check firstprivate variables in worksharing constructs
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_do,
std::make_pair(
llvm::omp::Directive::OMPD_parallel, llvm::omp::privateReductionSet));
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_sections,
std::make_pair(
llvm::omp::Directive::OMPD_parallel, llvm::omp::privateReductionSet));
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_single,
std::make_pair(
llvm::omp::Directive::OMPD_parallel, llvm::omp::privateReductionSet));
// Check firstprivate variables in distribute construct
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_distribute,
std::make_pair(
llvm::omp::Directive::OMPD_teams, llvm::omp::privateReductionSet));
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_distribute,
std::make_pair(llvm::omp::Directive::OMPD_target_teams,
llvm::omp::privateReductionSet));
// Check firstprivate variables in task and taskloop constructs
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_task,
std::make_pair(llvm::omp::Directive::OMPD_parallel,
OmpClauseSet{llvm::omp::Clause::OMPC_reduction}));
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_taskloop,
std::make_pair(llvm::omp::Directive::OMPD_parallel,
OmpClauseSet{llvm::omp::Clause::OMPC_reduction}));
CheckPrivateSymbolsInOuterCxt(
currSymbols, dirClauseTriple, llvm::omp::Clause::OMPC_firstprivate);
}
void OmpStructureChecker::CheckIsLoopIvPartOfClause(
llvmOmpClause clause, const parser::OmpObjectList &ompObjectList) {
for (const auto &ompObject : ompObjectList.v) {
if (const parser::Name * name{parser::Unwrap<parser::Name>(ompObject)}) {
if (name->symbol == GetContext().loopIV) {
context_.Say(name->source,
"DO iteration variable %s is not allowed in %s clause."_err_en_US,
name->ToString(),
parser::ToUpperCaseLetters(getClauseName(clause).str()));
}
}
}
}
// Following clauses have a seperate node in parse-tree.h.
// Atomic-clause
CHECK_SIMPLE_PARSER_CLAUSE(OmpAtomicRead, OMPC_read)
CHECK_SIMPLE_PARSER_CLAUSE(OmpAtomicWrite, OMPC_write)
CHECK_SIMPLE_PARSER_CLAUSE(OmpAtomicUpdate, OMPC_update)
CHECK_SIMPLE_PARSER_CLAUSE(OmpAtomicCapture, OMPC_capture)
void OmpStructureChecker::Leave(const parser::OmpAtomicRead &) {
CheckNotAllowedIfClause(llvm::omp::Clause::OMPC_read,
{llvm::omp::Clause::OMPC_release, llvm::omp::Clause::OMPC_acq_rel});
}
void OmpStructureChecker::Leave(const parser::OmpAtomicWrite &) {
CheckNotAllowedIfClause(llvm::omp::Clause::OMPC_write,
{llvm::omp::Clause::OMPC_acquire, llvm::omp::Clause::OMPC_acq_rel});
}
void OmpStructureChecker::Leave(const parser::OmpAtomicUpdate &) {
CheckNotAllowedIfClause(llvm::omp::Clause::OMPC_update,
{llvm::omp::Clause::OMPC_acquire, llvm::omp::Clause::OMPC_acq_rel});
}
// OmpAtomic node represents atomic directive without atomic-clause.
// atomic-clause - READ,WRITE,UPDATE,CAPTURE.
void OmpStructureChecker::Leave(const parser::OmpAtomic &) {
if (const auto *clause{FindClause(llvm::omp::Clause::OMPC_acquire)}) {
context_.Say(clause->source,
"Clause ACQUIRE is not allowed on the ATOMIC directive"_err_en_US);
}
if (const auto *clause{FindClause(llvm::omp::Clause::OMPC_acq_rel)}) {
context_.Say(clause->source,
"Clause ACQ_REL is not allowed on the ATOMIC directive"_err_en_US);
}
}
// Restrictions specific to each clause are implemented apart from the
// generalized restrictions.
void OmpStructureChecker::Enter(const parser::OmpClause::Aligned &x) {
CheckAllowed(llvm::omp::Clause::OMPC_aligned);
if (const auto &expr{
std::get<std::optional<parser::ScalarIntConstantExpr>>(x.v.t)}) {
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_aligned, *expr);
}
// 2.8.1 TODO: list-item attribute check
}
void OmpStructureChecker::Enter(const parser::OmpClause::Defaultmap &x) {
CheckAllowed(llvm::omp::Clause::OMPC_defaultmap);
using VariableCategory = parser::OmpDefaultmapClause::VariableCategory;
if (!std::get<std::optional<VariableCategory>>(x.v.t)) {
context_.Say(GetContext().clauseSource,
"The argument TOFROM:SCALAR must be specified on the DEFAULTMAP "
"clause"_err_en_US);
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::If &x) {
CheckAllowed(llvm::omp::Clause::OMPC_if);
using dirNameModifier = parser::OmpIfClause::DirectiveNameModifier;
static std::unordered_map<dirNameModifier, OmpDirectiveSet>
dirNameModifierMap{{dirNameModifier::Parallel, llvm::omp::parallelSet},
{dirNameModifier::Target, llvm::omp::targetSet},
{dirNameModifier::TargetEnterData,
{llvm::omp::Directive::OMPD_target_enter_data}},
{dirNameModifier::TargetExitData,
{llvm::omp::Directive::OMPD_target_exit_data}},
{dirNameModifier::TargetData,
{llvm::omp::Directive::OMPD_target_data}},
{dirNameModifier::TargetUpdate,
{llvm::omp::Directive::OMPD_target_update}},
{dirNameModifier::Task, {llvm::omp::Directive::OMPD_task}},
{dirNameModifier::Taskloop, llvm::omp::taskloopSet}};
if (const auto &directiveName{
std::get<std::optional<dirNameModifier>>(x.v.t)}) {
auto search{dirNameModifierMap.find(*directiveName)};
if (search == dirNameModifierMap.end() ||
!search->second.test(GetContext().directive)) {
context_
.Say(GetContext().clauseSource,
"Unmatched directive name modifier %s on the IF clause"_err_en_US,
parser::ToUpperCaseLetters(
parser::OmpIfClause::EnumToString(*directiveName)))
.Attach(
GetContext().directiveSource, "Cannot apply to directive"_en_US);
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Linear &x) {
CheckAllowed(llvm::omp::Clause::OMPC_linear);
// 2.7 Loop Construct Restriction
if ((llvm::omp::doSet | llvm::omp::simdSet).test(GetContext().directive)) {
if (std::holds_alternative<parser::OmpLinearClause::WithModifier>(x.v.u)) {
context_.Say(GetContext().clauseSource,
"A modifier may not be specified in a LINEAR clause "
"on the %s directive"_err_en_US,
ContextDirectiveAsFortran());
}
}
}
void OmpStructureChecker::CheckAllowedMapTypes(
const parser::OmpMapType::Type &type,
const std::list<parser::OmpMapType::Type> &allowedMapTypeList) {
const auto found{std::find(
std::begin(allowedMapTypeList), std::end(allowedMapTypeList), type)};
if (found == std::end(allowedMapTypeList)) {
std::string commaSeperatedMapTypes;
llvm::interleave(
allowedMapTypeList.begin(), allowedMapTypeList.end(),
[&](const parser::OmpMapType::Type &mapType) {
commaSeperatedMapTypes.append(parser::ToUpperCaseLetters(
parser::OmpMapType::EnumToString(mapType)));
},
[&] { commaSeperatedMapTypes.append(", "); });
context_.Say(GetContext().clauseSource,
"Only the %s map types are permitted "
"for MAP clauses on the %s directive"_err_en_US,
commaSeperatedMapTypes, ContextDirectiveAsFortran());
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
CheckAllowed(llvm::omp::Clause::OMPC_map);
if (const auto &maptype{std::get<std::optional<parser::OmpMapType>>(x.v.t)}) {
using Type = parser::OmpMapType::Type;
const Type &type{std::get<Type>(maptype->t)};
switch (GetContext().directive) {
case llvm::omp::Directive::OMPD_target:
case llvm::omp::Directive::OMPD_target_teams:
case llvm::omp::Directive::OMPD_target_teams_distribute:
case llvm::omp::Directive::OMPD_target_teams_distribute_simd:
case llvm::omp::Directive::OMPD_target_teams_distribute_parallel_do:
case llvm::omp::Directive::OMPD_target_teams_distribute_parallel_do_simd:
case llvm::omp::Directive::OMPD_target_data:
CheckAllowedMapTypes(
type, {Type::To, Type::From, Type::Tofrom, Type::Alloc});
break;
case llvm::omp::Directive::OMPD_target_enter_data:
CheckAllowedMapTypes(type, {Type::To, Type::Alloc});
break;
case llvm::omp::Directive::OMPD_target_exit_data:
CheckAllowedMapTypes(type, {Type::From, Type::Release, Type::Delete});
break;
default:
break;
}
}
}
bool OmpStructureChecker::ScheduleModifierHasType(
const parser::OmpScheduleClause &x,
const parser::OmpScheduleModifierType::ModType &type) {
const auto &modifier{
std::get<std::optional<parser::OmpScheduleModifier>>(x.t)};
if (modifier) {
const auto &modType1{
std::get<parser::OmpScheduleModifier::Modifier1>(modifier->t)};
const auto &modType2{
std::get<std::optional<parser::OmpScheduleModifier::Modifier2>>(
modifier->t)};
if (modType1.v.v == type || (modType2 && modType2->v.v == type)) {
return true;
}
}
return false;
}
void OmpStructureChecker::Enter(const parser::OmpClause::Schedule &x) {
CheckAllowed(llvm::omp::Clause::OMPC_schedule);
const parser::OmpScheduleClause &scheduleClause = x.v;
// 2.7 Loop Construct Restriction
if (llvm::omp::doSet.test(GetContext().directive)) {
const auto &kind{std::get<1>(scheduleClause.t)};
const auto &chunk{std::get<2>(scheduleClause.t)};
if (chunk) {
if (kind == parser::OmpScheduleClause::ScheduleType::Runtime ||
kind == parser::OmpScheduleClause::ScheduleType::Auto) {
context_.Say(GetContext().clauseSource,
"When SCHEDULE clause has %s specified, "
"it must not have chunk size specified"_err_en_US,
parser::ToUpperCaseLetters(
parser::OmpScheduleClause::EnumToString(kind)));
}
if (const auto &chunkExpr{std::get<std::optional<parser::ScalarIntExpr>>(
scheduleClause.t)}) {
RequiresPositiveParameter(
llvm::omp::Clause::OMPC_schedule, *chunkExpr, "chunk size");
}
}
if (ScheduleModifierHasType(scheduleClause,
parser::OmpScheduleModifierType::ModType::Nonmonotonic)) {
if (kind != parser::OmpScheduleClause::ScheduleType::Dynamic &&
kind != parser::OmpScheduleClause::ScheduleType::Guided) {
context_.Say(GetContext().clauseSource,
"The NONMONOTONIC modifier can only be specified with "
"SCHEDULE(DYNAMIC) or SCHEDULE(GUIDED)"_err_en_US);
}
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Depend &x) {
CheckAllowed(llvm::omp::Clause::OMPC_depend);
if (const auto *inOut{std::get_if<parser::OmpDependClause::InOut>(&x.v.u)}) {
const auto &designators{std::get<std::list<parser::Designator>>(inOut->t)};
for (const auto &ele : designators) {
if (const auto *dataRef{std::get_if<parser::DataRef>(&ele.u)}) {
CheckDependList(*dataRef);
if (const auto *arr{
std::get_if<common::Indirection<parser::ArrayElement>>(
&dataRef->u)}) {
CheckArraySection(arr->value(), GetLastName(*dataRef),
llvm::omp::Clause::OMPC_depend);
}
}
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Copyprivate &x) {
CheckAllowed(llvm::omp::Clause::OMPC_copyprivate);
CheckIntentInPointer(x.v, llvm::omp::Clause::OMPC_copyprivate);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Lastprivate &x) {
CheckAllowed(llvm::omp::Clause::OMPC_lastprivate);
DirectivesClauseTriple dirClauseTriple;
SymbolSourceMap currSymbols;
GetSymbolsInObjectList(x.v, currSymbols);
CheckDefinableObjects(currSymbols, GetClauseKindForParserClass(x));
// Check lastprivate variables in worksharing constructs
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_do,
std::make_pair(
llvm::omp::Directive::OMPD_parallel, llvm::omp::privateReductionSet));
dirClauseTriple.emplace(llvm::omp::Directive::OMPD_sections,
std::make_pair(
llvm::omp::Directive::OMPD_parallel, llvm::omp::privateReductionSet));
CheckPrivateSymbolsInOuterCxt(
currSymbols, dirClauseTriple, GetClauseKindForParserClass(x));
}
llvm::StringRef OmpStructureChecker::getClauseName(llvm::omp::Clause clause) {
return llvm::omp::getOpenMPClauseName(clause);
}
llvm::StringRef OmpStructureChecker::getDirectiveName(
llvm::omp::Directive directive) {
return llvm::omp::getOpenMPDirectiveName(directive);
}
void OmpStructureChecker::CheckDependList(const parser::DataRef &d) {
std::visit(
common::visitors{
[&](const common::Indirection<parser::ArrayElement> &elem) {
// Check if the base element is valid on Depend Clause
CheckDependList(elem.value().base);
},
[&](const common::Indirection<parser::StructureComponent> &) {
context_.Say(GetContext().clauseSource,
"A variable that is part of another variable "
"(such as an element of a structure) but is not an array "
"element or an array section cannot appear in a DEPEND "
"clause"_err_en_US);
},
[&](const common::Indirection<parser::CoindexedNamedObject> &) {
context_.Say(GetContext().clauseSource,
"Coarrays are not supported in DEPEND clause"_err_en_US);
},
[&](const parser::Name &) { return; },
},
d.u);
}
// Called from both Reduction and Depend clause.
void OmpStructureChecker::CheckArraySection(
const parser::ArrayElement &arrayElement, const parser::Name &name,
const llvm::omp::Clause clause) {
if (!arrayElement.subscripts.empty()) {
for (const auto &subscript : arrayElement.subscripts) {
if (const auto *triplet{
std::get_if<parser::SubscriptTriplet>(&subscript.u)}) {
if (std::get<0>(triplet->t) && std::get<1>(triplet->t)) {
const auto &lower{std::get<0>(triplet->t)};
const auto &upper{std::get<1>(triplet->t)};
if (lower && upper) {
const auto lval{GetIntValue(lower)};
const auto uval{GetIntValue(upper)};
if (lval && uval && *uval < *lval) {
context_.Say(GetContext().clauseSource,
"'%s' in %s clause"
" is a zero size array section"_err_en_US,
name.ToString(),
parser::ToUpperCaseLetters(getClauseName(clause).str()));
break;
} else if (std::get<2>(triplet->t)) {
const auto &strideExpr{std::get<2>(triplet->t)};
if (strideExpr) {
if (clause == llvm::omp::Clause::OMPC_depend) {
context_.Say(GetContext().clauseSource,
"Stride should not be specified for array section in "
"DEPEND "
"clause"_err_en_US);
}
const auto stride{GetIntValue(strideExpr)};
if ((stride && stride != 1)) {
context_.Say(GetContext().clauseSource,
"A list item that appears in a REDUCTION clause"
" should have a contiguous storage array section."_err_en_US,
ContextDirectiveAsFortran());
break;
}
}
}
}
}
}
}
}
}
void OmpStructureChecker::CheckIntentInPointer(
const parser::OmpObjectList &objectList, const llvm::omp::Clause clause) {
SymbolSourceMap symbols;
GetSymbolsInObjectList(objectList, symbols);
for (auto it{symbols.begin()}; it != symbols.end(); ++it) {
const auto *symbol{it->first};
const auto source{it->second};
if (IsPointer(*symbol) && IsIntentIn(*symbol)) {
context_.Say(source,
"Pointer '%s' with the INTENT(IN) attribute may not appear "
"in a %s clause"_err_en_US,
symbol->name(),
parser::ToUpperCaseLetters(getClauseName(clause).str()));
}
}
}
void OmpStructureChecker::GetSymbolsInObjectList(
const parser::OmpObjectList &objectList, SymbolSourceMap &symbols) {
for (const auto &ompObject : objectList.v) {
if (const auto *name{parser::Unwrap<parser::Name>(ompObject)}) {
if (const auto *symbol{name->symbol}) {
if (const auto *commonBlockDetails{
symbol->detailsIf<CommonBlockDetails>()}) {
for (const auto &object : commonBlockDetails->objects()) {
symbols.emplace(&object->GetUltimate(), name->source);
}
} else {
symbols.emplace(&symbol->GetUltimate(), name->source);
}
}
}
}
}
void OmpStructureChecker::CheckDefinableObjects(
SymbolSourceMap &symbols, const llvm::omp::Clause clause) {
for (auto it{symbols.begin()}; it != symbols.end(); ++it) {
const auto *symbol{it->first};
const auto source{it->second};
if (auto msg{WhyNotModifiable(*symbol, context_.FindScope(source))}) {
context_
.Say(source,
"Variable '%s' on the %s clause is not definable"_err_en_US,
symbol->name(),
parser::ToUpperCaseLetters(getClauseName(clause).str()))
.Attach(source, std::move(*msg), symbol->name());
}
}
}
void OmpStructureChecker::CheckPrivateSymbolsInOuterCxt(
SymbolSourceMap &currSymbols, DirectivesClauseTriple &dirClauseTriple,
const llvm::omp::Clause currClause) {
SymbolSourceMap enclosingSymbols;
auto range{dirClauseTriple.equal_range(GetContext().directive)};
for (auto dirIter{range.first}; dirIter != range.second; ++dirIter) {
auto enclosingDir{dirIter->second.first};
auto enclosingClauseSet{dirIter->second.second};
if (auto *enclosingContext{GetEnclosingContextWithDir(enclosingDir)}) {
for (auto it{enclosingContext->clauseInfo.begin()};
it != enclosingContext->clauseInfo.end(); ++it) {
if (enclosingClauseSet.test(it->first)) {
if (const auto *ompObjectList{GetOmpObjectList(*it->second)}) {
GetSymbolsInObjectList(*ompObjectList, enclosingSymbols);
}
}
}
// Check if the symbols in current context are private in outer context
for (auto iter{currSymbols.begin()}; iter != currSymbols.end(); ++iter) {
const auto *symbol{iter->first};
const auto source{iter->second};
if (enclosingSymbols.find(symbol) != enclosingSymbols.end()) {
context_.Say(source,
"%s variable '%s' is PRIVATE in outer context"_err_en_US,
parser::ToUpperCaseLetters(getClauseName(currClause).str()),
symbol->name());
}
}
}
}
}
void OmpStructureChecker::CheckWorkshareBlockStmts(
const parser::Block &block, parser::CharBlock source) {
OmpWorkshareBlockChecker ompWorkshareBlockChecker{context_, source};
for (auto it{block.begin()}; it != block.end(); ++it) {
if (parser::Unwrap<parser::AssignmentStmt>(*it) ||
parser::Unwrap<parser::ForallStmt>(*it) ||
parser::Unwrap<parser::ForallConstruct>(*it) ||
parser::Unwrap<parser::WhereStmt>(*it) ||
parser::Unwrap<parser::WhereConstruct>(*it)) {
parser::Walk(*it, ompWorkshareBlockChecker);
} else if (const auto *ompConstruct{
parser::Unwrap<parser::OpenMPConstruct>(*it)}) {
if (const auto *ompAtomicConstruct{
std::get_if<parser::OpenMPAtomicConstruct>(&ompConstruct->u)}) {
// Check if assignment statements in the enclosing OpenMP Atomic
// construct are allowed in the Workshare construct
parser::Walk(*ompAtomicConstruct, ompWorkshareBlockChecker);
} else if (const auto *ompCriticalConstruct{
std::get_if<parser::OpenMPCriticalConstruct>(
&ompConstruct->u)}) {
// All the restrictions on the Workshare construct apply to the
// statements in the enclosing critical constructs
const auto &criticalBlock{
std::get<parser::Block>(ompCriticalConstruct->t)};
CheckWorkshareBlockStmts(criticalBlock, source);
} else {
// Check if OpenMP constructs enclosed in the Workshare construct are
// 'Parallel' constructs
auto currentDir{llvm::omp::Directive::OMPD_unknown};
const OmpDirectiveSet parallelDirSet{
llvm::omp::Directive::OMPD_parallel,
llvm::omp::Directive::OMPD_parallel_do,
llvm::omp::Directive::OMPD_parallel_sections,
llvm::omp::Directive::OMPD_parallel_workshare,
llvm::omp::Directive::OMPD_parallel_do_simd};
if (const auto *ompBlockConstruct{
std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) {
const auto &beginBlockDir{
std::get<parser::OmpBeginBlockDirective>(ompBlockConstruct->t)};
const auto &beginDir{
std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
currentDir = beginDir.v;
} else if (const auto *ompLoopConstruct{
std::get_if<parser::OpenMPLoopConstruct>(
&ompConstruct->u)}) {
const auto &beginLoopDir{
std::get<parser::OmpBeginLoopDirective>(ompLoopConstruct->t)};
const auto &beginDir{
std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
currentDir = beginDir.v;
} else if (const auto *ompSectionsConstruct{
std::get_if<parser::OpenMPSectionsConstruct>(
&ompConstruct->u)}) {
const auto &beginSectionsDir{
std::get<parser::OmpBeginSectionsDirective>(
ompSectionsConstruct->t)};
const auto &beginDir{
std::get<parser::OmpSectionsDirective>(beginSectionsDir.t)};
currentDir = beginDir.v;
}
if (!parallelDirSet.test(currentDir)) {
context_.Say(source,
"OpenMP constructs enclosed in WORKSHARE construct may consist "
"of ATOMIC, CRITICAL or PARALLEL constructs only"_err_en_US);
}
}
} else {
context_.Say(source,
"The structured block in a WORKSHARE construct may consist of only "
"SCALAR or ARRAY assignments, FORALL or WHERE statements, "
"FORALL, WHERE, ATOMIC, CRITICAL or PARALLEL constructs"_err_en_US);
}
}
}
const parser::OmpObjectList *OmpStructureChecker::GetOmpObjectList(
const parser::OmpClause &clause) {
// Clauses with OmpObjectList as its data member
using MemberObjectListClauses = std::tuple<parser::OmpClause::Copyprivate,
parser::OmpClause::Copyin, parser::OmpClause::Firstprivate,
parser::OmpClause::From, parser::OmpClause::Lastprivate,
parser::OmpClause::Link, parser::OmpClause::Private,
parser::OmpClause::Shared, parser::OmpClause::To>;
// Clauses with OmpObjectList in the tuple
using TupleObjectListClauses = std::tuple<parser::OmpClause::Allocate,
parser::OmpClause::Map, parser::OmpClause::Reduction>;
// TODO:: Generate the tuples using TableGen.
// Handle other constructs with OmpObjectList such as OpenMPThreadprivate.
return std::visit(
common::visitors{
[&](const auto &x) -> const parser::OmpObjectList * {
using Ty = std::decay_t<decltype(x)>;
if constexpr (common::HasMember<Ty, MemberObjectListClauses>) {
return &x.v;
} else if constexpr (common::HasMember<Ty,
TupleObjectListClauses>) {
return &(std::get<parser::OmpObjectList>(x.v.t));
} else {
return nullptr;
}
},
},
clause.u);
}
} // namespace Fortran::semantics