[flang] [llvm] [flang][OpenMP] Semantic checks for TASKGRAPH (PR #160115)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 22 07:10:17 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-parser

Author: Krzysztof Parzyszek (kparzysz)

<details>
<summary>Changes</summary>

This verifies the "structural" restrictions on constructs encountered in a TASKGRAPH construct.

There are also restrictions that apply to list items, specifically in the following contexts:
- a list item on a clause on a replayable construct,
- data-sharing attributes for a variable on a replayable construct. These restrictions are not verified, because that would require knowing which clauses (on a potential compound directive) apply to the task- generating construct of interest. This information is not available during semantic checks.

---

Patch is 22.22 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/160115.diff


12 Files Affected:

- (modified) flang/include/flang/Parser/parse-tree.h (+2-2) 
- (modified) flang/include/flang/Semantics/openmp-utils.h (+2) 
- (modified) flang/lib/Lower/OpenMP/Clauses.cpp (+2-2) 
- (modified) flang/lib/Parser/openmp-parsers.cpp (+2-2) 
- (modified) flang/lib/Semantics/check-omp-structure.cpp (+195-6) 
- (modified) flang/lib/Semantics/check-omp-structure.h (+1) 
- (modified) flang/lib/Semantics/openmp-utils.cpp (+41) 
- (modified) flang/test/Parser/OpenMP/taskgraph.f90 (+2-2) 
- (added) flang/test/Semantics/OpenMP/graph-id.f90 (+13) 
- (added) flang/test/Semantics/OpenMP/graph-reset.f90 (+15) 
- (added) flang/test/Semantics/OpenMP/taskgraph.f90 (+117) 
- (modified) llvm/include/llvm/Frontend/OpenMP/ClauseT.h (+9-10) 


``````````diff
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<Expr>);
+  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<Expr>);
+  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<SomeExpr> GetEvaluateExpr(const parser::Expr &parserExpr);
 std::optional<evaluate::DynamicType> GetDynamicType(
     const parser::Expr &parserExpr);
 
+std::optional<bool> GetLogicalValue(const SomeExpr &expr);
+
 std::optional<bool> 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<OmpFailClause>(
     "RELEASE" >> pure(common::OmpMemoryOrderType::Release) ||
     "SEQ_CST" >> pure(common::OmpMemoryOrderType::Seq_Cst)))
 
-TYPE_PARSER(construct<OmpGraphIdClause>(expr))
+TYPE_PARSER(construct<OmpGraphIdClause>(scalarIntExpr))
 
-TYPE_PARSER(construct<OmpGraphResetClause>(expr))
+TYPE_PARSER(construct<OmpGraphResetClause>(scalarLogicalExpr))
 
 // 2.5 PROC_BIND (MASTER | CLOSE | PRIMARY | SPREAD)
 TYPE_PARSER(construct<OmpProcBindClause>(
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index c39daef6b0ea9..73aea2f714eb3 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"
@@ -990,21 +991,21 @@ void OmpStructureChecker::Enter(const parser::OmpBlockConstruct &x) {
       EnterDirectiveNest(TargetBlockOnlyTeams);
     }
     break;
-  case llvm::omp::OMPD_workshare:
-  case llvm::omp::OMPD_parallel_workshare:
+  case llvm::omp::Directive::OMPD_workshare:
+  case llvm::omp::Directive::OMPD_parallel_workshare:
     CheckWorkshareBlockStmts(block, beginSpec.source);
     HasInvalidWorksharingNesting(
         beginSpec.source, llvm::omp::nestedWorkshareErrSet);
     break;
-  case llvm::omp::OMPD_workdistribute:
+  case llvm::omp::Directive::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:
+  case llvm::omp::Directive::OMPD_teams_workdistribute:
+  case llvm::omp::Directive::OMPD_target_teams_workdistribute:
     CheckWorkdistributeBlockStmts(block, beginSpec.source);
     break;
   case llvm::omp::Directive::OMPD_scope:
@@ -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,191 @@ void OmpStructureChecker::CheckTargetUpdate() {
   }
 }
 
+namespace {
+struct TaskgraphVisitor {
+  TaskgraphVisitor(SemanticsContext &context) : context_(context) {}
+
+  template <typename T> bool Pre(const T &) { return true; }
+  template <typename T> void Post(const T &) {}
+
+  bool Pre(const parser::OpenMPConstruct &x) {
+    parser::OmpDirectiveName name{GetOmpDirectiveName(x)};
+    llvm::ArrayRef<llvm::omp::Directive> 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) -> decltype(auto) {
+              return y.BeginDir();
+            },
+            [&](const parser::OpenMPLoopConstruct &y) -> decltype(auto) {
+              return y.BeginDir();
+            },
+            [&](const parser::OpenMPStandaloneConstruct &y)
+                -> const parser::OmpDirectiveSpecification & {
+              return std::get<parser::OpenMPSimpleStandaloneConstruct>(y.u).v;
+            },
+            [&](const auto &) -> const parser::OmpDirectiveSpecification & {
+              llvm_unreachable("Invalid construct");
+            },
+        },
+        x.u);
+  }
+
+  bool IsTaskGenerating(llvm::ArrayRef<llvm::omp::Directive> 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<parser::OmpClause::Replayable>(clause.u).v}) {
+        // Scalar<Logical<Constant<indirection<Expr>>>>
+        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<parser::OmpClause::Transparent>(clause.u).v}) {
+      // Scalar<Integer<indirection<Expr>>>
+      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<llvm::omp::Directive> 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<parser::OmpClause::If>(clause.u)};
+    // Check if there is a directive-name-modifier first.
+    auto &modifiers{OmpGetModifiers(ifc.v)};
+    if (auto *dnm{OmpGetUniqueModifier<parser::OmpDirectiveNameModifier>(
+            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<Logical<indirection<Expr>>>
+    auto &parserExpr{
+        std::get<parser::ScalarLogicalExpr>(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<parser::Block>(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<llvm::omp::Directive> 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<evaluate::DynamicType> GetDynamicType(
   }
 }
 
+namespace {
+struct LogicalConstantVistor : public evaluate::Traverse<LogicalConstantVistor,
+                                   std::optional<bool>, false> {
+  using Result = std::optional<bool>;
+  using Base = evaluate::Traverse<LogicalConstantVistor, Result, false>;
+  LogicalConstantVistor() : Base(*this) {}
+
+  Result Default() const { return std::nullopt; }
+
+  using Base::operator();
+
+  template <typename T> //
+  Result operator()(const evaluate::Constant<T> &x) const {
+    if constexpr (T::category == common::TypeCategory::Logical) {
+      return llvm::transformOptional(
+          x.GetScalarValue(), [](auto &&v) { return v.IsTrue(); });
+    } else {
+      return std::nullopt;
+    }
+  }
+
+  template <typename... Rs> //
+  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<bool> 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..e45ef46c3bef2
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/taskgraph.f90
@@ -0,0 +1,117 @@
+!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
---...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/160115


More information about the llvm-commits mailing list