[flang-commits] [flang] [flang][OpenMP] Move modifier verification out of header file (PR #218506)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Tue Aug 25 06:09:52 PDT 2026


https://github.com/kparzysz updated https://github.com/llvm/llvm-project/pull/218506

>From b8708878ae4864e59b197b22afb6016776ed00cd Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 13 Aug 2026 14:20:19 -0500
Subject: [PATCH] [flang][OpenMP] Move modifier verification out of header file

Since modifiers now have enum ids, verifying syntactical properties
of them no longer has to be type-based. Move the modifier verification
code out of a header file with some changes to use the numeric ids.

Some form of a connection between AST nodes for modifiers and the enum
ids is still required, now it's a static member of the modifier AST node.
---
 flang/include/flang/Parser/openmp-utils.h     |  41 ++
 flang/include/flang/Parser/parse-tree.h       |  59 ++-
 .../flang/Semantics/openmp-modifiers.h        | 368 +-----------------
 flang/include/flang/Semantics/openmp-utils.h  |   2 +
 flang/lib/Parser/openmp-utils.cpp             |  42 ++
 flang/lib/Semantics/check-omp-loop.cpp        |   4 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 294 +++++++++++---
 flang/lib/Semantics/check-omp-structure.h     |  13 +
 .../Semantics/OpenMP/clause-validity01.f90    |   1 +
 .../Semantics/OpenMP/depobj-construct-v52.f90 |   1 +
 .../test/Semantics/OpenMP/linear-clause02.f90 |   1 +
 flang/test/Semantics/OpenMP/ordered01.f90     |   3 -
 12 files changed, 405 insertions(+), 424 deletions(-)

diff --git a/flang/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index fe2d06018c850..b0fae4e5d2094 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -15,8 +15,10 @@
 
 #include "flang/Common/indirection.h"
 #include "flang/Parser/parse-tree.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/Frontend/OpenMP/OMP.h"
+#include "llvm/Frontend/OpenMP/OMPDescriptors.h"
 
 #include <cassert>
 #include <iterator>
@@ -201,6 +203,45 @@ struct OmpAllocateInfo {
 
 OmpAllocateInfo SplitOmpAllocate(const OmpAllocateDirective &x);
 
+namespace detail {
+template <typename ClauseTy, typename VoidTy = void> struct HasModifierImpl {
+  static constexpr bool value{false};
+};
+template <typename ClauseTy>
+struct HasModifierImpl<ClauseTy, std::void_t<typename ClauseTy::Modifier>> {
+  static constexpr bool value{true};
+};
+} // namespace detail
+template <typename ClauseTy>
+static constexpr bool HasModifier = detail::HasModifierImpl<ClauseTy>::value;
+
+struct AppliedModifier {
+  llvm::omp::Modifier modifierId;
+  parser::CharBlock source;
+};
+
+struct AppliedModifierInfo {
+  llvm::SmallVector<AppliedModifier> modifiers;
+};
+
+template <typename UnionTy>
+AppliedModifierInfo GetAppliedModifiers(
+    const std::optional<std::list<UnionTy>> &modifiers) {
+  AppliedModifierInfo info;
+  if (modifiers) {
+    for (auto &m : *modifiers) {
+      common::visit(
+          [&](auto &&s) {
+            info.modifiers.emplace_back(AppliedModifier{s.Id, m.source});
+          },
+          m.u);
+    }
+  }
+  return info;
+}
+
+AppliedModifierInfo GetAppliedModifiers(const parser::OmpClause &clause);
+
 template <typename R, typename = void, typename = void> struct is_range {
   static constexpr bool value{false};
 };
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index aeefb4d0fdf44..75ce04625a584 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -31,6 +31,7 @@
 #include "llvm/Frontend/OpenACC/ACC.h.inc"
 #include "llvm/Frontend/OpenMP/OMP.h"
 #include "llvm/Frontend/OpenMP/OMPConstants.h"
+#include "llvm/Frontend/OpenMP/OMPDescriptors.h"
 #include <cinttypes>
 #include <list>
 #include <optional>
@@ -3676,6 +3677,7 @@ struct OmpStylizedExpression {
 //    + | * | .AND. | .OR. | .EQV. | .NEQV. |       // since 4.5
 //    MIN | MAX | IAND | IOR | IEOR                 // since 4.5
 struct OmpReductionIdentifier {
+  static constexpr auto Id = llvm::omp::Modifier::ReductionIdentifier;
   UNION_CLASS_BOILERPLATE(OmpReductionIdentifier);
   std::variant<DefinedOperator, ProcedureDesignator> u;
 };
@@ -3882,6 +3884,7 @@ struct OmpTraitSetSelector {
 // context-selector-specification ->
 //    trait-set-selector, ...
 struct OmpContextSelectorSpecification { // Modifier
+  static constexpr auto Id = llvm::omp::Modifier::ContextSelector;
   CharBlock source;
   WRAPPER_CLASS_BOILERPLATE(
       OmpContextSelectorSpecification, std::list<OmpTraitSetSelector>);
@@ -3906,6 +3909,7 @@ inline namespace modifier {
 // };
 
 struct OmpAccessGroup {
+  static constexpr auto Id = llvm::omp::Modifier::AccessGroup;
   ENUM_CLASS(Value, Cgroup);
   WRAPPER_CLASS_BOILERPLATE(OmpAccessGroup, Value);
 };
@@ -3916,6 +3920,7 @@ struct OmpAccessGroup {
 //    NOTHING |                                     // since 5.1
 //    NEED_DEVICE_PTR                               // since 5.1
 struct OmpAdjustOp {
+  static constexpr auto Id = llvm::omp::Modifier::AdjustOp;
   ENUM_CLASS(Value, Nothing, Need_Device_Ptr)
   WRAPPER_CLASS_BOILERPLATE(OmpAdjustOp, Value);
 };
@@ -3925,6 +3930,7 @@ struct OmpAdjustOp {
 // alignment ->
 //    scalar-integer-expression                     // since 4.5
 struct OmpAlignment {
+  static constexpr auto Id = llvm::omp::Modifier::Alignment;
   WRAPPER_CLASS_BOILERPLATE(OmpAlignment, ScalarIntExpr);
 };
 
@@ -3933,6 +3939,7 @@ struct OmpAlignment {
 // align-modifier ->
 //    ALIGN(alignment)                              // since 5.1
 struct OmpAlignModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AlignModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpAlignModifier, ScalarIntExpr);
 };
 
@@ -3941,6 +3948,7 @@ struct OmpAlignModifier {
 // allocator-simple-modifier ->
 //    allocator                                     // since 5.0
 struct OmpAllocatorSimpleModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AllocatorSimpleModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpAllocatorSimpleModifier, ScalarIntExpr);
 };
 
@@ -3949,6 +3957,7 @@ struct OmpAllocatorSimpleModifier {
 // allocator-complex-modifier ->
 //    ALLOCATOR(allocator)                          // since 5.1
 struct OmpAllocatorComplexModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AllocatorComplexModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpAllocatorComplexModifier, ScalarIntExpr);
 };
 
@@ -3961,6 +3970,7 @@ struct OmpAllocatorComplexModifier {
 // Until 5.2, it was a part of map-type-modifier. Since 6.0 the
 // map-type-modifier has been split into individual modifiers.
 struct OmpAlwaysModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AlwaysModifier;
   ENUM_CLASS(Value, Always)
   WRAPPER_CLASS_BOILERPLATE(OmpAlwaysModifier, Value);
 };
@@ -3973,6 +3983,7 @@ struct OmpAlwaysModifier {
 // attachment-mode ->
 //    ALWAYS | AUTO | NEVER
 struct OmpAttachModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AttachModifier;
   ENUM_CLASS(Value, Always, Never, Auto)
   WRAPPER_CLASS_BOILERPLATE(OmpAttachModifier, Value);
 };
@@ -3983,6 +3994,7 @@ struct OmpAttachModifier {
 //    automap                                       // since 6.0
 //
 struct OmpAutomapModifier {
+  static constexpr auto Id = llvm::omp::Modifier::AutomapModifier;
   ENUM_CLASS(Value, Automap);
   WRAPPER_CLASS_BOILERPLATE(OmpAutomapModifier, Value);
 };
@@ -3994,6 +4006,7 @@ struct OmpAutomapModifier {
 //
 // Prior to 5.2 "chunk-modifier" was a part of "modifier" on SCHEDULE clause.
 struct OmpChunkModifier {
+  static constexpr auto Id = llvm::omp::Modifier::ChunkModifier;
   ENUM_CLASS(Value, Simd)
   WRAPPER_CLASS_BOILERPLATE(OmpChunkModifier, Value);
 };
@@ -4007,6 +4020,7 @@ struct OmpChunkModifier {
 // Until 5.2, it was a part of map-type-modifier. Since 6.0 the
 // map-type-modifier has been split into individual modifiers.
 struct OmpCloseModifier {
+  static constexpr auto Id = llvm::omp::Modifier::CloseModifier;
   ENUM_CLASS(Value, Close)
   WRAPPER_CLASS_BOILERPLATE(OmpCloseModifier, Value);
 };
@@ -4019,6 +4033,7 @@ struct OmpCloseModifier {
 //
 // Until 5.2, it was a part of map-type.
 struct OmpDeleteModifier {
+  static constexpr auto Id = llvm::omp::Modifier::DeleteModifier;
   ENUM_CLASS(Value, Delete)
   WRAPPER_CLASS_BOILERPLATE(OmpDeleteModifier, Value);
 };
@@ -4040,6 +4055,7 @@ struct OmpDeleteModifier {
 // vector). This would accept the vector "i, j, k" (although interpreted
 // incorrectly), while flagging a syntax error for "i+1, j, k".
 struct OmpDependenceType {
+  static constexpr auto Id = llvm::omp::Modifier::DependenceType;
   ENUM_CLASS(Value, Sink, Source);
   WRAPPER_CLASS_BOILERPLATE(OmpDependenceType, Value);
 };
@@ -4051,6 +4067,7 @@ struct OmpDependenceType {
 // keyword ->
 //    IN | INOUT | INOUTSET | MUTEXINOUTSET | OUT   // since 6.0
 struct OmpDepinfoModifier {
+  static constexpr auto Id = llvm::omp::Modifier::DepinfoModifier;
   using Value = common::OmpDependenceKind;
   TUPLE_CLASS_BOILERPLATE(OmpDepinfoModifier);
   std::tuple<Value, OmpObject> t;
@@ -4061,6 +4078,7 @@ struct OmpDepinfoModifier {
 // device-modifier ->
 //    ANCESTOR | DEVICE_NUM                         // since 5.0
 struct OmpDeviceModifier {
+  static constexpr auto Id = llvm::omp::Modifier::DeviceModifier;
   ENUM_CLASS(Value, Ancestor, Device_Num)
   WRAPPER_CLASS_BOILERPLATE(OmpDeviceModifier, Value);
 };
@@ -4070,6 +4088,7 @@ struct OmpDeviceModifier {
 // dims-modifier ->
 //   constant integer expression                    // since 6.1
 struct OmpDimsModifier {
+  static constexpr auto Id = llvm::omp::Modifier::DimsModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpDimsModifier, ScalarIntConstantExpr);
 };
 
@@ -4087,21 +4106,19 @@ struct OmpDimsModifier {
 // the directive-name-modifier. For the sake of uniformity CANCEL can be
 // considered a valid value in 4.5 as well.
 struct OmpDirectiveNameModifier : public OmpDirectiveName {
+  static constexpr auto Id = llvm::omp::Modifier::DirectiveNameModifier;
   INHERITED_WRAPPER_CLASS_BOILERPLATE(
       OmpDirectiveNameModifier, OmpDirectiveName);
 };
 
-// Ref: [5.1:205-209], [5.2:166-168]
+// Ref: [5.2:166-168]
 //
-// motion-modifier ->
-//    PRESENT |                                     // since 5.0, until 5.0
-//    mapper | iterator
 // expectation ->
-//    PRESENT                                       // since 5.1
-//
+//    PRESENT                                       // since 5.2, until 5.2
 // The PRESENT value was a part of motion-modifier in 5.1, and became a
 // value of expectation in 5.2.
 struct OmpExpectation {
+  static constexpr auto Id = llvm::omp::Modifier::Expectation;
   ENUM_CLASS(Value, Present);
   WRAPPER_CLASS_BOILERPLATE(OmpExpectation, Value);
 };
@@ -4113,6 +4130,7 @@ struct OmpExpectation {
 // fallback-mode ->
 //    ABORT | DEFAULT_MEM | NULL                    // since 6.1
 struct OmpFallbackModifier {
+  static constexpr auto Id = llvm::omp::Modifier::FallbackModifier;
   ENUM_CLASS(Value, Abort, Default_Mem, Null);
   WRAPPER_CLASS_BOILERPLATE(OmpFallbackModifier, Value);
 };
@@ -4124,6 +4142,7 @@ struct OmpFallbackModifier {
 //    TARGETSYNC
 // There can be at most only two interop-type.
 struct OmpInteropType {
+  static constexpr auto Id = llvm::omp::Modifier::InteropType;
   ENUM_CLASS(Value, Target, Targetsync)
   WRAPPER_CLASS_BOILERPLATE(OmpInteropType, Value);
 };
@@ -4146,6 +4165,7 @@ struct OmpIteratorSpecifier {
 // iterator-modifier ->
 //    ITERATOR(iterator-specifier [, ...])          // since 5.0
 struct OmpIterator {
+  static constexpr auto Id = llvm::omp::Modifier::Iterator;
   WRAPPER_CLASS_BOILERPLATE(OmpIterator, std::list<OmpIteratorSpecifier>);
 };
 
@@ -4154,6 +4174,7 @@ struct OmpIterator {
 // lastprivate-modifier ->
 //    CONDITIONAL                                   // since 5.0
 struct OmpLastprivateModifier {
+  static constexpr auto Id = llvm::omp::Modifier::LastprivateModifier;
   ENUM_CLASS(Value, Conditional)
   WRAPPER_CLASS_BOILERPLATE(OmpLastprivateModifier, Value);
 };
@@ -4163,6 +4184,7 @@ struct OmpLastprivateModifier {
 // linear-modifier ->
 //    REF | UVAL | VAL                              // since 4.5
 struct OmpLinearModifier {
+  static constexpr auto Id = llvm::omp::Modifier::LinearModifier;
   ENUM_CLASS(Value, Ref, Uval, Val);
   WRAPPER_CLASS_BOILERPLATE(OmpLinearModifier, Value);
 };
@@ -4172,6 +4194,7 @@ struct OmpLinearModifier {
 // linear-stepr ->
 //    integer-expresion                             // since 4.5, until 5.1
 struct OmpLinearStep {
+  static constexpr auto Id = llvm::omp::Modifier::LinearStep;
   WRAPPER_CLASS_BOILERPLATE(OmpLinearStep, ScalarIntExpr);
 };
 
@@ -4186,6 +4209,7 @@ struct OmpLinearStep {
 //    UNROLLED
 //    [( ScalarIntConstantExpr-list )]
 struct OmpLoopModifier {
+  static constexpr auto Id = llvm::omp::Modifier::LoopModifier;
   TUPLE_CLASS_BOILERPLATE(OmpLoopModifier);
   std::tuple<llvm::omp::LoopModifier,
       std::optional<std::list<ScalarIntConstantExpr>>>
@@ -4198,6 +4222,7 @@ struct OmpLoopModifier {
 // lower-bound ->
 //    scalar-integer-expression                     // since 5.1
 struct OmpLowerBound {
+  static constexpr auto Id = llvm::omp::Modifier::LowerBound;
   WRAPPER_CLASS_BOILERPLATE(OmpLowerBound, ScalarIntExpr);
 };
 
@@ -4206,6 +4231,7 @@ struct OmpLowerBound {
 // mapper ->
 //    identifier                                    // since 4.5
 struct OmpMapper {
+  static constexpr auto Id = llvm::omp::Modifier::Mapper;
   WRAPPER_CLASS_BOILERPLATE(OmpMapper, Name);
 };
 
@@ -4219,6 +4245,7 @@ struct OmpMapper {
 //
 // Since 6.0 DELETE is a separate delete-modifier.
 struct OmpMapType {
+  static constexpr auto Id = llvm::omp::Modifier::MapType;
   ENUM_CLASS(Value, Alloc, Delete, From, Release, Storage, To, Tofrom);
   WRAPPER_CLASS_BOILERPLATE(OmpMapType, Value);
 };
@@ -4231,6 +4258,7 @@ struct OmpMapType {
 //    PRESENT                                       // since 5.1, until 5.2
 // Since 6.0 the map-type-modifier has been split into individual modifiers.
 struct OmpMapTypeModifier {
+  static constexpr auto Id = llvm::omp::Modifier::MapTypeModifier;
   ENUM_CLASS(Value, Always, Close, Present)
   WRAPPER_CLASS_BOILERPLATE(OmpMapTypeModifier, Value);
 };
@@ -4240,6 +4268,7 @@ struct OmpMapTypeModifier {
 // mem-space ->
 //    MEMSPACE(memspace-handle)                     // since 5.2
 struct OmpMemSpace {
+  static constexpr auto Id = llvm::omp::Modifier::MemSpace;
   WRAPPER_CLASS_BOILERPLATE(OmpMemSpace, ScalarIntExpr);
 };
 
@@ -4252,6 +4281,7 @@ struct OmpMemSpace {
 // so it should be a modifier group rather than a modifier. Both iterator
 // and mapper are separate modifiers.
 struct OmpMotionModifier {
+  static constexpr auto Id = llvm::omp::Modifier::MotionModifier;
   ENUM_CLASS(Value, Present)
   WRAPPER_CLASS_BOILERPLATE(OmpMotionModifier, Value);
 };
@@ -4267,6 +4297,7 @@ struct OmpMotionModifier {
 // Since 5.2 "modifier" was replaced with "ordering-modifier" and "chunk-
 // modifier".
 struct OmpOrderingModifier {
+  static constexpr auto Id = llvm::omp::Modifier::OrderingModifier;
   ENUM_CLASS(Value, Monotonic, Nonmonotonic, Simd)
   WRAPPER_CLASS_BOILERPLATE(OmpOrderingModifier, Value);
 };
@@ -4276,6 +4307,7 @@ struct OmpOrderingModifier {
 // order-modifier ->
 //    REPRODUCIBLE | UNCONSTRAINED                  // since 5.1
 struct OmpOrderModifier {
+  static constexpr auto Id = llvm::omp::Modifier::OrderModifier;
   ENUM_CLASS(Value, Reproducible, Unconstrained)
   WRAPPER_CLASS_BOILERPLATE(OmpOrderModifier, Value);
 };
@@ -4310,6 +4342,7 @@ struct OmpPreferenceSpecification {
 // prefer-type ->                                   // since 5.1
 //    PREFER_TYPE(preference-specification...)
 struct OmpPreferType {
+  static constexpr auto Id = llvm::omp::Modifier::PreferType;
   WRAPPER_CLASS_BOILERPLATE(
       OmpPreferType, std::list<OmpPreferenceSpecification>);
 };
@@ -4319,6 +4352,7 @@ struct OmpPreferType {
 // prescriptiveness ->
 //    STRICT                                        // since 5.1
 struct OmpPrescriptiveness {
+  static constexpr auto Id = llvm::omp::Modifier::Prescriptiveness;
   ENUM_CLASS(Value, Strict)
   WRAPPER_CLASS_BOILERPLATE(OmpPrescriptiveness, Value);
 };
@@ -4328,6 +4362,7 @@ struct OmpPrescriptiveness {
 // present-modifier ->
 //    PRESENT                                       // since 6.0
 struct OmpPresentModifier {
+  static constexpr auto Id = llvm::omp::Modifier::PresentModifier;
   ENUM_CLASS(Value, Present)
   WRAPPER_CLASS_BOILERPLATE(OmpPresentModifier, Value);
 };
@@ -4337,6 +4372,7 @@ struct OmpPresentModifier {
 // reduction-modifier ->
 //    DEFAULT | INSCAN | TASK                       // since 5.0
 struct OmpReductionModifier {
+  static constexpr auto Id = llvm::omp::Modifier::ReductionModifier;
   ENUM_CLASS(Value, Default, Inscan, Task);
   WRAPPER_CLASS_BOILERPLATE(OmpReductionModifier, Value);
 };
@@ -4347,6 +4383,7 @@ struct OmpReductionModifier {
 //    REF_PTEE | REF_PTR | REF_PTR_PTEE             // since 6.0
 //
 struct OmpRefModifier {
+  static constexpr auto Id = llvm::omp::Modifier::RefModifier;
   ENUM_CLASS(Value, Ref_Ptee, Ref_Ptr, Ref_Ptr_Ptee)
   WRAPPER_CLASS_BOILERPLATE(OmpRefModifier, Value);
 };
@@ -4357,6 +4394,7 @@ struct OmpRefModifier {
 //    SELF                                          // since 6.0
 //
 struct OmpSelfModifier {
+  static constexpr auto Id = llvm::omp::Modifier::SelfModifier;
   ENUM_CLASS(Value, Self)
   WRAPPER_CLASS_BOILERPLATE(OmpSelfModifier, Value);
 };
@@ -4366,6 +4404,7 @@ struct OmpSelfModifier {
 // step-complex-modifier ->
 //    STEP(integer-expression)                      // since 5.2
 struct OmpStepComplexModifier {
+  static constexpr auto Id = llvm::omp::Modifier::StepComplexModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpStepComplexModifier, ScalarIntExpr);
 };
 
@@ -4374,6 +4413,7 @@ struct OmpStepComplexModifier {
 // step-simple-modifier ->
 //    integer-expresion                             // since 5.2
 struct OmpStepSimpleModifier {
+  static constexpr auto Id = llvm::omp::Modifier::StepSimpleModifier;
   WRAPPER_CLASS_BOILERPLATE(OmpStepSimpleModifier, ScalarIntExpr);
 };
 
@@ -4384,6 +4424,7 @@ struct OmpStepSimpleModifier {
 //    MUTEXINOUTSET | DEPOBJ |                      // since 5.0
 //    INOUTSET                                      // since 5.2
 struct OmpTaskDependenceType {
+  static constexpr auto Id = llvm::omp::Modifier::TaskDependenceType;
   using Value = common::OmpDependenceKind;
   WRAPPER_CLASS_BOILERPLATE(OmpTaskDependenceType, Value);
 };
@@ -4393,6 +4434,7 @@ struct OmpTaskDependenceType {
 // traits-array ->
 //    TRAITS(traits-array)                          // since 5.2
 struct OmpTraitsArray {
+  static constexpr auto Id = llvm::omp::Modifier::TraitsArray;
   WRAPPER_CLASS_BOILERPLATE(OmpTraitsArray, common::Indirection<Expr>);
 };
 
@@ -4403,6 +4445,7 @@ struct OmpTraitsArray {
 //    AGGREGATE | ALLOCATABLE | POINTER |           // since 5.0
 //    ALL                                           // since 5.2
 struct OmpVariableCategory {
+  static constexpr auto Id = llvm::omp::Modifier::VariableCategory;
   ENUM_CLASS(Value, Aggregate, All, Allocatable, Pointer, Scalar)
   WRAPPER_CLASS_BOILERPLATE(OmpVariableCategory, Value);
 };
@@ -4412,10 +4455,8 @@ struct OmpVariableCategory {
 //
 // ompx-hold-modifier ->
 //    OMPX_HOLD                                     // since 4.5
-//
-// Until 5.2, it was a part of map-type-modifier. Since 6.0 the
-// map-type-modifier has been split into individual modifiers.
 struct OmpxHoldModifier {
+  static constexpr auto Id = llvm::omp::Modifier::OmpxHoldModifier;
   ENUM_CLASS(Value, Ompx_Hold)
   WRAPPER_CLASS_BOILERPLATE(OmpxHoldModifier, Value);
 };
diff --git a/flang/include/flang/Semantics/openmp-modifiers.h b/flang/include/flang/Semantics/openmp-modifiers.h
index a199e55639ea8..a045a92b951e4 100644
--- a/flang/include/flang/Semantics/openmp-modifiers.h
+++ b/flang/include/flang/Semantics/openmp-modifiers.h
@@ -9,7 +9,6 @@
 #ifndef FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_
 #define FORTRAN_SEMANTICS_OPENMP_MODIFIERS_H_
 
-#include "flang/Common/enum-set.h"
 #include "flang/Parser/characters.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/openmp-utils.h"
@@ -41,94 +40,6 @@ namespace Fortran::semantics {
 // Argument defaults: Required,     Unique, Compatible, Free
 // Modifier defaults: Optional,     Unique, Compatible, Free
 //
-template <typename SpecificTy> llvm::omp::Modifier OmpGetModifierId();
-template <typename SpecificTy>
-const llvm::omp::descriptor::Modifier &OmpGetDescriptor();
-
-#define DECLARE_DESCRIPTOR(name, id) \
-  template <> inline llvm::omp::Modifier OmpGetModifierId<name>() { \
-    return id; \
-  } \
-  template <> \
-  inline const llvm::omp::descriptor::Modifier &OmpGetDescriptor<name>() { \
-    return llvm::omp::getDescriptor(OmpGetModifierId<name>()); \
-  }
-
-DECLARE_DESCRIPTOR(parser::OmpAccessGroup, llvm::omp::Modifier::AccessGroup)
-DECLARE_DESCRIPTOR(parser::OmpAlignment, llvm::omp::Modifier::Alignment)
-DECLARE_DESCRIPTOR(parser::OmpAlignModifier, llvm::omp::Modifier::AlignModifier)
-DECLARE_DESCRIPTOR(parser::OmpAllocatorComplexModifier,
-    llvm::omp::Modifier::AllocatorComplexModifier)
-DECLARE_DESCRIPTOR(parser::OmpAllocatorSimpleModifier,
-    llvm::omp::Modifier::AllocatorSimpleModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpAlwaysModifier, llvm::omp::Modifier::AlwaysModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpAttachModifier, llvm::omp::Modifier::AttachModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpAutomapModifier, llvm::omp::Modifier::AutomapModifier)
-DECLARE_DESCRIPTOR(parser::OmpChunkModifier, llvm::omp::Modifier::ChunkModifier)
-DECLARE_DESCRIPTOR(parser::OmpCloseModifier, llvm::omp::Modifier::CloseModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpContextSelector, llvm::omp::Modifier::ContextSelector)
-DECLARE_DESCRIPTOR(
-    parser::OmpDeleteModifier, llvm::omp::Modifier::DeleteModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpDependenceType, llvm::omp::Modifier::DependenceType)
-DECLARE_DESCRIPTOR(
-    parser::OmpDepinfoModifier, llvm::omp::Modifier::DepinfoModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpDeviceModifier, llvm::omp::Modifier::DeviceModifier)
-DECLARE_DESCRIPTOR(parser::OmpDimsModifier, llvm::omp::Modifier::DimsModifier)
-DECLARE_DESCRIPTOR(parser::OmpDirectiveNameModifier,
-    llvm::omp::Modifier::DirectiveNameModifier)
-DECLARE_DESCRIPTOR(parser::OmpExpectation, llvm::omp::Modifier::Expectation)
-DECLARE_DESCRIPTOR(
-    parser::OmpFallbackModifier, llvm::omp::Modifier::FallbackModifier)
-DECLARE_DESCRIPTOR(parser::OmpInteropType, llvm::omp::Modifier::InteropType)
-DECLARE_DESCRIPTOR(parser::OmpIterator, llvm::omp::Modifier::Iterator)
-DECLARE_DESCRIPTOR(
-    parser::OmpLastprivateModifier, llvm::omp::Modifier::LastprivateModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpLinearModifier, llvm::omp::Modifier::LinearModifier)
-DECLARE_DESCRIPTOR(parser::OmpLinearStep, llvm::omp::Modifier::LinearStep)
-DECLARE_DESCRIPTOR(parser::OmpLoopModifier, llvm::omp::Modifier::LoopModifier)
-DECLARE_DESCRIPTOR(parser::OmpLowerBound, llvm::omp::Modifier::LowerBound)
-DECLARE_DESCRIPTOR(parser::OmpMapper, llvm::omp::Modifier::Mapper)
-DECLARE_DESCRIPTOR(parser::OmpMapType, llvm::omp::Modifier::MapType)
-DECLARE_DESCRIPTOR(
-    parser::OmpMapTypeModifier, llvm::omp::Modifier::MapTypeModifier)
-DECLARE_DESCRIPTOR(parser::OmpMemSpace, llvm::omp::Modifier::MemSpace)
-DECLARE_DESCRIPTOR(
-    parser::OmpMotionModifier, llvm::omp::Modifier::MotionModifier)
-DECLARE_DESCRIPTOR(parser::OmpOrderModifier, llvm::omp::Modifier::OrderModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpOrderingModifier, llvm::omp::Modifier::OrderingModifier)
-DECLARE_DESCRIPTOR(parser::OmpPreferType, llvm::omp::Modifier::PreferType)
-DECLARE_DESCRIPTOR(
-    parser::OmpPrescriptiveness, llvm::omp::Modifier::Prescriptiveness)
-DECLARE_DESCRIPTOR(
-    parser::OmpPresentModifier, llvm::omp::Modifier::PresentModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpReductionIdentifier, llvm::omp::Modifier::ReductionIdentifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpReductionModifier, llvm::omp::Modifier::ReductionModifier)
-DECLARE_DESCRIPTOR(parser::OmpRefModifier, llvm::omp::Modifier::RefModifier)
-DECLARE_DESCRIPTOR(parser::OmpSelfModifier, llvm::omp::Modifier::SelfModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpStepComplexModifier, llvm::omp::Modifier::StepComplexModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpStepSimpleModifier, llvm::omp::Modifier::StepSimpleModifier)
-DECLARE_DESCRIPTOR(
-    parser::OmpTaskDependenceType, llvm::omp::Modifier::TaskDependenceType)
-DECLARE_DESCRIPTOR(parser::OmpTraitsArray, llvm::omp::Modifier::TraitsArray)
-DECLARE_DESCRIPTOR(
-    parser::OmpVariableCategory, llvm::omp::Modifier::VariableCategory)
-DECLARE_DESCRIPTOR(
-    parser::OmpxHoldModifier, llvm::omp::Modifier::OmpxHoldModifier)
-
-#undef DECLARE_DESCRIPTOR
-
 // Explanation of terminology:
 //
 // A typical clause with modifier[s] looks like this (with parts that are
@@ -161,7 +72,7 @@ const llvm::omp::descriptor::Modifier &OmpGetDescriptor(
   return common::visit(
       [](auto &&m) -> decltype(auto) {
         using SpecificTy = llvm::remove_cvref_t<decltype(m)>;
-        return OmpGetDescriptor<SpecificTy>();
+        return llvm::omp::getDescriptor(SpecificTy::Id);
       },
       modifier.u);
 }
@@ -282,285 +193,10 @@ Fortran::parser::CharBlock OmpGetModifierSource(
   llvm_unreachable("`specific` must be a member of `modifiers`");
 }
 
-namespace detail {
-template <typename T> constexpr const T *make_nullptr() {
-  return static_cast<const T *>(nullptr);
-}
-
-/// Verify that all modifiers are allowed in the given OpenMP version.
-template <typename UnionTy>
-bool verifyVersions(const std::optional<std::list<UnionTy>> &modifiers,
-    llvm::omp::Clause id, parser::CharBlock clauseSource,
-    SemanticsContext &semaCtx) {
-  if (!modifiers) {
-    return true;
-  }
-  unsigned version{semaCtx.langOptions().OpenMPVersion};
-  bool result{true};
-  for (auto &m : *modifiers) {
-    const llvm::omp::descriptor::Modifier &desc{OmpGetDescriptor(m)};
-    if (desc.getClauses(version).test(id)) {
-      continue;
-    }
-    // Find the next higher version that allows this modifier on this clause.
-    const auto &versions{desc.getVersions()};
-    unsigned since{~0u}, until{0u};
-    for (unsigned v : versions) {
-      if (desc.getClauses(v).test(id)) {
-        if (v < version) {
-          until = std::max(until, v);
-        } else if (v > version) {
-          since = std::min(since, v);
-        }
-      }
-    }
-    if (since == ~0u && until == 0u) {
-      // This shouldn't really happen, but have it just in case.
-      semaCtx.Say(m.source,
-          "'%s' modifier is not supported on %s clause"_err_en_US,
-          desc.getName().str(),
-          parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(id)));
-    } else if (since != ~0u && version < since) {
-      semaCtx.Say(m.source,
-          "'%s' modifier is not supported in %s on %s clause, %s"_warn_en_US,
-          desc.getName().str(), omp::ThisVersion(version),
-          parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(id)),
-          omp::TryVersion(since));
-      result = false;
-    } else if (until != 0u && version > until) {
-      semaCtx.Say(m.source,
-          "'%s' modifier is no longer supported in %s on %s clause"_warn_en_US,
-          desc.getName().str(), omp::ThisVersion(version),
-          parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(id)));
-      result = false;
-    }
-  }
-  return result;
-}
-
-/// Helper function for verifying the Required property:
-/// For a specific SpecificTy, if SpecificTy is has the Required property,
-/// check if the list has an item that holds SpecificTy as an alternative.
-/// If SpecificTy does not have the Required property, ignore it.
-template <typename SpecificTy, typename UnionTy>
-bool verifyIfRequired(const SpecificTy *,
-    const std::optional<std::list<UnionTy>> &modifiers,
-    parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
-  unsigned version{semaCtx.langOptions().OpenMPVersion};
-  const llvm::omp::descriptor::Modifier &desc{OmpGetDescriptor<SpecificTy>()};
-  if (!desc.getProperties(version).test(llvm::omp::Property::Required)) {
-    // If the modifier is not required, there is nothing to do.
-    return true;
-  }
-  bool present{modifiers.has_value()};
-  present = present && llvm::any_of(*modifiers, [](auto &&m) {
-    return std::holds_alternative<SpecificTy>(m.u);
-  });
-  if (!present) {
-    semaCtx.Say(clauseSource, "'%s' modifier is required"_err_en_US,
-        desc.getName().str());
-  }
-  return present;
-}
-
-/// Helper function for verifying the Required property:
-/// Visit all specific types in UnionTy, and verify the Required property
-/// for each one of them.
-template <typename UnionTy, size_t... Idxs>
-bool verifyRequiredPack(const std::optional<std::list<UnionTy>> &modifiers,
-    parser::CharBlock clauseSource, SemanticsContext &semaCtx,
-    std::integer_sequence<size_t, Idxs...>) {
-  using VariantTy = typename UnionTy::Variant;
-  return (verifyIfRequired(
-              make_nullptr<std::variant_alternative_t<Idxs, VariantTy>>(),
-              modifiers, clauseSource, semaCtx) &&
-      ...);
-}
-
-/// Verify the Required property for the given list. Return true if the
-/// list is valid, or false otherwise.
-template <typename UnionTy>
-bool verifyRequired(const std::optional<std::list<UnionTy>> &modifiers,
-    llvm::omp::Clause id, parser::CharBlock clauseSource,
-    SemanticsContext &semaCtx) {
-  using VariantTy = typename UnionTy::Variant;
-  return verifyRequiredPack(modifiers, clauseSource, semaCtx,
-      std::make_index_sequence<std::variant_size_v<VariantTy>>{});
-}
-
-/// Helper function to verify the Unique property.
-/// If SpecificTy has the Unique property, and an item is found holding
-/// it as the alternative, verify that none of the elements that follow
-/// hold SpecificTy as the alternative.
-template <typename UnionTy, typename SpecificTy>
-bool verifyIfUnique(const SpecificTy *,
-    typename std::list<UnionTy>::const_iterator specific,
-    typename std::list<UnionTy>::const_iterator end,
-    SemanticsContext &semaCtx) {
-  // `specific` is the location of the modifier of type SpecificTy.
-  assert(specific != end && "`specific` must be a valid location");
-
-  unsigned version{semaCtx.langOptions().OpenMPVersion};
-  const llvm::omp::descriptor::Modifier &desc{OmpGetDescriptor<SpecificTy>()};
-  // Ultimate implies Unique.
-  if (!desc.getProperties(version).test(llvm::omp::Property::Unique) &&
-      !desc.getProperties(version).test(llvm::omp::Property::Ultimate)) {
-    return true;
-  }
-  if (std::next(specific) != end) {
-    auto next{
-        detail::findInRange<SpecificTy, UnionTy>(std::next(specific), end)};
-    if (next != end) {
-      semaCtx.Say(next->source,
-          "'%s' modifier cannot occur multiple times"_err_en_US,
-          desc.getName().str());
-    }
-  }
-  return true;
-}
-
-/// Verify the Unique property for the given list. Return true if the
-/// list is valid, or false otherwise.
-template <typename UnionTy>
-bool verifyUnique(const std::optional<std::list<UnionTy>> &modifiers,
-    llvm::omp::Clause id, parser::CharBlock clauseSource,
-    SemanticsContext &semaCtx) {
-  if (!modifiers) {
-    return true;
-  }
-  bool result{true};
-  for (auto it{modifiers->cbegin()}, end{modifiers->cend()}; it != end; ++it) {
-    result = common::visit(
-                 [&](auto &&m) {
-                   return verifyIfUnique<UnionTy>(&m, it, end, semaCtx);
-                 },
-                 it->u) &&
-        result;
-  }
-  return result;
-}
-
-/// Verify the Ultimate property for the given list. Return true if the
-/// list is valid, or false otherwise.
-template <typename UnionTy>
-bool verifyUltimate(const std::optional<std::list<UnionTy>> &modifiers,
-    llvm::omp::Clause id, parser::CharBlock clauseSource,
-    SemanticsContext &semaCtx) {
-  if (!modifiers || modifiers->size() <= 1) {
-    return true;
-  }
-  unsigned version{semaCtx.langOptions().OpenMPVersion};
-  bool result{true};
-  auto first{modifiers->cbegin()};
-  auto last{std::prev(modifiers->cend())};
-
-  // Any item that has the Ultimate property has to be either at the back
-  // or at the front of the list (depending on whether it's a pre- or a post-
-  // modifier).
-  // Walk over the list, and if a given item has the Ultimate property but is
-  // not at the right position, mark it as an error.
-  for (auto it{first}, end{modifiers->cend()}; it != end; ++it) {
-    result = common::visit(
-                 [&](auto &&m) {
-                   using SpecificTy = llvm::remove_cvref_t<decltype(m)>;
-                   const llvm::omp::descriptor::Modifier &desc{
-                       OmpGetDescriptor<SpecificTy>()};
-                   const auto &props{desc.getProperties(version)};
-
-                   if (props.test(llvm::omp::Property::Ultimate)) {
-                     bool isPre = !llvm::omp::getProperties(id, version)
-                                       .test(llvm::omp::Property::PostModified);
-                     if (it == (isPre ? last : first)) {
-                       // Skip, since this is the correct place for this
-                       // modifier.
-                       return true;
-                     }
-                     llvm::StringRef where{isPre ? "last" : "first"};
-                     semaCtx.Say(it->source,
-                         "'%s' should be the %s modifier"_err_en_US,
-                         desc.getName().str(), where.str());
-                     return false;
-                   }
-                   return true;
-                 },
-                 it->u) &&
-        result;
-  }
-  return result;
-}
-
-/// Verify the Exclusive property for the given list. Return true if the
-/// list is valid, or false otherwise.
-template <typename UnionTy>
-bool verifyExclusive(const std::optional<std::list<UnionTy>> &modifiers,
-    llvm::omp::Clause id, parser::CharBlock clauseSource,
-    SemanticsContext &semaCtx) {
-  if (!modifiers || modifiers->size() <= 1) {
-    return true;
-  }
-  unsigned version{semaCtx.langOptions().OpenMPVersion};
-  const UnionTy &front{modifiers->front()};
-  const llvm::omp::descriptor::Modifier &frontDesc{OmpGetDescriptor(front)};
-
-  auto second{std::next(modifiers->cbegin())};
-  auto end{modifiers->end()};
-
-  auto emitErrorMessage{[&](const UnionTy &excl, const UnionTy &other) {
-    const llvm::omp::descriptor::Modifier &descExcl{OmpGetDescriptor(excl)};
-    const llvm::omp::descriptor::Modifier &descOther{OmpGetDescriptor(other)};
-    parser::MessageFormattedText txt(
-        "An exclusive '%s' modifier cannot be specified together with a modifier of a different type"_err_en_US,
-        descExcl.getName().str());
-    parser::Message message(excl.source, txt);
-    message.Attach(
-        other.source, "'%s' provided here"_en_US, descOther.getName().str());
-    semaCtx.Say(std::move(message));
-  }};
-
-  if (frontDesc.getProperties(version).test(llvm::omp::Property::Exclusive)) {
-    // If the first item has the Exclusive property, then check if there is
-    // another item in the rest of the list with a different SpecificTy as
-    // the alternative, and mark it as an error. This allows multiple Exclusive
-    // items to coexist as long as they hold the same SpecificTy.
-    bool result{true};
-    size_t frontIndex{front.u.index()};
-    for (auto it{second}; it != end; ++it) {
-      if (it->u.index() != frontIndex) {
-        emitErrorMessage(front, *it);
-        result = false;
-        break;
-      }
-    }
-    return result;
-  } else {
-    // If the first item does not have the Exclusive property, then check
-    // if there is an item in the rest of the list that is Exclusive, and
-    // mark it as an error if so.
-    bool result{true};
-    for (auto it{second}; it != end; ++it) {
-      const llvm::omp::descriptor::Modifier &desc{OmpGetDescriptor(*it)};
-      if (desc.getProperties(version).test(llvm::omp::Property::Exclusive)) {
-        emitErrorMessage(*it, front);
-        result = false;
-        break;
-      }
-    }
-    return result;
-  }
-}
-} // namespace detail
-
 template <typename ClauseTy>
 bool OmpVerifyModifiers(const ClauseTy &clause, llvm::omp::Clause id,
     parser::CharBlock clauseSource, SemanticsContext &semaCtx) {
-  auto &modifiers{OmpGetModifiers(clause)};
-  bool results[]{//
-      detail::verifyVersions(modifiers, id, clauseSource, semaCtx),
-      detail::verifyRequired(modifiers, id, clauseSource, semaCtx),
-      detail::verifyUnique(modifiers, id, clauseSource, semaCtx),
-      detail::verifyUltimate(modifiers, id, clauseSource, semaCtx),
-      detail::verifyExclusive(modifiers, id, clauseSource, semaCtx)};
-  return llvm::all_of(results, [](bool x) { return x; });
+  return true;
 }
 } // namespace Fortran::semantics
 
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 031926d4a7cc2..f09051bf07f16 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -73,6 +73,8 @@ template <typename T> struct WithSource {
   parser::CharBlock source;
 };
 
+template <typename T> WithSource(const T &, parser::CharBlock) -> WithSource<T>;
+
 // There is no consistent way to get the source of an ActionStmt, but there
 // is "source" in Statement<T>. This structure keeps the ActionStmt with the
 // extracted source for further use.
diff --git a/flang/lib/Parser/openmp-utils.cpp b/flang/lib/Parser/openmp-utils.cpp
index 4ea10f8a2e156..351743911b95f 100644
--- a/flang/lib/Parser/openmp-utils.cpp
+++ b/flang/lib/Parser/openmp-utils.cpp
@@ -231,6 +231,48 @@ const OmpInitializerExpression *GetInitializerExpr(const OmpClause &x) {
   return nullptr;
 }
 
+static AppliedModifierInfo GetAppliedModifiersFromWrapper(
+    const parser::OmpDependClause &depend) {
+  using TaskDep = parser::OmpDependClause::TaskDep;
+  if (auto *task{std::get_if<TaskDep>(&depend.u)}) {
+    using Modifiers = std::optional<std::list<TaskDep::Modifier>>;
+    return GetAppliedModifiers(std::get<Modifiers>(task->t));
+  } else if (auto *doa{std::get_if<OmpDoacross>(&depend.u)}) {
+    using Modifiers = std::optional<std::list<OmpDoacross::Modifier>>;
+    return GetAppliedModifiers(std::get<Modifiers>(doa->t));
+  }
+  llvm_unreachable("Unexpected alternative in depend");
+}
+
+static AppliedModifierInfo GetAppliedModifiersFromWrapper(
+    const parser::OmpDoacrossClause &doacross) {
+  using Modifiers = std::optional<std::list<OmpDoacross::Modifier>>;
+  return GetAppliedModifiers(std::get<Modifiers>(doacross.v.t));
+}
+
+template <typename T>
+static AppliedModifierInfo GetAppliedModifiersFromWrapper(const T &wrapper) {
+  if constexpr (HasModifier<T>) {
+    using Modifiers = std::optional<std::list<typename T::Modifier>>;
+    return GetAppliedModifiers(std::get<Modifiers>(wrapper.t));
+  } else {
+    return AppliedModifierInfo{};
+  }
+}
+
+AppliedModifierInfo GetAppliedModifiers(const parser::OmpClause &clause) {
+  return common::visit(
+      [&](auto &&s) {
+        using TypeS = llvm::remove_cvref_t<decltype(s)>;
+        if constexpr (WrapperTrait<TypeS>) {
+          return GetAppliedModifiersFromWrapper(s.v);
+        } else {
+          return AppliedModifierInfo{};
+        }
+      },
+      clause.u);
+}
+
 static void SplitOmpAllocateHelper(
     OmpAllocateInfo &n, const OmpAllocateDirective &x) {
   n.dirs.push_back(&x);
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index d1c354a9147b0..7c73ba8f17802 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -773,7 +773,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Linear &x) {
 
   auto CheckIntegerNoRef{[&](const Symbol *symbol, parser::CharBlock source) {
     if (!symbol->GetType()->IsNumeric(TypeCategory::Integer)) {
-      auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()};
+      auto &desc{llvm::omp::getDescriptor(llvm::omp::Modifier::LinearModifier)};
       context_.Say(source,
           "The list item '%s' specified without the REF '%s' must be of INTEGER type"_err_en_US,
           symbol->name(), desc.getName().str());
@@ -784,7 +784,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Linear &x) {
     auto &modifiers{OmpGetModifiers(x.v)};
     linearMod = OmpGetUniqueModifier<parser::OmpLinearModifier>(modifiers);
     if (linearMod) {
-      auto &desc{OmpGetDescriptor<parser::OmpLinearModifier>()};
+      auto &desc{llvm::omp::getDescriptor(llvm::omp::Modifier::LinearModifier)};
       parser::CharBlock modSource{OmpGetModifierSource(modifiers, linearMod)};
       bool valid{true};
 
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 1cc5a5d885a0c..deb33079c304f 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -1122,6 +1122,216 @@ void OmpStructureChecker::CheckClauses(parser::OmpDirectiveName dirName,
   }
 }
 
+bool OmpStructureChecker::VerifyModifierVersion(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  bool valid{true};
+
+  // Verify that the specified modifiers are allowed in this version.
+  for (const AppliedModifier &am : info.modifiers) {
+    auto &mdesc{llvm::omp::getDescriptor(am.modifierId)};
+    if (mdesc.getClauses(version).test(clause.value)) {
+      continue;
+    }
+    // Find the next higher version that allows this modifier on this clause.
+    unsigned since{~0u}, until{0u};
+    for (unsigned v : mdesc.getVersions()) {
+      if (mdesc.getClauses(v).test(clause.value)) {
+        if (v < version) {
+          until = std::max(until, v);
+        } else if (v > version) {
+          since = std::min(since, v);
+        }
+      }
+    }
+    if (since == ~0u && until == 0u) {
+      // This shouldn't really happen, but have it just in case.
+      context_.Say(am.source,
+          "'%s' modifier is not supported on %s clause"_err_en_US,
+          mdesc.getName().str(), GetUpperName(clause.value, version));
+      valid = false;
+    } else if (since != ~0u && version < since) {
+      context_.Say(am.source,
+          "'%s' modifier is not supported in %s on %s clause, %s"_warn_en_US,
+          mdesc.getName().str(), ThisVersion(version),
+          GetUpperName(clause.value, version), TryVersion(since));
+      valid = false;
+    } else if (until != 0u && version > until) {
+      context_.Say(am.source,
+          "'%s' modifier is no longer supported in %s on %s clause"_warn_en_US,
+          mdesc.getName().str(), ThisVersion(version),
+          GetUpperName(clause.value, version));
+      valid = false;
+    }
+  }
+
+  return valid;
+}
+
+bool OmpStructureChecker::VerifyModifierRequired(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  auto &cdesc{llvm::omp::getDescriptor(clause.value)};
+  bool valid{true};
+
+  // Verify required modifiers/modifier groups.
+  auto isPresent{[&](llvm::omp::Modifier m) {
+    auto f = llvm::find_if(info.modifiers,
+        [&](const AppliedModifier &am) { return am.modifierId == m; });
+    return f != info.modifiers.end();
+  }};
+
+  for (llvm::omp::Modifier m : cdesc.getModifiers(version)) {
+    auto &mdesc{llvm::omp::getDescriptor(m)};
+    if (mdesc.getProperties(version).test(llvm::omp::Property::Required)) {
+      if (!isPresent(m)) {
+        context_.Say(clause.source, "'%s' modifier is required"_err_en_US,
+            mdesc.getName().str());
+        valid = false;
+      }
+    }
+  }
+
+  return valid;
+}
+
+bool OmpStructureChecker::VerifyModifierUnique(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  bool valid{true};
+
+  // Collect unique modifiers for this clause.
+  llvm::omp::Modifiers unique;
+  auto &cdesc{llvm::omp::getDescriptor(clause.value)};
+  for (llvm::omp::Modifier m : cdesc.getModifiers(version)) {
+    auto &mdesc{llvm::omp::getDescriptor(m)};
+    // Exclusive modifiers should have the "unique" property present as well.
+    if (mdesc.getProperties(version).test(llvm::omp::Property::Unique)) {
+      unique.set(m);
+    }
+  }
+
+  llvm::DenseMap<llvm::omp::Modifier, parser::CharBlock> present;
+  for (const AppliedModifier &am : info.modifiers) {
+    auto &mdesc{llvm::omp::getDescriptor(am.modifierId)};
+    if (!mdesc.getClauses(version).test(clause.value)) {
+      continue;
+    }
+    if (unique.test(am.modifierId)) {
+      auto [where, inserted]{present.insert({am.modifierId, am.source})};
+      if (!inserted) {
+        context_
+            .Say(am.source,
+                "'%s' modifier cannot occur multiple times"_err_en_US,
+                mdesc.getName().str())
+            .Attach(
+                where->second, "previous occurrence of this modifier"_en_US);
+        valid = false;
+      }
+    }
+  }
+
+  return valid;
+}
+
+bool OmpStructureChecker::VerifyModifierExclusive(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  if (info.modifiers.empty()) {
+    return true;
+  }
+  unsigned version{context_.langOptions().OpenMPVersion};
+  bool valid{true};
+
+  // Check individual modifiers first.
+  const AppliedModifier &first{info.modifiers.front()};
+  const auto &fdesc{llvm::omp::getDescriptor(first.modifierId)};
+
+  auto emitErrorMessage{[&](const AppliedModifier &excl,
+                            const AppliedModifier &other) {
+    auto &descExcl{llvm::omp::getDescriptor(excl.modifierId)};
+    auto &descOther{llvm::omp::getDescriptor(other.modifierId)};
+    parser::MessageFormattedText txt(
+        "An exclusive '%s' modifier cannot be specified together with a modifier of a different type"_err_en_US,
+        descExcl.getName().str());
+    parser::Message message(excl.source, txt);
+    message.Attach(
+        other.source, "'%s' provided here"_en_US, descOther.getName().str());
+    context_.Say(std::move(message));
+  }};
+
+  auto rest{llvm::ArrayRef(info.modifiers).drop_front()};
+  if (fdesc.getProperties(version).test(llvm::omp::Property::Exclusive)) {
+    for (const AppliedModifier &am : rest) {
+      if (am.modifierId == first.modifierId) {
+        continue;
+      }
+      emitErrorMessage(first, am);
+      valid = false;
+      break;
+    }
+  } else {
+    for (const AppliedModifier &am : rest) {
+      auto &mdesc{llvm::omp::getDescriptor(am.modifierId)};
+      if (!mdesc.getProperties(version).test(llvm::omp::Property::Exclusive)) {
+        continue;
+      }
+      // The first modifier was not exclusive, so this must be a different
+      // modifier.
+      emitErrorMessage(am, first);
+      valid = false;
+      break;
+    }
+  }
+
+  return valid;
+}
+
+bool OmpStructureChecker::VerifyModifierUltimate(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  if (info.modifiers.empty()) {
+    return true;
+  }
+  unsigned version{context_.langOptions().OpenMPVersion};
+  bool valid{true};
+
+  // Check if there is an ultimate modifier that is in a wrong position.
+  auto &cdesc{llvm::omp::getDescriptor(clause.value)};
+  llvm::ArrayRef<AppliedModifier> modifiers(info.modifiers);
+  llvm::StringRef where;
+
+  if (cdesc.getProperties(version).test(llvm::omp::Property::PostModified)) {
+    modifiers = modifiers.drop_front(1);
+    where = "first";
+  } else {
+    modifiers = modifiers.drop_back(1);
+    where = "last";
+  }
+  for (const AppliedModifier &am : modifiers) {
+    auto &mdesc{llvm::omp::getDescriptor(am.modifierId)};
+    if (mdesc.getProperties(version).test(llvm::omp::Property::Ultimate)) {
+      context_.Say(am.source, "'%s' should be the %s modifier"_err_en_US,
+          mdesc.getName().str(), where.str());
+      valid = false;
+    }
+  }
+
+  return valid;
+}
+
+bool OmpStructureChecker::VerifyModifiers(
+    WithSource<llvm::omp::Clause> clause, const AppliedModifierInfo &info) {
+  // Run all checks without short-circuiting, return 'true' if all succeed.
+  bool valid[]{
+      VerifyModifierVersion(clause, info),
+      VerifyModifierRequired(clause, info),
+      VerifyModifierUnique(clause, info),
+      VerifyModifierUltimate(clause, info),
+      VerifyModifierExclusive(clause, info),
+  };
+
+  return llvm::all_of(valid, [](bool x) { return x; });
+}
+
 void OmpStructureChecker::CheckMultipleOccurrence(
     semantics::UnorderedSymbolSet &listVars,
     const std::list<parser::Name> &nameList, const parser::CharBlock &item,
@@ -2156,20 +2366,21 @@ void OmpStructureChecker::CheckInitOnDepobj(
           OmpGetUniqueModifier<parser::OmpDepinfoModifier>(modifiers)}) {
     auto depKind{std::get<common::OmpDependenceKind>(depInfo->t)};
     if (depKind == common::OmpDependenceKind::Depobj) {
-      auto &desc{OmpGetDescriptor<parser::OmpDepinfoModifier>()};
+      auto &desc{
+          llvm::omp::getDescriptor(llvm::omp::Modifier::DepinfoModifier)};
       context_.Say(OmpGetModifierSource(modifiers, depInfo),
           "'%s' is not an allowed value of the '%s' modifier"_err_en_US,
           parser::ToUpperCaseLetters(EnumToString(depKind)),
           desc.getName().str());
     }
   } else {
-    auto &desc{OmpGetDescriptor<parser::OmpDepinfoModifier>()};
+    auto &desc{llvm::omp::getDescriptor(llvm::omp::Modifier::DepinfoModifier)};
     context_.Say(initClause.source,
         "The '%s' modifier is required on a DEPOBJ construct"_err_en_US,
         desc.getName().str());
   }
   if (auto *prefType{OmpGetUniqueModifier<parser::OmpPreferType>(modifiers)}) {
-    auto &desc{OmpGetDescriptor<parser::OmpPreferType>()};
+    auto &desc{llvm::omp::getDescriptor(llvm::omp::Modifier::PreferType)};
     context_.Say(OmpGetModifierSource(modifiers, prefType),
         "The '%s' modifier is not allowed on a DEPOBJ construct"_err_en_US,
         desc.getName().str());
@@ -2945,48 +3156,15 @@ void OmpStructureChecker::ChecksOnOrderedAsStandalone() {
     context_.Say(GetContext().clauseSource,
         "THREADS and SIMD clauses are not allowed when ORDERED construct is a standalone construct with no ORDERED region"_err_en_US);
   }
-
-  int dependSinkCount{0}, dependSourceCount{0};
-  bool exclusiveShown{false}, duplicateSourceShown{false};
-
-  auto visitDoacross{[&](const parser::OmpDoacross &doa,
-                         const parser::CharBlock &src) {
-    // Modifiers should have been verified by now.
-    auto &modifiers{OmpGetModifiers(doa)};
-    if (auto *source{
-            OmpGetUniqueModifier<parser::OmpDependenceType>(modifiers)}) {
-      if (source->v == parser::OmpDependenceType::Value::Source) {
-        ++dependSourceCount;
-      } else {
-        ++dependSinkCount;
-      }
-    }
-    if (!exclusiveShown && dependSinkCount > 0 && dependSourceCount > 0) {
-      exclusiveShown = true;
-      context_.Say(src,
-          "The SINK and SOURCE dependence types are mutually exclusive"_err_en_US);
-    }
-    if (!duplicateSourceShown && dependSourceCount > 1) {
-      duplicateSourceShown = true;
-      context_.Say(src,
-          "At most one SOURCE dependence type can appear on ORDERED directive"_err_en_US);
-    }
-  }};
-
   // Visit the DEPEND and DOACROSS clauses.
   for (auto [_, clause] : FindClauses(llvm::omp::Clause::OMPC_depend)) {
     const auto &dependClause{std::get<parser::OmpClause::Depend>(clause->u)};
-    if (auto *doAcross{std::get_if<parser::OmpDoacross>(&dependClause.v.u)}) {
-      visitDoacross(*doAcross, clause->source);
-    } else {
+    using TaskDep = parser::OmpDependClause::TaskDep;
+    if (std::holds_alternative<TaskDep>(dependClause.v.u)) {
       context_.Say(clause->source,
           "Only SINK or SOURCE dependence types are allowed when ORDERED construct is a standalone construct with no ORDERED region"_err_en_US);
     }
   }
-  for (auto [_, clause] : FindClauses(llvm::omp::Clause::OMPC_doacross)) {
-    auto &doaClause{std::get<parser::OmpClause::Doacross>(clause->u)};
-    visitDoacross(doaClause.v.v, clause->source);
-  }
 
   bool isNestedInDoOrderedWithPara{false};
   if (CurrentDirectiveIsNested() &&
@@ -3902,6 +4080,28 @@ void OmpStructureChecker::Leave(const parser::OmpClauseList &x) {
 void OmpStructureChecker::Enter(const parser::OmpClause &x) {
   SetContextClause(x);
   CheckArgumentObjectKind(x);
+
+  llvm::omp::Clause id{x.Id()};
+  auto clauseId{omp::WithSource(id, x.source)};
+  switch (id) {
+  case llvm::omp::Clause::OMPC_ompx_bare:
+  case llvm::omp::Clause::OMPC_cancellation_construct_type:
+    // Those are extensions/synthetic clauses and they don't have descriptors.
+    break;
+  case llvm::omp::Clause::OMPC_uses_allocators: {
+    auto &uac{parser::UnwrapRef<parser::OmpUsesAllocatorsClause>(x)};
+    for (auto &&as : uac.v) {
+      bool legacy{std::get<bool>(as.t)};
+      if (!legacy) {
+        VerifyModifiers(clauseId, GetAppliedModifiers(OmpGetModifiers(as)));
+      }
+    }
+    break;
+  }
+  default:
+    VerifyModifiers(clauseId, GetAppliedModifiers(x));
+    break;
+  }
 }
 
 // Restrictions specific to each clause are implemented apart from the
@@ -4666,7 +4866,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::If &x) {
     std::string dirName{parser::omp::GetUpperName(dir, version)};
 
     parser::CharBlock modifierSource{OmpGetModifierSource(modifiers, dnm)};
-    auto desc{OmpGetDescriptor<parser::OmpDirectiveNameModifier>()};
+    auto desc{
+        llvm::omp::getDescriptor(llvm::omp::Modifier::DirectiveNameModifier)};
     std::string modName{desc.getName().str()};
 
     if (!isConstituent(dir, sub)) {
@@ -4891,7 +5092,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
         llvm::is_contained(leafs, Directive::OMPD_declare_mapper)};
 
     if (!mapEnteringConstructOrMapper || !IsMapEnteringType(mapType)) {
-      const auto &desc{OmpGetDescriptor<parser::OmpAttachModifier>()};
+      const auto &desc{
+          llvm::omp::getDescriptor(llvm::omp::Modifier::AttachModifier)};
       context_.Say(OmpGetModifierSource(modifiers, attach),
           "The '%s' modifier can only appear on a map-entering construct or on a DECLARE_MAPPER directive"_err_en_US,
           desc.getName().str());
@@ -5050,7 +5252,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Device &x) {
             OmpGetUniqueModifier<parser::OmpDeviceModifier>(modifiers)}) {
       using Value = parser::OmpDeviceModifier::Value;
       if (dir != llvm::omp::OMPD_target && deviceMod->v == Value::Ancestor) {
-        auto name{OmpGetDescriptor<parser::OmpDeviceModifier>().getName()};
+        auto name{llvm::omp::getDescriptor(llvm::omp::Modifier::DeviceModifier)
+                .getName()};
         context_.Say(OmpGetModifierSource(modifiers, deviceMod),
             "The ANCESTOR %s must not appear on the DEVICE clause on any directive other than the TARGET construct. Found on %s construct."_err_en_US,
             name.str(), parser::omp::GetUpperName(dir, version));
@@ -5718,7 +5921,8 @@ void OmpStructureChecker::CheckUsesAllocatorsSpec(
     bool ok{
         memSpaceName && IsUsesAllocatorsMemSpaceName(*memSpaceName, version)};
     if (!ok) {
-      auto name{OmpGetDescriptor<parser::OmpMemSpace>().getName()};
+      auto name{
+          llvm::omp::getDescriptor(llvm::omp::Modifier::MemSpace).getName()};
       context_.Say(memSpaceSource,
           "The '%s' modifier must name a predefined memory space"_err_en_US,
           name.str());
@@ -6407,7 +6611,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::SelfMaps &x) {
 
 void OmpStructureChecker::CheckDimsModifier(parser::CharBlock source,
     size_t numValues, const parser::OmpDimsModifier &x) {
-  std::string name{OmpGetDescriptor<parser::OmpDimsModifier>().getName().str()};
+  auto &desc{llvm::omp::getDescriptor(llvm::omp::Modifier::DimsModifier)};
+  std::string name{desc.getName().str()};
 
   if (auto dimsVal{GetIntValue(x.v)}) {
     if (*dimsVal > 0) {
@@ -6582,7 +6787,8 @@ void OmpStructureChecker::Enter(const parser::OpenMPInteropConstruct &x) {
                 if (auto *depInfo{
                         OmpGetUniqueModifier<parser::OmpDepinfoModifier>(
                             modifiers)}) {
-                  auto &desc{OmpGetDescriptor<parser::OmpDepinfoModifier>()};
+                  auto &desc{llvm::omp::getDescriptor(
+                      llvm::omp::Modifier::DepinfoModifier)};
                   context_.Say(OmpGetModifierSource(modifiers, depInfo),
                       "The '%s' is not allowed on INTEROP construct"_err_en_US,
                       desc.getName().str());
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 8016c82b8496f..966c32e212dcb 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -18,6 +18,7 @@
 #include "flang/Common/enum-set.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/openmp-utils.h"
 #include "flang/Semantics/semantics.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/iterator_range.h"
@@ -357,6 +358,18 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   std::pair<const parser::OmpClause *, const parser::OmpClause *>
   FindMutuallyExclusiveClauses(llvm::omp::ClauseSet exclusive,
       const std::vector<const parser::OmpClause *> &clauses);
+  bool VerifyModifierVersion(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
+  bool VerifyModifierRequired(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
+  bool VerifyModifierUnique(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
+  bool VerifyModifierExclusive(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
+  bool VerifyModifierUltimate(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
+  bool VerifyModifiers(omp::WithSource<llvm::omp::Clause> clause,
+      const parser::omp::AppliedModifierInfo &info);
 
   const parser::OpenMPConstruct *GetCurrentConstruct() const;
   void CheckSourceLabel(const parser::Label &);
diff --git a/flang/test/Semantics/OpenMP/clause-validity01.f90 b/flang/test/Semantics/OpenMP/clause-validity01.f90
index bc675d72dc9d3..cd4ad04197620 100644
--- a/flang/test/Semantics/OpenMP/clause-validity01.f90
+++ b/flang/test/Semantics/OpenMP/clause-validity01.f90
@@ -504,6 +504,7 @@
   !$omp taskyield
   !$omp barrier
   !$omp taskwait
+  !ERROR: 'task-dependence-type' modifier is required
   !ERROR: The SINK and SOURCE dependence types can only be used with the ORDERED directive, used here in the TASKWAIT construct
   !WARNING: 'dependence-type' modifier is no longer supported in OpenMP v5.2 on DEPEND clause
   !$omp taskwait depend(source)
diff --git a/flang/test/Semantics/OpenMP/depobj-construct-v52.f90 b/flang/test/Semantics/OpenMP/depobj-construct-v52.f90
index 021e07618bb51..6fea74be8f026 100644
--- a/flang/test/Semantics/OpenMP/depobj-construct-v52.f90
+++ b/flang/test/Semantics/OpenMP/depobj-construct-v52.f90
@@ -2,6 +2,7 @@
 
 subroutine f00
   integer :: obj
+!ERROR: 'task-dependence-type' modifier is required
 !WARNING: 'dependence-type' modifier is no longer supported in OpenMP v5.2 on DEPEND clause
 !ERROR: A DEPEND clause on a DEPOBJ construct must not have SINK or SOURCE as dependence type
   !$omp depobj(obj) depend(source)
diff --git a/flang/test/Semantics/OpenMP/linear-clause02.f90 b/flang/test/Semantics/OpenMP/linear-clause02.f90
index d76f921f63694..e6eedbcae7c92 100644
--- a/flang/test/Semantics/OpenMP/linear-clause02.f90
+++ b/flang/test/Semantics/OpenMP/linear-clause02.f90
@@ -8,6 +8,7 @@ subroutine f00(x)
 
 subroutine f01(x)
   integer :: x
+  !WARNING: The 'modifier(<list>)' syntax is deprecated in OpenMP v5.2, use '<list> : modifier' instead
   !ERROR: An exclusive 'step-simple-modifier' modifier cannot be specified together with a modifier of a different type
   !$omp declare simd linear(uval(x) : 2)
 end
diff --git a/flang/test/Semantics/OpenMP/ordered01.f90 b/flang/test/Semantics/OpenMP/ordered01.f90
index b445b0448ba18..13cd0e3c61151 100644
--- a/flang/test/Semantics/OpenMP/ordered01.f90
+++ b/flang/test/Semantics/OpenMP/ordered01.f90
@@ -38,11 +38,8 @@ program main
   !$omp do ordered(1)
   do i = 2, N
     !ERROR: Only SINK or SOURCE dependence types are allowed when ORDERED construct is a standalone construct with no ORDERED region
-    !ERROR: At most one SOURCE dependence type can appear on ORDERED directive
     !$omp ordered depend(source) depend(inout: arrayA) depend(source)
     arrayA(i) = foo(i)
-    !ERROR: The SINK and SOURCE dependence types are mutually exclusive
-    !ERROR: At most one SOURCE dependence type can appear on ORDERED directive
     !$omp ordered depend(sink: i - 1) depend(source) depend(source)
     arrayB(i) = bar(arrayA(i), arrayB(i-1))
     !ERROR: Only SINK or SOURCE dependence types are allowed when ORDERED construct is a standalone construct with no ORDERED region



More information about the flang-commits mailing list