[flang-commits] [flang] 25a3ba3 - Revert "[flang][OpenMP] Decompose compound constructs, do recursive lowering (#90098)"

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Mon May 13 06:43:56 PDT 2024


Author: Krzysztof Parzyszek
Date: 2024-05-13T08:43:45-05:00
New Revision: 25a3ba33153e99c4614d404ba18b761d652e24de

URL: https://github.com/llvm/llvm-project/commit/25a3ba33153e99c4614d404ba18b761d652e24de
DIFF: https://github.com/llvm/llvm-project/commit/25a3ba33153e99c4614d404ba18b761d652e24de.diff

LOG: Revert "[flang][OpenMP] Decompose compound constructs, do recursive lowering (#90098)"

It breaks some builds, e.g.
https://lab.llvm.org/buildbot/#/builders/268/builds/13909

This reverts commit ca1bd5995f6ed934f9187305190a5abfac049173.

Added: 
    

Modified: 
    flang/lib/Lower/CMakeLists.txt
    flang/lib/Lower/OpenMP/Clauses.cpp
    flang/lib/Lower/OpenMP/Clauses.h
    flang/lib/Lower/OpenMP/OpenMP.cpp
    flang/lib/Lower/OpenMP/Utils.cpp
    flang/lib/Lower/OpenMP/Utils.h
    flang/test/Lower/OpenMP/default-clause-byref.f90
    flang/test/Lower/OpenMP/default-clause.f90
    flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
    llvm/include/llvm/Frontend/OpenMP/ClauseT.h
    llvm/unittests/Frontend/CMakeLists.txt

Removed: 
    flang/lib/Lower/OpenMP/Decomposer.cpp
    flang/lib/Lower/OpenMP/Decomposer.h
    llvm/include/llvm/Frontend/OpenMP/ConstructCompositionT.h
    llvm/include/llvm/Frontend/OpenMP/ConstructDecompositionT.h
    llvm/unittests/Frontend/OpenMPDecompositionTest.cpp


################################################################################
diff  --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 1546409752e78..f92d1a2bc7de1 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -27,7 +27,6 @@ add_flang_library(FortranLower
   OpenMP/ClauseProcessor.cpp
   OpenMP/Clauses.cpp
   OpenMP/DataSharingProcessor.cpp
-  OpenMP/Decomposer.cpp
   OpenMP/OpenMP.cpp
   OpenMP/ReductionProcessor.cpp
   OpenMP/Utils.cpp

diff  --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index 87370c92964a5..97337cfc08c72 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1227,27 +1227,4 @@ List<Clause> makeClauses(const parser::OmpClauseList &clauses,
     return makeClause(s, semaCtx);
   });
 }
-
-bool transferLocations(const List<Clause> &from, List<Clause> &to) {
-  bool allDone = true;
-
-  for (Clause &clause : to) {
-    if (!clause.source.empty())
-      continue;
-    auto found =
-        llvm::find_if(from, [&](const Clause &c) { return c.id == clause.id; });
-    // This is not completely accurate, but should be good enough for now.
-    // It can be improved in the future if necessary, but in cases of
-    // synthesized clauses getting accurate location may be impossible.
-    if (found != from.end()) {
-      clause.source = found->source;
-    } else {
-      // Found a clause that won't have "source".
-      allDone = false;
-    }
-  }
-
-  return allDone;
-}
-
 } // namespace Fortran::lower::omp

diff  --git a/flang/lib/Lower/OpenMP/Clauses.h b/flang/lib/Lower/OpenMP/Clauses.h
index 407579319279e..3e776425c733e 100644
--- a/flang/lib/Lower/OpenMP/Clauses.h
+++ b/flang/lib/Lower/OpenMP/Clauses.h
@@ -23,15 +23,11 @@
 
 namespace Fortran::lower::omp {
 using namespace Fortran;
+using SomeType = evaluate::SomeType;
 using SomeExpr = semantics::SomeExpr;
 using MaybeExpr = semantics::MaybeExpr;
 
-// evaluate::SomeType doesn't provide == operation. It's not really used in
-// flang's clauses so far, so a trivial implementation is sufficient.
-struct TypeTy : public evaluate::SomeType {
-  bool operator==(const TypeTy &t) const { return true; }
-};
-
+using TypeTy = SomeType;
 using IdTy = semantics::Symbol *;
 using ExprTy = SomeExpr;
 
@@ -226,8 +222,6 @@ using When = tomp::clause::WhenT<TypeTy, IdTy, ExprTy>;
 using Write = tomp::clause::WriteT<TypeTy, IdTy, ExprTy>;
 } // namespace clause
 
-using tomp::type::operator==;
-
 struct CancellationConstructType {
   using EmptyTrait = std::true_type;
 };
@@ -250,7 +244,6 @@ using ClauseBase = tomp::ClauseT<TypeTy, IdTy, ExprTy,
                                  MemoryOrder, Threadprivate>;
 
 struct Clause : public ClauseBase {
-  // "source" will be ignored by tomp::type::operator==.
   parser::CharBlock source;
 };
 
@@ -265,8 +258,6 @@ Clause makeClause(const Fortran::parser::OmpClause &cls,
 
 List<Clause> makeClauses(const parser::OmpClauseList &clauses,
                          semantics::SemanticsContext &semaCtx);
-
-bool transferLocations(const List<Clause> &from, List<Clause> &to);
 } // namespace Fortran::lower::omp
 
 #endif // FORTRAN_LOWER_OPENMP_CLAUSES_H

diff  --git a/flang/lib/Lower/OpenMP/Decomposer.cpp b/flang/lib/Lower/OpenMP/Decomposer.cpp
deleted file mode 100644
index e6897cb81e947..0000000000000
--- a/flang/lib/Lower/OpenMP/Decomposer.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-//===-- Decomposer.cpp -- Compound directive decomposition ----------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-//
-// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
-//
-//===----------------------------------------------------------------------===//
-
-#include "Decomposer.h"
-
-#include "Clauses.h"
-#include "Utils.h"
-#include "flang/Lower/PFTBuilder.h"
-#include "flang/Semantics/semantics.h"
-#include "flang/Tools/CrossToolHelpers.h"
-#include "mlir/IR/BuiltinOps.h"
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Frontend/OpenMP/ClauseT.h"
-#include "llvm/Frontend/OpenMP/ConstructCompositionT.h"
-#include "llvm/Frontend/OpenMP/ConstructDecompositionT.h"
-#include "llvm/Frontend/OpenMP/OMP.h"
-#include "llvm/Support/raw_ostream.h"
-
-#include <optional>
-#include <utility>
-#include <variant>
-
-using namespace Fortran;
-
-namespace {
-using namespace Fortran::lower::omp;
-
-struct ConstructDecomposition {
-  ConstructDecomposition(mlir::ModuleOp modOp,
-                         semantics::SemanticsContext &semaCtx,
-                         lower::pft::Evaluation &ev,
-                         llvm::omp::Directive compound,
-                         const List<Clause> &clauses)
-      : semaCtx(semaCtx), mod(modOp), eval(ev) {
-    tomp::ConstructDecompositionT decompose(getOpenMPVersionAttribute(modOp),
-                                            *this, compound,
-                                            llvm::ArrayRef(clauses));
-    output = std::move(decompose.output);
-  }
-
-  // Given an object, return its base object if one exists.
-  std::optional<Object> getBaseObject(const Object &object) {
-    return lower::omp::getBaseObject(object, semaCtx);
-  }
-
-  // Return the iteration variable of the associated loop if any.
-  std::optional<Object> getLoopIterVar() {
-    if (semantics::Symbol *symbol = getIterationVariableSymbol(eval))
-      return Object{symbol, /*designator=*/{}};
-    return std::nullopt;
-  }
-
-  semantics::SemanticsContext &semaCtx;
-  mlir::ModuleOp mod;
-  lower::pft::Evaluation &eval;
-  List<UnitConstruct> output;
-};
-} // namespace
-
-static UnitConstruct mergeConstructs(uint32_t version,
-                                     llvm::ArrayRef<UnitConstruct> units) {
-  tomp::ConstructCompositionT compose(version, units);
-  return compose.merged;
-}
-
-namespace Fortran::lower::omp {
-LLVM_DUMP_METHOD llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
-                                               const UnitConstruct &uc) {
-  os << llvm::omp::getOpenMPDirectiveName(uc.id);
-  for (auto [index, clause] : llvm::enumerate(uc.clauses)) {
-    os << (index == 0 ? '\t' : ' ');
-    os << llvm::omp::getOpenMPClauseName(clause.id);
-  }
-  return os;
-}
-
-ConstructQueue buildConstructQueue(
-    mlir::ModuleOp modOp, Fortran::semantics::SemanticsContext &semaCtx,
-    Fortran::lower::pft::Evaluation &eval, const parser::CharBlock &source,
-    llvm::omp::Directive compound, const List<Clause> &clauses) {
-
-  List<UnitConstruct> constructs;
-
-  ConstructDecomposition decompose(modOp, semaCtx, eval, compound, clauses);
-  assert(!decompose.output.empty() && "Construct decomposition failed");
-
-  llvm::SmallVector<llvm::omp::Directive> loweringUnits;
-  std::ignore =
-      llvm::omp::getLeafOrCompositeConstructs(compound, loweringUnits);
-  uint32_t version = getOpenMPVersionAttribute(modOp);
-
-  int leafIndex = 0;
-  for (llvm::omp::Directive dir_id : loweringUnits) {
-    llvm::ArrayRef<llvm::omp::Directive> leafsOrSelf =
-        llvm::omp::getLeafConstructsOrSelf(dir_id);
-    size_t numLeafs = leafsOrSelf.size();
-
-    llvm::ArrayRef<UnitConstruct> toMerge{&decompose.output[leafIndex],
-                                          numLeafs};
-    auto &uc = constructs.emplace_back(mergeConstructs(version, toMerge));
-
-    if (!transferLocations(clauses, uc.clauses)) {
-      // If some clauses are left without source information, use the
-      // directive's source.
-      for (auto &clause : uc.clauses) {
-        if (clause.source.empty())
-          clause.source = source;
-      }
-    }
-    leafIndex += numLeafs;
-  }
-
-  return constructs;
-}
-} // namespace Fortran::lower::omp

diff  --git a/flang/lib/Lower/OpenMP/Decomposer.h b/flang/lib/Lower/OpenMP/Decomposer.h
deleted file mode 100644
index f42d8f5c17408..0000000000000
--- a/flang/lib/Lower/OpenMP/Decomposer.h
+++ /dev/null
@@ -1,51 +0,0 @@
-//===-- Decomposer.h -- Compound directive decomposition ------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-#ifndef FORTRAN_LOWER_OPENMP_DECOMPOSER_H
-#define FORTRAN_LOWER_OPENMP_DECOMPOSER_H
-
-#include "Clauses.h"
-#include "mlir/IR/BuiltinOps.h"
-#include "llvm/Frontend/OpenMP/ConstructCompositionT.h"
-#include "llvm/Frontend/OpenMP/ConstructDecompositionT.h"
-#include "llvm/Frontend/OpenMP/OMP.h"
-#include "llvm/Support/Compiler.h"
-
-namespace llvm {
-class raw_ostream;
-}
-
-namespace Fortran {
-namespace semantics {
-class SemanticsContext;
-}
-namespace lower::pft {
-struct Evaluation;
-}
-} // namespace Fortran
-
-namespace Fortran::lower::omp {
-using UnitConstruct = tomp::DirectiveWithClauses<lower::omp::Clause>;
-using ConstructQueue = List<UnitConstruct>;
-
-LLVM_DUMP_METHOD llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
-                                               const UnitConstruct &uc);
-
-// Given a potentially compound construct with a list of clauses that
-// apply to it, break it up into individual sub-constructs each with
-// the subset of applicable clauses (plus implicit clauses, if any).
-// From that create a work queue where each work item corresponds to
-// the sub-construct with its clauses.
-ConstructQueue buildConstructQueue(mlir::ModuleOp modOp,
-                                   semantics::SemanticsContext &semaCtx,
-                                   lower::pft::Evaluation &eval,
-                                   const parser::CharBlock &source,
-                                   llvm::omp::Directive compound,
-                                   const List<Clause> &clauses);
-} // namespace Fortran::lower::omp
-
-#endif // FORTRAN_LOWER_OPENMP_DECOMPOSER_H

diff  --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index eaf4b5f997ff7..f23902d6a8239 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -15,7 +15,6 @@
 #include "ClauseProcessor.h"
 #include "Clauses.h"
 #include "DataSharingProcessor.h"
-#include "Decomposer.h"
 #include "DirectivesCommon.h"
 #include "ReductionProcessor.h"
 #include "Utils.h"
@@ -45,13 +44,6 @@ using namespace Fortran::lower::omp;
 // Code generation helper functions
 //===----------------------------------------------------------------------===//
 
-static void genOMPDispatch(Fortran::lower::AbstractConverter &converter,
-                           Fortran::lower::SymMap &symTable,
-                           Fortran::semantics::SemanticsContext &semaCtx,
-                           Fortran::lower::pft::Evaluation &eval,
-                           mlir::Location loc, const ConstructQueue &queue,
-                           ConstructQueue::iterator item);
-
 static Fortran::lower::pft::Evaluation *
 getCollapsedLoopEval(Fortran::lower::pft::Evaluation &eval, int collapseValue) {
   // Return the Evaluation of the innermost collapsed loop, or the current one
@@ -468,6 +460,81 @@ markDeclareTarget(mlir::Operation *op,
   declareTargetOp.setDeclareTarget(deviceType, captureClause);
 }
 
+/// Split a combined directive into an outer leaf directive and the (possibly
+/// combined) rest of the combined directive. Composite directives and
+/// non-compound directives are not split, in which case it will return the
+/// input directive as its first output and an empty value as its second output.
+static std::pair<llvm::omp::Directive, std::optional<llvm::omp::Directive>>
+splitCombinedDirective(llvm::omp::Directive dir) {
+  using D = llvm::omp::Directive;
+  switch (dir) {
+  case D::OMPD_masked_taskloop:
+    return {D::OMPD_masked, D::OMPD_taskloop};
+  case D::OMPD_masked_taskloop_simd:
+    return {D::OMPD_masked, D::OMPD_taskloop_simd};
+  case D::OMPD_master_taskloop:
+    return {D::OMPD_master, D::OMPD_taskloop};
+  case D::OMPD_master_taskloop_simd:
+    return {D::OMPD_master, D::OMPD_taskloop_simd};
+  case D::OMPD_parallel_do:
+    return {D::OMPD_parallel, D::OMPD_do};
+  case D::OMPD_parallel_do_simd:
+    return {D::OMPD_parallel, D::OMPD_do_simd};
+  case D::OMPD_parallel_masked:
+    return {D::OMPD_parallel, D::OMPD_masked};
+  case D::OMPD_parallel_masked_taskloop:
+    return {D::OMPD_parallel, D::OMPD_masked_taskloop};
+  case D::OMPD_parallel_masked_taskloop_simd:
+    return {D::OMPD_parallel, D::OMPD_masked_taskloop_simd};
+  case D::OMPD_parallel_master:
+    return {D::OMPD_parallel, D::OMPD_master};
+  case D::OMPD_parallel_master_taskloop:
+    return {D::OMPD_parallel, D::OMPD_master_taskloop};
+  case D::OMPD_parallel_master_taskloop_simd:
+    return {D::OMPD_parallel, D::OMPD_master_taskloop_simd};
+  case D::OMPD_parallel_sections:
+    return {D::OMPD_parallel, D::OMPD_sections};
+  case D::OMPD_parallel_workshare:
+    return {D::OMPD_parallel, D::OMPD_workshare};
+  case D::OMPD_target_parallel:
+    return {D::OMPD_target, D::OMPD_parallel};
+  case D::OMPD_target_parallel_do:
+    return {D::OMPD_target, D::OMPD_parallel_do};
+  case D::OMPD_target_parallel_do_simd:
+    return {D::OMPD_target, D::OMPD_parallel_do_simd};
+  case D::OMPD_target_simd:
+    return {D::OMPD_target, D::OMPD_simd};
+  case D::OMPD_target_teams:
+    return {D::OMPD_target, D::OMPD_teams};
+  case D::OMPD_target_teams_distribute:
+    return {D::OMPD_target, D::OMPD_teams_distribute};
+  case D::OMPD_target_teams_distribute_parallel_do:
+    return {D::OMPD_target, D::OMPD_teams_distribute_parallel_do};
+  case D::OMPD_target_teams_distribute_parallel_do_simd:
+    return {D::OMPD_target, D::OMPD_teams_distribute_parallel_do_simd};
+  case D::OMPD_target_teams_distribute_simd:
+    return {D::OMPD_target, D::OMPD_teams_distribute_simd};
+  case D::OMPD_teams_distribute:
+    return {D::OMPD_teams, D::OMPD_distribute};
+  case D::OMPD_teams_distribute_parallel_do:
+    return {D::OMPD_teams, D::OMPD_distribute_parallel_do};
+  case D::OMPD_teams_distribute_parallel_do_simd:
+    return {D::OMPD_teams, D::OMPD_distribute_parallel_do_simd};
+  case D::OMPD_teams_distribute_simd:
+    return {D::OMPD_teams, D::OMPD_distribute_simd};
+  case D::OMPD_parallel_loop:
+    return {D::OMPD_parallel, D::OMPD_loop};
+  case D::OMPD_target_parallel_loop:
+    return {D::OMPD_target, D::OMPD_parallel_loop};
+  case D::OMPD_target_teams_loop:
+    return {D::OMPD_target, D::OMPD_teams_loop};
+  case D::OMPD_teams_loop:
+    return {D::OMPD_teams, D::OMPD_loop};
+  default:
+    return {dir, std::nullopt};
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // Op body generation helper structures and functions
 //===----------------------------------------------------------------------===//
@@ -488,6 +555,11 @@ struct OpWithBodyGenInfo {
       : converter(converter), symTable(symTable), semaCtx(semaCtx), loc(loc),
         eval(eval), dir(dir) {}
 
+  OpWithBodyGenInfo &setGenNested(bool value) {
+    genNested = value;
+    return *this;
+  }
+
   OpWithBodyGenInfo &setOuterCombined(bool value) {
     outerCombined = value;
     return *this;
@@ -528,6 +600,8 @@ struct OpWithBodyGenInfo {
   Fortran::lower::pft::Evaluation &eval;
   /// [in] leaf directive for which to generate the op body.
   llvm::omp::Directive dir;
+  /// [in] whether to generate FIR for nested evaluations
+  bool genNested = true;
   /// [in] is this an outer operation - prevents privatization.
   bool outerCombined = false;
   /// [in] list of clauses to process.
@@ -546,13 +620,9 @@ struct OpWithBodyGenInfo {
 
 /// Create the body (block) for an OpenMP Operation.
 ///
-/// \param [in]   op  - the operation the body belongs to.
-/// \param [in] info  - options controlling code-gen for the construction.
-/// \param [in] queue - work queue with nested constructs.
-/// \param [in] item  - item in the queue to generate body for.
-static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
-                           const ConstructQueue &queue,
-                           ConstructQueue::iterator item) {
+/// \param [in]   op - the operation the body belongs to.
+/// \param [in] info - options controlling code-gen for the construction.
+static void createBodyOfOp(mlir::Operation &op, OpWithBodyGenInfo &info) {
   fir::FirOpBuilder &firOpBuilder = info.converter.getFirOpBuilder();
 
   auto insertMarker = [](fir::FirOpBuilder &builder) {
@@ -608,10 +678,7 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
     }
   }
 
-  if (ConstructQueue::iterator next = std::next(item); next != queue.end()) {
-    genOMPDispatch(info.converter, info.symTable, info.semaCtx, info.eval,
-                   info.loc, queue, next);
-  } else {
+  if (info.genNested) {
     // genFIR(Evaluation&) tries to patch up unterminated blocks, causing
     // a lot of complications for our approach if the terminator generation
     // is delayed past this point. Insert a temporary terminator here, then
@@ -702,12 +769,11 @@ static void genBodyOfTargetDataOp(
     Fortran::lower::AbstractConverter &converter,
     Fortran::lower::SymMap &symTable,
     Fortran::semantics::SemanticsContext &semaCtx,
-    Fortran::lower::pft::Evaluation &eval, mlir::omp::TargetDataOp &dataOp,
-    llvm::ArrayRef<mlir::Type> useDeviceTypes,
+    Fortran::lower::pft::Evaluation &eval, bool genNested,
+    mlir::omp::TargetDataOp &dataOp, llvm::ArrayRef<mlir::Type> useDeviceTypes,
     llvm::ArrayRef<mlir::Location> useDeviceLocs,
     llvm::ArrayRef<const Fortran::semantics::Symbol *> useDeviceSymbols,
-    const mlir::Location &currentLocation, const ConstructQueue &queue,
-    ConstructQueue::iterator item) {
+    const mlir::Location &currentLocation) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   mlir::Region &region = dataOp.getRegion();
 
@@ -760,13 +826,8 @@ static void genBodyOfTargetDataOp(
 
   // Set the insertion point after the marker.
   firOpBuilder.setInsertionPointAfter(undefMarker.getDefiningOp());
-
-  if (ConstructQueue::iterator next = std::next(item); next != queue.end()) {
-    genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
-                   next);
-  } else {
+  if (genNested)
     genNestedEvaluations(converter, eval);
-  }
 }
 
 // This functions creates a block for the body of the targetOp's region. It adds
@@ -775,13 +836,12 @@ static void
 genBodyOfTargetOp(Fortran::lower::AbstractConverter &converter,
                   Fortran::lower::SymMap &symTable,
                   Fortran::semantics::SemanticsContext &semaCtx,
-                  Fortran::lower::pft::Evaluation &eval,
+                  Fortran::lower::pft::Evaluation &eval, bool genNested,
                   mlir::omp::TargetOp &targetOp,
                   llvm::ArrayRef<const Fortran::semantics::Symbol *> mapSyms,
                   llvm::ArrayRef<mlir::Location> mapSymLocs,
                   llvm::ArrayRef<mlir::Type> mapSymTypes,
-                  const mlir::Location &currentLocation,
-                  const ConstructQueue &queue, ConstructQueue::iterator item) {
+                  const mlir::Location &currentLocation) {
   assert(mapSymTypes.size() == mapSymLocs.size());
 
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
@@ -923,22 +983,15 @@ genBodyOfTargetOp(Fortran::lower::AbstractConverter &converter,
 
   // Create the insertion point after the marker.
   firOpBuilder.setInsertionPointAfter(undefMarker.getDefiningOp());
-
-  if (ConstructQueue::iterator next = std::next(item); next != queue.end()) {
-    genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
-                   next);
-  } else {
+  if (genNested)
     genNestedEvaluations(converter, eval);
-  }
 }
 
 template <typename OpTy, typename... Args>
-static OpTy genOpWithBody(const OpWithBodyGenInfo &info,
-                          const ConstructQueue &queue,
-                          ConstructQueue::iterator item, Args &&...args) {
+static OpTy genOpWithBody(OpWithBodyGenInfo &info, Args &&...args) {
   auto op = info.converter.getFirOpBuilder().create<OpTy>(
       info.loc, std::forward<Args>(args)...);
-  createBodyOfOp(*op, info, queue, item);
+  createBodyOfOp(*op, info);
   return op;
 }
 
@@ -1223,8 +1276,7 @@ static mlir::omp::BarrierOp
 genBarrierOp(Fortran::lower::AbstractConverter &converter,
              Fortran::lower::SymMap &symTable,
              Fortran::semantics::SemanticsContext &semaCtx,
-             Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-             const ConstructQueue &queue, ConstructQueue::iterator item) {
+             Fortran::lower::pft::Evaluation &eval, mlir::Location loc) {
   return converter.getFirOpBuilder().create<mlir::omp::BarrierOp>(loc);
 }
 
@@ -1232,9 +1284,8 @@ static mlir::omp::CriticalOp
 genCriticalOp(Fortran::lower::AbstractConverter &converter,
               Fortran::lower::SymMap &symTable,
               Fortran::semantics::SemanticsContext &semaCtx,
-              Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-              const List<Clause> &clauses, const ConstructQueue &queue,
-              ConstructQueue::iterator item,
+              Fortran::lower::pft::Evaluation &eval, bool genNested,
+              mlir::Location loc, const List<Clause> &clauses,
               const std::optional<Fortran::parser::Name> &name) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   mlir::FlatSymbolRefAttr nameAttr;
@@ -1257,17 +1308,17 @@ genCriticalOp(Fortran::lower::AbstractConverter &converter,
 
   return genOpWithBody<mlir::omp::CriticalOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
-                        llvm::omp::Directive::OMPD_critical),
-      queue, item, nameAttr);
+                        llvm::omp::Directive::OMPD_critical)
+          .setGenNested(genNested),
+      nameAttr);
 }
 
 static mlir::omp::DistributeOp
 genDistributeOp(Fortran::lower::AbstractConverter &converter,
                 Fortran::lower::SymMap &symTable,
                 Fortran::semantics::SemanticsContext &semaCtx,
-                Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-                const List<Clause> &clauses, const ConstructQueue &queue,
-                ConstructQueue::iterator item) {
+                Fortran::lower::pft::Evaluation &eval, bool genNested,
+                mlir::Location loc, const List<Clause> &clauses) {
   TODO(loc, "Distribute construct");
   return nullptr;
 }
@@ -1277,8 +1328,7 @@ genFlushOp(Fortran::lower::AbstractConverter &converter,
            Fortran::lower::SymMap &symTable,
            Fortran::semantics::SemanticsContext &semaCtx,
            Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-           const ObjectList &objects, const List<Clause> &clauses,
-           const ConstructQueue &queue, ConstructQueue::iterator item) {
+           const ObjectList &objects, const List<Clause> &clauses) {
   llvm::SmallVector<mlir::Value> operandRange;
   genFlushClauses(converter, semaCtx, objects, clauses, loc, operandRange);
 
@@ -1290,13 +1340,12 @@ static mlir::omp::MasterOp
 genMasterOp(Fortran::lower::AbstractConverter &converter,
             Fortran::lower::SymMap &symTable,
             Fortran::semantics::SemanticsContext &semaCtx,
-            Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-            const List<Clause> &clauses, const ConstructQueue &queue,
-            ConstructQueue::iterator item) {
+            Fortran::lower::pft::Evaluation &eval, bool genNested,
+            mlir::Location loc) {
   return genOpWithBody<mlir::omp::MasterOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
-                        llvm::omp::Directive::OMPD_master),
-      queue, item);
+                        llvm::omp::Directive::OMPD_master)
+          .setGenNested(genNested));
 }
 
 static mlir::omp::OrderedOp
@@ -1304,8 +1353,7 @@ genOrderedOp(Fortran::lower::AbstractConverter &converter,
              Fortran::lower::SymMap &symTable,
              Fortran::semantics::SemanticsContext &semaCtx,
              Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-             const List<Clause> &clauses, const ConstructQueue &queue,
-             ConstructQueue::iterator item) {
+             const List<Clause> &clauses) {
   TODO(loc, "OMPD_ordered");
   return nullptr;
 }
@@ -1314,25 +1362,25 @@ static mlir::omp::OrderedRegionOp
 genOrderedRegionOp(Fortran::lower::AbstractConverter &converter,
                    Fortran::lower::SymMap &symTable,
                    Fortran::semantics::SemanticsContext &semaCtx,
-                   Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-                   const List<Clause> &clauses, const ConstructQueue &queue,
-                   ConstructQueue::iterator item) {
+                   Fortran::lower::pft::Evaluation &eval, bool genNested,
+                   mlir::Location loc, const List<Clause> &clauses) {
   mlir::omp::OrderedRegionClauseOps clauseOps;
   genOrderedRegionClauses(converter, semaCtx, clauses, loc, clauseOps);
 
   return genOpWithBody<mlir::omp::OrderedRegionOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
-                        llvm::omp::Directive::OMPD_ordered),
-      queue, item, clauseOps);
+                        llvm::omp::Directive::OMPD_ordered)
+          .setGenNested(genNested),
+      clauseOps);
 }
 
 static mlir::omp::ParallelOp
 genParallelOp(Fortran::lower::AbstractConverter &converter,
               Fortran::lower::SymMap &symTable,
               Fortran::semantics::SemanticsContext &semaCtx,
-              Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-              const List<Clause> &clauses, const ConstructQueue &queue,
-              ConstructQueue::iterator item, bool outerCombined = false) {
+              Fortran::lower::pft::Evaluation &eval, bool genNested,
+              mlir::Location loc, const List<Clause> &clauses,
+              bool outerCombined = false) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   Fortran::lower::StatementContext stmtCtx;
   mlir::omp::ParallelClauseOps clauseOps;
@@ -1351,14 +1399,14 @@ genParallelOp(Fortran::lower::AbstractConverter &converter,
   OpWithBodyGenInfo genInfo =
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_parallel)
+          .setGenNested(genNested)
           .setOuterCombined(outerCombined)
           .setClauses(&clauses)
           .setReductions(&reductionSyms, &reductionTypes)
           .setGenRegionEntryCb(reductionCallback);
 
   if (!enableDelayedPrivatization)
-    return genOpWithBody<mlir::omp::ParallelOp>(genInfo, queue, item,
-                                                clauseOps);
+    return genOpWithBody<mlir::omp::ParallelOp>(genInfo, clauseOps);
 
   bool privatize = !outerCombined;
   DataSharingProcessor dsp(converter, semaCtx, clauses, eval,
@@ -1406,23 +1454,19 @@ genParallelOp(Fortran::lower::AbstractConverter &converter,
   };
 
   genInfo.setGenRegionEntryCb(genRegionEntryCB).setDataSharingProcessor(&dsp);
-  return genOpWithBody<mlir::omp::ParallelOp>(genInfo, queue, item, clauseOps);
+  return genOpWithBody<mlir::omp::ParallelOp>(genInfo, clauseOps);
 }
 
 static mlir::omp::SectionOp
 genSectionOp(Fortran::lower::AbstractConverter &converter,
              Fortran::lower::SymMap &symTable,
              Fortran::semantics::SemanticsContext &semaCtx,
-             Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-             const List<Clause> &clauses, const ConstructQueue &queue,
-             ConstructQueue::iterator item) {
-  // Currently only private/firstprivate clause is handled, and
-  // all privatization is done within `omp.section` operations.
+             Fortran::lower::pft::Evaluation &eval, bool genNested,
+             mlir::Location loc) {
   return genOpWithBody<mlir::omp::SectionOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_section)
-          .setClauses(&clauses),
-      queue, item);
+          .setGenNested(genNested));
 }
 
 static mlir::omp::SectionsOp
@@ -1430,77 +1474,12 @@ genSectionsOp(Fortran::lower::AbstractConverter &converter,
               Fortran::lower::SymMap &symTable,
               Fortran::semantics::SemanticsContext &semaCtx,
               Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-              const List<Clause> &clauses, const ConstructQueue &queue,
-              ConstructQueue::iterator item) {
-  mlir::omp::SectionsClauseOps clauseOps;
-  genSectionsClauses(converter, semaCtx, clauses, loc, clauseOps);
-
-  auto &builder = converter.getFirOpBuilder();
-
-  // Insert privatizations before SECTIONS
-  symTable.pushScope();
-  DataSharingProcessor dsp(converter, semaCtx, clauses, eval);
-  dsp.processStep1();
-
-  List<Clause> nonDsaClauses;
-  List<const clause::Lastprivate *> lastprivates;
-
-  for (const Clause &clause : clauses) {
-    if (clause.id == llvm::omp::Clause::OMPC_lastprivate) {
-      lastprivates.push_back(&std::get<clause::Lastprivate>(clause.u));
-    } else {
-      switch (clause.id) {
-      case llvm::omp::Clause::OMPC_firstprivate:
-      case llvm::omp::Clause::OMPC_private:
-      case llvm::omp::Clause::OMPC_shared:
-        break;
-      default:
-        nonDsaClauses.push_back(clause);
-      }
-    }
-  }
-
-  // SECTIONS construct.
-  mlir::omp::SectionsOp sectionsOp = genOpWithBody<mlir::omp::SectionsOp>(
+              const mlir::omp::SectionsClauseOps &clauseOps) {
+  return genOpWithBody<mlir::omp::SectionsOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_sections)
-          .setClauses(&nonDsaClauses),
-      queue, item, clauseOps);
-
-  if (!lastprivates.empty()) {
-    mlir::Region &sectionsBody = sectionsOp.getRegion();
-    assert(sectionsBody.hasOneBlock());
-    mlir::Block &body = sectionsBody.front();
-
-    auto lastSectionOp = llvm::find_if(
-        llvm::reverse(body.getOperations()), [](const mlir::Operation &op) {
-          return llvm::isa<mlir::omp::SectionOp>(op);
-        });
-    assert(lastSectionOp != body.rend());
-
-    for (const clause::Lastprivate *lastp : lastprivates) {
-      builder.setInsertionPoint(
-          lastSectionOp->getRegion(0).back().getTerminator());
-      mlir::OpBuilder::InsertPoint insp = builder.saveInsertionPoint();
-      const auto &objList = std::get<ObjectList>(lastp->t);
-      for (const Object &object : objList) {
-        Fortran::semantics::Symbol *sym = object.id();
-        converter.copyHostAssociateVar(*sym, &insp);
-      }
-    }
-  }
-
-  // Perform DataSharingProcessor's step2 out of SECTIONS
-  builder.setInsertionPointAfter(sectionsOp.getOperation());
-  dsp.processStep2(sectionsOp, false);
-  // Emit implicit barrier to synchronize threads and avoid data
-  // races on post-update of lastprivate variables when `nowait`
-  // clause is present.
-  if (clauseOps.nowaitAttr && !lastprivates.empty())
-    builder.create<mlir::omp::BarrierOp>(loc);
-
-  symTable.popScope();
-  return sectionsOp;
+          .setGenNested(false),
+      clauseOps);
 }
 
 static mlir::omp::SimdOp
@@ -1508,8 +1487,7 @@ genSimdOp(Fortran::lower::AbstractConverter &converter,
           Fortran::lower::SymMap &symTable,
           Fortran::semantics::SemanticsContext &semaCtx,
           Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-          const List<Clause> &clauses, const ConstructQueue &queue,
-          ConstructQueue::iterator item) {
+          const List<Clause> &clauses) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   DataSharingProcessor dsp(converter, semaCtx, clauses, eval);
   dsp.processStep1();
@@ -1544,8 +1522,7 @@ genSimdOp(Fortran::lower::AbstractConverter &converter,
                                    *nestedEval, llvm::omp::Directive::OMPD_simd)
                      .setClauses(&clauses)
                      .setDataSharingProcessor(&dsp)
-                     .setGenRegionEntryCb(ivCallback),
-                 queue, item);
+                     .setGenRegionEntryCb(ivCallback));
 
   return simdOp;
 }
@@ -1554,26 +1531,26 @@ static mlir::omp::SingleOp
 genSingleOp(Fortran::lower::AbstractConverter &converter,
             Fortran::lower::SymMap &symTable,
             Fortran::semantics::SemanticsContext &semaCtx,
-            Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-            const List<Clause> &clauses, const ConstructQueue &queue,
-            ConstructQueue::iterator item) {
+            Fortran::lower::pft::Evaluation &eval, bool genNested,
+            mlir::Location loc, const List<Clause> &clauses) {
   mlir::omp::SingleClauseOps clauseOps;
   genSingleClauses(converter, semaCtx, clauses, loc, clauseOps);
 
   return genOpWithBody<mlir::omp::SingleOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_single)
+          .setGenNested(genNested)
           .setClauses(&clauses),
-      queue, item, clauseOps);
+      clauseOps);
 }
 
 static mlir::omp::TargetOp
 genTargetOp(Fortran::lower::AbstractConverter &converter,
             Fortran::lower::SymMap &symTable,
             Fortran::semantics::SemanticsContext &semaCtx,
-            Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-            const List<Clause> &clauses, const ConstructQueue &queue,
-            ConstructQueue::iterator item, bool outerCombined = false) {
+            Fortran::lower::pft::Evaluation &eval, bool genNested,
+            mlir::Location loc, const List<Clause> &clauses,
+            bool outerCombined = false) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   Fortran::lower::StatementContext stmtCtx;
 
@@ -1680,8 +1657,8 @@ genTargetOp(Fortran::lower::AbstractConverter &converter,
   Fortran::lower::pft::visitAllSymbols(eval, captureImplicitMap);
 
   auto targetOp = firOpBuilder.create<mlir::omp::TargetOp>(loc, clauseOps);
-  genBodyOfTargetOp(converter, symTable, semaCtx, eval, targetOp, mapSyms,
-                    mapLocs, mapTypes, loc, queue, item);
+  genBodyOfTargetOp(converter, symTable, semaCtx, eval, genNested, targetOp,
+                    mapSyms, mapLocs, mapTypes, loc);
   return targetOp;
 }
 
@@ -1689,9 +1666,8 @@ static mlir::omp::TargetDataOp
 genTargetDataOp(Fortran::lower::AbstractConverter &converter,
                 Fortran::lower::SymMap &symTable,
                 Fortran::semantics::SemanticsContext &semaCtx,
-                Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-                const List<Clause> &clauses, const ConstructQueue &queue,
-                ConstructQueue::iterator item) {
+                Fortran::lower::pft::Evaluation &eval, bool genNested,
+                mlir::Location loc, const List<Clause> &clauses) {
   Fortran::lower::StatementContext stmtCtx;
   mlir::omp::TargetDataClauseOps clauseOps;
   llvm::SmallVector<mlir::Type> useDeviceTypes;
@@ -1703,9 +1679,9 @@ genTargetDataOp(Fortran::lower::AbstractConverter &converter,
   auto targetDataOp =
       converter.getFirOpBuilder().create<mlir::omp::TargetDataOp>(loc,
                                                                   clauseOps);
-  genBodyOfTargetDataOp(converter, symTable, semaCtx, eval, targetDataOp,
-                        useDeviceTypes, useDeviceLocs, useDeviceSyms, loc,
-                        queue, item);
+  genBodyOfTargetDataOp(converter, symTable, semaCtx, eval, genNested,
+                        targetDataOp, useDeviceTypes, useDeviceLocs,
+                        useDeviceSyms, loc);
   return targetDataOp;
 }
 
@@ -1714,9 +1690,8 @@ static OpTy
 genTargetEnterExitUpdateDataOp(Fortran::lower::AbstractConverter &converter,
                                Fortran::lower::SymMap &symTable,
                                Fortran::semantics::SemanticsContext &semaCtx,
-                               mlir::Location loc, const List<Clause> &clauses,
-                               const ConstructQueue &queue,
-                               ConstructQueue::iterator item) {
+                               mlir::Location loc,
+                               const List<Clause> &clauses) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   Fortran::lower::StatementContext stmtCtx;
 
@@ -1743,9 +1718,8 @@ static mlir::omp::TaskOp
 genTaskOp(Fortran::lower::AbstractConverter &converter,
           Fortran::lower::SymMap &symTable,
           Fortran::semantics::SemanticsContext &semaCtx,
-          Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-          const List<Clause> &clauses, const ConstructQueue &queue,
-          ConstructQueue::iterator item) {
+          Fortran::lower::pft::Evaluation &eval, bool genNested,
+          mlir::Location loc, const List<Clause> &clauses) {
   Fortran::lower::StatementContext stmtCtx;
   mlir::omp::TaskClauseOps clauseOps;
   genTaskClauses(converter, semaCtx, stmtCtx, clauses, loc, clauseOps);
@@ -1753,25 +1727,26 @@ genTaskOp(Fortran::lower::AbstractConverter &converter,
   return genOpWithBody<mlir::omp::TaskOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_task)
+          .setGenNested(genNested)
           .setClauses(&clauses),
-      queue, item, clauseOps);
+      clauseOps);
 }
 
 static mlir::omp::TaskgroupOp
 genTaskgroupOp(Fortran::lower::AbstractConverter &converter,
                Fortran::lower::SymMap &symTable,
                Fortran::semantics::SemanticsContext &semaCtx,
-               Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-               const List<Clause> &clauses, const ConstructQueue &queue,
-               ConstructQueue::iterator item) {
+               Fortran::lower::pft::Evaluation &eval, bool genNested,
+               mlir::Location loc, const List<Clause> &clauses) {
   mlir::omp::TaskgroupClauseOps clauseOps;
   genTaskgroupClauses(converter, semaCtx, clauses, loc, clauseOps);
 
   return genOpWithBody<mlir::omp::TaskgroupOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_taskgroup)
+          .setGenNested(genNested)
           .setClauses(&clauses),
-      queue, item, clauseOps);
+      clauseOps);
 }
 
 static mlir::omp::TaskloopOp
@@ -1779,8 +1754,7 @@ genTaskloopOp(Fortran::lower::AbstractConverter &converter,
               Fortran::lower::SymMap &symTable,
               Fortran::semantics::SemanticsContext &semaCtx,
               Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-              const List<Clause> &clauses, const ConstructQueue &queue,
-              ConstructQueue::iterator item) {
+              const List<Clause> &clauses) {
   TODO(loc, "Taskloop construct");
 }
 
@@ -1789,8 +1763,7 @@ genTaskwaitOp(Fortran::lower::AbstractConverter &converter,
               Fortran::lower::SymMap &symTable,
               Fortran::semantics::SemanticsContext &semaCtx,
               Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-              const List<Clause> &clauses, const ConstructQueue &queue,
-              ConstructQueue::iterator item) {
+              const List<Clause> &clauses) {
   mlir::omp::TaskwaitClauseOps clauseOps;
   genTaskwaitClauses(converter, semaCtx, clauses, loc, clauseOps);
   return converter.getFirOpBuilder().create<mlir::omp::TaskwaitOp>(loc,
@@ -1801,8 +1774,7 @@ static mlir::omp::TaskyieldOp
 genTaskyieldOp(Fortran::lower::AbstractConverter &converter,
                Fortran::lower::SymMap &symTable,
                Fortran::semantics::SemanticsContext &semaCtx,
-               Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-               const ConstructQueue &queue, ConstructQueue::iterator item) {
+               Fortran::lower::pft::Evaluation &eval, mlir::Location loc) {
   return converter.getFirOpBuilder().create<mlir::omp::TaskyieldOp>(loc);
 }
 
@@ -1810,9 +1782,9 @@ static mlir::omp::TeamsOp
 genTeamsOp(Fortran::lower::AbstractConverter &converter,
            Fortran::lower::SymMap &symTable,
            Fortran::semantics::SemanticsContext &semaCtx,
-           Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-           const List<Clause> &clauses, const ConstructQueue &queue,
-           ConstructQueue::iterator item, bool outerCombined = false) {
+           Fortran::lower::pft::Evaluation &eval, bool genNested,
+           mlir::Location loc, const List<Clause> &clauses,
+           bool outerCombined = false) {
   Fortran::lower::StatementContext stmtCtx;
   mlir::omp::TeamsClauseOps clauseOps;
   genTeamsClauses(converter, semaCtx, stmtCtx, clauses, loc, clauseOps);
@@ -1820,9 +1792,10 @@ genTeamsOp(Fortran::lower::AbstractConverter &converter,
   return genOpWithBody<mlir::omp::TeamsOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_teams)
+          .setGenNested(genNested)
           .setOuterCombined(outerCombined)
           .setClauses(&clauses),
-      queue, item, clauseOps);
+      clauseOps);
 }
 
 static mlir::omp::WsloopOp
@@ -1830,8 +1803,7 @@ genWsloopOp(Fortran::lower::AbstractConverter &converter,
             Fortran::lower::SymMap &symTable,
             Fortran::semantics::SemanticsContext &semaCtx,
             Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-            const List<Clause> &clauses, const ConstructQueue &queue,
-            ConstructQueue::iterator item) {
+            const List<Clause> &clauses) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   DataSharingProcessor dsp(converter, semaCtx, clauses, eval);
   dsp.processStep1();
@@ -1872,8 +1844,7 @@ genWsloopOp(Fortran::lower::AbstractConverter &converter,
                      .setClauses(&clauses)
                      .setDataSharingProcessor(&dsp)
                      .setReductions(&reductionSyms, &reductionTypes)
-                     .setGenRegionEntryCb(ivCallback),
-                 queue, item);
+                     .setGenRegionEntryCb(ivCallback));
   return wsloopOp;
 }
 
@@ -1881,13 +1852,13 @@ genWsloopOp(Fortran::lower::AbstractConverter &converter,
 // Code generation functions for composite constructs
 //===----------------------------------------------------------------------===//
 
-static void genCompositeDistributeParallelDo(
-    Fortran::lower::AbstractConverter &converter,
-    Fortran::lower::SymMap &symTable,
-    Fortran::semantics::SemanticsContext &semaCtx,
-    Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-    const List<Clause> &clauses, const ConstructQueue &queue,
-    ConstructQueue::iterator item) {
+static void
+genCompositeDistributeParallelDo(Fortran::lower::AbstractConverter &converter,
+                                 Fortran::lower::SymMap &symTable,
+                                 Fortran::semantics::SemanticsContext &semaCtx,
+                                 Fortran::lower::pft::Evaluation &eval,
+                                 const List<Clause> &clauses,
+                                 mlir::Location loc) {
   TODO(loc, "Composite DISTRIBUTE PARALLEL DO");
 }
 
@@ -1895,9 +1866,8 @@ static void genCompositeDistributeParallelDoSimd(
     Fortran::lower::AbstractConverter &converter,
     Fortran::lower::SymMap &symTable,
     Fortran::semantics::SemanticsContext &semaCtx,
-    Fortran::lower::pft::Evaluation &eval, mlir::Location loc,
-    const List<Clause> &clauses, const ConstructQueue &queue,
-    ConstructQueue::iterator item) {
+    Fortran::lower::pft::Evaluation &eval, const List<Clause> &clauses,
+    mlir::Location loc) {
   TODO(loc, "Composite DISTRIBUTE PARALLEL DO SIMD");
 }
 
@@ -1906,9 +1876,7 @@ genCompositeDistributeSimd(Fortran::lower::AbstractConverter &converter,
                            Fortran::lower::SymMap &symTable,
                            Fortran::semantics::SemanticsContext &semaCtx,
                            Fortran::lower::pft::Evaluation &eval,
-                           mlir::Location loc, const List<Clause> &clauses,
-                           const ConstructQueue &queue,
-                           ConstructQueue::iterator item) {
+                           const List<Clause> &clauses, mlir::Location loc) {
   TODO(loc, "Composite DISTRIBUTE SIMD");
 }
 
@@ -1916,9 +1884,8 @@ static void genCompositeDoSimd(Fortran::lower::AbstractConverter &converter,
                                Fortran::lower::SymMap &symTable,
                                Fortran::semantics::SemanticsContext &semaCtx,
                                Fortran::lower::pft::Evaluation &eval,
-                               mlir::Location loc, const List<Clause> &clauses,
-                               const ConstructQueue &queue,
-                               ConstructQueue::iterator item) {
+                               const List<Clause> &clauses,
+                               mlir::Location loc) {
   ClauseProcessor cp(converter, semaCtx, clauses);
   cp.processTODO<clause::Aligned, clause::Allocate, clause::Linear,
                  clause::Order, clause::Safelen, clause::Simdlen>(
@@ -1931,7 +1898,7 @@ static void genCompositeDoSimd(Fortran::lower::AbstractConverter &converter,
   // When support for vectorization is enabled, then we need to add handling of
   // if clause. Currently if clause can be skipped because we always assume
   // SIMD length = 1.
-  genWsloopOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
+  genWsloopOp(converter, symTable, semaCtx, eval, loc, clauses);
 }
 
 static void
@@ -1939,128 +1906,10 @@ genCompositeTaskloopSimd(Fortran::lower::AbstractConverter &converter,
                          Fortran::lower::SymMap &symTable,
                          Fortran::semantics::SemanticsContext &semaCtx,
                          Fortran::lower::pft::Evaluation &eval,
-                         mlir::Location loc, const List<Clause> &clauses,
-                         const ConstructQueue &queue,
-                         ConstructQueue::iterator item) {
+                         const List<Clause> &clauses, mlir::Location loc) {
   TODO(loc, "Composite TASKLOOP SIMD");
 }
 
-//===----------------------------------------------------------------------===//
-// Dispatch
-//===----------------------------------------------------------------------===//
-
-static void genOMPDispatch(Fortran::lower::AbstractConverter &converter,
-                           Fortran::lower::SymMap &symTable,
-                           Fortran::semantics::SemanticsContext &semaCtx,
-                           Fortran::lower::pft::Evaluation &eval,
-                           mlir::Location loc, const ConstructQueue &queue,
-                           ConstructQueue::iterator item) {
-  assert(item != queue.end());
-  const List<Clause> &clauses = item->clauses;
-
-  switch (llvm::omp::Directive dir = item->id) {
-  case llvm::omp::Directive::OMPD_distribute:
-    genDistributeOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                    item);
-    break;
-  case llvm::omp::Directive::OMPD_do:
-    genWsloopOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_loop:
-  case llvm::omp::Directive::OMPD_masked:
-  case llvm::omp::Directive::OMPD_tile:
-  case llvm::omp::Directive::OMPD_unroll:
-    TODO(loc, "Unhandled loop directive (" +
-                  llvm::omp::getOpenMPDirectiveName(dir) + ")");
-    break;
-  case llvm::omp::Directive::OMPD_master:
-    genMasterOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_ordered:
-    genOrderedRegionOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                       item);
-    break;
-  case llvm::omp::Directive::OMPD_parallel:
-    genParallelOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item,
-                  /*outerCombined=*/false);
-    break;
-  case llvm::omp::Directive::OMPD_sections:
-    genSectionsOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                  item);
-    break;
-  case llvm::omp::Directive::OMPD_simd:
-    genSimdOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_single:
-    genSingleOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_target:
-    genTargetOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item,
-                /*outerCombined=*/false);
-    break;
-  case llvm::omp::Directive::OMPD_target_data:
-    genTargetDataOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                    item);
-    break;
-  case llvm::omp::Directive::OMPD_target_enter_data:
-    genTargetEnterExitUpdateDataOp<mlir::omp::TargetEnterDataOp>(
-        converter, symTable, semaCtx, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_target_exit_data:
-    genTargetEnterExitUpdateDataOp<mlir::omp::TargetExitDataOp>(
-        converter, symTable, semaCtx, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_target_update:
-    genTargetEnterExitUpdateDataOp<mlir::omp::TargetUpdateOp>(
-        converter, symTable, semaCtx, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_task:
-    genTaskOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_taskgroup:
-    genTaskgroupOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                   item);
-    break;
-  case llvm::omp::Directive::OMPD_taskloop:
-    genTaskloopOp(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                  item);
-    break;
-  case llvm::omp::Directive::OMPD_teams:
-    genTeamsOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  // case llvm::omp::Directive::OMPD_workdistribute:
-  case llvm::omp::Directive::OMPD_workshare:
-    // FIXME: Workshare is not a commonly used OpenMP construct, an
-    // implementation for this feature will come later. For the codes
-    // that use this construct, add a single construct for now.
-    genSingleOp(converter, symTable, semaCtx, eval, loc, clauses, queue, item);
-    break;
-  // Composite constructs
-  case llvm::omp::Directive::OMPD_distribute_parallel_do:
-    genCompositeDistributeParallelDo(converter, symTable, semaCtx, eval, loc,
-                                     clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_distribute_parallel_do_simd:
-    genCompositeDistributeParallelDoSimd(converter, symTable, semaCtx, eval,
-                                         loc, clauses, queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_distribute_simd:
-    genCompositeDistributeSimd(converter, symTable, semaCtx, eval, loc, clauses,
-                               queue, item);
-    break;
-  case llvm::omp::Directive::OMPD_do_simd:
-    genCompositeDoSimd(converter, symTable, semaCtx, eval, loc, clauses, queue,
-                       item);
-    break;
-  case llvm::omp::Directive::OMPD_taskloop_simd:
-    genCompositeTaskloopSimd(converter, symTable, semaCtx, eval, loc, clauses,
-                             queue, item);
-    break;
-  default:
-    break;
-  }
-}
-
 //===----------------------------------------------------------------------===//
 // OpenMPDeclarativeConstruct visitors
 //===----------------------------------------------------------------------===//
@@ -2171,47 +2020,36 @@ static void genOMP(Fortran::lower::AbstractConverter &converter,
       semaCtx);
   mlir::Location currentLocation = converter.genLocation(directive.source);
 
-  ConstructQueue queue{
-      buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
-                          eval, directive.source, directive.v, clauses)};
-
   switch (directive.v) {
   default:
     break;
   case llvm::omp::Directive::OMPD_barrier:
-    genBarrierOp(converter, symTable, semaCtx, eval, currentLocation, queue,
-                 queue.begin());
+    genBarrierOp(converter, symTable, semaCtx, eval, currentLocation);
     break;
   case llvm::omp::Directive::OMPD_taskwait:
-    genTaskwaitOp(converter, symTable, semaCtx, eval, currentLocation, clauses,
-                  queue, queue.begin());
+    genTaskwaitOp(converter, symTable, semaCtx, eval, currentLocation, clauses);
     break;
   case llvm::omp::Directive::OMPD_taskyield:
-    genTaskyieldOp(converter, symTable, semaCtx, eval, currentLocation, queue,
-                   queue.begin());
+    genTaskyieldOp(converter, symTable, semaCtx, eval, currentLocation);
     break;
   case llvm::omp::Directive::OMPD_target_data:
-    genTargetDataOp(converter, symTable, semaCtx, eval, currentLocation,
-                    clauses, queue, queue.begin());
+    genTargetDataOp(converter, symTable, semaCtx, eval, /*genNested=*/true,
+                    currentLocation, clauses);
     break;
   case llvm::omp::Directive::OMPD_target_enter_data:
     genTargetEnterExitUpdateDataOp<mlir::omp::TargetEnterDataOp>(
-        converter, symTable, semaCtx, currentLocation, clauses, queue,
-        queue.begin());
+        converter, symTable, semaCtx, currentLocation, clauses);
     break;
   case llvm::omp::Directive::OMPD_target_exit_data:
     genTargetEnterExitUpdateDataOp<mlir::omp::TargetExitDataOp>(
-        converter, symTable, semaCtx, currentLocation, clauses, queue,
-        queue.begin());
+        converter, symTable, semaCtx, currentLocation, clauses);
     break;
   case llvm::omp::Directive::OMPD_target_update:
     genTargetEnterExitUpdateDataOp<mlir::omp::TargetUpdateOp>(
-        converter, symTable, semaCtx, currentLocation, clauses, queue,
-        queue.begin());
+        converter, symTable, semaCtx, currentLocation, clauses);
     break;
   case llvm::omp::Directive::OMPD_ordered:
-    genOrderedOp(converter, symTable, semaCtx, eval, currentLocation, clauses,
-                 queue, queue.begin());
+    genOrderedOp(converter, symTable, semaCtx, eval, currentLocation, clauses);
     break;
   }
 }
@@ -2235,12 +2073,8 @@ genOMP(Fortran::lower::AbstractConverter &converter,
                             [&](auto &&s) { return makeClause(s.v, semaCtx); })
                  : List<Clause>{};
   mlir::Location currentLocation = converter.genLocation(verbatim.source);
-
-  ConstructQueue queue{buildConstructQueue(
-      converter.getFirOpBuilder().getModule(), semaCtx, eval, verbatim.source,
-      llvm::omp::Directive::OMPD_flush, clauses)};
   genFlushOp(converter, symTable, semaCtx, eval, currentLocation, objects,
-             clauses, queue, queue.begin());
+             clauses);
 }
 
 static void
@@ -2383,15 +2217,75 @@ genOMP(Fortran::lower::AbstractConverter &converter,
     }
   }
 
-  llvm::omp::Directive directive =
-      std::get<parser::OmpBlockDirective>(beginBlockDirective.t).v;
-  const parser::CharBlock &source =
-      std::get<parser::OmpBlockDirective>(beginBlockDirective.t).source;
-  ConstructQueue queue{
-      buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
-                          eval, source, directive, clauses)};
-  genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
-                 queue.begin());
+  std::optional<llvm::omp::Directive> nextDir = origDirective;
+  bool outermostLeafConstruct = true;
+  while (nextDir) {
+    llvm::omp::Directive leafDir;
+    std::tie(leafDir, nextDir) = splitCombinedDirective(*nextDir);
+    const bool genNested = !nextDir;
+    const bool outerCombined = outermostLeafConstruct && nextDir.has_value();
+    switch (leafDir) {
+    case llvm::omp::Directive::OMPD_master:
+      // 2.16 MASTER construct.
+      genMasterOp(converter, symTable, semaCtx, eval, genNested,
+                  currentLocation);
+      break;
+    case llvm::omp::Directive::OMPD_ordered:
+      // 2.17.9 ORDERED construct.
+      genOrderedRegionOp(converter, symTable, semaCtx, eval, genNested,
+                         currentLocation, clauses);
+      break;
+    case llvm::omp::Directive::OMPD_parallel:
+      // 2.6 PARALLEL construct.
+      genParallelOp(converter, symTable, semaCtx, eval, genNested,
+                    currentLocation, clauses, outerCombined);
+      break;
+    case llvm::omp::Directive::OMPD_single:
+      // 2.8.2 SINGLE construct.
+      genSingleOp(converter, symTable, semaCtx, eval, genNested,
+                  currentLocation, clauses);
+      break;
+    case llvm::omp::Directive::OMPD_target:
+      // 2.12.5 TARGET construct.
+      genTargetOp(converter, symTable, semaCtx, eval, genNested,
+                  currentLocation, clauses, outerCombined);
+      break;
+    case llvm::omp::Directive::OMPD_target_data:
+      // 2.12.2 TARGET DATA construct.
+      genTargetDataOp(converter, symTable, semaCtx, eval, genNested,
+                      currentLocation, clauses);
+      break;
+    case llvm::omp::Directive::OMPD_task:
+      // 2.10.1 TASK construct.
+      genTaskOp(converter, symTable, semaCtx, eval, genNested, currentLocation,
+                clauses);
+      break;
+    case llvm::omp::Directive::OMPD_taskgroup:
+      // 2.17.6 TASKGROUP construct.
+      genTaskgroupOp(converter, symTable, semaCtx, eval, genNested,
+                     currentLocation, clauses);
+      break;
+    case llvm::omp::Directive::OMPD_teams:
+      // 2.7 TEAMS construct.
+      // FIXME Pass the outerCombined argument or rename it to better describe
+      // what it represents if it must always be `false` in this context.
+      genTeamsOp(converter, symTable, semaCtx, eval, genNested, currentLocation,
+                 clauses);
+      break;
+    case llvm::omp::Directive::OMPD_workshare:
+      // 2.8.3 WORKSHARE construct.
+      // FIXME: Workshare is not a commonly used OpenMP construct, an
+      // implementation for this feature will come later. For the codes
+      // that use this construct, add a single construct for now.
+      genSingleOp(converter, symTable, semaCtx, eval, genNested,
+                  currentLocation, clauses);
+      break;
+    default:
+      llvm_unreachable("Unexpected block construct");
+      break;
+    }
+    outermostLeafConstruct = false;
+  }
 }
 
 static void
@@ -2404,15 +2298,10 @@ genOMP(Fortran::lower::AbstractConverter &converter,
       std::get<Fortran::parser::OmpCriticalDirective>(criticalConstruct.t);
   List<Clause> clauses =
       makeClauses(std::get<Fortran::parser::OmpClauseList>(cd.t), semaCtx);
-
-  ConstructQueue queue{buildConstructQueue(
-      converter.getFirOpBuilder().getModule(), semaCtx, eval, cd.source,
-      llvm::omp::Directive::OMPD_critical, clauses)};
-
   const auto &name = std::get<std::optional<Fortran::parser::Name>>(cd.t);
   mlir::Location currentLocation = converter.getCurrentLocation();
-  genCriticalOp(converter, symTable, semaCtx, eval, currentLocation, clauses,
-                queue, queue.begin(), name);
+  genCriticalOp(converter, symTable, semaCtx, eval, /*genNested=*/true,
+                currentLocation, clauses, name);
 }
 
 static void
@@ -2433,6 +2322,14 @@ static void genOMP(Fortran::lower::AbstractConverter &converter,
       std::get<Fortran::parser::OmpBeginLoopDirective>(loopConstruct.t);
   List<Clause> clauses = makeClauses(
       std::get<Fortran::parser::OmpClauseList>(beginLoopDirective.t), semaCtx);
+  mlir::Location currentLocation =
+      converter.genLocation(beginLoopDirective.source);
+  const auto origDirective =
+      std::get<Fortran::parser::OmpLoopDirective>(beginLoopDirective.t).v;
+
+  assert(llvm::omp::loopConstructSet.test(origDirective) &&
+         "Expected loop construct");
+
   if (auto &endLoopDirective =
           std::get<std::optional<Fortran::parser::OmpEndLoopDirective>>(
               loopConstruct.t)) {
@@ -2441,18 +2338,101 @@ static void genOMP(Fortran::lower::AbstractConverter &converter,
         semaCtx));
   }
 
-  mlir::Location currentLocation =
-      converter.genLocation(beginLoopDirective.source);
-
-  llvm::omp::Directive directive =
-      std::get<parser::OmpLoopDirective>(beginLoopDirective.t).v;
-  const parser::CharBlock &source =
-      std::get<parser::OmpLoopDirective>(beginLoopDirective.t).source;
-  ConstructQueue queue{
-      buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
-                          eval, source, directive, clauses)};
-  genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
-                 queue.begin());
+  std::optional<llvm::omp::Directive> nextDir = origDirective;
+  while (nextDir) {
+    llvm::omp::Directive leafDir;
+    std::tie(leafDir, nextDir) = splitCombinedDirective(*nextDir);
+    if (llvm::omp::compositeConstructSet.test(leafDir)) {
+      assert(!nextDir && "Composite construct cannot be split");
+      switch (leafDir) {
+      case llvm::omp::Directive::OMPD_distribute_parallel_do:
+        // 2.9.4.3 DISTRIBUTE PARALLEL Worksharing-Loop construct.
+        genCompositeDistributeParallelDo(converter, symTable, semaCtx, eval,
+                                         clauses, currentLocation);
+        break;
+      case llvm::omp::Directive::OMPD_distribute_parallel_do_simd:
+        // 2.9.4.4 DISTRIBUTE PARALLEL Worksharing-Loop SIMD construct.
+        genCompositeDistributeParallelDoSimd(converter, symTable, semaCtx, eval,
+                                             clauses, currentLocation);
+        break;
+      case llvm::omp::Directive::OMPD_distribute_simd:
+        // 2.9.4.2 DISTRIBUTE SIMD construct.
+        genCompositeDistributeSimd(converter, symTable, semaCtx, eval, clauses,
+                                   currentLocation);
+        break;
+      case llvm::omp::Directive::OMPD_do_simd:
+        // 2.9.3.2 Worksharing-Loop SIMD construct.
+        genCompositeDoSimd(converter, symTable, semaCtx, eval, clauses,
+                           currentLocation);
+        break;
+      case llvm::omp::Directive::OMPD_taskloop_simd:
+        // 2.10.3 TASKLOOP SIMD construct.
+        genCompositeTaskloopSimd(converter, symTable, semaCtx, eval, clauses,
+                                 currentLocation);
+        break;
+      default:
+        llvm_unreachable("Unexpected composite construct");
+      }
+    } else {
+      const bool genNested = !nextDir;
+      switch (leafDir) {
+      case llvm::omp::Directive::OMPD_distribute:
+        // 2.9.4.1 DISTRIBUTE construct.
+        genDistributeOp(converter, symTable, semaCtx, eval, genNested,
+                        currentLocation, clauses);
+        break;
+      case llvm::omp::Directive::OMPD_do:
+        // 2.9.2 Worksharing-Loop construct.
+        genWsloopOp(converter, symTable, semaCtx, eval, currentLocation,
+                    clauses);
+        break;
+      case llvm::omp::Directive::OMPD_parallel:
+        // 2.6 PARALLEL construct.
+        // FIXME This is not necessarily always the outer leaf construct of a
+        // combined construct in this constext (e.g. distribute parallel do).
+        // Maybe rename the argument if it represents something else or
+        // initialize it properly.
+        genParallelOp(converter, symTable, semaCtx, eval, genNested,
+                      currentLocation, clauses,
+                      /*outerCombined=*/true);
+        break;
+      case llvm::omp::Directive::OMPD_simd:
+        // 2.9.3.1 SIMD construct.
+        genSimdOp(converter, symTable, semaCtx, eval, currentLocation, clauses);
+        break;
+      case llvm::omp::Directive::OMPD_target:
+        // 2.12.5 TARGET construct.
+        genTargetOp(converter, symTable, semaCtx, eval, genNested,
+                    currentLocation, clauses, /*outerCombined=*/true);
+        break;
+      case llvm::omp::Directive::OMPD_taskloop:
+        // 2.10.2 TASKLOOP construct.
+        genTaskloopOp(converter, symTable, semaCtx, eval, currentLocation,
+                      clauses);
+        break;
+      case llvm::omp::Directive::OMPD_teams:
+        // 2.7 TEAMS construct.
+        // FIXME This is not necessarily always the outer leaf construct of a
+        // combined construct in this constext (e.g. target teams distribute).
+        // Maybe rename the argument if it represents something else or
+        // initialize it properly.
+        genTeamsOp(converter, symTable, semaCtx, eval, genNested,
+                   currentLocation, clauses, /*outerCombined=*/true);
+        break;
+      case llvm::omp::Directive::OMPD_loop:
+      case llvm::omp::Directive::OMPD_masked:
+      case llvm::omp::Directive::OMPD_master:
+      case llvm::omp::Directive::OMPD_tile:
+      case llvm::omp::Directive::OMPD_unroll:
+        TODO(currentLocation, "Unhandled loop directive (" +
+                                  llvm::omp::getOpenMPDirectiveName(leafDir) +
+                                  ")");
+        break;
+      default:
+        llvm_unreachable("Unexpected loop construct");
+      }
+    }
+  }
 }
 
 static void
@@ -2461,12 +2441,8 @@ genOMP(Fortran::lower::AbstractConverter &converter,
        Fortran::semantics::SemanticsContext &semaCtx,
        Fortran::lower::pft::Evaluation &eval,
        const Fortran::parser::OpenMPSectionConstruct &sectionConstruct) {
-  mlir::Location loc = converter.getCurrentLocation();
-  ConstructQueue queue{buildConstructQueue(
-      converter.getFirOpBuilder().getModule(), semaCtx, eval,
-      sectionConstruct.source, llvm::omp::Directive::OMPD_section, {})};
-  genSectionOp(converter, symTable, semaCtx, eval, loc,
-               /*clauses=*/{}, queue, queue.begin());
+  // SECTION constructs are handled as a part of SECTIONS.
+  llvm_unreachable("Unexpected standalone OMP SECTION");
 }
 
 static void
@@ -2485,17 +2461,77 @@ genOMP(Fortran::lower::AbstractConverter &converter,
   clauses.append(makeClauses(
       std::get<Fortran::parser::OmpClauseList>(endSectionsDirective.t),
       semaCtx));
+
+  // Process clauses before optional omp.parallel, so that new variables are
+  // allocated outside of the parallel region
   mlir::Location currentLocation = converter.getCurrentLocation();
+  mlir::omp::SectionsClauseOps clauseOps;
+  genSectionsClauses(converter, semaCtx, clauses, currentLocation, clauseOps);
+
+  // Parallel wrapper of PARALLEL SECTIONS construct
+  llvm::omp::Directive dir =
+      std::get<Fortran::parser::OmpSectionsDirective>(beginSectionsDirective.t)
+          .v;
+  if (dir == llvm::omp::Directive::OMPD_parallel_sections) {
+    genParallelOp(converter, symTable, semaCtx, eval,
+                  /*genNested=*/false, currentLocation, clauses,
+                  /*outerCombined=*/true);
+  }
+
+  // Insert privatizations before SECTIONS
+  symTable.pushScope();
+  DataSharingProcessor dsp(converter, semaCtx, clauses, eval);
+  dsp.processStep1();
+
+  // SECTIONS construct.
+  mlir::omp::SectionsOp sectionsOp = genSectionsOp(
+      converter, symTable, semaCtx, eval, currentLocation, clauseOps);
+
+  // Generate nested SECTION operations recursively.
+  const auto &sectionBlocks =
+      std::get<Fortran::parser::OmpSectionBlocks>(sectionsConstruct.t);
+  auto &firOpBuilder = converter.getFirOpBuilder();
+  auto ip = firOpBuilder.saveInsertionPoint();
+  mlir::omp::SectionOp lastSectionOp;
+  for (const auto &[nblock, neval] :
+       llvm::zip(sectionBlocks.v, eval.getNestedEvaluations())) {
+    symTable.pushScope();
+    lastSectionOp = genSectionOp(converter, symTable, semaCtx, neval,
+                                 /*genNested=*/true, currentLocation);
+    symTable.popScope();
+    firOpBuilder.restoreInsertionPoint(ip);
+  }
+
+  // For `omp.sections`, lastprivatized variables occur in
+  // lexically final `omp.section` operation.
+  bool hasLastPrivate = false;
+  if (lastSectionOp) {
+    for (const Clause &clause : clauses) {
+      if (const auto *lastPrivate =
+              std::get_if<clause::Lastprivate>(&clause.u)) {
+        hasLastPrivate = true;
+        firOpBuilder.setInsertionPoint(
+            lastSectionOp.getRegion().back().getTerminator());
+        mlir::OpBuilder::InsertPoint lastPrivIP =
+            converter.getFirOpBuilder().saveInsertionPoint();
+        const auto &objList = std::get<1>(lastPrivate->t);
+        for (const Object &obj : objList) {
+          Fortran::semantics::Symbol *sym = obj.id();
+          converter.copyHostAssociateVar(*sym, &lastPrivIP);
+        }
+      }
+    }
+  }
 
-  llvm::omp::Directive directive =
-      std::get<parser::OmpSectionsDirective>(beginSectionsDirective.t).v;
-  const parser::CharBlock &source =
-      std::get<parser::OmpSectionsDirective>(beginSectionsDirective.t).source;
-  ConstructQueue queue{
-      buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
-                          eval, source, directive, clauses)};
-  genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
-                 queue.begin());
+  // Perform DataSharingProcessor's step2 out of SECTIONS
+  firOpBuilder.setInsertionPointAfter(sectionsOp.getOperation());
+  dsp.processStep2(sectionsOp, false);
+  // Emit implicit barrier to synchronize threads and avoid data
+  // races on post-update of lastprivate variables when `nowait`
+  // clause is present.
+  if (clauseOps.nowaitAttr && hasLastPrivate)
+    firOpBuilder.create<mlir::omp::BarrierOp>(converter.getCurrentLocation());
+  symTable.popScope();
 }
 
 static void genOMP(Fortran::lower::AbstractConverter &converter,

diff  --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index cb1d1a5a7f3dd..eed63b226133a 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -51,6 +51,12 @@ int64_t getCollapseValue(const List<Clause> &clauses) {
   return 1;
 }
 
+uint32_t getOpenMPVersion(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");
+}
+
 void genObjectList(const ObjectList &objects,
                    Fortran::lower::AbstractConverter &converter,
                    llvm::SmallVectorImpl<mlir::Value> &operands) {

diff  --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 345ce55620ee9..8fbb18fa8656f 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -93,6 +93,7 @@ void gatherFuncAndVarSyms(
     llvm::SmallVectorImpl<DeclareTargetCapturePair> &symbolAndClause);
 
 int64_t getCollapseValue(const List<Clause> &clauses);
+uint32_t getOpenMPVersion(mlir::ModuleOp mod);
 
 Fortran::semantics::Symbol *
 getOmpObjectSymbol(const Fortran::parser::OmpObject &ompObject);

diff  --git a/flang/test/Lower/OpenMP/default-clause-byref.f90 b/flang/test/Lower/OpenMP/default-clause-byref.f90
index 7cc2bc2e0c710..62ba67e5962f4 100644
--- a/flang/test/Lower/OpenMP/default-clause-byref.f90
+++ b/flang/test/Lower/OpenMP/default-clause-byref.f90
@@ -161,12 +161,12 @@ subroutine nested_default_clause_tests
 !CHECK: %[[Z:.*]] = fir.alloca i32 {bindc_name = "z", uniq_name = "_QFnested_default_clause_testsEz"}
 !CHECK: %[[Z_DECL:.*]]:2 = hlfir.declare %[[Z]] {uniq_name = "_QFnested_default_clause_testsEz"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: omp.parallel   {
-!CHECK: %[[PRIVATE_Y:.*]] = fir.alloca i32 {bindc_name = "y", pinned, uniq_name = "_QFnested_default_clause_testsEy"}
-!CHECK: %[[PRIVATE_Y_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Y]] {uniq_name = "_QFnested_default_clause_testsEy"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_testsEx"}
 !CHECK: %[[PRIVATE_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_X]] {uniq_name = "_QFnested_default_clause_testsEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[TEMP:.*]] = fir.load %[[X_DECL]]#0 : !fir.ref<i32>
 !CHECK: hlfir.assign %[[TEMP]] to %[[PRIVATE_X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
+!CHECK: %[[PRIVATE_Y:.*]] = fir.alloca i32 {bindc_name = "y", pinned, uniq_name = "_QFnested_default_clause_testsEy"}
+!CHECK: %[[PRIVATE_Y_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Y]] {uniq_name = "_QFnested_default_clause_testsEy"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_testsEz"}
 !CHECK: %[[PRIVATE_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Z]] {uniq_name = "_QFnested_default_clause_testsEz"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_K:.*]] = fir.alloca i32 {bindc_name = "k", pinned, uniq_name = "_QFnested_default_clause_testsEk"}
@@ -221,7 +221,6 @@ subroutine nested_default_clause_tests
     
     
 !CHECK: omp.parallel {
-!CHECK: %[[PRIVATE_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_testsEx"}
 !CHECK: %[[PRIVATE_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_X]] {uniq_name = "_QFnested_default_clause_testsEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_Y:.*]] = fir.alloca i32 {bindc_name = "y", pinned, uniq_name = "_QFnested_default_clause_testsEy"}
 !CHECK: %[[PRIVATE_Y_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Y]] {uniq_name = "_QFnested_default_clause_testsEy"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)

diff  --git a/flang/test/Lower/OpenMP/default-clause.f90 b/flang/test/Lower/OpenMP/default-clause.f90
index 843ee6bb7910b..a90f0f4ef5f84 100644
--- a/flang/test/Lower/OpenMP/default-clause.f90
+++ b/flang/test/Lower/OpenMP/default-clause.f90
@@ -160,12 +160,12 @@ end program default_clause_lowering
 !CHECK: %[[Z:.*]] = fir.alloca i32 {bindc_name = "z", uniq_name = "_QFnested_default_clause_test1Ez"}
 !CHECK: %[[Z_DECL:.*]]:2 = hlfir.declare %[[Z]] {uniq_name = "_QFnested_default_clause_test1Ez"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: omp.parallel   {
-!CHECK: %[[PRIVATE_Y:.*]] = fir.alloca i32 {bindc_name = "y", pinned, uniq_name = "_QFnested_default_clause_test1Ey"}
-!CHECK: %[[PRIVATE_Y_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Y]] {uniq_name = "_QFnested_default_clause_test1Ey"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_test1Ex"}
 !CHECK: %[[PRIVATE_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_X]] {uniq_name = "_QFnested_default_clause_test1Ex"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[TEMP:.*]] = fir.load %[[X_DECL]]#0 : !fir.ref<i32>
 !CHECK: hlfir.assign %[[TEMP]] to %[[PRIVATE_X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
+!CHECK: %[[PRIVATE_Y:.*]] = fir.alloca i32 {bindc_name = "y", pinned, uniq_name = "_QFnested_default_clause_test1Ey"}
+!CHECK: %[[PRIVATE_Y_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Y]] {uniq_name = "_QFnested_default_clause_test1Ey"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_test1Ez"}
 !CHECK: %[[PRIVATE_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_Z]] {uniq_name = "_QFnested_default_clause_test1Ez"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[PRIVATE_K:.*]] = fir.alloca i32 {bindc_name = "k", pinned, uniq_name = "_QFnested_default_clause_test1Ek"}

diff  --git a/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90 b/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
index e6ee75c8a5bef..b7f11c8c722f7 100644
--- a/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
+++ b/flang/test/Lower/OpenMP/parallel-lastprivate-clause-scalar.f90
@@ -145,10 +145,10 @@ subroutine mult_lastprivate_int(arg1, arg2)
 !CHECK: %[[ARG1_DECL:.*]]:2 = hlfir.declare %[[ARG1]] dummy_scope %{{[0-9]+}} {uniq_name = "_QFmult_lastprivate_int2Earg1"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: %[[ARG2_DECL:.*]]:2 = hlfir.declare %[[ARG2]] dummy_scope %{{[0-9]+}} {uniq_name = "_QFmult_lastprivate_int2Earg2"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: omp.parallel  {
-!CHECK-DAG: %[[CLONE2:.*]] = fir.alloca i32 {bindc_name = "arg2"
-!CHECK-DAG: %[[CLONE2_DECL:.*]]:2 = hlfir.declare %[[CLONE2]] {uniq_name = "_QFmult_lastprivate_int2Earg2"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK-DAG: %[[CLONE1:.*]] = fir.alloca i32 {bindc_name = "arg1"
 !CHECK-DAG: %[[CLONE1_DECL:.*]]:2 = hlfir.declare %[[CLONE1]] {uniq_name = "_QFmult_lastprivate_int2Earg1"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+!CHECK-DAG: %[[CLONE2:.*]] = fir.alloca i32 {bindc_name = "arg2"
+!CHECK-DAG: %[[CLONE2_DECL:.*]]:2 = hlfir.declare %[[CLONE2]] {uniq_name = "_QFmult_lastprivate_int2Earg2"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
 !CHECK: omp.wsloop {
 !CHECK-NEXT: omp.loop_nest (%[[INDX_WS:.*]]) : {{.*}} {
 

diff  --git a/llvm/include/llvm/Frontend/OpenMP/ClauseT.h b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h
index 07c95497b7a41..daef02bcfc9a3 100644
--- a/llvm/include/llvm/Frontend/OpenMP/ClauseT.h
+++ b/llvm/include/llvm/Frontend/OpenMP/ClauseT.h
@@ -178,12 +178,6 @@ template <typename T> using ListT = llvm::SmallVector<T, 0>;
 // provide their own specialization that conforms to the above requirements.
 template <typename IdType, typename ExprType> struct ObjectT;
 
-// By default, object equality is only determined by its identity.
-template <typename I, typename E>
-bool operator==(const ObjectT<I, E> &o1, const ObjectT<I, E> &o2) {
-  return o1.id() == o2.id();
-}
-
 template <typename I, typename E> using ObjectListT = ListT<ObjectT<I, E>>;
 
 using DirectiveName = llvm::omp::Directive;
@@ -270,32 +264,6 @@ struct ReductionIdentifierT {
 
 template <typename T, typename I, typename E> //
 using IteratorT = ListT<IteratorSpecifierT<T, I, E>>;
-
-template <typename T>
-std::enable_if_t<T::EmptyTrait::value, bool> operator==(const T &a,
-                                                        const T &b) {
-  return true;
-}
-template <typename T>
-std::enable_if_t<T::IncompleteTrait::value, bool> operator==(const T &a,
-                                                             const T &b) {
-  return true;
-}
-template <typename T>
-std::enable_if_t<T::WrapperTrait::value, bool> operator==(const T &a,
-                                                          const T &b) {
-  return a.v == b.v;
-}
-template <typename T>
-std::enable_if_t<T::TupleTrait::value, bool> operator==(const T &a,
-                                                        const T &b) {
-  return a.t == b.t;
-}
-template <typename T>
-std::enable_if_t<T::UnionTrait::value, bool> operator==(const T &a,
-                                                        const T &b) {
-  return a.u == b.u;
-}
 } // namespace type
 
 template <typename T> using ListT = type::ListT<T>;
@@ -317,8 +285,6 @@ ListT<ResultTy> makeList(ContainerTy &&container, FunctionTy &&func) {
 }
 
 namespace clause {
-using type::operator==;
-
 // V5.2: [8.3.1] `assumption` clauses
 template <typename T, typename I, typename E> //
 struct AbsentT {
@@ -760,7 +726,7 @@ struct LinearT {
   ENUM(LinearModifier, Ref, Val, Uval);
 
   using TupleTrait = std::true_type;
-  // Step == nullopt means 1.
+  // Step == nullptr means 1.
   std::tuple<OPT(StepSimpleModifier), OPT(StepComplexModifier),
              OPT(LinearModifier), List>
       t;
@@ -1176,11 +1142,9 @@ struct UsesAllocatorsT {
   using MemSpace = E;
   using TraitsArray = ObjectT<I, E>;
   using Allocator = E;
-  struct AllocatorSpec { // Not a spec name
-    using TupleTrait = std::true_type;
-    std::tuple<OPT(MemSpace), OPT(TraitsArray), Allocator> t;
-  };
-  using Allocators = ListT<AllocatorSpec>; // Not a spec name
+  using AllocatorSpec =
+      std::tuple<OPT(MemSpace), OPT(TraitsArray), Allocator>; // Not a spec name
+  using Allocators = ListT<AllocatorSpec>;                    // Not a spec name
   using WrapperTrait = std::true_type;
   Allocators v;
 };
@@ -1268,9 +1232,8 @@ using UnionOfAllClausesT = typename type::Union< //
     UnionClausesT<T, I, E>,                      //
     WrapperClausesT<T, I, E>                     //
     >::type;
-} // namespace clause
 
-using type::operator==;
+} // namespace clause
 
 // The variant wrapper that encapsulates all possible specific clauses.
 // The `Extras` arguments are additional types representing local extensions
@@ -1297,11 +1260,6 @@ struct ClauseT {
   VariantTy u;
 };
 
-template <typename ClauseType> struct DirectiveWithClauses {
-  llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
-  tomp::type::ListT<ClauseType> clauses;
-};
-
 } // namespace tomp
 
 #undef OPT

diff  --git a/llvm/include/llvm/Frontend/OpenMP/ConstructCompositionT.h b/llvm/include/llvm/Frontend/OpenMP/ConstructCompositionT.h
deleted file mode 100644
index 7a4ed92a10703..0000000000000
--- a/llvm/include/llvm/Frontend/OpenMP/ConstructCompositionT.h
+++ /dev/null
@@ -1,403 +0,0 @@
-//===- ConstructCompositionT.h -- Composing compound constructs -----------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-// Given a list of leaf construct, each with a set of clauses, generate the
-// compound construct whose leaf constructs are the given list, and whose clause
-// list is the merged lists of individual leaf clauses.
-//
-// *** At the moment it assumes that the individual constructs and their clauses
-// *** are a subset of those created by splitting a valid compound construct.
-//===----------------------------------------------------------------------===//
-#ifndef LLVM_FRONTEND_OPENMP_CONSTRUCTCOMPOSITIONT_H
-#define LLVM_FRONTEND_OPENMP_CONSTRUCTCOMPOSITIONT_H
-
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/BitVector.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Frontend/OpenMP/ClauseT.h"
-#include "llvm/Frontend/OpenMP/OMP.h"
-
-#include <iterator>
-#include <optional>
-#include <tuple>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-
-namespace tomp {
-template <typename ClauseType> struct ConstructCompositionT {
-  using ClauseTy = ClauseType;
-
-  using TypeTy = typename ClauseTy::TypeTy;
-  using IdTy = typename ClauseTy::IdTy;
-  using ExprTy = typename ClauseTy::ExprTy;
-
-  ConstructCompositionT(uint32_t version,
-                        llvm::ArrayRef<DirectiveWithClauses<ClauseTy>> leafs);
-
-  DirectiveWithClauses<ClauseTy> merged;
-
-private:
-  // Use an ordered container, since we beed to maintain the order in which
-  // clauses are added to it. This is to avoid non-deterministic output.
-  using ClauseSet = ListT<ClauseTy>;
-
-  enum class Presence {
-    All,  // Clause is preesnt on all leaf constructs that allow it.
-    Some, // Clause is present on some, but not on all constructs.
-    None, // Clause is absent on all constructs.
-  };
-
-  template <typename S>
-  ClauseTy makeClause(llvm::omp::Clause clauseId, S &&specific) {
-    return ClauseTy{clauseId, std::move(specific)};
-  }
-
-  llvm::omp::Directive
-  makeCompound(llvm::ArrayRef<DirectiveWithClauses<ClauseTy>> parts);
-
-  Presence checkPresence(llvm::omp::Clause clauseId);
-
-  // There are clauses that need special handling:
-  // 1. "if": the "directive-name-modifier" on the merged clause may need
-  // to be set appropriately.
-  // 2. "reduction": implies "privateness" of all objects (incompatible
-  // with "shared"); there are rules for merging modifiers
-  void mergeIf();
-  void mergeReduction();
-  void mergeDSA();
-
-  uint32_t version;
-  llvm::ArrayRef<DirectiveWithClauses<ClauseTy>> leafs;
-
-  // clause id -> set of leaf constructs that contain it
-  std::unordered_map<llvm::omp::Clause, llvm::BitVector> clausePresence;
-  // clause id -> set of instances of that clause
-  std::unordered_map<llvm::omp::Clause, ClauseSet> clauseSets;
-};
-
-template <typename C>
-ConstructCompositionT<C>::ConstructCompositionT(
-    uint32_t version, llvm::ArrayRef<DirectiveWithClauses<C>> leafs)
-    : version(version), leafs(leafs) {
-  // Merge the list of constructs with clauses into a compound construct
-  // with a single list of clauses.
-  // The intended use of this function is in splitting compound constructs,
-  // while preserving composite constituent constructs:
-  // Step 1: split compound construct into leaf constructs.
-  // Step 2: identify composite sub-construct, and merge the constituent leafs.
-  //
-  // *** At the moment it assumes that the individual constructs and their
-  // *** clauses are a subset of those created by splitting a valid compound
-  // *** construct.
-  //
-  // 1. Deduplicate clauses
-  //    - exact duplicates: e.g. shared(x) shared(x) -> shared(x)
-  //    - special cases of clauses 
diff ering in modifier:
-  //      (a) reduction: inscan + (none|default) = inscan
-  //      (b) reduction: task + (none|default) = task
-  //      (c) combine repeated "if" clauses if possible
-  // 2. Merge DSA clauses: e.g. private(x) private(y) -> private(x, y).
-  // 3. Resolve potential DSA conflicts (typically due to implied clauses).
-
-  if (leafs.empty())
-    return;
-
-  merged.id = makeCompound(leafs);
-
-  // Populate the two maps:
-  for (const auto &[index, leaf] : llvm::enumerate(leafs)) {
-    for (const auto &clause : leaf.clauses) {
-      // Update clausePresence.
-      auto &pset = clausePresence[clause.id];
-      if (pset.size() < leafs.size())
-        pset.resize(leafs.size());
-      pset.set(index);
-      // Update clauseSets.
-      ClauseSet &cset = clauseSets[clause.id];
-      if (!llvm::is_contained(cset, clause))
-        cset.push_back(clause);
-    }
-  }
-
-  mergeIf();
-  mergeReduction();
-  mergeDSA();
-
-  // Fir the rest of the clauses, just copy them.
-  for (auto &[id, clauses] : clauseSets) {
-    // Skip clauses we've already dealt with.
-    switch (id) {
-    case llvm::omp::Clause::OMPC_if:
-    case llvm::omp::Clause::OMPC_reduction:
-    case llvm::omp::Clause::OMPC_shared:
-    case llvm::omp::Clause::OMPC_private:
-    case llvm::omp::Clause::OMPC_firstprivate:
-    case llvm::omp::Clause::OMPC_lastprivate:
-      continue;
-    default:
-      break;
-    }
-    llvm::append_range(merged.clauses, clauses);
-  }
-}
-
-template <typename C>
-llvm::omp::Directive ConstructCompositionT<C>::makeCompound(
-    llvm::ArrayRef<DirectiveWithClauses<ClauseTy>> parts) {
-  llvm::SmallVector<llvm::omp::Directive> dirIds;
-  llvm::transform(parts, std::back_inserter(dirIds),
-                  [](auto &&dwc) { return dwc.id; });
-
-  return llvm::omp::getCompoundConstruct(dirIds);
-}
-
-template <typename C>
-auto ConstructCompositionT<C>::checkPresence(llvm::omp::Clause clauseId)
-    -> Presence {
-  auto found = clausePresence.find(clauseId);
-  if (found == clausePresence.end())
-    return Presence::None;
-
-  bool OnAll = true, OnNone = true;
-  for (const auto &[index, leaf] : llvm::enumerate(leafs)) {
-    if (!llvm::omp::isAllowedClauseForDirective(leaf.id, clauseId, version))
-      continue;
-
-    if (found->second.test(index))
-      OnNone = false;
-    else
-      OnAll = false;
-  }
-
-  if (OnNone)
-    return Presence::None;
-  if (OnAll)
-    return Presence::All;
-  return Presence::Some;
-}
-
-template <typename C> void ConstructCompositionT<C>::mergeIf() {
-  using IfTy = tomp::clause::IfT<TypeTy, IdTy, ExprTy>;
-  // Deal with the "if" clauses. If it's on all leafs that allow it, then it
-  // will apply to the compound construct. Otherwise it will apply to the
-  // single (assumed) leaf construct.
-  // This assumes that the "if" clauses have the same expression.
-  Presence presence = checkPresence(llvm::omp::Clause::OMPC_if);
-  if (presence == Presence::None)
-    return;
-
-  const ClauseTy &some = *clauseSets[llvm::omp::Clause::OMPC_if].begin();
-  const auto &someIf = std::get<IfTy>(some.u);
-
-  if (presence == Presence::All) {
-    // Create "if" without "directive-name-modifier".
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_if,
-                   IfTy{{/*DirectiveNameModifier=*/std::nullopt,
-                         /*IfExpression=*/std::get<typename IfTy::IfExpression>(
-                             someIf.t)}}));
-  } else {
-    // Find out where it's present and create "if" with the corresponding
-    // "directive-name-modifier".
-    int Idx = clausePresence[llvm::omp::Clause::OMPC_if].find_first();
-    assert(Idx >= 0);
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_if,
-                   IfTy{{/*DirectiveNameModifier=*/leafs[Idx].id,
-                         /*IfExpression=*/std::get<typename IfTy::IfExpression>(
-                             someIf.t)}}));
-  }
-}
-
-template <typename C> void ConstructCompositionT<C>::mergeReduction() {
-  Presence presence = checkPresence(llvm::omp::Clause::OMPC_reduction);
-  if (presence == Presence::None)
-    return;
-
-  using ReductionTy = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
-  using ModifierTy = typename ReductionTy::ReductionModifier;
-  using IdentifiersTy = typename ReductionTy::ReductionIdentifiers;
-  using ListTy = typename ReductionTy::List;
-  // There are exceptions on which constructs "reduction" may appear
-  // (specifically "parallel", and "teams"). Assume that if "reduction"
-  // is present, it can be applied to the compound construct.
-
-  // What's left is to see if there are any modifiers present. Again,
-  // assume that there are no conflicting modifiers.
-  // There can be, however, multiple reductions on 
diff erent objects.
-  auto equal = [](const ClauseTy &red1, const ClauseTy &red2) {
-    // Extract actual reductions.
-    const auto r1 = std::get<ReductionTy>(red1.u);
-    const auto r2 = std::get<ReductionTy>(red2.u);
-    // Compare everything except modifiers.
-    if (std::get<IdentifiersTy>(r1.t) != std::get<IdentifiersTy>(r2.t))
-      return false;
-    if (std::get<ListTy>(r1.t) != std::get<ListTy>(r2.t))
-      return false;
-    return true;
-  };
-
-  auto getModifier = [](const ClauseTy &clause) {
-    const ReductionTy &red = std::get<ReductionTy>(clause.u);
-    return std::get<std::optional<ModifierTy>>(red.t);
-  };
-
-  const ClauseSet &reductions = clauseSets[llvm::omp::Clause::OMPC_reduction];
-  std::unordered_set<const ClauseTy *> visited;
-  while (reductions.size() != visited.size()) {
-    typename ClauseSet::const_iterator first;
-
-    // Find first non-visited reduction.
-    for (first = reductions.begin(); first != reductions.end(); ++first) {
-      if (visited.count(&*first))
-        continue;
-      visited.insert(&*first);
-      break;
-    }
-
-    std::optional<ModifierTy> modifier = getModifier(*first);
-
-    // Visit all other reductions that are "equal" (with respect to the
-    // definition above) to "first". Collect modifiers.
-    for (auto iter = std::next(first); iter != reductions.end(); ++iter) {
-      if (!equal(*first, *iter))
-        continue;
-      visited.insert(&*iter);
-      if (!modifier || *modifier == ModifierTy::Default)
-        modifier = getModifier(*iter);
-    }
-
-    const auto &firstRed = std::get<ReductionTy>(first->u);
-    merged.clauses.emplace_back(makeClause(
-        llvm::omp::Clause::OMPC_reduction,
-        ReductionTy{
-            {/*ReductionModifier=*/modifier,
-             /*ReductionIdentifiers=*/std::get<IdentifiersTy>(firstRed.t),
-             /*List=*/std::get<ListTy>(firstRed.t)}}));
-  }
-}
-
-template <typename C> void ConstructCompositionT<C>::mergeDSA() {
-  using ObjectTy = tomp::type::ObjectT<IdTy, ExprTy>;
-
-  // Resolve data-sharing attributes.
-  enum DSA : int {
-    None = 0,
-    Shared = 1 << 0,
-    Private = 1 << 1,
-    FirstPrivate = 1 << 2,
-    LastPrivate = 1 << 3,
-    LastPrivateConditional = 1 << 4,
-  };
-
-  // Use ordered containers to avoid non-deterministic output.
-  llvm::SmallVector<std::pair<ObjectTy, int>> objectDsa;
-
-  auto getDsa = [&](const ObjectTy &object) -> std::pair<ObjectTy, int> & {
-    auto found = llvm::find_if(objectDsa, [&](std::pair<ObjectTy, int> &p) {
-      return p.first.id() == object.id();
-    });
-    if (found != objectDsa.end())
-      return *found;
-    return objectDsa.emplace_back(object, DSA::None);
-  };
-
-  using SharedTy = tomp::clause::SharedT<TypeTy, IdTy, ExprTy>;
-  using PrivateTy = tomp::clause::PrivateT<TypeTy, IdTy, ExprTy>;
-  using FirstprivateTy = tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>;
-  using LastprivateTy = tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>;
-
-  // Visit clauses that affect DSA.
-  for (auto &clause : clauseSets[llvm::omp::Clause::OMPC_shared]) {
-    for (auto &object : std::get<SharedTy>(clause.u).v)
-      getDsa(object).second |= DSA::Shared;
-  }
-
-  for (auto &clause : clauseSets[llvm::omp::Clause::OMPC_private]) {
-    for (auto &object : std::get<PrivateTy>(clause.u).v)
-      getDsa(object).second |= DSA::Private;
-  }
-
-  for (auto &clause : clauseSets[llvm::omp::Clause::OMPC_firstprivate]) {
-    for (auto &object : std::get<FirstprivateTy>(clause.u).v)
-      getDsa(object).second |= DSA::FirstPrivate;
-  }
-
-  for (auto &clause : clauseSets[llvm::omp::Clause::OMPC_lastprivate]) {
-    using ModifierTy = typename LastprivateTy::LastprivateModifier;
-    using ListTy = typename LastprivateTy::List;
-    const auto &lastp = std::get<LastprivateTy>(clause.u);
-    for (auto &object : std::get<ListTy>(lastp.t)) {
-      auto &mod = std::get<std::optional<ModifierTy>>(lastp.t);
-      if (mod && *mod == ModifierTy::Conditional) {
-        getDsa(object).second |= DSA::LastPrivateConditional;
-      } else {
-        getDsa(object).second |= DSA::LastPrivate;
-      }
-    }
-  }
-
-  // Check reductions as well, clear "shared" if set.
-  for (auto &clause : clauseSets[llvm::omp::Clause::OMPC_reduction]) {
-    using ReductionTy = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
-    using ListTy = typename ReductionTy::List;
-    for (auto &object : std::get<ListTy>(std::get<ReductionTy>(clause.u).t))
-      getDsa(object).second &= ~DSA::Shared;
-  }
-
-  tomp::ListT<ObjectTy> privateObj, sharedObj, firstpObj, lastpObj, lastpcObj;
-  for (auto &[object, dsa] : objectDsa) {
-    if (dsa &
-        (DSA::FirstPrivate | DSA::LastPrivate | DSA::LastPrivateConditional)) {
-      if (dsa & DSA::FirstPrivate)
-        firstpObj.push_back(object); // no else
-      if (dsa & DSA::LastPrivateConditional)
-        lastpcObj.push_back(object);
-      else if (dsa & DSA::LastPrivate)
-        lastpObj.push_back(object);
-    } else if (dsa & DSA::Private) {
-      privateObj.push_back(object);
-    } else if (dsa & DSA::Shared) {
-      sharedObj.push_back(object);
-    }
-  }
-
-  // Materialize each clause.
-  if (!privateObj.empty()) {
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_private,
-                   PrivateTy{/*List=*/std::move(privateObj)}));
-  }
-  if (!sharedObj.empty()) {
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_shared,
-                   SharedTy{/*List=*/std::move(sharedObj)}));
-  }
-  if (!firstpObj.empty()) {
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_firstprivate,
-                   FirstprivateTy{/*List=*/std::move(firstpObj)}));
-  }
-  if (!lastpObj.empty()) {
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_lastprivate,
-                   LastprivateTy{{/*LastprivateModifier=*/std::nullopt,
-                                  /*List=*/std::move(lastpObj)}}));
-  }
-  if (!lastpcObj.empty()) {
-    auto conditional = LastprivateTy::LastprivateModifier::Conditional;
-    merged.clauses.emplace_back(
-        makeClause(llvm::omp::Clause::OMPC_lastprivate,
-                   LastprivateTy{{/*LastprivateModifier=*/conditional,
-                                  /*List=*/std::move(lastpcObj)}}));
-  }
-}
-} // namespace tomp
-
-#endif // LLVM_FRONTEND_OPENMP_CONSTRUCTCOMPOSITIONT_H

diff  --git a/llvm/include/llvm/Frontend/OpenMP/ConstructDecompositionT.h b/llvm/include/llvm/Frontend/OpenMP/ConstructDecompositionT.h
deleted file mode 100644
index 37c88f0fa07b4..0000000000000
--- a/llvm/include/llvm/Frontend/OpenMP/ConstructDecompositionT.h
+++ /dev/null
@@ -1,1161 +0,0 @@
-//===- ConstructDecompositionT.h -- Decomposing compound constructs -------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-// Given a compound construct with a set of clauses, generate the list of
-// constituent leaf constructs, each with a list of clauses that apply to it.
-//
-// Note: Clauses that are not originally present, but that are implied by the
-// OpenMP spec are materialized, and are present in the output.
-//
-// Note: Composite constructs will also be broken up into leaf constructs.
-// If composite constructs require processing as a whole, the lists of clauses
-// for each leaf constituent should be merged.
-//===----------------------------------------------------------------------===//
-#ifndef LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H
-#define LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H
-
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/iterator_range.h"
-#include "llvm/Frontend/OpenMP/ClauseT.h"
-#include "llvm/Frontend/OpenMP/OMP.h"
-
-#include <iterator>
-#include <list>
-#include <optional>
-#include <tuple>
-#include <type_traits>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <variant>
-
-static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharing() {
-  static llvm::omp::Directive worksharing[] = {
-      llvm::omp::Directive::OMPD_do,     llvm::omp::Directive::OMPD_for,
-      llvm::omp::Directive::OMPD_scope,  llvm::omp::Directive::OMPD_sections,
-      llvm::omp::Directive::OMPD_single, llvm::omp::Directive::OMPD_workshare,
-  };
-  return worksharing;
-}
-
-static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharingLoop() {
-  static llvm::omp::Directive worksharingLoop[] = {
-      llvm::omp::Directive::OMPD_do,
-      llvm::omp::Directive::OMPD_for,
-  };
-  return worksharingLoop;
-}
-
-namespace detail {
-template <typename Container, typename Predicate>
-typename std::remove_reference_t<Container>::iterator
-find_unique(Container &&container, Predicate &&pred) {
-  auto first = std::find_if(container.begin(), container.end(), pred);
-  if (first == container.end())
-    return first;
-  auto second = std::find_if(std::next(first), container.end(), pred);
-  if (second == container.end())
-    return first;
-  return container.end();
-}
-
-} // namespace detail
-
-namespace tomp {
-
-// ClauseType - Either instance of ClauseT, or a type derived from ClauseT.
-//
-// This is the clause representation in the code using this infrastructure.
-//
-// HelperType - A class that implements two member functions:
-//
-//   // Return the base object of the given object, if any.
-//   std::optional<Object> getBaseObject(const Object &object) const
-//   // Return the iteration variable of the outermost loop associated
-//   // with the construct being worked on, if any.
-//   std::optional<Object> getLoopIterVar() const
-template <typename ClauseType, typename HelperType>
-struct ConstructDecompositionT {
-  using ClauseTy = ClauseType;
-
-  using TypeTy = typename ClauseTy::TypeTy;
-  using IdTy = typename ClauseTy::IdTy;
-  using ExprTy = typename ClauseTy::ExprTy;
-  using HelperTy = HelperType;
-  using ObjectTy = tomp::ObjectT<IdTy, ExprTy>;
-
-  using ClauseSet = std::unordered_set<const ClauseTy *>;
-
-  ConstructDecompositionT(uint32_t ver, HelperType &helper,
-                          llvm::omp::Directive dir,
-                          llvm::ArrayRef<ClauseTy> clauses)
-      : version(ver), construct(dir), helper(helper) {
-    for (const ClauseTy &clause : clauses)
-      nodes.push_back(&clause);
-
-    bool success = split();
-    if (!success)
-      return;
-
-    // Copy the individual leaf directives with their clauses to the
-    // output list. Copy by value, since we don't own the storage
-    // with the input clauses, and the internal representation uses
-    // clause addresses.
-    for (auto &leaf : leafs) {
-      output.push_back({leaf.id});
-      auto &out = output.back();
-      for (const ClauseTy *c : leaf.clauses)
-        out.clauses.push_back(*c);
-    }
-  }
-
-  tomp::ListT<DirectiveWithClauses<ClauseType>> output;
-
-private:
-  bool split();
-
-  struct LeafReprInternal {
-    llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
-    tomp::type::ListT<const ClauseTy *> clauses;
-  };
-
-  LeafReprInternal *findDirective(llvm::omp::Directive dirId) {
-    auto found = llvm::find_if(
-        leafs, [&](const LeafReprInternal &leaf) { return leaf.id == dirId; });
-    return found != leafs.end() ? &*found : nullptr;
-  }
-
-  ClauseSet *findClausesWith(const ObjectTy &object) {
-    if (auto found = syms.find(object.id()); found != syms.end())
-      return &found->second;
-    return nullptr;
-  }
-
-  template <typename S>
-  ClauseTy *makeClause(llvm::omp::Clause clauseId, S &&specific) {
-    implicit.push_back(ClauseTy{clauseId, std::move(specific)});
-    return &implicit.back();
-  }
-
-  void addClauseSymsToMap(const ObjectTy &object, const ClauseTy *);
-  void addClauseSymsToMap(const tomp::ObjectListT<IdTy, ExprTy> &objects,
-                          const ClauseTy *);
-  void addClauseSymsToMap(const TypeTy &item, const ClauseTy *);
-  void addClauseSymsToMap(const ExprTy &item, const ClauseTy *);
-  void addClauseSymsToMap(const tomp::clause::MapT<TypeTy, IdTy, ExprTy> &item,
-                          const ClauseTy *);
-
-  template <typename U>
-  void addClauseSymsToMap(const std::optional<U> &item, const ClauseTy *);
-  template <typename U>
-  void addClauseSymsToMap(const tomp::ListT<U> &item, const ClauseTy *);
-  template <typename... U, size_t... Is>
-  void addClauseSymsToMap(const std::tuple<U...> &item, const ClauseTy *,
-                          std::index_sequence<Is...> = {});
-  template <typename U>
-  std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  template <typename U>
-  std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  template <typename U>
-  std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  template <typename U>
-  std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  template <typename U>
-  std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  template <typename U>
-  std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void>
-  addClauseSymsToMap(U &&item, const ClauseTy *);
-
-  // Apply a clause to the only directive that allows it. If there are no
-  // directives that allow it, or if there is more that one, do not apply
-  // anything and return false, otherwise return true.
-  bool applyToUnique(const ClauseTy *node);
-
-  // Apply a clause to the first directive in given range that allows it.
-  // If such a directive does not exist, return false, otherwise return true.
-  template <typename Iterator>
-  bool applyToFirst(const ClauseTy *node, llvm::iterator_range<Iterator> range);
-
-  // Apply a clause to the innermost directive that allows it. If such a
-  // directive does not exist, return false, otherwise return true.
-  bool applyToInnermost(const ClauseTy *node);
-
-  // Apply a clause to the outermost directive that allows it. If such a
-  // directive does not exist, return false, otherwise return true.
-  bool applyToOutermost(const ClauseTy *node);
-
-  template <typename Predicate>
-  bool applyIf(const ClauseTy *node, Predicate shouldApply);
-
-  bool applyToAll(const ClauseTy *node);
-
-  template <typename Clause>
-  bool applyClause(Clause &&clause, const ClauseTy *node);
-
-  bool applyClause(const tomp::clause::CollapseT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::PrivateT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool
-  applyClause(const tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy> &clause,
-              const ClauseTy *);
-  bool
-  applyClause(const tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy> &clause,
-              const ClauseTy *);
-  bool applyClause(const tomp::clause::SharedT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::DefaultT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool
-  applyClause(const tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy> &clause,
-              const ClauseTy *);
-  bool applyClause(const tomp::clause::OrderT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::AllocateT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::ReductionT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::IfT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::LinearT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-  bool applyClause(const tomp::clause::NowaitT<TypeTy, IdTy, ExprTy> &clause,
-                   const ClauseTy *);
-
-  uint32_t version;
-  llvm::omp::Directive construct;
-  HelperType &helper;
-  ListT<LeafReprInternal> leafs;
-  tomp::ListT<const ClauseTy *> nodes;
-  std::list<ClauseTy> implicit; // Container for materialized implicit clauses.
-                                // Inserting must preserve element addresses.
-  std::unordered_map<IdTy, ClauseSet> syms;
-  std::unordered_set<IdTy> mapBases;
-};
-
-// Deduction guide
-template <typename ClauseType, typename HelperType>
-ConstructDecompositionT(uint32_t, HelperType &, llvm::omp::Directive,
-                        llvm::ArrayRef<ClauseType>)
-    -> ConstructDecompositionT<ClauseType, HelperType>;
-
-template <typename C, typename H>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(const ObjectTy &object,
-                                                       const ClauseTy *node) {
-  syms[object.id()].insert(node);
-}
-
-template <typename C, typename H>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(
-    const tomp::ObjectListT<IdTy, ExprTy> &objects, const ClauseTy *node) {
-  for (auto &object : objects)
-    syms[object.id()].insert(node);
-}
-
-template <typename C, typename H>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(const TypeTy &item,
-                                                       const ClauseTy *node) {
-  // Nothing to do for types.
-}
-
-template <typename C, typename H>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(const ExprTy &item,
-                                                       const ClauseTy *node) {
-  // Nothing to do for expressions.
-}
-
-template <typename C, typename H>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(
-    const tomp::clause::MapT<TypeTy, IdTy, ExprTy> &item,
-    const ClauseTy *node) {
-  auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(item.t);
-  addClauseSymsToMap(objects, node);
-  for (auto &object : objects) {
-    if (auto base = helper.getBaseObject(object))
-      mapBases.insert(base->id());
-  }
-}
-
-template <typename C, typename H>
-template <typename U>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(
-    const std::optional<U> &item, const ClauseTy *node) {
-  if (item)
-    addClauseSymsToMap(*item, node);
-}
-
-template <typename C, typename H>
-template <typename U>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(
-    const tomp::ListT<U> &item, const ClauseTy *node) {
-  for (auto &s : item)
-    addClauseSymsToMap(s, node);
-}
-
-template <typename C, typename H>
-template <typename... U, size_t... Is>
-void ConstructDecompositionT<C, H>::addClauseSymsToMap(
-    const std::tuple<U...> &item, const ClauseTy *node,
-    std::index_sequence<Is...>) {
-  (void)node; // Silence strange warning from GCC.
-  (addClauseSymsToMap(std::get<Is>(item), node), ...);
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  // Nothing to do for enums.
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  // Nothing to do for an empty class.
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  // Nothing to do for an incomplete class (they're empty).
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  addClauseSymsToMap(item.v, node);
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  constexpr size_t tuple_size =
-      std::tuple_size_v<llvm::remove_cvref_t<decltype(item.t)>>;
-  addClauseSymsToMap(item.t, node, std::make_index_sequence<tuple_size>{});
-}
-
-template <typename C, typename H>
-template <typename U>
-std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void>
-ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
-                                                  const ClauseTy *node) {
-  std::visit([&](auto &&s) { addClauseSymsToMap(s, node); }, item.u);
-}
-
-// Apply a clause to the only directive that allows it. If there are no
-// directives that allow it, or if there is more that one, do not apply
-// anything and return false, otherwise return true.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyToUnique(const ClauseTy *node) {
-  auto unique = detail::find_unique(leafs, [=](const auto &dirInfo) {
-    return llvm::omp::isAllowedClauseForDirective(dirInfo.id, node->id,
-                                                  version);
-  });
-
-  if (unique != leafs.end()) {
-    unique->clauses.push_back(node);
-    return true;
-  }
-  return false;
-}
-
-// Apply a clause to the first directive in given range that allows it.
-// If such a directive does not exist, return false, otherwise return true.
-template <typename C, typename H>
-template <typename Iterator>
-bool ConstructDecompositionT<C, H>::applyToFirst(
-    const ClauseTy *node, llvm::iterator_range<Iterator> range) {
-  if (range.empty())
-    return false;
-
-  for (auto &leaf : range) {
-    if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
-      continue;
-    leaf.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 C, typename H>
-bool ConstructDecompositionT<C, H>::applyToInnermost(const ClauseTy *node) {
-  return applyToFirst(node, llvm::reverse(leafs));
-}
-
-// Apply a clause to the outermost directive that allows it. If such a
-// directive does not exist, return false, otherwise return true.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyToOutermost(const ClauseTy *node) {
-  return applyToFirst(node, llvm::iterator_range(leafs));
-}
-
-template <typename C, typename H>
-template <typename Predicate>
-bool ConstructDecompositionT<C, H>::applyIf(const ClauseTy *node,
-                                            Predicate shouldApply) {
-  bool applied = false;
-  for (auto &leaf : leafs) {
-    if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
-      continue;
-    if (!shouldApply(leaf))
-      continue;
-    leaf.clauses.push_back(node);
-    applied = true;
-  }
-
-  return applied;
-}
-
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyToAll(const ClauseTy *node) {
-  return applyIf(node, [](auto) { return true; });
-}
-
-template <typename C, typename H>
-template <typename Clause>
-bool ConstructDecompositionT<C, H>::applyClause(Clause &&clause,
-                                                const ClauseTy *node) {
-  // The default behavior is to find the unique directive to which the
-  // given clause may be applied. If there are no such directives, or
-  // if there are multiple ones, flag an error.
-  // From "OpenMP Application Programming Interface", Version 5.2:
-  // S Some clauses are permitted only on a single leaf construct of the
-  // S combined or composite construct, in which case the effect is as if
-  // S the clause is applied to that specific construct. (p339, 31-33)
-  if (applyToUnique(node))
-    return true;
-
-  return false;
-}
-
-// COLLAPSE
-// [5.2:93:20-21]
-// Directives: distribute, do, for, loop, simd, taskloop
-//
-// [5.2:339:35]
-// (35) The collapse clause is applied once to the combined or composite
-// construct.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::CollapseT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // Apply "collapse" to the innermost directive. If it's not one that
-  // allows it flag an error.
-  if (!leafs.empty()) {
-    auto &last = leafs.back();
-
-    if (llvm::omp::isAllowedClauseForDirective(last.id, node->id, version)) {
-      last.clauses.push_back(node);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-// PRIVATE
-// [5.2:111:5-7]
-// Directives: distribute, do, for, loop, parallel, scope, sections, simd,
-// single, target, task, taskloop, teams
-//
-// [5.2:340:1-2]
-// (1) The effect of the 1 private clause is as if it is applied only to the
-// innermost leaf construct that permits it.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::PrivateT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  return applyToInnermost(node);
-}
-
-// FIRSTPRIVATE
-// [5.2:112:5-7]
-// Directives: distribute, do, for, parallel, scope, sections, single, target,
-// task, taskloop, teams
-//
-// [5.2:340:3-20]
-// (3) The effect of the firstprivate clause is as if it is applied to one or
-// more leaf constructs as follows:
-//  (5) To the distribute construct if it is among the constituent constructs;
-//  (6) To the teams construct if it is among the constituent constructs and the
-//      distribute construct is not;
-//  (8) To a worksharing construct that accepts the clause if one is among the
-//      constituent constructs;
-//  (9) To the taskloop construct if it is among the constituent constructs;
-// (10) To the parallel construct if it is among the constituent constructs and
-//      neither a taskloop construct nor a worksharing construct that accepts
-//      the clause is among them;
-// (12) To the target construct if it is among the constituent constructs and
-//      the same list item neither appears in a lastprivate clause nor is the
-//      base variable or base pointer of a list item that appears in a map
-//      clause.
-//
-// (15) If the parallel construct is among the constituent constructs and the
-// effect is not as if the firstprivate clause is applied to it by the above
-// rules, then the effect is as if the shared clause with the same list item is
-// applied to the parallel construct.
-// (17) If the teams construct is among the constituent constructs and the
-// effect is not as if the firstprivate clause is applied to it by the above
-// rules, then the effect is as if the shared clause with the same list item is
-// applied to the teams construct.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  bool applied = false;
-
-  // [5.2:340:3-6]
-  auto dirDistribute = findDirective(llvm::omp::OMPD_distribute);
-  auto dirTeams = findDirective(llvm::omp::OMPD_teams);
-  if (dirDistribute != nullptr) {
-    dirDistribute->clauses.push_back(node);
-    applied = true;
-    // [5.2:340:17]
-    if (dirTeams != nullptr) {
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/clause.v});
-      dirTeams->clauses.push_back(shared);
-    }
-  } else if (dirTeams != nullptr) {
-    dirTeams->clauses.push_back(node);
-    applied = true;
-  }
-
-  // [5.2:340:8]
-  auto findWorksharing = [&]() {
-    auto worksharing = getWorksharing();
-    for (auto &leaf : leafs) {
-      auto found = llvm::find(worksharing, leaf.id);
-      if (found != std::end(worksharing))
-        return &leaf;
-    }
-    return static_cast<typename decltype(leafs)::value_type *>(nullptr);
-  };
-
-  auto dirWorksharing = findWorksharing();
-  if (dirWorksharing != nullptr) {
-    dirWorksharing->clauses.push_back(node);
-    applied = true;
-  }
-
-  // [5.2:340:9]
-  auto dirTaskloop = findDirective(llvm::omp::OMPD_taskloop);
-  if (dirTaskloop != nullptr) {
-    dirTaskloop->clauses.push_back(node);
-    applied = true;
-  }
-
-  // [5.2:340:10]
-  auto dirParallel = findDirective(llvm::omp::OMPD_parallel);
-  if (dirParallel != nullptr) {
-    if (dirTaskloop == nullptr && dirWorksharing == nullptr) {
-      dirParallel->clauses.push_back(node);
-      applied = true;
-    } else {
-      // [5.2:340:15]
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/clause.v});
-      dirParallel->clauses.push_back(shared);
-    }
-  }
-
-  // [5.2:340:12]
-  auto inLastprivate = [&](const ObjectTy &object) {
-    if (ClauseSet *set = findClausesWith(object)) {
-      return llvm::find_if(*set, [](const ClauseTy *c) {
-               return c->id == llvm::omp::Clause::OMPC_lastprivate;
-             }) != set->end();
-    }
-    return false;
-  };
-
-  auto dirTarget = findDirective(llvm::omp::OMPD_target);
-  if (dirTarget != nullptr) {
-    tomp::ObjectListT<IdTy, ExprTy> objects;
-    llvm::copy_if(
-        clause.v, std::back_inserter(objects), [&](const ObjectTy &object) {
-          return !inLastprivate(object) && !mapBases.count(object.id());
-        });
-    if (!objects.empty()) {
-      auto *firstp = makeClause(
-          llvm::omp::Clause::OMPC_firstprivate,
-          tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>{/*List=*/objects});
-      dirTarget->clauses.push_back(firstp);
-      applied = true;
-    }
-  }
-
-  // "task" is not handled by any of the cases above.
-  if (auto dirTask = findDirective(llvm::omp::OMPD_task)) {
-    dirTask->clauses.push_back(node);
-    applied = true;
-  }
-
-  return applied;
-}
-
-// LASTPRIVATE
-// [5.2:115:7-8]
-// Directives: distribute, do, for, loop, sections, simd, taskloop
-//
-// [5.2:340:21-30]
-// (21) The effect of the lastprivate clause is as if it is applied to all leaf
-// constructs that permit the clause.
-// (22) If the parallel construct is among the constituent constructs and the
-// list item is not also specified in the firstprivate clause, then the effect
-// of the lastprivate clause is as if the shared clause with the same list item
-// is applied to the parallel construct.
-// (24) If the teams construct is among the constituent constructs and the list
-// item is not also specified in the firstprivate clause, then the effect of the
-// lastprivate clause is as if the shared clause with the same list item is
-// applied to the teams construct.
-// (27) If the target construct is among the constituent constructs and the list
-// item is not the base variable or base pointer of a list item that appears in
-// a map clause, the effect of the lastprivate clause is as if the same list
-// item appears in a map clause with a map-type of tofrom.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  bool applied = false;
-
-  // [5.2:340:21]
-  applied = applyToAll(node);
-  if (!applied)
-    return false;
-
-  auto inFirstprivate = [&](const ObjectTy &object) {
-    if (ClauseSet *set = findClausesWith(object)) {
-      return llvm::find_if(*set, [](const ClauseTy *c) {
-               return c->id == llvm::omp::Clause::OMPC_firstprivate;
-             }) != set->end();
-    }
-    return false;
-  };
-
-  auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t);
-
-  // Prepare list of objects that could end up in a "shared" clause.
-  tomp::ObjectListT<IdTy, ExprTy> sharedObjects;
-  llvm::copy_if(
-      objects, std::back_inserter(sharedObjects),
-      [&](const ObjectTy &object) { return !inFirstprivate(object); });
-
-  if (!sharedObjects.empty()) {
-    // [5.2:340:22]
-    if (auto dirParallel = findDirective(llvm::omp::OMPD_parallel)) {
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects});
-      dirParallel->clauses.push_back(shared);
-      applied = true;
-    }
-
-    // [5.2:340:24]
-    if (auto dirTeams = findDirective(llvm::omp::OMPD_teams)) {
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects});
-      dirTeams->clauses.push_back(shared);
-      applied = true;
-    }
-  }
-
-  // [5.2:340:27]
-  if (auto dirTarget = findDirective(llvm::omp::OMPD_target)) {
-    tomp::ObjectListT<IdTy, ExprTy> tofrom;
-    llvm::copy_if(
-        objects, std::back_inserter(tofrom),
-        [&](const ObjectTy &object) { return !mapBases.count(object.id()); });
-
-    if (!tofrom.empty()) {
-      using MapType =
-          typename tomp::clause::MapT<TypeTy, IdTy, ExprTy>::MapType;
-      auto *map =
-          makeClause(llvm::omp::Clause::OMPC_map,
-                     tomp::clause::MapT<TypeTy, IdTy, ExprTy>{
-                         {/*MapType=*/MapType::Tofrom,
-                          /*MapTypeModifier=*/std::nullopt,
-                          /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
-                          /*LocatorList=*/std::move(tofrom)}});
-      dirTarget->clauses.push_back(map);
-      applied = true;
-    }
-  }
-
-  return applied;
-}
-
-// SHARED
-// [5.2:110:5-6]
-// Directives: parallel, task, taskloop, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::SharedT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // [5.2:340:31]
-  return applyToAll(node);
-}
-
-// DEFAULT
-// [5.2:109:5-6]
-// Directives: parallel, task, taskloop, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::DefaultT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // [5.2:340:31]
-  return applyToAll(node);
-}
-
-// THREAD_LIMIT
-// [5.2:277:14-15]
-// Directives: target, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // [5.2:340:31]
-  return applyToAll(node);
-}
-
-// ORDER
-// [5.2:234:3-4]
-// Directives: distribute, do, for, loop, simd
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::OrderT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // [5.2:340:31]
-  return applyToAll(node);
-}
-
-// ALLOCATE
-// [5.2:178:7-9]
-// Directives: allocators, distribute, do, for, parallel, scope, sections,
-// single, target, task, taskgroup, taskloop, teams
-//
-// [5.2:340:33-35]
-// (33) The effect of the allocate clause is as if it is applied to all leaf
-// constructs that permit the clause and to which a data-sharing attribute
-// clause that may create a private copy of the same list item is applied.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::AllocateT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // This one needs to be applied at the end, once we know which clauses are
-  // assigned to which leaf constructs.
-
-  // [5.2:340:33]
-  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 auto &leaf) {
-    return llvm::any_of(leaf.clauses, [&](const ClauseTy *n) {
-      return canMakePrivateCopy(n->id);
-    });
-  });
-
-  return applied;
-}
-
-// REDUCTION
-// [5.2:134:17-18]
-// Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams
-//
-// [5.2:340:36-37], [5.2:341:1-13]
-// (36) The effect of the reduction clause is as if it is applied to all leaf
-// constructs that permit the clause, except for the following constructs:
-//  (1) The parallel construct, when combined with the sections,
-//      worksharing-loop, loop, or taskloop construct; and
-//  (3) The teams construct, when combined with the loop construct.
-// (4) For the parallel and teams constructs above, the effect of the reduction
-// clause instead is as if each list item or, for any list item that is an array
-// item, its corresponding base array or base pointer appears in a shared clause
-// for the construct.
-// (6) If the task reduction-modifier is specified, the effect is as if it only
-// modifies the behavior of the reduction clause on the innermost leaf construct
-// that accepts the modifier (see Section 5.5.8).
-// (8) If the inscan reduction-modifier is specified, the effect is as if it
-// modifies the behavior of the reduction clause on all constructs of the
-// combined construct to which the clause is applied and that accept the
-// modifier.
-// (10) If a list item in a reduction clause on a combined target construct does
-// not have the same base variable or base pointer as a list item in a map
-// clause on the construct, then the effect is as if the list item in the
-// reduction clause appears as a list item in a map clause with a map-type of
-// tofrom.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::ReductionT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  using ReductionTy = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
-
-  // [5.2:340:36], [5.2:341:1], [5.2:341:3]
-  bool applyToParallel = true, applyToTeams = true;
-
-  auto dirParallel = findDirective(llvm::omp::Directive::OMPD_parallel);
-  if (dirParallel) {
-    auto exclusions = llvm::concat<const llvm::omp::Directive>(
-        getWorksharingLoop(), tomp::ListT<llvm::omp::Directive>{
-                                  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 dirTeams = findDirective(llvm::omp::Directive::OMPD_teams);
-  if (dirTeams) {
-    // The only exclusion is OMPD_loop.
-    if (findDirective(llvm::omp::Directive::OMPD_loop))
-      applyToTeams = false;
-  }
-
-  using ReductionModifier = typename ReductionTy::ReductionModifier;
-  using ReductionIdentifiers = typename ReductionTy::ReductionIdentifiers;
-
-  auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t);
-  auto &modifier = std::get<std::optional<ReductionModifier>>(clause.t);
-
-  // Apply the reduction clause first to all directives according to the spec.
-  // If the reduction was applied at least once, proceed with the data sharing
-  // side-effects.
-  bool applied = false;
-
-  // [5.2:341:6], [5.2:341:8]
-  auto isValidModifier = [](llvm::omp::Directive dir, ReductionModifier mod,
-                            bool alreadyApplied) {
-    switch (mod) {
-    case ReductionModifier::Inscan:
-      // According to [5.2:135:11-13], "inscan" only applies to
-      // worksharing-loop, worksharing-loop-simd, or "simd" constructs.
-      return dir == llvm::omp::Directive::OMPD_simd ||
-             llvm::is_contained(getWorksharingLoop(), dir);
-    case ReductionModifier::Task:
-      if (alreadyApplied)
-        return false;
-      // According to [5.2:135:16-18], "task" only applies to "parallel" and
-      // worksharing constructs.
-      return dir == llvm::omp::Directive::OMPD_parallel ||
-             llvm::is_contained(getWorksharing(), dir);
-    case ReductionModifier::Default:
-      return true;
-    }
-    llvm_unreachable("Unexpected modifier");
-  };
-
-  auto *unmodified = makeClause(
-      llvm::omp::Clause::OMPC_reduction,
-      ReductionTy{
-          {/*ReductionModifier=*/std::nullopt,
-           /*ReductionIdentifiers=*/std::get<ReductionIdentifiers>(clause.t),
-           /*List=*/objects}});
-
-  ReductionModifier effective =
-      modifier.has_value() ? *modifier : ReductionModifier::Default;
-  bool effectiveApplied = false;
-  // Walk over the leaf constructs starting from the innermost, and apply
-  // the clause as required by the spec.
-  for (auto &leaf : llvm::reverse(leafs)) {
-    if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
-      continue;
-    if (!applyToParallel && &leaf == dirParallel)
-      continue;
-    if (!applyToTeams && &leaf == dirTeams)
-      continue;
-    // Some form of the clause will be applied past this point.
-    if (isValidModifier(leaf.id, effective, effectiveApplied)) {
-      // Apply clause with modifier.
-      leaf.clauses.push_back(node);
-      effectiveApplied = true;
-    } else {
-      // Apply clause without modifier.
-      leaf.clauses.push_back(unmodified);
-    }
-    applied = true;
-  }
-
-  if (!applied)
-    return false;
-
-  tomp::ObjectListT<IdTy, ExprTy> sharedObjects;
-  llvm::transform(objects, std::back_inserter(sharedObjects),
-                  [&](const ObjectTy &object) {
-                    auto maybeBase = helper.getBaseObject(object);
-                    return maybeBase ? *maybeBase : object;
-                  });
-
-  // [5.2:341:4]
-  if (!sharedObjects.empty()) {
-    if (dirParallel && !applyToParallel) {
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects});
-      dirParallel->clauses.push_back(shared);
-    }
-    if (dirTeams && !applyToTeams) {
-      auto *shared = makeClause(
-          llvm::omp::Clause::OMPC_shared,
-          tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects});
-      dirTeams->clauses.push_back(shared);
-    }
-  }
-
-  // [5.2:341:10]
-  auto dirTarget = findDirective(llvm::omp::Directive::OMPD_target);
-  if (dirTarget && leafs.size() > 1) {
-    tomp::ObjectListT<IdTy, ExprTy> tofrom;
-    llvm::copy_if(objects, std::back_inserter(tofrom),
-                  [&](const ObjectTy &object) {
-                    if (auto maybeBase = helper.getBaseObject(object))
-                      return !mapBases.count(maybeBase->id());
-                    return !mapBases.count(object.id()); // XXX is this ok?
-                  });
-    if (!tofrom.empty()) {
-      using MapType =
-          typename tomp::clause::MapT<TypeTy, IdTy, ExprTy>::MapType;
-      auto *map = makeClause(
-          llvm::omp::Clause::OMPC_map,
-          tomp::clause::MapT<TypeTy, IdTy, ExprTy>{
-              {/*MapType=*/MapType::Tofrom, /*MapTypeModifier=*/std::nullopt,
-               /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt,
-               /*LocatorList=*/std::move(tofrom)}});
-
-      dirTarget->clauses.push_back(map);
-      applied = true;
-    }
-  }
-
-  return applied;
-}
-
-// IF
-// [5.2:72:7-9]
-// Directives: cancel, parallel, simd, target, target data, target enter data,
-// target exit data, target update, task, taskloop
-//
-// [5.2:72:15-18]
-// (15) For combined or composite constructs, the if clause only applies to the
-// semantics of the construct named in the directive-name-modifier.
-// (16) For a combined or composite construct, if no directive-name-modifier is
-// specified then the if clause applies to all constituent constructs to which
-// an if clause can apply.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::IfT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  using DirectiveNameModifier =
-      typename clause::IfT<TypeTy, IdTy, ExprTy>::DirectiveNameModifier;
-  using IfExpression = typename clause::IfT<TypeTy, IdTy, ExprTy>::IfExpression;
-  auto &modifier = std::get<std::optional<DirectiveNameModifier>>(clause.t);
-
-  if (modifier) {
-    llvm::omp::Directive dirId = *modifier;
-    auto *unmodified =
-        makeClause(llvm::omp::Clause::OMPC_if,
-                   tomp::clause::IfT<TypeTy, IdTy, ExprTy>{
-                       {/*DirectiveNameModifier=*/std::nullopt,
-                        /*IfExpression=*/std::get<IfExpression>(clause.t)}});
-
-    if (auto *hasDir = findDirective(dirId)) {
-      hasDir->clauses.push_back(unmodified);
-      return true;
-    }
-    return false;
-  }
-
-  return applyToAll(node);
-}
-
-// LINEAR
-// [5.2:118:1-2]
-// Directives: declare simd, do, for, simd
-//
-// [5.2:341:15-22]
-// (15.1) The effect of the linear clause is as if it is applied to the
-// innermost leaf construct.
-// (15.2) Additionally, if the list item is not the iteration variable of a simd
-// or worksharing-loop SIMD construct, the effect on the outer leaf constructs
-// is as if the list item was specified in firstprivate and lastprivate clauses
-// on the combined or composite construct, with the rules specified above
-// applied.
-// (19) If a list item of the linear clause is the iteration variable of a simd
-// or worksharing-loop SIMD construct and it is not declared in the construct,
-// the effect on the outer leaf constructs is as if the list item was specified
-// in a lastprivate clause on the combined or composite construct with the rules
-// specified above applied.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::LinearT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  // [5.2:341:15.1]
-  if (!applyToInnermost(node))
-    return false;
-
-  // [5.2:341:15.2], [5.2:341:19]
-  auto dirSimd = findDirective(llvm::omp::Directive::OMPD_simd);
-  std::optional<ObjectTy> iterVar = helper.getLoopIterVar();
-  const auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t);
-
-  // Lists of objects that will be used to construct "firstprivate" and
-  // "lastprivate" clauses.
-  tomp::ObjectListT<IdTy, ExprTy> first, last;
-
-  for (const ObjectTy &object : objects) {
-    last.push_back(object);
-    if (!dirSimd || !iterVar || object.id() != iterVar->id())
-      first.push_back(object);
-  }
-
-  if (!first.empty()) {
-    auto *firstp = makeClause(
-        llvm::omp::Clause::OMPC_firstprivate,
-        tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>{/*List=*/first});
-    nodes.push_back(firstp); // Appending to the main clause list.
-  }
-  if (!last.empty()) {
-    auto *lastp =
-        makeClause(llvm::omp::Clause::OMPC_lastprivate,
-                   tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>{
-                       {/*LastprivateModifier=*/std::nullopt, /*List=*/last}});
-    nodes.push_back(lastp); // Appending to the main clause list.
-  }
-  return true;
-}
-
-// NOWAIT
-// [5.2:308:11-13]
-// Directives: dispatch, do, for, interop, scope, sections, single, target,
-// target enter data, target exit data, target update, taskwait, workshare
-//
-// [5.2:341:23]
-// (23) The effect of the nowait clause is as if it is applied to the outermost
-// leaf construct that permits it.
-template <typename C, typename H>
-bool ConstructDecompositionT<C, H>::applyClause(
-    const tomp::clause::NowaitT<TypeTy, IdTy, ExprTy> &clause,
-    const ClauseTy *node) {
-  return applyToOutermost(node);
-}
-
-template <typename C, typename H> bool ConstructDecompositionT<C, H>::split() {
-  bool success = true;
-
-  for (llvm::omp::Directive leaf :
-       llvm::omp::getLeafConstructsOrSelf(construct))
-    leafs.push_back(LeafReprInternal{leaf, /*clauses=*/{}});
-
-  for (const ClauseTy *node : nodes)
-    addClauseSymsToMap(*node, node);
-
-  // First we need to apply LINEAR, because it can generate additional
-  // "firstprivate" and "lastprivate" clauses that apply to the combined/
-  // composite construct.
-  // Collect them separately, because they may modify the clause list.
-  llvm::SmallVector<const ClauseTy *> linears;
-  for (const ClauseTy *node : nodes) {
-    if (node->id == llvm::omp::Clause::OMPC_linear)
-      linears.push_back(node);
-  }
-  for (const auto *node : linears) {
-    success = success &&
-              applyClause(std::get<tomp::clause::LinearT<TypeTy, IdTy, ExprTy>>(
-                              node->u),
-                          node);
-  }
-
-  // "allocate" clauses need to be applied last since they need to see
-  // which directives have data-privatizing clauses.
-  auto skip = [](const ClauseTy *node) {
-    switch (node->id) {
-    case llvm::omp::Clause::OMPC_allocate:
-    case llvm::omp::Clause::OMPC_linear:
-      return true;
-    default:
-      return false;
-    }
-  };
-
-  // Apply (almost) all clauses.
-  for (const ClauseTy *node : nodes) {
-    if (skip(node))
-      continue;
-    success =
-        success &&
-        std::visit([&](auto &&s) { return applyClause(s, node); }, node->u);
-  }
-
-  // Apply "allocate".
-  for (const ClauseTy *node : nodes) {
-    if (node->id != llvm::omp::Clause::OMPC_allocate)
-      continue;
-    success =
-        success &&
-        std::visit([&](auto &&s) { return applyClause(s, node); }, node->u);
-  }
-
-  return success;
-}
-
-} // namespace tomp
-
-#endif // LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H

diff  --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt
index 85e113816e3bc..3f290b63ba647 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -15,7 +15,6 @@ add_llvm_unittest(LLVMFrontendTests
   OpenMPIRBuilderTest.cpp
   OpenMPParsingTest.cpp
   OpenMPCompositionTest.cpp
-  OpenMPDecompositionTest.cpp
 
   DEPENDS
   acc_gen

diff  --git a/llvm/unittests/Frontend/OpenMPDecompositionTest.cpp b/llvm/unittests/Frontend/OpenMPDecompositionTest.cpp
deleted file mode 100644
index df48e9cc0ff4a..0000000000000
--- a/llvm/unittests/Frontend/OpenMPDecompositionTest.cpp
+++ /dev/null
@@ -1,999 +0,0 @@
-//===- llvm/unittests/Frontend/OpenMPDecompositionTest.cpp ----------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Frontend/OpenMP/ClauseT.h"
-#include "llvm/Frontend/OpenMP/ConstructDecompositionT.h"
-#include "llvm/Frontend/OpenMP/OMP.h"
-#include "gtest/gtest.h"
-
-#include <iterator>
-#include <optional>
-#include <sstream>
-#include <string>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-
-// The actual tests start at comment "--- Test" below.
-
-// Create simple instantiations of all clauses to allow manual construction
-// of clauses, and implement emitting of a directive with clauses to a string.
-//
-// The tests then follow the pattern
-// 1. Create a list of clauses.
-// 2. Pass them, together with a construct, to the decomposition class.
-// 3. Extract individual resulting leaf constructs with clauses applied
-//    to them.
-// 4. Convert them to strings and compare with expected outputs.
-
-namespace omp {
-struct TypeTy {}; // placeholder
-struct ExprTy {}; // placeholder
-using IdTy = std::string;
-} // namespace omp
-
-namespace tomp::type {
-template <> struct ObjectT<omp::IdTy, omp::ExprTy> {
-  const omp::IdTy &id() const { return name; }
-  const std::optional<omp::ExprTy> ref() const { return omp::ExprTy{}; }
-
-  omp::IdTy name;
-};
-} // namespace tomp::type
-
-namespace omp {
-template <typename ElemTy> using List = tomp::type::ListT<ElemTy>;
-
-using Object = tomp::ObjectT<IdTy, ExprTy>;
-
-namespace clause {
-using DefinedOperator = tomp::type::DefinedOperatorT<IdTy, ExprTy>;
-using ProcedureDesignator = tomp::type::ProcedureDesignatorT<IdTy, ExprTy>;
-using ReductionOperator = tomp::type::ReductionIdentifierT<IdTy, ExprTy>;
-
-using AcqRel = tomp::clause::AcqRelT<TypeTy, IdTy, ExprTy>;
-using Acquire = tomp::clause::AcquireT<TypeTy, IdTy, ExprTy>;
-using AdjustArgs = tomp::clause::AdjustArgsT<TypeTy, IdTy, ExprTy>;
-using Affinity = tomp::clause::AffinityT<TypeTy, IdTy, ExprTy>;
-using Aligned = tomp::clause::AlignedT<TypeTy, IdTy, ExprTy>;
-using Align = tomp::clause::AlignT<TypeTy, IdTy, ExprTy>;
-using Allocate = tomp::clause::AllocateT<TypeTy, IdTy, ExprTy>;
-using Allocator = tomp::clause::AllocatorT<TypeTy, IdTy, ExprTy>;
-using AppendArgs = tomp::clause::AppendArgsT<TypeTy, IdTy, ExprTy>;
-using AtomicDefaultMemOrder =
-    tomp::clause::AtomicDefaultMemOrderT<TypeTy, IdTy, ExprTy>;
-using At = tomp::clause::AtT<TypeTy, IdTy, ExprTy>;
-using Bind = tomp::clause::BindT<TypeTy, IdTy, ExprTy>;
-using Capture = tomp::clause::CaptureT<TypeTy, IdTy, ExprTy>;
-using Collapse = tomp::clause::CollapseT<TypeTy, IdTy, ExprTy>;
-using Compare = tomp::clause::CompareT<TypeTy, IdTy, ExprTy>;
-using Copyin = tomp::clause::CopyinT<TypeTy, IdTy, ExprTy>;
-using Copyprivate = tomp::clause::CopyprivateT<TypeTy, IdTy, ExprTy>;
-using Defaultmap = tomp::clause::DefaultmapT<TypeTy, IdTy, ExprTy>;
-using Default = tomp::clause::DefaultT<TypeTy, IdTy, ExprTy>;
-using Depend = tomp::clause::DependT<TypeTy, IdTy, ExprTy>;
-using Destroy = tomp::clause::DestroyT<TypeTy, IdTy, ExprTy>;
-using Detach = tomp::clause::DetachT<TypeTy, IdTy, ExprTy>;
-using Device = tomp::clause::DeviceT<TypeTy, IdTy, ExprTy>;
-using DeviceType = tomp::clause::DeviceTypeT<TypeTy, IdTy, ExprTy>;
-using DistSchedule = tomp::clause::DistScheduleT<TypeTy, IdTy, ExprTy>;
-using Doacross = tomp::clause::DoacrossT<TypeTy, IdTy, ExprTy>;
-using DynamicAllocators =
-    tomp::clause::DynamicAllocatorsT<TypeTy, IdTy, ExprTy>;
-using Enter = tomp::clause::EnterT<TypeTy, IdTy, ExprTy>;
-using Exclusive = tomp::clause::ExclusiveT<TypeTy, IdTy, ExprTy>;
-using Fail = tomp::clause::FailT<TypeTy, IdTy, ExprTy>;
-using Filter = tomp::clause::FilterT<TypeTy, IdTy, ExprTy>;
-using Final = tomp::clause::FinalT<TypeTy, IdTy, ExprTy>;
-using Firstprivate = tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>;
-using From = tomp::clause::FromT<TypeTy, IdTy, ExprTy>;
-using Full = tomp::clause::FullT<TypeTy, IdTy, ExprTy>;
-using Grainsize = tomp::clause::GrainsizeT<TypeTy, IdTy, ExprTy>;
-using HasDeviceAddr = tomp::clause::HasDeviceAddrT<TypeTy, IdTy, ExprTy>;
-using Hint = tomp::clause::HintT<TypeTy, IdTy, ExprTy>;
-using If = tomp::clause::IfT<TypeTy, IdTy, ExprTy>;
-using Inbranch = tomp::clause::InbranchT<TypeTy, IdTy, ExprTy>;
-using Inclusive = tomp::clause::InclusiveT<TypeTy, IdTy, ExprTy>;
-using Indirect = tomp::clause::IndirectT<TypeTy, IdTy, ExprTy>;
-using Init = tomp::clause::InitT<TypeTy, IdTy, ExprTy>;
-using InReduction = tomp::clause::InReductionT<TypeTy, IdTy, ExprTy>;
-using IsDevicePtr = tomp::clause::IsDevicePtrT<TypeTy, IdTy, ExprTy>;
-using Lastprivate = tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>;
-using Linear = tomp::clause::LinearT<TypeTy, IdTy, ExprTy>;
-using Link = tomp::clause::LinkT<TypeTy, IdTy, ExprTy>;
-using Map = tomp::clause::MapT<TypeTy, IdTy, ExprTy>;
-using Match = tomp::clause::MatchT<TypeTy, IdTy, ExprTy>;
-using Mergeable = tomp::clause::MergeableT<TypeTy, IdTy, ExprTy>;
-using Message = tomp::clause::MessageT<TypeTy, IdTy, ExprTy>;
-using Nocontext = tomp::clause::NocontextT<TypeTy, IdTy, ExprTy>;
-using Nogroup = tomp::clause::NogroupT<TypeTy, IdTy, ExprTy>;
-using Nontemporal = tomp::clause::NontemporalT<TypeTy, IdTy, ExprTy>;
-using Notinbranch = tomp::clause::NotinbranchT<TypeTy, IdTy, ExprTy>;
-using Novariants = tomp::clause::NovariantsT<TypeTy, IdTy, ExprTy>;
-using Nowait = tomp::clause::NowaitT<TypeTy, IdTy, ExprTy>;
-using NumTasks = tomp::clause::NumTasksT<TypeTy, IdTy, ExprTy>;
-using NumTeams = tomp::clause::NumTeamsT<TypeTy, IdTy, ExprTy>;
-using NumThreads = tomp::clause::NumThreadsT<TypeTy, IdTy, ExprTy>;
-using OmpxAttribute = tomp::clause::OmpxAttributeT<TypeTy, IdTy, ExprTy>;
-using OmpxBare = tomp::clause::OmpxBareT<TypeTy, IdTy, ExprTy>;
-using OmpxDynCgroupMem = tomp::clause::OmpxDynCgroupMemT<TypeTy, IdTy, ExprTy>;
-using Ordered = tomp::clause::OrderedT<TypeTy, IdTy, ExprTy>;
-using Order = tomp::clause::OrderT<TypeTy, IdTy, ExprTy>;
-using Partial = tomp::clause::PartialT<TypeTy, IdTy, ExprTy>;
-using Priority = tomp::clause::PriorityT<TypeTy, IdTy, ExprTy>;
-using Private = tomp::clause::PrivateT<TypeTy, IdTy, ExprTy>;
-using ProcBind = tomp::clause::ProcBindT<TypeTy, IdTy, ExprTy>;
-using Read = tomp::clause::ReadT<TypeTy, IdTy, ExprTy>;
-using Reduction = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>;
-using Relaxed = tomp::clause::RelaxedT<TypeTy, IdTy, ExprTy>;
-using Release = tomp::clause::ReleaseT<TypeTy, IdTy, ExprTy>;
-using ReverseOffload = tomp::clause::ReverseOffloadT<TypeTy, IdTy, ExprTy>;
-using Safelen = tomp::clause::SafelenT<TypeTy, IdTy, ExprTy>;
-using Schedule = tomp::clause::ScheduleT<TypeTy, IdTy, ExprTy>;
-using SeqCst = tomp::clause::SeqCstT<TypeTy, IdTy, ExprTy>;
-using Severity = tomp::clause::SeverityT<TypeTy, IdTy, ExprTy>;
-using Shared = tomp::clause::SharedT<TypeTy, IdTy, ExprTy>;
-using Simdlen = tomp::clause::SimdlenT<TypeTy, IdTy, ExprTy>;
-using Simd = tomp::clause::SimdT<TypeTy, IdTy, ExprTy>;
-using Sizes = tomp::clause::SizesT<TypeTy, IdTy, ExprTy>;
-using TaskReduction = tomp::clause::TaskReductionT<TypeTy, IdTy, ExprTy>;
-using ThreadLimit = tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy>;
-using Threads = tomp::clause::ThreadsT<TypeTy, IdTy, ExprTy>;
-using To = tomp::clause::ToT<TypeTy, IdTy, ExprTy>;
-using UnifiedAddress = tomp::clause::UnifiedAddressT<TypeTy, IdTy, ExprTy>;
-using UnifiedSharedMemory =
-    tomp::clause::UnifiedSharedMemoryT<TypeTy, IdTy, ExprTy>;
-using Uniform = tomp::clause::UniformT<TypeTy, IdTy, ExprTy>;
-using Unknown = tomp::clause::UnknownT<TypeTy, IdTy, ExprTy>;
-using Untied = tomp::clause::UntiedT<TypeTy, IdTy, ExprTy>;
-using Update = tomp::clause::UpdateT<TypeTy, IdTy, ExprTy>;
-using UseDeviceAddr = tomp::clause::UseDeviceAddrT<TypeTy, IdTy, ExprTy>;
-using UseDevicePtr = tomp::clause::UseDevicePtrT<TypeTy, IdTy, ExprTy>;
-using UsesAllocators = tomp::clause::UsesAllocatorsT<TypeTy, IdTy, ExprTy>;
-using Use = tomp::clause::UseT<TypeTy, IdTy, ExprTy>;
-using Weak = tomp::clause::WeakT<TypeTy, IdTy, ExprTy>;
-using When = tomp::clause::WhenT<TypeTy, IdTy, ExprTy>;
-using Write = tomp::clause::WriteT<TypeTy, IdTy, ExprTy>;
-} // namespace clause
-
-struct Helper {
-  std::optional<Object> getBaseObject(const Object &object) {
-    return std::nullopt;
-  }
-  std::optional<Object> getLoopIterVar() { return std::nullopt; }
-};
-
-using Clause = tomp::ClauseT<TypeTy, IdTy, ExprTy>;
-using ConstructDecomposition = tomp::ConstructDecompositionT<Clause, Helper>;
-using DirectiveWithClauses = tomp::DirectiveWithClauses<Clause>;
-} // namespace omp
-
-struct StringifyClause {
-  static std::string join(const omp::List<std::string> &Strings) {
-    std::stringstream Stream;
-    for (const auto &[Index, String] : llvm::enumerate(Strings)) {
-      if (Index != 0)
-        Stream << ", ";
-      Stream << String;
-    }
-    return Stream.str();
-  }
-
-  static std::string to_str(llvm::omp::Directive D) {
-    return getOpenMPDirectiveName(D).str();
-  }
-  static std::string to_str(llvm::omp::Clause C) {
-    return getOpenMPClauseName(C).str();
-  }
-  static std::string to_str(const omp::TypeTy &Type) { return "type"; }
-  static std::string to_str(const omp::ExprTy &Expr) { return "expr"; }
-  static std::string to_str(const omp::Object &Obj) { return Obj.id(); }
-
-  template <typename U>
-  static std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, std::string>
-  to_str(U &&Item) {
-    return std::to_string(llvm::to_underlying(Item));
-  }
-
-  template <typename U> static std::string to_str(const omp::List<U> &Items) {
-    omp::List<std::string> Names;
-    llvm::transform(Items, std::back_inserter(Names),
-                    [](auto &&S) { return to_str(S); });
-    return "(" + join(Names) + ")";
-  }
-
-  template <typename U>
-  static std::string to_str(const std::optional<U> &Item) {
-    if (Item)
-      return to_str(*Item);
-    return "";
-  }
-
-  template <typename... Us, size_t... Is>
-  static std::string to_str(const std::tuple<Us...> &Tuple,
-                            std::index_sequence<Is...>) {
-    omp::List<std::string> Strings;
-    (Strings.push_back(to_str(std::get<Is>(Tuple))), ...);
-    return "(" + join(Strings) + ")";
-  }
-
-  template <typename U>
-  static std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value,
-                          std::string>
-  to_str(U &&Item) {
-    return "";
-  }
-
-  template <typename U>
-  static std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value,
-                          std::string>
-  to_str(U &&Item) {
-    return "";
-  }
-
-  template <typename U>
-  static std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value,
-                          std::string>
-  to_str(U &&Item) {
-    // For a wrapper, stringify the wrappee, and only add parentheses if
-    // there aren't any already.
-    std::string Str = to_str(Item.v);
-    if (!Str.empty()) {
-      if (Str.front() == '(' && Str.back() == ')')
-        return Str;
-    }
-    return "(" + to_str(Item.v) + ")";
-  }
-
-  template <typename U>
-  static std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value,
-                          std::string>
-  to_str(U &&Item) {
-    constexpr size_t TupleSize =
-        std::tuple_size_v<llvm::remove_cvref_t<decltype(Item.t)>>;
-    return to_str(Item.t, std::make_index_sequence<TupleSize>{});
-  }
-
-  template <typename U>
-  static std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value,
-                          std::string>
-  to_str(U &&Item) {
-    return std::visit([](auto &&S) { return to_str(S); }, Item.u);
-  }
-
-  StringifyClause(const omp::Clause &C)
-      // Rely on content stringification to emit enclosing parentheses.
-      : Str(to_str(C.id) + to_str(C)) {}
-
-  std::string Str;
-};
-
-std::string stringify(const omp::DirectiveWithClauses &DWC) {
-  std::stringstream Stream;
-
-  Stream << getOpenMPDirectiveName(DWC.id).str();
-  for (const omp::Clause &C : DWC.clauses)
-    Stream << ' ' << StringifyClause(C).Str;
-
-  return Stream.str();
-}
-
-// --- Tests ----------------------------------------------------------
-
-namespace {
-using namespace llvm::omp;
-
-class OpenMPDecompositionTest : public testing::Test {
-protected:
-  void SetUp() override {}
-  void TearDown() override {}
-
-  omp::Helper Helper;
-  uint32_t AnyVersion = 999;
-};
-
-// PRIVATE
-// [5.2:111:5-7]
-// Directives: distribute, do, for, loop, parallel, scope, sections, simd,
-// single, target, task, taskloop, teams
-//
-// [5.2:340:1-2]
-// (1) The effect of the 1 private clause is as if it is applied only to the
-// innermost leaf construct that permits it.
-TEST_F(OpenMPDecompositionTest, Private1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_private, omp::clause::Private{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel");            // (1)
-  ASSERT_EQ(Dir1, "sections private(x)"); // (1)
-}
-
-TEST_F(OpenMPDecompositionTest, Private2) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_private, omp::clause::Private{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel private(x)"); // (1)
-  ASSERT_EQ(Dir1, "masked");              // (1)
-}
-
-// FIRSTPRIVATE
-// [5.2:112:5-7]
-// Directives: distribute, do, for, parallel, scope, sections, single, target,
-// task, taskloop, teams
-//
-// [5.2:340:3-20]
-// (3) The effect of the firstprivate clause is as if it is applied to one or
-// more leaf constructs as follows:
-//  (5) To the distribute construct if it is among the constituent constructs;
-//  (6) To the teams construct if it is among the constituent constructs and the
-//      distribute construct is not;
-//  (8) To a worksharing construct that accepts the clause if one is among the
-//      constituent constructs;
-//  (9) To the taskloop construct if it is among the constituent constructs;
-// (10) To the parallel construct if it is among the constituent constructs and
-//      neither a taskloop construct nor a worksharing construct that accepts
-//      the clause is among them;
-// (12) To the target construct if it is among the constituent constructs and
-//      the same list item neither appears in a lastprivate clause nor is the
-//      base variable or base pointer of a list item that appears in a map
-//      clause.
-//
-// (15) If the parallel construct is among the constituent constructs and the
-// effect is not as if the firstprivate clause is applied to it by the above
-// rules, then the effect is as if the shared clause with the same list item is
-// applied to the parallel construct.
-// (17) If the teams construct is among the constituent constructs and the
-// effect is not as if the firstprivate clause is applied to it by the above
-// rules, then the effect is as if the shared clause with the same list item is
-// applied to the teams construct.
-TEST_F(OpenMPDecompositionTest, Firstprivate1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel shared(x)");       // (10), (15)
-  ASSERT_EQ(Dir1, "sections firstprivate(x)"); // (8)
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate2) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_target_teams_distribute, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "target firstprivate(x)");     // (12)
-  ASSERT_EQ(Dir1, "teams shared(x)");            // (6), (17)
-  ASSERT_EQ(Dir2, "distribute firstprivate(x)"); // (5)
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate3) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-      {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_target_teams_distribute, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (12), (27)
-  ASSERT_EQ(Dir1, "teams shared(x)");          // (6), (17)
-  ASSERT_EQ(Dir2, "distribute firstprivate(x) lastprivate(, (x))"); // (5), (21)
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate4) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_teams,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "target firstprivate(x)"); // (12)
-  ASSERT_EQ(Dir1, "teams firstprivate(x)");  // (6)
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate5) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_parallel_masked_taskloop, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "parallel shared(x)"); // (10)
-  ASSERT_EQ(Dir1, "masked");
-  ASSERT_EQ(Dir2, "taskloop firstprivate(x)"); // (9)
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate6) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel firstprivate(x)"); // (10)
-  ASSERT_EQ(Dir1, "masked");
-}
-
-TEST_F(OpenMPDecompositionTest, Firstprivate7) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_firstprivate, omp::clause::Firstprivate{{x}}},
-  };
-
-  // Composite constructs are still decomposed.
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "teams shared(x)");            // (17)
-  ASSERT_EQ(Dir1, "distribute firstprivate(x)"); // (5)
-}
-
-// LASTPRIVATE
-// [5.2:115:7-8]
-// Directives: distribute, do, for, loop, sections, simd, taskloop
-//
-// [5.2:340:21-30]
-// (21) The effect of the lastprivate clause is as if it is applied to all leaf
-// constructs that permit the clause.
-// (22) If the parallel construct is among the constituent constructs and the
-// list item is not also specified in the firstprivate clause, then the effect
-// of the lastprivate clause is as if the shared clause with the same list item
-// is applied to the parallel construct.
-// (24) If the teams construct is among the constituent constructs and the list
-// item is not also specified in the firstprivate clause, then the effect of the
-// lastprivate clause is as if the shared clause with the same list item is
-// applied to the teams construct.
-// (27) If the target construct is among the constituent constructs and the list
-// item is not the base variable or base pointer of a list item that appears in
-// a map clause, the effect of the lastprivate clause is as if the same list
-// item appears in a map clause with a map-type of tofrom.
-TEST_F(OpenMPDecompositionTest, Lastprivate1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel shared(x)");          // (21), (22)
-  ASSERT_EQ(Dir1, "sections lastprivate(, (x))"); // (21)
-}
-
-TEST_F(OpenMPDecompositionTest, Lastprivate2) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_distribute,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "teams shared(x)");               // (21), (25)
-  ASSERT_EQ(Dir1, "distribute lastprivate(, (x))"); // (21)
-}
-
-TEST_F(OpenMPDecompositionTest, Lastprivate3) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_lastprivate, omp::clause::Lastprivate{{std::nullopt, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "target map(2, , , , (x))"); // (21), (27)
-  ASSERT_EQ(Dir1, "parallel shared(x)");       // (22)
-  ASSERT_EQ(Dir2, "do lastprivate(, (x))");    // (21)
-}
-
-// SHARED
-// [5.2:110:5-6]
-// Directives: parallel, task, taskloop, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-TEST_F(OpenMPDecompositionTest, Shared1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_shared, omp::clause::Shared{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_parallel_masked_taskloop, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "parallel shared(x)"); // (31)
-  ASSERT_EQ(Dir1, "masked");             // (31)
-  ASSERT_EQ(Dir2, "taskloop shared(x)"); // (31)
-}
-
-// DEFAULT
-// [5.2:109:5-6]
-// Directives: parallel, task, taskloop, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-TEST_F(OpenMPDecompositionTest, Default1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_default,
-       omp::clause::Default{
-           omp::clause::Default::DataSharingAttribute::Firstprivate}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_parallel_masked_taskloop, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "parallel default(0)"); // (31)
-  ASSERT_EQ(Dir1, "masked");              // (31)
-  ASSERT_EQ(Dir2, "taskloop default(0)"); // (31)
-}
-
-// THREAD_LIMIT
-// [5.2:277:14-15]
-// Directives: target, teams
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-TEST_F(OpenMPDecompositionTest, ThreadLimit1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_thread_limit, omp::clause::ThreadLimit{omp::ExprTy{}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_target_teams_distribute, Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "target thread_limit(expr)"); // (31)
-  ASSERT_EQ(Dir1, "teams thread_limit(expr)");  // (31)
-  ASSERT_EQ(Dir2, "distribute");                // (31)
-}
-
-// ORDER
-// [5.2:234:3-4]
-// Directives: distribute, do, for, loop, simd
-//
-// [5.2:340:31-32]
-// (31) The effect of the shared, default, thread_limit, or order clause is as
-// if it is applied to all leaf constructs that permit the clause.
-TEST_F(OpenMPDecompositionTest, Order1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_order,
-       omp::clause::Order{{omp::clause::Order::OrderModifier::Unconstrained,
-                           omp::clause::Order::Ordering::Concurrent}}},
-  };
-
-  omp::ConstructDecomposition Dec(
-      AnyVersion, Helper, OMPD_target_teams_distribute_parallel_for_simd,
-      Clauses);
-  ASSERT_EQ(Dec.output.size(), 6u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  std::string Dir4 = stringify(Dec.output[4]);
-  std::string Dir5 = stringify(Dec.output[5]);
-  ASSERT_EQ(Dir0, "target"); // (31)
-  ASSERT_EQ(Dir1, "teams");  // (31)
-  // XXX OMP.td doesn't list "order" as allowed for "distribute"
-  ASSERT_EQ(Dir2, "distribute");       // (31)
-  ASSERT_EQ(Dir3, "parallel");         // (31)
-  ASSERT_EQ(Dir4, "for order(1, 0)");  // (31)
-  ASSERT_EQ(Dir5, "simd order(1, 0)"); // (31)
-}
-
-// ALLOCATE
-// [5.2:178:7-9]
-// Directives: allocators, distribute, do, for, parallel, scope, sections,
-// single, target, task, taskgroup, taskloop, teams
-//
-// [5.2:340:33-35]
-// (33) The effect of the allocate clause is as if it is applied to all leaf
-// constructs that permit the clause and to which a data-sharing attribute
-// clause that may create a private copy of the same list item is applied.
-TEST_F(OpenMPDecompositionTest, Allocate1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_allocate,
-       omp::clause::Allocate{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
-      {OMPC_private, omp::clause::Private{{x}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel");                                // (33)
-  ASSERT_EQ(Dir1, "sections private(x) allocate(, , , (x))"); // (33)
-}
-
-// REDUCTION
-// [5.2:134:17-18]
-// Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams
-//
-// [5.2:340-341:36-13]
-// (36) The effect of the reduction clause is as if it is applied to all leaf
-// constructs that permit the clause, except for the following constructs:
-//  (1) The parallel construct, when combined with the sections,
-//      worksharing-loop, loop, or taskloop construct; and
-//  (3) The teams construct, when combined with the loop construct.
-// (4) For the parallel and teams constructs above, the effect of the reduction
-// clause instead is as if each list item or, for any list item that is an array
-// item, its corresponding base array or base pointer appears in a shared clause
-// for the construct.
-// (6) If the task reduction-modifier is specified, the effect is as if it only
-// modifies the behavior of the reduction clause on the innermost leaf construct
-// that accepts the modifier (see Section 5.5.8).
-// (8) If the inscan reduction-modifier is specified, the effect is as if it
-// modifies the behavior of the reduction clause on all constructs of the
-// combined construct to which the clause is applied and that accept the
-// modifier.
-// (10) If a list item in a reduction clause on a combined target construct does
-// not have the same base variable or base pointer as a list item in a map
-// clause on the construct, then the effect is as if the list item in the
-// reduction clause appears as a list item in a map clause with a map-type of
-// tofrom.
-namespace red {
-// Make is easier to construct reduction operators from built-in intrinsics.
-omp::clause::ReductionOperator
-makeOp(omp::clause::DefinedOperator::IntrinsicOperator Op) {
-  return omp::clause::ReductionOperator{omp::clause::DefinedOperator{Op}};
-}
-} // namespace red
-
-TEST_F(OpenMPDecompositionTest, Reduction1) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_sections,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel shared(x)");             // (36), (1), (4)
-  ASSERT_EQ(Dir1, "sections reduction(, (3), (x))"); // (36)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction2) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_parallel_masked,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "parallel reduction(, (3), (x))"); // (36), (1), (4)
-  ASSERT_EQ(Dir1, "masked");                         // (36)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction3) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_teams_loop, Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "teams shared(x)");            // (36), (3), (4)
-  ASSERT_EQ(Dir1, "loop reduction(, (3), (x))"); // (36)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction4) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_teams_distribute_parallel_for, Clauses);
-  ASSERT_EQ(Dec.output.size(), 4u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3)
-  ASSERT_EQ(Dir1, "distribute");                  // (36)
-  ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
-  ASSERT_EQ(Dir3, "for reduction(, (3), (x))");   // (36)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction5) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-  auto TaskMod = omp::clause::Reduction::ReductionModifier::Task;
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{TaskMod, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_teams_distribute_parallel_for, Clauses);
-  ASSERT_EQ(Dec.output.size(), 4u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (6)
-  ASSERT_EQ(Dir1, "distribute");                  // (36)
-  ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
-  ASSERT_EQ(Dir3, "for reduction(2, (3), (x))");  // (36), (6)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction6) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-  auto InscanMod = omp::clause::Reduction::ReductionModifier::Inscan;
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{InscanMod, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_teams_distribute_parallel_for, Clauses);
-  ASSERT_EQ(Dec.output.size(), 4u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  ASSERT_EQ(Dir0, "teams reduction(, (3), (x))"); // (36), (3), (8)
-  ASSERT_EQ(Dir1, "distribute");                  // (36)
-  ASSERT_EQ(Dir2, "parallel shared(x)");          // (36), (1), (4)
-  ASSERT_EQ(Dir3, "for reduction(1, (3), (x))");  // (36), (8)
-}
-
-TEST_F(OpenMPDecompositionTest, Reduction7) {
-  omp::Object x{"x"};
-  auto Add = red::makeOp(omp::clause::DefinedOperator::IntrinsicOperator::Add);
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_reduction, omp::clause::Reduction{{std::nullopt, {Add}, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_do,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  // XXX Currently OMP.td allows "reduction" on "target".
-  ASSERT_EQ(Dir0,
-            "target reduction(, (3), (x)) map(2, , , , (x))"); // (36), (10)
-  ASSERT_EQ(Dir1, "parallel shared(x)");                       // (36), (1), (4)
-  ASSERT_EQ(Dir2, "do reduction(, (3), (x))");                 // (36)
-}
-
-// IF
-// [5.2:72:7-9]
-// Directives: cancel, parallel, simd, target, target data, target enter data,
-// target exit data, target update, task, taskloop
-//
-// [5.2:72:15-18]
-// (15) For combined or composite constructs, the if clause only applies to the
-// semantics of the construct named in the directive-name-modifier.
-// (16) For a combined or composite construct, if no directive-name-modifier is
-// specified then the if clause applies to all constituent constructs to which
-// an if clause can apply.
-TEST_F(OpenMPDecompositionTest, If1) {
-  omp::List<omp::Clause> Clauses{
-      {OMPC_if,
-       omp::clause::If{{llvm::omp::Directive::OMPD_parallel, omp::ExprTy{}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_target_parallel_for_simd, Clauses);
-  ASSERT_EQ(Dec.output.size(), 4u);
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  ASSERT_EQ(Dir0, "target");              // (15)
-  ASSERT_EQ(Dir1, "parallel if(, expr)"); // (15)
-  ASSERT_EQ(Dir2, "for");                 // (15)
-  ASSERT_EQ(Dir3, "simd");                // (15)
-}
-
-TEST_F(OpenMPDecompositionTest, If2) {
-  omp::List<omp::Clause> Clauses{
-      {OMPC_if, omp::clause::If{{std::nullopt, omp::ExprTy{}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper,
-                                  OMPD_target_parallel_for_simd, Clauses);
-  ASSERT_EQ(Dec.output.size(), 4u);
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  std::string Dir3 = stringify(Dec.output[3]);
-  ASSERT_EQ(Dir0, "target if(, expr)");   // (16)
-  ASSERT_EQ(Dir1, "parallel if(, expr)"); // (16)
-  ASSERT_EQ(Dir2, "for");                 // (16)
-  ASSERT_EQ(Dir3, "simd if(, expr)");     // (16)
-}
-
-// LINEAR
-// [5.2:118:1-2]
-// Directives: declare simd, do, for, simd
-//
-// [5.2:341:15-22]
-// (15.1) The effect of the linear clause is as if it is applied to the
-// innermost leaf construct.
-// (15.2) Additionally, if the list item is not the iteration variable of a simd
-// or worksharing-loop SIMD construct, the effect on the outer leaf constructs
-// is as if the list item was specified in firstprivate and lastprivate clauses
-// on the combined or composite construct, with the rules specified above
-// applied.
-// (19) If a list item of the linear clause is the iteration variable of a simd
-// or worksharing-loop SIMD construct and it is not declared in the construct,
-// the effect on the outer leaf constructs is as if the list item was specified
-// in a lastprivate clause on the combined or composite construct with the rules
-// specified above applied.
-TEST_F(OpenMPDecompositionTest, Linear1) {
-  omp::Object x{"x"};
-
-  omp::List<omp::Clause> Clauses{
-      {OMPC_linear,
-       omp::clause::Linear{{std::nullopt, std::nullopt, std::nullopt, {x}}}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_for_simd, Clauses);
-  ASSERT_EQ(Dec.output.size(), 2u);
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  ASSERT_EQ(Dir0, "for firstprivate(x) lastprivate(, (x))"); // (15.1), (15.2)
-  ASSERT_EQ(Dir1, "simd linear(, , , (x)) lastprivate(, (x))"); // (15.1)
-}
-
-// NOWAIT
-// [5.2:308:11-13]
-// Directives: dispatch, do, for, interop, scope, sections, single, target,
-// target enter data, target exit data, target update, taskwait, workshare
-//
-// [5.2:341:23]
-// (23) The effect of the nowait clause is as if it is applied to the outermost
-// leaf construct that permits it.
-TEST_F(OpenMPDecompositionTest, Nowait1) {
-  omp::List<omp::Clause> Clauses{
-      {OMPC_nowait, omp::clause::Nowait{}},
-  };
-
-  omp::ConstructDecomposition Dec(AnyVersion, Helper, OMPD_target_parallel_for,
-                                  Clauses);
-  ASSERT_EQ(Dec.output.size(), 3u);
-  std::string Dir0 = stringify(Dec.output[0]);
-  std::string Dir1 = stringify(Dec.output[1]);
-  std::string Dir2 = stringify(Dec.output[2]);
-  ASSERT_EQ(Dir0, "target nowait"); // (23)
-  ASSERT_EQ(Dir1, "parallel");      // (23)
-  ASSERT_EQ(Dir2, "for");           // (23)
-}
-} // namespace


        


More information about the flang-commits mailing list