Files
clang-p2996/flang/lib/Semantics/check-acc-structure.cpp
Valentin Clement d3a0df78bf [flang][openacc] Add semantic check for reduction operator and types
Check the combination of reduction operator and types. This is
currently not checking common block and composite types.

Depends on D155105

Reviewed By: razvanlupusoru

Differential Revision: https://reviews.llvm.org/D155106
2023-07-13 08:55:01 -07:00

558 lines
22 KiB
C++

//===-- lib/Semantics/check-acc-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-acc-structure.h"
#include "flang/Common/enum-set.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/tools.h"
#define CHECK_SIMPLE_CLAUSE(X, Y) \
void AccStructureChecker::Enter(const parser::AccClause::X &) { \
CheckAllowed(llvm::acc::Clause::Y); \
}
#define CHECK_REQ_SCALAR_INT_CONSTANT_CLAUSE(X, Y) \
void AccStructureChecker::Enter(const parser::AccClause::X &c) { \
CheckAllowed(llvm::acc::Clause::Y); \
RequiresConstantPositiveParameter(llvm::acc::Clause::Y, c.v); \
}
using ReductionOpsSet =
Fortran::common::EnumSet<Fortran::parser::AccReductionOperator::Operator,
Fortran::parser::AccReductionOperator::Operator_enumSize>;
static ReductionOpsSet reductionIntegerSet{
Fortran::parser::AccReductionOperator::Operator::Plus,
Fortran::parser::AccReductionOperator::Operator::Multiply,
Fortran::parser::AccReductionOperator::Operator::Max,
Fortran::parser::AccReductionOperator::Operator::Min,
Fortran::parser::AccReductionOperator::Operator::Iand,
Fortran::parser::AccReductionOperator::Operator::Ior,
Fortran::parser::AccReductionOperator::Operator::Ieor};
static ReductionOpsSet reductionRealSet{
Fortran::parser::AccReductionOperator::Operator::Plus,
Fortran::parser::AccReductionOperator::Operator::Multiply,
Fortran::parser::AccReductionOperator::Operator::Max,
Fortran::parser::AccReductionOperator::Operator::Min};
static ReductionOpsSet reductionComplexSet{
Fortran::parser::AccReductionOperator::Operator::Plus,
Fortran::parser::AccReductionOperator::Operator::Multiply};
static ReductionOpsSet reductionLogicalSet{
Fortran::parser::AccReductionOperator::Operator::And,
Fortran::parser::AccReductionOperator::Operator::Or,
Fortran::parser::AccReductionOperator::Operator::Eqv,
Fortran::parser::AccReductionOperator::Operator::Neqv};
namespace Fortran::semantics {
static constexpr inline AccClauseSet
computeConstructOnlyAllowedAfterDeviceTypeClauses{
llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait,
llvm::acc::Clause::ACCC_num_gangs, llvm::acc::Clause::ACCC_num_workers,
llvm::acc::Clause::ACCC_vector_length};
static constexpr inline AccClauseSet loopOnlyAllowedAfterDeviceTypeClauses{
llvm::acc::Clause::ACCC_auto, llvm::acc::Clause::ACCC_collapse,
llvm::acc::Clause::ACCC_independent, llvm::acc::Clause::ACCC_gang,
llvm::acc::Clause::ACCC_seq, llvm::acc::Clause::ACCC_tile,
llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_worker};
static constexpr inline AccClauseSet updateOnlyAllowedAfterDeviceTypeClauses{
llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait};
static constexpr inline AccClauseSet routineOnlyAllowedAfterDeviceTypeClauses{
llvm::acc::Clause::ACCC_bind, llvm::acc::Clause::ACCC_gang,
llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_worker};
bool AccStructureChecker::CheckAllowedModifier(llvm::acc::Clause clause) {
if (GetContext().directive == llvm::acc::ACCD_enter_data ||
GetContext().directive == llvm::acc::ACCD_exit_data) {
context_.Say(GetContext().clauseSource,
"Modifier is not allowed for the %s clause "
"on the %s directive"_err_en_US,
parser::ToUpperCaseLetters(getClauseName(clause).str()),
ContextDirectiveAsFortran());
return true;
}
return false;
}
bool AccStructureChecker::IsComputeConstruct(
llvm::acc::Directive directive) const {
return directive == llvm::acc::ACCD_parallel ||
directive == llvm::acc::ACCD_parallel_loop ||
directive == llvm::acc::ACCD_serial ||
directive == llvm::acc::ACCD_serial_loop ||
directive == llvm::acc::ACCD_kernels ||
directive == llvm::acc::ACCD_kernels_loop;
}
bool AccStructureChecker::IsInsideComputeConstruct() const {
if (dirContext_.size() <= 1) {
return false;
}
// Check all nested context skipping the first one.
for (std::size_t i = dirContext_.size() - 1; i > 0; --i) {
if (IsComputeConstruct(dirContext_[i - 1].directive)) {
return true;
}
}
return false;
}
void AccStructureChecker::CheckNotInComputeConstruct() {
if (IsInsideComputeConstruct()) {
context_.Say(GetContext().directiveSource,
"Directive %s may not be called within a compute region"_err_en_US,
ContextDirectiveAsFortran());
}
}
void AccStructureChecker::Enter(const parser::AccClause &x) {
SetContextClause(x);
}
void AccStructureChecker::Leave(const parser::AccClauseList &) {}
void AccStructureChecker::Enter(const parser::OpenACCBlockConstruct &x) {
const auto &beginBlockDir{std::get<parser::AccBeginBlockDirective>(x.t)};
const auto &endBlockDir{std::get<parser::AccEndBlockDirective>(x.t)};
const auto &beginAccBlockDir{
std::get<parser::AccBlockDirective>(beginBlockDir.t)};
CheckMatching(beginAccBlockDir, endBlockDir.v);
PushContextAndClauseSets(beginAccBlockDir.source, beginAccBlockDir.v);
}
void AccStructureChecker::Leave(const parser::OpenACCBlockConstruct &x) {
const auto &beginBlockDir{std::get<parser::AccBeginBlockDirective>(x.t)};
const auto &blockDir{std::get<parser::AccBlockDirective>(beginBlockDir.t)};
const parser::Block &block{std::get<parser::Block>(x.t)};
switch (blockDir.v) {
case llvm::acc::Directive::ACCD_kernels:
case llvm::acc::Directive::ACCD_parallel:
case llvm::acc::Directive::ACCD_serial:
// Restriction - line 1004-1005
CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type,
computeConstructOnlyAllowedAfterDeviceTypeClauses);
// Restriction - line 1001
CheckNoBranching(block, GetContext().directive, blockDir.source);
break;
case llvm::acc::Directive::ACCD_data:
// Restriction - line 1249-1250
CheckRequireAtLeastOneOf();
break;
case llvm::acc::Directive::ACCD_host_data:
// Restriction - line 1746
CheckRequireAtLeastOneOf();
break;
default:
break;
}
dirContext_.pop_back();
}
void AccStructureChecker::Enter(
const parser::OpenACCStandaloneDeclarativeConstruct &x) {
const auto &declarativeDir{std::get<parser::AccDeclarativeDirective>(x.t)};
PushContextAndClauseSets(declarativeDir.source, declarativeDir.v);
}
void AccStructureChecker::Leave(
const parser::OpenACCStandaloneDeclarativeConstruct &x) {
// Restriction - line 2409
CheckAtLeastOneClause();
// Restriction - line 2417-2418 - In a Fortran module declaration section,
// only create, copyin, device_resident, and link clauses are allowed.
const auto &declarativeDir{std::get<parser::AccDeclarativeDirective>(x.t)};
const auto &scope{context_.FindScope(declarativeDir.source)};
const Scope &containingScope{GetProgramUnitContaining(scope)};
if (containingScope.kind() == Scope::Kind::Module) {
for (auto cl : GetContext().actualClauses) {
if (cl != llvm::acc::Clause::ACCC_create &&
cl != llvm::acc::Clause::ACCC_copyin &&
cl != llvm::acc::Clause::ACCC_device_resident &&
cl != llvm::acc::Clause::ACCC_link) {
context_.Say(GetContext().directiveSource,
"%s clause is not allowed on the %s directive in module "
"declaration "
"section"_err_en_US,
parser::ToUpperCaseLetters(
llvm::acc::getOpenACCClauseName(cl).str()),
ContextDirectiveAsFortran());
}
}
}
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCCombinedConstruct &x) {
const auto &beginCombinedDir{
std::get<parser::AccBeginCombinedDirective>(x.t)};
const auto &combinedDir{
std::get<parser::AccCombinedDirective>(beginCombinedDir.t)};
// check matching, End directive is optional
if (const auto &endCombinedDir{
std::get<std::optional<parser::AccEndCombinedDirective>>(x.t)}) {
CheckMatching<parser::AccCombinedDirective>(combinedDir, endCombinedDir->v);
}
PushContextAndClauseSets(combinedDir.source, combinedDir.v);
}
void AccStructureChecker::Leave(const parser::OpenACCCombinedConstruct &x) {
const auto &beginBlockDir{std::get<parser::AccBeginCombinedDirective>(x.t)};
const auto &combinedDir{
std::get<parser::AccCombinedDirective>(beginBlockDir.t)};
switch (combinedDir.v) {
case llvm::acc::Directive::ACCD_kernels_loop:
case llvm::acc::Directive::ACCD_parallel_loop:
case llvm::acc::Directive::ACCD_serial_loop:
// Restriction - line 1004-1005
CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type,
computeConstructOnlyAllowedAfterDeviceTypeClauses);
break;
default:
break;
}
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCLoopConstruct &x) {
const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)};
PushContextAndClauseSets(loopDir.source, loopDir.v);
}
void AccStructureChecker::Leave(const parser::OpenACCLoopConstruct &x) {
const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)};
if (loopDir.v == llvm::acc::Directive::ACCD_loop) {
// Restriction - line 1818-1819
CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type,
loopOnlyAllowedAfterDeviceTypeClauses);
// Restriction - line 1834
CheckNotAllowedIfClause(llvm::acc::Clause::ACCC_seq,
{llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_vector,
llvm::acc::Clause::ACCC_worker});
}
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCStandaloneConstruct &x) {
const auto &standaloneDir{std::get<parser::AccStandaloneDirective>(x.t)};
PushContextAndClauseSets(standaloneDir.source, standaloneDir.v);
}
void AccStructureChecker::Leave(const parser::OpenACCStandaloneConstruct &x) {
const auto &standaloneDir{std::get<parser::AccStandaloneDirective>(x.t)};
switch (standaloneDir.v) {
case llvm::acc::Directive::ACCD_enter_data:
case llvm::acc::Directive::ACCD_exit_data:
// Restriction - line 1310-1311 (ENTER DATA)
// Restriction - line 1312-1313 (EXIT DATA)
CheckRequireAtLeastOneOf();
break;
case llvm::acc::Directive::ACCD_set:
// Restriction - line 2610
CheckRequireAtLeastOneOf();
// Restriction - line 2602
CheckNotInComputeConstruct();
break;
case llvm::acc::Directive::ACCD_update:
// Restriction - line 2636
CheckRequireAtLeastOneOf();
// Restriction - line 2669
CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type,
updateOnlyAllowedAfterDeviceTypeClauses);
break;
case llvm::acc::Directive::ACCD_init:
case llvm::acc::Directive::ACCD_shutdown:
// Restriction - line 2525 (INIT)
// Restriction - line 2561 (SHUTDOWN)
CheckNotInComputeConstruct();
break;
default:
break;
}
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCRoutineConstruct &x) {
PushContextAndClauseSets(x.source, llvm::acc::Directive::ACCD_routine);
const auto &optName{std::get<std::optional<parser::Name>>(x.t)};
if (!optName) {
const auto &verbatim{std::get<parser::Verbatim>(x.t)};
const auto &scope{context_.FindScope(verbatim.source)};
const Scope &containingScope{GetProgramUnitContaining(scope)};
if (containingScope.kind() == Scope::Kind::Module) {
context_.Say(GetContext().directiveSource,
"ROUTINE directive without name must appear within the specification "
"part of a subroutine or function definition, or within an interface "
"body for a subroutine or function in an interface block"_err_en_US);
}
}
}
void AccStructureChecker::Leave(const parser::OpenACCRoutineConstruct &) {
// Restriction - line 2790
CheckRequireAtLeastOneOf();
// Restriction - line 2788-2789
CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type,
routineOnlyAllowedAfterDeviceTypeClauses);
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCWaitConstruct &x) {
const auto &verbatim{std::get<parser::Verbatim>(x.t)};
PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_wait);
}
void AccStructureChecker::Leave(const parser::OpenACCWaitConstruct &x) {
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCAtomicConstruct &x) {
PushContextAndClauseSets(x.source, llvm::acc::Directive::ACCD_atomic);
}
void AccStructureChecker::Leave(const parser::OpenACCAtomicConstruct &x) {
dirContext_.pop_back();
}
void AccStructureChecker::Enter(const parser::OpenACCCacheConstruct &x) {
const auto &verbatim = std::get<parser::Verbatim>(x.t);
PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_cache);
SetContextDirectiveSource(verbatim.source);
}
void AccStructureChecker::Leave(const parser::OpenACCCacheConstruct &x) {
dirContext_.pop_back();
}
// Clause checkers
CHECK_SIMPLE_CLAUSE(Auto, ACCC_auto)
CHECK_SIMPLE_CLAUSE(Async, ACCC_async)
CHECK_SIMPLE_CLAUSE(Attach, ACCC_attach)
CHECK_SIMPLE_CLAUSE(Bind, ACCC_bind)
CHECK_SIMPLE_CLAUSE(Capture, ACCC_capture)
CHECK_SIMPLE_CLAUSE(Copy, ACCC_copy)
CHECK_SIMPLE_CLAUSE(Default, ACCC_default)
CHECK_SIMPLE_CLAUSE(DefaultAsync, ACCC_default_async)
CHECK_SIMPLE_CLAUSE(Delete, ACCC_delete)
CHECK_SIMPLE_CLAUSE(Detach, ACCC_detach)
CHECK_SIMPLE_CLAUSE(Device, ACCC_device)
CHECK_SIMPLE_CLAUSE(DeviceNum, ACCC_device_num)
CHECK_SIMPLE_CLAUSE(Deviceptr, ACCC_deviceptr)
CHECK_SIMPLE_CLAUSE(DeviceResident, ACCC_device_resident)
CHECK_SIMPLE_CLAUSE(DeviceType, ACCC_device_type)
CHECK_SIMPLE_CLAUSE(Finalize, ACCC_finalize)
CHECK_SIMPLE_CLAUSE(Firstprivate, ACCC_firstprivate)
CHECK_SIMPLE_CLAUSE(Host, ACCC_host)
CHECK_SIMPLE_CLAUSE(If, ACCC_if)
CHECK_SIMPLE_CLAUSE(IfPresent, ACCC_if_present)
CHECK_SIMPLE_CLAUSE(Independent, ACCC_independent)
CHECK_SIMPLE_CLAUSE(Link, ACCC_link)
CHECK_SIMPLE_CLAUSE(NoCreate, ACCC_no_create)
CHECK_SIMPLE_CLAUSE(Nohost, ACCC_nohost)
CHECK_SIMPLE_CLAUSE(NumWorkers, ACCC_num_workers)
CHECK_SIMPLE_CLAUSE(Present, ACCC_present)
CHECK_SIMPLE_CLAUSE(Private, ACCC_private)
CHECK_SIMPLE_CLAUSE(Read, ACCC_read)
CHECK_SIMPLE_CLAUSE(Seq, ACCC_seq)
CHECK_SIMPLE_CLAUSE(Tile, ACCC_tile)
CHECK_SIMPLE_CLAUSE(UseDevice, ACCC_use_device)
CHECK_SIMPLE_CLAUSE(Vector, ACCC_vector)
CHECK_SIMPLE_CLAUSE(VectorLength, ACCC_vector_length)
CHECK_SIMPLE_CLAUSE(Wait, ACCC_wait)
CHECK_SIMPLE_CLAUSE(Worker, ACCC_worker)
CHECK_SIMPLE_CLAUSE(Write, ACCC_write)
CHECK_SIMPLE_CLAUSE(Unknown, ACCC_unknown)
void AccStructureChecker::Enter(const parser::AccClause::Create &c) {
CheckAllowed(llvm::acc::Clause::ACCC_create);
const auto &modifierClause{c.v};
if (const auto &modifier{
std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) {
if (modifier->v != parser::AccDataModifier::Modifier::Zero) {
context_.Say(GetContext().clauseSource,
"Only the ZERO modifier is allowed for the %s clause "
"on the %s directive"_err_en_US,
parser::ToUpperCaseLetters(
llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_create)
.str()),
ContextDirectiveAsFortran());
}
}
}
void AccStructureChecker::Enter(const parser::AccClause::Copyin &c) {
CheckAllowed(llvm::acc::Clause::ACCC_copyin);
const auto &modifierClause{c.v};
if (const auto &modifier{
std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) {
if (CheckAllowedModifier(llvm::acc::Clause::ACCC_copyin)) {
return;
}
if (modifier->v != parser::AccDataModifier::Modifier::ReadOnly) {
context_.Say(GetContext().clauseSource,
"Only the READONLY modifier is allowed for the %s clause "
"on the %s directive"_err_en_US,
parser::ToUpperCaseLetters(
llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_copyin)
.str()),
ContextDirectiveAsFortran());
}
}
}
void AccStructureChecker::Enter(const parser::AccClause::Copyout &c) {
CheckAllowed(llvm::acc::Clause::ACCC_copyout);
const auto &modifierClause{c.v};
if (const auto &modifier{
std::get<std::optional<parser::AccDataModifier>>(modifierClause.t)}) {
if (CheckAllowedModifier(llvm::acc::Clause::ACCC_copyout)) {
return;
}
if (modifier->v != parser::AccDataModifier::Modifier::Zero) {
context_.Say(GetContext().clauseSource,
"Only the ZERO modifier is allowed for the %s clause "
"on the %s directive"_err_en_US,
parser::ToUpperCaseLetters(
llvm::acc::getOpenACCClauseName(llvm::acc::Clause::ACCC_copyout)
.str()),
ContextDirectiveAsFortran());
}
}
}
void AccStructureChecker::Enter(const parser::AccClause::Gang &g) {
CheckAllowed(llvm::acc::Clause::ACCC_gang);
if (g.v) {
bool hasNum = false;
bool hasDim = false;
const Fortran::parser::AccGangArgList &x = *g.v;
for (const Fortran::parser::AccGangArg &gangArg : x.v) {
if (std::get_if<Fortran::parser::AccGangArg::Num>(&gangArg.u))
hasNum = true;
else if (std::get_if<Fortran::parser::AccGangArg::Dim>(&gangArg.u))
hasDim = true;
}
if (hasDim && hasNum)
context_.Say(GetContext().clauseSource,
"The num argument is not allowed when dim is specified"_err_en_US);
}
}
void AccStructureChecker::Enter(const parser::AccClause::NumGangs &n) {
CheckAllowed(llvm::acc::Clause::ACCC_num_gangs);
if (n.v.size() > 3)
context_.Say(GetContext().clauseSource,
"NUM_GANGS clause accepts a maximum of 3 arguments"_err_en_US);
}
void AccStructureChecker::Enter(const parser::AccClause::Reduction &reduction) {
CheckAllowed(llvm::acc::Clause::ACCC_reduction);
// From OpenACC 3.3
// At a minimum, the supported data types include Fortran logical as well as
// the numerical data types (e.g. integer, real, double precision, complex).
// However, for each reduction operator, the supported data types include only
// the types permitted as operands to the corresponding operator in the base
// language where (1) for max and min, the corresponding operator is less-than
// and (2) for other operators, the operands and the result are the same type.
//
// The following check that the reduction operator is supported with the given
// type.
const parser::AccObjectListWithReduction &list{reduction.v};
const auto &op{std::get<parser::AccReductionOperator>(list.t)};
const auto &objects{std::get<parser::AccObjectList>(list.t)};
for (const auto &object : objects.v) {
std::visit(
Fortran::common::visitors{
[&](const Fortran::parser::Designator &designator) {
if (const auto *name = getDesignatorNameIfDataRef(designator)) {
const auto *type{name->symbol->GetType()};
if (type->IsNumeric(TypeCategory::Integer) &&
!reductionIntegerSet.test(op.v)) {
context_.Say(GetContext().clauseSource,
"reduction operator not supported for integer type"_err_en_US);
} else if (type->IsNumeric(TypeCategory::Real) &&
!reductionRealSet.test(op.v)) {
context_.Say(GetContext().clauseSource,
"reduction operator not supported for real type"_err_en_US);
} else if (type->IsNumeric(TypeCategory::Complex) &&
!reductionComplexSet.test(op.v)) {
context_.Say(GetContext().clauseSource,
"reduction operator not supported for complex type"_err_en_US);
} else if (type->category() ==
Fortran::semantics::DeclTypeSpec::Category::Logical &&
!reductionLogicalSet.test(op.v)) {
context_.Say(GetContext().clauseSource,
"reduction operator not supported for logical type"_err_en_US);
}
// TODO: check composite type.
}
},
[&](const Fortran::parser::Name &name) {
// TODO: check common block
}},
object.u);
}
}
void AccStructureChecker::Enter(const parser::AccClause::Self &x) {
CheckAllowed(llvm::acc::Clause::ACCC_self);
const std::optional<parser::AccSelfClause> &accSelfClause = x.v;
if (GetContext().directive == llvm::acc::Directive::ACCD_update &&
((accSelfClause &&
std::holds_alternative<std::optional<parser::ScalarLogicalExpr>>(
(*accSelfClause).u)) ||
!accSelfClause)) {
context_.Say(GetContext().clauseSource,
"SELF clause on the %s directive must have a var-list"_err_en_US,
ContextDirectiveAsFortran());
} else if (GetContext().directive != llvm::acc::Directive::ACCD_update &&
accSelfClause &&
std::holds_alternative<parser::AccObjectList>((*accSelfClause).u)) {
const auto &accObjectList =
std::get<parser::AccObjectList>((*accSelfClause).u);
if (accObjectList.v.size() != 1) {
context_.Say(GetContext().clauseSource,
"SELF clause on the %s directive only accepts optional scalar logical"
" expression"_err_en_US,
ContextDirectiveAsFortran());
}
}
}
void AccStructureChecker::Enter(const parser::AccClause::Collapse &x) {
CheckAllowed(llvm::acc::Clause::ACCC_collapse);
const parser::AccCollapseArg &accCollapseArg = x.v;
const auto &collapseValue{
std::get<parser::ScalarIntConstantExpr>(accCollapseArg.t)};
RequiresConstantPositiveParameter(
llvm::acc::Clause::ACCC_collapse, collapseValue);
}
llvm::StringRef AccStructureChecker::getClauseName(llvm::acc::Clause clause) {
return llvm::acc::getOpenACCClauseName(clause);
}
llvm::StringRef AccStructureChecker::getDirectiveName(
llvm::acc::Directive directive) {
return llvm::acc::getOpenACCDirectiveName(directive);
}
} // namespace Fortran::semantics