diff options
Diffstat (limited to 'flang/lib/Semantics/check-omp-structure.cpp')
| -rw-r--r-- | flang/lib/Semantics/check-omp-structure.cpp | 217 |
1 files changed, 195 insertions, 22 deletions
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp index 4a5e0b412874..d1654a3adcc9 100644 --- a/flang/lib/Semantics/check-omp-structure.cpp +++ b/flang/lib/Semantics/check-omp-structure.cpp @@ -143,6 +143,64 @@ private: parser::CharBlock source_; }; +// 'OmpWorkdistributeBlockChecker' is used to check the validity of the +// assignment statements and the expressions enclosed in an OpenMP +// WORKDISTRIBUTE construct +class OmpWorkdistributeBlockChecker { +public: + OmpWorkdistributeBlockChecker( + 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(context_, var)}; + const auto *rhs{GetExpr(context_, expr)}; + if (lhs && rhs) { + 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 WORKDISTRIBUTE construct"_err_en_US); + } + } + return true; + } + + bool Pre(const parser::Expr &expr) { + if (const auto *e{GetExpr(context_, expr)}) { + if (!e) + return false; + for (const Symbol &symbol : evaluate::CollectSymbols(*e)) { + const Symbol &root{GetAssociationRoot(symbol)}; + if (IsFunction(root)) { + std::vector<std::string> attrs; + if (!IsElementalProcedure(root)) { + attrs.push_back("non-ELEMENTAL"); + } + if (root.attrs().test(Attr::IMPURE)) { + attrs.push_back("IMPURE"); + } + std::string attrsStr = + attrs.empty() ? "" : " " + llvm::join(attrs, ", "); + context_.Say(expr.source, + "User defined%s function '%s' is not allowed in a WORKDISTRIBUTE construct"_err_en_US, + attrsStr, root.name()); + } + } + } + return false; + } + +private: + SemanticsContext &context_; + parser::CharBlock source_; +}; + // `OmpUnitedTaskDesignatorChecker` is used to check if the designator // can appear within the TASK construct class OmpUnitedTaskDesignatorChecker { @@ -210,6 +268,41 @@ bool OmpStructureChecker::CheckAllowedClause(llvmOmpClause clause) { return CheckAllowed(clause); } +void OmpStructureChecker::AnalyzeObject(const parser::OmpObject &object) { + if (std::holds_alternative<parser::Name>(object.u)) { + // Do not analyze common block names. The analyzer will flag an error + // on those. + return; + } + if (auto *symbol{GetObjectSymbol(object)}) { + // Eliminate certain kinds of symbols before running the analyzer to + // avoid confusing error messages. The analyzer assumes that the context + // of the object use is an expression, and some diagnostics are tailored + // to that. + if (symbol->has<DerivedTypeDetails>() || symbol->has<MiscDetails>()) { + // Type names, construct names, etc. + return; + } + if (auto *typeSpec{symbol->GetType()}) { + if (typeSpec->category() == DeclTypeSpec::Category::Character) { + // Don't pass character objects to the analyzer, it can emit somewhat + // cryptic errors (e.g. "'obj' is not an array"). Substrings are + // checked elsewhere in OmpStructureChecker. + return; + } + } + } + evaluate::ExpressionAnalyzer ea{context_}; + auto restore{ea.AllowWholeAssumedSizeArray(true)}; + common::visit([&](auto &&s) { ea.Analyze(s); }, object.u); +} + +void OmpStructureChecker::AnalyzeObjects(const parser::OmpObjectList &objects) { + for (const parser::OmpObject &object : objects.v) { + AnalyzeObject(object); + } +} + bool OmpStructureChecker::IsCloselyNestedRegion(const OmpDirectiveSet &set) { // Definition of close nesting: // @@ -531,14 +624,6 @@ template <typename Checker> struct DirectiveSpellingVisitor { checker_(GetDirName(x.t).source, Directive::OMPD_allocators); return false; } - bool Pre(const parser::OmpAssumeDirective &x) { - checker_(std::get<parser::Verbatim>(x.t).source, Directive::OMPD_assume); - return false; - } - bool Pre(const parser::OmpEndAssumeDirective &x) { - checker_(x.v.source, Directive::OMPD_assume); - return false; - } bool Pre(const parser::OmpMetadirectiveDirective &x) { checker_( std::get<parser::Verbatim>(x.t).source, Directive::OMPD_metadirective); @@ -729,7 +814,7 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) { parser::CharBlock source; common::visit( common::visitors{ - [&](const parser::OpenMPBlockConstruct &c) { + [&](const parser::OmpBlockConstruct &c) { const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()}; source = beginSpec.DirName().source; if (beginSpec.DirId() == llvm::omp::Directive::OMPD_target_data) { @@ -779,12 +864,44 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) { } } -void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { +void OmpStructureChecker::Enter(const parser::OmpBlockConstruct &x) { const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; const std::optional<parser::OmpEndDirective> &endSpec{x.EndDir()}; const parser::Block &block{std::get<parser::Block>(x.t)}; PushContextAndClauseSets(beginSpec.DirName().source, beginSpec.DirId()); + + // Missing mandatory end block: this is checked in semantics because that + // makes it easier to control the error messages. + // The end block is mandatory when the construct is not applied to a strictly + // structured block (aka it is applied to a loosely structured block). In + // other words, the body doesn't contain exactly one parser::BlockConstruct. + auto isStrictlyStructuredBlock{[](const parser::Block &block) -> bool { + if (block.size() != 1) { + return false; + } + const parser::ExecutionPartConstruct &contents{block.front()}; + auto *executableConstruct{ + std::get_if<parser::ExecutableConstruct>(&contents.u)}; + if (!executableConstruct) { + return false; + } + return std::holds_alternative<common::Indirection<parser::BlockConstruct>>( + executableConstruct->u); + }}; + if (!endSpec && !isStrictlyStructuredBlock(block)) { + llvm::omp::Directive dirId{beginSpec.DirId()}; + auto &msg{context_.Say(beginSpec.source, + "Expected OpenMP END %s directive"_err_en_US, + parser::ToUpperCaseLetters(getDirectiveName(dirId)))}; + // ORDERED has two variants, so be explicit about which variant we think + // this is. + if (dirId == llvm::omp::Directive::OMPD_ordered) { + msg.Attach( + beginSpec.source, "The ORDERED directive is block-associated"_en_US); + } + } + if (llvm::omp::allTargetSet.test(GetContext().directive)) { EnterDirectiveNest(TargetNest); } @@ -815,6 +932,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { "TARGET construct with nested TEAMS region contains statements or " "directives outside of the TEAMS construct"_err_en_US); } + if (GetContext().directive == llvm::omp::Directive::OMPD_workdistribute && + GetContextParent().directive != llvm::omp::Directive::OMPD_teams) { + context_.Say(x.BeginDir().DirName().source, + "%s region can only be strictly nested within TEAMS region"_err_en_US, + ContextDirectiveAsFortran()); + } } CheckNoBranching(block, beginSpec.DirId(), beginSpec.source); @@ -898,6 +1021,17 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { HasInvalidWorksharingNesting( beginSpec.source, llvm::omp::nestedWorkshareErrSet); break; + case llvm::omp::OMPD_workdistribute: + if (!CurrentDirectiveIsNested()) { + context_.Say(beginSpec.source, + "A WORKDISTRIBUTE region must be nested inside TEAMS region only."_err_en_US); + } + CheckWorkdistributeBlockStmts(block, beginSpec.source); + break; + case llvm::omp::OMPD_teams_workdistribute: + case llvm::omp::OMPD_target_teams_workdistribute: + CheckWorkdistributeBlockStmts(block, beginSpec.source); + break; case llvm::omp::Directive::OMPD_scope: case llvm::omp::Directive::OMPD_single: // TODO: This check needs to be extended while implementing nesting of @@ -919,7 +1053,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { } void OmpStructureChecker::CheckMasterNesting( - const parser::OpenMPBlockConstruct &x) { + const parser::OmpBlockConstruct &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 @@ -948,7 +1082,7 @@ void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAssumes &) { dirContext_.pop_back(); } -void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) { +void OmpStructureChecker::Leave(const parser::OmpBlockConstruct &) { if (GetDirectiveNest(TargetBlockOnlyTeams)) { ExitDirectiveNest(TargetBlockOnlyTeams); } @@ -1039,14 +1173,23 @@ void OmpStructureChecker::Leave(const parser::OmpBeginDirective &) { 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 &endSectionsDir{ + std::get<std::optional<parser::OmpEndSectionsDirective>>(x.t)}; const auto &beginDir{ std::get<parser::OmpSectionsDirective>(beginSectionsDir.t)}; - const auto &endDir{std::get<parser::OmpSectionsDirective>(endSectionsDir.t)}; + PushContextAndClauseSets(beginDir.source, beginDir.v); + + if (!endSectionsDir) { + context_.Say(beginSectionsDir.source, + "Expected OpenMP END SECTIONS directive"_err_en_US); + // Following code assumes the option is present. + return; + } + + const auto &endDir{std::get<parser::OmpSectionsDirective>(endSectionsDir->t)}; CheckMatching<parser::OmpSectionsDirective>(beginDir, endDir); - PushContextAndClauseSets(beginDir.source, beginDir.v); - AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endSectionsDir.t)); + AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endSectionsDir->t)); const auto §ionBlocks{std::get<std::list<parser::OpenMPConstruct>>(x.t)}; for (const parser::OpenMPConstruct &construct : sectionBlocks) { @@ -2597,8 +2740,9 @@ void OmpStructureChecker::Leave(const parser::OmpClauseList &) { void OmpStructureChecker::Enter(const parser::OmpClause &x) { SetContextClause(x); + llvm::omp::Clause id{x.Id()}; // The visitors for these clauses do their own checks. - switch (x.Id()) { + switch (id) { case llvm::omp::Clause::OMPC_copyprivate: case llvm::omp::Clause::OMPC_enter: case llvm::omp::Clause::OMPC_lastprivate: @@ -2612,7 +2756,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause &x) { // Named constants are OK to be used within 'shared' and 'firstprivate' // clauses. The check for this happens a few lines below. bool SharedOrFirstprivate = false; - switch (x.Id()) { + switch (id) { case llvm::omp::Clause::OMPC_shared: case llvm::omp::Clause::OMPC_firstprivate: SharedOrFirstprivate = true; @@ -2622,6 +2766,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause &x) { } if (const parser::OmpObjectList *objList{GetOmpObjectList(x)}) { + AnalyzeObjects(*objList); SymbolSourceMap symbols; GetSymbolsInObjectList(*objList, symbols); for (const auto &[symbol, source] : symbols) { @@ -2648,6 +2793,8 @@ CHECK_SIMPLE_CLAUSE(Final, OMPC_final) CHECK_SIMPLE_CLAUSE(Flush, OMPC_flush) CHECK_SIMPLE_CLAUSE(Full, OMPC_full) CHECK_SIMPLE_CLAUSE(Grainsize, OMPC_grainsize) +CHECK_SIMPLE_CLAUSE(GraphId, OMPC_graph_id) +CHECK_SIMPLE_CLAUSE(GraphReset, OMPC_graph_reset) CHECK_SIMPLE_CLAUSE(Holds, OMPC_holds) CHECK_SIMPLE_CLAUSE(Inclusive, OMPC_inclusive) CHECK_SIMPLE_CLAUSE(Initializer, OMPC_initializer) @@ -3069,7 +3216,7 @@ void OmpStructureChecker::CheckReductionObjectTypes( // r = 0; r = r + r2 // But it might be valid to use these with DECLARE REDUCTION. // Assumed size is already caught elsewhere. - bool cannotBeBuiltinReduction{evaluate::IsAssumedRank(*symbol)}; + bool cannotBeBuiltinReduction{IsAssumedRank(*symbol)}; if (auto *type{symbol->GetType()}) { const auto &scope{context_.FindScope(symbol->name())}; if (!IsReductionAllowedForType( @@ -3352,9 +3499,14 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Aligned &x) { x.v, llvm::omp::OMPC_aligned, GetContext().clauseSource, context_)) { auto &modifiers{OmpGetModifiers(x.v)}; if (auto *align{OmpGetUniqueModifier<parser::OmpAlignment>(modifiers)}) { - if (const auto &v{GetIntValue(align->v)}; !v || *v <= 0) { + const auto &v{GetIntValue(align->v)}; + if (!v || *v <= 0) { context_.Say(OmpGetModifierSource(modifiers, align), "The alignment value should be a constant positive integer"_err_en_US); + } else if (((*v) & (*v - 1)) != 0) { + context_.Warn(common::UsageWarning::OpenMPUsage, + OmpGetModifierSource(modifiers, align), + "Alignment is not a power of 2, Aligned clause will be ignored"_warn_en_US); } } } @@ -4463,7 +4615,7 @@ bool OmpStructureChecker::CheckTargetBlockOnlyTeams( if (const auto *ompConstruct{ parser::Unwrap<parser::OpenMPConstruct>(*it)}) { if (const auto *ompBlockConstruct{ - std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) { + std::get_if<parser::OmpBlockConstruct>(&ompConstruct->u)}) { llvm::omp::Directive dirId{ompBlockConstruct->BeginDir().DirId()}; if (dirId == llvm::omp::Directive::OMPD_teams) { nestedTeams = true; @@ -4510,7 +4662,7 @@ void OmpStructureChecker::CheckWorkshareBlockStmts( // 'Parallel' constructs auto currentDir{llvm::omp::Directive::OMPD_unknown}; if (const auto *ompBlockConstruct{ - std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) { + std::get_if<parser::OmpBlockConstruct>(&ompConstruct->u)}) { currentDir = ompBlockConstruct->BeginDir().DirId(); } else if (const auto *ompLoopConstruct{ std::get_if<parser::OpenMPLoopConstruct>( @@ -4546,6 +4698,27 @@ void OmpStructureChecker::CheckWorkshareBlockStmts( } } +void OmpStructureChecker::CheckWorkdistributeBlockStmts( + const parser::Block &block, parser::CharBlock source) { + unsigned version{context_.langOptions().OpenMPVersion}; + unsigned since{60}; + if (version < since) + context_.Say(source, + "WORKDISTRIBUTE construct is not allowed in %s, %s"_err_en_US, + ThisVersion(version), TryVersion(since)); + + OmpWorkdistributeBlockChecker ompWorkdistributeBlockChecker{context_, source}; + + for (auto it{block.begin()}; it != block.end(); ++it) { + if (parser::Unwrap<parser::AssignmentStmt>(*it)) { + parser::Walk(*it, ompWorkdistributeBlockChecker); + } else { + context_.Say(source, + "The structured block in a WORKDISTRIBUTE construct may consist of only SCALAR or ARRAY assignments"_err_en_US); + } + } +} + void OmpStructureChecker::CheckIfContiguous(const parser::OmpObject &object) { if (auto contig{IsContiguous(context_, object)}; contig && !*contig) { const parser::Name *name{GetObjectName(object)}; |
