[flang-commits] [flang] [flang][OpenMP] Better diagnostics for invalid or misplaced directives (PR #168885)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Sat Nov 22 10:54:58 PST 2025


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

>From 1e13a5c75fb694d94c057fe267a3b62dd8545d3e Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 17 Nov 2025 09:36:28 -0600
Subject: [PATCH 01/11] [flang][OpenMP] Fix some typo-like things in test case

---
 .../loop-transformation-construct01.f90       | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index f718efc32aabf..f4628c9533db8 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -13,13 +13,13 @@ subroutine loop_transformation_construct1
 subroutine loop_transformation_construct2
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
   !$omp tile
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
   !$omp end tile
   !$omp end do
@@ -30,26 +30,26 @@ subroutine loop_transformation_construct2
 subroutine loop_transformation_construct2
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
   !ERROR: Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs
   !$omp parallel do
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
 end subroutine
 
 subroutine loop_transformation_construct3
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
   !ERROR: A DO loop must follow the TILE directive
   !$omp tile
@@ -58,7 +58,7 @@ subroutine loop_transformation_construct3
 subroutine loop_transformation_construct4
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
@@ -66,14 +66,14 @@ subroutine loop_transformation_construct4
   !$omp tile
   !$omp unroll full
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
 end subroutine
 
 subroutine loop_transformation_construct5
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
@@ -81,20 +81,20 @@ subroutine loop_transformation_construct5
   !$omp tile
   !$omp unroll
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
 end subroutine
 
 subroutine loop_transformation_construct6
   implicit none
   integer :: i = 5
-  integer :: y
+  integer :: x
   integer :: v(i)
 
   !$omp do
   !$omp tile
   !$omp unroll partial(2)
   do x = 1, i
-    v(x) = x(x) * 2
+    v(x) = v(x) * 2
   end do
 end subroutine

>From aaea8e6b47808333581569d40e3721050be52bf1 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 14 Nov 2025 15:52:11 -0600
Subject: [PATCH 02/11] [flang][OpenMP] Implement loop nest parser

Previously, loop constructs were parsed in a piece-wise manner: the
begin directive, the body, and the end directive were parsed separately.
Later on in canonicalization they were all coalesced into a loop
construct. To facilitate that end-loop directives were given a special
treatment, namely they were parsed as OpenMP constructs. As a result
syntax errors caused by misplaced end-loop directives were handled
differently from those cause by misplaced non-loop end directives.

The new loop nest parser constructs the complete loop construct,
removing the need for the canonicalization step. Additionally, it is
the basis for parsing loop-sequence-associated constructs in the future.

It also removes the need for the special treatment of end-loop
directives. While this patch temporarily degrades the error messaging
for misplaced end-loop directives, it enables uniform handling of any
misplaced end-directives in the future.
---
 flang/include/flang/Parser/parse-tree.h       |   3 +-
 flang/lib/Parser/executable-parsers.cpp       |   1 -
 flang/lib/Parser/openmp-parsers.cpp           | 143 ++++++++++++++-
 flang/lib/Parser/parse-tree.cpp               |  35 +++-
 flang/lib/Semantics/canonicalize-omp.cpp      | 163 ------------------
 flang/lib/Semantics/check-omp-loop.cpp        |  74 ++++++++
 flang/lib/Semantics/check-omp-structure.h     |   4 +
 flang/lib/Semantics/resolve-directives.cpp    |   7 -
 ...nested-loop-transformation-construct02.f90 |   2 +-
 .../loop-transformation-construct01.f90       |   7 +-
 .../loop-transformation-construct02.f90       |   7 +-
 flang/test/Parser/OpenMP/tile-fail.f90        |   5 +-
 flang/test/Semantics/OpenMP/do21.f90          |  10 +-
 .../Semantics/OpenMP/loop-association.f90     |  33 ++--
 .../loop-transformation-construct01.f90       |  40 +++--
 flang/test/Semantics/OpenMP/tile02.f90        |   2 +-
 16 files changed, 293 insertions(+), 243 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 60d2ad0b764b9..9795a0d2ae25e 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -271,7 +271,6 @@ struct OpenACCRoutineConstruct;
 struct OpenMPConstruct;
 struct OpenMPLoopConstruct;
 struct OpenMPDeclarativeConstruct;
-struct OmpEndLoopDirective;
 struct CUFKernelDoConstruct;
 
 // Cooked character stream locations
@@ -539,7 +538,6 @@ struct ExecutableConstruct {
       common::Indirection<OpenACCConstruct>,
       common::Indirection<AccEndCombinedDirective>,
       common::Indirection<OpenMPConstruct>,
-      common::Indirection<OmpEndLoopDirective>,
       common::Indirection<CUFKernelDoConstruct>>
       u;
 };
@@ -5359,6 +5357,7 @@ struct OpenMPLoopConstruct {
   const DoConstruct *GetNestedLoop() const;
   const OpenMPLoopConstruct *GetNestedConstruct() const;
 
+  CharBlock source;
   std::tuple<OmpBeginLoopDirective, Block, std::optional<OmpEndLoopDirective>>
       t;
 };
diff --git a/flang/lib/Parser/executable-parsers.cpp b/flang/lib/Parser/executable-parsers.cpp
index fadec1f11d1db..8d777a6671495 100644
--- a/flang/lib/Parser/executable-parsers.cpp
+++ b/flang/lib/Parser/executable-parsers.cpp
@@ -49,7 +49,6 @@ constexpr auto executableConstruct{first(
     construct<ExecutableConstruct>(indirect(Parser<SelectTypeConstruct>{})),
     construct<ExecutableConstruct>(indirect(whereConstruct)),
     construct<ExecutableConstruct>(indirect(forallConstruct)),
-    construct<ExecutableConstruct>(indirect(ompEndLoopDirective)),
     construct<ExecutableConstruct>(indirect(openmpConstruct)),
     construct<ExecutableConstruct>(indirect(Parser<OpenACCConstruct>{})),
     construct<ExecutableConstruct>(indirect(compilerDirective)),
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index e2da60ed19de8..d50f45794230b 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -17,6 +17,7 @@
 #include "type-parser-implementation.h"
 #include "flang/Parser/openmp-utils.h"
 #include "flang/Parser/parse-tree.h"
+#include "flang/Parser/tools.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/Bitset.h"
 #include "llvm/ADT/STLExtras.h"
@@ -30,6 +31,7 @@
 #include <iterator>
 #include <list>
 #include <optional>
+#include <set>
 #include <string>
 #include <tuple>
 #include <type_traits>
@@ -1656,6 +1658,100 @@ struct LooselyStructuredBlockParser {
   }
 };
 
+struct NonBlockDoConstructParser {
+  using resultType = Block;
+
+  std::optional<resultType> Parse(ParseState &state) const {
+    std::set<Label> labels;
+    Block body;
+
+    // Parse nests like
+    // do 20 i = 1, n     LabelDoStmt.t<Label> = 20
+    //   do 10 j = 1, m
+    //     ...
+    //   10 continue      Statement<...>.label = 10
+    // 20 continue
+
+    // Keep parsing ExecutionPartConstructs until the set of open label-do
+    // statements becomes empty, or until the EPC parser fails.
+    while (auto &&epc{attempt(executionPartConstruct).Parse(state)}) {
+      if (auto &&label{GetStatementLabel(*epc)}) {
+        labels.erase(*label);
+      }
+      if (auto *labelDo{Unwrap<LabelDoStmt>(*epc)}) {
+        labels.insert(std::get<Label>(labelDo->t));
+      }
+      body.push_back(std::move(*epc));
+      if (labels.empty()) {
+        break;
+      }
+    }
+
+    if (!body.empty()) {
+      return std::move(body);
+    }
+    return std::nullopt;
+  }
+
+private:
+  // Is the template argument "Statement<T>" for some T?
+  template <typename T> struct IsStatement {
+    static constexpr bool value{false};
+  };
+  template <typename T> struct IsStatement<Statement<T>> {
+    static constexpr bool value{true};
+  };
+
+  // Get the Label from a Statement<...> contained in an ExecutionPartConstruct,
+  // or std::nullopt, if there is no Statement<...> contained in there.
+  template <typename T>
+  static std::optional<Label> GetStatementLabel(const T &stmt) {
+    if constexpr (IsStatement<T>::value) {
+      return stmt.label;
+    } else if constexpr (WrapperTrait<T>) {
+      return GetStatementLabel(stmt.v);
+    } else if constexpr (UnionTrait<T>) {
+      return common::visit(
+          [&](auto &&s) { return GetStatementLabel(s); }, stmt.u);
+    }
+    return std::nullopt;
+  }
+};
+
+struct LoopNestParser {
+  using resultType = Block;
+
+  std::optional<resultType> Parse(ParseState &state) const {
+    // Parse !$DIR as an ExecutionPartConstruct
+    auto fortranDirective{predicated(executionPartConstruct,
+        [](auto &epc) { return Unwrap<CompilerDirective>(epc); })};
+    // Parse DO loop as an ExecutionPartConstruct
+    auto fortranDoConstruct{predicated(executionPartConstruct,
+        [&](auto &epc) { return Unwrap<DoConstruct>(epc); })};
+    ParseState backtrack{state};
+
+    Block body;
+    llvm::move(*many(fortranDirective).Parse(state), std::back_inserter(body));
+
+    if (auto &&doLoop{attempt(fortranDoConstruct).Parse(state)}) {
+      body.push_back(std::move(*doLoop));
+      return std::move(body);
+    }
+    if (auto &&labelDo{attempt(NonBlockDoConstructParser{}).Parse(state)}) {
+      llvm::move(*labelDo, std::back_inserter(body));
+      return std::move(body);
+    }
+    if (auto &&sblock{attempt(StrictlyStructuredBlockParser{}).Parse(state)}) {
+      llvm::move(*sblock, std::back_inserter(body));
+      return std::move(body);
+    }
+    // If it's neither a DO-loop, nor a BLOCK, undo the parsing of the
+    // directives and fail.
+    state = backtrack;
+    return std::nullopt;
+  }
+};
+
 TYPE_PARSER(construct<OmpErrorDirective>(
     predicated(Parser<OmpDirectiveName>{},
         IsDirective(llvm::omp::Directive::OMPD_error)) >=
@@ -1783,6 +1879,43 @@ struct OmpBlockConstructParser {
   llvm::omp::Directive dir_;
 };
 
+struct OmpLoopConstructParser {
+  using resultType = OpenMPLoopConstruct;
+
+  constexpr OmpLoopConstructParser(DirectiveSet dirs) : dirs_(dirs) {}
+
+  std::optional<resultType> Parse(ParseState &state) const {
+    if (auto &&begin{OmpBeginDirectiveParser(dirs_).Parse(state)}) {
+      if (auto &&nest{attempt(LoopNestParser{}).Parse(state)}) {
+        auto end{maybe(OmpEndDirectiveParser{dirs_}).Parse(state)};
+        return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
+            std::move(*nest),
+            llvm::transformOptional(std::move(*end),
+                [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
+      } else {
+        // Parse a nested OpenMPLoopConstruct as the body.
+        auto ompLoopConstruct{predicated(executionPartConstruct,
+            [](auto &epc) { return Unwrap<OpenMPLoopConstruct>(epc); })};
+
+        // Allow empty body.
+        Block body;
+        if (auto &&omp{attempt(ompLoopConstruct).Parse(state)}) {
+          body.push_back(std::move(*omp));
+        }
+        auto end{maybe(OmpEndDirectiveParser{dirs_}).Parse(state)};
+        return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
+            std::move(body),
+            llvm::transformOptional(std::move(*end),
+                [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
+      }
+    }
+    return std::nullopt;
+  }
+
+private:
+  DirectiveSet dirs_;
+};
+
 struct OmpDeclarativeAllocateParser {
   using resultType = OmpAllocateDirective;
 
@@ -2266,13 +2399,7 @@ static constexpr DirectiveSet GetLoopDirectives() {
   return loopDirectives;
 }
 
-TYPE_PARSER(sourced(construct<OmpBeginLoopDirective>(
-    sourced(OmpBeginDirectiveParser(GetLoopDirectives())))))
-
-// END OMP Loop directives
-TYPE_PARSER(sourced(construct<OmpEndLoopDirective>(
-    sourced(OmpEndDirectiveParser(GetLoopDirectives())))))
+TYPE_PARSER(sourced(construct<OpenMPLoopConstruct>(
+    OmpLoopConstructParser(GetLoopDirectives()))))
 
-TYPE_PARSER(construct<OpenMPLoopConstruct>(
-    Parser<OmpBeginLoopDirective>{} / endOmpLine))
 } // namespace Fortran::parser
diff --git a/flang/lib/Parser/parse-tree.cpp b/flang/lib/Parser/parse-tree.cpp
index 60e51895cdcea..53d4e4e680caa 100644
--- a/flang/lib/Parser/parse-tree.cpp
+++ b/flang/lib/Parser/parse-tree.cpp
@@ -435,17 +435,36 @@ const OmpClauseList &OmpDirectiveSpecification::Clauses() const {
 }
 
 const DoConstruct *OpenMPLoopConstruct::GetNestedLoop() const {
-  if (auto &body{std::get<Block>(t)}; !body.empty()) {
-    return Unwrap<DoConstruct>(body.front());
-  }
-  return nullptr;
+  auto getFromBlock{[](const Block &body, auto self) -> const DoConstruct * {
+    for (auto &stmt : body) {
+      if (auto *block{Unwrap<BlockConstruct>(&stmt)}) {
+        return self(std::get<Block>(block->t), self);
+      }
+      if (auto *loop{Unwrap<DoConstruct>(&stmt)}) {
+        return loop;
+      }
+    }
+    return nullptr;
+  }};
+
+  return getFromBlock(std::get<Block>(t), getFromBlock);
 }
 
 const OpenMPLoopConstruct *OpenMPLoopConstruct::GetNestedConstruct() const {
-  if (auto &body{std::get<Block>(t)}; !body.empty()) {
-    return Unwrap<OpenMPLoopConstruct>(body.front());
-  }
-  return nullptr;
+  auto getFromBlock{
+      [](const Block &body, auto self) -> const OpenMPLoopConstruct * {
+        for (auto &stmt : body) {
+          if (auto *block{Unwrap<BlockConstruct>(&stmt)}) {
+            return self(std::get<Block>(block->t), self);
+          }
+          if (auto *omp{Unwrap<OpenMPLoopConstruct>(&stmt)}) {
+            return omp;
+          }
+        }
+        return nullptr;
+      }};
+
+  return getFromBlock(std::get<Block>(t), getFromBlock);
 }
 
 static bool InitCharBlocksFromStrings(llvm::MutableArrayRef<CharBlock> blocks,
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index 0cec1969e0978..8a45cc3a88f45 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -31,26 +31,6 @@ class CanonicalizationOfOmp {
   CanonicalizationOfOmp(SemanticsContext &context)
       : context_{context}, messages_{context.messages()} {}
 
-  void Post(parser::Block &block) {
-    for (auto it{block.begin()}; it != block.end(); ++it) {
-      if (auto *ompCons{GetConstructIf<parser::OpenMPConstruct>(*it)}) {
-        // OpenMPLoopConstruct
-        if (auto *ompLoop{
-                std::get_if<parser::OpenMPLoopConstruct>(&ompCons->u)}) {
-          RewriteOpenMPLoopConstruct(*ompLoop, block, it);
-        }
-      } else if (auto *endDir{
-                     GetConstructIf<parser::OmpEndLoopDirective>(*it)}) {
-        // Unmatched OmpEndLoopDirective
-        const parser::OmpDirectiveName &endName{endDir->DirName()};
-        messages_.Say(endName.source,
-            "The %s directive must follow the DO loop associated with the "
-            "loop construct"_err_en_US,
-            parser::ToUpperCaseLetters(endName.source.ToString()));
-      }
-    } // Block list
-  }
-
   // Pre-visit all constructs that have both a specification part and
   // an execution part, and store the connection between the two.
   bool Pre(parser::BlockConstruct &x) {
@@ -92,149 +72,6 @@ class CanonicalizationOfOmp {
   void Post(parser::OmpMapClause &map) { CanonicalizeMapModifiers(map); }
 
 private:
-  template <typename T> T *GetConstructIf(parser::ExecutionPartConstruct &x) {
-    if (auto *y{std::get_if<parser::ExecutableConstruct>(&x.u)}) {
-      if (auto *z{std::get_if<common::Indirection<T>>(&y->u)}) {
-        return &z->value();
-      }
-    }
-    return nullptr;
-  }
-
-  template <typename T> T *GetOmpIf(parser::ExecutionPartConstruct &x) {
-    if (auto *construct{GetConstructIf<parser::OpenMPConstruct>(x)}) {
-      if (auto *omp{std::get_if<T>(&construct->u)}) {
-        return omp;
-      }
-    }
-    return nullptr;
-  }
-
-  void RewriteOpenMPLoopConstruct(parser::OpenMPLoopConstruct &x,
-      parser::Block &block, parser::Block::iterator it) {
-    // Check the sequence of DoConstruct and OmpEndLoopDirective
-    // in the same iteration
-    //
-    // Original:
-    //   ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
-    //     OmpBeginLoopDirective
-    //   ExecutableConstruct -> DoConstruct
-    //   ExecutableConstruct -> OmpEndLoopDirective (if available)
-    //
-    // After rewriting:
-    //   ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
-    //     OmpBeginLoopDirective
-    //     DoConstruct
-    //     OmpEndLoopDirective (if available)
-    parser::Block::iterator nextIt;
-    const parser::OmpDirectiveSpecification &beginDir{x.BeginDir()};
-    const parser::OmpDirectiveName &beginName{beginDir.DirName()};
-
-    auto missingDoConstruct = [](const parser::OmpDirectiveName &dirName,
-                                  parser::Messages &messages) {
-      messages.Say(dirName.source,
-          "A DO loop must follow the %s directive"_err_en_US,
-          parser::ToUpperCaseLetters(dirName.source.ToString()));
-    };
-    auto tileUnrollError = [](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,
-          parser::ToUpperCaseLetters(dirName.source.ToString()));
-    };
-
-    auto &body{std::get<parser::Block>(x.t)};
-
-    nextIt = it;
-    while (++nextIt != block.end()) {
-      // Ignore compiler directives.
-      if (GetConstructIf<parser::CompilerDirective>(*nextIt))
-        continue;
-
-      if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
-        if (doCons->GetLoopControl()) {
-          // move DoConstruct
-          body.push_back(std::move(*nextIt));
-          nextIt = block.erase(nextIt);
-          // try to match OmpEndLoopDirective
-          if (nextIt != block.end()) {
-            if (auto *endDir{
-                    GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
-              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()));
-        }
-      } 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
-        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)) {
-          // 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()) {
-            if (auto *endDir{
-                    GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
-              auto &endDirName = endDir->DirName();
-              if (endDirName.v == beginName.v) {
-                std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
-                    std::move(*endDir);
-                endIt = block.erase(endIt);
-                continue;
-              }
-            }
-            ++endIt;
-          }
-          RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
-          body.push_back(std::move(*nextIt));
-          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()));
-        }
-      } else {
-        missingDoConstruct(beginName, messages_);
-      }
-      // If we get here, we either found a loop, or issued an error message.
-      return;
-    }
-    if (nextIt == block.end()) {
-      missingDoConstruct(beginName, messages_);
-    }
-  }
-
   // Canonicalization of allocate directives
   //
   // In OpenMP 5.0 and 5.1 the allocate directive could either be a declarative
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 3d3596b500880..9798420eb8086 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -245,6 +245,78 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) {
   }
 }
 
+static bool IsLoopTransforming(llvm::omp::Directive dir) {
+  switch (dir) {
+  // TODO case llvm::omp::Directive::OMPD_flatten:
+  case llvm::omp::Directive::OMPD_fuse:
+  case llvm::omp::Directive::OMPD_interchange:
+  case llvm::omp::Directive::OMPD_nothing:
+  case llvm::omp::Directive::OMPD_reverse:
+  // TODO case llvm::omp::Directive::OMPD_split:
+  case llvm::omp::Directive::OMPD_stripe:
+  case llvm::omp::Directive::OMPD_tile:
+  case llvm::omp::Directive::OMPD_unroll:
+    return true;
+  default:
+    return false;
+  }
+}
+
+void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
+    const parser::Block &body, size_t &nestedCount) {
+  for (auto &stmt : body) {
+    if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
+      context_.Say(dir->source,
+          "Compiler directives are not allowed inside OpenMP loop constructs"_err_en_US);
+    } else if (parser::Unwrap<parser::DoConstruct>(stmt)) {
+      ++nestedCount;
+    } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(stmt)}) {
+      if (!IsLoopTransforming(omp->BeginDir().DirName().v)) {
+        context_.Say(omp->source,
+            "Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs"_err_en_US);
+      }
+      ++nestedCount;
+    } else if (auto *block{parser::Unwrap<parser::BlockConstruct>(stmt)}) {
+      CheckNestedBlock(x, std::get<parser::Block>(block->t), nestedCount);
+    } else {
+      parser::CharBlock source{parser::GetSource(stmt).value_or(x.source)};
+      context_.Say(source,
+          "OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs"_err_en_US);
+    }
+  }
+}
+
+void OmpStructureChecker::CheckNestedConstruct(
+    const parser::OpenMPLoopConstruct &x) {
+  size_t nestedCount{0};
+
+  auto &body{std::get<parser::Block>(x.t)};
+  if (body.empty()) {
+    context_.Say(x.source,
+        "OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
+  } else {
+    CheckNestedBlock(x, body, nestedCount);
+  }
+}
+
+void OmpStructureChecker::CheckFullUnroll(
+    const parser::OpenMPLoopConstruct &x) {
+  // If the nested construct is a full unroll, then this construct is invalid
+  // since it won't contain a loop.
+  if (const parser::OpenMPLoopConstruct *nested{x.GetNestedConstruct()}) {
+    auto &nestedSpec{nested->BeginDir()};
+    if (nestedSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
+      bool isPartial{
+          llvm::any_of(nestedSpec.Clauses().v, [](const parser::OmpClause &c) {
+            return c.Id() == llvm::omp::Clause::OMPC_partial;
+          })};
+      if (!isPartial) {
+        context_.Say(x.source, "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
+      }
+    }
+  }
+}
+
 void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
   loopStack_.push_back(&x);
 
@@ -290,6 +362,8 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
     CheckNoBranching(doBlock, beginName.v, beginName.source);
   }
   CheckLoopItrVariableIsInt(x);
+  CheckNestedConstruct(x);
+  CheckFullUnroll(x);
   CheckAssociatedLoopConstraints(x);
   HasInvalidDistributeNesting(x);
   HasInvalidLoopBinding(x);
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 1b84bc5dda471..7139f475e91d6 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -318,6 +318,10 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
 
   void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
   void CheckSIMDNest(const parser::OpenMPConstruct &x);
+  void CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
+      const parser::Block &body, size_t &nestedCount);
+  void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x);
+  void CheckFullUnroll(const parser::OpenMPLoopConstruct &x);
   void CheckTargetNest(const parser::OpenMPConstruct &x);
   void CheckTargetUpdate();
   void CheckTaskgraph(const parser::OmpBlockConstruct &x);
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index c4d103613b587..f69fce8a6b17a 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2352,7 +2352,6 @@ void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
 //     construct with multiple associated do-loops are lastprivate.
 void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
     const parser::OpenMPLoopConstruct &x) {
-  unsigned version{context_.langOptions().OpenMPVersion};
   std::int64_t level{GetContext().associatedLoopLevel};
   if (level <= 0) {
     return;
@@ -2410,12 +2409,6 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
       }
     }
     CheckAssocLoopLevel(level, GetAssociatedClause());
-  } 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()));
   }
 }
 
diff --git a/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90 b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
index cdc628a3b2e64..5200777e54cda 100644
--- a/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
+++ b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
@@ -9,7 +9,7 @@ program loop_transformation_construct
   integer :: y(I)
 
   !$omp do
-  !$omp unroll
+  !$omp unroll partial(2)
   do x = 1, I
     y(x) = y(x) * 5
   end do
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct01.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
index 8b314d8d823db..979dd0c57e8b5 100644
--- a/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
@@ -10,7 +10,7 @@ subroutine loop_transformation_construct
   integer :: y(I)
 
   !$omp do
-  !$omp unroll
+  !$omp unroll partial(1)
   do i = 1, I
     y(i) = y(i) * 5
   end do
@@ -28,7 +28,8 @@ subroutine loop_transformation_construct
 !CHECK-PARSE-NEXT: | | | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
 !CHECK-PARSE-NEXT: | | | | | OmpBeginLoopDirective
 !CHECK-PARSE-NEXT: | | | | | | OmpDirectiveName -> llvm::omp::Directive = unroll
-!CHECK-PARSE-NEXT: | | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | | OmpClauseList -> OmpClause -> Partial -> Scalar -> Integer -> Constant -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
 !CHECK-PARSE-NEXT: | | | | | | Flags = None
 !CHECK-PARSE-NEXT: | | | | | Block
 !CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> DoConstruct
@@ -71,7 +72,7 @@ subroutine loop_transformation_construct
 !CHECK-UNPARSE-NEXT:  INTEGER x
 !CHECK-UNPARSE-NEXT:  INTEGER y(i)
 !CHECK-UNPARSE-NEXT: !$OMP DO
-!CHECK-UNPARSE-NEXT: !$OMP UNROLL
+!CHECK-UNPARSE-NEXT: !$OMP UNROLL PARTIAL(1_4)
 !CHECK-UNPARSE-NEXT:  DO i=1_4,i
 !CHECK-UNPARSE-NEXT:    y(int(i,kind=8))=5_4*y(int(i,kind=8))
 !CHECK-UNPARSE-NEXT:  END DO
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct02.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct02.f90
index 5b5b591b35f8f..814a885f14a18 100644
--- a/flang/test/Parser/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct02.f90
@@ -10,7 +10,7 @@ subroutine loop_transformation_construct
   integer :: y(I)
 
   !$omp do
-  !$omp unroll
+  !$omp unroll partial(1)
   !$omp tile sizes(2)
   do i = 1, I
     y(i) = y(i) * 5
@@ -30,7 +30,8 @@ subroutine loop_transformation_construct
 !CHECK-PARSE-NEXT: | | | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
 !CHECK-PARSE-NEXT: | | | | | OmpBeginLoopDirective
 !CHECK-PARSE-NEXT: | | | | | | OmpDirectiveName -> llvm::omp::Directive = unroll
-!CHECK-PARSE-NEXT: | | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | | OmpClauseList -> OmpClause -> Partial -> Scalar -> Integer -> Constant -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
 !CHECK-PARSE-NEXT: | | | | | | Flags = None
 !CHECK-PARSE-NEXT: | | | | | Block
 !CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
@@ -84,7 +85,7 @@ subroutine loop_transformation_construct
 !CHECK-UNPARSE-NEXT:  INTEGER x
 !CHECK-UNPARSE-NEXT:  INTEGER y(i)
 !CHECK-UNPARSE-NEXT: !$OMP DO
-!CHECK-UNPARSE-NEXT: !$OMP UNROLL
+!CHECK-UNPARSE-NEXT: !$OMP UNROLL PARTIAL(1_4)
 !CHECK-UNPARSE-NEXT: !$OMP TILE
 !CHECK-UNPARSE-NEXT:  DO i=1_4,i
 !CHECK-UNPARSE-NEXT:    y(int(i,kind=8))=5_4*y(int(i,kind=8))
diff --git a/flang/test/Parser/OpenMP/tile-fail.f90 b/flang/test/Parser/OpenMP/tile-fail.f90
index 0a92e5bcb6570..3cb0ea96975c8 100644
--- a/flang/test/Parser/OpenMP/tile-fail.f90
+++ b/flang/test/Parser/OpenMP/tile-fail.f90
@@ -14,11 +14,10 @@ subroutine stray_end1
 
 
 !--- stray_end2.f90
-! Semantic error
 
 subroutine stray_end2
   print *
-  !CHECK: error: The END TILE directive must follow the DO loop associated with the loop construct
+  !CHECK: error: expected 'END'
   !$omp end tile
 end subroutine
 
@@ -26,7 +25,7 @@ subroutine stray_end2
 !--- stray_begin.f90
 
 subroutine stray_begin
-  !CHECK: error: A DO loop must follow the TILE directive
+  !CHECK: error: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp tile sizes(2)
 end subroutine
 
diff --git a/flang/test/Semantics/OpenMP/do21.f90 b/flang/test/Semantics/OpenMP/do21.f90
index 2f5815c10c11a..e6fe7dd39dd3e 100644
--- a/flang/test/Semantics/OpenMP/do21.f90
+++ b/flang/test/Semantics/OpenMP/do21.f90
@@ -2,26 +2,26 @@
 ! Check for existence of loop following a DO directive
 
 subroutine do1
-  !ERROR: A DO loop must follow the DO directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp do
 end subroutine
 
 subroutine do2
-  !ERROR: A DO loop must follow the PARALLEL DO directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp parallel do
 end subroutine
 
 subroutine do3
-  !ERROR: A DO loop must follow the SIMD directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp simd
 end subroutine
 
 subroutine do4
-  !ERROR: A DO loop must follow the DO SIMD directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp do simd
 end subroutine
 
 subroutine do5
-  !ERROR: A DO loop must follow the LOOP directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp loop
 end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 9fac508e6128a..0a3462048000e 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -17,11 +17,13 @@
   !$omp end parallel
 
   !$omp parallel do
+  !ERROR: DO CONCURRENT loops cannot form part of a loop nest.
   DO CONCURRENT (i = 1:N)
      a = 3.14
   END DO
 
   !$omp parallel do simd
+  !ERROR: The associated loop of a loop-associated directive cannot be a DO WHILE.
   outer: DO WHILE (c > 1)
      inner: do while (b > 100)
         a = 3.14
@@ -32,6 +34,8 @@
 
   ! Accept directives between parallel do and actual loop.
   !$OMP PARALLEL DO
+  !WARNING: Unrecognized compiler directive was ignored [-Wignored-directive]
+  !ERROR: Compiler directives are not allowed inside OpenMP loop constructs
   !DIR$ VECTOR ALIGNED
   DO 20 i=1,N
      a = a + 0.5
@@ -39,11 +43,14 @@
   !$OMP END PARALLEL DO
 
   c = 16
-  !ERROR: DO loop after the PARALLEL DO directive must have loop control
   !$omp parallel do
+  !ERROR: Loop control is not present in the DO LOOP
+  !ERROR: The associated loop of a loop-associated directive cannot be a DO without control.
   do
      a = 3.14
      c = c - 1
+     !ERROR: EXIT to construct outside of PARALLEL DO construct is not allowed
+     !ERROR: EXIT statement terminates associated loop of an OpenMP DO construct
      if (c < 1) exit
   enddo
 
@@ -57,9 +64,9 @@
      do 100 j=1, N
         a = 3.14
 100     continue
-    !ERROR: The ENDDO directive must follow the DO loop associated with the loop construct
     !$omp enddo
 
+  !ERROR: Non-THREADPRIVATE object 'a' in COPYIN clause
   !$omp parallel do copyin(a)
   do i = 1, N
      !$omp parallel do
@@ -74,8 +81,6 @@
   do i = 1, N
   enddo
   !$omp end parallel do
-  !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do
 
   !$omp parallel
   a = 3.0
@@ -84,27 +89,22 @@
   enddo
   !$omp end do simd
 
+  !ERROR: Non-THREADPRIVATE object 'a' in COPYIN clause
   !$omp parallel do copyin(a)
   do i = 1, N
   enddo
   !$omp end parallel
 
   a = 0.0
-  !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do
   !$omp parallel do private(c)
   do i = 1, N
      do j = 1, N
-        !ERROR: A DO loop must follow the PARALLEL DO directive
         !$omp parallel do shared(b)
+        !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
         a = 3.14
      enddo
-     !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-     !$omp end parallel do
   enddo
   a = 1.414
-  !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do
 
   do i = 1, N
      !$omp parallel do
@@ -112,29 +112,22 @@
         a = 3.14
      enddo
   enddo
-  !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do
 
-  !ERROR: A DO loop must follow the PARALLEL DO directive
   !$omp parallel do private(c)
+  !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
 5 FORMAT (1PE12.4, I10)
   do i=1, N
      a = 3.14
   enddo
-  !ERROR: The END PARALLEL DO directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do
 
   !$omp parallel do simd
   do i = 1, N
      a = 3.14
   enddo
   !$omp end parallel do simd
-  !ERROR: The END PARALLEL DO SIMD directive must follow the DO loop associated with the loop construct
-  !$omp end parallel do simd
 
-  !ERROR: A DO loop must follow the SIMD directive
   !$omp simd
+    !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
     a = i + 1
-  !ERROR: The END SIMD directive must follow the DO loop associated with the loop construct
   !$omp end simd
 end
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index f4628c9533db8..caa8f3f216fec 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -5,45 +5,45 @@
 subroutine loop_transformation_construct1
   implicit none
 
+  !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
   !$omp do
-  !ERROR: A DO loop must follow the UNROLL directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp unroll
 end subroutine
 
 subroutine loop_transformation_construct2
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
   !$omp do
+  !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
   do x = 1, i
     v(x) = v(x) * 2
   end do
   !$omp end tile
   !$omp end do
-  !ERROR: The END TILE directive must follow the DO loop associated with the loop construct
-  !$omp end tile
 end subroutine
 
-subroutine loop_transformation_construct2
+subroutine loop_transformation_construct3
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
   !$omp do
-  !ERROR: Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs
+  !ERROR: Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs
   !$omp parallel do
   do x = 1, i
     v(x) = v(x) * 2
   end do
 end subroutine
 
-subroutine loop_transformation_construct3
+subroutine loop_transformation_construct4
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
@@ -51,18 +51,20 @@ subroutine loop_transformation_construct3
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: A DO loop must follow the TILE directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
 end subroutine
 
-subroutine loop_transformation_construct4
+subroutine loop_transformation_construct5
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
   !$omp do
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+  !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
+  !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
   !$omp unroll full
   do x = 1, i
@@ -70,14 +72,15 @@ subroutine loop_transformation_construct4
   end do
 end subroutine
 
-subroutine loop_transformation_construct5
+subroutine loop_transformation_construct6
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
   !$omp do
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+  !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
+  !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
   !$omp unroll
   do x = 1, i
@@ -85,13 +88,14 @@ subroutine loop_transformation_construct5
   end do
 end subroutine
 
-subroutine loop_transformation_construct6
+subroutine loop_transformation_construct7
   implicit none
-  integer :: i = 5
+  integer, parameter :: i = 5
   integer :: x
   integer :: v(i)
 
   !$omp do
+  !ERROR: At least one of SIZES clause must appear on the TILE directive
   !$omp tile
   !$omp unroll partial(2)
   do x = 1, i
diff --git a/flang/test/Semantics/OpenMP/tile02.f90 b/flang/test/Semantics/OpenMP/tile02.f90
index 676796375353f..5b70f94afb09e 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: OpenMP loop construct cannot apply to a fully unrolled loop
   !$omp tile sizes(2)
   !$omp unroll
   do i = 1, 5

>From 72c046f755c26e7696ccff0ba4e25701293be726 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Wed, 19 Nov 2025 14:41:07 -0600
Subject: [PATCH 03/11] [flang][OpenMP] Better diagnostics for invalid or
 misplaced directives

Add two more AST nodes, one for a misplaced end-directive, and one for
an invalid string following the OpenMP sentinel (e.g. "!$OMP XYZ").

Emit error messages when either node is encountered in semantic analysis.
---
 flang/include/flang/Parser/dump-parse-tree.h  |  2 +
 flang/include/flang/Parser/parse-tree.h       | 20 +++++++-
 flang/lib/Parser/executable-parsers.cpp       |  2 +
 flang/lib/Parser/openmp-parsers.cpp           | 51 ++++++++++++-------
 flang/lib/Parser/program-parsers.cpp          |  3 ++
 flang/lib/Parser/type-parsers.h               |  3 +-
 flang/lib/Parser/unparse.cpp                  | 10 ++++
 flang/lib/Semantics/check-omp-structure.cpp   | 23 +++++++++
 flang/lib/Semantics/check-omp-structure.h     |  6 +++
 flang/lib/Semantics/resolve-directives.cpp    |  3 ++
 flang/test/Parser/OpenMP/fail-construct2.f90  |  2 +-
 flang/test/Parser/OpenMP/tile-fail.f90        |  4 +-
 .../Semantics/OpenMP/loop-association.f90     | 14 +++++
 13 files changed, 119 insertions(+), 24 deletions(-)

diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 5ca9deeb9b7f6..32fcd4182bed7 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -755,7 +755,9 @@ class ParseTreeDumper {
   NODE(parser, OpenMPDispatchConstruct)
   NODE(parser, OpenMPFlushConstruct)
   NODE(parser, OpenMPGroupprivate)
+  NODE(parser, OpenMPInvalidDirective)
   NODE(parser, OpenMPLoopConstruct)
+  NODE(parser, OpenMPMisplacedEndDirective)
   NODE(parser, OpenMPRequiresConstruct)
   NODE(parser, OpenMPSectionConstruct)
   NODE(parser, OpenMPSectionsConstruct)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 9795a0d2ae25e..003d11721908e 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -269,8 +269,9 @@ struct AccEndCombinedDirective;
 struct OpenACCDeclarativeConstruct;
 struct OpenACCRoutineConstruct;
 struct OpenMPConstruct;
-struct OpenMPLoopConstruct;
 struct OpenMPDeclarativeConstruct;
+struct OpenMPInvalidDirective;
+struct OpenMPMisplacedEndDirective;
 struct CUFKernelDoConstruct;
 
 // Cooked character stream locations
@@ -406,6 +407,8 @@ struct SpecificationConstruct {
       common::Indirection<StructureDef>,
       common::Indirection<OpenACCDeclarativeConstruct>,
       common::Indirection<OpenMPDeclarativeConstruct>,
+      common::Indirection<OpenMPMisplacedEndDirective>,
+      common::Indirection<OpenMPInvalidDirective>,
       common::Indirection<CompilerDirective>>
       u;
 };
@@ -538,6 +541,8 @@ struct ExecutableConstruct {
       common::Indirection<OpenACCConstruct>,
       common::Indirection<AccEndCombinedDirective>,
       common::Indirection<OpenMPConstruct>,
+      common::Indirection<OpenMPMisplacedEndDirective>,
+      common::Indirection<OpenMPInvalidDirective>,
       common::Indirection<CUFKernelDoConstruct>>
       u;
 };
@@ -5379,6 +5384,19 @@ struct OpenMPConstruct {
       u;
 };
 
+// Orphaned !$OMP END <directive>, i.e. not being a part of a valid OpenMP
+// construct.
+struct OpenMPMisplacedEndDirective : public OmpEndDirective {
+  INHERITED_TUPLE_CLASS_BOILERPLATE(
+      OpenMPMisplacedEndDirective, OmpEndDirective);
+};
+
+// Unrecognized string after the !$OMP sentinel.
+struct OpenMPInvalidDirective {
+  using EmptyTrait = std::true_type;
+  CharBlock source;
+};
+
 // Parse tree nodes for OpenACC 3.3 directives and clauses
 
 struct AccObject {
diff --git a/flang/lib/Parser/executable-parsers.cpp b/flang/lib/Parser/executable-parsers.cpp
index 8d777a6671495..2241c04f5d26d 100644
--- a/flang/lib/Parser/executable-parsers.cpp
+++ b/flang/lib/Parser/executable-parsers.cpp
@@ -50,6 +50,8 @@ constexpr auto executableConstruct{first(
     construct<ExecutableConstruct>(indirect(whereConstruct)),
     construct<ExecutableConstruct>(indirect(forallConstruct)),
     construct<ExecutableConstruct>(indirect(openmpConstruct)),
+    construct<ExecutableConstruct>(indirect(openmpMisplacedEndDirective)),
+    construct<ExecutableConstruct>(indirect(openmpInvalidDirective)),
     construct<ExecutableConstruct>(indirect(Parser<OpenACCConstruct>{})),
     construct<ExecutableConstruct>(indirect(compilerDirective)),
     construct<ExecutableConstruct>(indirect(Parser<CUFKernelDoConstruct>{})))};
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index d50f45794230b..6592b87f8c62d 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -1573,6 +1573,14 @@ static inline constexpr auto IsMemberOf(const DirectiveSet &dirs) {
   };
 }
 
+constexpr auto validEPC{//
+    predicated(executionPartConstruct, [](auto &epc) {
+      return !Unwrap<OpenMPMisplacedEndDirective>(epc) &&
+          !Unwrap<OpenMPMisplacedEndDirective>(epc);
+    })};
+
+constexpr auto validBlock{many(validEPC)};
+
 TYPE_PARSER(sourced(construct<OmpDirectiveName>(OmpDirectiveNameParser{})))
 
 OmpDirectiveSpecification static makeFlushFromOldSyntax(Verbatim &&text,
@@ -1630,7 +1638,7 @@ struct StrictlyStructuredBlockParser {
   std::optional<resultType> Parse(ParseState &state) const {
     // Detect BLOCK construct without parsing the entire thing.
     if (lookAhead(skipStuffBeforeStatement >> "BLOCK"_tok).Parse(state)) {
-      if (auto epc{Parser<ExecutionPartConstruct>{}.Parse(state)}) {
+      if (auto &&epc{executionPartConstruct.Parse(state)}) {
         if (GetFortranBlockConstruct(*epc) != nullptr) {
           Block body;
           body.emplace_back(std::move(*epc));
@@ -1650,7 +1658,7 @@ struct LooselyStructuredBlockParser {
     if (lookAhead(skipStuffBeforeStatement >> "BLOCK"_tok).Parse(state)) {
       return std::nullopt;
     }
-    if (auto &&body{block.Parse(state)}) {
+    if (auto &&body{validBlock.Parse(state)}) {
       // Empty body is ok.
       return std::move(body);
     }
@@ -1674,7 +1682,7 @@ struct NonBlockDoConstructParser {
 
     // Keep parsing ExecutionPartConstructs until the set of open label-do
     // statements becomes empty, or until the EPC parser fails.
-    while (auto &&epc{attempt(executionPartConstruct).Parse(state)}) {
+    while (auto &&epc{attempt(validEPC).Parse(state)}) {
       if (auto &&label{GetStatementLabel(*epc)}) {
         labels.erase(*label);
       }
@@ -1825,7 +1833,7 @@ struct OmpStatementConstructParser {
   std::optional<resultType> Parse(ParseState &state) const {
     if (auto begin{OmpBeginDirectiveParser(dir_).Parse(state)}) {
       Block body;
-      if (auto stmt{attempt(Parser<ExecutionPartConstruct>{}).Parse(state)}) {
+      if (auto stmt{attempt(validEPC).Parse(state)}) {
         body.emplace_back(std::move(*stmt));
       }
       // Allow empty block. Check for this in semantics.
@@ -1995,11 +2003,9 @@ struct OmpAtomicConstructParser {
       return std::nullopt;
     }
 
-    auto exec{Parser<ExecutionPartConstruct>{}};
-    auto end{OmpEndDirectiveParser{llvm::omp::Directive::OMPD_atomic}};
     TailType tail;
 
-    if (ParseOne(exec, end, tail, state)) {
+    if (ParseOne(tail, state)) {
       if (!tail.first.empty()) {
         if (auto &&rest{attempt(LimitedTailParser(BodyLimit)).Parse(state)}) {
           for (auto &&s : rest->first) {
@@ -2026,13 +2032,12 @@ struct OmpAtomicConstructParser {
 
   // Parse either an ExecutionPartConstruct, or atomic end-directive. When
   // successful, record the result in the "tail" provided, otherwise fail.
-  static std::optional<Success> ParseOne( //
-      Parser<ExecutionPartConstruct> &exec, OmpEndDirectiveParser &end,
-      TailType &tail, ParseState &state) {
-    auto isRecovery{[](const ExecutionPartConstruct &e) {
-      return std::holds_alternative<ErrorRecovery>(e.u);
+  static std::optional<Success> ParseOne(TailType &tail, ParseState &state) {
+    auto isUsable{[](const std::optional<ExecutionPartConstruct> &e) {
+      return e && !std::holds_alternative<ErrorRecovery>(e->u);
     }};
-    if (auto &&stmt{attempt(exec).Parse(state)}; stmt && !isRecovery(*stmt)) {
+    auto end{OmpEndDirectiveParser{llvm::omp::Directive::OMPD_atomic}};
+    if (auto &&stmt{attempt(validEPC).Parse(state)}; isUsable(stmt)) {
       tail.first.emplace_back(std::move(*stmt));
     } else if (auto &&dir{attempt(end).Parse(state)}) {
       tail.second = std::move(*dir);
@@ -2048,12 +2053,10 @@ struct OmpAtomicConstructParser {
     constexpr LimitedTailParser(size_t count) : count_(count) {}
 
     std::optional<resultType> Parse(ParseState &state) const {
-      auto exec{Parser<ExecutionPartConstruct>{}};
-      auto end{OmpEndDirectiveParser{llvm::omp::Directive::OMPD_atomic}};
       TailType tail;
 
       for (size_t i{0}; i != count_; ++i) {
-        if (ParseOne(exec, end, tail, state)) {
+        if (ParseOne(tail, state)) {
           if (tail.second) {
             // Return when the end-directive was parsed.
             return std::move(tail);
@@ -2325,9 +2328,9 @@ TYPE_PARSER(sourced(construct<OpenMPSectionsConstruct>(
     Parser<OmpBeginSectionsDirective>{} / endOmpLine,
     cons( //
         construct<OpenMPConstruct>(sourced(
-            construct<OpenMPSectionConstruct>(maybe(sectionDir), block))),
-        many(construct<OpenMPConstruct>(
-            sourced(construct<OpenMPSectionConstruct>(sectionDir, block))))),
+            construct<OpenMPSectionConstruct>(maybe(sectionDir), validBlock))),
+        many(construct<OpenMPConstruct>(sourced(
+            construct<OpenMPSectionConstruct>(sectionDir, validBlock))))),
     maybe(Parser<OmpEndSectionsDirective>{} / endOmpLine))))
 
 static bool IsExecutionPart(const OmpDirectiveName &name) {
@@ -2402,4 +2405,14 @@ static constexpr DirectiveSet GetLoopDirectives() {
 TYPE_PARSER(sourced(construct<OpenMPLoopConstruct>(
     OmpLoopConstructParser(GetLoopDirectives()))))
 
+static constexpr DirectiveSet GetAllDirectives() { //
+  return ~DirectiveSet{};
+}
+
+TYPE_PARSER(construct<OpenMPMisplacedEndDirective>(
+    OmpEndDirectiveParser{GetAllDirectives()}))
+
+TYPE_PARSER( //
+    startOmpLine >> sourced(construct<OpenMPInvalidDirective>(
+                        !OmpDirectiveNameParser{} >> SkipTo<'\n'>{})))
 } // namespace Fortran::parser
diff --git a/flang/lib/Parser/program-parsers.cpp b/flang/lib/Parser/program-parsers.cpp
index 740dbbfab9db7..303335934a37a 100644
--- a/flang/lib/Parser/program-parsers.cpp
+++ b/flang/lib/Parser/program-parsers.cpp
@@ -201,6 +201,9 @@ TYPE_CONTEXT_PARSER("specification construct"_en_US,
         construct<SpecificationConstruct>(
             indirect(openaccDeclarativeConstruct)),
         construct<SpecificationConstruct>(indirect(openmpDeclarativeConstruct)),
+        construct<SpecificationConstruct>(
+            indirect(openmpMisplacedEndDirective)),
+        construct<SpecificationConstruct>(indirect(openmpInvalidDirective)),
         construct<SpecificationConstruct>(indirect(compilerDirective))))
 
 // R513 other-specification-stmt ->
diff --git a/flang/lib/Parser/type-parsers.h b/flang/lib/Parser/type-parsers.h
index 3900c5a86c874..142aa226893b6 100644
--- a/flang/lib/Parser/type-parsers.h
+++ b/flang/lib/Parser/type-parsers.h
@@ -139,7 +139,8 @@ constexpr Parser<OpenACCDeclarativeConstruct> openaccDeclarativeConstruct;
 constexpr Parser<OpenMPConstruct> openmpConstruct;
 constexpr Parser<OpenMPExecDirective> openmpExecDirective;
 constexpr Parser<OpenMPDeclarativeConstruct> openmpDeclarativeConstruct;
-constexpr Parser<OmpEndLoopDirective> ompEndLoopDirective;
+constexpr Parser<OpenMPMisplacedEndDirective> openmpMisplacedEndDirective;
+constexpr Parser<OpenMPInvalidDirective> openmpInvalidDirective;
 constexpr Parser<IntrinsicVectorTypeSpec> intrinsicVectorTypeSpec; // Extension
 constexpr Parser<VectorTypeSpec> vectorTypeSpec; // Extension
 constexpr Parser<UnsignedTypeSpec> unsignedTypeSpec; // Extension
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index f81200d092b11..3854d33d46d48 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2706,6 +2706,16 @@ class UnparseVisitor {
     Put("\n");
     EndOpenMP();
   }
+  void Unparse(const OpenMPMisplacedEndDirective &x) {
+    Unparse(static_cast<const OmpEndDirective &>(x));
+  }
+  void Unparse(const OpenMPInvalidDirective &x) {
+    BeginOpenMP();
+    Word("!$OMP ");
+    Put(parser::ToUpperCaseLetters(x.source.ToString()));
+    Put("\n");
+    EndOpenMP();
+  }
   void Unparse(const BasedPointer &x) {
     Put('('), Walk(std::get<0>(x.t)), Put(","), Walk(std::get<1>(x.t));
     Walk("(", std::get<std::optional<ArraySpec>>(x.t), ")"), Put(')');
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 37b4404cc598f..6c18b7a8ac4bd 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5474,6 +5474,29 @@ void OmpStructureChecker::CheckAllowedRequiresClause(llvmOmpClause clause) {
   }
 }
 
+void OmpStructureChecker::Enter(const parser::OpenMPMisplacedEndDirective &x) {
+  invalidState_ = true;
+  context_.Say(x.DirName().source, "Misplaced OpenMP end-directive"_err_en_US);
+  PushContextAndClauseSets(
+      x.DirName().source, llvm::omp::Directive::OMPD_unknown);
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPMisplacedEndDirective &x) {
+  dirContext_.pop_back();
+  invalidState_ = false;
+}
+
+void OmpStructureChecker::Enter(const parser::OpenMPInvalidDirective &x) {
+  invalidState_ = true;
+  context_.Say(x.source, "Invalid OpenMP directive"_err_en_US);
+  PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_unknown);
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPInvalidDirective &x) {
+  dirContext_.pop_back();
+  invalidState_ = false;
+}
+
 // Use when clause falls under 'struct OmpClause' in 'parse-tree.h'.
 #define CHECK_SIMPLE_CLAUSE(X, Y) \
   void OmpStructureChecker::Enter(const parser::OmpClause::X &) { \
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 7139f475e91d6..965f18bfbb352 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -94,6 +94,11 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void Enter(const parser::OpenMPDeclarativeConstruct &);
   void Leave(const parser::OpenMPDeclarativeConstruct &);
 
+  void Enter(const parser::OpenMPMisplacedEndDirective &);
+  void Leave(const parser::OpenMPMisplacedEndDirective &);
+  void Enter(const parser::OpenMPInvalidDirective &);
+  void Leave(const parser::OpenMPInvalidDirective &);
+
   void Enter(const parser::OpenMPLoopConstruct &);
   void Leave(const parser::OpenMPLoopConstruct &);
   void Enter(const parser::OmpEndLoopDirective &);
@@ -389,6 +394,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   std::vector<LoopConstruct> loopStack_;
   // Scopes for scoping units.
   std::vector<const Scope *> scopeStack_;
+  bool invalidState_{false}; // Set during visiting OpenMPMisplacedEndDirective
 
   enum class PartKind : int {
     // There are also other "parts", such as internal-subprogram-part, etc,
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index f69fce8a6b17a..4fadcf73c5cbb 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -529,6 +529,9 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
   void Post(const parser::OmpBeginLoopDirective &) {
     GetContext().withinConstruct = true;
   }
+  bool Pre(const parser::OpenMPMisplacedEndDirective &x) { return false; }
+  bool Pre(const parser::OpenMPInvalidDirective &x) { return false; }
+
   bool Pre(const parser::DoConstruct &);
 
   bool Pre(const parser::OpenMPSectionsConstruct &);
diff --git a/flang/test/Parser/OpenMP/fail-construct2.f90 b/flang/test/Parser/OpenMP/fail-construct2.f90
index b7f5736d1329b..3798c3dae3f0d 100644
--- a/flang/test/Parser/OpenMP/fail-construct2.f90
+++ b/flang/test/Parser/OpenMP/fail-construct2.f90
@@ -1,5 +1,5 @@
 ! RUN: not %flang_fc1 -fsyntax-only -fopenmp %s 2>&1 | FileCheck %s
 
-! CHECK: error: expected OpenMP construct
+! CHECK: error: Invalid OpenMP directive
 !$omp dummy
 end
diff --git a/flang/test/Parser/OpenMP/tile-fail.f90 b/flang/test/Parser/OpenMP/tile-fail.f90
index 3cb0ea96975c8..a69261a927961 100644
--- a/flang/test/Parser/OpenMP/tile-fail.f90
+++ b/flang/test/Parser/OpenMP/tile-fail.f90
@@ -8,7 +8,7 @@
 ! Parser error
 
 subroutine stray_end1
-  !CHECK: error: expected OpenMP construct
+  !CHECK: error: Misplaced OpenMP end-directive
   !$omp end tile
 end subroutine
 
@@ -17,7 +17,7 @@ subroutine stray_end1
 
 subroutine stray_end2
   print *
-  !CHECK: error: expected 'END'
+  !CHECK: error: Misplaced OpenMP end-directive
   !$omp end tile
 end subroutine
 
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 0a3462048000e..603bfdcc0a4e9 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -81,6 +81,8 @@
   do i = 1, N
   enddo
   !$omp end parallel do
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do
 
   !$omp parallel
   a = 3.0
@@ -96,6 +98,8 @@
   !$omp end parallel
 
   a = 0.0
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do
   !$omp parallel do private(c)
   do i = 1, N
      do j = 1, N
@@ -103,8 +107,12 @@
         !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
         a = 3.14
      enddo
+     !ERROR: Misplaced OpenMP end-directive
+     !$omp end parallel do
   enddo
   a = 1.414
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do
 
   do i = 1, N
      !$omp parallel do
@@ -112,6 +120,8 @@
         a = 3.14
      enddo
   enddo
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do
 
   !$omp parallel do private(c)
   !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
@@ -119,12 +129,16 @@
   do i=1, N
      a = 3.14
   enddo
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do
 
   !$omp parallel do simd
   do i = 1, N
      a = 3.14
   enddo
   !$omp end parallel do simd
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end parallel do simd
 
   !$omp simd
     !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs

>From 50ec7a3478b2cc0cd2c258ef2ce12dc3eda70d95 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 20 Nov 2025 09:17:30 -0600
Subject: [PATCH 04/11] format

---
 flang/lib/Semantics/check-omp-loop.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 9798420eb8086..b321012fc5421 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -311,7 +311,8 @@ void OmpStructureChecker::CheckFullUnroll(
             return c.Id() == llvm::omp::Clause::OMPC_partial;
           })};
       if (!isPartial) {
-        context_.Say(x.source, "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
+        context_.Say(x.source,
+            "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
       }
     }
   }

>From 4d92961ae2ff6f2c45a26fca889c7e097d3fd4d4 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 20 Nov 2025 11:04:52 -0600
Subject: [PATCH 05/11] Remove invalidState_

---
 flang/lib/Semantics/check-omp-structure.cpp | 4 ----
 flang/lib/Semantics/check-omp-structure.h   | 1 -
 2 files changed, 5 deletions(-)

diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 6c18b7a8ac4bd..3bae781ab2446 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5475,7 +5475,6 @@ void OmpStructureChecker::CheckAllowedRequiresClause(llvmOmpClause clause) {
 }
 
 void OmpStructureChecker::Enter(const parser::OpenMPMisplacedEndDirective &x) {
-  invalidState_ = true;
   context_.Say(x.DirName().source, "Misplaced OpenMP end-directive"_err_en_US);
   PushContextAndClauseSets(
       x.DirName().source, llvm::omp::Directive::OMPD_unknown);
@@ -5483,18 +5482,15 @@ void OmpStructureChecker::Enter(const parser::OpenMPMisplacedEndDirective &x) {
 
 void OmpStructureChecker::Leave(const parser::OpenMPMisplacedEndDirective &x) {
   dirContext_.pop_back();
-  invalidState_ = false;
 }
 
 void OmpStructureChecker::Enter(const parser::OpenMPInvalidDirective &x) {
-  invalidState_ = true;
   context_.Say(x.source, "Invalid OpenMP directive"_err_en_US);
   PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_unknown);
 }
 
 void OmpStructureChecker::Leave(const parser::OpenMPInvalidDirective &x) {
   dirContext_.pop_back();
-  invalidState_ = false;
 }
 
 // Use when clause falls under 'struct OmpClause' in 'parse-tree.h'.
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 965f18bfbb352..2990710f92d1d 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -394,7 +394,6 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   std::vector<LoopConstruct> loopStack_;
   // Scopes for scoping units.
   std::vector<const Scope *> scopeStack_;
-  bool invalidState_{false}; // Set during visiting OpenMPMisplacedEndDirective
 
   enum class PartKind : int {
     // There are also other "parts", such as internal-subprogram-part, etc,

>From a032a55d4e3f4e54324a027b6a73507a6cd3b19f Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 21 Nov 2025 14:33:15 -0600
Subject: [PATCH 06/11] Rebase + parser updates to reflect changes in main

---
 flang/lib/Parser/openmp-parsers.cpp        | 102 +++++++++++++++------
 flang/lib/Semantics/check-omp-loop.cpp     |   4 +-
 flang/lib/Semantics/resolve-directives.cpp |   1 +
 3 files changed, 80 insertions(+), 27 deletions(-)

diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 68720913ac2ca..a0c94296de5ed 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -58,6 +58,35 @@ constexpr auto endOmpLine = space >> endOfLine;
 constexpr auto logicalConstantExpr{logical(constantExpr)};
 constexpr auto scalarLogicalConstantExpr{scalar(logicalConstantExpr)};
 
+// Parser that wraps the result of another parser into a Block. If the given
+// parser succeeds, the result is a block containing the ExecutionPartConstruct
+// result of the argument parser. Otherwise the parser fails.
+template <typename ExecParser> struct AsBlockParser {
+  using resultType = Block;
+  static_assert(
+      std::is_same_v<typename ExecParser::resultType, ExecutionPartConstruct>);
+
+  constexpr AsBlockParser(ExecParser epc) : epc_(epc) {}
+  std::optional<resultType> Parse(ParseState &state) const {
+    if (auto &&exec{attempt(epc_).Parse(state)}) {
+      Block body;
+      body.push_back(std::move(*exec));
+      return body;
+    }
+    return std::nullopt;
+  }
+
+private:
+  const ExecParser epc_;
+};
+
+template <typename ExecParser,
+    typename = std::enable_if<std::is_same_v<typename ExecParser::resultType,
+        ExecutionPartConstruct>>>
+constexpr auto asBlock(ExecParser epc) {
+  return AsBlockParser<ExecParser>(epc);
+}
+
 // Given a parser for a single element, and a parser for a list of elements
 // of the same type, create a parser that constructs the entire list by having
 // the single element be the head of the list, and the rest be the tail.
@@ -1674,16 +1703,26 @@ struct NonBlockDoConstructParser {
 
     // Keep parsing ExecutionPartConstructs until the set of open label-do
     // statements becomes empty, or until the EPC parser fails.
-    while (auto &&epc{attempt(executionPartConstruct).Parse(state)}) {
-      if (auto &&label{GetStatementLabel(*epc)}) {
+    auto processEpc{[&](ExecutionPartConstruct &&epc) {
+      if (auto &&label{GetStatementLabel(epc)}) {
         labels.erase(*label);
       }
-      if (auto *labelDo{Unwrap<LabelDoStmt>(*epc)}) {
+      if (auto *labelDo{Unwrap<LabelDoStmt>(epc)}) {
         labels.insert(std::get<Label>(labelDo->t));
       }
-      body.push_back(std::move(*epc));
-      if (labels.empty()) {
-        break;
+      body.push_back(std::move(epc));
+    }};
+
+    auto nonBlockDo{predicated(executionPartConstruct,
+        [](auto &epc) { return Unwrap<LabelDoStmt>(epc); })};
+
+    if (auto &&nbd{nonBlockDo.Parse(state)}) {
+      processEpc(std::move(*nbd));
+      while (auto &&epc{attempt(executionPartConstruct).Parse(state)}) {
+        processEpc(std::move(*epc));
+        if (labels.empty()) {
+          break;
+        }
       }
     }
 
@@ -1885,28 +1924,39 @@ struct OmpLoopConstructParser {
   constexpr OmpLoopConstructParser(DirectiveSet dirs) : dirs_(dirs) {}
 
   std::optional<resultType> Parse(ParseState &state) const {
+    auto ompLoopConstruct{asBlock(predicated(executionPartConstruct,
+        [](auto &epc) { return Unwrap<OpenMPLoopConstruct>(epc); }))};
+    auto loopItem{LoopNestParser{} || ompLoopConstruct};
+
     if (auto &&begin{OmpBeginDirectiveParser(dirs_).Parse(state)}) {
-      if (auto &&nest{attempt(LoopNestParser{}).Parse(state)}) {
-        auto end{maybe(OmpEndDirectiveParser{dirs_}).Parse(state)};
-        return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
-            std::move(*nest),
-            llvm::transformOptional(std::move(*end),
-                [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
-      } else {
-        // Parse a nested OpenMPLoopConstruct as the body.
-        auto ompLoopConstruct{predicated(executionPartConstruct,
-            [](auto &epc) { return Unwrap<OpenMPLoopConstruct>(epc); })};
-
-        // Allow empty body.
-        Block body;
-        if (auto &&omp{attempt(ompLoopConstruct).Parse(state)}) {
-          body.push_back(std::move(*omp));
+      auto loopDir{begin->DirName().v};
+      auto assoc{llvm::omp::getDirectiveAssociation(loopDir)};
+      if (assoc == llvm::omp::Association::LoopNest) {
+        if (auto &&item{attempt(loopItem).Parse(state)}) {
+          auto end{maybe(OmpEndDirectiveParser{loopDir}).Parse(state)};
+          return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
+              std::move(*item),
+              llvm::transformOptional(std::move(*end),
+                  [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
+        } else if (auto &&empty{pure<Block>().Parse(state)}) {
+          // Allow empty body.
+          auto end{maybe(OmpEndDirectiveParser{loopDir}).Parse(state)};
+          return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
+              std::move(*empty),
+              llvm::transformOptional(std::move(*end),
+                  [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
         }
-        auto end{maybe(OmpEndDirectiveParser{dirs_}).Parse(state)};
-        return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
-            std::move(body),
-            llvm::transformOptional(std::move(*end),
-                [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
+      } else if (assoc == llvm::omp::Association::LoopSeq) {
+        // Parse loop sequence as a block.
+        if (auto &&body{block.Parse(state)}) {
+          auto end{maybe(OmpEndDirectiveParser{loopDir}).Parse(state)};
+          return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
+              std::move(*body),
+              llvm::transformOptional(std::move(*end),
+                  [](auto &&s) { return OmpEndLoopDirective(std::move(s)); })};
+        }
+      } else {
+        llvm_unreachable("Unexpected association");
       }
     }
     return std::nullopt;
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index f5885c700f445..6d83ee62c3b73 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -550,7 +550,9 @@ void OmpStructureChecker::CheckLooprangeBounds(
 void OmpStructureChecker::CheckNestedFuse(
     const parser::OpenMPLoopConstruct &x) {
   auto &loopConsList{std::get<parser::Block>(x.t)};
-  assert(loopConsList.size() == 1 && "Not Expecting a loop sequence");
+  if (loopConsList.empty()) {
+    return;
+  }
   const auto *ompConstruct{parser::omp::GetOmpLoop(loopConsList.front())};
   if (!ompConstruct) {
     return;
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 6aa606e9524b4..bd6c1f8e4c776 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2417,6 +2417,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
       }
       CheckAssocLoopLevel(level, GetAssociatedClause());
     } else {
+      unsigned version{context_.langOptions().OpenMPVersion};
       context_.Say(GetContext().directiveSource,
           "A DO loop must follow the %s directive"_err_en_US,
           parser::ToUpperCaseLetters(

>From 13f734504f83238b257ad1131314a4201de2a95c Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 22 Nov 2025 09:13:21 -0600
Subject: [PATCH 07/11] Temporary test changes

---
 flang/test/Parser/OpenMP/fail-looprange.f90              | 6 +++---
 flang/test/Semantics/OpenMP/clause-validity01.f90        | 5 +----
 flang/test/Semantics/OpenMP/loop-association.f90         | 9 +++++----
 .../Semantics/OpenMP/loop-transformation-construct02.f90 | 1 +
 .../Semantics/OpenMP/loop-transformation-construct03.f90 | 1 +
 5 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/flang/test/Parser/OpenMP/fail-looprange.f90 b/flang/test/Parser/OpenMP/fail-looprange.f90
index ebe3480b44f12..1c8a080871389 100644
--- a/flang/test/Parser/OpenMP/fail-looprange.f90
+++ b/flang/test/Parser/OpenMP/fail-looprange.f90
@@ -1,11 +1,11 @@
 ! RUN: not %flang_fc1 -fsyntax-only -fopenmp %s 2>&1 | FileCheck %s
 
-! CHECK: error: expected end of line
+! CHECK: error:
 !$omp fuse looprange
 
-! CHECK: error: expected end of line
+! CHECK: error:
 !$omp fuse looprange(1)
 
-! CHECK: error: expected end of line
+! CHECK: error:
 !$omp fuse looprange(1,2,3)
 end
diff --git a/flang/test/Semantics/OpenMP/clause-validity01.f90 b/flang/test/Semantics/OpenMP/clause-validity01.f90
index 5f74978f38279..fda15eb9876ef 100644
--- a/flang/test/Semantics/OpenMP/clause-validity01.f90
+++ b/flang/test/Semantics/OpenMP/clause-validity01.f90
@@ -252,8 +252,6 @@
   !$omp parallel do if(target:a>1.)
   do i = 1, N
   enddo
-  !ERROR: Unmatched END SIMD directive
-  !$omp end simd
 
 ! 2.7.2 sections-clause -> private-clause |
 !                         firstprivate-clause |
@@ -574,8 +572,7 @@
   do i = 1, N
      a = a + 3.14
   enddo
-  !ERROR: Unmatched END TASKLOOP directive
-  !$omp end taskloop
+  !$omp end taskloop simd
 
   !ERROR: GRAINSIZE and NUM_TASKS clauses are mutually exclusive and may not appear on the same TASKLOOP SIMD directive
   !$omp taskloop simd num_tasks(3) grainsize(2)
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 0a3462048000e..b2898d3967a25 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -33,6 +33,7 @@
   END DO outer
 
   ! Accept directives between parallel do and actual loop.
+  !ERROR: A DO loop must follow the PARALLEL DO directive
   !$OMP PARALLEL DO
   !WARNING: Unrecognized compiler directive was ignored [-Wignored-directive]
   !ERROR: Compiler directives are not allowed inside OpenMP loop constructs
@@ -99,8 +100,8 @@
   !$omp parallel do private(c)
   do i = 1, N
      do j = 1, N
+        !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
         !$omp parallel do shared(b)
-        !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
         a = 3.14
      enddo
   enddo
@@ -113,8 +114,8 @@
      enddo
   enddo
 
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp parallel do private(c)
-  !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
 5 FORMAT (1PE12.4, I10)
   do i=1, N
      a = 3.14
@@ -126,8 +127,8 @@
   enddo
   !$omp end parallel do simd
 
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp simd
-    !ERROR: OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs
-    a = i + 1
   !$omp end simd
+   a = i + 1
 end
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 7cf7b15c41a62..3c07d6d08508a 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -2,6 +2,7 @@
 ! nested Loop Transformation Constructs
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
index 88c3bd2bae4e0..ab6b1bd7d67f9 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -1,6 +1,7 @@
 ! Testing the Semantic failure of forming loop sequences under regular OpenMP directives 
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none

>From da686bef8fa571cfbda3aa48eed74057d8e8ef61 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 22 Nov 2025 09:47:17 -0600
Subject: [PATCH 08/11] Restore tests

---
 flang/test/Semantics/OpenMP/clause-validity01.f90    |  5 ++++-
 flang/test/Semantics/OpenMP/loop-association.f90     |  3 ++-
 .../OpenMP/loop-transformation-construct02.f90       | 12 ++++++------
 .../OpenMP/loop-transformation-construct03.f90       |  5 ++---
 4 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/flang/test/Semantics/OpenMP/clause-validity01.f90 b/flang/test/Semantics/OpenMP/clause-validity01.f90
index fda15eb9876ef..ad0b764b2cc28 100644
--- a/flang/test/Semantics/OpenMP/clause-validity01.f90
+++ b/flang/test/Semantics/OpenMP/clause-validity01.f90
@@ -252,6 +252,8 @@
   !$omp parallel do if(target:a>1.)
   do i = 1, N
   enddo
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end simd
 
 ! 2.7.2 sections-clause -> private-clause |
 !                         firstprivate-clause |
@@ -572,7 +574,8 @@
   do i = 1, N
      a = a + 3.14
   enddo
-  !$omp end taskloop simd
+  !ERROR: Misplaced OpenMP end-directive
+  !$omp end taskloop
 
   !ERROR: GRAINSIZE and NUM_TASKS clauses are mutually exclusive and may not appear on the same TASKLOOP SIMD directive
   !$omp taskloop simd num_tasks(3) grainsize(2)
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 8f696546c05e0..6ca95496c0e9c 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -143,6 +143,7 @@
 
   !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp simd
+    a = i + 1
+  !ERROR: Misplaced OpenMP end-directive
   !$omp end simd
-   a = i + 1
 end
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 3c07d6d08508a..1b15c938915cd 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -2,13 +2,12 @@
 ! nested Loop Transformation Constructs
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
-!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none
 
   !$omp do
-  !ERROR: The FUSE construct requires the END FUSE directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse 
 end subroutine
 
@@ -16,7 +15,7 @@ subroutine loop_transformation_construct2
   implicit none
 
   !$omp do
-  !ERROR: A DO loop must follow the FUSE directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse 
   !$omp end fuse
 end subroutine
@@ -37,7 +36,7 @@ subroutine loop_transformation_construct3
   end do
   !$omp end fuse
   !$omp end do
-  !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+  !ERROR: Misplaced OpenMP end-directive
   !$omp end fuse
 end subroutine
 
@@ -51,7 +50,7 @@ subroutine loop_transformation_construct4
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: A DO loop must follow the FUSE directive
+  !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
   !$omp fuse
   !$omp end fuse
 end subroutine
@@ -63,7 +62,7 @@ subroutine loop_transformation_construct5
   integer :: v(i)
 
   !$omp do
-  !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
+  !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
   !$omp fuse
   !$omp unroll full
   do x = 1, i
@@ -81,6 +80,7 @@ subroutine loop_transformation_construct6
   integer :: x
   integer :: v(i)
 
+  !ERROR: The loop sequence following the DO construct must be fully fused first.
   !$omp do
   !$omp fuse looprange(1,1)
   !$omp unroll partial(2)
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
index ab6b1bd7d67f9..578c55b3c0aff 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -1,7 +1,6 @@
 ! Testing the Semantic failure of forming loop sequences under regular OpenMP directives 
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
-!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none
@@ -17,7 +16,7 @@ subroutine loop_transformation_construct1
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: The END DO directive must follow the DO loop associated with the loop construct
+  !ERROR: Misplaced OpenMP end-directive
   !$omp end do
 end subroutine
 
@@ -35,6 +34,6 @@ subroutine loop_transformation_construct2
   do x = 1, i
     v(x) = v(x) * 2
   end do
-  !ERROR: The END TILE directive must follow the DO loop associated with the loop construct
+  !ERROR: Misplaced OpenMP end-directive
   !$omp end tile
 end subroutine

>From 31578653d009a528b5e0f4a738840cbb83e3c372 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 22 Nov 2025 11:26:08 -0600
Subject: [PATCH 09/11] Update loop construct parser

---
 flang/lib/Parser/openmp-parsers.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 993b8c5f2ec07..77b0e3f5435ce 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -1956,7 +1956,7 @@ struct OmpLoopConstructParser {
         }
       } else if (assoc == llvm::omp::Association::LoopSeq) {
         // Parse loop sequence as a block.
-        if (auto &&body{block.Parse(state)}) {
+        if (auto &&body{validBlock.Parse(state)}) {
           auto end{maybe(OmpEndDirectiveParser{loopDir}).Parse(state)};
           return OpenMPLoopConstruct{OmpBeginLoopDirective(std::move(*begin)),
               std::move(*body),

>From 4a0b1a0f4d8a5452de8652717f7209b9b6ae7879 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 22 Nov 2025 12:54:24 -0600
Subject: [PATCH 10/11] Update loop-transformation-construct02.f90

---
 flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 974f31b928883..1b15c938915cd 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -2,7 +2,6 @@
 ! nested Loop Transformation Constructs
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
-!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none

>From e37a530dbc0a5ccc4bb3c3a65033f6dbb9ba1f69 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Sat, 22 Nov 2025 12:54:46 -0600
Subject: [PATCH 11/11] Update loop-transformation-construct03.f90

---
 flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
index 1e37d6105b2c8..578c55b3c0aff 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -1,7 +1,6 @@
 ! Testing the Semantic failure of forming loop sequences under regular OpenMP directives 
 
 !RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
-!XFAIL: *
 
 subroutine loop_transformation_construct1
   implicit none



More information about the flang-commits mailing list