Skip to content

[Flang][OpenMP] Add Semantics support for Nested OpenMPLoopConstructs #145917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 1, 2025

Conversation

Stylie777
Copy link
Contributor

In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself.

Relevant tests have been added, and previous behaviour has been retained with no changes.

See also, #110008

@Stylie777 Stylie777 self-assigned this Jun 26, 2025
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir flang:openmp flang:semantics flang:parser labels Jun 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 26, 2025

@llvm/pr-subscribers-flang-semantics
@llvm/pr-subscribers-flang-fir-hlfir
@llvm/pr-subscribers-flang-openmp

@llvm/pr-subscribers-flang-parser

Author: Jack Styles (Stylie777)

Changes

In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself.

Relevant tests have been added, and previous behaviour has been retained with no changes.

See also, #110008


Patch is 26.86 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/145917.diff

11 Files Affected:

  • (modified) flang/include/flang/Parser/parse-tree.h (+1-1)
  • (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+6)
  • (modified) flang/lib/Parser/unparse.cpp (+1-1)
  • (modified) flang/lib/Semantics/canonicalize-omp.cpp (+49-11)
  • (modified) flang/lib/Semantics/check-omp-structure.cpp (+31-13)
  • (modified) flang/lib/Semantics/resolve-directives.cpp (+58-39)
  • (added) flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90 (+20)
  • (added) flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90 (+20)
  • (added) flang/test/Parser/OpenMP/loop-transformation-construct01.f90 (+74)
  • (added) flang/test/Parser/OpenMP/loop-transformation-construct02.f90 (+85)
  • (added) flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 (+28)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 61f97b855b0e5..7aa9250234978 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5025,7 +5025,7 @@ struct OpenMPLoopConstruct {
   TUPLE_CLASS_BOILERPLATE(OpenMPLoopConstruct);
   OpenMPLoopConstruct(OmpBeginLoopDirective &&a)
       : t({std::move(a), std::nullopt, std::nullopt}) {}
-  std::tuple<OmpBeginLoopDirective, std::optional<DoConstruct>,
+  std::tuple<OmpBeginLoopDirective, std::optional<std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>,
       std::optional<OmpEndLoopDirective>>
       t;
 };
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3e865a1ee7185..e81ce5e3dd22f 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4107,6 +4107,12 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   mlir::Location currentLocation =
       converter.genLocation(beginLoopDirective.source);
 
+  auto &optLoopCons = std::get<1>(loopConstruct.t);
+  if(optLoopCons.has_value())
+    if(auto *ompNestedLoopCons{std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(&*optLoopCons)}) {
+      genOMP(converter, symTable, semaCtx, eval, ompNestedLoopCons->value());
+    }
+
   llvm::omp::Directive directive =
       std::get<parser::OmpLoopDirective>(beginLoopDirective.t).v;
   const parser::CharBlock &source =
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index ed0f227fd5b98..276be878dd44e 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2926,7 +2926,7 @@ class UnparseVisitor {
     Walk(std::get<OmpBeginLoopDirective>(x.t));
     Put("\n");
     EndOpenMP();
-    Walk(std::get<std::optional<DoConstruct>>(x.t));
+    Walk(std::get<std::optional<std::variant<DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(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 5164f1dc6faab..d6827435173d3 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -8,6 +8,7 @@
 
 #include "canonicalize-omp.h"
 #include "flang/Parser/parse-tree-visitor.h"
+#include "flang/Parser/parse-tree.h"
 
 // After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
 // Constructs more structured which provide explicit scopes for later
@@ -106,6 +107,12 @@ class CanonicalizationOfOmp {
     return nullptr;
   }
 
+  void missingDoConstruct(parser::OmpLoopDirective &dir) {
+    messages_.Say(dir.source,
+      "A DO loop must follow the %s directive"_err_en_US,
+      parser::ToUpperCaseLetters(dir.source.ToString()));
+  }
+
   void RewriteOpenMPLoopConstruct(parser::OpenMPLoopConstruct &x,
       parser::Block &block, parser::Block::iterator it) {
     // Check the sequence of DoConstruct and OmpEndLoopDirective
@@ -135,31 +142,62 @@ class CanonicalizationOfOmp {
       if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
         if (doCons->GetLoopControl()) {
           // move DoConstruct
-          std::get<std::optional<parser::DoConstruct>>(x.t) =
+          std::get<std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t) =
               std::move(*doCons);
           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);
-              block.erase(nextIt);
-            }
+          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(dir.source,
               "DO loop after the %s directive must have loop control"_err_en_US,
               parser::ToUpperCaseLetters(dir.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 &beginDirective =
+            std::get<parser::OmpBeginLoopDirective>(ompLoopCons->t);
+        auto &beginLoopDirective =
+        std::get<parser::OmpLoopDirective>(beginDirective.t);
+        // 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 &endLoopDirective = std::get<parser::OmpLoopDirective>(endDir->t);
+              if(endLoopDirective.v == dir.v) {
+                std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+                std::move(*endDir);
+                endIt = block.erase(endIt);
+                continue;
+              }
+            }
+            ++endIt;
+          }
+        if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll ||
+            beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) {
+          RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
+          auto &ompLoop = std::get<std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t);
+          ompLoop = std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>{
+            std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>{
+            common::Indirection{std::move(*ompLoopCons)}}};
+          nextIt = block.erase(nextIt);
+        }
       } else {
-        messages_.Say(dir.source,
-            "A DO loop must follow the %s directive"_err_en_US,
-            parser::ToUpperCaseLetters(dir.source.ToString()));
+        missingDoConstruct(dir);
       }
       // If we get here, we either found a loop, or issued an error message.
       return;
     }
+    if (nextIt == block.end()) {
+      missingDoConstruct(dir);
+    }
   }
 
   void RewriteOmpAllocations(parser::ExecutionPart &body) {
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 36d4bcb5d99fa..4e71641ae3858 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -761,10 +761,13 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
   }
   SetLoopInfo(x);
 
-  if (const auto &doConstruct{
-          std::get<std::optional<parser::DoConstruct>>(x.t)}) {
-    const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
-    CheckNoBranching(doBlock, beginDir.v, beginDir.source);
+  auto &optLoopCons = std::get<1>(x.t);
+  if(optLoopCons.has_value()) {
+    if (const auto &doConstruct{
+            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+      const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
+      CheckNoBranching(doBlock, beginDir.v, beginDir.source);
+    }
   }
   CheckLoopItrVariableIsInt(x);
   CheckAssociatedLoopConstraints(x);
@@ -778,6 +781,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
       (beginDir.v == llvm::omp::Directive::OMPD_distribute_simd)) {
     CheckDistLinear(x);
   }
+  if (beginDir.v == llvm::omp::Directive::OMPD_tile) {
+    const auto &clauses{std::get<parser::OmpClauseList>(beginLoopDir.t)};
+    for (auto &clause : clauses.v) {
+
+    }
+  }
 }
 const parser::Name OmpStructureChecker::GetLoopIndex(
     const parser::DoConstruct *x) {
@@ -785,12 +794,15 @@ const parser::Name OmpStructureChecker::GetLoopIndex(
   return std::get<Bounds>(x->GetLoopControl()->u).name.thing;
 }
 void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
-  if (const auto &loopConstruct{
-          std::get<std::optional<parser::DoConstruct>>(x.t)}) {
-    const parser::DoConstruct *loop{&*loopConstruct};
-    if (loop && loop->IsDoNormal()) {
-      const parser::Name &itrVal{GetLoopIndex(loop)};
-      SetLoopIv(itrVal.symbol);
+  auto &optLoopCons = std::get<1>(x.t);
+  if (optLoopCons.has_value()) {
+    if (const auto &loopConstruct{
+            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+      const parser::DoConstruct *loop{&*loopConstruct};
+      if (loop && loop->IsDoNormal()) {
+        const parser::Name &itrVal{GetLoopIndex(loop)};
+        SetLoopIv(itrVal.symbol);
+      }
     }
   }
 }
@@ -856,8 +868,10 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) {
 
 void OmpStructureChecker::CheckLoopItrVariableIsInt(
     const parser::OpenMPLoopConstruct &x) {
-  if (const auto &loopConstruct{
-          std::get<std::optional<parser::DoConstruct>>(x.t)}) {
+  auto &optLoopCons = std::get<1>(x.t);
+  if (optLoopCons.has_value()) {
+    if (const auto &loopConstruct{
+          std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
 
     for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
       if (loop->IsDoNormal()) {
@@ -877,6 +891,7 @@ void OmpStructureChecker::CheckLoopItrVariableIsInt(
       const auto it{block.begin()};
       loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
                                : nullptr;
+      }
     }
   }
 }
@@ -1076,8 +1091,10 @@ void OmpStructureChecker::CheckDistLinear(
 
     // Match the loop index variables with the collected symbols from linear
     // clauses.
+    auto &optLoopCons = std::get<1>(x.t);
+    if (optLoopCons.has_value()) {
     if (const auto &loopConstruct{
-            std::get<std::optional<parser::DoConstruct>>(x.t)}) {
+            std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
       for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
         if (loop->IsDoNormal()) {
           const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -1095,6 +1112,7 @@ void OmpStructureChecker::CheckDistLinear(
         const auto it{block.begin()};
         loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
                                  : nullptr;
+        }
       }
     }
 
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 885c02e6ec74b..36bea4fbe7ae5 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1796,10 +1796,13 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
   SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList));
 
   if (beginDir.v == llvm::omp::Directive::OMPD_do) {
-    if (const auto &doConstruct{
-            std::get<std::optional<parser::DoConstruct>>(x.t)}) {
-      if (doConstruct.value().IsDoWhile()) {
-        return true;
+    auto &optLoopCons = std::get<1>(x.t);
+    if (optLoopCons.has_value()) {
+      if (const auto &doConstruct{
+              std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+        if (doConstruct->IsDoWhile()) {
+          return true;
+        }
       }
     }
   }
@@ -1962,48 +1965,64 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
   bool hasCollapseClause{
       clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false};
 
-  const auto &outer{std::get<std::optional<parser::DoConstruct>>(x.t)};
-  if (outer.has_value()) {
-    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);
+  auto &optLoopCons = std::get<1>(x.t);
+  if (optLoopCons.has_value()) {
+    if (const auto &outer{std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+      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);
           }
-        } 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);
         }
+        // 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>>(&*optLoopCons)}) {
+      auto &beginDirective =
+            std::get<parser::OmpBeginLoopDirective>(loop->value().t);
+      auto &beginLoopDirective =
+          std::get<parser::OmpLoopDirective>(beginDirective.t);
+      if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll &&
+            beginLoopDirective.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()));
+      } 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()));
     }
-    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()));
   }
 }
+
 void OmpAttributeVisitor::CheckAssocLoopLevel(
     std::int64_t level, const parser::OmpClause *clause) {
   if (clause && level != 0) {
diff --git a/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90 b/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90
new file mode 100644
index 0000000000000..b24e2d62ac2aa
--- /dev/null
+++ b/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90
@@ -0,0 +1,20 @@
+! Test to ensure TODO message is emitted for tile OpenMP 5.1 Directives when they are nested.
+
+!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2<&1 | FileCheck %s
+
+subroutine loop_transformation_construct
+  implicit none
+  integer :: I = 10
+  integer :: x
+  integer :: y(I)
+
+  !$omp do
+  !$omp tile
+  do i = 1, I
+    y(i) = y(i) * 5
+  end do
+  !$omp end tile
+  !$omp end do
+end subroutine
+
+!CHECK: not yet implemented: Unhandled loop directive (tile)
diff --git a/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90 b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
new file mode 100644
index 0000000000000..bbb16a6a699e9
--- /dev/null
+++ b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
@@ -0,0 +1,20 @@
+! Test to ensure TODO message is emitted for unroll OpenMP 5.1 Directives when they are nested.
+
+!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2<&1 | FileCheck %s
+
+program loop_transformation_construct
+  implicit none
+  integer, parameter :: I = 10
+  integer :: x
+  integer :: y(I)
+
+  !$omp do
+  !$omp unroll
+  do x = 1, I
+    y(x) = y(x) * 5
+  end do
+  !$omp end unroll
+  !$omp end do
+end program loop_transformation_construct
+
+!CHECK: not yet implemented: Unhandled loop directive (unroll)
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct01.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
new file mode 100644
index 0000000000000..baffc2f6e2f1e
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
@@ -0,0 +1,74 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Constructs nest correctly with 1 nested loop.
+
+! 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 :: x
+  integer :: y(I)
+
+  !$omp do
+  !$omp unroll
+  do i = 1, I
+    y(i) = y(i) * 5
+  end do
+  !$omp end unroll
+  !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpLoopDirective -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpLoopDirective -> llvm::omp::Directive = unroll
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!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 -> AssignmentStmt = 'y(int(i,kind=8))=5_4*y(int(i,kind=8))'
+!CHECK-PARSE-NEXT: | | | | | | | Variable = 'y(int(i,kind=8))'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> ArrayElement
+!CHECK-PARSE-NEXT: | | | | | | | | | DataRef -> Name = 'y'
+!CHECK-PARSE-NEXT: | | | | | | | | | SectionSubscript -> Integer -> Expr = 'i'
+!CHECK-PARSE-NEXT: |...
[truncated]

Copy link

github-actions bot commented Jun 26, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@jsjodin
Copy link
Contributor

jsjodin commented Jun 26, 2025

I have a question about nested loop constructs and checking the legality of combining them. For example doing full unrolling and then tiling would be illegal since if a loop is unrolled there is nothing to tile. Another example that I ran into is collapse and tiling, since collapse assumes independent loops, but only the outer loops after tiling are independent. At some point in the front-end there has to be a checker that makes sure that the nested loop constructs are legal and can report meaningful error messages if they are not. Do you have any ideas how this can be done? Maybe a separate pass would be required that somehow tracks certain properties about the resulting loops that are created from a transform?

Comment on lines 169 to 176
if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll ||
beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many parentheses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -4107,6 +4107,14 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation =
converter.genLocation(beginLoopDirective.source);

auto &optLoopCons = std::get<1>(loopConstruct.t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please spell out the type in <>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 5029 to 5030
std::optional<
std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add a type alias for this, like

using NestedConstruct = std::variant<...>;
std::tuple<OmpBeginLoopDirective, std::optional<NestedConstruct>, ...

The full thing is spelled out in several places, it would make the code more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

std::get<std::optional<parser::DoConstruct>>(x.t)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginDir.v, beginDir.source);
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please put the type name in <>. It's longer, but it makes it a bit easier to read, especially since you have auto in the declaration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
SetLoopIv(itrVal.symbol);
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOne

@@ -856,27 +862,30 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) {

void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
if (const auto &loopConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

In OpenMP Version 5.1, the tile and unroll directives were added. When
using these directives, it is possible to nest them within other OpenMP
Loop Constructs. This patch enables the semantics to allow for this
behaviour on these specific directives. Any nested loops will be stored
within the initial Loop Construct until reaching the DoConstruct itself.

Relevant tests have been added, and previous behaviour has been retained
with no changes.

See also, llvm#110008
@Stylie777 Stylie777 force-pushed the loop_transformation_constructs branch from e075bfb to fec9503 Compare June 27, 2025 09:23
Copy link
Contributor Author

@Stylie777 Stylie777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review comments - these have now been addressed

Comment on lines 5029 to 5030
std::optional<
std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -4107,6 +4107,14 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation =
converter.genLocation(beginLoopDirective.source);

auto &optLoopCons = std::get<1>(loopConstruct.t);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 169 to 176
if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll ||
beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

std::get<std::optional<parser::DoConstruct>>(x.t)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginDir.v, beginDir.source);
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
SetLoopIv(itrVal.symbol);
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOne

@@ -856,27 +862,30 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) {

void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
if (const auto &loopConstruct{
std::get<std::optional<parser::DoConstruct>>(x.t)}) {
auto &optLoopCons = std::get<1>(x.t);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@Stylie777
Copy link
Contributor Author

@jsjodin Regarding your comment. This is not something I had considered. I could look at adding a check here? If we have an unroll followed by a tile there may be enough information available to give an error as both OpenMPLoopConstruct's will be in scope for that function call when processing the tile. The only question would be around if it can be determined whether the unroll is full or partial. If this is not the case, then it cannot be dealt with at the canonicalise-omp stage. I think this is possible but I will need to investigate this.

With the second example you have given, I am not sure if this can be dealt with here and that might require a new pass to check this. I will take a look and see whats possible in the scope canonicalise-omp has, but it may be too limiting.

I suspect the first point is possible, the second may need some level of processing in the lowering.

Admittedly my experience with Flang is limited, having only begun working on this part of the LLVM Project 4 weeks ago, so if others have other ideas please bring them forward.

@jsjodin
Copy link
Contributor

jsjodin commented Jun 27, 2025

I think having the checking done in a separate pass makes sense and the same module could also provide utility functions that could be used for codegen. The problem I had to handle was the combination of collapse and tile and calculate how many loops that need considered to be combined into a single loop nest op. That happened in a couple of places in the code. Another issue is restrictions on what these transforms can do in the OpenMPIRBuilder. What is legal or not may vary over time, so having a central place where this is encoded would be useful. Another question is if the checking should implemented in LLVM similar to how directive and clause handling is done? The same problem has to be solved for Clang and maybe MLIR depending on other possible front-ends.

Edit: I think for now if there is a simple check that can be added in your PR you might want to do that, but if it is complicated it should probably be done in a separate PR.

@Stylie777
Copy link
Contributor Author

Stylie777 commented Jun 27, 2025

@jsjodin
So I have managed to implement a check for tiling after unrolling and not allowing it - I will update the PR with this.

I think for other cases where it is more involved, a new pass/PR is more sensible. I don't think solving this in the backend is a bad idea, because then as you pointed out it would deal with MLIR/Clang (and any other frontend that uses llvm). I suspect the new pass could be implemented there and it may remove the need for some of the checks we are doing currently in the frontend of clang. Either way, it's a subject for another PR.

During review, it was noted that tiling after unrolling should not
be possible, so a semantics check is now implemented for this. This
also refactors the error messages to be lambda functions where an
error message can be called from multiple places.
@tblah
Copy link
Contributor

tblah commented Jun 27, 2025

I don't know of a way to generate proper diagnostic messages after semantics. Obviously we can print to stderr and stop the compiler but we loose all of the usual formatting and printing of source code in standard error messages. I think this needs to be done in flang semantics if at all possible (possibly using utilities shared with other places).

@@ -125,6 +126,16 @@ class CanonicalizationOfOmp {
parser::Block::iterator nextIt;
auto &beginDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
auto &dir{std::get<parser::OmpLoopDirective>(beginDir.t)};
auto missingDoConstruct = [](auto &dir, auto &messages_) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameters shouldn't have a trailing underscore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

"A DO loop must follow the %s directive"_err_en_US,
parser::ToUpperCaseLetters(dir.source.ToString()));
};
auto tileUnrollError = [](auto &dir, auto &messages_) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -4231,6 +4231,15 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation =
converter.genLocation(beginLoopDirective.source);

auto &optLoopCons =
std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t);
if (optLoopCons.has_value())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add braces here. This is the common convention even for single statements when the statement contains its own sub-statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 2011 to 2012
if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll &&
beginLoopDirective.v != llvm::omp::Directive::OMPD_tile)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra parentheses here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - apologies did not spot this.

Copy link
Contributor

@kparzysz kparzysz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor Author

@Stylie777 Stylie777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kparzysz

@@ -4231,6 +4231,15 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation =
converter.genLocation(beginLoopDirective.source);

auto &optLoopCons =
std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t);
if (optLoopCons.has_value())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -125,6 +126,16 @@ class CanonicalizationOfOmp {
parser::Block::iterator nextIt;
auto &beginDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
auto &dir{std::get<parser::OmpLoopDirective>(beginDir.t)};
auto missingDoConstruct = [](auto &dir, auto &messages_) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

"A DO loop must follow the %s directive"_err_en_US,
parser::ToUpperCaseLetters(dir.source.ToString()));
};
auto tileUnrollError = [](auto &dir, auto &messages_) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 2011 to 2012
if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll &&
beginLoopDirective.v != llvm::omp::Directive::OMPD_tile)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - apologies did not spot this.

Copy link
Contributor

@tblah tblah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@Stylie777 Stylie777 merged commit 65cb0ea into llvm:main Jul 1, 2025
7 checks passed
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
…llvm#145917)

In OpenMP Version 5.1, the tile and unroll directives were added. When
using these directives, it is possible to nest them within other OpenMP
Loop Constructs. This patch enables the semantics to allow for this
behaviour on these specific directives. Any nested loops will be stored
within the initial Loop Construct until reaching the DoConstruct itself.

Relevant tests have been added, and previous behaviour has been retained
with no changes.

See also, llvm#110008
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
…llvm#145917)

In OpenMP Version 5.1, the tile and unroll directives were added. When
using these directives, it is possible to nest them within other OpenMP
Loop Constructs. This patch enables the semantics to allow for this
behaviour on these specific directives. Any nested loops will be stored
within the initial Loop Construct until reaching the DoConstruct itself.

Relevant tests have been added, and previous behaviour has been retained
with no changes.

See also, llvm#110008
@ronlieb
Copy link
Contributor

ronlieb commented Jul 6, 2025

seeing random 5 out of 30 compilations fail

"flang" "-fc1" "-triple" "x86_64-unknown-linux-gnu" "-emit-llvm-bc" "-mrelocation-model" "static" "-ffast-math" "-target-cpu" "x86-64" "-floop-interchange" "-vectorize-loops" "-vectorize-slp" "-fversion-loops-for-stride" "-fopenmp" "-fopenmp-targets=amdgcn-amd-amdhsa" "-mframe-pointer=none" "-O3" "-x" "f95" "update_halo_kernel-gfx90a-8cde79"

#4 0x000014937e341abe Fortran::semantics::CanonicalizationOfOmp::RewriteOpenMPLoopConstruct(Fortran::parser::OpenMPLoopConstruct&, std
::__cxx11::list<Fortran::parser::ExecutionPartConstruct, std::allocatorFortran::parser::ExecutionPartConstruct>&, std::_List_iterator
Fortran::parser::ExecutionPartConstruct) (/mnt/COD/2025-07-06/trunk_21.0-0/bin/../lib/../lib/libFortranSemantics.so.21.0git+0x1a8abe)
#5 0x000014937e342040 Fortran::semantics::CanonicalizationOfOmp::Post(std::__cxx11::list<Fortran::parser::ExecutionPartConstruct, std:
:allocatorFortran::parser::ExecutionPartConstruct>&) (/mnt/COD/2025-07-06/trunk_21.0-0/bin/../lib/../lib/libFortranSemantics.so.21.0g
it+0x1a9040)
#6 0x000014937e35151d void Fortran::parser::detail::ParseTreeVisitorLookupScope::WalkFortran::semantics::CanonicalizationOfOmp(std::
__cxx11::list<Fortran::parser::ExecutionPartConstruct, std::allocatorFortran::parser::ExecutionPartConstruct>&, Fortran::semantics::C
anonicalizationOfOmp&) (/mnt/COD/2025-07-06/trunk_21.0-0/bin/../lib/../lib/libFortranSemantics.so.21.0git+0x1b851d)
#7 0x000014937e35151d void Fortran::parser::detail::ParseTreeVisitorLookupScope::WalkFortran::semantics::CanonicalizationOfOmp(std::
__cxx11::list<Fortran::parser::ExecutionPartConstruct, std::allocatorFortran::parser::ExecutionPartConstruct>&, Fortran::semantics::C
anonicalizationOfOmp&) (/mnt/COD/2025-07-06/trunk_21.0-0/bin/../lib/../lib/libFortranSemantics.so.21.0git+0x1b851d)

@Stylie777
Copy link
Contributor Author

Stylie777 commented Jul 7, 2025

Hi @ronlieb,
Can you provide a fortran reproducer for this issue? It's not something I hit when working on this and the output you've given does not indicate where in the function this is crashing.
Thanks.

EDIT: Using the CMD Line options you provided I cannot reproduce this crash using the test files I created for developing this feature. A Reproducer would be very handy in this case to try and locate any potential issues.

@ronlieb
Copy link
Contributor

ronlieb commented Jul 7, 2025

hi @Stylie777 did you try running the compilation 30 times ? its very intermittent for me

@ronlieb ronlieb self-requested a review July 7, 2025 12:11
@Stylie777
Copy link
Contributor Author

Stylie777 commented Jul 7, 2025

@ronlieb
I did yes, I ran it 30 times against a test file(s) I have and all ended in errors stating that tile and unroll are unimplemented which is expected behviour. The CMD line is the same as you posted above, with the only change being the target file.

This is done with a build based on 1c8283a30c6e87640510f523dfc244bd9d2b991a from this morning.

Are you seeing this error on a pure LLVM/Flang build or with other changes applied?

@kparzysz
Copy link
Contributor

kparzysz commented Jul 7, 2025

This line sets nextIt to block.end(), which makes the next if load some indeterminate value. I'm trying to see if I can come up with a small testcase.

@kparzysz
Copy link
Contributor

kparzysz commented Jul 7, 2025

subroutine fred(x_min, x_max, a)
implicit none

integer :: x_min, x_max
real(kind=8), dimension(x_min:x_max, 2) :: a
integer :: i, j

!$omp target teams distribute parallel do collapse(2) private(i)
do i = x_min, x_max
  a(i, 1) = a(i, 0)
enddo

end

Command:

flang -fc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -fopenmp -O3 h.f90

@Stylie777
Copy link
Contributor Author

Thanks @kparzysz I will take a look.

@Stylie777
Copy link
Contributor Author

Apologies @kparzysz @ronlieb I am so far unable to reproduce this issue. I ran the reproducer with the command provided 120 times and it has not crashed.

I agree that where it sets nextIt could be an issue, and a check should be added here that if the end of the block has been reached, the process returns. Something like this after each time block.erase is used.

if (nextIt == block.end()) {
  return;
}

Is there any other changes you have applied ontop of mine that could be causing this?

@kparzysz
Copy link
Contributor

kparzysz commented Jul 8, 2025

Add assert(nextIt != block.end()) after the line I showed and it should fail with the short testcase.

I'm pretty sure that all other cases where nextIt changes go back to the beginning of the loop where nextIt != block.end() is checked. In this one case I wrapped the following "if" in "if (nextIt != block.end())" and that fixed the problem.

@DavidSpickett
Copy link
Collaborator

I think this is the cause of #147309.

On Windows on Arm a few tests fail pretty consistently, and the backtrace points here. @kparzysz's assessment seems to be correct.

@Stylie777
Copy link
Contributor Author

Ok, I will post a fix up or review and tag you both. Thanks all.

@DavidSpickett
Copy link
Collaborator

Apologies @kparzysz @ronlieb I am so far unable to reproduce this issue. I ran the reproducer with the command provided 120 times and it has not crashed.

I also hammered the tests on AArch64 Ubuntu Linux and never found a failure but it would fail within a few runs on Windows on Arm. So there is some unknown factor, perhaps memory allocation if the list has to reallocate as a result of the erase.

@DavidSpickett
Copy link
Collaborator

In this one case I wrapped the following "if" in "if (nextIt != block.end())" and that fixed the problem.

On Windows on Arm, the following hack passes all tests and hammering the specific tests produced no failures:

diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index 1edcb376596b..f33d161d6870 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -151,12 +151,14 @@ private:
               std::move(*doCons);
           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(dir.source,
               "DO loop after the %s directive must have loop control"_err_en_US,

@Stylie777
Copy link
Contributor Author

I also hammered the tests on AArch64 Ubuntu Linux and never found a failure but it would fail within a few runs on Windows on Arm.

I tested it on X86 Ubuntu and found the same. I don't have access to Windows on Arm but that may explain why I could not reproduce the issue.

Stylie777 added a commit to Stylie777/llvm-project that referenced this pull request Jul 8, 2025
As reported in llvm#145917 and llvm#147309, there are situation's where
flang may crash. This is because `nextIt` in
`RewriteOpenMPLoopConstruct` gets re-assigned when an iterator is
erased from the block. If this is missed, Flang may attempt to access
a location in memory that is not accessable and cause a compiler crash.

This adds protection where the crash can occur, and a test with a
reproducer that can trigger the crash.

Fixes llvm#147309
@Stylie777
Copy link
Contributor Author

#147519

Fix is up for review. Apologies everyone for the disruption and thanks for the collaboration.

kparzysz pushed a commit that referenced this pull request Jul 8, 2025
As reported in #145917 and #147309, there are situation's where flang
may crash. This is because `nextIt` in
`RewriteOpenMPLoopConstruct` gets re-assigned when an iterator is erased
from the block. If this is missed, Flang may attempt to access a
location in memory that is not accessable and cause a compiler crash.

This adds protection where the crash can occur, and a test with a
reproducer that can trigger the crash.

Fixes #147309
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants