[llvm-branch-commits] [flang] [llvm] [flang][OpenMP] Main splitting functionality dev-complete (PR #83264)

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Feb 28 06:19:10 PST 2024


https://github.com/kparzysz created https://github.com/llvm/llvm-project/pull/83264

The clause templates are now based on the OpenMP spec 5.2.

The splitting code is now separated into its own file.

**This is still just the splitting part, it's not applied yet.**

[flang][OpenMP] TableGen support for getting leaf constructs

Implement getLeafConstructs(D), which for a composite directive D will return the list of the constituent leaf directives.

getOpenMPVersion

Split function complete

>From 544bc1e2c4b98cb592fb01af290673a5419e9540 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 16 Jan 2024 16:40:47 -0600
Subject: [PATCH 1/4] [flang][OpenMP] TableGen support for getting leaf
 constructs

Implement getLeafConstructs(D), which for a composite directive D
will return the list of the constituent leaf directives.
---
 .../llvm/Frontend/Directive/DirectiveBase.td  |  4 +
 llvm/include/llvm/Frontend/OpenMP/OMP.td      | 60 +++++++++++++--
 llvm/include/llvm/TableGen/DirectiveEmitter.h |  4 +
 llvm/utils/TableGen/DirectiveEmitter.cpp      | 77 +++++++++++++++++++
 4 files changed, 139 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/Frontend/Directive/DirectiveBase.td b/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
index 31578710365b212..24eb54e75c96baf 100644
--- a/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
+++ b/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
@@ -152,6 +152,10 @@ class Directive<string d> {
   // List of clauses that are required.
   list<VersionedClause> requiredClauses = [];
 
+  // List of leaf constituent directives in the order in which they appear
+  // in the combined/composite directive.
+  list<Directive> leafs = [];
+
   // Set directive used by default when unknown.
   bit isDefault = false;
 }
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index 77d207f2b10a835..945704c4ab4cbfb 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -771,6 +771,7 @@ def OMP_TargetParallel : Directive<"target parallel"> {
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_ThreadLimit, 51>,
   ];
+  let leafs = [OMP_Target, OMP_Parallel];
 }
 def OMP_TargetParallelFor : Directive<"target parallel for"> {
   let allowedClauses = [
@@ -803,6 +804,7 @@ def OMP_TargetParallelFor : Directive<"target parallel for"> {
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_ThreadLimit, 51>,
   ];
+  let leafs = [OMP_Target, OMP_Parallel, OMP_For];
 }
 def OMP_TargetParallelDo : Directive<"target parallel do"> {
   let allowedClauses = [
@@ -833,6 +835,7 @@ def OMP_TargetParallelDo : Directive<"target parallel do"> {
     VersionedClause<OMPC_NoWait>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Target, OMP_Parallel, OMP_Do];
 }
 def OMP_TargetUpdate : Directive<"target update"> {
   let allowedClauses = [
@@ -846,6 +849,11 @@ def OMP_TargetUpdate : Directive<"target update"> {
     VersionedClause<OMPC_NoWait>
   ];
 }
+def OMP_masked : Directive<"masked"> {
+  let allowedOnceClauses = [
+    VersionedClause<OMPC_Filter>
+  ];
+}
 def OMP_ParallelFor : Directive<"parallel for"> {
   let allowedClauses = [
     VersionedClause<OMPC_If>,
@@ -866,6 +874,7 @@ def OMP_ParallelFor : Directive<"parallel for"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_For];
 }
 def OMP_ParallelDo : Directive<"parallel do"> {
   let allowedClauses = [
@@ -887,6 +896,7 @@ def OMP_ParallelDo : Directive<"parallel do"> {
     VersionedClause<OMPC_Collapse>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Parallel, OMP_Do];
 }
 def OMP_ParallelForSimd : Directive<"parallel for simd"> {
   let allowedClauses = [
@@ -912,6 +922,7 @@ def OMP_ParallelForSimd : Directive<"parallel for simd"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_For, OMP_Simd];
 }
 def OMP_ParallelDoSimd : Directive<"parallel do simd"> {
   let allowedClauses = [
@@ -938,6 +949,7 @@ def OMP_ParallelDoSimd : Directive<"parallel do simd"> {
     VersionedClause<OMPC_SimdLen>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Parallel, OMP_Do, OMP_Simd];
 }
 def OMP_ParallelMaster : Directive<"parallel master"> {
   let allowedClauses = [
@@ -953,6 +965,7 @@ def OMP_ParallelMaster : Directive<"parallel master"> {
     VersionedClause<OMPC_Allocate>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_Master];
 }
 def OMP_ParallelMasked : Directive<"parallel masked"> {
   let allowedClauses = [
@@ -969,6 +982,7 @@ def OMP_ParallelMasked : Directive<"parallel masked"> {
     VersionedClause<OMPC_Filter>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_masked];
 }
 def OMP_ParallelSections : Directive<"parallel sections"> {
   let allowedClauses = [
@@ -987,6 +1001,7 @@ def OMP_ParallelSections : Directive<"parallel sections"> {
     VersionedClause<OMPC_If>,
     VersionedClause<OMPC_NumThreads>
   ];
+  let leafs = [OMP_Parallel, OMP_Sections];
 }
 def OMP_ForSimd : Directive<"for simd"> {
   let allowedClauses = [
@@ -1007,6 +1022,7 @@ def OMP_ForSimd : Directive<"for simd"> {
     VersionedClause<OMPC_NonTemporal, 50>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_For, OMP_Simd];
 }
 def OMP_DoSimd : Directive<"do simd"> {
   let allowedClauses = [
@@ -1027,6 +1043,7 @@ def OMP_DoSimd : Directive<"do simd"> {
     VersionedClause<OMPC_NoWait>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Do, OMP_Simd];
 }
 def OMP_CancellationPoint : Directive<"cancellation point"> {}
 def OMP_DeclareReduction : Directive<"declare reduction"> {}
@@ -1104,6 +1121,7 @@ def OMP_TaskLoopSimd : Directive<"taskloop simd"> {
     VersionedClause<OMPC_GrainSize>,
     VersionedClause<OMPC_NumTasks>
   ];
+  let leafs = [OMP_TaskLoop, OMP_Simd];
 }
 def OMP_Distribute : Directive<"distribute"> {
   let allowedClauses = [
@@ -1156,6 +1174,7 @@ def OMP_DistributeParallelFor : Directive<"distribute parallel for"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Distribute, OMP_Parallel, OMP_For];
 }
 def OMP_DistributeParallelDo : Directive<"distribute parallel do"> {
   let allowedClauses = [
@@ -1179,6 +1198,7 @@ def OMP_DistributeParallelDo : Directive<"distribute parallel do"> {
     VersionedClause<OMPC_Ordered>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Distribute, OMP_Parallel, OMP_Do];
 }
 def OMP_DistributeParallelForSimd : Directive<"distribute parallel for simd"> {
   let allowedClauses = [
@@ -1204,6 +1224,7 @@ def OMP_DistributeParallelForSimd : Directive<"distribute parallel for simd"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Distribute, OMP_Parallel, OMP_For, OMP_Simd];
 }
 def OMP_DistributeParallelDoSimd : Directive<"distribute parallel do simd"> {
   let allowedClauses = [
@@ -1228,6 +1249,7 @@ def OMP_DistributeParallelDoSimd : Directive<"distribute parallel do simd"> {
     VersionedClause<OMPC_NonTemporal>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Distribute, OMP_Parallel, OMP_Do, OMP_Simd];
 }
 def OMP_DistributeSimd : Directive<"distribute simd"> {
   let allowedClauses = [
@@ -1254,6 +1276,7 @@ def OMP_DistributeSimd : Directive<"distribute simd"> {
     VersionedClause<OMPC_SimdLen>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Distribute, OMP_Simd];
 }
 
 def OMP_TargetParallelForSimd : Directive<"target parallel for simd"> {
@@ -1291,6 +1314,7 @@ def OMP_TargetParallelForSimd : Directive<"target parallel for simd"> {
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_ThreadLimit, 51>,
   ];
+  let leafs = [OMP_Target, OMP_Parallel, OMP_For, OMP_Simd];
 }
 def OMP_TargetParallelDoSimd : Directive<"target parallel do simd"> {
   let allowedClauses = [
@@ -1322,6 +1346,7 @@ def OMP_TargetParallelDoSimd : Directive<"target parallel do simd"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_UsesAllocators>
   ];
+  let leafs = [OMP_Target, OMP_Parallel, OMP_Do, OMP_Simd];
 }
 def OMP_TargetSimd : Directive<"target simd"> {
   let allowedClauses = [
@@ -1356,6 +1381,7 @@ def OMP_TargetSimd : Directive<"target simd"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_ThreadLimit, 51>,
   ];
+  let leafs = [OMP_Target, OMP_Simd];
 }
 def OMP_TeamsDistribute : Directive<"teams distribute"> {
   let allowedClauses = [
@@ -1375,6 +1401,7 @@ def OMP_TeamsDistribute : Directive<"teams distribute"> {
   let allowedOnceClauses = [
     VersionedClause<OMPC_If>
   ];
+  let leafs = [OMP_Teams, OMP_Distribute];
 }
 def OMP_TeamsDistributeSimd : Directive<"teams distribute simd"> {
   let allowedClauses = [
@@ -1400,6 +1427,7 @@ def OMP_TeamsDistributeSimd : Directive<"teams distribute simd"> {
     VersionedClause<OMPC_ThreadLimit>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Teams, OMP_Distribute, OMP_Simd];
 }
 
 def OMP_TeamsDistributeParallelForSimd :
@@ -1428,6 +1456,7 @@ def OMP_TeamsDistributeParallelForSimd :
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_For, OMP_Simd];
 }
 def OMP_TeamsDistributeParallelDoSimd :
     Directive<"teams distribute parallel do simd"> {
@@ -1456,6 +1485,7 @@ def OMP_TeamsDistributeParallelDoSimd :
     VersionedClause<OMPC_SimdLen>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_Do, OMP_Simd];
 }
 def OMP_TeamsDistributeParallelFor :
     Directive<"teams distribute parallel for"> {
@@ -1479,6 +1509,7 @@ def OMP_TeamsDistributeParallelFor :
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_For];
 }
 def OMP_TeamsDistributeParallelDo :
     Directive<"teams distribute parallel do"> {
@@ -1505,6 +1536,7 @@ let allowedOnceClauses = [
     VersionedClause<OMPC_Schedule>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_Do];
 }
 def OMP_TargetTeams : Directive<"target teams"> {
   let allowedClauses = [
@@ -1532,6 +1564,7 @@ def OMP_TargetTeams : Directive<"target teams"> {
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_OMX_Bare>,
   ];
+  let leafs = [OMP_Target, OMP_Teams];
 }
 def OMP_TargetTeamsDistribute : Directive<"target teams distribute"> {
   let allowedClauses = [
@@ -1560,6 +1593,7 @@ def OMP_TargetTeamsDistribute : Directive<"target teams distribute"> {
     VersionedClause<OMPC_DistSchedule>,
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute];
 }
 
 def OMP_TargetTeamsDistributeParallelFor :
@@ -1594,6 +1628,7 @@ def OMP_TargetTeamsDistributeParallelFor :
   let allowedOnceClauses = [
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_For];
 }
 def OMP_TargetTeamsDistributeParallelDo :
     Directive<"target teams distribute parallel do"> {
@@ -1628,6 +1663,7 @@ def OMP_TargetTeamsDistributeParallelDo :
     VersionedClause<OMPC_Schedule>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_Do];
 }
 def OMP_TargetTeamsDistributeParallelForSimd :
     Directive<"target teams distribute parallel for simd"> {
@@ -1666,6 +1702,7 @@ def OMP_TargetTeamsDistributeParallelForSimd :
   let allowedOnceClauses = [
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_For, OMP_Simd];
 }
 def OMP_TargetTeamsDistributeParallelDoSimd :
     Directive<"target teams distribute parallel do simd"> {
@@ -1704,6 +1741,7 @@ def OMP_TargetTeamsDistributeParallelDoSimd :
     VersionedClause<OMPC_SimdLen>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute, OMP_Parallel, OMP_Do, OMP_Simd];
 }
 def OMP_TargetTeamsDistributeSimd :
     Directive<"target teams distribute simd"> {
@@ -1738,6 +1776,7 @@ def OMP_TargetTeamsDistributeSimd :
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_Distribute, OMP_Simd];
 }
 def OMP_Allocate : Directive<"allocate"> {
   let allowedOnceClauses = [
@@ -1779,6 +1818,7 @@ def OMP_MasterTaskloop : Directive<"master taskloop"> {
     VersionedClause<OMPC_InReduction>,
     VersionedClause<OMPC_Allocate>
   ];
+  let leafs = [OMP_Master, OMP_TaskLoop];
 }
 def OMP_MaskedTaskloop : Directive<"masked taskloop"> {
   let allowedClauses = [
@@ -1801,6 +1841,7 @@ def OMP_MaskedTaskloop : Directive<"masked taskloop"> {
     VersionedClause<OMPC_Allocate>,
     VersionedClause<OMPC_Filter>
   ];
+  let leafs = [OMP_masked, OMP_TaskLoop];
 }
 def OMP_ParallelMasterTaskloop :
     Directive<"parallel master taskloop"> {
@@ -1826,6 +1867,7 @@ def OMP_ParallelMasterTaskloop :
     VersionedClause<OMPC_Copyin>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_Master, OMP_TaskLoop];
 }
 def OMP_ParallelMaskedTaskloop :
     Directive<"parallel masked taskloop"> {
@@ -1852,6 +1894,7 @@ def OMP_ParallelMaskedTaskloop :
     VersionedClause<OMPC_Filter>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_masked, OMP_TaskLoop];
 }
 def OMP_MasterTaskloopSimd : Directive<"master taskloop simd"> {
   let allowedClauses = [
@@ -1879,6 +1922,7 @@ def OMP_MasterTaskloopSimd : Directive<"master taskloop simd"> {
     VersionedClause<OMPC_NonTemporal, 50>,
     VersionedClause<OMPC_Order, 50>
   ];
+  let leafs = [OMP_Master, OMP_TaskLoop, OMP_Simd];
 }
 def OMP_MaskedTaskloopSimd : Directive<"masked taskloop simd"> {
   let allowedClauses = [
@@ -1907,6 +1951,7 @@ def OMP_MaskedTaskloopSimd : Directive<"masked taskloop simd"> {
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_Filter>
   ];
+  let leafs = [OMP_masked, OMP_TaskLoop, OMP_Simd];
 }
 def OMP_ParallelMasterTaskloopSimd :
     Directive<"parallel master taskloop simd"> {
@@ -1938,6 +1983,7 @@ def OMP_ParallelMasterTaskloopSimd :
     VersionedClause<OMPC_Order, 50>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_Master, OMP_TaskLoop, OMP_Simd];
 }
 def OMP_ParallelMaskedTaskloopSimd :
     Directive<"parallel masked taskloop simd"> {
@@ -1970,6 +2016,7 @@ def OMP_ParallelMaskedTaskloopSimd :
     VersionedClause<OMPC_Filter>,
     VersionedClause<OMPC_OMPX_Attribute>,
   ];
+  let leafs = [OMP_Parallel, OMP_masked, OMP_TaskLoop, OMP_Simd];
 }
 def OMP_Depobj : Directive<"depobj"> {
   let allowedClauses = [
@@ -2001,6 +2048,7 @@ def OMP_scope : Directive<"scope"> {
     VersionedClause<OMPC_NoWait, 51>
   ];
 }
+def OMP_Workshare : Directive<"workshare"> {}
 def OMP_ParallelWorkshare : Directive<"parallel workshare"> {
   let allowedClauses = [
     VersionedClause<OMPC_Allocate>,
@@ -2016,8 +2064,8 @@ def OMP_ParallelWorkshare : Directive<"parallel workshare"> {
     VersionedClause<OMPC_NumThreads>,
     VersionedClause<OMPC_ProcBind>
   ];
+  let leafs = [OMP_Parallel, OMP_Workshare];
 }
-def OMP_Workshare : Directive<"workshare"> {}
 def OMP_EndDo : Directive<"end do"> {
   let allowedOnceClauses = [
     VersionedClause<OMPC_NoWait>
@@ -2067,11 +2115,6 @@ def OMP_dispatch : Directive<"dispatch"> {
     VersionedClause<OMPC_Nocontext>
   ];
 }
-def OMP_masked : Directive<"masked"> {
-  let allowedOnceClauses = [
-    VersionedClause<OMPC_Filter>
-  ];
-}
 def OMP_loop : Directive<"loop"> {
   let allowedClauses = [
     VersionedClause<OMPC_LastPrivate>,
@@ -2102,6 +2145,7 @@ def OMP_teams_loop : Directive<"teams loop"> {
     VersionedClause<OMPC_Order>,
     VersionedClause<OMPC_ThreadLimit>,
   ];
+  let leafs = [OMP_Teams, OMP_loop];
 }
 def OMP_target_teams_loop : Directive<"target teams loop"> {
   let allowedClauses = [
@@ -2131,6 +2175,7 @@ def OMP_target_teams_loop : Directive<"target teams loop"> {
     VersionedClause<OMPC_ThreadLimit>,
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
   ];
+  let leafs = [OMP_Target, OMP_Teams, OMP_loop];
 }
 def OMP_parallel_loop : Directive<"parallel loop"> {
   let allowedClauses = [
@@ -2152,6 +2197,7 @@ def OMP_parallel_loop : Directive<"parallel loop"> {
     VersionedClause<OMPC_Order>,
     VersionedClause<OMPC_ProcBind>,
   ];
+  let leafs = [OMP_Parallel, OMP_loop];
 }
 def OMP_target_parallel_loop : Directive<"target parallel loop"> {
   let allowedClauses = [
@@ -2183,11 +2229,13 @@ def OMP_target_parallel_loop : Directive<"target parallel loop"> {
     VersionedClause<OMPC_OMPX_DynCGroupMem>,
     VersionedClause<OMPC_ThreadLimit, 51>,
   ];
+  let leafs = [OMP_Target, OMP_Parallel, OMP_loop];
 }
 def OMP_Metadirective : Directive<"metadirective"> {
   let allowedClauses = [VersionedClause<OMPC_When>];
   let allowedOnceClauses = [VersionedClause<OMPC_Default>];
 }
+
 def OMP_Unknown : Directive<"unknown"> {
   let isDefault = true;
 }
diff --git a/llvm/include/llvm/TableGen/DirectiveEmitter.h b/llvm/include/llvm/TableGen/DirectiveEmitter.h
index c86018715a48a12..f655e584f891e15 100644
--- a/llvm/include/llvm/TableGen/DirectiveEmitter.h
+++ b/llvm/include/llvm/TableGen/DirectiveEmitter.h
@@ -121,6 +121,10 @@ class Directive : public BaseRecord {
   std::vector<Record *> getRequiredClauses() const {
     return Def->getValueAsListOfDefs("requiredClauses");
   }
+
+  std::vector<Record *> getLeafConstructs() const {
+    return Def->getValueAsListOfDefs("leafs");
+  }
 };
 
 // Wrapper class that contains Clause's information defined in DirectiveBase.td
diff --git a/llvm/utils/TableGen/DirectiveEmitter.cpp b/llvm/utils/TableGen/DirectiveEmitter.cpp
index b6aee665f8ee0bb..7cb2a5cbe95954f 100644
--- a/llvm/utils/TableGen/DirectiveEmitter.cpp
+++ b/llvm/utils/TableGen/DirectiveEmitter.cpp
@@ -186,6 +186,7 @@ static void EmitDirectivesDecl(RecordKeeper &Records, raw_ostream &OS) {
 
   if (DirLang.hasEnableBitmaskEnumInNamespace())
     OS << "\n#include \"llvm/ADT/BitmaskEnum.h\"\n";
+  OS << "#include \"llvm/ADT/SmallVector.h\"\n";
 
   OS << "\n";
   OS << "namespace llvm {\n";
@@ -231,6 +232,7 @@ static void EmitDirectivesDecl(RecordKeeper &Records, raw_ostream &OS) {
   OS << "bool isAllowedClauseForDirective(Directive D, "
      << "Clause C, unsigned Version);\n";
   OS << "\n";
+  OS << "const llvm::SmallVector<Directive> &getLeafConstructs(Directive D);\n";
   if (EnumHelperFuncs.length() > 0) {
     OS << EnumHelperFuncs;
     OS << "\n";
@@ -435,6 +437,78 @@ static void GenerateIsAllowedClause(const DirectiveLanguage &DirLang,
   OS << "}\n"; // End of function isAllowedClauseForDirective
 }
 
+// Generate the getLeafConstructs function implementation.
+static void GenerateGetLeafConstructs(const DirectiveLanguage &DirLang,
+                                      raw_ostream &OS) {
+  auto getQualifiedName = [&](StringRef Formatted) -> std::string {
+    return (llvm::Twine("llvm::") + DirLang.getCppNamespace() +
+            "::Directive::" + DirLang.getDirectivePrefix() + Formatted)
+        .str();
+  };
+
+  // For each list of leafs, generate a static local object, then
+  // return a reference to that object for a given directive, e.g.
+  //
+  //   static ListTy leafConstructs_A_B = { A, B };
+  //   static ListTy leafConstructs_C_D_E = { C, D, E };
+  //   switch (Dir) {
+  //     case A_B:
+  //       return leafConstructs_A_B;
+  //     case C_D_E:
+  //       return leafConstructs_C_D_E;
+
+  // Map from a record that defines a directive to the name of the
+  // local object with the list of its leafs.
+  DenseMap<Record *, std::string> ListNames;
+
+  std::string DirectiveTypeName =
+      std::string("llvm::") + DirLang.getCppNamespace().str() + "::Directive";
+  std::string DirectiveListTypeName =
+      std::string("llvm::SmallVector<") + DirectiveTypeName + ">";
+
+  // const Container &llvm::<ns>::GetLeafConstructs(llvm::<ns>::Directive Dir)
+  OS << "const " << DirectiveListTypeName
+     << " &llvm::" << DirLang.getCppNamespace() << "::getLeafConstructs("
+     << DirectiveTypeName << " Dir) ";
+  OS << "{\n";
+
+  // Generate the locals.
+  for (Record *R : DirLang.getDirectives()) {
+    Directive Dir{R};
+
+    std::vector<Record *> LeafConstructs = Dir.getLeafConstructs();
+    if (LeafConstructs.empty())
+      continue;
+
+    std::string ListName = "leafConstructs_" + Dir.getFormattedName();
+    OS << "  static " << DirectiveListTypeName << ' ' << ListName << " {\n";
+    for (Record *L : LeafConstructs) {
+      Directive LeafDir{L};
+      OS << "    " << getQualifiedName(LeafDir.getFormattedName()) << ",\n";
+    }
+    OS << "  };\n";
+    ListNames.insert(std::make_pair(R, std::move(ListName)));
+  }
+
+  OS << "  static " << DirectiveListTypeName << " nothing {};\n";
+
+  OS << '\n';
+  OS << "  switch (Dir) {\n";
+  for (Record *R : DirLang.getDirectives()) {
+    auto F = ListNames.find(R);
+    if (F == ListNames.end())
+      continue;
+
+    Directive Dir{R};
+    OS << "  case " << getQualifiedName(Dir.getFormattedName()) << ":\n";
+    OS << "    return " << F->second << ";\n";
+  }
+  OS << "  default:\n";
+  OS << "    return nothing;\n";
+  OS << "  } // switch (Dir)\n";
+  OS << "}\n";
+}
+
 // Generate a simple enum set with the give clauses.
 static void GenerateClauseSet(const std::vector<Record *> &Clauses,
                               raw_ostream &OS, StringRef ClauseSetPrefix,
@@ -876,6 +950,9 @@ void EmitDirectivesBasicImpl(const DirectiveLanguage &DirLang,
 
   // isAllowedClauseForDirective(Directive D, Clause C, unsigned Version)
   GenerateIsAllowedClause(DirLang, OS);
+
+  // getLeafConstructs(Directive D)
+  GenerateGetLeafConstructs(DirLang, OS);
 }
 
 // Generate the implemenation section for the enumeration in the directive

>From 209a6d79dcd99db201f4a156d5bfc39136b6693e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 29 Jan 2024 09:45:25 -0600
Subject: [PATCH 2/4] [flang][OpenMP] Set OpenMP attributes in MLIR module in
 bbc before lowering

Right now attributes like OpenMP version or target attributes for offload
are set after lowering in bbc. The flang frontend sets them before lowering,
making them available in the lowering process.

This change sets them before lowering in bbc as well.
---
 flang/tools/bbc/bbc.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index c9358c83e795c44..bdae1731260de11 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -359,7 +359,6 @@ static mlir::LogicalResult convertFortranSourceToMLIR(
       semanticsContext.targetCharacteristics(), parsing.allCooked(),
       targetTriple, kindMap, loweringOptions, {},
       semanticsContext.languageFeatures(), targetMachine);
-  burnside.lower(parseTree, semanticsContext);
   mlir::ModuleOp mlirModule = burnside.getModule();
   if (enableOpenMP) {
     if (enableOpenMPGPU && !enableOpenMPDevice) {
@@ -375,6 +374,7 @@ static mlir::LogicalResult convertFortranSourceToMLIR(
     setOffloadModuleInterfaceAttributes(mlirModule, offloadModuleOpts);
     setOpenMPVersionAttribute(mlirModule, setOpenMPVersion);
   }
+  burnside.lower(parseTree, semanticsContext);
   std::error_code ec;
   std::string outputName = outputFilename;
   if (!outputName.size())

>From b20e19509cd4cd32682ecc191f403038adc4f9ca Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 22 Feb 2024 12:10:01 -0600
Subject: [PATCH 3/4] [flang][OpenMP] Implement getOpenMPVersion function

---
 flang/lib/Lower/OpenMP/Utils.cpp | 5 +++++
 flang/lib/Lower/OpenMP/Utils.h   | 2 ++
 2 files changed, 7 insertions(+)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 9a6a28ded7006d2..d42ac56fa98c678 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -29,6 +29,11 @@ namespace Fortran {
 namespace lower {
 namespace omp {
 
+uint32_t getOpenMPVersion(mlir::ModuleOp mod) {
+  mlir::Attribute verAttr = mod->getAttr("omp.version");
+  return llvm::cast<mlir::omp::VersionAttr>(verAttr).getVersion();
+}
+
 void genObjectList(const ObjectList &objects,
                    Fortran::lower::AbstractConverter &converter,
                    llvm::SmallVectorImpl<mlir::Value> &operands) {
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 4ab4bc9c137071c..b7542851ce8fba7 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -38,6 +38,8 @@ class AbstractConverter;
 
 namespace omp {
 
+uint32_t getOpenMPVersion(mlir::ModuleOp mod);
+
 using DeclareTargetCapturePair =
     std::pair<mlir::omp::DeclareTargetCaptureClause,
               Fortran::semantics::Symbol>;

>From a18c4e3dc455425fa9048201fdebaec1f5babff7 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 21 Feb 2024 12:51:13 -0600
Subject: [PATCH 4/4] [flang][OpenMP] Implement flexible OpenMP clause
 representation

Introduce a set of generic classes (templates) that represent OpenMP
clauses in a language-agnostic manner. OpenMP clauses can contain
expressions and data objects and the exact representation of each
depends on the source language of the compiled program. To deal with
this, the templates depend on two type parameters:
- IdType: type that represent object's identity (in a way that
  satisfied OpenMP requirements), and
- ExprType: type that can represent numeric values, as well as
  data references (e.g. x.y[1].z[2]).

In addition to that, implement code instantiating these templates
from flang's AST.

This patch only introduces the new classes, they are not yet used
anywhere.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    |  180 +-
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |    3 +-
 flang/lib/Lower/OpenMP/ClauseT.h              |  715 ------
 flang/lib/Lower/OpenMP/Clauses.cpp            |  789 +++++--
 flang/lib/Lower/OpenMP/Clauses.h              |  225 +-
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp |    8 +-
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  124 +-
 flang/lib/Lower/OpenMP/Utils.cpp              |   22 +
 flang/lib/Lower/OpenMP/Utils.h                |    8 +-
 llvm/include/llvm/Frontend/OpenMP/ClauseT.h   | 2035 +++++++++++++++++
 10 files changed, 3024 insertions(+), 1085 deletions(-)
 delete mode 100644 flang/lib/Lower/OpenMP/ClauseT.h
 create mode 100644 llvm/include/llvm/Frontend/OpenMP/ClauseT.h

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index f97637fda136f52..e3d6422df52dd8d 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -31,57 +31,33 @@ static void checkMapType(mlir::Location location, mlir::Type type) {
 }
 
 static mlir::omp::ScheduleModifier
-translateScheduleModifier(const omp::clause::Schedule::ModType &m) {
+translateScheduleModifier(const omp::clause::Schedule::OrderingModifier &m) {
   switch (m) {
-  case omp::clause::Schedule::ModType::Monotonic:
+  case omp::clause::Schedule::OrderingModifier::Monotonic:
     return mlir::omp::ScheduleModifier::monotonic;
-  case omp::clause::Schedule::ModType::Nonmonotonic:
+  case omp::clause::Schedule::OrderingModifier::Nonmonotonic:
     return mlir::omp::ScheduleModifier::nonmonotonic;
-  case omp::clause::Schedule::ModType::Simd:
-    return mlir::omp::ScheduleModifier::simd;
   }
   return mlir::omp::ScheduleModifier::none;
 }
 
 static mlir::omp::ScheduleModifier
 getScheduleModifier(const omp::clause::Schedule &clause) {
-  using ScheduleModifier = omp::clause::Schedule::ScheduleModifier;
-  const auto &modifier = std::get<std::optional<ScheduleModifier>>(clause.t);
-  // The input may have the modifier any order, so we look for one that isn't
-  // SIMD. If modifier is not set at all, fall down to the bottom and return
-  // "none".
-  if (modifier) {
-    using ModType = omp::clause::Schedule::ModType;
-    const auto &modType1 = std::get<ModType>(modifier->t);
-    if (modType1 == ModType::Simd) {
-      const auto &modType2 = std::get<std::optional<ModType>>(modifier->t);
-      if (modType2 && *modType2 != ModType::Simd)
-        return translateScheduleModifier(*modType2);
-      return mlir::omp::ScheduleModifier::none;
-    }
-
-    return translateScheduleModifier(modType1);
-  }
+  using Schedule = omp::clause::Schedule;
+  const auto &modifier =
+      std::get<std::optional<Schedule::OrderingModifier>>(clause.t);
+  if (modifier)
+    return translateScheduleModifier(*modifier);
   return mlir::omp::ScheduleModifier::none;
 }
 
 static mlir::omp::ScheduleModifier
 getSimdModifier(const omp::clause::Schedule &clause) {
-  using ScheduleModifier = omp::clause::Schedule::ScheduleModifier;
-  const auto &modifier = std::get<std::optional<ScheduleModifier>>(clause.t);
-  // Either of the two possible modifiers in the input can be the SIMD modifier,
-  // so look in either one, and return simd if we find one. Not found = return
-  // "none".
-  if (modifier) {
-    using ModType = omp::clause::Schedule::ModType;
-    const auto &modType1 = std::get<ModType>(modifier->t);
-    if (modType1 == ModType::Simd)
-      return mlir::omp::ScheduleModifier::simd;
-
-    const auto &modType2 = std::get<std::optional<ModType>>(modifier->t);
-    if (modType2 && *modType2 == ModType::Simd)
-      return mlir::omp::ScheduleModifier::simd;
-  }
+  using Schedule = omp::clause::Schedule;
+  const auto &modifier =
+      std::get<std::optional<Schedule::ChunkModifier>>(clause.t);
+  if (modifier && *modifier == Schedule::ChunkModifier::Simd)
+    return mlir::omp::ScheduleModifier::simd;
   return mlir::omp::ScheduleModifier::none;
 }
 
@@ -94,37 +70,31 @@ genAllocateClause(Fortran::lower::AbstractConverter &converter,
   mlir::Location currentLocation = converter.getCurrentLocation();
   Fortran::lower::StatementContext stmtCtx;
 
-  mlir::Value allocatorOperand;
-  const omp::ObjectList &objectList = std::get<omp::ObjectList>(clause.t);
-  const auto &modifier =
-      std::get<std::optional<omp::clause::Allocate::Modifier>>(clause.t);
-
-  // If the allocate modifier is present, check if we only use the allocator
-  // submodifier.  ALIGN in this context is unimplemented
-  const bool onlyAllocator =
-      modifier &&
-      std::holds_alternative<omp::clause::Allocate::Modifier::Allocator>(
-          modifier->u);
+  auto &objects = std::get<omp::ObjectList>(clause.t);
 
-  if (modifier && !onlyAllocator) {
+  using Allocate = omp::clause::Allocate;
+  // ALIGN in this context is unimplemented
+  if (std::get<std::optional<Allocate::AlignModifier>>(clause.t))
     TODO(currentLocation, "OmpAllocateClause ALIGN modifier");
-  }
 
   // Check if allocate clause has allocator specified. If so, add it
   // to list of allocators, otherwise, add default allocator to
   // list of allocators.
-  if (onlyAllocator) {
-    const auto &value =
-        std::get<omp::clause::Allocate::Modifier::Allocator>(modifier->u);
-    mlir::Value operand =
-        fir::getBase(converter.genExprValue(value.v, stmtCtx));
-    allocatorOperands.append(objectList.size(), operand);
+  using SimpleModifier = Allocate::AllocatorSimpleModifier;
+  using ComplexModifier = Allocate::AllocatorComplexModifier;
+  if (auto &mod = std::get<std::optional<SimpleModifier>>(clause.t)) {
+    mlir::Value operand = fir::getBase(converter.genExprValue(*mod, stmtCtx));
+    allocatorOperands.append(objects.size(), operand);
+  } else if (auto &mod = std::get<std::optional<ComplexModifier>>(clause.t)) {
+    mlir::Value operand = fir::getBase(converter.genExprValue(mod->v, stmtCtx));
+    allocatorOperands.append(objects.size(), operand);
   } else {
     mlir::Value operand = firOpBuilder.createIntegerConstant(
         currentLocation, firOpBuilder.getI32Type(), 1);
-    allocatorOperands.append(objectList.size(), operand);
+    allocatorOperands.append(objects.size(), operand);
   }
-  genObjectList(objectList, converter, allocateOperands);
+
+  genObjectList(objects, converter, allocateOperands);
 }
 
 static mlir::omp::ClauseProcBindKindAttr
@@ -132,16 +102,16 @@ genProcBindKindAttr(fir::FirOpBuilder &firOpBuilder,
                     const omp::clause::ProcBind &clause) {
   mlir::omp::ClauseProcBindKind procBindKind;
   switch (clause.v) {
-  case omp::clause::ProcBind::Type::Master:
+  case omp::clause::ProcBind::AffinityPolicy::Master:
     procBindKind = mlir::omp::ClauseProcBindKind::Master;
     break;
-  case omp::clause::ProcBind::Type::Close:
+  case omp::clause::ProcBind::AffinityPolicy::Close:
     procBindKind = mlir::omp::ClauseProcBindKind::Close;
     break;
-  case omp::clause::ProcBind::Type::Spread:
+  case omp::clause::ProcBind::AffinityPolicy::Spread:
     procBindKind = mlir::omp::ClauseProcBindKind::Spread;
     break;
-  case omp::clause::ProcBind::Type::Primary:
+  case omp::clause::ProcBind::AffinityPolicy::Primary:
     procBindKind = mlir::omp::ClauseProcBindKind::Primary;
     break;
   }
@@ -151,21 +121,22 @@ genProcBindKindAttr(fir::FirOpBuilder &firOpBuilder,
 
 static mlir::omp::ClauseTaskDependAttr
 genDependKindAttr(fir::FirOpBuilder &firOpBuilder,
-                  const omp::clause::Depend &clause) {
+                  const omp::clause::Depend::TaskDependenceType kind) {
   mlir::omp::ClauseTaskDepend pbKind;
-  const auto &inOut = std::get<omp::clause::Depend::InOut>(clause.u);
-  switch (std::get<omp::clause::Depend::Type>(inOut.t)) {
-  case omp::clause::Depend::Type::In:
+  switch (kind) {
+  case omp::clause::Depend::TaskDependenceType::In:
     pbKind = mlir::omp::ClauseTaskDepend::taskdependin;
     break;
-  case omp::clause::Depend::Type::Out:
+  case omp::clause::Depend::TaskDependenceType::Out:
     pbKind = mlir::omp::ClauseTaskDepend::taskdependout;
     break;
-  case omp::clause::Depend::Type::Inout:
+  case omp::clause::Depend::TaskDependenceType::Inout:
     pbKind = mlir::omp::ClauseTaskDepend::taskdependinout;
     break;
-  default:
-    llvm_unreachable("unknown parser task dependence type");
+  case omp::clause::Depend::TaskDependenceType::Mutexinoutset:
+  case omp::clause::Depend::TaskDependenceType::Inoutset:
+  case omp::clause::Depend::TaskDependenceType::Depobj:
+    llvm_unreachable("unhandled parser task dependence type");
     break;
   }
   return mlir::omp::ClauseTaskDependAttr::get(firOpBuilder.getContext(),
@@ -275,16 +246,16 @@ bool ClauseProcessor::processDefault() const {
   if (auto *clause = findUniqueClause<omp::clause::Default>()) {
     // Private, Firstprivate, Shared, None
     switch (clause->v) {
-    case omp::clause::Default::Type::Shared:
-    case omp::clause::Default::Type::None:
+    case omp::clause::Default::DataSharingAttribute::Shared:
+    case omp::clause::Default::DataSharingAttribute::None:
       // Default clause with shared or none do not require any handling since
       // Shared is the default behavior in the IR and None is only required
       // for semantic checks.
       break;
-    case omp::clause::Default::Type::Private:
+    case omp::clause::Default::DataSharingAttribute::Private:
       // TODO Support default(private)
       break;
-    case omp::clause::Default::Type::Firstprivate:
+    case omp::clause::Default::DataSharingAttribute::Firstprivate:
       // TODO Support default(firstprivate)
       break;
     }
@@ -317,13 +288,13 @@ bool ClauseProcessor::processDeviceType(
   if (auto *clause = findUniqueClause<omp::clause::DeviceType>()) {
     // Case: declare target ... device_type(any | host | nohost)
     switch (clause->v) {
-    case omp::clause::DeviceType::Type::Nohost:
+    case omp::clause::DeviceType::DeviceTypeDescription::Nohost:
       result = mlir::omp::DeclareTargetDeviceType::nohost;
       break;
-    case omp::clause::DeviceType::Type::Host:
+    case omp::clause::DeviceType::DeviceTypeDescription::Host:
       result = mlir::omp::DeclareTargetDeviceType::host;
       break;
-    case omp::clause::DeviceType::Type::Any:
+    case omp::clause::DeviceType::DeviceTypeDescription::Any:
       result = mlir::omp::DeclareTargetDeviceType::any;
       break;
     }
@@ -371,7 +342,9 @@ bool ClauseProcessor::processNumTeams(Fortran::lower::StatementContext &stmtCtx,
   // TODO Get lower and upper bounds for num_teams when parser is updated to
   // accept both.
   if (auto *clause = findUniqueClause<omp::clause::NumTeams>()) {
-    result = fir::getBase(converter.genExprValue(clause->v, stmtCtx));
+    // auto lowerBound = std::get<std::optional<ExprTy>>(clause->t);
+    auto &upperBound = std::get<ExprTy>(clause->t);
+    result = fir::getBase(converter.genExprValue(upperBound, stmtCtx));
     return true;
   }
   return false;
@@ -436,24 +409,23 @@ bool ClauseProcessor::processSchedule(
   if (auto *clause = findUniqueClause<omp::clause::Schedule>()) {
     fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
     mlir::MLIRContext *context = firOpBuilder.getContext();
-    const auto &scheduleType =
-        std::get<omp::clause::Schedule::ScheduleType>(clause->t);
+    const auto &scheduleType = std::get<omp::clause::Schedule::Kind>(clause->t);
 
     mlir::omp::ClauseScheduleKind scheduleKind;
     switch (scheduleType) {
-    case omp::clause::Schedule::ScheduleType::Static:
+    case omp::clause::Schedule::Kind::Static:
       scheduleKind = mlir::omp::ClauseScheduleKind::Static;
       break;
-    case omp::clause::Schedule::ScheduleType::Dynamic:
+    case omp::clause::Schedule::Kind::Dynamic:
       scheduleKind = mlir::omp::ClauseScheduleKind::Dynamic;
       break;
-    case omp::clause::Schedule::ScheduleType::Guided:
+    case omp::clause::Schedule::Kind::Guided:
       scheduleKind = mlir::omp::ClauseScheduleKind::Guided;
       break;
-    case omp::clause::Schedule::ScheduleType::Auto:
+    case omp::clause::Schedule::Kind::Auto:
       scheduleKind = mlir::omp::ClauseScheduleKind::Auto;
       break;
-    case omp::clause::Schedule::ScheduleType::Runtime:
+    case omp::clause::Schedule::Kind::Runtime:
       scheduleKind = mlir::omp::ClauseScheduleKind::Runtime;
       break;
     }
@@ -729,13 +701,15 @@ bool ClauseProcessor::processDepend(
   return findRepeatableClause<omp::clause::Depend>(
       [&](const omp::clause::Depend &clause,
           const Fortran::parser::CharBlock &) {
-        assert(std::holds_alternative<omp::clause::Depend::InOut>(clause.u) &&
-               "Only InOut is handled at the moment");
-        const auto &inOut = std::get<omp::clause::Depend::InOut>(clause.u);
-        const auto &objects = std::get<omp::ObjectList>(inOut.t);
+        using Depend = omp::clause::Depend;
+        assert(std::holds_alternative<Depend::WithLocators>(clause.u) &&
+               "Only the modern form is handled at the moment");
+        auto &modern = std::get<Depend::WithLocators>(clause.u);
+        auto kind = std::get<Depend::TaskDependenceType>(modern.t);
+        auto &objects = std::get<omp::ObjectList>(modern.t);
 
         mlir::omp::ClauseTaskDependAttr dependTypeOperand =
-            genDependKindAttr(firOpBuilder, clause);
+            genDependKindAttr(firOpBuilder, kind);
         dependTypeOperands.append(objects.size(), dependTypeOperand);
 
         for (const omp::Object &object : objects) {
@@ -824,38 +798,38 @@ bool ClauseProcessor::processMap(
           const Fortran::parser::CharBlock &source) {
         using Map = omp::clause::Map;
         mlir::Location clauseLocation = converter.genLocation(source);
-        const auto &oMapType = std::get<std::optional<Map::MapType>>(clause.t);
+        const auto &mapType = std::get<std::optional<Map::MapType>>(clause.t);
         llvm::omp::OpenMPOffloadMappingFlags mapTypeBits =
             llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
         // If the map type is specified, then process it else Tofrom is the
         // default.
-        if (oMapType) {
-          const Map::MapType::Type &mapType =
-              std::get<Map::MapType::Type>(oMapType->t);
-          switch (mapType) {
-          case Map::MapType::Type::To:
+        if (mapType) {
+          switch (*mapType) {
+          case Map::MapType::To:
             mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
             break;
-          case Map::MapType::Type::From:
+          case Map::MapType::From:
             mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
             break;
-          case Map::MapType::Type::Tofrom:
+          case Map::MapType::Tofrom:
             mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO |
                            llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
             break;
-          case Map::MapType::Type::Alloc:
-          case Map::MapType::Type::Release:
+          case Map::MapType::Alloc:
+          case Map::MapType::Release:
             // alloc and release is the default map_type for the Target Data
             // Ops, i.e. if no bits for map_type is supplied then alloc/release
             // is implicitly assumed based on the target directive. Default
             // value for Target Data and Enter Data is alloc and for Exit Data
             // it is release.
             break;
-          case Map::MapType::Type::Delete:
+          case Map::MapType::Delete:
             mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
           }
 
-          if (std::get<std::optional<Map::MapType::Always>>(oMapType->t))
+          auto &modTypeMod =
+              std::get<std::optional<Map::MapTypeModifier>>(clause.t);
+          if (modTypeMod && *modTypeMod == Map::MapTypeModifier::Always)
             mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
         } else {
           mapTypeBits |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO |
@@ -929,7 +903,7 @@ bool ClauseProcessor::processTo(
   return findRepeatableClause<omp::clause::To>(
       [&](const omp::clause::To &clause, const Fortran::parser::CharBlock &) {
         // Case: declare target to(func, var1, var2)...
-        gatherFuncAndVarSyms(clause.v,
+        gatherFuncAndVarSyms(std::get<ObjectList>(clause.t),
                              mlir::omp::DeclareTargetCaptureClause::to, result);
       });
 }
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index f2d8bcebf0b07a5..cbfb9e26ddfbe57 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -201,7 +201,8 @@ bool ClauseProcessor::processMotionClauses(
                 ? llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO
                 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
 
-        for (const omp::Object &object : clause.v) {
+        auto &objects = std::get<ObjectList>(clause.t);
+        for (const omp::Object &object : objects) {
           llvm::SmallVector<mlir::Value> bounds;
           std::stringstream asFortran;
           Fortran::lower::AddrAndBoundsInfo info =
diff --git a/flang/lib/Lower/OpenMP/ClauseT.h b/flang/lib/Lower/OpenMP/ClauseT.h
deleted file mode 100644
index b9fbbd18bf2b941..000000000000000
--- a/flang/lib/Lower/OpenMP/ClauseT.h
+++ /dev/null
@@ -1,715 +0,0 @@
-//===- ClauseT -- clause template definitions -----------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-#ifndef FORTRAN_LOWER_OPENMP_CLAUSET_H
-#define FORTRAN_LOWER_OPENMP_CLAUSET_H
-
-#include "flang/Parser/parse-tree.h" // For enum reuse
-
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Support/raw_ostream.h"
-
-#include <algorithm>
-#include <iterator>
-#include <optional>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include <variant>
-#include <vector>
-
-#include "llvm/Frontend/OpenMP/OMP.h.inc"
-
-namespace tomp {
-
-template <typename T>
-using ListT = std::vector<T>;
-
-// A specialization of ObjectT<Id, Expr> must provide the following definitions:
-// {
-//    using IdType = Id;
-//    using ExprType = Expr;
-//
-//    auto id() const -> Id {
-//      return the identifier of the object (for use in tests for
-//         presence/absence of the object)
-//    }
-//
-//    auto ref() const -> const Expr& {
-//      return the expression accessing (referencing) the object
-//    }
-// }
-//
-// For example, the ObjectT instance created for "var[x+1]" would have
-// the `id()` return the identifier for `var`, and the `ref()` return the
-// representation of the array-access `var[x+1]`.
-template <typename Id, typename Expr>
-struct ObjectT;
-
-template <typename I, typename E>
-using ObjectListT = ListT<ObjectT<I, E>>;
-
-namespace clause {
-// Helper objects
-
-template <typename I, typename E>
-struct DefinedOperatorT {
-  struct DefinedOpName {
-    using WrapperTrait = std::true_type;
-    ObjectT<I, E> v;
-  };
-  using IntrinsicOperator = Fortran::parser::DefinedOperator::IntrinsicOperator;
-  using UnionTrait = std::true_type;
-  std::variant<DefinedOpName, IntrinsicOperator> u;
-};
-
-template <typename I, typename E>
-struct ProcedureDesignatorT {
-  using WrapperTrait = std::true_type;
-  ObjectT<I, E> v;
-};
-
-template <typename I, typename E>
-struct ReductionOperatorT {
-  using UnionTrait = std::true_type;
-  std::variant<DefinedOperatorT<I, E>, ProcedureDesignatorT<I, E>> u;
-};
-
-template <typename I, typename E>
-struct AcqRelT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AcquireT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AdjustArgsT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AffinityT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AlignT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AppendArgsT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct AtT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct BindT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct CancellationConstructTypeT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct CaptureT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct CompareT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct DepobjT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct DestroyT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct DetachT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct DoacrossT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct DynamicAllocatorsT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ExclusiveT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct FailT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct FlushT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct FullT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct InbranchT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct InclusiveT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct IndirectT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct InitT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct MatchT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct MemoryOrderT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct MergeableT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct MessageT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct NogroupT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct NotinbranchT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct NowaitT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct OmpxAttributeT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct OmpxBareT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ReadT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct RelaxedT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ReleaseT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ReverseOffloadT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct SeqCstT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct SeverityT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct SimdT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ThreadprivateT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct ThreadsT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UnifiedAddressT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UnifiedSharedMemoryT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UnknownT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UntiedT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UpdateT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UseT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct UsesAllocatorsT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct WeakT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct WhenT {
-  using EmptyTrait = std::true_type;
-};
-template <typename I, typename E>
-struct WriteT {
-  using EmptyTrait = std::true_type;
-};
-
-template <typename I, typename E>
-struct AlignedT {
-  using TupleTrait = std::true_type;
-  std::tuple<ObjectListT<I, E>, std::optional<E>> t;
-};
-
-template <typename I, typename E>
-struct AllocateT {
-  struct Modifier {
-    struct Allocator {
-      using WrapperTrait = std::true_type;
-      E v;
-    };
-    struct Align {
-      using WrapperTrait = std::true_type;
-      E v;
-    };
-    struct ComplexModifier {
-      using TupleTrait = std::true_type;
-      std::tuple<Allocator, Align> t;
-    };
-    using UnionTrait = std::true_type;
-    std::variant<Allocator, ComplexModifier, Align> u;
-  };
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<Modifier>, ObjectListT<I, E>> t;
-};
-
-template <typename I, typename E>
-struct AllocatorT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct AtomicDefaultMemOrderT {
-  using WrapperTrait = std::true_type;
-  using OmpAtomicDefaultMemOrderType =
-      Fortran::common::OmpAtomicDefaultMemOrderType;
-  OmpAtomicDefaultMemOrderType v;
-};
-
-template <typename I, typename E>
-struct CollapseT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct CopyinT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct CopyprivateT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct DefaultmapT {
-  using ImplicitBehavior =
-      Fortran::parser::OmpDefaultmapClause::ImplicitBehavior;
-  using VariableCategory =
-      Fortran::parser::OmpDefaultmapClause::VariableCategory;
-  using TupleTrait = std::true_type;
-  std::tuple<ImplicitBehavior, std::optional<VariableCategory>> t;
-};
-
-template <typename I, typename E>
-struct DefaultT {
-  using Type = Fortran::parser::OmpDefaultClause::Type;
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
-template <typename I, typename E>
-struct DependT {
-  struct Source {
-    using EmptyTrait = std::true_type;
-  };
-  struct Sink {
-    using Length = std::tuple<DefinedOperatorT<I, E>, E>;
-    using Vec = std::tuple<ObjectT<I, E>, std::optional<Length>>;
-    using WrapperTrait = std::true_type;
-    ListT<Vec> v;
-  };
-  using Type = Fortran::parser::OmpDependenceType::Type;
-  struct InOut {
-    using TupleTrait = std::true_type;
-    std::tuple<Type, ObjectListT<I, E>> t;
-  };
-  using UnionTrait = std::true_type;
-  std::variant<Source, Sink, InOut> u;
-};
-
-template <typename I, typename E>
-struct DeviceT {
-  using DeviceModifier = Fortran::parser::OmpDeviceClause::DeviceModifier;
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<DeviceModifier>, E> t;
-};
-
-template <typename I, typename E>
-struct DeviceTypeT {
-  using Type = Fortran::parser::OmpDeviceTypeClause::Type;
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
-template <typename I, typename E>
-struct DistScheduleT {
-  using WrapperTrait = std::true_type;
-  std::optional<E> v;
-};
-
-template <typename I, typename E>
-struct EnterT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct FilterT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct FinalT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct FirstprivateT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct FromT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct GrainsizeT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct HasDeviceAddrT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct HintT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct IfT {
-  using DirectiveNameModifier =
-      Fortran::parser::OmpIfClause::DirectiveNameModifier;
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<DirectiveNameModifier>, E> t;
-};
-
-template <typename I, typename E>
-struct InReductionT {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperatorT<I, E>, ObjectListT<I, E>> t;
-};
-
-template <typename I, typename E>
-struct IsDevicePtrT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct LastprivateT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct LinearT {
-  struct Modifier {
-    using Type = Fortran::parser::OmpLinearModifier::Type;
-    using WrapperTrait = std::true_type;
-    Type v;
-  };
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<Modifier>, ObjectListT<I, E>, std::optional<E>> t;
-};
-
-template <typename I, typename E>
-struct LinkT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct MapT {
-  struct MapType {
-    struct Always {
-      using EmptyTrait = std::true_type;
-    };
-    using Type = Fortran::parser::OmpMapType::Type;
-    using TupleTrait = std::true_type;
-    std::tuple<std::optional<Always>, Type> t;
-  };
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<MapType>, ObjectListT<I, E>> t;
-};
-
-template <typename I, typename E>
-struct NocontextT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct NontemporalT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct NovariantsT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct NumTasksT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct NumTeamsT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct NumThreadsT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct OmpxDynCgroupMemT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct OrderedT {
-  using WrapperTrait = std::true_type;
-  std::optional<E> v;
-};
-
-template <typename I, typename E>
-struct OrderT {
-  using Kind = Fortran::parser::OmpOrderModifier::Kind;
-  using Type = Fortran::parser::OmpOrderClause::Type;
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<Kind>, Type> t;
-};
-
-template <typename I, typename E>
-struct PartialT {
-  using WrapperTrait = std::true_type;
-  std::optional<E> v;
-};
-
-template <typename I, typename E>
-struct PriorityT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct PrivateT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct ProcBindT {
-  using Type = Fortran::parser::OmpProcBindClause::Type;
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
-template <typename I, typename E>
-struct ReductionT {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperatorT<I, E>, ObjectListT<I, E>> t;
-};
-
-template <typename I, typename E>
-struct SafelenT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct ScheduleT {
-  using ModType = Fortran::parser::OmpScheduleModifierType::ModType;
-  struct ScheduleModifier {
-    using TupleTrait = std::true_type;
-    std::tuple<ModType, std::optional<ModType>> t;
-  };
-  using ScheduleType = Fortran::parser::OmpScheduleClause::ScheduleType;
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<ScheduleModifier>, ScheduleType, std::optional<E>> t;
-};
-
-template <typename I, typename E>
-struct SharedT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct SimdlenT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct SizesT {
-  using WrapperTrait = std::true_type;
-  ListT<E> v;
-};
-
-template <typename I, typename E>
-struct TaskReductionT {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperatorT<I, E>, ObjectListT<I, E>> t;
-};
-
-template <typename I, typename E>
-struct ThreadLimitT {
-  using WrapperTrait = std::true_type;
-  E v;
-};
-
-template <typename I, typename E>
-struct ToT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct UniformT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct UseDeviceAddrT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-struct UseDevicePtrT {
-  using WrapperTrait = std::true_type;
-  ObjectListT<I, E> v;
-};
-
-template <typename I, typename E>
-using UnionOfAllClausesT = std::variant<
-    AcqRelT<I, E>, AcquireT<I, E>, AdjustArgsT<I, E>, AffinityT<I, E>,
-    AlignT<I, E>, AlignedT<I, E>, AllocateT<I, E>, AllocatorT<I, E>,
-    AppendArgsT<I, E>, AtT<I, E>, AtomicDefaultMemOrderT<I, E>, BindT<I, E>,
-    CancellationConstructTypeT<I, E>, CaptureT<I, E>, CollapseT<I, E>,
-    CompareT<I, E>, CopyprivateT<I, E>, CopyinT<I, E>, DefaultT<I, E>,
-    DefaultmapT<I, E>, DependT<I, E>, DepobjT<I, E>, DestroyT<I, E>,
-    DetachT<I, E>, DeviceT<I, E>, DeviceTypeT<I, E>, DistScheduleT<I, E>,
-    DoacrossT<I, E>, DynamicAllocatorsT<I, E>, EnterT<I, E>, ExclusiveT<I, E>,
-    FailT<I, E>, FilterT<I, E>, FinalT<I, E>, FirstprivateT<I, E>, FlushT<I, E>,
-    FromT<I, E>, FullT<I, E>, GrainsizeT<I, E>, HasDeviceAddrT<I, E>,
-    HintT<I, E>, IfT<I, E>, InReductionT<I, E>, InbranchT<I, E>,
-    InclusiveT<I, E>, IndirectT<I, E>, InitT<I, E>, IsDevicePtrT<I, E>,
-    LastprivateT<I, E>, LinearT<I, E>, LinkT<I, E>, MapT<I, E>, MatchT<I, E>,
-    MemoryOrderT<I, E>, MergeableT<I, E>, MessageT<I, E>, NogroupT<I, E>,
-    NowaitT<I, E>, NocontextT<I, E>, NontemporalT<I, E>, NotinbranchT<I, E>,
-    NovariantsT<I, E>, NumTasksT<I, E>, NumTeamsT<I, E>, NumThreadsT<I, E>,
-    OmpxAttributeT<I, E>, OmpxDynCgroupMemT<I, E>, OmpxBareT<I, E>,
-    OrderT<I, E>, OrderedT<I, E>, PartialT<I, E>, PriorityT<I, E>,
-    PrivateT<I, E>, ProcBindT<I, E>, ReadT<I, E>, ReductionT<I, E>,
-    RelaxedT<I, E>, ReleaseT<I, E>, ReverseOffloadT<I, E>, SafelenT<I, E>,
-    ScheduleT<I, E>, SeqCstT<I, E>, SeverityT<I, E>, SharedT<I, E>, SimdT<I, E>,
-    SimdlenT<I, E>, SizesT<I, E>, TaskReductionT<I, E>, ThreadLimitT<I, E>,
-    ThreadprivateT<I, E>, ThreadsT<I, E>, ToT<I, E>, UnifiedAddressT<I, E>,
-    UnifiedSharedMemoryT<I, E>, UniformT<I, E>, UnknownT<I, E>, UntiedT<I, E>,
-    UpdateT<I, E>, UseT<I, E>, UseDeviceAddrT<I, E>, UseDevicePtrT<I, E>,
-    UsesAllocatorsT<I, E>, WeakT<I, E>, WhenT<I, E>, WriteT<I, E>>;
-} // namespace clause
-
-template <typename Id, typename Expr>
-struct ClauseT {
-  llvm::omp::Clause id; // The numeric id of the clause
-  using UnionTrait = std::true_type;
-  clause::UnionOfAllClausesT<Id, Expr> u;
-};
-
-} // namespace tomp
-
-#endif // FORTRAN_LOWER_OPENMP_CLAUSET_H
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index a3aa3d4de3cdc9a..2d94c4bf8a03376 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -184,29 +184,99 @@ getBaseObject(const Object &object,
   return std::nullopt;
 }
 
+template <typename U, typename T>
+U enum_cast(T t) {
+  using BareT = llvm::remove_cvref_t<T>;
+  using BareU = llvm::remove_cvref_t<U>;
+  static_assert(std::is_enum_v<BareT> && std::is_enum_v<BareU>);
+
+  return U{static_cast<std::underlying_type_t<BareT>>(t)};
+}
+
 namespace clause {
 // Helper objects
-#ifdef EMPTY_CLASS
-#undef EMPTY_CLASS
-#endif
-#define EMPTY_CLASS(cls)                                                       \
-  cls make(const parser::OmpClause::cls &, semantics::SemanticsContext &) {    \
+#define MAKE_EMPTY_CLASS(cls, from_cls)                                        \
+  cls make(const parser::OmpClause::from_cls &,                                \
+           semantics::SemanticsContext &) {                                    \
+    static_assert(cls::EmptyTrait::value);                                     \
     return cls{};                                                              \
-  }                                                                            \
-  [[maybe_unused]] extern int xyzzy_semicolon_absorber
-
-#ifdef WRAPPER_CLASS
-#undef WRAPPER_CLASS
-#endif
-#define WRAPPER_CLASS(cls, content)                                            \
-  [[maybe_unused]] extern int xyzzy_semicolon_absorber
-#define GEN_FLANG_CLAUSE_PARSER_CLASSES
-#include "llvm/Frontend/OpenMP/OMP.inc"
-#undef EMPTY_CLASS
-#undef WRAPPER_CLASS
+  }
+
+MAKE_EMPTY_CLASS(AcqRel, AcqRel)
+MAKE_EMPTY_CLASS(Acquire, Acquire)
+MAKE_EMPTY_CLASS(CancellationConstructType, CancellationConstructType)
+MAKE_EMPTY_CLASS(Capture, Capture)
+MAKE_EMPTY_CLASS(Compare, Compare)
+MAKE_EMPTY_CLASS(Depobj, Depobj)
+MAKE_EMPTY_CLASS(DynamicAllocators, DynamicAllocators)
+MAKE_EMPTY_CLASS(Flush, Flush)
+MAKE_EMPTY_CLASS(Full, Full)
+MAKE_EMPTY_CLASS(Inbranch, Inbranch)
+MAKE_EMPTY_CLASS(MemoryOrder, MemoryOrder)
+MAKE_EMPTY_CLASS(Mergeable, Mergeable)
+MAKE_EMPTY_CLASS(Nogroup, Nogroup)
+// MAKE_EMPTY_CLASS(NoOpenmp, )         // missing-in-parser
+// MAKE_EMPTY_CLASS(NoOpenmpRoutines, ) // missing-in-parser
+// MAKE_EMPTY_CLASS(NoParallelism, )    // missing-in-parser
+MAKE_EMPTY_CLASS(Notinbranch, Notinbranch)
+MAKE_EMPTY_CLASS(Nowait, Nowait)
+MAKE_EMPTY_CLASS(OmpxAttribute, OmpxAttribute)
+MAKE_EMPTY_CLASS(OmpxBare, OmpxBare)
+MAKE_EMPTY_CLASS(Read, Read)
+MAKE_EMPTY_CLASS(Relaxed, Relaxed)
+MAKE_EMPTY_CLASS(Release, Release)
+MAKE_EMPTY_CLASS(ReverseOffload, ReverseOffload)
+MAKE_EMPTY_CLASS(SeqCst, SeqCst)
+MAKE_EMPTY_CLASS(Simd, Simd)
+MAKE_EMPTY_CLASS(Threadprivate, Threadprivate)
+MAKE_EMPTY_CLASS(Threads, Threads)
+MAKE_EMPTY_CLASS(UnifiedAddress, UnifiedAddress)
+MAKE_EMPTY_CLASS(UnifiedSharedMemory, UnifiedSharedMemory)
+MAKE_EMPTY_CLASS(Unknown, Unknown)
+MAKE_EMPTY_CLASS(Untied, Untied)
+MAKE_EMPTY_CLASS(Update, Update)
+MAKE_EMPTY_CLASS(Weak, Weak)
+MAKE_EMPTY_CLASS(Write, Write)
+
+#define MAKE_INCOMPLETE_CLASS(cls, from_cls)                                   \
+  cls make(const parser::OmpClause::from_cls &,                                \
+           semantics::SemanticsContext &) {                                    \
+    static_assert(cls::IncompleteTrait::value);                                \
+    return cls{};                                                              \
+  }
+
+MAKE_INCOMPLETE_CLASS(AdjustArgs, AdjustArgs)
+MAKE_INCOMPLETE_CLASS(AppendArgs, AppendArgs)
+MAKE_INCOMPLETE_CLASS(Match, Match)
+// MAKE_INCOMPLETE_CLASS(Otherwise, )   // missing-in-parser
+MAKE_INCOMPLETE_CLASS(When, When)
 
 DefinedOperator makeDefOp(const parser::DefinedOperator &inp,
                           semantics::SemanticsContext &semaCtx) {
+  ENUM_CONVERT( //
+      convert, parser::DefinedOperator::IntrinsicOperator,
+      DefinedOperator::IntrinsicOperator,
+      // clang-format off
+      M(Add,      Add)
+      M(AND,      AND)
+      M(Concat,   Concat)
+      M(Divide,   Divide)
+      M(EQ,       EQ)
+      M(EQV,      EQV)
+      M(GE,       GE)
+      M(GT,       GT)
+      M(NOT,      NOT)
+      M(LE,       LE)
+      M(LT,       LT)
+      M(Multiply, Multiply)
+      M(NE,       NE)
+      M(NEQV,     NEQV)
+      M(OR,       OR)
+      M(Power,    Power)
+      M(Subtract, Subtract)
+      // clang-format on
+  );
+
   return DefinedOperator{
       std::visit(common::visitors{
                      [&](const parser::DefinedOpName &s) {
@@ -214,7 +284,7 @@ DefinedOperator makeDefOp(const parser::DefinedOperator &inp,
                            makeObject(s.v, semaCtx)}};
                      },
                      [&](const parser::DefinedOperator::IntrinsicOperator &s) {
-                       return DefinedOperator{s};
+                       return DefinedOperator{convert(s)};
                      },
                  },
                  inp.u),
@@ -246,7 +316,26 @@ ReductionOperator makeRedOp(const parser::OmpReductionOperator &inp,
                     inp.u);
 }
 
-// Actual clauses. Each T (where OmpClause::T exists) has its "make".
+// --------------------------------------------------------------------
+// Actual clauses. Each T (where tomp::T exists in ClauseT) has its "make".
+
+// Absent: missing-in-parser
+// AcqRel: empty
+// Acquire: empty
+// AdjustArgs: incomplate
+
+Affinity make(const parser::OmpClause::Affinity &inp,
+              semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: affinity");
+}
+
+Align make(const parser::OmpClause::Align &inp,
+           semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: align");
+}
+
 Aligned make(const parser::OmpClause::Aligned &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpAlignedClause
@@ -254,8 +343,8 @@ Aligned make(const parser::OmpClause::Aligned &inp,
   auto &t1 = std::get<std::optional<parser::ScalarIntConstantExpr>>(inp.v.t);
 
   return Aligned{{
-      makeList(t0, semaCtx),
-      maybeApply(makeExprF(semaCtx), t1),
+      /*Alignment=*/maybeApply(makeExprF(semaCtx), t1),
+      /*List=*/makeList(t0, semaCtx),
   }};
 }
 
@@ -266,66 +355,121 @@ Allocate make(const parser::OmpClause::Allocate &inp,
   auto &t0 = std::get<std::optional<wrapped::AllocateModifier>>(inp.v.t);
   auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
 
-  auto convert = [&](auto &&s) -> Allocate::Modifier {
-    using Modifier = Allocate::Modifier;
-    using Allocator = Modifier::Allocator;
-    using Align = Modifier::Align;
-    using ComplexModifier = Modifier::ComplexModifier;
-
-    return Modifier{
-        std::visit(
-            common::visitors{
-                [&](const wrapped::AllocateModifier::Allocator &v) {
-                  return Modifier{Allocator{makeExpr(v.v, semaCtx)}};
-                },
-                [&](const wrapped::AllocateModifier::ComplexModifier &v) {
-                  auto &s0 =
-                      std::get<wrapped::AllocateModifier::Allocator>(v.t);
-                  auto &s1 = std::get<wrapped::AllocateModifier::Align>(v.t);
-                  return Modifier{ComplexModifier{{
-                      Allocator{makeExpr(s0.v, semaCtx)},
-                      Align{makeExpr(s1.v, semaCtx)},
-                  }}};
-                },
-                [&](const wrapped::AllocateModifier::Align &v) {
-                  return Modifier{Align{makeExpr(v.v, semaCtx)}};
-                },
-            },
-            s.u),
-    };
-  };
+  if (!t0) {
+    return Allocate{{/*AllocatorSimpleModifier=*/std::nullopt,
+                     /*AllocatorComplexModifier=*/std::nullopt,
+                     /*AlignModifier=*/std::nullopt,
+                     /*List=*/makeList(t1, semaCtx)}};
+  }
+
+  using Tuple = decltype(Allocate::t);
 
-  return Allocate{{maybeApply(convert, t0), makeList(t1, semaCtx)}};
+  return Allocate{std::visit(
+      common::visitors{
+          // simple-modifier
+          [&](const wrapped::AllocateModifier::Allocator &v) -> Tuple {
+            return {/*AllocatorSimpleModifier=*/makeExpr(v.v, semaCtx),
+                    /*AllocatorComplexModifier=*/std::nullopt,
+                    /*AlignModifier=*/std::nullopt,
+                    /*List=*/makeList(t1, semaCtx)};
+          },
+          // complex-modifier + align-modifier
+          [&](const wrapped::AllocateModifier::ComplexModifier &v) -> Tuple {
+            auto &s0 = std::get<wrapped::AllocateModifier::Allocator>(v.t);
+            auto &s1 = std::get<wrapped::AllocateModifier::Align>(v.t);
+            return {
+                /*AllocatorSimpleModifier=*/std::nullopt,
+                /*AllocatorComplexModifier=*/Allocator{makeExpr(s0.v, semaCtx)},
+                /*AlignModifier=*/Align{makeExpr(s1.v, semaCtx)},
+                /*List=*/makeList(t1, semaCtx)};
+          },
+          // align-modifier
+          [&](const wrapped::AllocateModifier::Align &v) -> Tuple {
+            return {/*AllocatorSimpleModifier=*/std::nullopt,
+                    /*AllocatorComplexModifier=*/std::nullopt,
+                    /*AlignModifier=*/Align{makeExpr(v.v, semaCtx)},
+                    /*List=*/makeList(t1, semaCtx)};
+          },
+      },
+      t0->u)};
 }
 
 Allocator make(const parser::OmpClause::Allocator &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return Allocator{makeExpr(inp.v, semaCtx)};
+  return Allocator{/*Allocator=*/makeExpr(inp.v, semaCtx)};
+}
+
+// AppendArgs: incomplete
+
+At make(const parser::OmpClause::At &inp,
+        semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: at");
 }
 
 AtomicDefaultMemOrder make(const parser::OmpClause::AtomicDefaultMemOrder &inp,
                            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpAtomicDefaultMemOrderClause
-  return AtomicDefaultMemOrder{inp.v.v};
+  ENUM_CONVERT( //
+      convert, common::OmpAtomicDefaultMemOrderType,
+      AtomicDefaultMemOrder::MemoryOrder,
+      // clang-format off
+      M(AcqRel,   AcqRel)
+      M(Relaxed,  Relaxed)
+      M(SeqCst,   SeqCst)
+      // clang-format on
+  );
+
+  return AtomicDefaultMemOrder{/*MemoryOrder=*/convert(inp.v.v)};
 }
 
+Bind make(const parser::OmpClause::Bind &inp,
+          semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: bind");
+}
+
+// CancellationConstructType: empty
+// Capture: empty
+
 Collapse make(const parser::OmpClause::Collapse &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
-  return Collapse{makeExpr(inp.v, semaCtx)};
+  return Collapse{/*N=*/makeExpr(inp.v, semaCtx)};
 }
 
+// Compare: empty
+// Contains: missing-in-parser
+
 Copyin make(const parser::OmpClause::Copyin &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Copyin{makeList(inp.v, semaCtx)};
+  return Copyin{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 Copyprivate make(const parser::OmpClause::Copyprivate &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Copyprivate{makeList(inp.v, semaCtx)};
+  return Copyprivate{/*List=*/makeList(inp.v, semaCtx)};
+}
+
+Default make(const parser::OmpClause::Default &inp,
+             semantics::SemanticsContext &semaCtx) {
+  // inp.v -> parser::OmpDefaultClause
+  using wrapped = parser::OmpDefaultClause;
+
+  ENUM_CONVERT( //
+      convert, wrapped::Type, Default::DataSharingAttribute,
+      // clang-format off
+      M(Firstprivate, Firstprivate)
+      M(None,         None)
+      M(Private,      Private)
+      M(Shared,       Shared)
+      // clang-format on
+  );
+
+  return Default{/*DataSharingAttribute=*/convert(inp.v.v)};
 }
 
 Defaultmap make(const parser::OmpClause::Defaultmap &inp,
@@ -333,51 +477,111 @@ Defaultmap make(const parser::OmpClause::Defaultmap &inp,
   // inp.v -> parser::OmpDefaultmapClause
   using wrapped = parser::OmpDefaultmapClause;
 
+  ENUM_CONVERT( //
+      convert1, wrapped::ImplicitBehavior, Defaultmap::ImplicitBehavior,
+      // clang-format off
+      M(Alloc,        Alloc)
+      M(To,           To)
+      M(From,         From)
+      M(Tofrom,       Tofrom)
+      M(Firstprivate, Firstprivate)
+      M(None,         None)
+      M(Default,      Default)
+      // M(, Present)  missing-in-parser
+      // clang-format on
+  );
+
+  ENUM_CONVERT( //
+      convert2, wrapped::VariableCategory, Defaultmap::VariableCategory,
+      // clang-format off
+      M(Scalar,       Scalar)
+      M(Aggregate,    Aggregate)
+      M(Pointer,      Pointer)
+      M(Allocatable,  Allocatable)
+      // clang-format on
+  );
+
   auto &t0 = std::get<wrapped::ImplicitBehavior>(inp.v.t);
   auto &t1 = std::get<std::optional<wrapped::VariableCategory>>(inp.v.t);
-  return Defaultmap{{t0, t1}};
-}
-
-Default make(const parser::OmpClause::Default &inp,
-             semantics::SemanticsContext &semaCtx) {
-  // inp.v -> parser::OmpDefaultClause
-  return Default{inp.v.v};
+  return Defaultmap{{/*ImplicitBehavior=*/convert1(t0),
+                     /*VariableCategory=*/maybeApply(convert2, t1)}};
 }
 
 Depend make(const parser::OmpClause::Depend &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDependClause
   using wrapped = parser::OmpDependClause;
-
-  return std::visit(
+  using Variant = decltype(Depend::u);
+  // Iteration is the equivalent of parser::OmpDependSinkVec
+  using Iteration = Doacross::Vector::value_type; // LoopIterationT
+
+  ENUM_CONVERT( //
+      convert1, parser::OmpDependenceType::Type, Depend::TaskDependenceType,
+      // clang-format off
+      M(In,     In)
+      M(Out,    Out)
+      M(Inout,  Inout)
+      // M(, Mutexinoutset)   // missing-in-parser
+      // M(, Inputset)        // missing-in-parser
+      // M(, Depobj)          // missing-in-parser
+      // clang-format on
+  );
+
+  return Depend{std::visit( //
       common::visitors{
-          [&](const wrapped::Source &s) { return Depend{Depend::Source{}}; },
-          [&](const wrapped::Sink &s) {
-            auto convert = [&](const parser::OmpDependSinkVec &v) {
+          // Doacross
+          [&](const wrapped::Source &s) -> Variant {
+            return Doacross{
+                {/*DependenceType=*/Doacross::DependenceType::Source,
+                 /*Vector=*/{}}};
+          },
+          // Doacross
+          [&](const wrapped::Sink &s) -> Variant {
+            using DependLength = parser::OmpDependSinkVecLength;
+            auto convert2 = [&](const parser::OmpDependSinkVec &v) {
               auto &t0 = std::get<parser::Name>(v.t);
-              auto &t1 =
-                  std::get<std::optional<parser::OmpDependSinkVecLength>>(v.t);
-              auto convert1 = [&](const parser::OmpDependSinkVecLength &u) {
+              auto &t1 = std::get<std::optional<DependLength>>(v.t);
+
+              auto convert3 = [&](const DependLength &u) {
                 auto &s0 = std::get<parser::DefinedOperator>(u.t);
                 auto &s1 = std::get<parser::ScalarIntConstantExpr>(u.t);
-                return Depend::Sink::Length{makeDefOp(s0, semaCtx),
-                                            makeExpr(s1, semaCtx)};
+                return Iteration::Length{
+                    {makeDefOp(s0, semaCtx), makeExpr(s1, semaCtx)}};
               };
-              return Depend::Sink::Vec{makeObject(t0, semaCtx),
-                                       maybeApply(convert1, t1)};
+              return Iteration{
+                  {makeObject(t0, semaCtx), maybeApply(convert3, t1)}};
             };
-            return Depend{Depend::Sink{makeList(s.v, convert)}};
+            return Doacross{{/*DependenceType=*/Doacross::DependenceType::Sink,
+                             /*Vector=*/makeList(s.v, convert2)}};
           },
-          [&](const wrapped::InOut &s) {
+          // Depend::WithLocators
+          [&](const wrapped::InOut &s) -> Variant {
             auto &t0 = std::get<parser::OmpDependenceType>(s.t);
             auto &t1 = std::get<std::list<parser::Designator>>(s.t);
-            auto convert = [&](const parser::Designator &t) {
+            auto convert4 = [&](const parser::Designator &t) {
               return makeObject(t, semaCtx);
             };
-            return Depend{Depend::InOut{{t0.v, makeList(t1, convert)}}};
+            return Depend::WithLocators{
+                {/*TaskDependenceType=*/convert1(t0.v),
+                 /*Iterator=*/std::nullopt,
+                 /*LocatorList=*/makeList(t1, convert4)}};
           },
       },
-      inp.v.u);
+      inp.v.u)};
+}
+
+// Depobj: empty
+
+Destroy make(const parser::OmpClause::Destroy &inp,
+             semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: destroy");
+}
+
+Detach make(const parser::OmpClause::Detach &inp,
+            semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: detach");
 }
 
 Device make(const parser::OmpClause::Device &inp,
@@ -385,99 +589,186 @@ Device make(const parser::OmpClause::Device &inp,
   // inp.v -> parser::OmpDeviceClause
   using wrapped = parser::OmpDeviceClause;
 
+  ENUM_CONVERT( //
+      convert, parser::OmpDeviceClause::DeviceModifier, Device::DeviceModifier,
+      // clang-format off
+      M(Ancestor,   Ancestor)
+      M(Device_Num, DeviceNum)
+      // clang-format on
+  );
   auto &t0 = std::get<std::optional<wrapped::DeviceModifier>>(inp.v.t);
   auto &t1 = std::get<parser::ScalarIntExpr>(inp.v.t);
-  return Device{{t0, makeExpr(t1, semaCtx)}};
+  return Device{{/*DeviceModifier=*/maybeApply(convert, t0),
+                 /*DeviceDescription=*/makeExpr(t1, semaCtx)}};
 }
 
 DeviceType make(const parser::OmpClause::DeviceType &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDeviceTypeClause
-  return DeviceType{inp.v.v};
+  using wrapped = parser::OmpDeviceTypeClause;
+
+  ENUM_CONVERT( //
+      convert, wrapped::Type, DeviceType::DeviceTypeDescription,
+      // clang-format off
+      M(Any,    Any)
+      M(Host,   Host)
+      M(Nohost, Nohost)
+      // clang-format om
+  );
+  return DeviceType{/*DeviceTypeDescription=*/convert(inp.v.v)};
 }
 
 DistSchedule make(const parser::OmpClause::DistSchedule &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::optional<parser::ScalarIntExpr>
-  return DistSchedule{maybeApply(makeExprF(semaCtx), inp.v)};
+  return DistSchedule{{/*Kind=*/DistSchedule::Kind::Static,
+                       /*ChunkSize=*/maybeApply(makeExprF(semaCtx), inp.v)}};
+}
+
+Doacross make(const parser::OmpClause::Doacross &inp,
+              semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: doacross");
 }
 
+// DynamicAllocators: empty
+
 Enter make(const parser::OmpClause::Enter &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Enter{makeList(inp.v, semaCtx)};
+  return Enter{makeList(/*List=*/inp.v, semaCtx)};
+}
+
+Exclusive make(const parser::OmpClause::Exclusive &inp,
+               semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: exclusive");
+}
+
+Fail make(const parser::OmpClause::Fail &inp,
+          semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: fail");
 }
 
 Filter make(const parser::OmpClause::Filter &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return Filter{makeExpr(inp.v, semaCtx)};
+  return Filter{/*ThreadNum=*/makeExpr(inp.v, semaCtx)};
 }
 
 Final make(const parser::OmpClause::Final &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
-  return Final{makeExpr(inp.v, semaCtx)};
+  return Final{/*Finalize=*/makeExpr(inp.v, semaCtx)};
 }
 
 Firstprivate make(const parser::OmpClause::Firstprivate &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Firstprivate{makeList(inp.v, semaCtx)};
+  return Firstprivate{/*List=*/makeList(inp.v, semaCtx)};
 }
 
+// Flush: empty
+
 From make(const parser::OmpClause::From &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return From{makeList(inp.v, semaCtx)};
+  return From{{/*Expectation=*/std::nullopt, /*Mapper=*/std::nullopt,
+               /*Iterator=*/std::nullopt,
+               /*LocatorList=*/makeList(inp.v, semaCtx)}};
 }
 
+// Full: empty
+
 Grainsize make(const parser::OmpClause::Grainsize &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return Grainsize{makeExpr(inp.v, semaCtx)};
+  return Grainsize{/*GrainSize=*/makeExpr(inp.v, semaCtx)};
 }
 
 HasDeviceAddr make(const parser::OmpClause::HasDeviceAddr &inp,
                    semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return HasDeviceAddr{makeList(inp.v, semaCtx)};
+  return HasDeviceAddr{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 Hint make(const parser::OmpClause::Hint &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ConstantExpr
-  return Hint{makeExpr(inp.v, semaCtx)};
+  return Hint{/*HintExpr=*/makeExpr(inp.v, semaCtx)};
 }
 
+// Holds: missing-in-parser
+
 If make(const parser::OmpClause::If &inp,
         semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpIfClause
   using wrapped = parser::OmpIfClause;
 
+  ENUM_CONVERT( //
+      convert, wrapped::DirectiveNameModifier, llvm::omp::Directive,
+      // clang-format off
+      M(Parallel,         OMPD_parallel)
+      M(Simd,             OMPD_simd)
+      M(Target,           OMPD_target)
+      M(TargetData,       OMPD_target_data)
+      M(TargetEnterData,  OMPD_target_enter_data)
+      M(TargetExitData,   OMPD_target_exit_data)
+      M(TargetUpdate,     OMPD_target_update)
+      M(Task,             OMPD_task)
+      M(Taskloop,         OMPD_taskloop)
+      M(Teams,            OMPD_teams)
+      // clang-format on
+  );
   auto &t0 = std::get<std::optional<wrapped::DirectiveNameModifier>>(inp.v.t);
   auto &t1 = std::get<parser::ScalarLogicalExpr>(inp.v.t);
-  return If{{t0, makeExpr(t1, semaCtx)}};
+  return If{{/*DirectiveNameModifier=*/maybeApply(convert, t0),
+             /*IfExpression=*/makeExpr(t1, semaCtx)}};
+}
+
+// Inbranch: empty
+
+Inclusive make(const parser::OmpClause::Inclusive &inp,
+               semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: inclusive");
+}
+
+Indirect make(const parser::OmpClause::Indirect &inp,
+              semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: indirect");
 }
 
+Init make(const parser::OmpClause::Init &inp,
+          semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: init");
+}
+
+// Initializer: missing-in-parser
+
 InReduction make(const parser::OmpClause::InReduction &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpInReductionClause
   auto &t0 = std::get<parser::OmpReductionOperator>(inp.v.t);
   auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
-  return InReduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
+  return InReduction{{/*ReductionIdentifier=*/makeRedOp(t0, semaCtx),
+                      /*List=*/makeList(t1, semaCtx)}};
 }
 
 IsDevicePtr make(const parser::OmpClause::IsDevicePtr &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return IsDevicePtr{makeList(inp.v, semaCtx)};
+  return IsDevicePtr{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 Lastprivate make(const parser::OmpClause::Lastprivate &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Lastprivate{makeList(inp.v, semaCtx)};
+  return Lastprivate{{/*LastprivateModifier=*/std::nullopt,
+                      /*List=*/makeList(inp.v, semaCtx)}};
 }
 
 Linear make(const parser::OmpClause::Linear &inp,
@@ -485,140 +776,243 @@ Linear make(const parser::OmpClause::Linear &inp,
   // inp.v -> parser::OmpLinearClause
   using wrapped = parser::OmpLinearClause;
 
-  return std::visit(
+  ENUM_CONVERT( //
+      convert, parser::OmpLinearModifier::Type, Linear::LinearModifier,
+      // clang-format off
+      M(Ref,  Ref)
+      M(Val,  Val)
+      M(Uval, Uval)
+      // clang-format on
+  );
+
+  using Tuple = decltype(Linear::t);
+
+  return Linear{std::visit(
       common::visitors{
-          [&](const wrapped::WithModifier &s) {
-            return Linear{{Linear::Modifier{s.modifier.v},
-                           makeList(s.names, makeObjectF(semaCtx)),
-                           maybeApply(makeExprF(semaCtx), s.step)}};
+          [&](const wrapped::WithModifier &s) -> Tuple {
+            return {
+                /*StepSimpleModifier=*/std::nullopt,
+                /*StepComplexModifier=*/maybeApply(makeExprF(semaCtx), s.step),
+                /*LinearModifier=*/convert(s.modifier.v),
+                /*List=*/makeList(s.names, makeObjectF(semaCtx))};
           },
-          [&](const wrapped::WithoutModifier &s) {
-            return Linear{{std::nullopt,
-                           makeList(s.names, makeObjectF(semaCtx)),
-                           maybeApply(makeExprF(semaCtx), s.step)}};
+          [&](const wrapped::WithoutModifier &s) -> Tuple {
+            return {
+                /*StepSimpleModifier=*/maybeApply(makeExprF(semaCtx), s.step),
+                /*StepComplexModifier=*/std::nullopt,
+                /*LinearModifier=*/std::nullopt,
+                /*List=*/makeList(s.names, makeObjectF(semaCtx))};
           },
       },
-      inp.v.u);
+      inp.v.u)};
 }
 
 Link make(const parser::OmpClause::Link &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Link{makeList(inp.v, semaCtx)};
+  return Link{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 Map make(const parser::OmpClause::Map &inp,
          semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpMapClause
+
+  ENUM_CONVERT( //
+      convert1, parser::OmpMapType::Type, Map::MapType,
+      // clang-format off
+      M(To,       To)
+      M(From,     From)
+      M(Tofrom,   Tofrom)
+      M(Alloc,    Alloc)
+      M(Release,  Release)
+      M(Delete,   Delete)
+      // clang-format on
+  );
+
+  // No convert2: MapTypeModifier is not an enum in parser.
+
   auto &t0 = std::get<std::optional<parser::OmpMapType>>(inp.v.t);
   auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
-  auto convert = [](const parser::OmpMapType &s) {
-    auto &s0 = std::get<std::optional<parser::OmpMapType::Always>>(s.t);
-    auto &s1 = std::get<parser::OmpMapType::Type>(s.t);
-    auto convertT = [](parser::OmpMapType::Always) {
-      return Map::MapType::Always{};
-    };
-    return Map::MapType{{maybeApply(convertT, s0), s1}};
+
+  if (!t0) {
+    return Map{{/*MapType=*/std::nullopt, /*MapTypeModifier=*/std::nullopt,
+                /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
+                /*LocatorList=*/makeList(t1, semaCtx)}};
+  }
+
+  auto &s0 = std::get<std::optional<parser::OmpMapType::Always>>(t0->t);
+  auto &s1 = std::get<parser::OmpMapType::Type>(t0->t);
+
+  auto convert3 = [](parser::OmpMapType::Always) {
+    return Map::MapTypeModifier::Always;
   };
-  return Map{{maybeApply(convert, t0), makeList(t1, semaCtx)}};
+
+  return Map{{/*MapType=*/convert1(s1),
+              /*MapTypeModifier=*/maybeApply(convert3, s0),
+              /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
+              /*LocatorList=*/makeList(t1, semaCtx)}};
+}
+
+// Match: incomplete
+// MemoryOrder: empty
+// Mergeable: empty
+
+Message make(const parser::OmpClause::Message &inp,
+             semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: message");
 }
 
 Nocontext make(const parser::OmpClause::Nocontext &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
-  return Nocontext{makeExpr(inp.v, semaCtx)};
+  return Nocontext{/*DoNotUpdateContext=*/makeExpr(inp.v, semaCtx)};
 }
 
+// Nogroup: empty
+
 Nontemporal make(const parser::OmpClause::Nontemporal &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::Name>
-  return Nontemporal{makeList(inp.v, makeObjectF(semaCtx))};
+  return Nontemporal{/*List=*/makeList(inp.v, makeObjectF(semaCtx))};
 }
 
+// NoOpenmp: missing-in-parser
+// NoOpenmpRoutines: missing-in-parser
+// NoParallelism: missing-in-parser
+// Notinbranch: empty
+
 Novariants make(const parser::OmpClause::Novariants &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
-  return Novariants{makeExpr(inp.v, semaCtx)};
+  return Novariants{/*DoNotUseVariant=*/makeExpr(inp.v, semaCtx)};
 }
 
+// Nowait: empty
+
 NumTasks make(const parser::OmpClause::NumTasks &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return NumTasks{makeExpr(inp.v, semaCtx)};
+  return NumTasks{{/*Prescriptiveness=*/std::nullopt,
+                   /*NumTasks=*/makeExpr(inp.v, semaCtx)}};
 }
 
 NumTeams make(const parser::OmpClause::NumTeams &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return NumTeams{makeExpr(inp.v, semaCtx)};
+  return NumTeams{{/*LowerBound=*/std::nullopt,
+                   /*UpperBound=*/makeExpr(inp.v, semaCtx)}};
 }
 
 NumThreads make(const parser::OmpClause::NumThreads &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return NumThreads{makeExpr(inp.v, semaCtx)};
+  return NumThreads{/*Nthreads=*/makeExpr(inp.v, semaCtx)};
 }
 
+// OmpxAttribute: empty
+// OmpxBare: empty
+
 OmpxDynCgroupMem make(const parser::OmpClause::OmpxDynCgroupMem &inp,
                       semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return OmpxDynCgroupMem{makeExpr(inp.v, semaCtx)};
 }
 
-Ordered make(const parser::OmpClause::Ordered &inp,
-             semantics::SemanticsContext &semaCtx) {
-  // inp.v -> std::optional<parser::ScalarIntConstantExpr>
-  return Ordered{maybeApply(makeExprF(semaCtx), inp.v)};
-}
-
 Order make(const parser::OmpClause::Order &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpOrderClause
   using wrapped = parser::OmpOrderClause;
+
+  ENUM_CONVERT( //
+      convert1, parser::OmpOrderModifier::Kind, Order::OrderModifier,
+      // clang-format off
+      M(Reproducible,   Reproducible)
+      M(Unconstrained,  Unconstrained)
+      // clang-format on
+  );
+
+  ENUM_CONVERT( //
+      convert2, wrapped::Type, Order::Ordering,
+      // clang-format off
+      M(Concurrent, Concurrent)
+      // clang-format on
+  );
+
   auto &t0 = std::get<std::optional<parser::OmpOrderModifier>>(inp.v.t);
   auto &t1 = std::get<wrapped::Type>(inp.v.t);
-  auto convert = [](const parser::OmpOrderModifier &s) -> Order::Kind {
-    return std::get<parser::OmpOrderModifier::Kind>(s.u);
+
+  auto convert3 = [&](const parser::OmpOrderModifier &s) {
+    return std::visit(
+        [&](parser::OmpOrderModifier::Kind k) { return convert1(k); }, s.u);
   };
-  return Order{{maybeApply(convert, t0), t1}};
+  return Order{
+      {/*OrderModifier=*/maybeApply(convert3, t0), /*Ordering=*/convert2(t1)}};
+}
+
+Ordered make(const parser::OmpClause::Ordered &inp,
+             semantics::SemanticsContext &semaCtx) {
+  // inp.v -> std::optional<parser::ScalarIntConstantExpr>
+  return Ordered{/*N=*/maybeApply(makeExprF(semaCtx), inp.v)};
 }
 
+// Otherwise: incomplete, missing-in-parser
+
 Partial make(const parser::OmpClause::Partial &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::optional<parser::ScalarIntConstantExpr>
-  return Partial{maybeApply(makeExprF(semaCtx), inp.v)};
+  return Partial{/*UnrollFactor=*/maybeApply(makeExprF(semaCtx), inp.v)};
 }
 
 Priority make(const parser::OmpClause::Priority &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return Priority{makeExpr(inp.v, semaCtx)};
+  return Priority{/*PriorityValue=*/makeExpr(inp.v, semaCtx)};
 }
 
 Private make(const parser::OmpClause::Private &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Private{makeList(inp.v, semaCtx)};
+  return Private{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 ProcBind make(const parser::OmpClause::ProcBind &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpProcBindClause
-  return ProcBind{inp.v.v};
+  using wrapped = parser::OmpProcBindClause;
+
+  ENUM_CONVERT( //
+      convert, wrapped::Type, ProcBind::AffinityPolicy,
+      // clang-format off
+      M(Close,    Close)
+      M(Master,   Master)
+      M(Spread,   Spread)
+      M(Primary,  Primary)
+      // clang-format on
+  );
+  return ProcBind{/*AffinityPolicy=*/convert(inp.v.v)};
 }
 
+// Read: empty
+
 Reduction make(const parser::OmpClause::Reduction &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpReductionClause
   auto &t0 = std::get<parser::OmpReductionOperator>(inp.v.t);
   auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
-  return Reduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
+  return Reduction{{/*ReductionIdentifier=*/makeRedOp(t0, semaCtx),
+                    /*ReductionModifier=*/std::nullopt,
+                    /*List=*/makeList(t1, semaCtx)}};
 }
 
+// Relaxed: empty
+// Release: empty
+// ReverseOffload: empty
+
 Safelen make(const parser::OmpClause::Safelen &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
-  return Safelen{makeExpr(inp.v, semaCtx)};
+  return Safelen{/*Length=*/makeExpr(inp.v, semaCtx)};
 }
 
 Schedule make(const parser::OmpClause::Schedule &inp,
@@ -626,41 +1020,97 @@ Schedule make(const parser::OmpClause::Schedule &inp,
   // inp.v -> parser::OmpScheduleClause
   using wrapped = parser::OmpScheduleClause;
 
+  ENUM_CONVERT( //
+      convert1, wrapped::ScheduleType, Schedule::Kind,
+      // clang-format off
+      M(Static,   Static)
+      M(Dynamic,  Dynamic)
+      M(Guided,   Guided)
+      M(Auto,     Auto)
+      M(Runtime,  Runtime)
+      // clang-format on
+  );
+
+  ENUM_CONVERT( //
+      convert2, parser::OmpScheduleModifierType::ModType,
+      Schedule::OrderingModifier,
+      // clang-format off
+      M(Monotonic,    Monotonic)
+      M(Nonmonotonic, Nonmonotonic)
+      // clang-format on
+  );
+
+  ENUM_CONVERT( //
+      convert3, parser::OmpScheduleModifierType::ModType,
+      Schedule::ChunkModifier,
+      // clang-format off
+      M(Simd, Simd)
+      // clang-format on
+  );
+
   auto &t0 = std::get<std::optional<parser::OmpScheduleModifier>>(inp.v.t);
   auto &t1 = std::get<wrapped::ScheduleType>(inp.v.t);
   auto &t2 = std::get<std::optional<parser::ScalarIntExpr>>(inp.v.t);
 
-  auto convert = [](auto &&s) -> Schedule::ScheduleModifier {
-    auto &s0 = std::get<parser::OmpScheduleModifier::Modifier1>(s.t);
-    auto &s1 =
-        std::get<std::optional<parser::OmpScheduleModifier::Modifier2>>(s.t);
+  if (!t0) {
+    return Schedule{{/*Kind=*/convert1(t1), /*OrderingModifier=*/std::nullopt,
+                     /*ChunkModifier=*/std::nullopt,
+                     /*ChunkSize=*/maybeApply(makeExprF(semaCtx), t2)}};
+  }
 
-    auto convert1 = [](auto &&v) { // Modifier1 or Modifier2
-      return v.v.v;
-    };
-    return Schedule::ScheduleModifier{{s0.v.v, maybeApply(convert1, s1)}};
-  };
+  // The members of parser::OmpScheduleModifier correspond to OrderingModifier,
+  // and ChunkModifier, but they can appear in any order.
+  auto &m1 = std::get<parser::OmpScheduleModifier::Modifier1>(t0->t);
+  auto &m2 =
+      std::get<std::optional<parser::OmpScheduleModifier::Modifier2>>(t0->t);
+
+  std::optional<Schedule::OrderingModifier> omod;
+  std::optional<Schedule::ChunkModifier> cmod;
+
+  if (m1.v.v == parser::OmpScheduleModifierType::ModType::Simd) {
+    // m1 is chunk-modifier
+    cmod = convert3(m1.v.v);
+    if (m2)
+      omod = convert2(m2->v.v);
+  } else {
+    // m1 is ordering-modifier
+    omod = convert2(m1.v.v);
+    if (m2)
+      cmod = convert3(m2->v.v);
+  }
 
-  return Schedule{
-      {maybeApply(convert, t0), t1, maybeApply(makeExprF(semaCtx), t2)}};
+  return Schedule{{/*Kind=*/convert1(t1),
+                   /*OrderingModifier=*/omod,
+                   /*ChunkModifier=*/cmod,
+                   /*ChunkSize=*/maybeApply(makeExprF(semaCtx), t2)}};
+}
+
+// SeqCst: empty
+
+Severity make(const parser::OmpClause::Severity &inp,
+              semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: severity");
 }
 
 Shared make(const parser::OmpClause::Shared &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return Shared{makeList(inp.v, semaCtx)};
+  return Shared{/*List=*/makeList(inp.v, semaCtx)};
 }
 
+// Simd: empty
+
 Simdlen make(const parser::OmpClause::Simdlen &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
-  return Simdlen{makeExpr(inp.v, semaCtx)};
+  return Simdlen{/*Length=*/makeExpr(inp.v, semaCtx)};
 }
 
 Sizes make(const parser::OmpClause::Sizes &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::ScalarIntExpr>
-  return Sizes{makeList(inp.v, makeExprF(semaCtx))};
+  return Sizes{/*SizeList=*/makeList(inp.v, makeExprF(semaCtx))};
 }
 
 TaskReduction make(const parser::OmpClause::TaskReduction &inp,
@@ -668,38 +1118,67 @@ TaskReduction make(const parser::OmpClause::TaskReduction &inp,
   // inp.v -> parser::OmpReductionClause
   auto &t0 = std::get<parser::OmpReductionOperator>(inp.v.t);
   auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
-  return TaskReduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
+  return TaskReduction{{/*ReductionIdentifier=*/makeRedOp(t0, semaCtx),
+                        /*List=*/makeList(t1, semaCtx)}};
 }
 
 ThreadLimit make(const parser::OmpClause::ThreadLimit &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
-  return ThreadLimit{makeExpr(inp.v, semaCtx)};
+  return ThreadLimit{/*Threadlim=*/makeExpr(inp.v, semaCtx)};
 }
 
+// Threadprivate: empty
+// Threads: empty
+
 To make(const parser::OmpClause::To &inp,
         semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return To{makeList(inp.v, semaCtx)};
+  return To{{/*Expectation=*/std::nullopt, /*Mapper=*/std::nullopt,
+             /*Iterator=*/std::nullopt,
+             /*LocatorList=*/makeList(inp.v, semaCtx)}};
 }
 
+// UnifiedAddress: empty
+// UnifiedSharedMemory: empty
+
 Uniform make(const parser::OmpClause::Uniform &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::Name>
-  return Uniform{makeList(inp.v, makeObjectF(semaCtx))};
+  return Uniform{/*ParameterList=*/makeList(inp.v, makeObjectF(semaCtx))};
+}
+
+// Unknown: empty
+// Untied: empty
+// Update: empty
+
+Use make(const parser::OmpClause::Use &inp,
+         semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: use");
 }
 
 UseDeviceAddr make(const parser::OmpClause::UseDeviceAddr &inp,
                    semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return UseDeviceAddr{makeList(inp.v, semaCtx)};
+  return UseDeviceAddr{/*List=*/makeList(inp.v, semaCtx)};
 }
 
 UseDevicePtr make(const parser::OmpClause::UseDevicePtr &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
-  return UseDevicePtr{makeList(inp.v, semaCtx)};
+  return UseDevicePtr{/*List=*/makeList(inp.v, semaCtx)};
 }
+
+UsesAllocators make(const parser::OmpClause::UsesAllocators &inp,
+                    semantics::SemanticsContext &semaCtx) {
+  // inp -> empty
+  llvm_unreachable("Empty: uses_allocators");
+}
+
+// Weak: empty
+// When: incomplete
+// Write: empty
 } // namespace clause
 
 Clause makeClause(const Fortran::parser::OmpClause &cls,
@@ -713,7 +1192,7 @@ Clause makeClause(const Fortran::parser::OmpClause &cls,
 }
 
 List<Clause> makeList(const parser::OmpClauseList &clauses,
-                           semantics::SemanticsContext &semaCtx) {
+                      semantics::SemanticsContext &semaCtx) {
   return makeList(clauses.v, [&](const parser::OmpClause &s) {
     return makeClause(s, semaCtx);
   });
diff --git a/flang/lib/Lower/OpenMP/Clauses.h b/flang/lib/Lower/OpenMP/Clauses.h
index c167e34637d5008..0a0dbe5587dadc0 100644
--- a/flang/lib/Lower/OpenMP/Clauses.h
+++ b/flang/lib/Lower/OpenMP/Clauses.h
@@ -8,8 +8,6 @@
 #ifndef FORTRAN_LOWER_OPENMP_CLAUSES_H
 #define FORTRAN_LOWER_OPENMP_CLAUSES_H
 
-#include "ClauseT.h"
-
 #include "flang/Evaluate/expression.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/expression.h"
@@ -17,6 +15,7 @@
 #include "flang/Semantics/symbol.h"
 
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/Frontend/OpenMP/ClauseT.h"
 
 #include <optional>
 #include <type_traits>
@@ -28,19 +27,19 @@ using SomeType = evaluate::SomeType;
 using SomeExpr = semantics::SomeExpr;
 using MaybeExpr = semantics::MaybeExpr;
 
-using SymIdent = semantics::Symbol *;
-using SymReference = SomeExpr;
+using TypeTy = SomeType;
+using IdentTy = semantics::Symbol *;
+using ExprTy = SomeExpr;
 
 template <typename T>
 using List = tomp::ListT<T>;
 } // namespace Fortran::lower::omp
 
-namespace tomp {
+namespace tomp::type {
 template <>
-struct ObjectT<Fortran::lower::omp::SymIdent,
-               Fortran::lower::omp::SymReference> {
-  using IdType = Fortran::lower::omp::SymIdent;
-  using ExprType = Fortran::lower::omp::SymReference;
+struct ObjectT<Fortran::lower::omp::IdentTy, Fortran::lower::omp::ExprTy> {
+  using IdType = Fortran::lower::omp::IdentTy;
+  using ExprType = Fortran::lower::omp::ExprTy;
 
   const IdType &id() const { return symbol; }
   const std::optional<ExprType> &ref() const { return designator; }
@@ -48,12 +47,12 @@ struct ObjectT<Fortran::lower::omp::SymIdent,
   IdType symbol;
   std::optional<ExprType> designator;
 };
-} // namespace tomp
+} // namespace tomp::type
 
 namespace Fortran::lower::omp {
 
-using Object = tomp::ObjectT<SymIdent, SymReference>;
-using ObjectList = tomp::ObjectListT<SymIdent, SymReference>;
+using Object = tomp::ObjectT<IdentTy, ExprTy>;
+using ObjectList = tomp::ObjectListT<IdentTy, ExprTy>;
 
 Object makeObject(const parser::OmpObject &object,
                   semantics::SemanticsContext &semaCtx);
@@ -94,11 +93,14 @@ inline ObjectList makeList(const parser::OmpObjectList &objects,
   return makeList(objects.v, makeObjectF(semaCtx));
 }
 
-template <typename F, typename T, typename U = std::invoke_result_t<F, T>>
-std::optional<U> maybeApply(F &&func, const std::optional<T> &inp) {
-  if (!inp)
+template <typename FuncTy, //
+          typename ArgTy,  //
+          typename ResultTy = std::invoke_result_t<FuncTy, ArgTy>>
+std::optional<ResultTy> maybeApply(FuncTy &&func,
+                                   const std::optional<ArgTy> &arg) {
+  if (!arg)
     return std::nullopt;
-  return std::move(func(*inp));
+  return std::move(func(*arg));
 }
 
 std::optional<Object>
@@ -106,93 +108,130 @@ getBaseObject(const Object &object,
               Fortran::semantics::SemanticsContext &semaCtx);
 
 namespace clause {
-using DefinedOperator = tomp::clause::DefinedOperatorT<SymIdent, SymReference>;
-using ProcedureDesignator =
-    tomp::clause::ProcedureDesignatorT<SymIdent, SymReference>;
-using ReductionOperator =
-    tomp::clause::ReductionOperatorT<SymIdent, SymReference>;
-
-#ifdef EMPTY_CLASS
-#undef EMPTY_CLASS
-#endif
-#define EMPTY_CLASS(cls)                                                       \
-  using cls = tomp::clause::cls##T<SymIdent, SymReference>
-
-#ifdef WRAPPER_CLASS
-#undef WRAPPER_CLASS
-#endif
-#define WRAPPER_CLASS(cls, content)                                            \
-  [[maybe_unused]] extern int xyzzy_semicolon_absorber
-#define GEN_FLANG_CLAUSE_PARSER_CLASSES
-#include "llvm/Frontend/OpenMP/OMP.inc"
-#undef EMPTY_CLASS
-#undef WRAPPER_CLASS
-
-using Aligned = tomp::clause::AlignedT<SymIdent, SymReference>;
-using Allocate = tomp::clause::AllocateT<SymIdent, SymReference>;
-using Allocator = tomp::clause::AllocatorT<SymIdent, SymReference>;
+using DefinedOperator = tomp::type::DefinedOperatorT<IdentTy, ExprTy>;
+using ProcedureDesignator = tomp::type::ProcedureDesignatorT<IdentTy, ExprTy>;
+using ReductionOperator = tomp::type::ReductionIdentifierT<IdentTy, ExprTy>;
+
+using AcqRel = tomp::clause::AcqRelT<TypeTy, IdentTy, ExprTy>;
+using Acquire = tomp::clause::AcquireT<TypeTy, IdentTy, ExprTy>;
+using AdjustArgs = tomp::clause::AdjustArgsT<TypeTy, IdentTy, ExprTy>;
+using Affinity = tomp::clause::AffinityT<TypeTy, IdentTy, ExprTy>;
+using Aligned = tomp::clause::AlignedT<TypeTy, IdentTy, ExprTy>;
+using Align = tomp::clause::AlignT<TypeTy, IdentTy, ExprTy>;
+using Allocate = tomp::clause::AllocateT<TypeTy, IdentTy, ExprTy>;
+using Allocator = tomp::clause::AllocatorT<TypeTy, IdentTy, ExprTy>;
+using AppendArgs = tomp::clause::AppendArgsT<TypeTy, IdentTy, ExprTy>;
 using AtomicDefaultMemOrder =
-    tomp::clause::AtomicDefaultMemOrderT<SymIdent, SymReference>;
-using Collapse = tomp::clause::CollapseT<SymIdent, SymReference>;
-using Copyin = tomp::clause::CopyinT<SymIdent, SymReference>;
-using Copyprivate = tomp::clause::CopyprivateT<SymIdent, SymReference>;
-using Defaultmap = tomp::clause::DefaultmapT<SymIdent, SymReference>;
-using Default = tomp::clause::DefaultT<SymIdent, SymReference>;
-using Depend = tomp::clause::DependT<SymIdent, SymReference>;
-using Device = tomp::clause::DeviceT<SymIdent, SymReference>;
-using DeviceType = tomp::clause::DeviceTypeT<SymIdent, SymReference>;
-using DistSchedule = tomp::clause::DistScheduleT<SymIdent, SymReference>;
-using Enter = tomp::clause::EnterT<SymIdent, SymReference>;
-using Filter = tomp::clause::FilterT<SymIdent, SymReference>;
-using Final = tomp::clause::FinalT<SymIdent, SymReference>;
-using Firstprivate = tomp::clause::FirstprivateT<SymIdent, SymReference>;
-using From = tomp::clause::FromT<SymIdent, SymReference>;
-using Grainsize = tomp::clause::GrainsizeT<SymIdent, SymReference>;
-using HasDeviceAddr = tomp::clause::HasDeviceAddrT<SymIdent, SymReference>;
-using Hint = tomp::clause::HintT<SymIdent, SymReference>;
-using If = tomp::clause::IfT<SymIdent, SymReference>;
-using InReduction = tomp::clause::InReductionT<SymIdent, SymReference>;
-using IsDevicePtr = tomp::clause::IsDevicePtrT<SymIdent, SymReference>;
-using Lastprivate = tomp::clause::LastprivateT<SymIdent, SymReference>;
-using Linear = tomp::clause::LinearT<SymIdent, SymReference>;
-using Link = tomp::clause::LinkT<SymIdent, SymReference>;
-using Map = tomp::clause::MapT<SymIdent, SymReference>;
-using Nocontext = tomp::clause::NocontextT<SymIdent, SymReference>;
-using Nontemporal = tomp::clause::NontemporalT<SymIdent, SymReference>;
-using Novariants = tomp::clause::NovariantsT<SymIdent, SymReference>;
-using NumTasks = tomp::clause::NumTasksT<SymIdent, SymReference>;
-using NumTeams = tomp::clause::NumTeamsT<SymIdent, SymReference>;
-using NumThreads = tomp::clause::NumThreadsT<SymIdent, SymReference>;
+    tomp::clause::AtomicDefaultMemOrderT<TypeTy, IdentTy, ExprTy>;
+using At = tomp::clause::AtT<TypeTy, IdentTy, ExprTy>;
+using Bind = tomp::clause::BindT<TypeTy, IdentTy, ExprTy>;
+using CancellationConstructType =
+    tomp::clause::CancellationConstructTypeT<TypeTy, IdentTy, ExprTy>;
+using Capture = tomp::clause::CaptureT<TypeTy, IdentTy, ExprTy>;
+using Collapse = tomp::clause::CollapseT<TypeTy, IdentTy, ExprTy>;
+using Compare = tomp::clause::CompareT<TypeTy, IdentTy, ExprTy>;
+using Copyin = tomp::clause::CopyinT<TypeTy, IdentTy, ExprTy>;
+using Copyprivate = tomp::clause::CopyprivateT<TypeTy, IdentTy, ExprTy>;
+using Defaultmap = tomp::clause::DefaultmapT<TypeTy, IdentTy, ExprTy>;
+using Default = tomp::clause::DefaultT<TypeTy, IdentTy, ExprTy>;
+using Depend = tomp::clause::DependT<TypeTy, IdentTy, ExprTy>;
+using Depobj = tomp::clause::DepobjT<TypeTy, IdentTy, ExprTy>;
+using Destroy = tomp::clause::DestroyT<TypeTy, IdentTy, ExprTy>;
+using Detach = tomp::clause::DetachT<TypeTy, IdentTy, ExprTy>;
+using Device = tomp::clause::DeviceT<TypeTy, IdentTy, ExprTy>;
+using DeviceType = tomp::clause::DeviceTypeT<TypeTy, IdentTy, ExprTy>;
+using DistSchedule = tomp::clause::DistScheduleT<TypeTy, IdentTy, ExprTy>;
+using Doacross = tomp::clause::DoacrossT<TypeTy, IdentTy, ExprTy>;
+using DynamicAllocators =
+    tomp::clause::DynamicAllocatorsT<TypeTy, IdentTy, ExprTy>;
+using Enter = tomp::clause::EnterT<TypeTy, IdentTy, ExprTy>;
+using Exclusive = tomp::clause::ExclusiveT<TypeTy, IdentTy, ExprTy>;
+using Fail = tomp::clause::FailT<TypeTy, IdentTy, ExprTy>;
+using Filter = tomp::clause::FilterT<TypeTy, IdentTy, ExprTy>;
+using Final = tomp::clause::FinalT<TypeTy, IdentTy, ExprTy>;
+using Firstprivate = tomp::clause::FirstprivateT<TypeTy, IdentTy, ExprTy>;
+using Flush = tomp::clause::FlushT<TypeTy, IdentTy, ExprTy>;
+using From = tomp::clause::FromT<TypeTy, IdentTy, ExprTy>;
+using Full = tomp::clause::FullT<TypeTy, IdentTy, ExprTy>;
+using Grainsize = tomp::clause::GrainsizeT<TypeTy, IdentTy, ExprTy>;
+using HasDeviceAddr = tomp::clause::HasDeviceAddrT<TypeTy, IdentTy, ExprTy>;
+using Hint = tomp::clause::HintT<TypeTy, IdentTy, ExprTy>;
+using If = tomp::clause::IfT<TypeTy, IdentTy, ExprTy>;
+using Inbranch = tomp::clause::InbranchT<TypeTy, IdentTy, ExprTy>;
+using Inclusive = tomp::clause::InclusiveT<TypeTy, IdentTy, ExprTy>;
+using Indirect = tomp::clause::IndirectT<TypeTy, IdentTy, ExprTy>;
+using Init = tomp::clause::InitT<TypeTy, IdentTy, ExprTy>;
+using InReduction = tomp::clause::InReductionT<TypeTy, IdentTy, ExprTy>;
+using IsDevicePtr = tomp::clause::IsDevicePtrT<TypeTy, IdentTy, ExprTy>;
+using Lastprivate = tomp::clause::LastprivateT<TypeTy, IdentTy, ExprTy>;
+using Linear = tomp::clause::LinearT<TypeTy, IdentTy, ExprTy>;
+using Link = tomp::clause::LinkT<TypeTy, IdentTy, ExprTy>;
+using Map = tomp::clause::MapT<TypeTy, IdentTy, ExprTy>;
+using Match = tomp::clause::MatchT<TypeTy, IdentTy, ExprTy>;
+using MemoryOrder = tomp::clause::MemoryOrderT<TypeTy, IdentTy, ExprTy>;
+using Mergeable = tomp::clause::MergeableT<TypeTy, IdentTy, ExprTy>;
+using Message = tomp::clause::MessageT<TypeTy, IdentTy, ExprTy>;
+using Nocontext = tomp::clause::NocontextT<TypeTy, IdentTy, ExprTy>;
+using Nogroup = tomp::clause::NogroupT<TypeTy, IdentTy, ExprTy>;
+using Nontemporal = tomp::clause::NontemporalT<TypeTy, IdentTy, ExprTy>;
+using Notinbranch = tomp::clause::NotinbranchT<TypeTy, IdentTy, ExprTy>;
+using Novariants = tomp::clause::NovariantsT<TypeTy, IdentTy, ExprTy>;
+using Nowait = tomp::clause::NowaitT<TypeTy, IdentTy, ExprTy>;
+using NumTasks = tomp::clause::NumTasksT<TypeTy, IdentTy, ExprTy>;
+using NumTeams = tomp::clause::NumTeamsT<TypeTy, IdentTy, ExprTy>;
+using NumThreads = tomp::clause::NumThreadsT<TypeTy, IdentTy, ExprTy>;
+using OmpxAttribute = tomp::clause::OmpxAttributeT<TypeTy, IdentTy, ExprTy>;
+using OmpxBare = tomp::clause::OmpxBareT<TypeTy, IdentTy, ExprTy>;
 using OmpxDynCgroupMem =
-    tomp::clause::OmpxDynCgroupMemT<SymIdent, SymReference>;
-using Ordered = tomp::clause::OrderedT<SymIdent, SymReference>;
-using Order = tomp::clause::OrderT<SymIdent, SymReference>;
-using Partial = tomp::clause::PartialT<SymIdent, SymReference>;
-using Priority = tomp::clause::PriorityT<SymIdent, SymReference>;
-using Private = tomp::clause::PrivateT<SymIdent, SymReference>;
-using ProcBind = tomp::clause::ProcBindT<SymIdent, SymReference>;
-using Reduction = tomp::clause::ReductionT<SymIdent, SymReference>;
-using Safelen = tomp::clause::SafelenT<SymIdent, SymReference>;
-using Schedule = tomp::clause::ScheduleT<SymIdent, SymReference>;
-using Shared = tomp::clause::SharedT<SymIdent, SymReference>;
-using Simdlen = tomp::clause::SimdlenT<SymIdent, SymReference>;
-using Sizes = tomp::clause::SizesT<SymIdent, SymReference>;
-using TaskReduction = tomp::clause::TaskReductionT<SymIdent, SymReference>;
-using ThreadLimit = tomp::clause::ThreadLimitT<SymIdent, SymReference>;
-using To = tomp::clause::ToT<SymIdent, SymReference>;
-using Uniform = tomp::clause::UniformT<SymIdent, SymReference>;
-using UseDeviceAddr = tomp::clause::UseDeviceAddrT<SymIdent, SymReference>;
-using UseDevicePtr = tomp::clause::UseDevicePtrT<SymIdent, SymReference>;
+    tomp::clause::OmpxDynCgroupMemT<TypeTy, IdentTy, ExprTy>;
+using Ordered = tomp::clause::OrderedT<TypeTy, IdentTy, ExprTy>;
+using Order = tomp::clause::OrderT<TypeTy, IdentTy, ExprTy>;
+using Partial = tomp::clause::PartialT<TypeTy, IdentTy, ExprTy>;
+using Priority = tomp::clause::PriorityT<TypeTy, IdentTy, ExprTy>;
+using Private = tomp::clause::PrivateT<TypeTy, IdentTy, ExprTy>;
+using ProcBind = tomp::clause::ProcBindT<TypeTy, IdentTy, ExprTy>;
+using Read = tomp::clause::ReadT<TypeTy, IdentTy, ExprTy>;
+using Reduction = tomp::clause::ReductionT<TypeTy, IdentTy, ExprTy>;
+using Relaxed = tomp::clause::RelaxedT<TypeTy, IdentTy, ExprTy>;
+using Release = tomp::clause::ReleaseT<TypeTy, IdentTy, ExprTy>;
+using ReverseOffload = tomp::clause::ReverseOffloadT<TypeTy, IdentTy, ExprTy>;
+using Safelen = tomp::clause::SafelenT<TypeTy, IdentTy, ExprTy>;
+using Schedule = tomp::clause::ScheduleT<TypeTy, IdentTy, ExprTy>;
+using SeqCst = tomp::clause::SeqCstT<TypeTy, IdentTy, ExprTy>;
+using Severity = tomp::clause::SeverityT<TypeTy, IdentTy, ExprTy>;
+using Shared = tomp::clause::SharedT<TypeTy, IdentTy, ExprTy>;
+using Simdlen = tomp::clause::SimdlenT<TypeTy, IdentTy, ExprTy>;
+using Simd = tomp::clause::SimdT<TypeTy, IdentTy, ExprTy>;
+using Sizes = tomp::clause::SizesT<TypeTy, IdentTy, ExprTy>;
+using TaskReduction = tomp::clause::TaskReductionT<TypeTy, IdentTy, ExprTy>;
+using ThreadLimit = tomp::clause::ThreadLimitT<TypeTy, IdentTy, ExprTy>;
+using Threadprivate = tomp::clause::ThreadprivateT<TypeTy, IdentTy, ExprTy>;
+using Threads = tomp::clause::ThreadsT<TypeTy, IdentTy, ExprTy>;
+using To = tomp::clause::ToT<TypeTy, IdentTy, ExprTy>;
+using UnifiedAddress = tomp::clause::UnifiedAddressT<TypeTy, IdentTy, ExprTy>;
+using UnifiedSharedMemory =
+    tomp::clause::UnifiedSharedMemoryT<TypeTy, IdentTy, ExprTy>;
+using Uniform = tomp::clause::UniformT<TypeTy, IdentTy, ExprTy>;
+using Unknown = tomp::clause::UnknownT<TypeTy, IdentTy, ExprTy>;
+using Untied = tomp::clause::UntiedT<TypeTy, IdentTy, ExprTy>;
+using Update = tomp::clause::UpdateT<TypeTy, IdentTy, ExprTy>;
+using UseDeviceAddr = tomp::clause::UseDeviceAddrT<TypeTy, IdentTy, ExprTy>;
+using UseDevicePtr = tomp::clause::UseDevicePtrT<TypeTy, IdentTy, ExprTy>;
+using UsesAllocators = tomp::clause::UsesAllocatorsT<TypeTy, IdentTy, ExprTy>;
+using Use = tomp::clause::UseT<TypeTy, IdentTy, ExprTy>;
+using Weak = tomp::clause::WeakT<TypeTy, IdentTy, ExprTy>;
+using When = tomp::clause::WhenT<TypeTy, IdentTy, ExprTy>;
+using Write = tomp::clause::WriteT<TypeTy, IdentTy, ExprTy>;
 } // namespace clause
 
-struct Clause : public tomp::ClauseT<SymIdent, SymReference> {
+struct Clause : public tomp::ClauseT<TypeTy, IdentTy, ExprTy> {
   parser::CharBlock source;
 };
 
 template <typename Specific>
 Clause makeClause(llvm::omp::Clause id, Specific &&specific,
                   parser::CharBlock source = {}) {
-  return Clause{id, specific, source};
+  return Clause{{id, specific}, source};
 }
 
 Clause makeClause(const Fortran::parser::OmpClause &cls,
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 3b687d03adf3719..aad5a8097f8fe27 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -98,7 +98,8 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
       collectOmpObjectListSymbol(firstPrivateClause->v, privatizedSymbols);
     } else if (const auto &lastPrivateClause =
                    std::get_if<omp::clause::Lastprivate>(&clause.u)) {
-      collectOmpObjectListSymbol(lastPrivateClause->v, privatizedSymbols);
+      const ObjectList &objects = std::get<ObjectList>(lastPrivateClause->t);
+      collectOmpObjectListSymbol(objects, privatizedSymbols);
       hasLastPrivateOp = true;
     } else if (std::get_if<omp::clause::Collapse>(&clause.u)) {
       hasCollapse = true;
@@ -285,12 +286,13 @@ void DataSharingProcessor::collectSymbols(
 }
 
 void DataSharingProcessor::collectDefaultSymbols() {
+  using DataSharingAttribute = omp::clause::Default::DataSharingAttribute;
   for (const omp::Clause &clause : clauses) {
     if (const auto *defaultClause =
             std::get_if<omp::clause::Default>(&clause.u)) {
-      if (defaultClause->v == omp::clause::Default::Type::Private)
+      if (defaultClause->v == DataSharingAttribute::Private)
         collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate);
-      else if (defaultClause->v == omp::clause::Default::Type::Firstprivate)
+      else if (defaultClause->v == DataSharingAttribute::Firstprivate)
         collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate);
     }
   }
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index e20c39887c59e5e..d45db7963b0acd2 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -70,6 +70,103 @@ static void genNestedEvaluations(Fortran::lower::AbstractConverter &converter,
     converter.genEval(e);
 }
 
+//===----------------------------------------------------------------------===//
+// Directive decomposition
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct CompositeInfo
+    : public tomp::CompositeInfoBase<TypeTy, IdentTy, ExprTy, CompositeInfo> {
+  CompositeInfo(const mlir::ModuleOp &modOp,
+                Fortran::semantics::SemanticsContext &semaCtx,
+                Fortran::lower::pft::Evaluation &ev,
+                llvm::omp::Directive compDir,
+                const Fortran::parser::OmpClauseList &clauseList)
+      : CompositeInfoBase<TypeTy, IdentTy, ExprTy, CompositeInfo>(
+            getOpenMPVersion(modOp), compDir, *this),
+        semaCtx(semaCtx), mod(modOp), eval(ev) {
+    // Convert each parser::OmpClause to our representation, append to list
+    for (auto &parserClause : clauseList.v) {
+      clauses.push_back(Fortran::lower::omp::makeClause(parserClause, semaCtx));
+      add(&clauses.back()); // Tell the base class about the clause.
+    }
+  }
+
+  // Produce a clause with empty source from the bare clause provided.
+  Clause *makeClause(tomp::ClauseT<TypeTy, IdentTy, ExprTy> &&specific) {
+    clauses.push_back(Clause{{std::move(specific)}, {}});
+    return &clauses.back();
+  }
+
+  // Given an object, return its base object if one exists.
+  std::optional<Object> getBaseObject(const Object &object) {
+    return Fortran::lower::omp::getBaseObject(object, semaCtx);
+  }
+
+  // Return the iteration variable of the associated loop if any.
+  std::optional<Object> getIterVar() {
+    Fortran::semantics::Symbol *symbol = getIterationVariableSymbol(eval);
+    if (symbol)
+      return Object{symbol, /*designator=*/{}};
+    return std::nullopt;
+  }
+
+  Fortran::semantics::SemanticsContext &semaCtx;
+  const mlir::ModuleOp &mod;
+  Fortran::lower::pft::Evaluation &eval;
+  // Beware of invalidating clause addresses: use std::list.
+  std::list<Clause> clauses;
+};
+} // namespace
+
+#if 0
+[[maybe_unused]] static llvm::raw_ostream &
+operator<<(llvm::raw_ostream &os, const DirectiveInfo &dirInfo) {
+  os << llvm::omp::getOpenMPDirectiveName(dirInfo.id);
+  for (auto [index, clause] : llvm::enumerate(dirInfo.clauses)) {
+    os << (index == 0 ? '\t' : ' ');
+    os << llvm::omp::getOpenMPClauseName(clause->id);
+  }
+  return os;
+}
+
+// XXX
+[[maybe_unused]] static llvm::raw_ostream &
+operator<<(llvm::raw_ostream &os, const CompositeInfo &compInfo) {
+  for (const auto &[index, dirInfo] : llvm::enumerate(compInfo.leafs))
+    os << "leaf[" << index << "]: " << dirInfo << '\n';
+
+  os << "syms:\n";
+  for (const auto &[sym, clauses] : compInfo.syms) {
+    os << *sym << " -> {";
+    for (const auto *clause : clauses)
+      os << ' ' << llvm::omp::getOpenMPClauseName(clause->id);
+    os << " }\n";
+  }
+  os << "mapBases: {";
+  for (const auto &sym : compInfo.mapBases)
+    os << ' ' << *sym;
+  os << " }\n";
+  return os;
+}
+#endif
+
+static void splitCompositeConstruct(
+    const mlir::ModuleOp &modOp, Fortran::semantics::SemanticsContext &semaCtx,
+    Fortran::lower::pft::Evaluation &eval, llvm::omp::Directive compDir,
+    const Fortran::parser::OmpClauseList &clauseList) {
+  llvm::errs() << "composite name:"
+               << llvm::omp::getOpenMPDirectiveName(compDir) << '\n';
+
+  CompositeInfo compInfo(modOp, semaCtx, eval, compDir, clauseList);
+
+  bool success = compInfo.split();
+  llvm::errs() << "success:" << success << '\n';
+
+  for (auto &s : compInfo.leafs)
+    llvm::errs() << s << '\n';
+}
+
 static fir::GlobalOp globalInitialization(
     Fortran::lower::AbstractConverter &converter,
     fir::FirOpBuilder &firOpBuilder, const Fortran::semantics::Symbol &sym,
@@ -572,7 +669,7 @@ genParallelOp(Fortran::lower::AbstractConverter &converter,
   llvm::SmallVector<const Fortran::semantics::Symbol *> reductionSymbols;
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(clause::If::DirectiveNameModifier::Parallel, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_parallel, ifClauseOperand);
   cp.processNumThreads(stmtCtx, numThreadsClauseOperand);
   cp.processProcBind(procBindKindAttr);
   cp.processDefault();
@@ -675,7 +772,7 @@ genTaskOp(Fortran::lower::AbstractConverter &converter,
       dependOperands;
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(clause::If::DirectiveNameModifier::Task, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_task, ifClauseOperand);
   cp.processAllocate(allocatorOperands, allocateOperands);
   cp.processDefault();
   cp.processFinal(stmtCtx, finalClauseOperand);
@@ -734,7 +831,7 @@ genDataOp(Fortran::lower::AbstractConverter &converter,
   llvm::SmallVector<const Fortran::semantics::Symbol *> useDeviceSymbols;
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(clause::If::DirectiveNameModifier::TargetData, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_target_data, ifClauseOperand);
   cp.processDevice(stmtCtx, deviceOperand);
   cp.processUseDevicePtr(devicePtrOperands, useDeviceTypes, useDeviceLocs,
                          useDeviceSymbols);
@@ -765,23 +862,19 @@ genEnterExitUpdateDataOp(Fortran::lower::AbstractConverter &converter,
   llvm::SmallVector<mlir::Value> mapOperands, dependOperands;
   llvm::SmallVector<mlir::Attribute> dependTypeOperands;
 
-  clause::If::DirectiveNameModifier directiveName;
   llvm::omp::Directive directive;
   if constexpr (std::is_same_v<OpTy, mlir::omp::EnterDataOp>) {
-    directiveName = clause::If::DirectiveNameModifier::TargetEnterData;
     directive = llvm::omp::Directive::OMPD_target_enter_data;
   } else if constexpr (std::is_same_v<OpTy, mlir::omp::ExitDataOp>) {
-    directiveName = clause::If::DirectiveNameModifier::TargetExitData;
     directive = llvm::omp::Directive::OMPD_target_exit_data;
   } else if constexpr (std::is_same_v<OpTy, mlir::omp::UpdateDataOp>) {
-    directiveName = clause::If::DirectiveNameModifier::TargetUpdate;
     directive = llvm::omp::Directive::OMPD_target_update;
   } else {
     return nullptr;
   }
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(directiveName, ifClauseOperand);
+  cp.processIf(directive, ifClauseOperand);
   cp.processDevice(stmtCtx, deviceOperand);
   cp.processDepend(dependTypeOperands, dependOperands);
   cp.processNowait(nowaitAttr);
@@ -973,7 +1066,7 @@ genTargetOp(Fortran::lower::AbstractConverter &converter,
   llvm::SmallVector<const Fortran::semantics::Symbol *> mapSymbols;
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(clause::If::DirectiveNameModifier::Target, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_target, ifClauseOperand);
   cp.processDevice(stmtCtx, deviceOperand);
   cp.processThreadLimit(stmtCtx, threadLimitOperand);
   cp.processDepend(dependTypeOperands, dependOperands);
@@ -983,9 +1076,8 @@ genTargetOp(Fortran::lower::AbstractConverter &converter,
 
   cp.processTODO<clause::Private, clause::Firstprivate, clause::IsDevicePtr,
                  clause::HasDeviceAddr, clause::Reduction, clause::InReduction,
-                 clause::Allocate, clause::UsesAllocators,
-                 clause::Defaultmap>(currentLocation,
-                                     llvm::omp::Directive::OMPD_target);
+                 clause::Allocate, clause::UsesAllocators, clause::Defaultmap>(
+      currentLocation, llvm::omp::Directive::OMPD_target);
 
   // 5.8.1 Implicit Data-Mapping Attribute Rules
   // The following code follows the implicit data-mapping rules to map all the
@@ -1086,7 +1178,7 @@ genTeamsOp(Fortran::lower::AbstractConverter &converter,
   llvm::SmallVector<mlir::Attribute> reductionDeclSymbols;
 
   ClauseProcessor cp(converter, semaCtx, clauseList);
-  cp.processIf(clause::If::DirectiveNameModifier::Teams, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_teams, ifClauseOperand);
   cp.processAllocate(allocatorOperands, allocateOperands);
   cp.processDefault();
   cp.processNumTeams(stmtCtx, numTeamsClauseOperand);
@@ -1401,7 +1493,7 @@ createSimdLoop(Fortran::lower::AbstractConverter &converter,
                      loopVarTypeSize);
   cp.processScheduleChunk(stmtCtx, scheduleChunkClauseOperand);
   cp.processReduction(loc, reductionVars, reductionDeclSymbols);
-  cp.processIf(clause::If::DirectiveNameModifier::Simd, ifClauseOperand);
+  cp.processIf(llvm::omp::Directive::OMPD_simd, ifClauseOperand);
   cp.processSimdlen(simdlenClauseOperand);
   cp.processSafelen(safelenClauseOperand);
   cp.processTODO<clause::Aligned, clause::Allocate, clause::Linear,
@@ -1551,6 +1643,10 @@ static void genOMP(Fortran::lower::AbstractConverter &converter,
                    const Fortran::parser::OpenMPLoopConstruct &loopConstruct) {
   const auto &beginLoopDirective =
       std::get<Fortran::parser::OmpBeginLoopDirective>(loopConstruct.t);
+  // Test call
+  splitCompositeConstruct(converter.getFirOpBuilder().getModule(), semaCtx,
+                          eval, std::get<0>(beginLoopDirective.t).v,
+                          std::get<1>(beginLoopDirective.t));
   const auto &loopOpClauseList =
       std::get<Fortran::parser::OmpClauseList>(beginLoopDirective.t);
   mlir::Location currentLocation =
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index d42ac56fa98c678..6e18d4ee88e6356 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -15,6 +15,7 @@
 
 #include <flang/Lower/AbstractConverter.h>
 #include <flang/Lower/ConvertType.h>
+#include <flang/Lower/PFTBuilder.h>
 #include <flang/Parser/parse-tree.h>
 #include <flang/Parser/tools.h>
 #include <flang/Semantics/tools.h>
@@ -104,6 +105,27 @@ getOmpObjectSymbol(const Fortran::parser::OmpObject &ompObject) {
   return sym;
 }
 
+Fortran::semantics::Symbol *
+getIterationVariableSymbol(const Fortran::lower::pft::Evaluation &eval) {
+  return eval.visit(Fortran::common::visitors{
+      [&](const Fortran::parser::DoConstruct &doLoop) {
+        if (const auto &maybeCtrl = doLoop.GetLoopControl()) {
+          using LoopControl = Fortran::parser::LoopControl;
+          if (auto *bounds = std::get_if<LoopControl::Bounds>(&maybeCtrl->u)) {
+            static_assert(
+                std::is_same_v<decltype(bounds->name),
+                               Fortran::parser::Scalar<Fortran::parser::Name>>);
+            return bounds->name.thing.symbol;
+          }
+        }
+        return static_cast<Fortran::semantics::Symbol *>(nullptr);
+      },
+      [](auto &&) {
+        return static_cast<Fortran::semantics::Symbol *>(nullptr);
+      },
+  });
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index b7542851ce8fba7..12dae7f7bd6a4a9 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -33,9 +33,12 @@ struct OmpObjectList;
 } // namespace parser
 
 namespace lower {
-
 class AbstractConverter;
 
+namespace pft {
+struct Evaluation;
+} // namespace pft
+
 namespace omp {
 
 uint32_t getOpenMPVersion(mlir::ModuleOp mod);
@@ -67,6 +70,9 @@ void genObjectList2(const Fortran::parser::OmpObjectList &objectList,
                     Fortran::lower::AbstractConverter &converter,
                     llvm::SmallVectorImpl<mlir::Value> &operands);
 
+Fortran::semantics::Symbol *
+getIterationVariableSymbol(const Fortran::lower::pft::Evaluation &eval);
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/llvm/include/llvm/Frontend/OpenMP/ClauseT.h b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h
new file mode 100644
index 000000000000000..bbced8ada2f2516
--- /dev/null
+++ b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h
@@ -0,0 +1,2035 @@
+//===- ClauseT -- clause template definitions -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef FORTRAN_LOWER_OPENMP_CLAUSET_H
+#define FORTRAN_LOWER_OPENMP_CLAUSET_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <algorithm>
+#include <iterator>
+#include <optional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "llvm/Frontend/OpenMP/OMP.h.inc"
+
+#define ENUM(Name, ...) enum class Name { __VA_ARGS__ }
+#define OPT(x) std::optional<x>
+
+// Helper macros for enum-class conversion.
+#define M(Ov, Tv)                                                              \
+  if (v == OtherEnum::Ov) {                                                    \
+    return ThisEnum::Tv;                                                       \
+  }
+
+#define ENUM_CONVERT(func, OtherE, ThisE, Maps)                                \
+  auto func = [](OtherE v) -> ThisE {                                          \
+    using ThisEnum = ThisE;                                                    \
+    using OtherEnum = OtherE;                                                  \
+    Maps;                                                                      \
+    llvm_unreachable("Unexpected value in " #OtherE);                          \
+  }
+
+// Usage:
+//
+// Given two enums,
+//   enum class Other { o1, o2 };
+//   enum class This { t1, t2 };
+// generate conversion function "Func : Other -> This" with
+//   ENUM_CONVERT(Func, Other, This, M(o1, t1) M(o2, t2) ...)
+//
+// Note that the sequence of M(other-value, this-value) is separated
+// with _spaces_, not commas.
+
+static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharing() {
+  static llvm::omp::Directive worksharing[] = {
+      llvm::omp::Directive::OMPD_do,     llvm::omp::Directive::OMPD_for,
+      llvm::omp::Directive::OMPD_scope,  llvm::omp::Directive::OMPD_sections,
+      llvm::omp::Directive::OMPD_single, llvm::omp::Directive::OMPD_workshare,
+  };
+  return worksharing;
+}
+
+static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharingLoop() {
+  static llvm::omp::Directive worksharingLoop[] = {
+      llvm::omp::Directive::OMPD_do,
+      llvm::omp::Directive::OMPD_for,
+  };
+  return worksharingLoop;
+}
+
+namespace detail {
+template <typename Container, typename Predicate>
+typename std::remove_reference_t<Container>::iterator
+find_unique(Container &&container, Predicate &&pred) {
+  auto first = std::find_if(container.begin(), container.end(), pred);
+  if (first == container.end())
+    return first;
+  auto second = std::find_if(std::next(first), container.end(), pred);
+  if (second == container.end())
+    return first;
+  return container.end();
+}
+
+// is_variant<T>
+template <typename T>
+struct is_variant {
+  static constexpr bool value = false;
+};
+
+template <typename... Ts>
+struct is_variant<std::variant<Ts...>> {
+  static constexpr bool value = true;
+};
+
+template <typename T>
+constexpr bool is_variant_v = is_variant<T>::value;
+
+// Unions of variants
+template <typename...>
+struct UnionOfTwo;
+
+template <typename... Types1, typename... Types2>
+struct UnionOfTwo<std::variant<Types1...>, std::variant<Types2...>> {
+  using type = std::variant<Types1..., Types2...>;
+};
+
+template <typename...>
+struct Union;
+
+template <>
+struct Union<> {
+  // Legal to define, illegal to instantiate.
+  using type = std::variant<>;
+};
+
+template <typename T, typename... Ts>
+struct Union<T, Ts...> {
+  static_assert(is_variant_v<T>);
+  using type = typename UnionOfTwo<T, typename Union<Ts...>::type>::type;
+};
+} // namespace detail
+
+namespace tomp {
+
+namespace type {
+template <typename T>
+using ListT = std::vector<T>;
+
+// A specialization of ObjectT<Id, Expr> must provide the following definitions:
+// {
+//    using IdType = Id;
+//    using ExprType = Expr;
+//
+//    auto id() const -> Id {
+//      return the identifier of the object (for use in tests for
+//         presence/absence of the object)
+//    }
+//
+//    auto ref() const -> const Expr& {
+//      return the expression accessing (referencing) the object
+//    }
+// }
+//
+// For example, the ObjectT instance created for "var[x+1]" would have
+// the `id()` return the identifier for `var`, and the `ref()` return the
+// representation of the array-access `var[x+1]`.
+template <typename IdType, typename ExprType>
+struct ObjectT;
+
+template <typename I, typename E>
+using ObjectListT = ListT<ObjectT<I, E>>;
+
+using DirectiveName = llvm::omp::Directive;
+
+template <typename I, typename E>
+struct DefinedOperatorT {
+  struct DefinedOpName {
+    using WrapperTrait = std::true_type;
+    ObjectT<I, E> v;
+  };
+  ENUM(IntrinsicOperator, Power, Multiply, Divide, Add, Subtract, Concat, LT,
+       LE, EQ, NE, GE, GT, NOT, AND, OR, EQV, NEQV);
+  using UnionTrait = std::true_type;
+  std::variant<DefinedOpName, IntrinsicOperator> u;
+};
+
+template <typename E>
+struct RangeT {
+  // range-specification: begin : end[: step]
+  using TupleTrait = std::true_type;
+  std::tuple<E, E, OPT(E)> t;
+};
+
+template <typename TypeType, typename IdType, typename ExprType>
+struct IteratorT {
+  // iterators-specifier: [ iterator-type ] identifier = range-specification
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(TypeType), ObjectT<IdType, ExprType>, RangeT<ExprType>> t;
+};
+
+template <typename I, typename E>
+struct MapperT {
+  using MapperIdentifier = ObjectT<I, E>;
+  using WrapperTrait = std::true_type;
+  MapperIdentifier v;
+};
+
+ENUM(MemoryOrder, AcqRel, Acquire, Relaxed, Release, SeqCst);
+ENUM(MotionExpectation, Present);
+
+template <typename I, typename E>
+struct LoopIterationT {
+  struct Length {
+    using TupleTrait = std::true_type;
+    std::tuple<DefinedOperatorT<I, E>, E> t;
+  };
+  using TupleTrait = std::true_type;
+  std::tuple<ObjectT<I, E>, OPT(Length)> t;
+};
+
+template <typename I, typename E>
+struct ProcedureDesignatorT {
+  using WrapperTrait = std::true_type;
+  ObjectT<I, E> v;
+};
+
+template <typename I, typename E>
+struct ReductionIdentifierT {
+  using UnionTrait = std::true_type;
+  std::variant<DefinedOperatorT<I, E>, ProcedureDesignatorT<I, E>> u;
+};
+} // namespace type
+
+template <typename T>
+using ListT = type::ListT<T>;
+
+template <typename I, typename E>
+using ObjectT = type::ObjectT<I, E>;
+
+template <typename I, typename E>
+using ObjectListT = type::ObjectListT<I, E>;
+
+namespace clause {
+template <typename T, typename I, typename E>
+struct AbsentT {
+  using List = ListT<type::DirectiveName>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct AcqRelT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct AcquireT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct AdjustArgsT {
+  using IncompleteTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct AffinityT {
+  using Iterator = type::IteratorT<T, I, E>;
+  using LocatorList = ObjectListT<I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(Iterator), LocatorList> t;
+};
+
+template <typename T, typename I, typename E>
+struct AlignT {
+  using Alignment = E;
+
+  using WrapperTrait = std::true_type;
+  Alignment v;
+};
+
+template <typename T, typename I, typename E>
+struct AlignedT {
+  using Alignment = E;
+  using List = ObjectListT<I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(Alignment), List> t;
+};
+
+template <typename T, typename I, typename E>
+struct AllocatorT;
+
+template <typename T, typename I, typename E>
+struct AllocateT {
+  using AllocatorSimpleModifier = E;
+  using AllocatorComplexModifier = AllocatorT<T, I, E>;
+  using AlignModifier = AlignT<T, I, E>;
+  using List = ObjectListT<I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(AllocatorSimpleModifier), OPT(AllocatorComplexModifier),
+             OPT(AlignModifier), List>
+      t;
+};
+
+template <typename T, typename I, typename E>
+struct AllocatorT {
+  using Allocator = E;
+  using WrapperTrait = std::true_type;
+  Allocator v;
+};
+
+template <typename T, typename I, typename E>
+struct AppendArgsT {
+  using IncompleteTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct AtT {
+  ENUM(ActionTime, Compilation, Execution);
+  using WrapperTrait = std::true_type;
+  ActionTime v;
+};
+
+template <typename T, typename I, typename E>
+struct AtomicDefaultMemOrderT {
+  using MemoryOrder = type::MemoryOrder;
+  using WrapperTrait = std::true_type;
+  MemoryOrder v; // Name not provided in spec
+};
+
+template <typename T, typename I, typename E>
+struct BindT {
+  ENUM(Binding, Teams, Parallel, Thread);
+  using WrapperTrait = std::true_type;
+  Binding v;
+};
+
+template <typename T, typename I, typename E>
+struct CancellationConstructTypeT {
+  // Artificial
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct CaptureT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct CollapseT {
+  using N = E;
+  using WrapperTrait = std::true_type;
+  N v;
+};
+
+template <typename T, typename I, typename E>
+struct CompareT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ContainsT {
+  using List = ListT<type::DirectiveName>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct CopyinT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct CopyprivateT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct DefaultT {
+  ENUM(DataSharingAttribute, Firstprivate, None, Private, Shared);
+  using WrapperTrait = std::true_type;
+  DataSharingAttribute v;
+};
+
+template <typename T, typename I, typename E>
+struct DefaultmapT {
+  ENUM(ImplicitBehavior, Alloc, To, From, Tofrom, Firstprivate, None, Default,
+       Present);
+  ENUM(VariableCategory, Scalar, Aggregate, Pointer, Allocatable);
+  using TupleTrait = std::true_type;
+  std::tuple<ImplicitBehavior, OPT(VariableCategory)> t;
+};
+
+template <typename T, typename I, typename E>
+struct DoacrossT;
+
+template <typename T, typename I, typename E>
+struct DependT {
+  ENUM(TaskDependenceType, In, Out, Inout, Mutexinoutset, Inoutset, Depobj);
+  using Iterator = type::IteratorT<T, I, E>;
+  using LocatorList = ObjectListT<I, E>;
+
+  struct WithLocators { // Modern form
+    using TupleTrait = std::true_type;
+    std::tuple<TaskDependenceType, OPT(Iterator), LocatorList> t;
+  };
+
+  using Doacross = DoacrossT<T, I, E>;
+  using UnionTrait = std::true_type;
+  std::variant<Doacross, WithLocators> u; // Doacross form is legacy
+};
+
+template <typename T, typename I, typename E>
+struct DepobjT {
+  // Artificial
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct DestroyT {
+  using DestroyVar = ObjectT<I, E>;
+  using WrapperTrait = std::true_type;
+  DestroyVar v;
+};
+
+template <typename T, typename I, typename E>
+struct DetachT {
+  using EventHandle = ObjectT<I, E>;
+  using WrapperTrait = std::true_type;
+  EventHandle v;
+};
+
+template <typename T, typename I, typename E>
+struct DeviceT {
+  using DeviceDescription = E;
+  ENUM(DeviceModifier, Ancestor, DeviceNum);
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(DeviceModifier), DeviceDescription> t;
+};
+
+template <typename T, typename I, typename E>
+struct DeviceTypeT {
+  ENUM(DeviceTypeDescription, Any, Host, Nohost);
+  using WrapperTrait = std::true_type;
+  DeviceTypeDescription v;
+};
+
+template <typename T, typename I, typename E>
+struct DistScheduleT {
+  ENUM(Kind, Static);
+  using ChunkSize = E;
+  using TupleTrait = std::true_type;
+  std::tuple<Kind, OPT(ChunkSize)> t;
+};
+
+template <typename T, typename I, typename E>
+struct DoacrossT {
+  using Vector = ListT<type::LoopIterationT<I, E>>;
+  ENUM(DependenceType, Source, Sink);
+  using TupleTrait = std::true_type;
+  std::tuple<DependenceType, Vector> t;
+};
+
+template <typename T, typename I, typename E>
+struct DynamicAllocatorsT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct EnterT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct ExclusiveT {
+  using WrapperTrait = std::true_type;
+  using List = ObjectListT<I, E>;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct FailT {
+  using MemoryOrder = type::MemoryOrder;
+  using WrapperTrait = std::true_type;
+  MemoryOrder v;
+};
+
+template <typename T, typename I, typename E>
+struct FilterT {
+  using ThreadNum = E;
+  using WrapperTrait = std::true_type;
+  ThreadNum v;
+};
+
+template <typename T, typename I, typename E>
+struct FinalT {
+  using Finalize = E;
+  using WrapperTrait = std::true_type;
+  Finalize v;
+};
+
+template <typename T, typename I, typename E>
+struct FirstprivateT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct FlushT {
+  // Artificial
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct FromT {
+  using LocatorList = ObjectListT<I, E>;
+  using Expectation = type::MotionExpectation;
+  using Mapper = type::MapperT<I, E>;
+  using Iterator = type::IteratorT<T, I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(Expectation), OPT(Mapper), OPT(Iterator), LocatorList> t;
+};
+
+template <typename T, typename I, typename E>
+struct FullT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct GrainsizeT {
+  using GrainSize = E;
+  using WrapperTrait = std::true_type;
+  GrainSize v;
+};
+
+template <typename T, typename I, typename E>
+struct HasDeviceAddrT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct HintT {
+  using HintExpr = E;
+  using WrapperTrait = std::true_type;
+  HintExpr v;
+};
+
+template <typename T, typename I, typename E>
+struct HoldsT {
+  using WrapperTrait = std::true_type;
+  E v; // No argument name in spec 5.2
+};
+
+template <typename T, typename I, typename E>
+struct IfT {
+  using DirectiveNameModifier = type::DirectiveName;
+  using IfExpression = E;
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(DirectiveNameModifier), IfExpression> t;
+};
+
+template <typename T, typename I, typename E>
+struct InbranchT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct InclusiveT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct IndirectT {
+  using InvokedByFptr = E;
+  using WrapperTrait = std::true_type;
+  InvokedByFptr v;
+};
+
+template <typename T, typename I, typename E>
+struct InitT {
+  using ForeignRuntimeId = E;
+  using InteropVar = ObjectT<I, E>;
+  using InteropPreference = ListT<ForeignRuntimeId>;
+  ENUM(InteropType, Target, Targetsync);
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(InteropPreference), InteropType, InteropVar> t;
+};
+
+template <typename T, typename I, typename E>
+struct InitializerT {
+  using InitializerExpr = E;
+  using WrapperTrait = std::true_type;
+  InitializerExpr v;
+};
+
+template <typename T, typename I, typename E>
+struct InReductionT {
+  using List = ObjectListT<I, E>;
+  using ReductionIdentifier = type::ReductionIdentifierT<I, E>;
+  using TupleTrait = std::true_type;
+  std::tuple<ReductionIdentifier, List> t;
+};
+
+template <typename T, typename I, typename E>
+struct IsDevicePtrT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct LastprivateT {
+  using List = ObjectListT<I, E>;
+  ENUM(LastprivateModifier, Conditional);
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(LastprivateModifier), List> t;
+};
+
+template <typename T, typename I, typename E>
+struct LinearT {
+  // std::get<type> won't work here due to duplicate types in the tuple.
+  using List = ObjectListT<I, E>;
+  using StepSimpleModifier = E;
+  using StepComplexModifier = E;
+  ENUM(LinearModifier, Ref, Val, Uval);
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(StepSimpleModifier), OPT(StepComplexModifier),
+             OPT(LinearModifier), List>
+      t;
+};
+
+template <typename T, typename I, typename E>
+struct LinkT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct MapT {
+  using LocatorList = ObjectListT<I, E>;
+  ENUM(MapType, To, From, Tofrom, Alloc, Release, Delete);
+  ENUM(MapTypeModifier, Always, Close, Present);
+  using Mapper = type::MapperT<I, E>;
+  using Iterator = type::IteratorT<T, I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(MapType), OPT(MapTypeModifier), OPT(Mapper), OPT(Iterator),
+             LocatorList>
+      t;
+};
+
+template <typename T, typename I, typename E>
+struct MatchT {
+  using IncompleteTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct MemoryOrderT {
+  // Artificial
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct MergeableT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct MessageT {
+  using MsgString = E;
+  using WrapperTrait = std::true_type;
+  MsgString v;
+};
+
+template <typename T, typename I, typename E>
+struct NocontextT {
+  using DoNotUpdateContext = E;
+  using WrapperTrait = std::true_type;
+  DoNotUpdateContext v;
+};
+
+template <typename T, typename I, typename E>
+struct NogroupT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NontemporalT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct NoOpenmp {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NoOpenmpRoutines {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NoParallelism {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NotinbranchT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NovariantsT {
+  using DoNotUseVariant = E;
+  using WrapperTrait = std::true_type;
+  DoNotUseVariant v;
+};
+
+template <typename T, typename I, typename E>
+struct NowaitT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct NumTasksT {
+  using NumTasks = E;
+  ENUM(Prescriptiveness, Strict);
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(Prescriptiveness), NumTasks> t;
+};
+
+template <typename T, typename I, typename E>
+struct NumTeamsT {
+  using TupleTrait = std::true_type;
+  using LowerBound = E;
+  using UpperBound = E;
+  std::tuple<OPT(LowerBound), UpperBound> t;
+};
+
+template <typename T, typename I, typename E>
+struct NumThreadsT {
+  using Nthreads = E;
+  using WrapperTrait = std::true_type;
+  Nthreads v;
+};
+
+template <typename T, typename I, typename E>
+struct OmpxAttributeT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct OmpxBareT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct OmpxDynCgroupMemT {
+  using WrapperTrait = std::true_type;
+  E v;
+};
+
+template <typename T, typename I, typename E>
+struct OrderT {
+  ENUM(OrderModifier, Reproducible, Unconstrained);
+  ENUM(Ordering, Concurrent);
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(OrderModifier), Ordering> t;
+};
+
+template <typename T, typename I, typename E>
+struct OrderedT {
+  using N = E;
+  using WrapperTrait = std::true_type;
+  OPT(N) v;
+};
+
+template <typename T, typename I, typename E>
+struct OtherwiseT {
+  using IncompleteTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct PartialT {
+  using UnrollFactor = E;
+  using WrapperTrait = std::true_type;
+  OPT(UnrollFactor) v;
+};
+
+template <typename T, typename I, typename E>
+struct PriorityT {
+  using PriorityValue = E;
+  using WrapperTrait = std::true_type;
+  PriorityValue v;
+};
+
+template <typename T, typename I, typename E>
+struct PrivateT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct ProcBindT {
+  ENUM(AffinityPolicy, Close, Master, Spread, Primary);
+  using WrapperTrait = std::true_type;
+  AffinityPolicy v;
+};
+
+template <typename T, typename I, typename E>
+struct ReadT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ReductionT {
+  using List = ObjectListT<I, E>;
+  using ReductionIdentifier = type::ReductionIdentifierT<I, E>;
+  ENUM(ReductionModifier, Default, Inscan, Task);
+  using TupleTrait = std::true_type;
+  std::tuple<ReductionIdentifier, OPT(ReductionModifier), List> t;
+};
+
+template <typename T, typename I, typename E>
+struct RelaxedT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ReleaseT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ReverseOffloadT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct SafelenT {
+  using Length = E;
+  using WrapperTrait = std::true_type;
+  Length v;
+};
+
+template <typename T, typename I, typename E>
+struct ScheduleT {
+  ENUM(Kind, Static, Dynamic, Guided, Auto, Runtime);
+  using ChunkSize = E;
+  ENUM(OrderingModifier, Monotonic, Nonmonotonic);
+  ENUM(ChunkModifier, Simd);
+  using TupleTrait = std::true_type;
+  std::tuple<Kind, OPT(OrderingModifier), OPT(ChunkModifier), OPT(ChunkSize)> t;
+};
+
+template <typename T, typename I, typename E>
+struct SeqCstT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct SeverityT {
+  ENUM(SevLevel, Fatal, Warning);
+  using WrapperTrait = std::true_type;
+  SevLevel v;
+};
+
+template <typename T, typename I, typename E>
+struct SharedT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct SimdT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct SimdlenT {
+  using Length = E;
+  using WrapperTrait = std::true_type;
+  Length v;
+};
+
+template <typename T, typename I, typename E>
+struct SizesT {
+  using SizeList = ListT<E>;
+  using WrapperTrait = std::true_type;
+  SizeList v;
+};
+
+template <typename T, typename I, typename E>
+struct TaskReductionT {
+  using List = ObjectListT<I, E>;
+  using ReductionIdentifier = type::ReductionIdentifierT<I, E>;
+  using TupleTrait = std::true_type;
+  std::tuple<ReductionIdentifier, List> t;
+};
+
+template <typename T, typename I, typename E>
+struct ThreadLimitT {
+  using Threadlim = E;
+  using WrapperTrait = std::true_type;
+  Threadlim v;
+};
+
+template <typename T, typename I, typename E>
+struct ThreadprivateT {
+  // Artificial
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ThreadsT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct ToT {
+  using LocatorList = ObjectListT<I, E>;
+  using Expectation = type::MotionExpectation;
+  using Mapper = type::MapperT<I, E>;
+  using Iterator = type::IteratorT<T, I, E>;
+
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(Expectation), OPT(Mapper), OPT(Iterator), LocatorList> t;
+};
+
+template <typename T, typename I, typename E>
+struct UnifiedAddressT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct UnifiedSharedMemoryT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct UniformT {
+  using ParameterList = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  ParameterList v;
+};
+
+template <typename T, typename I, typename E>
+struct UnknownT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct UntiedT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct UpdateT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct UseT {
+  using InteropVar = ObjectT<I, E>;
+  using WrapperTrait = std::true_type;
+  InteropVar v;
+};
+
+template <typename T, typename I, typename E>
+struct UseDeviceAddrT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct UseDevicePtrT {
+  using List = ObjectListT<I, E>;
+  using WrapperTrait = std::true_type;
+  List v;
+};
+
+template <typename T, typename I, typename E>
+struct UsesAllocatorsT {
+  using MemSpace = E;
+  using TraitsArray = ObjectT<I, E>;
+  using Allocator = E;
+  using TupleTrait = std::true_type;
+  std::tuple<OPT(MemSpace), OPT(TraitsArray), Allocator> t;
+};
+
+template <typename T, typename I, typename E>
+struct WeakT {
+  using EmptyTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct WhenT {
+  using IncompleteTrait = std::true_type;
+};
+
+template <typename T, typename I, typename E>
+struct WriteT {
+  using EmptyTrait = std::true_type;
+};
+
+// ---
+
+template <typename T, typename I, typename E>
+using ArtificialClausesT =
+    std::variant<CancellationConstructTypeT<T, I, E>, DepobjT<T, I, E>,
+                 FlushT<T, I, E>, MemoryOrderT<T, I, E>,
+                 ThreadprivateT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using EmptyClausesT = std::variant<
+    AcqRelT<T, I, E>, AcquireT<T, I, E>, CaptureT<T, I, E>, CompareT<T, I, E>,
+    DynamicAllocatorsT<T, I, E>, FullT<T, I, E>, InbranchT<T, I, E>,
+    MergeableT<T, I, E>, NogroupT<T, I, E>, NoOpenmpRoutines<T, I, E>,
+    NoOpenmp<T, I, E>, NoParallelism<T, I, E>, NotinbranchT<T, I, E>,
+    NowaitT<T, I, E>, OmpxAttributeT<T, I, E>, OmpxBareT<T, I, E>,
+    ReadT<T, I, E>, RelaxedT<T, I, E>, ReleaseT<T, I, E>,
+    ReverseOffloadT<T, I, E>, SeqCstT<T, I, E>, SimdT<T, I, E>,
+    ThreadsT<T, I, E>, UnifiedAddressT<T, I, E>, UnifiedSharedMemoryT<T, I, E>,
+    UnknownT<T, I, E>, UntiedT<T, I, E>, UpdateT<T, I, E>, UseT<T, I, E>,
+    WeakT<T, I, E>, WriteT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using IncompleteClausesT =
+    std::variant<AdjustArgsT<T, I, E>, AppendArgsT<T, I, E>, MatchT<T, I, E>,
+                 OtherwiseT<T, I, E>, WhenT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using TupleClausesT =
+    std::variant<AffinityT<T, I, E>, AlignedT<T, I, E>, AllocateT<T, I, E>,
+                 DefaultmapT<T, I, E>, DeviceT<T, I, E>, DistScheduleT<T, I, E>,
+                 DoacrossT<T, I, E>, FromT<T, I, E>, IfT<T, I, E>,
+                 InitT<T, I, E>, InReductionT<T, I, E>, LastprivateT<T, I, E>,
+                 LinearT<T, I, E>, MapT<T, I, E>, NumTasksT<T, I, E>,
+                 OrderT<T, I, E>, ReductionT<T, I, E>, ScheduleT<T, I, E>,
+                 TaskReductionT<T, I, E>, ToT<T, I, E>,
+                 UsesAllocatorsT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using UnionClausesT = std::variant<DependT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using WrapperClausesT = std::variant<
+    AbsentT<T, I, E>, AlignT<T, I, E>, AllocatorT<T, I, E>,
+    AtomicDefaultMemOrderT<T, I, E>, AtT<T, I, E>, BindT<T, I, E>,
+    CollapseT<T, I, E>, ContainsT<T, I, E>, CopyinT<T, I, E>,
+    CopyprivateT<T, I, E>, DefaultT<T, I, E>, DestroyT<T, I, E>,
+    DetachT<T, I, E>, DeviceTypeT<T, I, E>, EnterT<T, I, E>,
+    ExclusiveT<T, I, E>, FailT<T, I, E>, FilterT<T, I, E>, FinalT<T, I, E>,
+    FirstprivateT<T, I, E>, GrainsizeT<T, I, E>, HasDeviceAddrT<T, I, E>,
+    HintT<T, I, E>, HoldsT<T, I, E>, InclusiveT<T, I, E>, IndirectT<T, I, E>,
+    InitializerT<T, I, E>, IsDevicePtrT<T, I, E>, LinkT<T, I, E>,
+    MessageT<T, I, E>, NocontextT<T, I, E>, NontemporalT<T, I, E>,
+    NovariantsT<T, I, E>, NumTeamsT<T, I, E>, NumThreadsT<T, I, E>,
+    OmpxDynCgroupMemT<T, I, E>, OrderedT<T, I, E>, PartialT<T, I, E>,
+    PriorityT<T, I, E>, PrivateT<T, I, E>, ProcBindT<T, I, E>,
+    SafelenT<T, I, E>, SeverityT<T, I, E>, SharedT<T, I, E>, SimdlenT<T, I, E>,
+    SizesT<T, I, E>, ThreadLimitT<T, I, E>, UniformT<T, I, E>,
+    UseDeviceAddrT<T, I, E>, UseDevicePtrT<T, I, E>>;
+
+template <typename T, typename I, typename E>
+using UnionOfAllClausesT = typename detail::Union< //
+    ArtificialClausesT<T, I, E>,                   //
+    EmptyClausesT<T, I, E>,                        //
+    IncompleteClausesT<T, I, E>,                   //
+    TupleClausesT<T, I, E>,                        //
+    UnionClausesT<T, I, E>,                        //
+    WrapperClausesT<T, I, E>                       //
+    >::type;
+
+} // namespace clause
+
+template <typename TypeType, typename IdType, typename ExprType>
+struct ClauseT {
+  llvm::omp::Clause id; // The numeric id of the clause
+  using UnionTrait = std::true_type;
+  clause::UnionOfAllClausesT<TypeType, IdType, ExprType> u;
+};
+
+// --------------------------------------------------------------------
+template <typename TypeType, typename IdType, typename ExprType>
+struct DirectiveInfo {
+  llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
+  llvm::SmallVector<const tomp::ClauseT<TypeType, IdType, ExprType> *> clauses;
+};
+
+template <typename T, typename I, typename E>
+llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
+                              const DirectiveInfo<T, I, E> &di) {
+  os << llvm::omp::getOpenMPDirectiveName(di.id) << " -> {";
+  for (int i = 0, e = di.clauses.size(); i != e; ++i) {
+    if (i != 0)
+      os << ", ";
+    os << llvm::omp::getOpenMPClauseName(di.clauses[i]->id);
+  }
+  os << '}';
+  return os;
+}
+
+template <typename TypeType, typename IdType, typename ExprType,
+          typename HelperType>
+struct CompositeInfoBase {
+  using IdTy = IdType;
+  using ExprTy = ExprType;
+  using TypeTy = TypeType;
+  using HelperTy = HelperType;
+  using ObjectTy = tomp::ObjectT<IdTy, ExprTy>;
+  using ClauseTy = tomp::ClauseT<TypeTy, IdTy, ExprTy>;
+
+  using ClauseSet = llvm::DenseSet<const ClauseTy *>;
+
+  CompositeInfoBase(uint32_t ver, llvm::omp::Directive dir, HelperType &hlp)
+      : version(ver), construct(dir), helper(hlp) {}
+
+  void add(const ClauseTy *node) { nodes.push_back(node); }
+
+  bool split();
+
+  DirectiveInfo<TypeTy, IdTy, ExprTy> *
+  findDirective(llvm::omp::Directive dirId) {
+    for (DirectiveInfo<TypeTy, IdTy, ExprTy> &dir : leafs) {
+      if (dir.id == dirId)
+        return &dir;
+    }
+    return nullptr;
+  }
+  ClauseSet *findClauses(const ObjectTy &object) {
+    if (auto found = syms.find(object.id()); found != syms.end())
+      return &found->second;
+    return nullptr;
+  }
+
+  uint32_t version;
+  llvm::omp::Directive construct;
+
+  // Leafs are ordered outer to inner.
+  llvm::SmallVector<DirectiveInfo<TypeTy, IdTy, ExprTy>> leafs;
+
+private:
+  template <typename S>
+  ClauseTy *makeClause(llvm::omp::Clause clauseId, S &&specific) {
+    auto clause = ClauseTy{clauseId, std::move(specific)};
+    auto *addr = helper.makeClause(std::move(clause));
+    return static_cast<ClauseTy *>(addr);
+  }
+
+  void addClauseSymsToMap(const ObjectTy &object, const ClauseTy *);
+  void addClauseSymsToMap(const tomp::ObjectListT<IdTy, ExprTy> &objects,
+                          const ClauseTy *);
+  void addClauseSymsToMap(const TypeTy &item, const ClauseTy *);
+  void addClauseSymsToMap(const ExprTy &item, const ClauseTy *);
+  void addClauseSymsToMap(const tomp::clause::MapT<TypeTy, IdTy, ExprTy> &item,
+                          const ClauseTy *);
+
+  template <typename U>
+  void addClauseSymsToMap(const std::optional<U> &item, const ClauseTy *);
+  template <typename U>
+  void addClauseSymsToMap(const tomp::ListT<U> &item, const ClauseTy *);
+  template <typename... U, size_t... Is>
+  void addClauseSymsToMap(const std::tuple<U...> &item, const ClauseTy *,
+                          std::index_sequence<Is...> = {});
+  template <typename U>
+  std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  template <typename U>
+  std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  template <typename U>
+  std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  template <typename U>
+  std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  template <typename U>
+  std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  template <typename U>
+  std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void>
+  addClauseSymsToMap(U &&item, const ClauseTy *);
+
+  // Apply a clause to the only directive that allows it. If there are no
+  // directives that allow it, or if there is more that one, do not apply
+  // anything and return false, otherwise return true.
+  bool applyToUnique(const ClauseTy *node);
+
+  // Apply a clause to the first directive in given range that allows it.
+  // If such a directive does not exist, return false, otherwise return true.
+  template <typename Iterator>
+  bool applyToFirst(const ClauseTy *node, llvm::iterator_range<Iterator> range);
+
+  // Apply a clause to the innermost directive that allows it. If such a
+  // directive does not exist, return false, otherwise return true.
+  bool applyToInnermost(const ClauseTy *node);
+
+  // Apply a clause to the outermost directive that allows it. If such a
+  // directive does not exist, return false, otherwise return true.
+  bool applyToOutermost(const ClauseTy *node);
+
+  template <typename Predicate>
+  bool applyIf(const ClauseTy *node, Predicate shouldApply);
+
+  bool applyToAll(const ClauseTy *node);
+
+  template <typename Clause>
+  bool applyClause(Clause &&clause, const ClauseTy *node);
+
+  bool applyClause(const tomp::clause::CollapseT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::PrivateT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool
+  applyClause(const tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy> &clause,
+              const ClauseTy *);
+  bool
+  applyClause(const tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy> &clause,
+              const ClauseTy *);
+  bool applyClause(const tomp::clause::SharedT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::DefaultT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool
+  applyClause(const tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy> &clause,
+              const ClauseTy *);
+  bool applyClause(const tomp::clause::OrderT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::AllocateT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::ReductionT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::IfT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::LinearT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+  bool applyClause(const tomp::clause::NowaitT<TypeTy, IdTy, ExprTy> &clause,
+                   const ClauseTy *);
+
+  HelperType &helper;
+  tomp::ListT<const ClauseTy *> nodes;
+
+  llvm::DenseMap<IdTy, ClauseSet> syms;
+  llvm::DenseSet<IdTy> mapBases;
+};
+
+template <typename T, typename I, typename E, typename H>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(const ObjectTy &object,
+                                                       const ClauseTy *node) {
+  syms[object.id()].insert(node);
+}
+
+template <typename T, typename I, typename E, typename H>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(
+    const tomp::ObjectListT<I, E> &objects, const ClauseTy *node) {
+  for (auto &object : objects)
+    syms[object.id()].insert(node);
+}
+
+template <typename T, typename I, typename E, typename H>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(const T &item,
+                                                       const ClauseTy *node) {
+  // Nothing to do for types.
+}
+
+template <typename T, typename I, typename E, typename H>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(const E &item,
+                                                       const ClauseTy *node) {
+  // Nothing to do for expressions.
+}
+
+template <typename T, typename I, typename E, typename H>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(
+    const tomp::clause::MapT<T, I, E> &item, const ClauseTy *node) {
+  auto &objects = std::get<tomp::ObjectListT<I, E>>(item.t);
+  addClauseSymsToMap(objects, node);
+  for (auto &object : objects) {
+    if (auto base = helper.getBaseObject(object))
+      mapBases.insert(base->id());
+  }
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(
+    const std::optional<U> &item, const ClauseTy *node) {
+  if (item)
+    addClauseSymsToMap(*item, node);
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(
+    const tomp::ListT<U> &item, const ClauseTy *node) {
+  for (auto &s : item)
+    addClauseSymsToMap(s, node);
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename... U, size_t... Is>
+void CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(
+    const std::tuple<U...> &item, const ClauseTy *node,
+    std::index_sequence<Is...>) {
+  (void)node; // Silence strange warning from GCC.
+  (addClauseSymsToMap(std::get<Is>(item), node), ...);
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  // Nothing to do for enums.
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  // Nothing to do for an empty class.
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  // Nothing to do for an incomplete class (they're empty).
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  addClauseSymsToMap(item.v, node);
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  constexpr size_t tuple_size =
+      std::tuple_size_v<llvm::remove_cvref_t<decltype(item.t)>>;
+  addClauseSymsToMap(item.t, node, std::make_index_sequence<tuple_size>{});
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename U>
+std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void>
+CompositeInfoBase<T, I, E, H>::addClauseSymsToMap(U &&item,
+                                                  const ClauseTy *node) {
+  std::visit([&](auto &&s) { addClauseSymsToMap(s, node); }, item.u);
+}
+
+// Apply a clause to the only directive that allows it. If there are no
+// directives that allow it, or if there is more that one, do not apply
+// anything and return false, otherwise return true.
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyToUnique(const ClauseTy *node) {
+  auto unique = detail::find_unique(leafs, [=](const auto &dirInfo) {
+    return llvm::omp::isAllowedClauseForDirective(dirInfo.id, node->id,
+                                                  version);
+  });
+
+  if (unique != leafs.end()) {
+    unique->clauses.push_back(node);
+    return true;
+  }
+  return false;
+}
+
+// Apply a clause to the first directive in given range that allows it.
+// If such a directive does not exist, return false, otherwise return true.
+template <typename T, typename I, typename E, typename H>
+template <typename Iterator>
+bool CompositeInfoBase<T, I, E, H>::applyToFirst(
+    const ClauseTy *node, llvm::iterator_range<Iterator> range) {
+  if (range.empty())
+    return false;
+
+  for (DirectiveInfo<T, I, E> &dir : range) {
+    if (!llvm::omp::isAllowedClauseForDirective(dir.id, node->id, version))
+      continue;
+    dir.clauses.push_back(node);
+    return true;
+  }
+  return false;
+}
+
+// Apply a clause to the innermost directive that allows it. If such a
+// directive does not exist, return false, otherwise return true.
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyToInnermost(const ClauseTy *node) {
+  return applyToFirst(node, llvm::reverse(leafs));
+}
+
+// Apply a clause to the outermost directive that allows it. If such a
+// directive does not exist, return false, otherwise return true.
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyToOutermost(const ClauseTy *node) {
+  return applyToFirst(node, llvm::iterator_range(leafs));
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename Predicate>
+bool CompositeInfoBase<T, I, E, H>::applyIf(const ClauseTy *node,
+                                            Predicate shouldApply) {
+  bool applied = false;
+  for (DirectiveInfo<T, I, E> &dir : leafs) {
+    if (!llvm::omp::isAllowedClauseForDirective(dir.id, node->id, version))
+      continue;
+    if (!shouldApply(dir))
+      continue;
+    dir.clauses.push_back(node);
+    applied = true;
+  }
+
+  return applied;
+}
+
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyToAll(const ClauseTy *node) {
+  return applyIf(node, [](auto) { return true; });
+}
+
+template <typename T, typename I, typename E, typename H>
+template <typename Clause>
+bool CompositeInfoBase<T, I, E, H>::applyClause(Clause &&clause,
+                                                const ClauseTy *node) {
+  // The default behavior is to find the unique directive to which the
+  // given clause may be applied. If there are no such directives, or
+  // if there are multiple ones, flag an error.
+  // From "OpenMP Application Programming Interface", Version 5.2:
+  // S Some clauses are permitted only on a single leaf construct of the
+  // S combined or composite construct, in which case the effect is as if
+  // S the clause is applied to that specific construct. (p339, 31-33)
+  if (applyToUnique(node))
+    return true;
+
+  return false;
+}
+
+// COLLAPSE
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::CollapseT<T, I, E> &clause, const ClauseTy *node) {
+  // Apply COLLAPSE to the innermost directive. If it's not one that
+  // allows it flag an error.
+  if (!leafs.empty()) {
+    DirectiveInfo<T, I, E> &last = leafs.back();
+
+    if (llvm::omp::isAllowedClauseForDirective(last.id, node->id, version)) {
+      last.clauses.push_back(node);
+      return true;
+    }
+  }
+
+  // llvm::errs() << "Cannot apply COLLAPSE\n";
+  return false;
+}
+
+// PRIVATE
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::PrivateT<T, I, E> &clause, const ClauseTy *node) {
+  if (applyToInnermost(node))
+    return true;
+  // llvm::errs() << "Cannot apply PRIVATE\n";
+  return false;
+}
+
+// FIRSTPRIVATE
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::FirstprivateT<T, I, E> &clause, const ClauseTy *node) {
+  bool applied = false;
+
+  // S Section 17.2
+  // S The effect of the firstprivate clause is as if it is applied to one
+  // S or more leaf constructs as follows:
+
+  // S - To the distribute construct if it is among the constituent constructs;
+  // S - To the teams construct if it is among the constituent constructs and
+  // S   the distribute construct is not;
+  auto hasDistribute = findDirective(llvm::omp::OMPD_distribute);
+  auto hasTeams = findDirective(llvm::omp::OMPD_teams);
+  if (hasDistribute != nullptr) {
+    hasDistribute->clauses.push_back(node);
+    applied = true;
+    // S If the teams construct is among the constituent constructs and the
+    // S effect is not as if the firstprivate clause is applied to it by the
+    // S above rules, then the effect is as if the shared clause with the
+    // S same list item is applied to the teams construct.
+    if (hasTeams != nullptr) {
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/clause.v});
+      hasTeams->clauses.push_back(shared);
+    }
+  } else if (hasTeams != nullptr) {
+    hasTeams->clauses.push_back(node);
+    applied = true;
+  }
+
+  // S - To a worksharing construct that accepts the clause if one is among
+  // S   the constituent constructs;
+  auto findWorksharing = [&]() {
+    auto worksharing = getWorksharing();
+    for (DirectiveInfo<T, I, E> &dir : leafs) {
+      auto found = llvm::find(worksharing, dir.id);
+      if (found != std::end(worksharing))
+        return &dir;
+    }
+    return static_cast<DirectiveInfo<T, I, E> *>(nullptr);
+  };
+
+  auto hasWorksharing = findWorksharing();
+  if (hasWorksharing != nullptr) {
+    hasWorksharing->clauses.push_back(node);
+    applied = true;
+  }
+
+  // S - To the taskloop construct if it is among the constituent constructs;
+  auto hasTaskloop = findDirective(llvm::omp::OMPD_taskloop);
+  if (hasTaskloop != nullptr) {
+    hasTaskloop->clauses.push_back(node);
+    applied = true;
+  }
+
+  // S - To the parallel construct if it is among the constituent constructs
+  // S   and neither a taskloop construct nor a worksharing construct that
+  // S   accepts the clause is among them;
+  auto hasParallel = findDirective(llvm::omp::OMPD_parallel);
+  if (hasParallel != nullptr) {
+    if (hasTaskloop == nullptr && hasWorksharing == nullptr) {
+      hasParallel->clauses.push_back(node);
+      applied = true;
+    } else {
+      // S If the parallel construct is among the constituent constructs and
+      // S the effect is not as if the firstprivate clause is applied to it by
+      // S the above rules, then the effect is as if the shared clause with
+      // S the same list item is applied to the parallel construct.
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/clause.v});
+      hasParallel->clauses.push_back(shared);
+    }
+  }
+
+  // S - To the target construct if it is among the constituent constructs
+  // S   and the same list item neither appears in a lastprivate clause nor
+  // S   is the base variable or base pointer of a list item that appears in
+  // S   a map clause.
+  auto inLastprivate = [&](const ObjectTy &object) {
+    if (ClauseSet *set = findClauses(object)) {
+      return llvm::find_if(*set, [](const ClauseTy *c) {
+               return c->id == llvm::omp::Clause::OMPC_lastprivate;
+             }) != set->end();
+    }
+    return false;
+  };
+
+  auto hasTarget = findDirective(llvm::omp::OMPD_target);
+  if (hasTarget != nullptr) {
+    tomp::ObjectListT<I, E> objects;
+    llvm::copy_if(
+        clause.v, std::back_inserter(objects), [&](const ObjectTy &object) {
+          return !inLastprivate(object) && !mapBases.contains(object.id());
+        });
+    if (!objects.empty()) {
+      auto *firstp =
+          makeClause(llvm::omp::Clause::OMPC_firstprivate,
+                     tomp::clause::FirstprivateT<T, I, E>{/*List=*/objects});
+      hasTarget->clauses.push_back(firstp);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// LASTPRIVATE
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::LastprivateT<T, I, E> &clause, const ClauseTy *node) {
+  bool applied = false;
+
+  // S The effect of the lastprivate clause is as if it is applied to all leaf
+  // S constructs that permit the clause.
+  if (!applyToAll(node)) {
+    // llvm::errs() << "Cannot apply LASTPRIVATE\n";
+    return false;
+  }
+
+  auto inFirstprivate = [&](const ObjectTy &object) {
+    if (ClauseSet *set = findClauses(object)) {
+      return llvm::find_if(*set, [](const ClauseTy *c) {
+               return c->id == llvm::omp::Clause::OMPC_firstprivate;
+             }) != set->end();
+    }
+    return false;
+  };
+
+  auto &objects = std::get<tomp::ObjectListT<I, E>>(clause.t);
+
+  // Prepare list of objects that could end up in a SHARED clause.
+  tomp::ObjectListT<I, E> sharedObjects;
+  llvm::copy_if(
+      objects, std::back_inserter(sharedObjects),
+      [&](const ObjectTy &object) { return !inFirstprivate(object); });
+
+  if (!sharedObjects.empty()) {
+    // S If the parallel construct is among the constituent constructs and the
+    // S list item is not also specified in the firstprivate clause, then the
+    // S effect of the lastprivate clause is as if the shared clause with the
+    // S same list item is applied to the parallel construct.
+    if (auto hasParallel = findDirective(llvm::omp::OMPD_parallel)) {
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/sharedObjects});
+      hasParallel->clauses.push_back(shared);
+      applied = true;
+    }
+
+    // S If the teams construct is among the constituent constructs and the
+    // S list item is not also specified in the firstprivate clause, then the
+    // S effect of the lastprivate clause is as if the shared clause with the
+    // S same list item is applied to the teams construct.
+    if (auto hasTeams = findDirective(llvm::omp::OMPD_teams)) {
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/sharedObjects});
+      hasTeams->clauses.push_back(shared);
+      applied = true;
+    }
+  }
+
+  // S If the target construct is among the constituent constructs and the
+  // S list item is not the base variable or base pointer of a list item that
+  // S appears in a map clause, the effect of the lastprivate clause is as if
+  // S the same list item appears in a map clause with a map-type of tofrom.
+  if (auto hasTarget = findDirective(llvm::omp::OMPD_target)) {
+    tomp::ObjectListT<I, E> tofrom;
+    llvm::copy_if(objects, std::back_inserter(tofrom),
+                  [&](const ObjectTy &object) {
+                    return !mapBases.contains(object.id());
+                  });
+
+    if (!tofrom.empty()) {
+      using MapType = typename tomp::clause::MapT<T, I, E>::MapType;
+      auto *map =
+          makeClause(llvm::omp::Clause::OMPC_map,
+                     tomp::clause::MapT<T, I, E>{
+                         {/*MapType=*/MapType::Tofrom,
+                          /*MapTypeModifier=*/std::nullopt,
+                          /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
+                          /*LocatorList=*/std::move(tofrom)}});
+      hasTarget->clauses.push_back(map);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// SHARED
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::SharedT<T, I, E> &clause, const ClauseTy *node) {
+  // Apply SHARED to the all leafs that allow it.
+  if (applyToAll(node))
+    return true;
+  // llvm::errs() << "Cannot apply SHARED\n";
+  return false;
+}
+
+// DEFAULT
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::DefaultT<T, I, E> &clause, const ClauseTy *node) {
+  // Apply DEFAULT to the all leafs that allow it.
+  if (applyToAll(node))
+    return true;
+  // llvm::errs() << "Cannot apply DEFAULT\n";
+  return false;
+}
+
+// THREAD_LIMIT
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::ThreadLimitT<T, I, E> &clause, const ClauseTy *node) {
+  // Apply THREAD_LIMIT to the all leafs that allow it.
+  if (applyToAll(node))
+    return true;
+  // llvm::errs() << "Cannot apply THREAD_LIMIT\n";
+  return false;
+}
+
+// ORDER
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::OrderT<T, I, E> &clause, const ClauseTy *node) {
+  // Apply ORDER to the all leafs that allow it.
+  if (applyToAll(node))
+    return true;
+  // llvm::errs() << "Cannot apply ORDER\n";
+  return false;
+}
+
+// ALLOCATE
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::AllocateT<T, I, E> &clause, const ClauseTy *node) {
+  // This one needs to be applied at the end, once we know which clauses are
+  // assigned to which leaf constructs.
+
+  // S The effect of the allocate clause is as if it is applied to all leaf
+  // S constructs that permit the clause and to which a data-sharing attribute
+  // S clause that may create a private copy of the same list item is applied.
+
+  auto canMakePrivateCopy = [](llvm::omp::Clause id) {
+    switch (id) {
+    case llvm::omp::Clause::OMPC_firstprivate:
+    case llvm::omp::Clause::OMPC_lastprivate:
+    case llvm::omp::Clause::OMPC_private:
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  bool applied = applyIf(node, [&](const DirectiveInfo<T, I, E> &dir) {
+    return llvm::any_of(dir.clauses, [&](const ClauseTy *n) {
+      return canMakePrivateCopy(n->id);
+    });
+  });
+
+  return applied;
+}
+
+// REDUCTION
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::ReductionT<T, I, E> &clause, const ClauseTy *node) {
+  // S The effect of the reduction clause is as if it is applied to all leaf
+  // S constructs that permit the clause, except for the following constructs:
+  // S - The parallel construct, when combined with the sections, worksharing-
+  // S   loop, loop, or taskloop construct; and
+  // S - The teams construct, when combined with the loop construct.
+  bool applyToParallel = true, applyToTeams = true;
+
+  auto hasParallel = findDirective(llvm::omp::Directive::OMPD_parallel);
+  if (hasParallel) {
+    auto exclusions = llvm::concat<const llvm::omp::Directive>(
+        getWorksharingLoop(), llvm::ArrayRef{
+                                  llvm::omp::Directive::OMPD_loop,
+                                  llvm::omp::Directive::OMPD_sections,
+                                  llvm::omp::Directive::OMPD_taskloop,
+                              });
+    auto present = [&](llvm::omp::Directive id) {
+      return findDirective(id) != nullptr;
+    };
+
+    if (llvm::any_of(exclusions, present))
+      applyToParallel = false;
+  }
+
+  auto hasTeams = findDirective(llvm::omp::Directive::OMPD_teams);
+  if (hasTeams) {
+    // The only exclusion is OMPD_loop.
+    if (findDirective(llvm::omp::Directive::OMPD_loop))
+      applyToTeams = false;
+  }
+
+  auto &objects = std::get<tomp::ObjectListT<I, E>>(clause.t);
+
+  tomp::ObjectListT<I, E> sharedObjects;
+  llvm::transform(objects, std::back_inserter(sharedObjects),
+                  [&](const ObjectTy &object) {
+                    auto maybeBase = helper.getBaseObject(object);
+                    return maybeBase ? *maybeBase : object;
+                  });
+
+  // S For the parallel and teams constructs above, the effect of the
+  // S reduction clause instead is as if each list item or, for any list
+  // S item that is an array item, its corresponding base array or base
+  // S pointer appears in a shared clause for the construct.
+  if (!sharedObjects.empty()) {
+    if (hasParallel && !applyToParallel) {
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/sharedObjects});
+      hasParallel->clauses.push_back(shared);
+    }
+    if (hasTeams && !applyToTeams) {
+      auto *shared =
+          makeClause(llvm::omp::Clause::OMPC_shared,
+                     tomp::clause::SharedT<T, I, E>{/*List=*/sharedObjects});
+      hasTeams->clauses.push_back(shared);
+    }
+  }
+
+  // TODO(not implemented in parser yet): Apply the following.
+  // S If the task reduction-modifier is specified, the effect is as if
+  // S it only modifies the behavior of the reduction clause on the innermost
+  // S leaf construct that accepts the modifier (see Section 5.5.8). If the
+  // S inscan reduction-modifier is specified, the effect is as if it modifies
+  // S the behavior of the reduction clause on all constructs of the combined
+  // S construct to which the clause is applied and that accept the modifier.
+
+  bool applied = applyIf(node, [&](DirectiveInfo<T, I, E> &dir) {
+    if (!applyToParallel && &dir == hasParallel)
+      return false;
+    if (!applyToTeams && &dir == hasTeams)
+      return false;
+    return true;
+  });
+
+  // S If a list item in a reduction clause on a combined target construct
+  // S does not have the same base variable or base pointer as a list item
+  // S in a map clause on the construct, then the effect is as if the list
+  // S item in the reduction clause appears as a list item in a map clause
+  // S with a map-type of tofrom.
+  auto hasTarget = findDirective(llvm::omp::Directive::OMPD_target);
+  if (hasTarget && leafs.size() > 1) {
+    tomp::ObjectListT<I, E> tofrom;
+    llvm::copy_if(objects, std::back_inserter(tofrom),
+                  [&](const ObjectTy &object) {
+                    if (auto maybeBase = helper.getBaseObject(object))
+                      return !mapBases.contains(maybeBase->id());
+                    return !mapBases.contains(object.id()); // XXX is this ok?
+                  });
+    if (!tofrom.empty()) {
+      using MapType = typename tomp::clause::MapT<T, I, E>::MapType;
+      auto *map = makeClause(
+          llvm::omp::Clause::OMPC_map,
+          tomp::clause::MapT<T, I, E>{
+              {/*MapType=*/MapType::Tofrom, /*MapTypeModifier=*/std::nullopt,
+               /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
+               /*LocatorList=*/std::move(tofrom)}});
+
+      hasTarget->clauses.push_back(map);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// IF
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::IfT<T, I, E> &clause, const ClauseTy *node) {
+  using DirectiveNameModifier =
+      typename clause::IfT<T, I, T>::DirectiveNameModifier;
+  auto &modifier = std::get<std::optional<DirectiveNameModifier>>(clause.t);
+
+  if (modifier) {
+    llvm::omp::Directive dirId = llvm::omp::Directive::OMPD_unknown;
+
+    switch (*modifier) {
+    case llvm::omp::Directive::OMPD_parallel:
+    case llvm::omp::Directive::OMPD_simd:
+    case llvm::omp::Directive::OMPD_target:
+    case llvm::omp::Directive::OMPD_task:
+    case llvm::omp::Directive::OMPD_taskloop:
+    case llvm::omp::Directive::OMPD_teams:
+      break;
+
+    default:
+      // llvm::errs() << "Invalid modifier in IF clause\n";
+      return false;
+    }
+
+    if (auto *hasDir = findDirective(dirId)) {
+      hasDir->clauses.push_back(node);
+      return true;
+    }
+    // llvm::errs() << "Directive from modifier not found\n";
+    return false;
+  }
+
+  if (applyToAll(node))
+    return true;
+
+  // llvm::errs() << "Cannot apply IF\n";
+  return false;
+}
+
+// LINEAR
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::LinearT<T, I, E> &clause, const ClauseTy *node) {
+  // S The effect of the linear clause is as if it is applied to the innermost
+  // S leaf construct.
+  if (applyToInnermost(node)) {
+    // llvm::errs() << "Cannot apply LINEAR\n";
+    return false;
+  }
+
+  // The rest is about SIMD.
+  if (!findDirective(llvm::omp::OMPD_simd))
+    return true;
+
+  // S Additionally, if the list item is not the iteration variable of a
+  // S simd or worksharing-loop SIMD construct, the effect on the outer leaf
+  // S constructs is as if the list item was specified in firstprivate and
+  // S lastprivate clauses on the combined or composite construct, [...]
+  //
+  // S If a list item of the linear clause is the iteration variable of a
+  // S simd or worksharing-loop SIMD construct and it is not declared in
+  // S the construct, the effect on the outer leaf constructs is as if the
+  // S list item was specified in a lastprivate clause on the combined or
+  // S composite construct [...]
+
+  // It's not clear how an object can be listed in a clause AND be the
+  // iteration variable of a construct in which is it declared. If an
+  // object is declared in the construct, then the declaration is located
+  // after the clause listing it.
+
+  std::optional<ObjectTy> iterVar = helper.getIterVar();
+  const auto &objects = std::get<tomp::ObjectListT<I, E>>(clause.t);
+
+  // Lists of objects that will be used to construct FIRSTPRIVATE and
+  // LASTPRIVATE clauses.
+  tomp::ObjectListT<I, E> first, last;
+
+  for (const ObjectTy &object : objects) {
+    last.push_back(object);
+    if (iterVar && object.id() != iterVar->id())
+      first.push_back(object);
+  }
+
+  if (!first.empty()) {
+    auto *firstp =
+        makeClause(llvm::omp::Clause::OMPC_firstprivate,
+                   tomp::clause::FirstprivateT<T, I, E>{/*List=*/first});
+    add(firstp); // Appending to the main clause list.
+  }
+  if (!last.empty()) {
+    auto *lastp =
+        makeClause(llvm::omp::Clause::OMPC_lastprivate,
+                   tomp::clause::LastprivateT<T, I, E>{
+                       {/*LastprivateModifier=*/std::nullopt, /*List=*/last}});
+    add(lastp); // Appending to the main clause list.
+  }
+  return true;
+}
+
+// NOWAIT
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::applyClause(
+    const tomp::clause::NowaitT<T, I, E> &clause, const ClauseTy *node) {
+  if (applyToOutermost(node))
+    return true;
+  // llvm::errs() << "Cannot apply NOWAIT\n";
+  return false;
+}
+
+template <typename T, typename I, typename E, typename H>
+bool CompositeInfoBase<T, I, E, H>::split() {
+  bool success = true;
+
+  for (llvm::omp::Directive leaf : llvm::omp::getLeafConstructs(construct))
+    leafs.push_back(DirectiveInfo<T, I, E>{leaf, /*clauses=*/{}});
+
+  for (const ClauseTy *node : nodes)
+    addClauseSymsToMap(*node, node);
+
+  // First we need to apply LINEAR, because it can generate additional
+  // FIRSTPRIVATE and LASTPRIVATE clauses that apply to the combined/
+  // composite construct.
+  // Collect them separately, because they may modify the clause list.
+  llvm::SmallVector<const ClauseTy *> linears;
+  for (const ClauseTy *node : nodes) {
+    if (node->id == llvm::omp::Clause::OMPC_linear)
+      linears.push_back(node);
+  }
+  for (const auto *node : linears) {
+    success =
+        success &&
+        applyClause(std::get<tomp::clause::LinearT<T, I, E>>(node->u), node);
+  }
+
+  // ALLOCATE clauses need to be applied last since they need to see
+  // which directives have data-privatizing clauses.
+  auto skip = [](const ClauseTy *node) {
+    switch (node->id) {
+    case llvm::omp::Clause::OMPC_allocate:
+    case llvm::omp::Clause::OMPC_linear:
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  // Apply (almost) all clauses.
+  for (const ClauseTy *node : nodes) {
+    if (skip(node))
+      continue;
+    success =
+        success &&
+        std::visit([&](auto &&s) { return applyClause(s, node); }, node->u);
+  }
+
+  // Apply ALLOCATE.
+  for (const ClauseTy *node : nodes) {
+    if (node->id != llvm::omp::Clause::OMPC_allocate)
+      continue;
+    success =
+        success &&
+        std::visit([&](auto &&s) { return applyClause(s, node); }, node->u);
+  }
+
+  return success;
+}
+
+} // namespace tomp
+
+#endif // FORTRAN_LOWER_OPENMP_CLAUSET_H



More information about the llvm-branch-commits mailing list