diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h index bd0debe297916..78504276726fd 100644 --- a/flang/include/flang/Parser/parse-tree.h +++ b/flang/include/flang/Parser/parse-tree.h @@ -4435,7 +4435,7 @@ struct OmpGrainsizeClause { // graph_id-clause -> // GRAPH_ID(graph-id-value) // since 6.0 struct OmpGraphIdClause { - WRAPPER_CLASS_BOILERPLATE(OmpGraphIdClause, common::Indirection); + WRAPPER_CLASS_BOILERPLATE(OmpGraphIdClause, ScalarIntExpr); }; // Ref: [6.0:438-439] @@ -4443,7 +4443,7 @@ struct OmpGraphIdClause { // graph_reset-clause -> // GRAPH_RESET[(graph-reset-expression)] // since 6.0 struct OmpGraphResetClause { - WRAPPER_CLASS_BOILERPLATE(OmpGraphResetClause, common::Indirection); + WRAPPER_CLASS_BOILERPLATE(OmpGraphResetClause, ScalarLogicalExpr); }; // Ref: [5.0:234-242], [5.1:266-275], [5.2:299], [6.0:472-473] diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h index 68318d6093a1e..e7b0ab11825ec 100644 --- a/flang/include/flang/Semantics/openmp-utils.h +++ b/flang/include/flang/Semantics/openmp-utils.h @@ -74,6 +74,8 @@ std::optional GetEvaluateExpr(const parser::Expr &parserExpr); std::optional GetDynamicType( const parser::Expr &parserExpr); +std::optional GetLogicalValue(const SomeExpr &expr); + std::optional IsContiguous( SemanticsContext &semaCtx, const parser::OmpObject &object); diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp index 42b62413f4a26..48b90ccea2f2a 100644 --- a/flang/lib/Lower/OpenMP/Clauses.cpp +++ b/flang/lib/Lower/OpenMP/Clauses.cpp @@ -221,8 +221,6 @@ MAKE_EMPTY_CLASS(Capture, Capture); MAKE_EMPTY_CLASS(Compare, Compare); MAKE_EMPTY_CLASS(DynamicAllocators, DynamicAllocators); MAKE_EMPTY_CLASS(Full, Full); -MAKE_EMPTY_CLASS(GraphId, GraphId); -MAKE_EMPTY_CLASS(GraphReset, GraphReset); MAKE_EMPTY_CLASS(Inbranch, Inbranch); MAKE_EMPTY_CLASS(Mergeable, Mergeable); MAKE_EMPTY_CLASS(Nogroup, Nogroup); @@ -258,6 +256,8 @@ MAKE_EMPTY_CLASS(Groupprivate, Groupprivate); MAKE_INCOMPLETE_CLASS(AdjustArgs, AdjustArgs); MAKE_INCOMPLETE_CLASS(AppendArgs, AppendArgs); +MAKE_INCOMPLETE_CLASS(GraphId, GraphId); +MAKE_INCOMPLETE_CLASS(GraphReset, GraphReset); MAKE_INCOMPLETE_CLASS(Replayable, Replayable); MAKE_INCOMPLETE_CLASS(Transparent, Transparent); diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp index 8ab9905123135..f87bc1757d7f7 100644 --- a/flang/lib/Parser/openmp-parsers.cpp +++ b/flang/lib/Parser/openmp-parsers.cpp @@ -824,9 +824,9 @@ TYPE_PARSER(construct( "RELEASE" >> pure(common::OmpMemoryOrderType::Release) || "SEQ_CST" >> pure(common::OmpMemoryOrderType::Seq_Cst))) -TYPE_PARSER(construct(expr)) +TYPE_PARSER(construct(scalarIntExpr)) -TYPE_PARSER(construct(expr)) +TYPE_PARSER(construct(scalarLogicalExpr)) // 2.5 PROC_BIND (MASTER | CLOSE | PRIMARY | SPREAD) TYPE_PARSER(construct( diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp index c39daef6b0ea9..e45a6997ed564 100644 --- a/flang/lib/Semantics/check-omp-structure.cpp +++ b/flang/lib/Semantics/check-omp-structure.cpp @@ -15,6 +15,7 @@ #include "flang/Common/idioms.h" #include "flang/Common/indirection.h" #include "flang/Common/visit.h" +#include "flang/Evaluate/fold.h" #include "flang/Evaluate/tools.h" #include "flang/Evaluate/type.h" #include "flang/Parser/char-block.h" @@ -1057,7 +1058,10 @@ void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAssumes &) { dirContext_.pop_back(); } -void OmpStructureChecker::Leave(const parser::OmpBlockConstruct &) { +void OmpStructureChecker::Leave(const parser::OmpBlockConstruct &x) { + if (GetContext().directive == llvm::omp::Directive::OMPD_taskgraph) { + CheckTaskgraph(x); + } if (GetDirectiveNest(TargetBlockOnlyTeams)) { ExitDirectiveNest(TargetBlockOnlyTeams); } @@ -2017,6 +2021,193 @@ void OmpStructureChecker::CheckTargetUpdate() { } } +namespace { +struct TaskgraphVisitor { + TaskgraphVisitor(SemanticsContext &context) : context_(context) {} + + template bool Pre(const T &) { return true; } + template void Post(const T &) {} + + bool Pre(const parser::OpenMPConstruct &x) { + parser::OmpDirectiveName name{GetOmpDirectiveName(x)}; + llvm::ArrayRef leafs{getLeafConstructsOrSelf(name.v)}; + + if (!IsTaskGenerating(leafs)) { + context_.Say(name.source, + "Only task-generating constructs are allowed inside TASKGRAPH region"_err_en_US); + // Only visit top-level constructs. + return false; + } + + const parser::OmpDirectiveSpecification &dirSpec{GetDirSpec(x)}; + + // Most restrictions apply to replayable constructs. All constructs are + // replayable unless REPLAYABLE(false) is present. + bool isReplayable{IsReplayable(dirSpec)}; + const parser::OmpClause *nogroup{nullptr}; + + for (const parser::OmpClause &clause : dirSpec.Clauses().v) { + switch (clause.Id()) { + case llvm::omp::Clause::OMPC_transparent: + if (isReplayable) { + CheckTransparent(clause); + } + break; + case llvm::omp::Clause::OMPC_detach: + if (isReplayable) { + context_.Say(clause.source, + "Detachable replayable tasks are not allowed in a TASKGRAPH region"_err_en_US); + } + break; + case llvm::omp::Clause::OMPC_if: + if (isReplayable) { + CheckIf(clause, leafs); + } + break; + case llvm::omp::Clause::OMPC_nogroup: + nogroup = &clause; + break; + default: + break; + } + } + + unsigned version{context_.langOptions().OpenMPVersion}; + bool allowsNogroup{llvm::omp::isAllowedClauseForDirective( + leafs[0], llvm::omp::Clause::OMPC_nogroup, version)}; + + if (allowsNogroup) { + if (!nogroup) { + context_.Say(dirSpec.source, + "The NOGROUP clause must be specified on every construct in a TASKGRAPH region that could be enclosed in an implicit TASKGROUP"_err_en_US); + } + } + + // Only visit top-level constructs. + return false; + } + +private: + const parser::OmpDirectiveSpecification &GetDirSpec( + const parser::OpenMPConstruct &x) const { + return common::visit( + common::visitors{ + [&](const parser::OmpBlockConstruct &y) + -> const parser::OmpDirectiveSpecification & { + return y.BeginDir(); + }, + [&](const parser::OpenMPLoopConstruct &y) + -> const parser::OmpDirectiveSpecification & { + return y.BeginDir(); + }, + [&](const parser::OpenMPStandaloneConstruct &y) + -> const parser::OmpDirectiveSpecification & { + return std::get(y.u).v; + }, + [&](const auto &) -> const parser::OmpDirectiveSpecification & { + llvm_unreachable("Invalid construct"); + }, + }, + x.u); + } + + bool IsTaskGenerating(llvm::ArrayRef leafs) const { + const static llvm::omp::Directive taskGen[] = { + llvm::omp::Directive::OMPD_target, + llvm::omp::Directive::OMPD_target_data, + llvm::omp::Directive::OMPD_target_enter_data, + llvm::omp::Directive::OMPD_target_exit_data, + llvm::omp::Directive::OMPD_target_update, + llvm::omp::Directive::OMPD_task, + llvm::omp::Directive::OMPD_taskloop, + }; + return llvm::all_of(leafs, + [](llvm::omp::Directive d) { return llvm::is_contained(taskGen, d); }); + } + + bool IsReplayable(const parser::OmpDirectiveSpecification &dirSpec) const { + for (const parser::OmpClause &clause : dirSpec.Clauses().v) { + if (clause.Id() != llvm::omp::Clause::OMPC_replayable) { + continue; + } + if (auto &repl{std::get(clause.u).v}) { + // Scalar>>> + const parser::Expr &parserExpr{repl->v.thing.thing.thing.value()}; + if (auto &&expr{GetEvaluateExpr(parserExpr)}) { + return GetLogicalValue(*expr).value_or(true); + } + } + break; + } + return true; + } + + void CheckTransparent(const parser::OmpClause &clause) const { + bool isTransparent{true}; + if (auto &transp{std::get(clause.u).v}) { + // Scalar>> + const parser::Expr &parserExpr{transp->v.thing.thing.value()}; + if (auto &&expr{GetEvaluateExpr(parserExpr)}) { + // If the argument is omp_not_impex (defined as 0), then + // the task is not transparent, otherwise it is. + const int64_t omp_not_impex{0}; + if (auto &&val{evaluate::ToInt64(*expr)}) { + isTransparent = *val != omp_not_impex; + } + } + } + if (isTransparent) { + context_.Say(clause.source, + "Transparent replayable tasks are not allowed in a TASKGRAPH region"_err_en_US); + } + } + + void CheckIf(const parser::OmpClause &clause, + llvm::ArrayRef leafs) const { + // The only constructs that can generate undeferred tasks (via IF clause) + // are TASK and TASKLOOP. + if (leafs[0] != llvm::omp::Directive::OMPD_task && + leafs[0] != llvm::omp::Directive::OMPD_taskloop) { + return; + } + + auto &&ifc{std::get(clause.u)}; + // Check if there is a directive-name-modifier first. + auto &modifiers{OmpGetModifiers(ifc.v)}; + if (auto *dnm{OmpGetUniqueModifier( + modifiers)}) { + llvm::omp::Directive sub{dnm->v}; + auto subLeafs{llvm::omp::getLeafConstructsOrSelf(sub)}; + // Only interested in the outermost constructs. The body of the created + // task is not a part of the TASKGRAPH region. + if (subLeafs[0] != leafs[0]) { + return; + } + } + // Scalar>> + auto &parserExpr{ + std::get(ifc.v.t).thing.thing.value()}; + if (auto &&expr{GetEvaluateExpr(parserExpr)}) { + // If the value is known to be false, an undeferred task will be + // generated. + if (!GetLogicalValue(*expr).value_or(true)) { + context_.Say(clause.source, + "Undeferred replayable tasks are not allowed in a TASKGRAPH region"_err_en_US); + } + } + } + + SemanticsContext &context_; +}; +} // namespace + +void OmpStructureChecker::CheckTaskgraph(const parser::OmpBlockConstruct &x) { + const parser::Block &block{std::get(x.t)}; + + TaskgraphVisitor visitor{context_}; + parser::Walk(block, visitor); +} + void OmpStructureChecker::CheckTaskDependenceType( const parser::OmpTaskDependenceType::Value &x) { // Common checks for task-dependence-type (DEPEND and UPDATE clauses). diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h index ce074f5f3f86e..a22c80f0a3c3f 100644 --- a/flang/lib/Semantics/check-omp-structure.h +++ b/flang/lib/Semantics/check-omp-structure.h @@ -299,6 +299,7 @@ class OmpStructureChecker void CheckSIMDNest(const parser::OpenMPConstruct &x); void CheckTargetNest(const parser::OpenMPConstruct &x); void CheckTargetUpdate(); + void CheckTaskgraph(const parser::OmpBlockConstruct &x); void CheckDependenceType(const parser::OmpDependenceType::Value &x); void CheckTaskDependenceType(const parser::OmpTaskDependenceType::Value &x); std::optional GetCancelType( diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp index 2980f827d3ef3..ef04f0337ba12 100644 --- a/flang/lib/Semantics/openmp-utils.cpp +++ b/flang/lib/Semantics/openmp-utils.cpp @@ -12,6 +12,7 @@ #include "flang/Semantics/openmp-utils.h" +#include "flang/Common/Fortran-consts.h" #include "flang/Common/indirection.h" #include "flang/Common/reference.h" #include "flang/Common/visit.h" @@ -187,6 +188,46 @@ std::optional GetDynamicType( } } +namespace { +struct LogicalConstantVistor : public evaluate::Traverse, false> { + using Result = std::optional; + using Base = evaluate::Traverse; + LogicalConstantVistor() : Base(*this) {} + + Result Default() const { return std::nullopt; } + + using Base::operator(); + + template // + Result operator()(const evaluate::Constant &x) const { + if constexpr (T::category == common::TypeCategory::Logical) { + return llvm::transformOptional( + x.GetScalarValue(), [](auto &&v) { return v.IsTrue(); }); + } else { + return std::nullopt; + } + } + + template // + Result Combine(Result &&result, Rs &&...results) const { + if constexpr (sizeof...(results) == 0) { + return result; + } else { + if (result.has_value()) { + return result; + } else { + return Combine(std::move(results)...); + } + } + } +}; +} // namespace + +std::optional GetLogicalValue(const SomeExpr &expr) { + return LogicalConstantVistor{}(expr); +} + namespace { struct ContiguousHelper { ContiguousHelper(SemanticsContext &context) diff --git a/flang/test/Parser/OpenMP/taskgraph.f90 b/flang/test/Parser/OpenMP/taskgraph.f90 index 7fcbae4227508..fa9994f41345e 100644 --- a/flang/test/Parser/OpenMP/taskgraph.f90 +++ b/flang/test/Parser/OpenMP/taskgraph.f90 @@ -50,9 +50,9 @@ subroutine f01(x, y) !PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpBlockConstruct !PARSE-TREE: | OmpBeginDirective !PARSE-TREE: | | OmpDirectiveName -> llvm::omp::Directive = taskgraph -!PARSE-TREE: | | OmpClauseList -> OmpClause -> GraphId -> OmpGraphIdClause -> Expr = 'x' +!PARSE-TREE: | | OmpClauseList -> OmpClause -> GraphId -> OmpGraphIdClause -> Scalar -> Integer -> Expr = 'x' !PARSE-TREE: | | | Designator -> DataRef -> Name = 'x' -!PARSE-TREE: | | OmpClause -> GraphReset -> OmpGraphResetClause -> Expr = 'y' +!PARSE-TREE: | | OmpClause -> GraphReset -> OmpGraphResetClause -> Scalar -> Logical -> Expr = 'y' !PARSE-TREE: | | | Designator -> DataRef -> Name = 'y' !PARSE-TREE: | | Flags = None !PARSE-TREE: | Block diff --git a/flang/test/Semantics/OpenMP/graph-id.f90 b/flang/test/Semantics/OpenMP/graph-id.f90 new file mode 100644 index 0000000000000..64ce447ce587f --- /dev/null +++ b/flang/test/Semantics/OpenMP/graph-id.f90 @@ -0,0 +1,13 @@ +!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60 + +subroutine f00 + !ERROR: Must have INTEGER type, but is CHARACTER(KIND=1,LEN=8_8) + !$omp taskgraph graph_id("my graph") + !$omp end taskgraph +end + +subroutine f01 + !ERROR: At most one GRAPH_ID clause can appear on the TASKGRAPH directive + !$omp taskgraph graph_id(1) graph_id(2) + !$omp end taskgraph +end diff --git a/flang/test/Semantics/OpenMP/graph-reset.f90 b/flang/test/Semantics/OpenMP/graph-reset.f90 new file mode 100644 index 0000000000000..4ff9b3d11902b --- /dev/null +++ b/flang/test/Semantics/OpenMP/graph-reset.f90 @@ -0,0 +1,15 @@ +!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60 + +subroutine f00(x) + integer :: x(*) + !ERROR: Whole assumed-size array 'x' may not appear here without subscripts + !ERROR: Must have LOGICAL type, but is INTEGER(4) + !$omp taskgraph graph_reset(x) + !$omp end taskgraph +end + +subroutine f01 + !ERROR: At most one GRAPH_RESET clause can appear on the TASKGRAPH directive + !$omp taskgraph graph_reset(.true.) graph_reset(.false.) + !$omp end taskgraph +end diff --git a/flang/test/Semantics/OpenMP/taskgraph.f90 b/flang/test/Semantics/OpenMP/taskgraph.f90 new file mode 100644 index 0000000000000..6244779afe2b0 --- /dev/null +++ b/flang/test/Semantics/OpenMP/taskgraph.f90 @@ -0,0 +1,118 @@ +!REQUIRES: openmp_runtime +!RUN: %python %S/../test_errors.py %s %flang %openmp_flags -fopenmp -fopenmp-version=60 + +module m +use omp_lib + +implicit none +! Not in omp_lib yet. +integer, parameter :: omp_not_impex = 0 +integer, parameter :: omp_import = 1 +integer, parameter :: omp_export = 2 +integer, parameter :: omp_impex = 3 + +contains + +subroutine f00 + !$omp taskgraph + !ERROR: Only task-generating constructs are allowed inside TASKGRAPH region + !$omp parallel + !$omp end parallel + !$omp end taskgraph +end + +subroutine f01 + !$omp taskgraph + !$omp task + !Non-task-generating constructs are ok if contained in an encountered task. + !No diagnostic expected. + !$omp parallel + !$omp end parallel + !$omp end task + !$omp end taskgraph +end + +subroutine f02 + !$omp taskgraph + !ERROR: Transparent replayable tasks are not allowed in a TASKGRAPH region + !$omp task transparent + !$omp end task + !$omp end taskgraph + + !$omp taskgraph + !Not a transparent task. + !No diagnostic expected. + !$omp task transparent(omp_not_impex) + !$omp end task + !$omp end taskgraph + + !$omp taskgraph + !Ok: transparent, but not replayable task. + !No diagnostic expected. + !$omp task replayable(.false.) transparent + !$omp end task + !$omp end taskgraph +end + +subroutine f03 + integer(kind=omp_event_handle_kind) :: event + + !$omp taskgraph + !ERROR: Detachable replayable tasks are not allowed in a TASKGRAPH region + !$omp task detach(event) + !$omp end task + !$omp end taskgraph + + !$omp taskgraph + !Ok: task is detachable, but not replayable. + !No diagnostic expected + !$omp task detach(event) replayable(.false.) + !$omp end task + !$omp end taskgraph +end + +subroutine f04 + !$omp taskgraph + !ERROR: Undeferred replayable tasks are not allowed in a TASKGRAPH region + !$omp task if(.false.) + !$omp end task + !$omp end taskgraph + + !$omp taskgraph + !Ok: task is undeferred, but not replayable. + !No diagnostic expected. + !$omp task if(.false.) replayable(.false.) + !$omp end task + !$omp end taskgraph +end + +subroutine f05 + integer :: i + + !$omp taskgraph + !ERROR: The NOGROUP clause must be specified on every construct in a TASKGRAPH region that could be enclosed in an implicit TASKGROUP + !$omp taskloop + do i = 1, 10 + enddo + !$omp end taskloop + !$omp end taskgraph + + !$omp taskgraph + !This also applies to non-replayable constructs + !ERROR: The NOGROUP clause must be specified on every construct in a TASKGRAPH region that could be enclosed in an implicit TASKGROUP + !$omp taskloop replayable(.false.) + do i = 1, 10 + enddo + !$omp end taskloop + !$omp end taskgraph + + !$omp taskgraph + !No diagnostic expected. + !$omp taskloop replayable(.false.) nogroup + do i = 1, 10 + enddo + !$omp end taskloop + !$omp end taskgraph +end + +end module diff --git a/llvm/include/llvm/Frontend/OpenMP/ClauseT.h b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h index 1ed23eed1571d..1ade9ce0c3a7d 100644 --- a/llvm/include/llvm/Frontend/OpenMP/ClauseT.h +++ b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h @@ -663,13 +663,13 @@ struct GrainsizeT { // [6.0:438] `graph_id` clause template // struct GraphIdT { - using EmptyTrait = std::true_type; + using IncompleteTrait = std::true_type; }; // [6.0:438] `graph_reset` clause template // struct GraphResetT { - using EmptyTrait = std::true_type; + using IncompleteTrait = std::true_type; }; // V5.2: [5.4.9] `has_device_addr` clause @@ -1278,11 +1278,10 @@ using ExtensionClausesT = template using EmptyClausesT = std::variant< AcqRelT, AcquireT, CaptureT, CompareT, - DynamicAllocatorsT, FullT, GraphIdT, - GraphResetT, InbranchT, MergeableT, - NogroupT, NoOpenmpRoutinesT, NoOpenmpT, - NoParallelismT, NotinbranchT, NowaitT, - ReadT, RelaxedT, ReleaseT, + DynamicAllocatorsT, FullT, InbranchT, + MergeableT, NogroupT, NoOpenmpRoutinesT, + NoOpenmpT, NoParallelismT, NotinbranchT, + NowaitT, ReadT, RelaxedT, ReleaseT, ReverseOffloadT, SeqCstT, SimdT, ThreadsT, UnifiedAddressT, UnifiedSharedMemoryT, UnknownT, UntiedT, UseT, WeakT, @@ -1290,9 +1289,9 @@ using EmptyClausesT = std::variant< template using IncompleteClausesT = - std::variant, AppendArgsT, MatchT, - OtherwiseT, ReplayableT, - TransparentT, WhenT>; + std::variant, AppendArgsT, GraphIdT, + GraphResetT, MatchT, OtherwiseT, + ReplayableT, TransparentT, WhenT>; template using TupleClausesT =