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

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Feb 21 07:02:12 PST 2024


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

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.

[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.

getOpenMPVersion

Split function complete

Some TODOs still remain.

>From 640a889119e77e311f005b8250de5069ac146c01 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] [flang][OpenMP] Main splitting functionality dev-complete

The splitting code is now separated into its own file.

[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.

[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.

getOpenMPVersion

Split function complete

Some TODOs still remain.
---
 flang/lib/Lower/OpenMP.cpp                    |  740 ++++----
 flang/lib/Lower/OpenMPClauses.h               | 1663 +++++++++++++++++
 flang/lib/Parser/CMakeLists.txt               |    1 +
 flang/tools/bbc/bbc.cpp                       |    2 +-
 .../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 +
 8 files changed, 2118 insertions(+), 433 deletions(-)
 create mode 100644 flang/lib/Lower/OpenMPClauses.h

diff --git a/flang/lib/Lower/OpenMP.cpp b/flang/lib/Lower/OpenMP.cpp
index a179bb178c8d75..28ade94e8dca23 100644
--- a/flang/lib/Lower/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP.cpp
@@ -31,10 +31,13 @@
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
 #include "mlir/Dialect/SCF/IR/SCF.h"
 #include "mlir/Transforms/RegionUtils.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Frontend/OpenMP/OMPConstants.h"
 #include "llvm/Support/CommandLine.h"
 
+#include "OpenMPClauses.h"
+
 static llvm::cl::opt<bool> treatIndexAsSection(
     "openmp-treat-index-as-section",
     llvm::cl::desc("In the OpenMP data clauses treat `a(N)` as `a(N:N)`."),
@@ -48,6 +51,12 @@ using DeclareTargetCapturePair =
 // Common helper functions
 //===----------------------------------------------------------------------===//
 
+static uint32_t getOpenMPVersion(const mlir::ModuleOp &mod) {
+  if (mlir::Attribute verAttr = mod->getAttr("omp.version"))
+    return llvm::cast<mlir::omp::VersionAttr>(verAttr).getVersion();
+  llvm_unreachable("Expecting OpenMP version attribute in module");
+}
+
 static Fortran::semantics::Symbol *
 getOmpObjectSymbol(const Fortran::parser::OmpObject &ompObject) {
   Fortran::semantics::Symbol *sym = nullptr;
@@ -121,12 +130,33 @@ static void genNestedEvaluations(Fortran::lower::AbstractConverter &converter,
     converter.genEval(e);
 }
 
+static 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);
+      },
+  });
+}
+
 //===----------------------------------------------------------------------===//
 // Clauses
 //===----------------------------------------------------------------------===//
 
 namespace detail {
-template <typename C> //
+template <typename C>
 llvm::omp::Clause getClauseIdForClass(C &&) {
   using namespace Fortran;
   using A = llvm::remove_cvref_t<C>; // A is referenced in OMP.inc
@@ -151,46 +181,67 @@ using SomeType = evaluate::SomeType;
 using SomeExpr = semantics::SomeExpr;
 using MaybeExpr = semantics::MaybeExpr;
 
-template <typename T> //
-using List = std::vector<T>;
+using SymIdent = semantics::Symbol *;
+using SymReference = SomeExpr;
+
+template <typename T>
+using List = tomp::ListT<T>;
+} // namespace omp
 
+namespace tomp {
+template <>
+struct ObjectT<omp::SymIdent, omp::SymReference> {
+  using IdType = omp::SymIdent;
+  using ExprType = omp::SymReference;
+
+  const IdType &id() const { return sym_; }
+  const std::optional<ExprType> &ref() const { return dsg_; }
+
+  IdType sym_;
+  std::optional<ExprType> dsg_;
+};
+} // namespace tomp
+
+namespace omp {
 struct SymDsgExtractor {
   using SymDsg = std::tuple<semantics::Symbol *, MaybeExpr>;
 
-  template <typename T> //
+  template <typename T>
   static T &&AsRvalueRef(T &&t) {
     return std::move(t);
   }
-  template <typename T> //
+  template <typename T>
   static T AsRvalueRef(const T &t) {
     return t;
   }
 
-  template <typename T> //
+  static semantics::Symbol *symbol_addr(const evaluate::SymbolRef &ref) {
+    // Symbols cannot be created after semantic checks, so all symbol
+    // pointers that are non-null must point to one of those pre-existing
+    // objects. Throughout the code, symbols are often pointed to by
+    // non-const pointers, so there is no harm in casting the constness
+    // away.
+    return const_cast<semantics::Symbol *>(&ref.get());
+  }
+
+  template <typename T>
   static SymDsg visit(T &&) {
     // Use this to see missing overloads:
     // llvm::errs() << "NULL: " << __PRETTY_FUNCTION__ << '\n';
     return SymDsg{};
   }
 
-  template <typename T> //
+  template <typename T>
   static SymDsg visit(const evaluate::Designator<T> &e) {
-    // Symbols cannot be created after semantic checks, so all symbol
-    // pointers that are non-null must point to one of those pre-existing
-    // objects. Throughout the code, symbols are often pointed to by
-    // non-const pointers, so there is no harm in casting the constness
-    // away.
-    return std::make_tuple(const_cast<semantics::Symbol *>(e.GetLastSymbol()),
+    return std::make_tuple(symbol_addr(*e.GetLastSymbol()),
                            evaluate::AsGenericExpr(AsRvalueRef(e)));
   }
 
   static SymDsg visit(const evaluate::ProcedureDesignator &e) {
-    // See comment above regarding const_cast.
-    return std::make_tuple(const_cast<semantics::Symbol *>(e.GetSymbol()),
-                           std::nullopt);
+    return std::make_tuple(symbol_addr(*e.GetSymbol()), std::nullopt);
   }
 
-  template <typename T> //
+  template <typename T>
   static SymDsg visit(const evaluate::Expr<T> &e) {
     return std::visit([](auto &&s) { return visit(s); }, e.u);
   }
@@ -223,12 +274,8 @@ SymDsgExtractor::SymDsg getSymbolAndDesignator(const MaybeExpr &expr) {
                     expr->u);
 }
 
-struct Object {
-  semantics::Symbol *sym; // symbol
-  MaybeExpr dsg;          // designator ending with symbol
-};
-
-using ObjectList = List<Object>;
+using Object = tomp::ObjectT<SymIdent, SymReference>;
+using ObjectList = tomp::ObjectListT<SymIdent, SymReference>;
 
 Object makeObject(const parser::OmpObject &object,
                   semantics::SemanticsContext &semaCtx) {
@@ -286,8 +333,8 @@ auto makeExpr(semantics::SemanticsContext &semaCtx) {
 template <typename C, typename F,
           typename E = typename llvm::remove_cvref_t<C>::value_type,
           typename R = std::invoke_result_t<F, E>>
-List<R> makeList(C &&container, F &&func) {
-  List<R> v;
+omp::List<R> makeList(C &&container, F &&func) {
+  omp::List<R> v;
   llvm::transform(container, std::back_inserter(v), func);
   return v;
 }
@@ -297,7 +344,7 @@ ObjectList makeList(const parser::OmpObjectList &objects,
   return makeList(objects.v, makeObject(semaCtx));
 }
 
-template <typename U, typename T> //
+template <typename U, typename T>
 U enum_cast(T t) {
   using BareT = llvm::remove_cvref_t<T>;
   using BareU = llvm::remove_cvref_t<U>;
@@ -313,38 +360,70 @@ std::optional<U> maybeApply(F &&func, const std::optional<T> &inp) {
   return std::move(func(*inp));
 }
 
+std::optional<Object>
+getBaseObject(const Object &object,
+              Fortran::semantics::SemanticsContext &semaCtx) {
+  // If it's just the symbol, then there is no base.
+  if (!object.id())
+    return std::nullopt;
+
+  auto maybeRef = evaluate::ExtractDataRef(*object.ref());
+  if (!maybeRef)
+    return std::nullopt;
+
+  evaluate::DataRef ref = *maybeRef;
+
+  if (std::get_if<evaluate::SymbolRef>(&ref.u)) {
+    return std::nullopt;
+  } else if (auto *comp = std::get_if<evaluate::Component>(&ref.u)) {
+    const evaluate::DataRef &base = comp->base();
+    return Object{SymDsgExtractor::symbol_addr(base.GetLastSymbol()),
+                  evaluate::AsGenericExpr(SymDsgExtractor::AsRvalueRef(base))};
+  } else if (auto *arr = std::get_if<evaluate::ArrayRef>(&ref.u)) {
+    const evaluate::NamedEntity &base = arr->base();
+    evaluate::ExpressionAnalyzer ea{semaCtx};
+    if (auto *comp = base.UnwrapComponent()) {
+      return Object{
+          SymDsgExtractor::symbol_addr(comp->symbol()),
+          ea.Designate(evaluate::DataRef{SymDsgExtractor::AsRvalueRef(*comp)})};
+    } else if (base.UnwrapSymbolRef()) {
+      return std::nullopt;
+    }
+  } else {
+    assert(std::holds_alternative<evaluate::CoarrayRef>(ref.u));
+    llvm_unreachable("Coarray reference not supported at the moment");
+  }
+  return std::nullopt;
+}
+
 namespace clause {
 #ifdef EMPTY_CLASS
 #undef EMPTY_CLASS
 #endif
 #define EMPTY_CLASS(cls)                                                       \
-  struct cls {                                                                 \
-    using EmptyTrait = std::true_type;                                         \
-  };                                                                           \
+  using cls = tomp::clause::cls##T<SymIdent, SymReference>;                    \
   cls make(const parser::OmpClause::cls &, semantics::SemanticsContext &) {    \
     return cls{};                                                              \
-  }
+  }                                                                            \
+  [[maybe_unused]] extern int xyzzy_semicolon_absorber
 
 #ifdef WRAPPER_CLASS
 #undef WRAPPER_CLASS
 #endif
-#define WRAPPER_CLASS(cls, content) // Nothing
+#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
 
 // Helper objects
 
-struct DefinedOperator {
-  struct DefinedOpName {
-    using WrapperTrait = std::true_type;
-    Object v;
-  };
-  ENUM_CLASS(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;
-};
+using DefinedOperator = tomp::clause::DefinedOperatorT<SymIdent, SymReference>;
+using ProcedureDesignator =
+    tomp::clause::ProcedureDesignatorT<SymIdent, SymReference>;
+using ReductionOperator =
+    tomp::clause::ReductionOperatorT<SymIdent, SymReference>;
 
 DefinedOperator makeDefOp(const parser::DefinedOperator &inp,
                           semantics::SemanticsContext &semaCtx) {
@@ -363,11 +442,6 @@ DefinedOperator makeDefOp(const parser::DefinedOperator &inp,
   };
 }
 
-struct ProcedureDesignator {
-  using WrapperTrait = std::true_type;
-  Object v;
-};
-
 ProcedureDesignator makeProcDsg(const parser::ProcedureDesignator &inp,
                                 semantics::SemanticsContext &semaCtx) {
   return ProcedureDesignator{std::visit(
@@ -380,11 +454,6 @@ ProcedureDesignator makeProcDsg(const parser::ProcedureDesignator &inp,
       inp.u)};
 }
 
-struct ReductionOperator {
-  using UnionTrait = std::true_type;
-  std::variant<DefinedOperator, ProcedureDesignator> u;
-};
-
 ReductionOperator makeRedOp(const parser::OmpReductionOperator &inp,
                             semantics::SemanticsContext &semaCtx) {
   return std::visit(common::visitors{
@@ -399,11 +468,61 @@ ReductionOperator makeRedOp(const parser::OmpReductionOperator &inp,
 }
 
 // Actual clauses. Each T (where OmpClause::T exists) has its "make".
-
-struct Aligned {
-  using TupleTrait = std::true_type;
-  std::tuple<ObjectList, MaybeExpr> t;
-};
+using Aligned = tomp::clause::AlignedT<SymIdent, SymReference>;
+using Allocate = tomp::clause::AllocateT<SymIdent, SymReference>;
+using Allocator = tomp::clause::AllocatorT<SymIdent, SymReference>;
+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>;
+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>;
 
 Aligned make(const parser::OmpClause::Aligned &inp,
              semantics::SemanticsContext &semaCtx) {
@@ -417,27 +536,6 @@ Aligned make(const parser::OmpClause::Aligned &inp,
   }};
 }
 
-struct Allocate {
-  struct Modifier {
-    struct Allocator {
-      using WrapperTrait = std::true_type;
-      SomeExpr v;
-    };
-    struct Align {
-      using WrapperTrait = std::true_type;
-      SomeExpr 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>, ObjectList> t;
-};
-
 Allocate make(const parser::OmpClause::Allocate &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpAllocateClause
@@ -477,69 +575,37 @@ Allocate make(const parser::OmpClause::Allocate &inp,
   return Allocate{{maybeApply(convert, t0), makeList(t1, semaCtx)}};
 }
 
-struct Allocator {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Allocator make(const parser::OmpClause::Allocator &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return Allocator{makeExpr(inp.v, semaCtx)};
 }
 
-struct AtomicDefaultMemOrder {
-  using WrapperTrait = std::true_type;
-  common::OmpAtomicDefaultMemOrderType v;
-};
-
 AtomicDefaultMemOrder make(const parser::OmpClause::AtomicDefaultMemOrder &inp,
                            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpAtomicDefaultMemOrderClause
-  return AtomicDefaultMemOrder{inp.v.v};
+  return AtomicDefaultMemOrder{
+      enum_cast<AtomicDefaultMemOrder::OmpAtomicDefaultMemOrderType>(inp.v.v)};
 }
 
-struct Collapse {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Collapse make(const parser::OmpClause::Collapse &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
   return Collapse{makeExpr(inp.v, semaCtx)};
 }
 
-struct Copyin {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Copyin make(const parser::OmpClause::Copyin &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Copyin{makeList(inp.v, semaCtx)};
 }
 
-struct Copyprivate {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Copyprivate make(const parser::OmpClause::Copyprivate &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Copyprivate{makeList(inp.v, semaCtx)};
 }
 
-struct Defaultmap {
-  ENUM_CLASS(ImplicitBehavior, Alloc, To, From, Tofrom, Firstprivate, None,
-             Default)
-  ENUM_CLASS(VariableCategory, Scalar, Aggregate, Allocatable, Pointer)
-  using TupleTrait = std::true_type;
-  std::tuple<ImplicitBehavior, std::optional<VariableCategory>> t;
-};
-
 Defaultmap make(const parser::OmpClause::Defaultmap &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDefaultmapClause
@@ -554,37 +620,12 @@ Defaultmap make(const parser::OmpClause::Defaultmap &inp,
   return Defaultmap{{v0, maybeApply(convert, t1)}};
 }
 
-struct Default {
-  ENUM_CLASS(Type, Private, Firstprivate, Shared, None)
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
 Default make(const parser::OmpClause::Default &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDefaultClause
   return Default{enum_cast<Default::Type>(inp.v.v)};
 }
 
-struct Depend {
-  struct Source {
-    using EmptyTrait = std::true_type;
-  };
-  struct Sink {
-    using Length = std::tuple<DefinedOperator, SomeExpr>;
-    using Vec = std::tuple<Object, std::optional<Length>>;
-    using WrapperTrait = std::true_type;
-    List<Vec> v;
-  };
-  ENUM_CLASS(Type, In, Out, Inout, Source, Sink)
-  struct InOut {
-    using TupleTrait = std::true_type;
-    std::tuple<Type, ObjectList> t;
-  };
-  using UnionTrait = std::true_type;
-  std::variant<Source, Sink, InOut> u;
-};
-
 Depend make(const parser::OmpClause::Depend &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDependClause
@@ -622,12 +663,6 @@ Depend make(const parser::OmpClause::Depend &inp,
       inp.v.u);
 }
 
-struct Device {
-  ENUM_CLASS(DeviceModifier, Ancestor, Device_Num)
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<DeviceModifier>, SomeExpr> t;
-};
-
 Device make(const parser::OmpClause::Device &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDeviceClause
@@ -641,125 +676,66 @@ Device make(const parser::OmpClause::Device &inp,
   return Device{{maybeApply(convert, t0), makeExpr(t1, semaCtx)}};
 }
 
-struct DeviceType {
-  ENUM_CLASS(Type, Any, Host, Nohost)
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
 DeviceType make(const parser::OmpClause::DeviceType &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpDeviceTypeClause
   return DeviceType{enum_cast<DeviceType::Type>(inp.v.v)};
 }
 
-struct DistSchedule {
-  using WrapperTrait = std::true_type;
-  MaybeExpr v;
-};
-
 DistSchedule make(const parser::OmpClause::DistSchedule &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::optional<parser::ScalarIntExpr>
   return DistSchedule{maybeApply(makeExpr(semaCtx), inp.v)};
 }
 
-struct Enter {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Enter make(const parser::OmpClause::Enter &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Enter{makeList(inp.v, semaCtx)};
 }
 
-struct Filter {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Filter make(const parser::OmpClause::Filter &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return Filter{makeExpr(inp.v, semaCtx)};
 }
 
-struct Final {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Final make(const parser::OmpClause::Final &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
   return Final{makeExpr(inp.v, semaCtx)};
 }
 
-struct Firstprivate {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Firstprivate make(const parser::OmpClause::Firstprivate &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Firstprivate{makeList(inp.v, semaCtx)};
 }
 
-struct From {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 From make(const parser::OmpClause::From &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return From{makeList(inp.v, semaCtx)};
 }
 
-struct Grainsize {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Grainsize make(const parser::OmpClause::Grainsize &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return Grainsize{makeExpr(inp.v, semaCtx)};
 }
 
-struct HasDeviceAddr {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 HasDeviceAddr make(const parser::OmpClause::HasDeviceAddr &inp,
                    semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return HasDeviceAddr{makeList(inp.v, semaCtx)};
 }
 
-struct Hint {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Hint make(const parser::OmpClause::Hint &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ConstantExpr
   return Hint{makeExpr(inp.v, semaCtx)};
 }
 
-struct If {
-  ENUM_CLASS(DirectiveNameModifier, Parallel, Simd, Target, TargetData,
-             TargetEnterData, TargetExitData, TargetUpdate, Task, Taskloop,
-             Teams)
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<DirectiveNameModifier>, SomeExpr> t;
-};
-
 If make(const parser::OmpClause::If &inp,
         semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpIfClause
@@ -773,11 +749,6 @@ If make(const parser::OmpClause::If &inp,
   return If{{maybeApply(convert, t0), makeExpr(t1, semaCtx)}};
 }
 
-struct InReduction {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperator, ObjectList> t;
-};
-
 InReduction make(const parser::OmpClause::InReduction &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpInReductionClause
@@ -786,38 +757,18 @@ InReduction make(const parser::OmpClause::InReduction &inp,
   return InReduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
 }
 
-struct IsDevicePtr {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 IsDevicePtr make(const parser::OmpClause::IsDevicePtr &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return IsDevicePtr{makeList(inp.v, semaCtx)};
 }
 
-struct Lastprivate {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Lastprivate make(const parser::OmpClause::Lastprivate &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Lastprivate{makeList(inp.v, semaCtx)};
 }
 
-struct Linear {
-  struct Modifier {
-    ENUM_CLASS(Type, Ref, Val, Uval)
-    using WrapperTrait = std::true_type;
-    Type v;
-  };
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<Modifier>, ObjectList, MaybeExpr> t;
-};
-
 Linear make(const parser::OmpClause::Linear &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpLinearClause
@@ -839,30 +790,12 @@ Linear make(const parser::OmpClause::Linear &inp,
       inp.v.u);
 }
 
-struct Link {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Link make(const parser::OmpClause::Link &inp,
           semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Link{makeList(inp.v, semaCtx)};
 }
 
-struct Map {
-  struct MapType {
-    struct Always {
-      using EmptyTrait = std::true_type;
-    };
-    ENUM_CLASS(Type, To, From, Tofrom, Alloc, Release, Delete)
-    using TupleTrait = std::true_type;
-    std::tuple<std::optional<Always>, Type> t;
-  };
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<MapType>, ObjectList> t;
-};
-
 Map make(const parser::OmpClause::Map &inp,
          semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpMapClause
@@ -880,101 +813,54 @@ Map make(const parser::OmpClause::Map &inp,
   return Map{{maybeApply(convert, t0), makeList(t1, semaCtx)}};
 }
 
-struct Nocontext {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Nocontext make(const parser::OmpClause::Nocontext &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
   return Nocontext{makeExpr(inp.v, semaCtx)};
 }
 
-struct Nontemporal {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Nontemporal make(const parser::OmpClause::Nontemporal &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::Name>
   return Nontemporal{makeList(inp.v, makeObject(semaCtx))};
 }
 
-struct Novariants {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Novariants make(const parser::OmpClause::Novariants &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarLogicalExpr
   return Novariants{makeExpr(inp.v, semaCtx)};
 }
 
-struct NumTasks {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 NumTasks make(const parser::OmpClause::NumTasks &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return NumTasks{makeExpr(inp.v, semaCtx)};
 }
 
-struct NumTeams {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 NumTeams make(const parser::OmpClause::NumTeams &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return NumTeams{makeExpr(inp.v, semaCtx)};
 }
 
-struct NumThreads {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 NumThreads make(const parser::OmpClause::NumThreads &inp,
                 semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return NumThreads{makeExpr(inp.v, semaCtx)};
 }
 
-struct OmpxDynCgroupMem {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 OmpxDynCgroupMem make(const parser::OmpClause::OmpxDynCgroupMem &inp,
                       semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return OmpxDynCgroupMem{makeExpr(inp.v, semaCtx)};
 }
 
-struct Ordered {
-  using WrapperTrait = std::true_type;
-  MaybeExpr v;
-};
-
 Ordered make(const parser::OmpClause::Ordered &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::optional<parser::ScalarIntConstantExpr>
   return Ordered{maybeApply(makeExpr(semaCtx), inp.v)};
 }
 
-struct Order {
-  ENUM_CLASS(Kind, Reproducible, Unconstrained)
-  ENUM_CLASS(Type, Concurrent)
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<Kind>, Type> t;
-};
-
 Order make(const parser::OmpClause::Order &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpOrderClause
@@ -988,56 +874,30 @@ Order make(const parser::OmpClause::Order &inp,
   return Order{{maybeApply(convert, t0), enum_cast<Order::Type>(t1)}};
 }
 
-struct Partial {
-  using WrapperTrait = std::true_type;
-  MaybeExpr v;
-};
-
 Partial make(const parser::OmpClause::Partial &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::optional<parser::ScalarIntConstantExpr>
   return Partial{maybeApply(makeExpr(semaCtx), inp.v)};
 }
 
-struct Priority {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Priority make(const parser::OmpClause::Priority &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return Priority{makeExpr(inp.v, semaCtx)};
 }
 
-struct Private {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Private make(const parser::OmpClause::Private &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Private{makeList(inp.v, semaCtx)};
 }
 
-struct ProcBind {
-  ENUM_CLASS(Type, Close, Master, Spread, Primary)
-  using WrapperTrait = std::true_type;
-  Type v;
-};
-
 ProcBind make(const parser::OmpClause::ProcBind &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpProcBindClause
   return ProcBind{enum_cast<ProcBind::Type>(inp.v.v)};
 }
 
-struct Reduction {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperator, ObjectList> t;
-};
-
 Reduction make(const parser::OmpClause::Reduction &inp,
                semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpReductionClause
@@ -1046,28 +906,12 @@ Reduction make(const parser::OmpClause::Reduction &inp,
   return Reduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
 }
 
-struct Safelen {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Safelen make(const parser::OmpClause::Safelen &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
   return Safelen{makeExpr(inp.v, semaCtx)};
 }
 
-struct Schedule {
-  ENUM_CLASS(ModType, Monotonic, Nonmonotonic, Simd)
-  struct ScheduleModifier {
-    using TupleTrait = std::true_type;
-    std::tuple<ModType, std::optional<ModType>> t;
-  };
-  ENUM_CLASS(ScheduleType, Static, Dynamic, Guided, Auto, Runtime)
-  using TupleTrait = std::true_type;
-  std::tuple<std::optional<ScheduleModifier>, ScheduleType, MaybeExpr> t;
-};
-
 Schedule make(const parser::OmpClause::Schedule &inp,
               semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpScheduleClause
@@ -1093,44 +937,24 @@ Schedule make(const parser::OmpClause::Schedule &inp,
                    maybeApply(makeExpr(semaCtx), t2)}};
 }
 
-struct Shared {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Shared make(const parser::OmpClause::Shared &inp,
             semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return Shared{makeList(inp.v, semaCtx)};
 }
 
-struct Simdlen {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 Simdlen make(const parser::OmpClause::Simdlen &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntConstantExpr
   return Simdlen{makeExpr(inp.v, semaCtx)};
 }
 
-struct Sizes {
-  using WrapperTrait = std::true_type;
-  List<SomeExpr> v;
-};
-
 Sizes make(const parser::OmpClause::Sizes &inp,
            semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::ScalarIntExpr>
   return Sizes{makeList(inp.v, makeExpr(semaCtx))};
 }
 
-struct TaskReduction {
-  using TupleTrait = std::true_type;
-  std::tuple<ReductionOperator, ObjectList> t;
-};
-
 TaskReduction make(const parser::OmpClause::TaskReduction &inp,
                    semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpReductionClause
@@ -1139,98 +963,59 @@ TaskReduction make(const parser::OmpClause::TaskReduction &inp,
   return TaskReduction{{makeRedOp(t0, semaCtx), makeList(t1, semaCtx)}};
 }
 
-struct ThreadLimit {
-  using WrapperTrait = std::true_type;
-  SomeExpr v;
-};
-
 ThreadLimit make(const parser::OmpClause::ThreadLimit &inp,
                  semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::ScalarIntExpr
   return ThreadLimit{makeExpr(inp.v, semaCtx)};
 }
 
-struct To {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 To make(const parser::OmpClause::To &inp,
         semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return To{makeList(inp.v, semaCtx)};
 }
 
-struct Uniform {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 Uniform make(const parser::OmpClause::Uniform &inp,
              semantics::SemanticsContext &semaCtx) {
   // inp.v -> std::list<parser::Name>
   return Uniform{makeList(inp.v, makeObject(semaCtx))};
 }
 
-struct UseDeviceAddr {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 UseDeviceAddr make(const parser::OmpClause::UseDeviceAddr &inp,
                    semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return UseDeviceAddr{makeList(inp.v, semaCtx)};
 }
 
-struct UseDevicePtr {
-  using WrapperTrait = std::true_type;
-  ObjectList v;
-};
-
 UseDevicePtr make(const parser::OmpClause::UseDevicePtr &inp,
                   semantics::SemanticsContext &semaCtx) {
   // inp.v -> parser::OmpObjectList
   return UseDevicePtr{makeList(inp.v, semaCtx)};
 }
-
-using UnionOfAllClauses = std::variant<
-    AcqRel, Acquire, AdjustArgs, Affinity, Align, Aligned, Allocate, Allocator,
-    AppendArgs, At, AtomicDefaultMemOrder, Bind, CancellationConstructType,
-    Capture, Collapse, Compare, Copyprivate, Copyin, Default, Defaultmap,
-    Depend, Depobj, Destroy, Detach, Device, DeviceType, DistSchedule, Doacross,
-    DynamicAllocators, Enter, Exclusive, Fail, Filter, Final, Firstprivate,
-    Flush, From, Full, Grainsize, HasDeviceAddr, Hint, If, InReduction,
-    Inbranch, Inclusive, Indirect, Init, IsDevicePtr, Lastprivate, Linear, Link,
-    Map, Match, MemoryOrder, Mergeable, Message, Nogroup, Nowait, Nocontext,
-    Nontemporal, Notinbranch, Novariants, NumTasks, NumTeams, NumThreads,
-    OmpxAttribute, OmpxDynCgroupMem, OmpxBare, Order, Ordered, Partial,
-    Priority, Private, ProcBind, Read, Reduction, Relaxed, Release,
-    ReverseOffload, Safelen, Schedule, SeqCst, Severity, Shared, Simd, Simdlen,
-    Sizes, TaskReduction, ThreadLimit, Threadprivate, Threads, To,
-    UnifiedAddress, UnifiedSharedMemory, Uniform, Unknown, Untied, Update, Use,
-    UseDeviceAddr, UseDevicePtr, UsesAllocators, Weak, When, Write>;
-
 } // namespace clause
 
-struct Clause {
+struct Clause : public tomp::ClauseT<SymIdent, SymReference> {
   parser::CharBlock source;
-  llvm::omp::Clause id; // The numeric id of the clause
-  using UnionTrait = std::true_type;
-  clause::UnionOfAllClauses u;
 };
 
+template <typename Specific>
+Clause makeClause(llvm::omp::Clause id, Specific &&specific,
+                  parser::CharBlock source = {}) {
+  return Clause{id, specific, source};
+}
+
 Clause makeClause(const Fortran::parser::OmpClause &cls,
                   semantics::SemanticsContext &semaCtx) {
   return std::visit(
       [&](auto &&s) {
-        return Clause{cls.source, getClauseId(cls), clause::make(s, semaCtx)};
+        return makeClause(getClauseId(cls), clause::make(s, semaCtx),
+                          cls.source);
       },
       cls.u);
 }
 
-List<Clause> makeList(const parser::OmpClauseList &clauses,
-                      semantics::SemanticsContext &semaCtx) {
+omp::List<Clause> makeList(const parser::OmpClauseList &clauses,
+                           semantics::SemanticsContext &semaCtx) {
   return makeList(clauses.v, [&](const parser::OmpClause &s) {
     return makeClause(s, semaCtx);
   });
@@ -1241,7 +1026,7 @@ static void genObjectList(const omp::ObjectList &objects,
                           Fortran::lower::AbstractConverter &converter,
                           llvm::SmallVectorImpl<mlir::Value> &operands) {
   for (const omp::Object &object : objects) {
-    const Fortran::semantics::Symbol *sym = object.sym;
+    const Fortran::semantics::Symbol *sym = object.id();
     assert(sym && "Expected Symbol");
     if (mlir::Value variable = converter.getSymbolAddress(*sym)) {
       operands.push_back(variable);
@@ -1260,7 +1045,106 @@ static void gatherFuncAndVarSyms(
     mlir::omp::DeclareTargetCaptureClause clause,
     llvm::SmallVectorImpl<DeclareTargetCapturePair> &symbolAndClause) {
   for (const omp::Object &object : objects)
-    symbolAndClause.emplace_back(clause, *object.sym);
+    symbolAndClause.emplace_back(clause, *object.id());
+}
+
+//===----------------------------------------------------------------------===//
+// Directive decomposition
+//===----------------------------------------------------------------------===//
+
+namespace {
+struct CompositeInfo
+    : public tomp::CompositeInfoBase<omp::SymIdent, omp::SymReference,
+                                     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<omp::SymIdent, omp::SymReference, 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(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.
+  omp::Clause *
+  makeClause(tomp::ClauseT<omp::SymIdent, omp::SymReference> &&specific) {
+    clauses.push_back(omp::Clause{{std::move(specific)}, {}});
+    return &clauses.back();
+  }
+
+  // Given an object, return its base object if one exists.
+  std::optional<omp::Object> getBaseObject(const omp::Object &object) {
+    return omp::getBaseObject(object, semaCtx);
+  }
+
+  // Return the iteration variable of the associated loop if any.
+  std::optional<omp::Object> getIterVar() {
+    Fortran::semantics::Symbol *symbol = getIterationVariableSymbol(eval);
+    if (symbol)
+      return omp::Object{symbol};
+    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<omp::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';
 }
 
 //===----------------------------------------------------------------------===//
@@ -1389,7 +1273,7 @@ void DataSharingProcessor::collectOmpObjectListSymbol(
     const omp::ObjectList &objects,
     llvm::SetVector<const Fortran::semantics::Symbol *> &symbolSet) {
   for (const omp::Object &object : objects) {
-    Fortran::semantics::Symbol *sym = object.sym;
+    Fortran::semantics::Symbol *sym = object.id();
     symbolSet.insert(sym);
   }
 }
@@ -1866,7 +1750,7 @@ class ReductionProcessor {
   static ReductionIdentifier
   getReductionType(const omp::clause::ProcedureDesignator &pd) {
     auto redType = llvm::StringSwitch<std::optional<ReductionIdentifier>>(
-                       getRealName(pd.v.sym).ToString())
+                       getRealName(pd.v.id()).ToString())
                        .Case("max", ReductionIdentifier::MAX)
                        .Case("min", ReductionIdentifier::MIN)
                        .Case("iand", ReductionIdentifier::IAND)
@@ -1901,7 +1785,7 @@ class ReductionProcessor {
 
   static bool
   supportedIntrinsicProcReduction(const omp::clause::ProcedureDesignator &pd) {
-    Fortran::semantics::Symbol *sym = pd.v.sym;
+    Fortran::semantics::Symbol *sym = pd.v.id();
     if (!sym->GetUltimate().attrs().test(Fortran::semantics::Attr::INTRINSIC))
       return false;
     auto redType = llvm::StringSwitch<bool>(getRealName(sym).ToString())
@@ -1921,7 +1805,7 @@ class ReductionProcessor {
 
   static const Fortran::semantics::SourceName
   getRealName(const omp::clause::ProcedureDesignator &pd) {
-    return getRealName(pd.v.sym);
+    return getRealName(pd.v.id());
   }
 
   static std::string getReductionName(llvm::StringRef name, mlir::Type ty) {
@@ -2223,7 +2107,7 @@ class ReductionProcessor {
         break;
       }
       for (const omp::Object &object : objectList) {
-        if (const Fortran::semantics::Symbol *symbol = object.sym) {
+        if (const Fortran::semantics::Symbol *symbol = object.id()) {
           if (reductionSymbols)
             reductionSymbols->push_back(symbol);
           mlir::Value symVal = converter.getSymbolAddress(*symbol);
@@ -2256,7 +2140,7 @@ class ReductionProcessor {
         ReductionProcessor::ReductionIdentifier redId =
             ReductionProcessor::getReductionType(*reductionIntrinsic);
         for (const omp::Object &object : objectList) {
-          if (const Fortran::semantics::Symbol *symbol = object.sym) {
+          if (const Fortran::semantics::Symbol *symbol = object.id()) {
             if (reductionSymbols)
               reductionSymbols->push_back(symbol);
             mlir::Value symVal = converter.getSymbolAddress(*symbol);
@@ -2457,7 +2341,7 @@ addUseDeviceClause(Fortran::lower::AbstractConverter &converter,
     useDeviceLocs.push_back(operand.getLoc());
   }
   for (const omp::Object &object : objects)
-    useDeviceSymbols.push_back(object.sym);
+    useDeviceSymbols.push_back(object.id());
 }
 
 //===----------------------------------------------------------------------===//
@@ -2790,7 +2674,7 @@ bool ClauseProcessor::processCopyin() const {
       [&](const omp::clause::Copyin &clause,
           const Fortran::parser::CharBlock &) {
         for (const omp::Object &object : clause.v) {
-          Fortran::semantics::Symbol *sym = object.sym;
+          Fortran::semantics::Symbol *sym = object.id();
           assert(sym && "Expecting symbol");
           if (const auto *commonDetails =
                   sym->detailsIf<Fortran::semantics::CommonBlockDetails>()) {
@@ -2837,17 +2721,17 @@ bool ClauseProcessor::processDepend(
         dependTypeOperands.append(objects.size(), dependTypeOperand);
 
         for (const omp::Object &object : objects) {
-          assert(object.dsg && "Expecting designator");
+          assert(object.ref() && "Expecting designator");
 
-          if (Fortran::evaluate::ExtractSubstring(*object.dsg)) {
+          if (Fortran::evaluate::ExtractSubstring(*object.ref())) {
             TODO(converter.getCurrentLocation(),
                  "substring not supported for task depend");
-          } else if (Fortran::evaluate::IsArrayElement(*object.dsg)) {
+          } else if (Fortran::evaluate::IsArrayElement(*object.ref())) {
             TODO(converter.getCurrentLocation(),
                  "array sections not supported for task depend");
           }
 
-          Fortran::semantics::Symbol *sym = object.sym;
+          Fortran::semantics::Symbol *sym = object.id();
           const mlir::Value variable = converter.getSymbolAddress(*sym);
           dependOperands.push_back(variable);
         }
@@ -2967,11 +2851,11 @@ bool ClauseProcessor::processMap(
           Fortran::lower::AddrAndBoundsInfo info =
               Fortran::lower::gatherDataOperandAddrAndBounds<
                   mlir::omp::DataBoundsOp, mlir::omp::DataBoundsType>(
-                  converter, firOpBuilder, semaCtx, stmtCtx, *object.sym,
-                  object.dsg, clauseLocation, asFortran, bounds,
+                  converter, firOpBuilder, semaCtx, stmtCtx, *object.id(),
+                  object.ref(), clauseLocation, asFortran, bounds,
                   treatIndexAsSection);
 
-          auto origSymbol = converter.getSymbolAddress(*object.sym);
+          auto origSymbol = converter.getSymbolAddress(*object.id());
           mlir::Value symAddr = info.addr;
           if (origSymbol && fir::isTypeWithDescriptor(origSymbol.getType()))
             symAddr = origSymbol;
@@ -2994,7 +2878,7 @@ bool ClauseProcessor::processMap(
             mapSymLocs->push_back(symAddr.getLoc());
 
           if (mapSymbols)
-            mapSymbols->push_back(object.sym);
+            mapSymbols->push_back(object.id());
         }
       });
 }
@@ -3095,11 +2979,11 @@ bool ClauseProcessor::processMotionClauses(
           Fortran::lower::AddrAndBoundsInfo info =
               Fortran::lower::gatherDataOperandAddrAndBounds<
                   mlir::omp::DataBoundsOp, mlir::omp::DataBoundsType>(
-                  converter, firOpBuilder, semaCtx, stmtCtx, *object.sym,
-                  object.dsg, clauseLocation, asFortran, bounds,
+                  converter, firOpBuilder, semaCtx, stmtCtx, *object.id(),
+                  object.ref(), clauseLocation, asFortran, bounds,
                   treatIndexAsSection);
 
-          auto origSymbol = converter.getSymbolAddress(*object.sym);
+          auto origSymbol = converter.getSymbolAddress(*object.id());
           mlir::Value symAddr = info.addr;
           if (origSymbol && fir::isTypeWithDescriptor(origSymbol.getType()))
             symAddr = origSymbol;
@@ -4612,6 +4496,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 =
@@ -5284,7 +5172,7 @@ void Fortran::lower::genOpenMPReduction(
           continue;
         }
         for (const omp::Object &object : objectList) {
-          if (const Fortran::semantics::Symbol *symbol = object.sym) {
+          if (const Fortran::semantics::Symbol *symbol = object.id()) {
             mlir::Value reductionVal = converter.getSymbolAddress(*symbol);
             if (auto declOp = reductionVal.getDefiningOp<hlfir::DeclareOp>())
               reductionVal = declOp.getBase();
@@ -5323,7 +5211,7 @@ void Fortran::lower::genOpenMPReduction(
         ReductionProcessor::ReductionIdentifier redId =
             ReductionProcessor::getReductionType(*reductionIntrinsic);
         for (const omp::Object &object : objectList) {
-          if (const Fortran::semantics::Symbol *symbol = object.sym) {
+          if (const Fortran::semantics::Symbol *symbol = object.id()) {
             mlir::Value reductionVal = converter.getSymbolAddress(*symbol);
             if (auto declOp = reductionVal.getDefiningOp<hlfir::DeclareOp>())
               reductionVal = declOp.getBase();
diff --git a/flang/lib/Lower/OpenMPClauses.h b/flang/lib/Lower/OpenMPClauses.h
new file mode 100644
index 00000000000000..c621e5ac685aa6
--- /dev/null
+++ b/flang/lib/Lower/OpenMPClauses.h
@@ -0,0 +1,1663 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 OPENMPCLAUSES_H
+#define OPENMPCLAUSES_H
+
+#include "flang/Common/enum-class.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"
+
+static 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 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();
+}
+} // namespace detail
+
+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;
+  };
+  ENUM_CLASS(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 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;
+  // XXX common::OmpAtomicDefaultMemOrderType v;
+  ENUM_CLASS(OmpAtomicDefaultMemOrderType, SeqCst, AcqRel, Relaxed)
+  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 {
+  ENUM_CLASS(ImplicitBehavior, Alloc, To, From, Tofrom, Firstprivate, None,
+             Default)
+  ENUM_CLASS(VariableCategory, Scalar, Aggregate, Allocatable, Pointer)
+  using TupleTrait = std::true_type;
+  std::tuple<ImplicitBehavior, std::optional<VariableCategory>> t;
+};
+
+template <typename I, typename E>
+struct DefaultT {
+  ENUM_CLASS(Type, Private, Firstprivate, Shared, None)
+  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;
+  };
+  ENUM_CLASS(Type, In, Out, Inout, Source, Sink)
+  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 {
+  ENUM_CLASS(DeviceModifier, Ancestor, Device_Num)
+  using TupleTrait = std::true_type;
+  std::tuple<std::optional<DeviceModifier>, E> t;
+};
+
+template <typename I, typename E>
+struct DeviceTypeT {
+  ENUM_CLASS(Type, Any, Host, Nohost)
+  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 {
+  ENUM_CLASS(DirectiveNameModifier, Parallel, Simd, Target, TargetData,
+             TargetEnterData, TargetExitData, TargetUpdate, Task, Taskloop,
+             Teams)
+  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 {
+    ENUM_CLASS(Type, Exp, Val, Uval)
+    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;
+    };
+    ENUM_CLASS(Type, To, From, Tofrom, Alloc, Release, Delete)
+    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 {
+  ENUM_CLASS(Kind, Reproducible, Unconstrained)
+  ENUM_CLASS(Type, Concurrent)
+  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 {
+  ENUM_CLASS(Type, Close, Master, Spread, Primary)
+  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 {
+  ENUM_CLASS(ModType, Monotonic, Nonmonotonic, Simd)
+  struct ScheduleModifier {
+    using TupleTrait = std::true_type;
+    std::tuple<ModType, std::optional<ModType>> t;
+  };
+  ENUM_CLASS(ScheduleType, Static, Dynamic, Guided, Auto, Runtime)
+  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;
+};
+
+// --------------------------------------------------------------------
+template <typename IdType, typename ExprType>
+struct DirectiveInfo {
+  llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
+  llvm::SmallVector<const tomp::ClauseT<IdType, ExprType> *> clauses;
+};
+
+template <typename I, typename E>
+llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
+                              const DirectiveInfo<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 IdTy, typename ExprTy, typename HelperTy>
+struct CompositeInfoBase {
+  using IdType = IdTy;
+  using ExprType = ExprTy;
+  using HelperType = HelperTy;
+  using ObjectType = tomp::ObjectT<IdType, ExprType>;
+  using ClauseType = tomp::ClauseT<IdType, ExprType>;
+
+  using ClauseSet = llvm::DenseSet<const ClauseType *>;
+
+  CompositeInfoBase(uint32_t ver, llvm::omp::Directive dir, HelperType &hlp)
+      : version(ver), construct(dir), helper(hlp) {}
+
+  void add(const ClauseType *node) { nodes.push_back(node); }
+
+  bool split();
+
+  DirectiveInfo<IdType, ExprType> *findDirective(llvm::omp::Directive dirId) {
+    for (DirectiveInfo<IdType, ExprType> &dir : leafs) {
+      if (dir.id == dirId)
+        return &dir;
+    }
+    return nullptr;
+  }
+  ClauseSet *findClauses(const ObjectType &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<IdType, ExprType>> leafs;
+
+private:
+  template <typename S>
+  ClauseType *makeClause(llvm::omp::Clause clauseId, S &&specific) {
+    auto clause = ClauseType{clauseId, std::move(specific)};
+    auto *addr = helper.makeClause(std::move(clause));
+    return static_cast<ClauseType *>(addr);
+  }
+
+  void addClauseSymsToMap(const ObjectType &object, const ClauseType *);
+  void addClauseSymsToMap(const tomp::ObjectListT<IdType, ExprType> &objects,
+                          const ClauseType *);
+  void addClauseSymsToMap(const ExprType &item, const ClauseType *);
+  void addClauseSymsToMap(const tomp::clause::MapT<IdType, ExprType> &item,
+                          const ClauseType *);
+
+  template <typename T>
+  void addClauseSymsToMap(const std::optional<T> &item, const ClauseType *);
+  template <typename T>
+  void addClauseSymsToMap(const tomp::ListT<T> &item, const ClauseType *);
+  template <typename... T, size_t... Is>
+  void addClauseSymsToMap(const std::tuple<T...> &item, const ClauseType *,
+                          std::index_sequence<Is...> = {});
+  template <typename T>
+  std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<T>>, void>
+  addClauseSymsToMap(T &&item, const ClauseType *);
+
+  template <typename T>
+  std::enable_if_t<llvm::remove_cvref_t<T>::EmptyTrait::value, void>
+  addClauseSymsToMap(T &&item, const ClauseType *);
+
+  template <typename T>
+  std::enable_if_t<llvm::remove_cvref_t<T>::WrapperTrait::value, void>
+  addClauseSymsToMap(T &&item, const ClauseType *);
+
+  template <typename T>
+  std::enable_if_t<llvm::remove_cvref_t<T>::TupleTrait::value, void>
+  addClauseSymsToMap(T &&item, const ClauseType *);
+
+  template <typename T>
+  std::enable_if_t<llvm::remove_cvref_t<T>::UnionTrait::value, void>
+  addClauseSymsToMap(T &&item, const ClauseType *);
+
+  // 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 ClauseType *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 ClauseType *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 ClauseType *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 ClauseType *node);
+
+  template <typename Predicate>
+  bool applyIf(const ClauseType *node, Predicate shouldApply);
+
+  bool applyToAll(const ClauseType *node);
+
+  template <typename Clause>
+  bool applyClause(Clause &&clause, const ClauseType *node);
+
+  bool applyClause(const tomp::clause::CollapseT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::PrivateT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::FirstprivateT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::LastprivateT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::SharedT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::DefaultT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::ThreadLimitT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::OrderT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::AllocateT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::ReductionT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::IfT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::LinearT<IdType, ExprType> &clause,
+                   const ClauseType *);
+  bool applyClause(const tomp::clause::NowaitT<IdType, ExprType> &clause,
+                   const ClauseType *);
+
+  HelperType &helper;
+  tomp::ListT<const ClauseType *> nodes;
+
+  llvm::DenseMap<IdType, ClauseSet> syms;
+  llvm::DenseSet<IdType> mapBases;
+};
+
+template <typename I, typename E, typename H>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(const ObjectType &object,
+                                                    const ClauseType *node) {
+  syms[object.id()].insert(node);
+}
+
+template <typename I, typename E, typename H>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(
+    const tomp::ObjectListT<I, E> &objects, const ClauseType *node) {
+  for (auto &object : objects)
+    syms[object.id()].insert(node);
+}
+
+template <typename I, typename E, typename H>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(const E &expr,
+                                                    const ClauseType *node) {
+  // Nothing to do for expressions.
+}
+
+template <typename I, typename E, typename H>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(
+    const tomp::clause::MapT<I, E> &item, const ClauseType *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 I, typename E, typename H>
+template <typename T>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(
+    const std::optional<T> &item, const ClauseType *node) {
+  if (item)
+    addClauseSymsToMap(*item, node);
+}
+
+template <typename I, typename E, typename H>
+template <typename T>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(const tomp::ListT<T> &item,
+                                                    const ClauseType *node) {
+  for (auto &s : item)
+    addClauseSymsToMap(s, node);
+}
+
+template <typename I, typename E, typename H>
+template <typename... T, size_t... Is>
+void CompositeInfoBase<I, E, H>::addClauseSymsToMap(
+    const std::tuple<T...> &item, const ClauseType *node,
+    std::index_sequence<Is...>) {
+  (void)node; // Silence strange warning from GCC.
+  (addClauseSymsToMap(std::get<Is>(item), node), ...);
+}
+
+template <typename I, typename E, typename H>
+template <typename T>
+std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<T>>, void>
+CompositeInfoBase<I, E, H>::addClauseSymsToMap(T &&item,
+                                               const ClauseType *node) {
+  // Nothing to do for enums.
+}
+
+template <typename I, typename E, typename H>
+template <typename T>
+std::enable_if_t<llvm::remove_cvref_t<T>::EmptyTrait::value, void>
+CompositeInfoBase<I, E, H>::addClauseSymsToMap(T &&item,
+                                               const ClauseType *node) {
+  // Nothing to do for an empty class.
+}
+
+template <typename I, typename E, typename H>
+template <typename T>
+std::enable_if_t<llvm::remove_cvref_t<T>::WrapperTrait::value, void>
+CompositeInfoBase<I, E, H>::addClauseSymsToMap(T &&item,
+                                               const ClauseType *node) {
+  addClauseSymsToMap(item.v, node);
+}
+
+template <typename I, typename E, typename H>
+template <typename T>
+std::enable_if_t<llvm::remove_cvref_t<T>::TupleTrait::value, void>
+CompositeInfoBase<I, E, H>::addClauseSymsToMap(T &&item,
+                                               const ClauseType *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 I, typename E, typename H>
+template <typename T>
+std::enable_if_t<llvm::remove_cvref_t<T>::UnionTrait::value, void>
+CompositeInfoBase<I, E, H>::addClauseSymsToMap(T &&item,
+                                               const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyToUnique(const ClauseType *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 I, typename E, typename H>
+template <typename Iterator>
+bool CompositeInfoBase<I, E, H>::applyToFirst(
+    const ClauseType *node, llvm::iterator_range<Iterator> range) {
+  if (range.empty())
+    return false;
+
+  for (DirectiveInfo<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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyToInnermost(const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyToOutermost(const ClauseType *node) {
+  return applyToFirst(node, llvm::iterator_range(leafs));
+}
+
+template <typename I, typename E, typename H>
+template <typename Predicate>
+bool CompositeInfoBase<I, E, H>::applyIf(const ClauseType *node,
+                                         Predicate shouldApply) {
+  bool applied = false;
+  for (DirectiveInfo<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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyToAll(const ClauseType *node) {
+  return applyIf(node, [](auto) { return true; });
+}
+
+template <typename I, typename E, typename H>
+template <typename Clause>
+bool CompositeInfoBase<I, E, H>::applyClause(Clause &&clause,
+                                             const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::CollapseT<I, E> &clause, const ClauseType *node) {
+  // Apply COLLAPSE to the innermost directive. If it's not one that
+  // allows it flag an error.
+  if (!leafs.empty()) {
+    DirectiveInfo<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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::PrivateT<I, E> &clause, const ClauseType *node) {
+  if (applyToInnermost(node))
+    return true;
+  //llvm::errs() << "Cannot apply PRIVATE\n";
+  return false;
+}
+
+// FIRSTPRIVATE
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::FirstprivateT<I, E> &clause, const ClauseType *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<I, E>{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<I, E> &dir : leafs) {
+      auto found = llvm::find(worksharing, dir.id);
+      if (found != std::end(worksharing))
+        return &dir;
+    }
+    return static_cast<DirectiveInfo<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<I, E>{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 ObjectType &object) {
+    if (ClauseSet *set = findClauses(object)) {
+      return llvm::find_if(*set, [](const ClauseType *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 ObjectType &object) {
+          return !inLastprivate(object) && !mapBases.contains(object.id());
+        });
+    if (!objects.empty()) {
+      auto *firstp = makeClause(llvm::omp::Clause::OMPC_firstprivate,
+                                tomp::clause::FirstprivateT<I, E>{objects});
+      hasTarget->clauses.push_back(firstp);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// LASTPRIVATE
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::LastprivateT<I, E> &clause, const ClauseType *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 ObjectType &object) {
+    if (ClauseSet *set = findClauses(object)) {
+      return llvm::find_if(*set, [](const ClauseType *c) {
+               return c->id == llvm::omp::Clause::OMPC_firstprivate;
+             }) != set->end();
+    }
+    return false;
+  };
+
+  // Prepare list of objects that could end up in a SHARED clause.
+  tomp::ObjectListT<I, E> sharedObjects;
+  llvm::copy_if(
+      clause.v, std::back_inserter(sharedObjects),
+      [&](const ObjectType &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<I, E>{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<I, E>{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(clause.v, std::back_inserter(tofrom),
+                  [&](const ObjectType &object) {
+                    return !mapBases.contains(object.id());
+                  });
+
+    if (!tofrom.empty()) {
+      using MapType = typename tomp::clause::MapT<I, E>::MapType;
+      auto mapType = MapType{{std::nullopt, MapType::Type::Tofrom}};
+      auto *map =
+          makeClause(llvm::omp::Clause::OMPC_map,
+                     tomp::clause::MapT<I, E>{{mapType, std::move(tofrom)}});
+      hasTarget->clauses.push_back(map);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// SHARED
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::SharedT<I, E> &clause, const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::DefaultT<I, E> &clause, const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::ThreadLimitT<I, E> &clause, const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::OrderT<I, E> &clause, const ClauseType *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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::AllocateT<I, E> &clause, const ClauseType *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<I, E> &dir) {
+    return llvm::any_of(dir.clauses, [&](const ClauseType *n) {
+      return canMakePrivateCopy(n->id);
+    });
+  });
+
+  return applied;
+}
+
+// REDUCTION
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::ReductionT<I, E> &clause, const ClauseType *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 ObjectType &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<I, E>{sharedObjects});
+      hasParallel->clauses.push_back(shared);
+    }
+    if (hasTeams && !applyToTeams) {
+      auto *shared = makeClause(llvm::omp::Clause::OMPC_shared,
+                                tomp::clause::SharedT<I, E>{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<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 ObjectType &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<I, E>::MapType;
+      auto mapType = MapType{{std::nullopt, MapType::Type::Tofrom}};
+      auto *map =
+          makeClause(llvm::omp::Clause::OMPC_map,
+                     tomp::clause::MapT<I, E>{{mapType, std::move(tofrom)}});
+
+      hasTarget->clauses.push_back(map);
+      applied = true;
+    }
+  }
+
+  return applied;
+}
+
+// IF
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::IfT<I, E> &clause, const ClauseType *node) {
+  using DirectiveNameModifier =
+      typename tomp::clause::IfT<I, E>::DirectiveNameModifier;
+  auto &modifier = std::get<std::optional<DirectiveNameModifier>>(clause.t);
+
+  if (modifier) {
+    llvm::omp::Directive dirId = llvm::omp::Directive::OMPD_unknown;
+
+    switch (*modifier) {
+    case DirectiveNameModifier::Parallel:
+      dirId = llvm::omp::Directive::OMPD_parallel;
+      break;
+    case DirectiveNameModifier::Simd:
+      dirId = llvm::omp::Directive::OMPD_simd;
+      break;
+    case DirectiveNameModifier::Target:
+      dirId = llvm::omp::Directive::OMPD_target;
+      break;
+    case DirectiveNameModifier::Task:
+      dirId = llvm::omp::Directive::OMPD_task;
+      break;
+    case DirectiveNameModifier::Taskloop:
+      dirId = llvm::omp::Directive::OMPD_taskloop;
+      break;
+    case DirectiveNameModifier::Teams:
+      dirId = llvm::omp::Directive::OMPD_teams;
+      break;
+
+    case DirectiveNameModifier::TargetData:
+    case DirectiveNameModifier::TargetEnterData:
+    case DirectiveNameModifier::TargetExitData:
+    case DirectiveNameModifier::TargetUpdate:
+    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 I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::LinearT<I, E> &clause, const ClauseType *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<ObjectType> 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 ObjectType &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<I, E>{first});
+    add(firstp); // Appending to the main clause list.
+  }
+  if (!last.empty()) {
+    auto *lastp = makeClause(llvm::omp::Clause::OMPC_lastprivate,
+                             tomp::clause::LastprivateT<I, E>{last});
+    add(lastp); // Appending to the main clause list.
+  }
+  return true;
+}
+
+// NOWAIT
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::applyClause(
+    const tomp::clause::NowaitT<I, E> &clause, const ClauseType *node) {
+  if (applyToOutermost(node))
+    return true;
+  //llvm::errs() << "Cannot apply NOWAIT\n";
+  return false;
+}
+
+template <typename I, typename E, typename H>
+bool CompositeInfoBase<I, E, H>::split() {
+  bool success = true;
+
+  for (llvm::omp::Directive leaf : llvm::omp::getLeafConstructs(construct))
+    leafs.push_back(DirectiveInfo<I, E>{leaf});
+
+  for (const ClauseType *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 ClauseType *> linears;
+  for (const ClauseType *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<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 ClauseType *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 ClauseType *node : nodes) {
+    if (skip(node))
+      continue;
+    success =
+        success &&
+        std::visit([&](auto &&s) { return applyClause(s, node); }, node->u);
+  }
+
+  // Apply ALLOCATE.
+  for (const ClauseType *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 // OPENMPCLAUSES_H
diff --git a/flang/lib/Parser/CMakeLists.txt b/flang/lib/Parser/CMakeLists.txt
index 600a2f67df4431..d364671d7a3229 100644
--- a/flang/lib/Parser/CMakeLists.txt
+++ b/flang/lib/Parser/CMakeLists.txt
@@ -30,6 +30,7 @@ add_flang_library(FortranParser
   LINK_COMPONENTS
   Support
   FrontendOpenACC
+  FrontendOpenMP
 
   DEPENDS
   omp_gen
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index c9358c83e795c4..bdae1731260de1 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())
diff --git a/llvm/include/llvm/Frontend/Directive/DirectiveBase.td b/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
index 31578710365b21..24eb54e75c96ba 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 1481328bf483b8..534ab58985b57d 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -773,6 +773,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 = [
@@ -805,6 +806,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 = [
@@ -835,6 +837,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 = [
@@ -848,6 +851,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>,
@@ -868,6 +876,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 = [
@@ -889,6 +898,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 = [
@@ -914,6 +924,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 = [
@@ -940,6 +951,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 = [
@@ -955,6 +967,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 = [
@@ -971,6 +984,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 = [
@@ -989,6 +1003,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 = [
@@ -1009,6 +1024,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 = [
@@ -1029,6 +1045,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"> {}
@@ -1106,6 +1123,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 = [
@@ -1158,6 +1176,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 = [
@@ -1181,6 +1200,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 = [
@@ -1206,6 +1226,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 = [
@@ -1230,6 +1251,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 = [
@@ -1256,6 +1278,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"> {
@@ -1293,6 +1316,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 = [
@@ -1324,6 +1348,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 = [
@@ -1358,6 +1383,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 = [
@@ -1377,6 +1403,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 = [
@@ -1402,6 +1429,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 :
@@ -1430,6 +1458,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"> {
@@ -1458,6 +1487,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"> {
@@ -1481,6 +1511,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"> {
@@ -1507,6 +1538,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 = [
@@ -1534,6 +1566,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 = [
@@ -1562,6 +1595,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 :
@@ -1596,6 +1630,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"> {
@@ -1630,6 +1665,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"> {
@@ -1668,6 +1704,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"> {
@@ -1706,6 +1743,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"> {
@@ -1740,6 +1778,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 = [
@@ -1781,6 +1820,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 = [
@@ -1803,6 +1843,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"> {
@@ -1828,6 +1869,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"> {
@@ -1854,6 +1896,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 = [
@@ -1881,6 +1924,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 = [
@@ -1909,6 +1953,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"> {
@@ -1940,6 +1985,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"> {
@@ -1972,6 +2018,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 = [
@@ -2003,6 +2050,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>,
@@ -2018,8 +2066,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>
@@ -2069,11 +2117,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>,
@@ -2104,6 +2147,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 = [
@@ -2133,6 +2177,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 = [
@@ -2154,6 +2199,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 = [
@@ -2185,11 +2231,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 c86018715a48a1..f655e584f891e1 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 b6aee665f8ee0b..7cb2a5cbe95954 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



More information about the llvm-branch-commits mailing list