//===-- 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/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); \ } namespace Fortran::semantics { static constexpr inline AccClauseSet parallelAndKernelsOnlyAllowedAfterDeviceTypeClauses{ 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 serialOnlyAllowedAfterDeviceTypeClauses{ llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait}; 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}; class NoBranchingEnforce { public: NoBranchingEnforce(SemanticsContext &context, parser::CharBlock sourcePosition, llvm::acc::Directive directive) : context_{context}, sourcePosition_{sourcePosition}, currentDirective_{ directive} {} template bool Pre(const T &) { return true; } template void Post(const T &) {} template bool Pre(const parser::Statement &statement) { currentStatementSourcePosition_ = statement.source; return true; } void Post(const parser::ReturnStmt &) { emitBranchOutError("RETURN"); } void Post(const parser::ExitStmt &) { emitBranchOutError("EXIT"); } void Post(const parser::StopStmt &) { emitBranchOutError("STOP"); } private: parser::MessageFixedText GetEnclosingMsg() { return "Enclosing block construct"_en_US; } void emitBranchOutError(const char *stmt) { context_ .Say(currentStatementSourcePosition_, "%s statement is not allowed in a %s construct"_err_en_US, stmt, parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName(currentDirective_).str())) .Attach(sourcePosition_, GetEnclosingMsg()); } SemanticsContext &context_; parser::CharBlock currentStatementSourcePosition_; parser::CharBlock sourcePosition_; llvm::acc::Directive currentDirective_; }; 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(x.t)}; const auto &endBlockDir{std::get(x.t)}; const auto &beginAccBlockDir{ std::get(beginBlockDir.t)}; CheckMatching(beginAccBlockDir, endBlockDir.v); PushContextAndClauseSets(beginAccBlockDir.source, beginAccBlockDir.v); } void AccStructureChecker::Leave(const parser::OpenACCBlockConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &blockDir{std::get(beginBlockDir.t)}; const parser::Block &block{std::get(x.t)}; switch (blockDir.v) { case llvm::acc::Directive::ACCD_kernels: case llvm::acc::Directive::ACCD_parallel: // Restriction - 880-881 (KERNELS) // Restriction - 843-844 (PARALLEL) CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, parallelAndKernelsOnlyAllowedAfterDeviceTypeClauses); // Restriction - 877 (KERNELS) // Restriction - 840 (PARALLEL) CheckNoBranching(block, GetContext().directive, blockDir.source); break; case llvm::acc::Directive::ACCD_serial: // Restriction - 919 CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, serialOnlyAllowedAfterDeviceTypeClauses); // Restriction - 916 CheckNoBranching(block, llvm::acc::Directive::ACCD_serial, blockDir.source); break; case llvm::acc::Directive::ACCD_data: // Restriction - 1117-1118 CheckRequireAtLeastOneOf(); break; case llvm::acc::Directive::ACCD_host_data: // Restriction - 1578 CheckRequireAtLeastOneOf(); break; default: break; } accContext_.pop_back(); } void AccStructureChecker::CheckNoBranching(const parser::Block &block, const llvm::acc::Directive directive, const parser::CharBlock &directiveSource) const { NoBranchingEnforce noBranchingEnforce{context_, directiveSource, directive}; parser::Walk(block, noBranchingEnforce); } void AccStructureChecker::Enter( const parser::OpenACCStandaloneDeclarativeConstruct &x) { const auto &declarativeDir{std::get(x.t)}; PushContextAndClauseSets(declarativeDir.source, declarativeDir.v); } void AccStructureChecker::Leave( const parser::OpenACCStandaloneDeclarativeConstruct &) { // Restriction - 2075 CheckAtLeastOneClause(); accContext_.pop_back(); } void AccStructureChecker::Enter(const parser::OpenACCCombinedConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &combinedDir{ std::get(beginBlockDir.t)}; PushContextAndClauseSets(combinedDir.source, combinedDir.v); } void AccStructureChecker::Leave(const parser::OpenACCCombinedConstruct &x) { const auto &beginBlockDir{std::get(x.t)}; const auto &combinedDir{ std::get(beginBlockDir.t)}; switch (combinedDir.v) { case llvm::acc::Directive::ACCD_kernels_loop: case llvm::acc::Directive::ACCD_parallel_loop: // Restriction - 1962 -> (880-881) (KERNELS LOOP) // Restriction - 1962 -> (843-844) (PARALLEL LOOP) CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, {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}); break; case llvm::acc::Directive::ACCD_serial_loop: // Restriction - 1962 -> (919) (SERIAL LOOP) CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, {llvm::acc::Clause::ACCC_async, llvm::acc::Clause::ACCC_wait}); break; default: break; } accContext_.pop_back(); } std::string AccStructureChecker::ContextDirectiveAsFortran() { return parser::ToUpperCaseLetters( llvm::acc::getOpenACCDirectiveName(GetContext().directive).str()); } void AccStructureChecker::Enter(const parser::OpenACCLoopConstruct &x) { const auto &beginDir{std::get(x.t)}; const auto &loopDir{std::get(beginDir.t)}; PushContextAndClauseSets(loopDir.source, loopDir.v); } void AccStructureChecker::Leave(const parser::OpenACCLoopConstruct &x) { const auto &beginDir{std::get(x.t)}; const auto &loopDir{std::get(beginDir.t)}; if (loopDir.v == llvm::acc::Directive::ACCD_loop) { // Restriction - 1615-1616 CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, loopOnlyAllowedAfterDeviceTypeClauses); // Restriction - 1622 CheckNotAllowedIfClause(llvm::acc::Clause::ACCC_seq, {llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_vector, llvm::acc::Clause::ACCC_worker}); } accContext_.pop_back(); } void AccStructureChecker::Enter(const parser::OpenACCStandaloneConstruct &x) { const auto &standaloneDir{std::get(x.t)}; PushContextAndClauseSets(standaloneDir.source, standaloneDir.v); } void AccStructureChecker::Leave(const parser::OpenACCStandaloneConstruct &x) { const auto &standaloneDir{std::get(x.t)}; switch (standaloneDir.v) { case llvm::acc::Directive::ACCD_enter_data: case llvm::acc::Directive::ACCD_exit_data: case llvm::acc::Directive::ACCD_set: // Restriction - 1117-1118 (ENTER DATA) // Restriction - 1161-1162 (EXIT DATA) // Restriction - 2254 (SET) CheckRequireAtLeastOneOf(); break; case llvm::acc::Directive::ACCD_update: // Restriction - 2301 CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, updateOnlyAllowedAfterDeviceTypeClauses); break; default: break; } accContext_.pop_back(); } void AccStructureChecker::Enter(const parser::OpenACCRoutineConstruct &x) { PushContextAndClauseSets(x.source, llvm::acc::Directive::ACCD_routine); } void AccStructureChecker::Leave(const parser::OpenACCRoutineConstruct &) { // Restriction - 2409 CheckRequireAtLeastOneOf(); // Restriction - 2407-2408 CheckOnlyAllowedAfter(llvm::acc::Clause::ACCC_device_type, routineOnlyAllowedAfterDeviceTypeClauses); accContext_.pop_back(); } // Clause checkers CHECK_REQ_SCALAR_INT_CONSTANT_CLAUSE(Collapse, ACCC_collapse) 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(Gang, ACCC_gang) 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(NumGangs, ACCC_num_gangs) 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(Reduction, ACCC_reduction) CHECK_SIMPLE_CLAUSE(Self, ACCC_self) 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) 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>(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>(modifierClause.t)}) { 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>(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_copyout) .str()), ContextDirectiveAsFortran()); } } } void AccStructureChecker::CheckAllowed(llvm::acc::Clause clause) { if (!GetContext().allowedClauses.test(clause) && !GetContext().allowedOnceClauses.test(clause) && !GetContext().allowedExclusiveClauses.test(clause) && !GetContext().requiredClauses.test(clause)) { context_.Say(GetContext().clauseSource, "%s clause is not allowed on the %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), parser::ToUpperCaseLetters(GetContext().directiveSource.ToString())); return; } if ((GetContext().allowedOnceClauses.test(clause) || GetContext().allowedExclusiveClauses.test(clause)) && FindClause(clause)) { context_.Say(GetContext().clauseSource, "At most one %s clause can appear on the %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), parser::ToUpperCaseLetters(GetContext().directiveSource.ToString())); return; } if (GetContext().allowedExclusiveClauses.test(clause)) { std::vector others; GetContext().allowedExclusiveClauses.IterateOverMembers( [&](llvm::acc::Clause o) { if (FindClause(o)) { others.emplace_back(o); } }); for (const auto &e : others) { context_.Say(GetContext().clauseSource, "%s and %s clauses are mutually exclusive and may not appear on the " "same %s directive"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), parser::ToUpperCaseLetters(llvm::acc::getOpenACCClauseName(e).str()), parser::ToUpperCaseLetters(GetContext().directiveSource.ToString())); } if (!others.empty()) { return; } } SetContextClauseInfo(clause); AddClauseToCrtContext(clause); } void AccStructureChecker::CheckOnlyAllowedAfter( llvm::acc::Clause clause, AccClauseSet set) { bool enforceCheck = false; for (auto cl : GetContext().actualClauses) { if (cl == clause) { enforceCheck = true; continue; } else if (enforceCheck && !set.test(cl)) { auto parserClause = GetContext().clauseInfo.find(cl); context_.Say(parserClause->second->source, "Clause %s is not allowed after clause %s on the %s " "directive"_err_en_US, parser::ToUpperCaseLetters(llvm::acc::getOpenACCClauseName(cl).str()), parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), ContextDirectiveAsFortran()); } } } void AccStructureChecker::CheckRequireAtLeastOneOf() { for (auto cl : GetContext().actualClauses) { if (GetContext().requiredClauses.test(cl)) return; } // No clause matched in the actual clauses list context_.Say(GetContext().directiveSource, "At least one of %s clause must appear on the %s directive"_err_en_US, ClauseSetToString(GetContext().requiredClauses), ContextDirectiveAsFortran()); } void AccStructureChecker::CheckAtLeastOneClause() { if (GetContext().actualClauses.empty()) { context_.Say(GetContext().directiveSource, "At least one clause is required on the %s directive"_err_en_US, ContextDirectiveAsFortran()); } } // Enforce restriction where clauses in the given set are not allowed if the // given clause appears. void AccStructureChecker::CheckNotAllowedIfClause( llvm::acc::Clause clause, AccClauseSet set) { if (std::find(GetContext().actualClauses.begin(), GetContext().actualClauses.end(), clause) == GetContext().actualClauses.end()) { return; // Clause is not present } for (auto cl : GetContext().actualClauses) { if (set.test(cl)) { context_.Say(GetContext().directiveSource, "Clause %s is not allowed if clause %s appears on the %s directive"_err_en_US, parser::ToUpperCaseLetters(llvm::acc::getOpenACCClauseName(cl).str()), parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), ContextDirectiveAsFortran()); } } } void AccStructureChecker::RequiresConstantPositiveParameter( const llvm::acc::Clause &clause, const parser::ScalarIntConstantExpr &i) { if (const auto v{GetIntValue(i)}) { if (*v <= 0) { context_.Say(GetContext().clauseSource, "The parameter of the %s clause on the %s directive must be " "a constant positive integer expression"_err_en_US, parser::ToUpperCaseLetters( llvm::acc::getOpenACCClauseName(clause).str()), ContextDirectiveAsFortran()); } } } void AccStructureChecker::OptionalConstantPositiveParameter( const llvm::acc::Clause &clause, const std::optional &o) { if (o != std::nullopt) { RequiresConstantPositiveParameter(clause, o.value()); } } std::string AccStructureChecker::ClauseSetToString(const AccClauseSet set) { std::string list; set.IterateOverMembers([&](llvm::acc::Clause o) { if (!list.empty()) list.append(", "); list.append( parser::ToUpperCaseLetters(llvm::acc::getOpenACCClauseName(o).str())); }); return list; } void AccStructureChecker::SayNotMatching( const parser::CharBlock &beginSource, const parser::CharBlock &endSource) { context_ .Say(endSource, "Unmatched %s directive"_err_en_US, parser::ToUpperCaseLetters(endSource.ToString())) .Attach(beginSource, "Does not match directive"_en_US); } } // namespace Fortran::semantics