llvm/flang/lib/semantics/check-omp-structure.cc
Jinxin (Brian) Yang cdd1ca064c [flang] [OpenMP] Add Sections and Single Construct check (flang-compiler/f18#585)
* [OpenMP] Add Sections and Single Construct check

Parse tree for OmpEndSingle needs to be modified to save the provenance
of END SINGLE directive and check its own clauses

* Update on reviews

1. PushContext is created to push new context with source provenance

2. Tweak the logic for SECTION nesting, treak Orphaned or wrong nesting
   as the same error type

3. Make sure the check for NOWAIT clause only applies to the ones that
   are not handled by parser.
   Note that the case for DO or DO_SIMD will take effect after the
   loop association work (parse tree change) is done. But I still list
   them there for completeness.

4. Happen to find that NOWAIT is not accepted by PARALLEL SECTIONS,
   fixed it in the parser.


Original-commit: flang-compiler/f18@236cf1efea
Reviewed-on: https://github.com/flang-compiler/f18/pull/585
2019-08-01 14:32:33 -07:00

616 lines
22 KiB
C++

// Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "check-omp-structure.h"
#include "tools.h"
#include "../parser/parse-tree.h"
namespace Fortran::semantics {
static constexpr OmpDirectiveSet doSet{OmpDirective::DO,
OmpDirective::PARALLEL_DO, OmpDirective::DO_SIMD,
OmpDirective::PARALLEL_DO_SIMD};
static constexpr OmpDirectiveSet simdSet{
OmpDirective::SIMD, OmpDirective::DO_SIMD, OmpDirective::PARALLEL_DO_SIMD};
static constexpr OmpDirectiveSet doSimdSet{
OmpDirective::DO_SIMD, OmpDirective::PARALLEL_DO_SIMD};
std::string OmpStructureChecker::ContextDirectiveAsFortran() {
auto dir{EnumToString(GetContext().directive)};
std::replace(dir.begin(), dir.end(), '_', ' ');
return dir;
}
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 (CurrentDirectiveIsNested() && set.test(GetContext().directive)) {
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::CheckAllowed(const OmpClause &type) {
if (!GetContext().allowedClauses.test(type) &&
!GetContext().allowedOnceClauses.test(type)) {
context_.Say(GetContext().clauseSource,
"%s clause is not allowed on the %s directive"_err_en_US,
EnumToString(type),
parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
return;
}
if (GetContext().allowedOnceClauses.test(type) && FindClause(type)) {
context_.Say(GetContext().clauseSource,
"At most one %s clause can appear on the %s directive"_err_en_US,
EnumToString(type),
parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
return;
}
SetContextClauseInfo(type);
}
void OmpStructureChecker::Enter(const parser::OpenMPConstruct &x) {
// 2.8.1 TODO: Simd Construct with Ordered Construct Nesting check
}
void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
const auto &dir{std::get<parser::OmpLoopDirective>(x.t)};
if (dir.v == parser::OmpLoopDirective::Directive::Do) {
HasInvalidWorksharingNesting(dir.source,
{OmpDirective::DO, OmpDirective::SECTIONS, OmpDirective::SINGLE,
OmpDirective::WORKSHARE, OmpDirective::TASK, OmpDirective::TASKLOOP,
OmpDirective::CRITICAL, OmpDirective::ORDERED, OmpDirective::ATOMIC,
OmpDirective::MASTER});
}
// push a context even in the error case
PushContext(dir.source);
}
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
const auto &dir{std::get<parser::OmpBlockDirective>(x.t)};
PushContext(dir.source);
}
void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContext(dir.source, OmpDirective::SECTIONS);
OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
OmpClause::LASTPRIVATE, OmpClause::REDUCTION};
SetContextAllowed(allowed);
}
void OmpStructureChecker::Leave(const parser::OpenMPSectionsConstruct &x) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpSection &x) {
const auto &dir{x.v}; // Verbatim
if (!CurrentDirectiveIsNested() ||
GetContext().directive != OmpDirective::SECTIONS) {
// if not currently nested, SECTION is orphaned
context_.Say(dir.source,
"SECTION directive must appear within "
"the SECTIONS construct"_err_en_US);
}
}
void OmpStructureChecker::Enter(const parser::OpenMPSingleConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContext(dir.source, OmpDirective::SINGLE);
OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE};
SetContextAllowed(allowed);
}
void OmpStructureChecker::Leave(const parser::OpenMPSingleConstruct &x) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpEndSingle &x) {
// EndSingle is in SingleConstruct context
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContext(dir.source, OmpDirective::END_SINGLE);
OmpClauseSet allowed{OmpClause::COPYPRIVATE};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{OmpClause::NOWAIT};
SetContextAllowedOnce(allowedOnce);
}
void OmpStructureChecker::Leave(const parser::OmpEndSingle &x) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OpenMPWorkshareConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
PushContext(dir.source, OmpDirective::WORKSHARE);
}
void OmpStructureChecker::Leave(const parser::OpenMPWorkshareConstruct &x) {
ompContext_.pop_back();
}
void OmpStructureChecker::Enter(const parser::OmpBlockDirective &x) {
switch (x.v) {
// 2.5 parallel-clause -> if-clause |
// num-threads-clause |
// default-clause |
// private-clause |
// firstprivate-clause |
// shared-clause |
// copyin-clause |
// reduction-clause |
// proc-bind-clause
case parser::OmpBlockDirective::Directive::Parallel: {
// reserve for nesting check
SetContextDirectiveEnum(OmpDirective::PARALLEL);
OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
OmpClause::REDUCTION};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{
OmpClause::IF, OmpClause::NUM_THREADS, OmpClause::PROC_BIND};
SetContextAllowedOnce(allowedOnce);
} break;
default:
// TODO others
break;
}
}
void OmpStructureChecker::Enter(const parser::OmpLoopDirective &x) {
switch (x.v) {
// 2.7.1 do-clause -> private-clause |
// firstprivate-clause |
// lastprivate-clause |
// linear-clause |
// reduction-clause |
// schedule-clause |
// collapse-clause |
// ordered-clause
case parser::OmpLoopDirective::Directive::Do: {
// reserve for nesting check
SetContextDirectiveEnum(OmpDirective::DO);
OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
OmpClause::LASTPRIVATE, OmpClause::LINEAR, OmpClause::REDUCTION};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{
OmpClause::SCHEDULE, OmpClause::COLLAPSE, OmpClause::ORDERED};
SetContextAllowedOnce(allowedOnce);
} break;
// 2.11.1 parallel-do-clause -> parallel-clause |
// do-clause
case parser::OmpLoopDirective::Directive::ParallelDo: {
SetContextDirectiveEnum(OmpDirective::PARALLEL_DO);
OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
OmpClause::REDUCTION, OmpClause::LASTPRIVATE, OmpClause::LINEAR};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{OmpClause::IF, OmpClause::NUM_THREADS,
OmpClause::PROC_BIND, OmpClause::SCHEDULE, OmpClause::COLLAPSE,
OmpClause::ORDERED};
SetContextAllowedOnce(allowedOnce);
} break;
// 2.8.1 simd-clause -> safelen-clause |
// simdlen-clause |
// linear-clause |
// aligned-clause |
// private-clause |
// lastprivate-clause |
// reduction-clause |
// collapse-clause
case parser::OmpLoopDirective::Directive::Simd: {
SetContextDirectiveEnum(OmpDirective::SIMD);
OmpClauseSet allowed{OmpClause::LINEAR, OmpClause::ALIGNED,
OmpClause::PRIVATE, OmpClause::LASTPRIVATE, OmpClause::REDUCTION};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{
OmpClause::COLLAPSE, OmpClause::SAFELEN, OmpClause::SIMDLEN};
SetContextAllowedOnce(allowedOnce);
} break;
// 2.8.3 do-simd-clause -> do-clause |
// simd-clause
case parser::OmpLoopDirective::Directive::DoSimd: {
SetContextDirectiveEnum(OmpDirective::DO_SIMD);
OmpClauseSet allowed{OmpClause::PRIVATE, OmpClause::FIRSTPRIVATE,
OmpClause::LASTPRIVATE, OmpClause::LINEAR, OmpClause::REDUCTION,
OmpClause::ALIGNED};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{OmpClause::SCHEDULE, OmpClause::COLLAPSE,
OmpClause::ORDERED, OmpClause::SAFELEN, OmpClause::SIMDLEN};
SetContextAllowedOnce(allowedOnce);
} break;
// 2.11.4 parallel-do-simd-clause -> parallel-clause |
// do-simd-clause
case parser::OmpLoopDirective::Directive::ParallelDoSimd: {
SetContextDirectiveEnum(OmpDirective::PARALLEL_DO_SIMD);
OmpClauseSet allowed{OmpClause::DEFAULT, OmpClause::PRIVATE,
OmpClause::FIRSTPRIVATE, OmpClause::SHARED, OmpClause::COPYIN,
OmpClause::REDUCTION, OmpClause::LASTPRIVATE, OmpClause::LINEAR,
OmpClause::ALIGNED};
SetContextAllowed(allowed);
OmpClauseSet allowedOnce{OmpClause::IF, OmpClause::NUM_THREADS,
OmpClause::PROC_BIND, OmpClause::SCHEDULE, OmpClause::COLLAPSE,
OmpClause::ORDERED, OmpClause::SAFELEN, OmpClause::SIMDLEN};
SetContextAllowedOnce(allowedOnce);
} break;
default:
// TODO others
break;
}
}
void OmpStructureChecker::Leave(const parser::OmpClauseList &) {
// 2.7 Loop Construct Restriction
if (doSet.test(GetContext().directive)) {
if (auto *clause{FindClause(OmpClause::SCHEDULE)}) {
// only one schedule clause is allowed
const auto &schedClause{std::get<parser::OmpScheduleClause>(clause->u)};
if (ScheduleModifierHasType(schedClause,
parser::OmpScheduleModifierType::ModType::Nonmonotonic)) {
if (FindClause(OmpClause::ORDERED)) {
context_.Say(clause->source,
"The NONMONOTONIC modifier cannot be specified "
"if an ORDERED clause is specified"_err_en_US);
}
if (ScheduleModifierHasType(schedClause,
parser::OmpScheduleModifierType::ModType::Monotonic)) {
context_.Say(clause->source,
"The MONOTONIC and NONMONOTONIC modifiers "
"cannot be both specified"_err_en_US);
}
}
}
if (auto *clause{FindClause(OmpClause::ORDERED)}) {
// only one ordered clause is allowed
const auto &orderedClause{
std::get<parser::OmpClause::Ordered>(clause->u)};
if (orderedClause.v.has_value()) {
if (FindClause(OmpClause::LINEAR)) {
context_.Say(clause->source,
"A loop directive may not have both a LINEAR clause and "
"an ORDERED clause with a parameter"_err_en_US);
}
if (auto *clause2{FindClause(OmpClause::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 (simdSet.test(GetContext().directive)) {
if (auto *clause{FindClause(OmpClause::SIMDLEN)}) {
if (auto *clause2{FindClause(OmpClause::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);
}
}
}
}
}
// TODO: A list-item cannot appear in more than one aligned clause
} // SIMD
// 2.7.3 Single Construct Restriction
if (GetContext().directive == OmpDirective::END_SINGLE) {
if (auto *clause{FindClause(OmpClause::COPYPRIVATE)}) {
if (FindClause(OmpClause::NOWAIT)) {
context_.Say(clause->source,
"The COPYPRIVATE clause must not be used with "
"the NOWAIT clause"_err_en_US);
}
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause &x) {
SetContextClause(x);
}
void OmpStructureChecker::Enter(const parser::OmpNowait &) {
switch (GetContext().directive) {
case OmpDirective::SECTIONS:
case OmpDirective::WORKSHARE:
case OmpDirective::DO_SIMD:
case OmpDirective::DO: break; // NOWAIT is handled by parser
default: CheckAllowed(OmpClause::NOWAIT); break;
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Defaultmap &) {
CheckAllowed(OmpClause::DEFAULTMAP);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Inbranch &) {
CheckAllowed(OmpClause::INBRANCH);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Mergeable &) {
CheckAllowed(OmpClause::MERGEABLE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Nogroup &) {
CheckAllowed(OmpClause::NOGROUP);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Notinbranch &) {
CheckAllowed(OmpClause::NOTINBRANCH);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Untied &) {
CheckAllowed(OmpClause::UNTIED);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Collapse &x) {
CheckAllowed(OmpClause::COLLAPSE);
// collapse clause must have a parameter
if (const auto v{GetIntValue(x.v)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The parameter of the COLLAPSE clause must be "
"a constant positive integer expression"_err_en_US);
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Copyin &) {
CheckAllowed(OmpClause::COPYIN);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Copyprivate &) {
CheckAllowed(OmpClause::COPYPRIVATE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Device &) {
CheckAllowed(OmpClause::DEVICE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::DistSchedule &) {
CheckAllowed(OmpClause::DIST_SCHEDULE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Final &) {
CheckAllowed(OmpClause::FINAL);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Firstprivate &) {
CheckAllowed(OmpClause::FIRSTPRIVATE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::From &) {
CheckAllowed(OmpClause::FROM);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Grainsize &) {
CheckAllowed(OmpClause::GRAINSIZE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Lastprivate &) {
CheckAllowed(OmpClause::LASTPRIVATE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::NumTasks &) {
CheckAllowed(OmpClause::NUM_TASKS);
}
void OmpStructureChecker::Enter(const parser::OmpClause::NumTeams &) {
CheckAllowed(OmpClause::NUM_TEAMS);
}
void OmpStructureChecker::Enter(const parser::OmpClause::NumThreads &x) {
CheckAllowed(OmpClause::NUM_THREADS);
if (const auto v{GetIntValue(x.v)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The parameter of the NUM_THREADS clause must be "
"a positive integer expression"_err_en_US);
}
}
// if parameter is variable, defer to Expression Analysis
}
void OmpStructureChecker::Enter(const parser::OmpClause::Ordered &x) {
CheckAllowed(OmpClause::ORDERED);
// the parameter of ordered clause is optional
if (const auto &expr{x.v}) {
if (const auto v{GetIntValue(expr)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The parameter of the ORDERED clause must be "
"a constant positive integer expression"_err_en_US);
}
}
// 2.8.3 Loop SIMD Construct Restriction
if (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::Priority &) {
CheckAllowed(OmpClause::PRIORITY);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Private &) {
CheckAllowed(OmpClause::PRIVATE);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Safelen &x) {
CheckAllowed(OmpClause::SAFELEN);
if (const auto v{GetIntValue(x.v)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The parameter of the SAFELEN clause must be "
"a constant positive integer expression"_err_en_US);
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::Shared &) {
CheckAllowed(OmpClause::SHARED);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Simdlen &x) {
CheckAllowed(OmpClause::SIMDLEN);
if (const auto v{GetIntValue(x.v)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The parameter of the SIMDLEN clause must be "
"a constant positive integer expression"_err_en_US);
}
}
}
void OmpStructureChecker::Enter(const parser::OmpClause::ThreadLimit &) {
CheckAllowed(OmpClause::THREAD_LIMIT);
}
void OmpStructureChecker::Enter(const parser::OmpClause::To &) {
CheckAllowed(OmpClause::TO);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Link &) {
CheckAllowed(OmpClause::LINK);
}
void OmpStructureChecker::Enter(const parser::OmpClause::Uniform &) {
CheckAllowed(OmpClause::UNIFORM);
}
void OmpStructureChecker::Enter(const parser::OmpClause::UseDevicePtr &) {
CheckAllowed(OmpClause::USE_DEVICE_PTR);
}
void OmpStructureChecker::Enter(const parser::OmpClause::IsDevicePtr &) {
CheckAllowed(OmpClause::IS_DEVICE_PTR);
}
void OmpStructureChecker::Enter(const parser::OmpAlignedClause &x) {
CheckAllowed(OmpClause::ALIGNED);
if (const auto &expr{
std::get<std::optional<parser::ScalarIntConstantExpr>>(x.t)}) {
if (const auto v{GetIntValue(*expr)}) {
if (*v <= 0) {
context_.Say(GetContext().clauseSource,
"The ALIGNMENT parameter of the ALIGNED clause must be "
"a constant positive integer expression"_err_en_US);
}
}
}
// 2.8.1 TODO: list-item attribute check
}
void OmpStructureChecker::Enter(const parser::OmpDefaultClause &) {
CheckAllowed(OmpClause::DEFAULT);
}
void OmpStructureChecker::Enter(const parser::OmpDependClause &) {
CheckAllowed(OmpClause::DEPEND);
}
void OmpStructureChecker::Enter(const parser::OmpIfClause &) {
CheckAllowed(OmpClause::IF);
}
void OmpStructureChecker::Enter(const parser::OmpLinearClause &x) {
CheckAllowed(OmpClause::LINEAR);
// 2.7 Loop Construct Restriction
if ((doSet | simdSet).test(GetContext().directive)) {
if (std::holds_alternative<parser::OmpLinearClause::WithModifier>(x.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::Enter(const parser::OmpMapClause &) {
CheckAllowed(OmpClause::MAP);
}
void OmpStructureChecker::Enter(const parser::OmpProcBindClause &) {
CheckAllowed(OmpClause::PROC_BIND);
}
void OmpStructureChecker::Enter(const parser::OmpReductionClause &) {
CheckAllowed(OmpClause::REDUCTION);
}
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.has_value()) {
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.has_value() && modType2->v.v == type)) {
return true;
}
}
return false;
}
void OmpStructureChecker::Enter(const parser::OmpScheduleClause &x) {
CheckAllowed(OmpClause::SCHEDULE);
// 2.7 Loop Construct Restriction
if (doSet.test(GetContext().directive)) {
const auto &kind{std::get<1>(x.t)};
const auto &chunk{std::get<2>(x.t)};
if (chunk.has_value()) {
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 (ScheduleModifierHasType(
x, 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);
}
}
}
}
}