[flang-commits] [flang] [Flang][OpenMP] Add semantic support for Loop Sequences and OpenMP loop fuse (PR #161213)
    Ferran Toda via flang-commits 
    flang-commits at lists.llvm.org
       
    Tue Oct 28 09:43:55 PDT 2025
    
    
  
https://github.com/NouTimbaler updated https://github.com/llvm/llvm-project/pull/161213
>From c7d82bce3aa499efe2d7645c61e41e179118d7b9 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 29 Sep 2025 14:33:49 +0000
Subject: [PATCH 1/6] Loop sequences and loop fuse semantics
---
 flang/include/flang/Parser/parse-tree.h       |   4 +-
 .../flang/Semantics/openmp-directive-sets.h   |   7 +
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  13 +-
 flang/lib/Lower/OpenMP/Utils.cpp              |   8 +-
 flang/lib/Parser/openmp-parsers.cpp           |   1 +
 flang/lib/Parser/unparse.cpp                  |   3 +-
 flang/lib/Semantics/canonicalize-omp.cpp      |  92 ++++++----
 flang/lib/Semantics/check-omp-loop.cpp        |  30 ++--
 flang/lib/Semantics/resolve-directives.cpp    | 157 +++++++++---------
 flang/lib/Semantics/rewrite-parse-tree.cpp    |  44 +++--
 flang/test/Parser/OpenMP/fuse.f90             |  28 ++++
 .../loop-transformation-construct04.f90       |  78 +++++++++
 .../loop-transformation-construct05.f90       |  86 ++++++++++
 .../loop-transformation-construct01.f90       |   4 +-
 .../loop-transformation-construct02.f90       |  95 +++++++++++
 15 files changed, 493 insertions(+), 157 deletions(-)
 create mode 100644 flang/test/Parser/OpenMP/fuse.f90
 create mode 100644 flang/test/Parser/OpenMP/loop-transformation-construct04.f90
 create mode 100644 flang/test/Parser/OpenMP/loop-transformation-construct05.f90
 create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 6dd4f2492cf22..a21029b1f509f 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5260,7 +5260,7 @@ using NestedConstruct =
 struct OpenMPLoopConstruct {
   TUPLE_CLASS_BOILERPLATE(OpenMPLoopConstruct);
   OpenMPLoopConstruct(OmpBeginLoopDirective &&a)
-      : t({std::move(a), std::nullopt, std::nullopt}) {}
+      : t({std::move(a), std::list<NestedConstruct>(), std::nullopt}) {}
 
   const OmpBeginLoopDirective &BeginDir() const {
     return std::get<OmpBeginLoopDirective>(t);
@@ -5268,7 +5268,7 @@ struct OpenMPLoopConstruct {
   const std::optional<OmpEndLoopDirective> &EndDir() const {
     return std::get<std::optional<OmpEndLoopDirective>>(t);
   }
-  std::tuple<OmpBeginLoopDirective, std::optional<NestedConstruct>,
+  std::tuple<OmpBeginLoopDirective, std::list<NestedConstruct>,
       std::optional<OmpEndLoopDirective>>
       t;
 };
diff --git a/flang/include/flang/Semantics/openmp-directive-sets.h b/flang/include/flang/Semantics/openmp-directive-sets.h
index 01e8481e05721..609a7be700c28 100644
--- a/flang/include/flang/Semantics/openmp-directive-sets.h
+++ b/flang/include/flang/Semantics/openmp-directive-sets.h
@@ -275,10 +275,17 @@ static const OmpDirectiveSet loopConstructSet{
     Directive::OMPD_teams_distribute_parallel_do_simd,
     Directive::OMPD_teams_distribute_simd,
     Directive::OMPD_teams_loop,
+    Directive::OMPD_fuse,
     Directive::OMPD_tile,
     Directive::OMPD_unroll,
 };
 
+static const OmpDirectiveSet loopTransformationSet{
+    Directive::OMPD_tile,
+    Directive::OMPD_unroll,
+    Directive::OMPD_fuse,
+};
+
 static const OmpDirectiveSet nonPartialVarSet{
     Directive::OMPD_allocate,
     Directive::OMPD_allocators,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 71067283d13f7..618ad48e9e29f 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3470,6 +3470,11 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
     break;
   case llvm::omp::Directive::OMPD_tile:
     genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
+  case llvm::omp::Directive::OMPD_fuse:
+    unsigned version = semaCtx.langOptions().OpenMPVersion;
+    if (!semaCtx.langOptions().OpenMPSimd)
+      TODO(loc, "Unhandled loop directive (" +
+                    llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
     break;
   case llvm::omp::Directive::OMPD_unroll:
     genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
@@ -3918,12 +3923,12 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 
   mlir::Location currentLocation = converter.genLocation(beginSpec.source);
 
-  auto &optLoopCons =
-      std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t);
-  if (optLoopCons.has_value()) {
+  auto &loopConsList =
+      std::get<std::list<parser::NestedConstruct>>(loopConstruct.t);
+  for (auto &loopCons : loopConsList) {
     if (auto *ompNestedLoopCons{
             std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-                &*optLoopCons)}) {
+                &loopCons)}) {
       llvm::omp::Directive nestedDirective =
           parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v;
       switch (nestedDirective) {
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 6487f599df72a..7ae0c01c21987 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -631,13 +631,13 @@ static void processTileSizesFromOpenMPConstruct(
   if (!ompCons)
     return;
   if (auto *ompLoop{std::get_if<parser::OpenMPLoopConstruct>(&ompCons->u)}) {
-    const auto &nestedOptional =
-        std::get<std::optional<parser::NestedConstruct>>(ompLoop->t);
-    assert(nestedOptional.has_value() &&
+    const auto &loopConsList =
+        std::get<std::list<parser::NestedConstruct>>(ompLoop->t);
+    assert(loopConsList.size() == 1 &&
            "Expected a DoConstruct or OpenMPLoopConstruct");
     const auto *innerConstruct =
         std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-            &(nestedOptional.value()));
+            &(loopConsList.front()));
     if (innerConstruct) {
       const auto &innerLoopDirective = innerConstruct->value();
       const parser::OmpDirectiveSpecification &innerBeginSpec =
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index c0472ad3c0692..10452694d100d 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2037,6 +2037,7 @@ static constexpr DirectiveSet GetLoopDirectives() {
       unsigned(Directive::OMPD_teams_distribute_parallel_do_simd),
       unsigned(Directive::OMPD_teams_distribute_simd),
       unsigned(Directive::OMPD_teams_loop),
+      unsigned(Directive::OMPD_fuse),
       unsigned(Directive::OMPD_tile),
       unsigned(Directive::OMPD_unroll),
   };
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index b172e429c84e8..d8f8c4b270e27 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2725,8 +2725,7 @@ class UnparseVisitor {
   }
   void Unparse(const OpenMPLoopConstruct &x) {
     Walk(std::get<OmpBeginLoopDirective>(x.t));
-    Walk(std::get<std::optional<std::variant<DoConstruct,
-            common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t));
+    Walk(std::get<std::list<parser::NestedConstruct>>(x.t));
     Walk(std::get<std::optional<OmpEndLoopDirective>>(x.t));
   }
   void Unparse(const BasedPointer &x) {
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index c884658bf464a..74b3fa978a53c 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -10,6 +10,7 @@
 #include "flang/Parser/parse-tree-visitor.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/semantics.h"
+#include "flang/Semantics/openmp-directive-sets.h"
 
 // After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
 // Constructs more structured which provide explicit scopes for later
@@ -137,30 +138,42 @@ class CanonicalizationOfOmp {
           "A DO loop must follow the %s directive"_err_en_US,
           parser::ToUpperCaseLetters(dirName.source.ToString()));
     };
-    auto tileUnrollError = [](const parser::OmpDirectiveName &dirName,
+    auto transformUnrollError = [](const parser::OmpDirectiveName &dirName,
                                parser::Messages &messages) {
       messages.Say(dirName.source,
-          "If a loop construct has been fully unrolled, it cannot then be tiled"_err_en_US,
+          "If a loop construct has been fully unrolled, it cannot then be further transformed"_err_en_US,
           parser::ToUpperCaseLetters(dirName.source.ToString()));
     };
+    auto missingEndFuse = [](auto &dir, auto &messages) {
+      messages.Say(dir.source,
+          "The %s construct requires the END FUSE directive"_err_en_US,
+          parser::ToUpperCaseLetters(dir.source.ToString()));
+    };
+
+    bool endFuseNeeded = beginName.v == llvm::omp::Directive::OMPD_fuse;
 
     nextIt = it;
-    while (++nextIt != block.end()) {
+    nextIt++;
+    while (nextIt != block.end()) {
       // Ignore compiler directives.
-      if (GetConstructIf<parser::CompilerDirective>(*nextIt))
+      if (GetConstructIf<parser::CompilerDirective>(*nextIt)) {
+        nextIt++;
         continue;
+      }
 
       if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
         if (doCons->GetLoopControl()) {
           // move DoConstruct
-          std::get<std::optional<std::variant<parser::DoConstruct,
-              common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t) =
-              std::move(*doCons);
+          std::get<std::list<parser::NestedConstruct>>(x.t).push_back(
+              std::move(*doCons));
           nextIt = block.erase(nextIt);
           // try to match OmpEndLoopDirective
           if (nextIt != block.end()) {
             if (auto *endDir{
                     GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+              auto &endDirName = endDir->DirName();
+              if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+                endFuseNeeded = false;
               std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
                   std::move(*endDir);
               nextIt = block.erase(nextIt);
@@ -170,6 +183,7 @@ class CanonicalizationOfOmp {
           messages_.Say(beginName.source,
               "DO loop after the %s directive must have loop control"_err_en_US,
               parser::ToUpperCaseLetters(beginName.source.ToString()));
+          endFuseNeeded = false;
         }
       } else if (auto *ompLoopCons{
                      GetOmpIf<parser::OpenMPLoopConstruct>(*nextIt)}) {
@@ -177,10 +191,29 @@ class CanonicalizationOfOmp {
         // OpenMP Loop Construct and the DO loop itself
         auto &nestedBeginDirective = ompLoopCons->BeginDir();
         auto &nestedBeginName = nestedBeginDirective.DirName();
-        if ((nestedBeginName.v == llvm::omp::Directive::OMPD_unroll ||
-                nestedBeginName.v == llvm::omp::Directive::OMPD_tile) &&
-            !(nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
-                beginName.v == llvm::omp::Directive::OMPD_tile)) {
+        if (llvm::omp::loopTransformationSet.test(nestedBeginName.v)) {
+          if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
+              llvm::omp::loopTransformationSet.test(beginName.v)) {
+            // if a loop has been unrolled, the user can not then tile that loop
+            // as it has been unrolled
+            const parser::OmpClauseList &unrollClauseList{
+              nestedBeginDirective.Clauses()};
+            if (unrollClauseList.v.empty()) {
+              // if the clause list is empty for an unroll construct, we assume
+              // the loop is being fully unrolled
+              transformUnrollError(beginName, messages_);
+              endFuseNeeded = false;
+            } else {
+              // parse the clauses for the unroll directive to find the full
+              // clause
+              for (auto &clause : unrollClauseList.v) {
+                if (clause.Id() == llvm::omp::OMPC_full) {
+                  transformUnrollError(beginName, messages_);
+                  endFuseNeeded = false;
+                }
+              }
+            }
+          }
           // iterate through the remaining block items to find the end directive
           // for the unroll/tile directive.
           parser::Block::iterator endIt;
@@ -190,6 +223,8 @@ class CanonicalizationOfOmp {
                     GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
               auto &endDirName = endDir->DirName();
               if (endDirName.v == beginName.v) {
+                if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+                  endFuseNeeded = false;
                 std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
                     std::move(*endDir);
                 endIt = block.erase(endIt);
@@ -199,43 +234,30 @@ class CanonicalizationOfOmp {
             ++endIt;
           }
           RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
-          auto &ompLoop = std::get<std::optional<parser::NestedConstruct>>(x.t);
-          ompLoop =
-              std::optional<parser::NestedConstruct>{parser::NestedConstruct{
-                  common::Indirection{std::move(*ompLoopCons)}}};
+          auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+          loopConsList.push_back(parser::NestedConstruct{
+              common::Indirection{std::move(*ompLoopCons)}});
           nextIt = block.erase(nextIt);
-        } else if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
-            beginName.v == llvm::omp::Directive::OMPD_tile) {
-          // if a loop has been unrolled, the user can not then tile that loop
-          // as it has been unrolled
-          const parser::OmpClauseList &unrollClauseList{
-              nestedBeginDirective.Clauses()};
-          if (unrollClauseList.v.empty()) {
-            // if the clause list is empty for an unroll construct, we assume
-            // the loop is being fully unrolled
-            tileUnrollError(beginName, messages_);
-          } else {
-            // parse the clauses for the unroll directive to find the full
-            // clause
-            for (auto &clause : unrollClauseList.v) {
-              if (clause.Id() == llvm::omp::OMPC_full) {
-                tileUnrollError(beginName, messages_);
-              }
-            }
-          }
         } else {
           messages_.Say(nestedBeginName.source,
               "Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs"_err_en_US,
               parser::ToUpperCaseLetters(nestedBeginName.source.ToString()));
+          endFuseNeeded = false;
         }
       } else {
         missingDoConstruct(beginName, messages_);
+        endFuseNeeded = false;
       }
+      if (endFuseNeeded)
+        continue;
       // If we get here, we either found a loop, or issued an error message.
       return;
     }
     if (nextIt == block.end()) {
-      missingDoConstruct(beginName, messages_);
+      if (endFuseNeeded)
+        missingEndFuse(beginName, messages_);
+      else
+        missingDoConstruct(beginName, messages_);
     }
   }
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index aaaa2d6e78280..6ac55d836d2e7 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -285,10 +285,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
   }
   SetLoopInfo(x);
 
-  auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
-  if (optLoopCons.has_value()) {
-    if (const auto &doConstruct{
-            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+  auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+  for (auto &loopCons : loopConsList) {
+    if (const auto &doConstruct{std::get_if<parser::DoConstruct>(&loopCons)}) {
       const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
       CheckNoBranching(doBlock, beginName.v, beginName.source);
     }
@@ -314,10 +313,10 @@ const parser::Name OmpStructureChecker::GetLoopIndex(
 }
 
 void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
-  auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
-  if (optLoopCons.has_value()) {
+  auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+  if (loopConsList.size() == 1) {
     if (const auto &loopConstruct{
-            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+            std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
       const parser::DoConstruct *loop{&*loopConstruct};
       if (loop && loop->IsDoNormal()) {
         const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -329,10 +328,10 @@ void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
 
 void OmpStructureChecker::CheckLoopItrVariableIsInt(
     const parser::OpenMPLoopConstruct &x) {
-  auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
-  if (optLoopCons.has_value()) {
+  auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+  for (auto &loopCons : loopConsList) {
     if (const auto &loopConstruct{
-            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+            std::get_if<parser::DoConstruct>(&loopCons)}) {
 
       for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
         if (loop->IsDoNormal()) {
@@ -417,10 +416,11 @@ void OmpStructureChecker::CheckDistLinear(
 
     // Match the loop index variables with the collected symbols from linear
     // clauses.
-    auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
-    if (optLoopCons.has_value()) {
+    auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+    for (auto &loopCons : loopConsList) {
+      std::int64_t collapseVal_ = collapseVal;
       if (const auto &loopConstruct{
-              std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+              std::get_if<parser::DoConstruct>(&loopCons)}) {
         for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
           if (loop->IsDoNormal()) {
             const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -428,8 +428,8 @@ void OmpStructureChecker::CheckDistLinear(
               // Remove the symbol from the collected set
               indexVars.erase(&itrVal.symbol->GetUltimate());
             }
-            collapseVal--;
-            if (collapseVal == 0) {
+            collapseVal_--;
+            if (collapseVal_ == 0) {
               break;
             }
           }
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 196755e2912a8..ee781e62dc3d9 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -496,7 +496,10 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   void Post(const parser::OpenMPSimpleStandaloneConstruct &) { PopContext(); }
 
   bool Pre(const parser::OpenMPLoopConstruct &);
-  void Post(const parser::OpenMPLoopConstruct &) { PopContext(); }
+  void Post(const parser::OpenMPLoopConstruct &) {
+    ordCollapseLevel++;
+    PopContext();
+  }
   void Post(const parser::OmpBeginLoopDirective &) {
     GetContext().withinConstruct = true;
   }
@@ -1995,6 +1998,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
   case llvm::omp::Directive::OMPD_teams_distribute_parallel_do_simd:
   case llvm::omp::Directive::OMPD_teams_distribute_simd:
   case llvm::omp::Directive::OMPD_teams_loop:
+  case llvm::omp::Directive::OMPD_fuse:
   case llvm::omp::Directive::OMPD_tile:
   case llvm::omp::Directive::OMPD_unroll:
     PushContext(beginName.source, beginName.v);
@@ -2014,10 +2018,10 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
   SetContextAssociatedLoopLevel(GetNumAffectedLoopsFromLoopConstruct(x));
 
   if (beginName.v == llvm::omp::Directive::OMPD_do) {
-    auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
-    if (optLoopCons.has_value()) {
+    auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+    if (loopConsList.size() == 1) { //a OMPD_do cant be a loop sequence
       if (const auto &doConstruct{
-              std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+              std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
         if (doConstruct->IsDoWhile()) {
           return true;
         }
@@ -2178,17 +2182,18 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct(
     llvm::SmallVector<std::int64_t> &levels,
     llvm::SmallVector<const parser::OmpClause *> &clauses) {
 
-  const auto &nestedOptional =
-      std::get<std::optional<parser::NestedConstruct>>(x.t);
-  assert(nestedOptional.has_value() &&
+  const auto &nestedList = std::get<std::list<parser::NestedConstruct>>(x.t);
+  assert(nestedList.size() >= 1 &&
       "Expected a DoConstruct or OpenMPLoopConstruct");
-  const auto *innerConstruct =
-      std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-          &(nestedOptional.value()));
+  for (auto &nest : nestedList) {
+    const auto *innerConstruct =
+        std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+            &nest);
 
-  if (innerConstruct) {
-    CollectNumAffectedLoopsFromLoopConstruct(
-        innerConstruct->value(), levels, clauses);
+    if (innerConstruct) {
+      CollectNumAffectedLoopsFromLoopConstruct(
+          innerConstruct->value(), levels, clauses);
+    }
   }
 }
 
@@ -2365,76 +2370,78 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
   const parser::OmpClause *clause{GetAssociatedClause()};
   bool hasCollapseClause{
       clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false};
-  const parser::OpenMPLoopConstruct *innerMostLoop = &x;
-  const parser::NestedConstruct *innerMostNest = nullptr;
-  while (auto &optLoopCons{
-      std::get<std::optional<parser::NestedConstruct>>(innerMostLoop->t)}) {
-    innerMostNest = &(optLoopCons.value());
-    if (const auto *innerLoop{
-            std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-                innerMostNest)}) {
-      innerMostLoop = &(innerLoop->value());
-    } else
-      break;
-  }
 
-  if (innerMostNest) {
-    if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
-      for (const parser::DoConstruct *loop{&*outer}; loop && level > 0;
-          --level) {
-        if (loop->IsDoConcurrent()) {
-          // DO CONCURRENT is explicitly allowed for the LOOP construct so long
-          // as there isn't a COLLAPSE clause
-          if (isLoopConstruct) {
-            if (hasCollapseClause) {
-              // hasCollapseClause implies clause != nullptr
-              context_.Say(clause->source,
-                  "DO CONCURRENT loops cannot be used with the COLLAPSE clause."_err_en_US);
-            }
-          } else {
-            auto &stmt =
+  const parser::OpenMPLoopConstruct *innerMostLoop = &x;
+  auto &loopConsList =
+      std::get<std::list<parser::NestedConstruct>>(innerMostLoop->t);
+  for (auto &loopCons : loopConsList) {
+    std::int64_t level_{level};
+    const parser::NestedConstruct *innerMostNest = nullptr;
+    if (const auto &innerloop{std::get_if<parser::DoConstruct>(&loopCons)}) {
+      innerMostNest = &loopCons;
+    } else if (const auto *innerLoop{
+        std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+            &loopCons)}) {
+      PrivatizeAssociatedLoopIndexAndCheckLoopLevel(innerLoop->value());
+    }
+
+    if (innerMostNest) {
+      if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
+        for (const parser::DoConstruct *loop{&*outer}; loop && level_ > 0;
+            --level_) {
+          if (loop->IsDoConcurrent()) {
+            // DO CONCURRENT is explicitly allowed for the LOOP construct so long
+            // as there isn't a COLLAPSE clause
+            if (isLoopConstruct) {
+              if (hasCollapseClause) {
+                // hasCollapseClause implies clause != nullptr
+                context_.Say(clause->source,
+                    "DO CONCURRENT loops cannot be used with the COLLAPSE clause."_err_en_US);
+              }
+            } else {
+              auto &stmt =
                 std::get<parser::Statement<parser::NonLabelDoStmt>>(loop->t);
-            context_.Say(stmt.source,
-                "DO CONCURRENT loops cannot form part of a loop nest."_err_en_US);
-          }
-        }
-        // go through all the nested do-loops and resolve index variables
-        const parser::Name *iv{GetLoopIndex(*loop)};
-        if (iv) {
-          if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
-            SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
-            iv->symbol = symbol; // adjust the symbol within region
-            AddToContextObjectWithDSA(*symbol, ivDSA);
+              context_.Say(stmt.source,
+                  "DO CONCURRENT loops cannot form part of a loop nest."_err_en_US);
+            }
           }
+          // go through all the nested do-loops and resolve index variables
+          const parser::Name *iv{GetLoopIndex(*loop)};
+          if (iv) {
+            if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
+              SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
+              iv->symbol = symbol; // adjust the symbol within region
+              AddToContextObjectWithDSA(*symbol, ivDSA);
+            }
 
-          const auto &block{std::get<parser::Block>(loop->t)};
-          const auto it{block.begin()};
-          loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
+            const auto &block{std::get<parser::Block>(loop->t)};
+            const auto it{block.begin()};
+            loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
+          }
         }
-      }
-      CheckAssocLoopLevel(level, GetAssociatedClause());
-    } else if (const auto *loop{std::get_if<
-                   common::Indirection<parser::OpenMPLoopConstruct>>(
-                   innerMostNest)}) {
-      const parser::OmpDirectiveSpecification &beginSpec{
+        CheckAssocLoopLevel(level_, GetAssociatedClause());
+      } else if (const auto *loop{std::get_if<
+          common::Indirection<parser::OpenMPLoopConstruct>>(
+              innerMostNest)}) {
+        const parser::OmpDirectiveSpecification &beginSpec{
           loop->value().BeginDir()};
-      const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
-      if (beginName.v != llvm::omp::Directive::OMPD_unroll &&
-          beginName.v != llvm::omp::Directive::OMPD_tile) {
-        context_.Say(GetContext().directiveSource,
-            "Only UNROLL or TILE constructs are allowed between an OpenMP Loop Construct and a DO construct"_err_en_US,
-            parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
-                GetContext().directive, version)
-                    .str()));
+        const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
+        if (!llvm::omp::loopTransformationSet.test(beginName.v)) {
+          context_.Say(GetContext().directiveSource,
+              "Only Loop Transformation Constructs are allowed between an OpenMP Loop Construct and a DO construct"_err_en_US,
+              parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
+                  GetContext().directive, version)
+                .str()));
+        } else {
+          PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
+        }
       } else {
-        PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
-      }
-    } else {
-      context_.Say(GetContext().directiveSource,
-          "A DO loop must follow the %s directive"_err_en_US,
-          parser::ToUpperCaseLetters(
+        context_.Say(GetContext().directiveSource,
+            "A DO loop must follow the %s directive"_err_en_US,
+            parser::ToUpperCaseLetters(
               llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
-                  .str()));
+              .str()));
+      }
     }
   }
 }
diff --git a/flang/lib/Semantics/rewrite-parse-tree.cpp b/flang/lib/Semantics/rewrite-parse-tree.cpp
index 5b7dab309eda7..391099d5a866b 100644
--- a/flang/lib/Semantics/rewrite-parse-tree.cpp
+++ b/flang/lib/Semantics/rewrite-parse-tree.cpp
@@ -195,20 +195,26 @@ void RewriteMutator::OpenMPSimdOnly(
             ++it;
             continue;
           }
-          auto &nest =
-              std::get<std::optional<parser::NestedConstruct>>(ompLoop->t);
-
-          if (auto *doConstruct =
-                  std::get_if<parser::DoConstruct>(&nest.value())) {
-            auto &loopBody = std::get<parser::Block>(doConstruct->t);
-            // We can only remove some constructs from a loop when it's _not_ a
-            // OpenMP simd loop
-            OpenMPSimdOnly(loopBody, /*isNonSimdLoopBody=*/true);
-            auto newDoConstruct = std::move(*doConstruct);
-            auto newLoop = parser::ExecutionPartConstruct{
-                parser::ExecutableConstruct{std::move(newDoConstruct)}};
+          auto &nestList =
+              std::get<std::list<parser::NestedConstruct>>(ompLoop->t);
+
+          std::list<parser::ExecutionPartConstruct> doList;
+          for (auto &nest : nestList) {
+            if (auto *doConstruct = std::get_if<parser::DoConstruct>(&nest)) {
+              auto &loopBody = std::get<parser::Block>(doConstruct->t);
+              // We can only remove some constructs from a loop when it's _not_
+              // a OpenMP simd loop
+              OpenMPSimdOnly(loopBody, /*isNonSimdLoopBody=*/true);
+              auto newDoConstruct = std::move(*doConstruct);
+              auto newLoop = parser::ExecutionPartConstruct{
+                  parser::ExecutableConstruct{std::move(newDoConstruct)}};
+              doList.insert(doList.end(), std::move(newLoop));
+            }
+          }
+          if (!doList.empty()) {
             it = block.erase(it);
-            block.insert(it, std::move(newLoop));
+            for (auto &newLoop : doList)
+              block.insert(it, std::move(newLoop));
             continue;
           }
         } else if (auto *ompCon{std::get_if<parser::OpenMPSectionsConstruct>(
@@ -386,13 +392,15 @@ bool RewriteMutator::Pre(parser::OpenMPLoopConstruct &ompLoop) {
     // If we're looking at a non-simd OpenMP loop, we need to explicitly
     // call OpenMPSimdOnly on the nested loop block while indicating where
     // the block comes from.
-    auto &nest = std::get<std::optional<parser::NestedConstruct>>(ompLoop.t);
-    if (!nest.has_value()) {
+    auto &nestList = std::get<std::list<parser::NestedConstruct>>(ompLoop.t);
+    if (nestList.empty()) {
       return true;
     }
-    if (auto *doConstruct = std::get_if<parser::DoConstruct>(&*nest)) {
-      auto &innerBlock = std::get<parser::Block>(doConstruct->t);
-      OpenMPSimdOnly(innerBlock, /*isNonSimdLoopBody=*/true);
+    for (auto &nest : nestList) {
+      if (auto *doConstruct = std::get_if<parser::DoConstruct>(&nest)) {
+        auto &innerBlock = std::get<parser::Block>(doConstruct->t);
+        OpenMPSimdOnly(innerBlock, /*isNonSimdLoopBody=*/true);
+      }
     }
   }
   return true;
diff --git a/flang/test/Parser/OpenMP/fuse.f90 b/flang/test/Parser/OpenMP/fuse.f90
new file mode 100644
index 0000000000000..98ce0e33797b5
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse.f90
@@ -0,0 +1,28 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_fuse(x)
+
+  integer, intent(inout)::x
+
+!CHECK: !$omp fuse
+!$omp  fuse
+!CHECK: do
+  do x = 1, 100
+  	call F1()
+!CHECK: end do
+  end do
+!CHECK: do
+  do x = 1, 100
+  	call F1()
+!CHECK: end do
+  end do
+!CHECK: !$omp end fuse
+!$omp end fuse
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = fuse
+
+END subroutine openmp_fuse
+
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct04.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
new file mode 100644
index 0000000000000..65a5f2143e6aa
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
@@ -0,0 +1,78 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse constructs a correct sequence.
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine loop_transformation_construct
+  implicit none
+  integer :: I = 10
+  integer :: j
+
+  !$omp do
+  !$omp fuse
+  do i = 1, I
+    continue
+  end do
+  do j = 1, I
+    continue
+  end do
+  !$omp end fuse
+  !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE loop_transformation_construct
+!CHECK-UNPARSE-NEXT:  IMPLICIT NONE
+!CHECK-UNPARSE-NEXT:  INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT:  INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP DO
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT:  DO i=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT:  DO j=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT: !$OMP END DO
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct05.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
new file mode 100644
index 0000000000000..6b6d64d53ca23
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
@@ -0,0 +1,86 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse constructs a correct sequence
+! and can correctly combine with loop nests
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine loop_transformation_construct
+  implicit none
+  integer :: I = 10
+  integer :: j
+
+  !$omp do
+  !$omp fuse
+  do i = 1, I
+    continue
+  end do
+  !$omp tile
+    do j = 1, I
+      continue
+    end do
+  !$omp end fuse
+  !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | | OmpDirectiveName -> llvm::omp::Directive = tile
+!CHECK-PARSE-NEXT: | | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE loop_transformation_construct
+!CHECK-UNPARSE-NEXT:  IMPLICIT NONE
+!CHECK-UNPARSE-NEXT:  INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT:  INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP DO
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT:  DO i=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT:  !$OMP TILE
+!CHECK-UNPARSE-NEXT:  DO j=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT: !$OMP END DO
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index f718efc32aabf..927831a06d5fa 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -62,7 +62,7 @@ subroutine loop_transformation_construct4
   integer :: v(i)
 
   !$omp do
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+  !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
   !$omp tile
   !$omp unroll full
   do x = 1, i
@@ -77,7 +77,7 @@ subroutine loop_transformation_construct5
   integer :: v(i)
 
   !$omp do
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+  !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
   !$omp tile
   !$omp unroll
   do x = 1, i
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
new file mode 100644
index 0000000000000..74fbab89a8cf4
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -0,0 +1,95 @@
+! Testing the Semantics of loop sequences combined with 
+! nested Loop Transformation Constructs
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct1
+  implicit none
+
+  !$omp do
+  !ERROR: The FUSE construct requires the END FUSE directive
+  !$omp fuse 
+end subroutine
+
+subroutine loop_transformation_construct2
+  implicit none
+
+  !$omp do
+  !ERROR: A DO loop must follow the FUSE directive
+  !$omp fuse 
+  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+  !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct3
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  !$omp do
+  !$omp fuse
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !$omp end fuse
+  !$omp end do
+  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+  !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct4
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  !$omp do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !ERROR: A DO loop must follow the FUSE directive
+  !$omp fuse
+  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+  !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct5
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  !$omp do
+  !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
+  !$omp fuse
+  !$omp unroll full
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct6
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  !$omp do
+  !$omp fuse
+  !$omp unroll partial(2)
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !$omp end fuse 
+end subroutine
>From a898938eaf3fa1fcd1a13ee0fd58eded70aabccc Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 29 Sep 2025 14:35:44 +0000
Subject: [PATCH 2/6] looprange clause semantics
---
 flang/lib/Lower/OpenMP/Clauses.cpp            |  2 +-
 flang/lib/Semantics/check-omp-loop.cpp        | 29 ++++++++++++++
 flang/lib/Semantics/check-omp-structure.cpp   |  8 ++--
 flang/lib/Semantics/check-omp-structure.h     |  1 +
 flang/test/Parser/OpenMP/fuse-looprange.f90   | 38 +++++++++++++++++++
 .../OpenMP/loop-transformation-clauses01.f90  | 31 +++++++++++++++
 6 files changed, 105 insertions(+), 4 deletions(-)
 create mode 100644 flang/test/Parser/OpenMP/fuse-looprange.f90
 create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index d39f9dda92a28..12716c0b7de3c 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1054,7 +1054,7 @@ Link make(const parser::OmpClause::Link &inp,
 
 LoopRange make(const parser::OmpClause::Looprange &inp,
                semantics::SemanticsContext &semaCtx) {
-  llvm_unreachable("Unimplemented: looprange");
+  TODO_NOLOC("looprange clause");
 }
 
 Map make(const parser::OmpClause::Map &inp,
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 6ac55d836d2e7..6346a90b5f273 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -304,6 +304,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
       beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
     CheckDistLinear(x);
   }
+  if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
+    CheckLooprangeBounds(x);
+  }
 }
 
 const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -452,6 +455,32 @@ void OmpStructureChecker::CheckDistLinear(
   }
 }
 
+void OmpStructureChecker::CheckLooprangeBounds(
+    const parser::OpenMPLoopConstruct &x) {
+  const parser::OmpClauseList &clauseList = x.BeginDir().Clauses();
+  if (!clauseList.v.empty()) {
+    for (auto &clause : clauseList.v) {
+      if (const auto *lrClause{
+              std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+        if (const auto first{GetIntValue(std::get<0>((lrClause->v).t))}) {
+          if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
+            auto &loopConsList =
+                std::get<std::list<parser::NestedConstruct>>(x.t);
+            if (loopConsList.size() < (unsigned)(*first + *count - 1)) {
+              context_.Say(clause.source,
+                  "The loop range indicated in the %s clause"
+                  " must not be out of the bounds of the Loop Sequence"
+                  " following the construct."_err_en_US,
+                  parser::ToUpperCaseLetters(clause.source.ToString()));
+            }
+          }
+        }
+        return;
+      }
+    }
+  }
+}
+
 void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
   const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
 
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 41416304c1ea6..832f20aa22f72 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -3444,9 +3444,11 @@ CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Safelen, OMPC_safelen)
 CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Simdlen, OMPC_simdlen)
 
 void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
-  context_.Say(GetContext().clauseSource,
-      "LOOPRANGE clause is not implemented yet"_err_en_US,
-      ContextDirectiveAsFortran());
+  CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
+  auto &first = std::get<0>(x.v.t);
+  auto &count = std::get<1>(x.v.t);
+  RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
+  RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
 }
 
 // Restrictions specific to each clause are implemented apart from the
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 7426559e77ff7..14910833853f8 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -313,6 +313,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckAtomicWrite(const parser::OpenMPAtomicConstruct &x);
   void CheckAtomicUpdate(const parser::OpenMPAtomicConstruct &x);
 
+  void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/test/Parser/OpenMP/fuse-looprange.f90 b/flang/test/Parser/OpenMP/fuse-looprange.f90
new file mode 100644
index 0000000000000..75ec15fddd65f
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse-looprange.f90
@@ -0,0 +1,38 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=60 %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=60  %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_fuse(x)
+
+  integer, intent(inout)::x
+
+!CHECK: !$omp fuse looprange
+!$omp  fuse looprange(1,2)
+!CHECK: do
+  do x = 1, 100
+  	call F1()
+!CHECK: end do
+  end do
+!CHECK: do
+  do x = 1, 100
+  	call F1()
+!CHECK: end do
+  end do
+!CHECK: do
+  do x = 1, 100
+  	call F1()
+!CHECK: end do
+  end do
+!CHECK: !$omp end fuse
+!$omp end fuse
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = fuse
+!PARSE-TREE: OmpClauseList -> OmpClause -> Looprange -> OmpLoopRangeClause
+!PARSE-TREE: Scalar -> Integer -> Constant -> Expr = '1_4'
+!PARSE-TREE: LiteralConstant -> IntLiteralConstant = '1'
+!PARSE-TREE: Scalar -> Integer -> Constant -> Expr = '2_4'
+!PARSE-TREE: LiteralConstant -> IntLiteralConstant = '2'
+
+END subroutine openmp_fuse
+
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
new file mode 100644
index 0000000000000..07d4bb9886df5
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -0,0 +1,31 @@
+! Testing the Semantics of clauses on loop transformation directives
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+
+subroutine loop_transformation_construct1
+  implicit none
+  integer, parameter:: i = 5
+  integer :: x
+  integer :: v(i)
+
+  !$omp fuse looprange(1,2)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+
+  !ERROR: The loop range indicated in the LOOPRANGE(5,2) clause must not be out of the bounds of the Loop Sequence following the construct.
+  !$omp fuse looprange(5,2)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+end subroutine
+
>From 2691fc1c80fc8dc7da2893b8150a3d4ab1216c5b Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Tue, 7 Oct 2025 12:59:42 +0000
Subject: [PATCH 3/6] Address reviews, add more tests and fix an issue
---
 flang/lib/Lower/OpenMP/Clauses.cpp            |  2 +-
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  2 +-
 flang/lib/Semantics/canonicalize-omp.cpp      | 66 +++++++------
 flang/lib/Semantics/check-omp-loop.cpp        | 44 ++++++++-
 flang/lib/Semantics/check-omp-structure.h     |  1 +
 flang/lib/Semantics/resolve-directives.cpp    | 20 ++--
 flang/test/Parser/OpenMP/fail-looprange.f90   | 11 +++
 .../Parser/OpenMP/{fuse.f90 => fuse01.f90}    |  0
 flang/test/Parser/OpenMP/fuse02.f90           | 95 +++++++++++++++++++
 .../OpenMP/loop-transformation-clauses01.f90  | 24 ++++-
 .../loop-transformation-construct02.f90       |  4 +-
 .../loop-transformation-construct03.f90       | 39 ++++++++
 .../loop-transformation-construct04.f90       | 47 +++++++++
 13 files changed, 302 insertions(+), 53 deletions(-)
 create mode 100644 flang/test/Parser/OpenMP/fail-looprange.f90
 rename flang/test/Parser/OpenMP/{fuse.f90 => fuse01.f90} (100%)
 create mode 100644 flang/test/Parser/OpenMP/fuse02.f90
 create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
 create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index 12716c0b7de3c..d39f9dda92a28 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1054,7 +1054,7 @@ Link make(const parser::OmpClause::Link &inp,
 
 LoopRange make(const parser::OmpClause::Looprange &inp,
                semantics::SemanticsContext &semaCtx) {
-  TODO_NOLOC("looprange clause");
+  llvm_unreachable("Unimplemented: looprange");
 }
 
 Map make(const parser::OmpClause::Map &inp,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 618ad48e9e29f..bf3415631b461 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3470,7 +3470,7 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
     break;
   case llvm::omp::Directive::OMPD_tile:
     genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
-  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_fuse: {
     unsigned version = semaCtx.langOptions().OpenMPVersion;
     if (!semaCtx.langOptions().OpenMPSimd)
       TODO(loc, "Unhandled loop directive (" +
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index 74b3fa978a53c..fa2408efdb715 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -172,81 +172,85 @@ class CanonicalizationOfOmp {
             if (auto *endDir{
                     GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
               auto &endDirName = endDir->DirName();
-              if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
-                endFuseNeeded = false;
-              std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
-                  std::move(*endDir);
-              nextIt = block.erase(nextIt);
+              if (endDirName.v != llvm::omp::Directive::OMPD_fuse) {
+                std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+                    std::move(*endDir);
+                nextIt = block.erase(nextIt);
+              }
             }
           }
         } else {
           messages_.Say(beginName.source,
               "DO loop after the %s directive must have loop control"_err_en_US,
               parser::ToUpperCaseLetters(beginName.source.ToString()));
-          endFuseNeeded = false;
         }
       } else if (auto *ompLoopCons{
                      GetOmpIf<parser::OpenMPLoopConstruct>(*nextIt)}) {
-        // We should allow UNROLL and TILE constructs to be inserted between an
-        // OpenMP Loop Construct and the DO loop itself
+        // We should allow loop transformation constructs to be inserted between
+        // an OpenMP Loop Construct and the DO loop itself
         auto &nestedBeginDirective = ompLoopCons->BeginDir();
         auto &nestedBeginName = nestedBeginDirective.DirName();
         if (llvm::omp::loopTransformationSet.test(nestedBeginName.v)) {
           if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
               llvm::omp::loopTransformationSet.test(beginName.v)) {
-            // if a loop has been unrolled, the user can not then tile that loop
-            // as it has been unrolled
+            // if a loop has been unrolled, the user can not then transform that
+            // loop as it has been unrolled
             const parser::OmpClauseList &unrollClauseList{
               nestedBeginDirective.Clauses()};
             if (unrollClauseList.v.empty()) {
               // if the clause list is empty for an unroll construct, we assume
               // the loop is being fully unrolled
               transformUnrollError(beginName, messages_);
-              endFuseNeeded = false;
             } else {
               // parse the clauses for the unroll directive to find the full
               // clause
               for (auto &clause : unrollClauseList.v) {
                 if (clause.Id() == llvm::omp::OMPC_full) {
                   transformUnrollError(beginName, messages_);
-                  endFuseNeeded = false;
                 }
               }
             }
           }
-          // iterate through the remaining block items to find the end directive
-          // for the unroll/tile directive.
-          parser::Block::iterator endIt;
-          endIt = nextIt;
-          while (endIt != block.end()) {
+          RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
+          auto &loopConsList =
+              std::get<std::list<parser::NestedConstruct>>(x.t);
+          loopConsList.push_back(parser::NestedConstruct{
+              common::Indirection{std::move(*ompLoopCons)}});
+          nextIt = block.erase(nextIt);
+          // check the following block item to find the end directive
+          // for the loop transform directive.
+          if (nextIt != block.end()) {
             if (auto *endDir{
-                    GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
+                    GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
               auto &endDirName = endDir->DirName();
-              if (endDirName.v == beginName.v) {
-                if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
-                  endFuseNeeded = false;
+              if (endDirName.v == beginName.v &&
+                  endDirName.v != llvm::omp::Directive::OMPD_fuse) {
                 std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
                     std::move(*endDir);
-                endIt = block.erase(endIt);
-                continue;
+                nextIt = block.erase(nextIt);
               }
             }
-            ++endIt;
           }
-          RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
-          auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
-          loopConsList.push_back(parser::NestedConstruct{
-              common::Indirection{std::move(*ompLoopCons)}});
-          nextIt = block.erase(nextIt);
         } else {
           messages_.Say(nestedBeginName.source,
               "Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs"_err_en_US,
               parser::ToUpperCaseLetters(nestedBeginName.source.ToString()));
-          endFuseNeeded = false;
         }
       } else {
         missingDoConstruct(beginName, messages_);
-        endFuseNeeded = false;
+      }
+
+      if (endFuseNeeded && nextIt != block.end()) {
+        if (auto *endDir{
+                GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+          auto &endDirName = endDir->DirName();
+          if (endDirName.v == llvm::omp::Directive::OMPD_fuse) {
+            endFuseNeeded = false;
+            std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+                std::move(*endDir);
+            nextIt = block.erase(nextIt);
+          }
+        }
       }
       if (endFuseNeeded)
         continue;
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 6346a90b5f273..7ee31faed100a 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -306,7 +306,8 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
   }
   if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
     CheckLooprangeBounds(x);
-  }
+  } else
+    CheckNestedFuse(x);
 }
 
 const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -421,7 +422,7 @@ void OmpStructureChecker::CheckDistLinear(
     // clauses.
     auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
     for (auto &loopCons : loopConsList) {
-      std::int64_t collapseVal_ = collapseVal;
+      std::int64_t curCollapseVal{collapseVal};
       if (const auto &loopConstruct{
               std::get_if<parser::DoConstruct>(&loopCons)}) {
         for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
@@ -431,8 +432,8 @@ void OmpStructureChecker::CheckDistLinear(
               // Remove the symbol from the collected set
               indexVars.erase(&itrVal.symbol->GetUltimate());
             }
-            collapseVal_--;
-            if (collapseVal_ == 0) {
+            curCollapseVal--;
+            if (curCollapseVal == 0) {
               break;
             }
           }
@@ -466,7 +467,8 @@ void OmpStructureChecker::CheckLooprangeBounds(
           if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
             auto &loopConsList =
                 std::get<std::list<parser::NestedConstruct>>(x.t);
-            if (loopConsList.size() < (unsigned)(*first + *count - 1)) {
+            if (*first > 0 && *count > 0 &&
+                loopConsList.size() < (unsigned)(*first + *count - 1)) {
               context_.Say(clause.source,
                   "The loop range indicated in the %s clause"
                   " must not be out of the bounds of the Loop Sequence"
@@ -481,6 +483,38 @@ void OmpStructureChecker::CheckLooprangeBounds(
   }
 }
 
+void OmpStructureChecker::CheckNestedFuse(
+    const parser::OpenMPLoopConstruct &x) {
+  auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+  for (auto &loopCons : loopConsList) {
+    if (const auto &ompConstruct{
+            std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+                &loopCons)}) {
+      const parser::OmpClauseList &clauseList =
+          ompConstruct->value().BeginDir().Clauses();
+      if (!clauseList.v.empty()) {
+        for (auto &clause : clauseList.v) {
+          if (const auto *lrClause{
+                  std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+            if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
+              auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(
+                  ompConstruct->value().t);
+              if (loopConsList.size() > (unsigned)(*count)) {
+                context_.Say(x.BeginDir().DirName().source,
+                    "The loop sequence following the %s construct"
+                    " must be fully fused first."_err_en_US,
+                    parser::ToUpperCaseLetters(
+                        x.BeginDir().DirName().source.ToString()));
+              }
+            }
+            return;
+          }
+        }
+      }
+    }
+  }
+}
+
 void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
   const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
 
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 14910833853f8..69f255fb0bca5 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -314,6 +314,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckAtomicUpdate(const parser::OpenMPAtomicConstruct &x);
 
   void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
+  void CheckNestedFuse(const parser::OpenMPLoopConstruct &x);
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index ee781e62dc3d9..21af64ba90d34 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2019,12 +2019,12 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
 
   if (beginName.v == llvm::omp::Directive::OMPD_do) {
     auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
-    if (loopConsList.size() == 1) { //a OMPD_do cant be a loop sequence
-      if (const auto &doConstruct{
-              std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
-        if (doConstruct->IsDoWhile()) {
-          return true;
-        }
+    assert(loopConsList.size() == 1 &&
+        "Expected a single DoConstruct or OpenMPLoopConstruct");
+    if (const auto &doConstruct{
+            std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
+      if (doConstruct->IsDoWhile()) {
+        return true;
       }
     }
   }
@@ -2375,7 +2375,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
   auto &loopConsList =
       std::get<std::list<parser::NestedConstruct>>(innerMostLoop->t);
   for (auto &loopCons : loopConsList) {
-    std::int64_t level_{level};
+    std::int64_t curLevel{level};
     const parser::NestedConstruct *innerMostNest = nullptr;
     if (const auto &innerloop{std::get_if<parser::DoConstruct>(&loopCons)}) {
       innerMostNest = &loopCons;
@@ -2387,8 +2387,8 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
 
     if (innerMostNest) {
       if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
-        for (const parser::DoConstruct *loop{&*outer}; loop && level_ > 0;
-            --level_) {
+        for (const parser::DoConstruct *loop{&*outer}; loop && curLevel > 0;
+            --curLevel) {
           if (loop->IsDoConcurrent()) {
             // DO CONCURRENT is explicitly allowed for the LOOP construct so long
             // as there isn't a COLLAPSE clause
@@ -2419,7 +2419,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
             loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
           }
         }
-        CheckAssocLoopLevel(level_, GetAssociatedClause());
+        CheckAssocLoopLevel(curLevel, GetAssociatedClause());
       } else if (const auto *loop{std::get_if<
           common::Indirection<parser::OpenMPLoopConstruct>>(
               innerMostNest)}) {
diff --git a/flang/test/Parser/OpenMP/fail-looprange.f90 b/flang/test/Parser/OpenMP/fail-looprange.f90
new file mode 100644
index 0000000000000..ebe3480b44f12
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fail-looprange.f90
@@ -0,0 +1,11 @@
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp %s 2>&1 | FileCheck %s
+
+! CHECK: error: expected end of line
+!$omp fuse looprange
+
+! CHECK: error: expected end of line
+!$omp fuse looprange(1)
+
+! CHECK: error: expected end of line
+!$omp fuse looprange(1,2,3)
+end
diff --git a/flang/test/Parser/OpenMP/fuse.f90 b/flang/test/Parser/OpenMP/fuse01.f90
similarity index 100%
rename from flang/test/Parser/OpenMP/fuse.f90
rename to flang/test/Parser/OpenMP/fuse01.f90
diff --git a/flang/test/Parser/OpenMP/fuse02.f90 b/flang/test/Parser/OpenMP/fuse02.f90
new file mode 100644
index 0000000000000..7a46f4fe0f5cc
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse02.f90
@@ -0,0 +1,95 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse can be constructed on another Fuse
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine fuse_on_fuse
+  implicit none
+  integer :: I = 10
+  integer :: j
+
+  !$omp fuse
+    !$omp fuse
+      do i = 1, I
+        continue
+      end do
+      do j = 1, I
+        continue
+      end do
+    !$omp end fuse
+    do j = 1, I
+      continue
+    end do
+  !$omp end fuse
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | Block
+!CHECK-PARSE-NEXT: | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE fuse_on_fuse
+!CHECK-UNPARSE-NEXT:  IMPLICIT NONE
+!CHECK-UNPARSE-NEXT:  INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT:  INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT:  DO i=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT:  DO j=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT:  DO j=1_4,i
+!CHECK-UNPARSE-NEXT:    CONTINUE
+!CHECK-UNPARSE-NEXT:  END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 07d4bb9886df5..4e1b9c7f44ca5 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -9,7 +9,8 @@ subroutine loop_transformation_construct1
   integer :: x
   integer :: v(i)
 
-  !$omp fuse looprange(1,2)
+  !ERROR: At most one LOOPRANGE clause can appear on the FUSE directive
+  !$omp fuse looprange(1,2) looprange(1,2)
   do x = 1, i
     v(x) = x * 2
   end do
@@ -27,5 +28,24 @@ subroutine loop_transformation_construct1
     v(x) = x * 2
   end do
   !$omp end fuse
-end subroutine
 
+  !ERROR: The parameter of the LOOPRANGE clause must be a constant positive integer expression
+  !$omp fuse looprange(0,1)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+
+  !ERROR: The parameter of the LOOPRANGE clause must be a constant positive integer expression
+  !$omp fuse looprange(1,-1)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 74fbab89a8cf4..d82fc3668198d 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -17,7 +17,6 @@ subroutine loop_transformation_construct2
   !$omp do
   !ERROR: A DO loop must follow the FUSE directive
   !$omp fuse 
-  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
   !$omp end fuse
 end subroutine
 
@@ -53,7 +52,6 @@ subroutine loop_transformation_construct4
   end do
   !ERROR: A DO loop must follow the FUSE directive
   !$omp fuse
-  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
   !$omp end fuse
 end subroutine
 
@@ -83,7 +81,7 @@ subroutine loop_transformation_construct6
   integer :: v(i)
 
   !$omp do
-  !$omp fuse
+  !$omp fuse looprange(1,1)
   !$omp unroll partial(2)
   do x = 1, i
     v(x) = x(x) * 2
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
new file mode 100644
index 0000000000000..58e977241fd86
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -0,0 +1,39 @@
+! Testing the Semantic failure of forming loop sequences under regular OpenMP directives 
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct1
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  ! Only 1 do loop is associated with the OMP DO directive so the END DO directive is unmatched
+  !$omp do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !ERROR: The END DO directive must follow the DO loop associated with the loop construct
+  !$omp end do
+end subroutine
+
+subroutine loop_transformation_construct2
+  implicit none
+  integer :: i = 5
+  integer :: y
+  integer :: v(i)
+
+  ! Only 1 do loop is associated with the OMP TILE directive so the END TILE directive is unmatched
+  !$omp tile
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  do x = 1, i
+    v(x) = x(x) * 2
+  end do
+  !ERROR: The END TILE directive must follow the DO loop associated with the loop construct
+  !$omp end tile
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
new file mode 100644
index 0000000000000..589241ecfb6f8
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
@@ -0,0 +1,47 @@
+! Testing the Semantic failure of forming loop sequences under regular OpenMP directives 
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct3
+  implicit none
+  integer, parameter :: i = 5
+  integer :: x
+  integer :: v(i)
+
+  !ERROR: The loop sequence following the DO construct must be fully fused first.
+  !$omp do
+  !$omp fuse looprange(1,2)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+  !$omp end do
+end subroutine
+
+subroutine loop_transformation_construct4
+  implicit none
+  integer, parameter :: i = 5
+  integer :: x
+  integer :: v(i)
+
+  !ERROR: The loop sequence following the TILE construct must be fully fused first.
+  !$omp tile
+  !$omp fuse looprange(1,2)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+  !$omp end tile
+end subroutine
>From 29b3fd29aa83acce7445d5ab991478dc05d3d428 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 20 Oct 2025 14:51:26 +0000
Subject: [PATCH 4/6] looprange constant parameter test
---
 flang/lib/Lower/OpenMP/OpenMP.cpp                 |  1 +
 .../OpenMP/loop-transformation-clauses01.f90      | 15 +++++++++++++++
 2 files changed, 16 insertions(+)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bf3415631b461..3b1d03c6b301c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3476,6 +3476,7 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
       TODO(loc, "Unhandled loop directive (" +
                     llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
     break;
+                                        }
   case llvm::omp::Directive::OMPD_unroll:
     genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
     break;
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 4e1b9c7f44ca5..9ca0e8cfc9af1 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -7,6 +7,7 @@ subroutine loop_transformation_construct1
   implicit none
   integer, parameter:: i = 5
   integer :: x
+  integer :: a
   integer :: v(i)
 
   !ERROR: At most one LOOPRANGE clause can appear on the FUSE directive
@@ -48,4 +49,18 @@ subroutine loop_transformation_construct1
     v(x) = x * 2
   end do
   !$omp end fuse
+
+  !ERROR: Must be a constant value
+  !$omp fuse looprange(a,2)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
+
+  !ERROR: Must be a constant value
+  !$omp fuse looprange(1,a)
+  do x = 1, i
+    v(x) = x * 2
+  end do
+  !$omp end fuse
 end subroutine
>From ba1324eef5e09c049c63858b01aa761cd39ffa4f Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Thu, 23 Oct 2025 15:12:14 +0000
Subject: [PATCH 5/6] Resolve conflicts and format
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             |   3 +-
 flang/lib/Semantics/resolve-directives.cpp    | 129 +++++++++---------
 .../loop-transformation-construct05.f90       |   5 +-
 .../loop-transformation-construct03.f90       |   2 +-
 .../loop-transformation-construct04.f90       |   2 +-
 flang/test/Semantics/OpenMP/tile02.f90        |   2 +-
 6 files changed, 73 insertions(+), 70 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3b1d03c6b301c..acbdf3f4e84c9 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3470,13 +3470,14 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
     break;
   case llvm::omp::Directive::OMPD_tile:
     genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
+    break;
   case llvm::omp::Directive::OMPD_fuse: {
     unsigned version = semaCtx.langOptions().OpenMPVersion;
     if (!semaCtx.langOptions().OpenMPSimd)
       TODO(loc, "Unhandled loop directive (" +
                     llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
     break;
-                                        }
+  }
   case llvm::omp::Directive::OMPD_unroll:
     genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
     break;
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 21af64ba90d34..64429434d8e6c 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2259,85 +2259,86 @@ void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
   // Find the associated region by skipping nested loop-associated constructs
   // such as loop transformations
   const parser::NestedConstruct *innermostAssocRegion{nullptr};
-  const parser::OpenMPLoopConstruct *innermostConstruct{&x};
-  while (const auto &innerAssocStmt{
-      std::get<std::optional<parser::NestedConstruct>>(
-          innermostConstruct->t)}) {
-    innermostAssocRegion = &(innerAssocStmt.value());
+  const parser::OpenMPLoopConstruct *innermostConstruct = &x;
+  auto &loopConsList =
+      std::get<std::list<parser::NestedConstruct>>(innermostConstruct->t);
+  for (auto &loopCons : loopConsList) {
+    innermostAssocRegion = &loopCons;
     if (const auto *innerConstruct{
             std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
                 innermostAssocRegion)}) {
-      innermostConstruct = &innerConstruct->value();
+      CheckPerfectNestAndRectangularLoop(innerConstruct->value());
+      return;
     } else {
-      break;
-    }
-  }
 
-  if (!innermostAssocRegion)
-    return;
-  const auto &outer{std::get_if<parser::DoConstruct>(innermostAssocRegion)};
-  if (!outer)
-    return;
+      if (!innermostAssocRegion)
+        continue;
+      const auto &outer{std::get_if<parser::DoConstruct>(innermostAssocRegion)};
+      if (!outer)
+        continue;
 
-  llvm::SmallVector<Symbol *> ivs;
-  int curLevel{0};
-  const parser::DoConstruct *loop{outer};
-  while (true) {
-    auto [iv, lb, ub, step] = GetLoopBounds(*loop);
-
-    if (lb)
-      checkExprHasSymbols(ivs, lb);
-    if (ub)
-      checkExprHasSymbols(ivs, ub);
-    if (step)
-      checkExprHasSymbols(ivs, step);
-    if (iv) {
-      if (auto *symbol{currScope().FindSymbol(iv->source)})
-        ivs.push_back(symbol);
-    }
+      llvm::SmallVector<Symbol *> ivs;
+      int curLevel{0};
+      const parser::DoConstruct *loop{outer};
+      while (true) {
+        auto [iv, lb, ub, step] = GetLoopBounds(*loop);
+
+        if (lb)
+          checkExprHasSymbols(ivs, lb);
+        if (ub)
+          checkExprHasSymbols(ivs, ub);
+        if (step)
+          checkExprHasSymbols(ivs, step);
+        if (iv) {
+          if (auto *symbol{currScope().FindSymbol(iv->source)})
+            ivs.push_back(symbol);
+        }
 
-    // Stop after processing all affected loops
-    if (curLevel + 1 >= dirDepth)
-      break;
+        // Stop after processing all affected loops
+        if (curLevel + 1 >= dirDepth)
+          break;
 
-    // Recurse into nested loop
-    const auto &block{std::get<parser::Block>(loop->t)};
-    if (block.empty()) {
-      // Insufficient number of nested loops already reported by
-      // CheckAssocLoopLevel()
-      break;
-    }
+        // Recurse into nested loop
+        const auto &block{std::get<parser::Block>(loop->t)};
+        if (block.empty()) {
+          // Insufficient number of nested loops already reported by
+          // CheckAssocLoopLevel()
+          break;
+        }
 
-    loop = GetDoConstructIf(block.front());
-    if (!loop) {
-      // Insufficient number of nested loops already reported by
-      // CheckAssocLoopLevel()
-      break;
-    }
+        loop = GetDoConstructIf(block.front());
+        if (!loop) {
+          // Insufficient number of nested loops already reported by
+          // CheckAssocLoopLevel()
+          break;
+        }
 
-    auto checkPerfectNest = [&, this]() {
-      if (block.empty())
-        return;
-      auto last = block.end();
-      --last;
+        auto checkPerfectNest = [&, this]() {
+          if (block.empty())
+            return;
+          auto last = block.end();
+          --last;
 
-      // A trailing CONTINUE is not considered part of the loop body
-      if (parser::Unwrap<parser::ContinueStmt>(*last))
-        --last;
+          // A trailing CONTINUE is not considered part of the loop body
+          if (parser::Unwrap<parser::ContinueStmt>(*last))
+            --last;
 
-      // In a perfectly nested loop, the nested loop must be the only statement
-      if (last == block.begin())
-        return;
+          // In a perfectly nested loop, the nested loop must be the only
+          // statement
+          if (last == block.begin())
+            return;
 
-      // Non-perfectly nested loop
-      // TODO: Point to non-DO statement, directiveSource as a note
-      context_.Say(dirContext.directiveSource,
-          "Canonical loop nest must be perfectly nested."_err_en_US);
-    };
+          // Non-perfectly nested loop
+          // TODO: Point to non-DO statement, directiveSource as a note
+          context_.Say(dirContext.directiveSource,
+              "Canonical loop nest must be perfectly nested."_err_en_US);
+        };
 
-    checkPerfectNest();
+        checkPerfectNest();
 
-    ++curLevel;
+        ++curLevel;
+      }
+    }
   }
 }
 
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct05.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
index 6b6d64d53ca23..b1e1f91766640 100644
--- a/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
@@ -14,7 +14,7 @@ subroutine loop_transformation_construct
   do i = 1, I
     continue
   end do
-  !$omp tile
+  !$omp tile sizes(2)
     do j = 1, I
       continue
     end do
@@ -47,7 +47,8 @@ subroutine loop_transformation_construct
 !CHECK-PARSE-NEXT: | | | | OpenMPLoopConstruct
 !CHECK-PARSE-NEXT: | | | | | OmpBeginLoopDirective
 !CHECK-PARSE-NEXT: | | | | | | OmpDirectiveName -> llvm::omp::Directive = tile
-!CHECK-PARSE-NEXT: | | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | | OmpClauseList -> OmpClause -> Sizes -> Scalar -> Integer -> Expr = '2_4'
+!CHECK-PARSE-NEXT: | | | | | | | LiteralConstant -> IntLiteralConstant = '2'
 !CHECK-PARSE-NEXT: | | | | | | Flags = None
 !CHECK-PARSE-NEXT: | | | | | DoConstruct
 !CHECK-PARSE-NEXT: | | | | | | NonLabelDoStmt
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
index 58e977241fd86..5e459c7985523 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -27,7 +27,7 @@ subroutine loop_transformation_construct2
   integer :: v(i)
 
   ! Only 1 do loop is associated with the OMP TILE directive so the END TILE directive is unmatched
-  !$omp tile
+  !$omp tile sizes(2)
   do x = 1, i
     v(x) = x(x) * 2
   end do
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
index 589241ecfb6f8..2856247329f3b 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
@@ -31,7 +31,7 @@ subroutine loop_transformation_construct4
   integer :: v(i)
 
   !ERROR: The loop sequence following the TILE construct must be fully fused first.
-  !$omp tile
+  !$omp tile sizes(2)
   !$omp fuse looprange(1,2)
   do x = 1, i
     v(x) = x * 2
diff --git a/flang/test/Semantics/OpenMP/tile02.f90 b/flang/test/Semantics/OpenMP/tile02.f90
index 676796375353f..096a0f349932e 100644
--- a/flang/test/Semantics/OpenMP/tile02.f90
+++ b/flang/test/Semantics/OpenMP/tile02.f90
@@ -6,7 +6,7 @@ subroutine on_unroll
   implicit none
   integer i
 
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+  !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
   !$omp tile sizes(2)
   !$omp unroll
   do i = 1, 5
>From d17a256cde8c57aed5d821df053b9ca51f6b9543 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Tue, 28 Oct 2025 16:31:54 +0000
Subject: [PATCH 6/6] format
---
 flang/lib/Semantics/canonicalize-omp.cpp   |  6 ++---
 flang/lib/Semantics/resolve-directives.cpp | 29 +++++++++++-----------
 2 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index fa2408efdb715..3142813b75dfc 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -9,8 +9,8 @@
 #include "canonicalize-omp.h"
 #include "flang/Parser/parse-tree-visitor.h"
 #include "flang/Parser/parse-tree.h"
-#include "flang/Semantics/semantics.h"
 #include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/semantics.h"
 
 // After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
 // Constructs more structured which provide explicit scopes for later
@@ -139,7 +139,7 @@ class CanonicalizationOfOmp {
           parser::ToUpperCaseLetters(dirName.source.ToString()));
     };
     auto transformUnrollError = [](const parser::OmpDirectiveName &dirName,
-                               parser::Messages &messages) {
+                                    parser::Messages &messages) {
       messages.Say(dirName.source,
           "If a loop construct has been fully unrolled, it cannot then be further transformed"_err_en_US,
           parser::ToUpperCaseLetters(dirName.source.ToString()));
@@ -196,7 +196,7 @@ class CanonicalizationOfOmp {
             // if a loop has been unrolled, the user can not then transform that
             // loop as it has been unrolled
             const parser::OmpClauseList &unrollClauseList{
-              nestedBeginDirective.Clauses()};
+                nestedBeginDirective.Clauses()};
             if (unrollClauseList.v.empty()) {
               // if the clause list is empty for an unroll construct, we assume
               // the loop is being fully unrolled
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 64429434d8e6c..78261d98763de 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2187,8 +2187,7 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct(
       "Expected a DoConstruct or OpenMPLoopConstruct");
   for (auto &nest : nestedList) {
     const auto *innerConstruct =
-        std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-            &nest);
+        std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(&nest);
 
     if (innerConstruct) {
       CollectNumAffectedLoopsFromLoopConstruct(
@@ -2380,9 +2379,9 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
     const parser::NestedConstruct *innerMostNest = nullptr;
     if (const auto &innerloop{std::get_if<parser::DoConstruct>(&loopCons)}) {
       innerMostNest = &loopCons;
-    } else if (const auto *innerLoop{
-        std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
-            &loopCons)}) {
+    } else if (const auto *innerLoop{std::get_if<
+                   common::Indirection<parser::OpenMPLoopConstruct>>(
+                   &loopCons)}) {
       PrivatizeAssociatedLoopIndexAndCheckLoopLevel(innerLoop->value());
     }
 
@@ -2391,8 +2390,8 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
         for (const parser::DoConstruct *loop{&*outer}; loop && curLevel > 0;
             --curLevel) {
           if (loop->IsDoConcurrent()) {
-            // DO CONCURRENT is explicitly allowed for the LOOP construct so long
-            // as there isn't a COLLAPSE clause
+            // DO CONCURRENT is explicitly allowed for the LOOP construct so
+            // long as there isn't a COLLAPSE clause
             if (isLoopConstruct) {
               if (hasCollapseClause) {
                 // hasCollapseClause implies clause != nullptr
@@ -2401,7 +2400,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
               }
             } else {
               auto &stmt =
-                std::get<parser::Statement<parser::NonLabelDoStmt>>(loop->t);
+                  std::get<parser::Statement<parser::NonLabelDoStmt>>(loop->t);
               context_.Say(stmt.source,
                   "DO CONCURRENT loops cannot form part of a loop nest."_err_en_US);
             }
@@ -2422,26 +2421,26 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
         }
         CheckAssocLoopLevel(curLevel, GetAssociatedClause());
       } else if (const auto *loop{std::get_if<
-          common::Indirection<parser::OpenMPLoopConstruct>>(
-              innerMostNest)}) {
+                     common::Indirection<parser::OpenMPLoopConstruct>>(
+                     innerMostNest)}) {
         const parser::OmpDirectiveSpecification &beginSpec{
-          loop->value().BeginDir()};
+            loop->value().BeginDir()};
         const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
         if (!llvm::omp::loopTransformationSet.test(beginName.v)) {
           context_.Say(GetContext().directiveSource,
               "Only Loop Transformation Constructs are allowed between an OpenMP Loop Construct and a DO construct"_err_en_US,
               parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
                   GetContext().directive, version)
-                .str()));
+                      .str()));
         } else {
           PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
         }
       } else {
         context_.Say(GetContext().directiveSource,
             "A DO loop must follow the %s directive"_err_en_US,
-            parser::ToUpperCaseLetters(
-              llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
-              .str()));
+            parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
+                GetContext().directive, version)
+                    .str()));
       }
     }
   }
    
    
More information about the flang-commits
mailing list