[flang-commits] [flang] 8593524 - [flang][semantics][openacc] Allow collapse clauses on do concurrent (#192488)

via flang-commits flang-commits at lists.llvm.org
Mon Apr 27 07:42:33 PDT 2026


Author: Andre Kuhlenschmidt
Date: 2026-04-27T07:42:28-07:00
New Revision: 85935241b74f332a2d8c616510b7ef74ebe6a1ac

URL: https://github.com/llvm/llvm-project/commit/85935241b74f332a2d8c616510b7ef74ebe6a1ac
DIFF: https://github.com/llvm/llvm-project/commit/85935241b74f332a2d8c616510b7ef74ebe6a1ac.diff

LOG: [flang][semantics][openacc] Allow collapse clauses on do concurrent (#192488)

This PR generalizes the semantic checking for collapse clauses to work
on `do concurrent` and fixes two bugs exposed along the way:
- The first was that `collapse (n)` where n < the number of nested loops
was giving an assertion violation.
- The second was do concurrent index variables were causing an assertion
violation because they hadn't been declared before looking them up.

The lowering is implemented as a TODO which will happen in a following
diff.

Added: 
    

Modified: 
    flang/lib/Lower/OpenACC.cpp
    flang/lib/Semantics/canonicalize-acc.cpp
    flang/lib/Semantics/resolve-directives.cpp
    flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
    flang/test/Semantics/OpenACC/acc-canonicalization-validity.f90
    flang/test/Semantics/OpenACC/acc-loop.f90

Removed: 
    


################################################################################
diff  --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 48d59bd70a853..bcb77fdfd52b1 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -1538,9 +1538,11 @@ static void visitLoopControl(
         break; // No deeper loop; stop collecting collapsed bounds.
 
       Fortran::lower::markDoConstructAsCollapsed(*innerDo);
-      loopControl = &*innerDo->GetLoopControl();
       mlir::Location loc =
           converter.genLocation(Fortran::parser::FindSourceLocation(*innerDo));
+      if (innerDo->IsDoConcurrent())
+        TODO(loc, "OpenACC LOOP with nested DO CONCURRENT");
+      loopControl = &*innerDo->GetLoopControl();
       callback(std::get<Fortran::parser::LoopControl::Bounds>(loopControl->u),
                loc);
     }
@@ -1917,7 +1919,7 @@ static void privatizeInductionVariables(
   llvm::SmallVector<mlir::Type> ivTypes;
   llvm::SmallVector<mlir::Location> ivLocs;
   assert(!outerDoConstruct.IsDoConcurrent() &&
-         "do concurrent loops are not expected to contained earlty exits");
+         "do concurrent loops are not expected to contained early exits");
   visitLoopControl(converter, outerDoConstruct, loopsToProcess, eval,
                    [&](const Fortran::parser::LoopControl::Bounds &bounds,
                        mlir::Location loc) {
@@ -2237,6 +2239,11 @@ static mlir::acc::LoopOp createLoopOp(
 
   uint64_t loopsToProcess =
       Fortran::lower::getLoopCountForCollapseAndTile(accClauseList);
+
+  if (outerDoConstruct.IsDoConcurrent() &&
+      Fortran::lower::getCollapseSizeAndForce(accClauseList).first > 1)
+    TODO(currentLocation, "OpenACC LOOP COLLAPSE with DO CONCURRENT");
+
   auto loopOp = buildACCLoopOp(
       converter, currentLocation, semanticsContext, stmtCtx, outerDoConstruct,
       eval, privateOperands, dataMap, gangOperands, workerNumOperands,

diff  --git a/flang/lib/Semantics/canonicalize-acc.cpp b/flang/lib/Semantics/canonicalize-acc.cpp
index 9d2d2ce3a82fb..b878b500963fa 100644
--- a/flang/lib/Semantics/canonicalize-acc.cpp
+++ b/flang/lib/Semantics/canonicalize-acc.cpp
@@ -108,11 +108,6 @@ class CanonicalizationOfAcc {
             "TILE clause may not appear on loop construct "
             "associated with DO CONCURRENT"_err_en_US);
       }
-      if (std::holds_alternative<parser::AccClause::Collapse>(clause.u)) {
-        messages_.Say(beginLoopDirective.source,
-            "COLLAPSE clause may not appear on loop construct "
-            "associated with DO CONCURRENT"_err_en_US);
-      }
     }
   }
 

diff  --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index fa6a957ee3831..f54a73322ab96 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1677,34 +1677,68 @@ void AccAttributeVisitor::CheckAssociatedLoop(
 
   Symbol::Flag flag = Symbol::Flag::AccPrivate;
   llvm::SmallVector<Symbol *> ivs;
-  using Bounds = parser::LoopControl::Bounds;
+
+  // Iterate the index variables of one DoConstruct, calling fn(name, lower,
+  // upper) for each: once for a regular do loop, once per control variable for
+  // a do concurrent loop.  Null pointers signal a loop without valid bounds
+  // (e.g. do while); the level must still be consumed.
+  auto forEachIndex = [this](const parser::DoConstruct &loop, auto &&fn) {
+    if (loop.IsDoConcurrent()) {
+      const auto &loopControl{*loop.GetLoopControl()};
+      const auto &concurrent{
+          std::get<parser::LoopControl::Concurrent>(loopControl.u)};
+      const auto &header{std::get<parser::ConcurrentHeader>(concurrent.t)};
+      for (const auto &control :
+          std::get<std::list<parser::ConcurrentControl>>(header.t)) {
+        fn(&std::get<parser::Name>(control.t),
+            &parser::UnwrapRef<parser::Expr>(std::get<1>(control.t)),
+            &parser::UnwrapRef<parser::Expr>(std::get<2>(control.t)));
+      }
+    } else {
+      auto bounds{GetLoopBounds(loop)};
+      const parser::ScalarExpr *lower{std::get<1>(bounds)};
+      const parser::ScalarExpr *upper{std::get<2>(bounds)};
+      fn(std::get<0>(bounds),
+          lower ? &parser::UnwrapRef<parser::Expr>(*lower) : nullptr,
+          upper ? &parser::UnwrapRef<parser::Expr>(*upper) : nullptr);
+    }
+  };
+
   for (const parser::DoConstruct *loop{&outerDoConstruct}; loop && level > 0;) {
-    // Go through all nested loops to ensure index variable exists.
-    if (const parser::Name *ivName{GetLoopIndex(*loop)}) {
-      if (auto *symbol{ResolveAcc(*ivName, flag, currScope())}) {
-        if (auto &control{loop->GetLoopControl()}) {
-          if (const Bounds *b{std::get_if<Bounds>(&control->u)}) {
-            if (auto lowerExpr{semantics::AnalyzeExpr(context_, b->Lower())}) {
-              semantics::UnorderedSymbolSet lowerSyms =
-                  evaluate::CollectSymbols(*lowerExpr);
-              checkExprHasSymbols(ivs, lowerSyms);
-            }
-            if (auto upperExpr{semantics::AnalyzeExpr(context_, b->Upper())}) {
-              semantics::UnorderedSymbolSet upperSyms =
-                  evaluate::CollectSymbols(*upperExpr);
-              checkExprHasSymbols(ivs, upperSyms);
+    forEachIndex(*loop,
+        [&](const parser::Name *ivName, const parser::Expr *lower,
+            const parser::Expr *upper) {
+          if (level <= 0)
+            return;
+          if (ivName && lower && upper) {
+            if (auto *symbol{ResolveAcc(*ivName, flag, currScope())}) {
+              if (auto lowerExpr{semantics::AnalyzeExpr(context_, *lower)}) {
+                semantics::UnorderedSymbolSet lowerSyms =
+                    evaluate::CollectSymbols(*lowerExpr);
+                checkExprHasSymbols(ivs, lowerSyms);
+              }
+              if (auto upperExpr{semantics::AnalyzeExpr(context_, *upper)}) {
+                semantics::UnorderedSymbolSet upperSyms =
+                    evaluate::CollectSymbols(*upperExpr);
+                checkExprHasSymbols(ivs, upperSyms);
+              }
+              ivs.push_back(symbol);
             }
           }
-        }
-        ivs.push_back(symbol);
-      }
-    }
+          --level;
+        });
 
     const auto &block{std::get<parser::Block>(loop->t)};
-    --level;
     loop = getNextDoConstruct(block, level);
   }
-  CHECK(level == 0);
+
+  if (level != 0) {
+    context_.Say(GetContext().directiveSource,
+        "Not enough %s for COLLAPSE(%jd) clause, found %jd, expected %jd more"_err_en_US,
+        forceCollapsed ? "nested loops" : "perfectly nested loops",
+        GetContext().associatedLoopLevel,
+        GetContext().associatedLoopLevel - level, level);
+  }
 }
 
 void AccAttributeVisitor::EnsureAllocatableOrPointer(

diff  --git a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90 b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
index 3f2b77a9a1484..aa293642b0865 100644
--- a/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
+++ b/flang/test/Lower/OpenACC/Todo/do-loops-to-acc-loops-todo.f90
@@ -3,6 +3,8 @@
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/do_loop_with_cycle_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK2
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_goto_loop.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK3
 ! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/nested_loop_with_inner_goto.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK4
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK5
+! RUN: %not_todo_cmd bbc -fopenacc -emit-hlfir %t/collapse_nested.f90 -o - 2>&1 | FileCheck %s --check-prefix=CHECK6
 
 //--- do_loop_with_stop.f90
 
@@ -89,3 +91,34 @@ subroutine nested_loop_with_inner_goto()
 ! CHECK4: not yet implemented: unstructured do loop in acc kernels
 
 end subroutine
+
+//--- collapse.f90
+
+! !$acc parallel loop collapse(N) over a do concurrent.
+subroutine combined(i, j, k)
+  integer :: i, j, k
+  integer :: a(i,j,k)
+  !$acc parallel loop collapse(3)
+  do concurrent (i=1:10, j=1:100, k=1:200)
+    a(i,j,k) = a(i,j,k) + 1
+  end do
+  ! CHECK5: not yet implemented: OpenACC LOOP COLLAPSE with DO CONCURRENT
+end subroutine
+
+
+//--- collapse_nested.f90
+
+! !$acc parallel loop collapse(N) over a nested do concurrent.
+subroutine combined(i, j, k)
+  integer :: i, j, k
+  integer :: a(i,j,k)
+  !$acc parallel loop collapse(3)
+  do i = 1, 10
+    do concurrent (j=1:100, k=1:200)
+      a(i,j,k) = a(i,j,k) + 1
+    end do
+  end do
+  ! CHECK6: not yet implemented: OpenACC LOOP with nested DO CONCURRENT
+
+end subroutine
+

diff  --git a/flang/test/Semantics/OpenACC/acc-canonicalization-validity.f90 b/flang/test/Semantics/OpenACC/acc-canonicalization-validity.f90
index a92be44c60b74..3151d726380f1 100644
--- a/flang/test/Semantics/OpenACC/acc-canonicalization-validity.f90
+++ b/flang/test/Semantics/OpenACC/acc-canonicalization-validity.f90
@@ -85,7 +85,6 @@ program openacc_clause_validity
   end do
 
   !$acc parallel
-  !ERROR: COLLAPSE clause may not appear on loop construct associated with DO CONCURRENT
   !$acc loop collapse(2)
   do concurrent (i = 1:N, j = 1:N)
     aa(i, j) = 3.14
@@ -102,7 +101,6 @@ program openacc_clause_validity
 
   !$acc parallel
   !ERROR: TILE clause may not appear on loop construct associated with DO CONCURRENT
-  !ERROR: COLLAPSE clause may not appear on loop construct associated with DO CONCURRENT
   !$acc loop tile(2, 2) collapse(2)
   do concurrent (i = 1:N, j = 1:N)
     aa(i, j) = 3.14

diff  --git a/flang/test/Semantics/OpenACC/acc-loop.f90 b/flang/test/Semantics/OpenACC/acc-loop.f90
index 635dbb04cd666..0358b2fa4e1c6 100644
--- a/flang/test/Semantics/OpenACC/acc-loop.f90
+++ b/flang/test/Semantics/OpenACC/acc-loop.f90
@@ -447,6 +447,97 @@ program openacc_loop_validity
     END DO
   END DO
 
+  ! do concurrent: each index variable counts as one collapse level.
+
+  ! Valid: collapse(2) covers both indices of a 2-index do concurrent.
+  !$acc loop collapse(2)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    aa(i, j) = 3.14d0
+  END DO
+
+  ! Valid: collapse(3) covers both concurrent indices then one nested do.
+  !$acc loop collapse(3)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    DO k = 1, n
+      aa(i, j) = aa(i, j) + a(k)
+    END DO
+  END DO
+
+  ! Valid: collapse(2) with single-index do concurrent followed by a nested do.
+  !$acc loop collapse(2)
+  DO CONCURRENT (i = 1:n)
+    DO j = 1, n
+      aa(i, j) = 3.14d0
+    END DO
+  END DO
+
+  ! Valid: combined directive, collapse(2) with do concurrent.
+  !$acc parallel loop collapse(2)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    aa(i, j) = 3.14d0
+  END DO
+
+  ! Valid: outer regular do followed by inner do concurrent covering the
+  ! remaining collapse levels.
+  !$acc loop collapse(3)
+  DO i = 1, n
+    DO CONCURRENT (j = 1:n, k = 1:n)
+      aa(i, j) = aa(i, j) + a(k)
+    END DO
+  END DO
+
+  ! Valid (more concurrent indices than collapse levels): collapse(2) consumes
+  ! only the first two indices of a 3-index do concurrent; the third is outside
+  ! the collapsed nest.
+  !$acc loop collapse(2)
+  DO CONCURRENT (i = 1:n, j = 1:n, k = 1:n)
+    aa(i, j) = aa(i, j) + a(k)
+  END DO
+
+  ! Valid (more loops than collapse levels): collapse(1) consumes only the
+  ! first index of a 2-index do concurrent; the second index is outside the
+  ! collapsed nest.
+  !$acc loop collapse(1)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    aa(i, j) = 3.14d0
+  END DO
+
+  ! Invalid: nested do's upper bound depends on a collapsed concurrent index.
+  !ERROR: Trip count must be computable and invariant
+  !$acc loop collapse(3)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    DO k = 1, i
+      aa(i, j) = aa(i, j) + a(k)
+    END DO
+  END DO
+
+  ! Invalid: nested do's upper bound depends on a collapsed concurrent index.
+  !ERROR: Trip count must be computable and invariant
+  !$acc loop collapse(2)
+  DO CONCURRENT (i = 1:n)
+    DO j = 1, i
+      aa(i, j) = 3.14d0
+    END DO
+  END DO
+
+  ! Invalid: inner concurrent index bound depends on the outer collapsed regular
+  ! do index.
+  !ERROR: Trip count must be computable and invariant
+  !$acc loop collapse(3)
+  DO i = 1, n
+    DO CONCURRENT (j = 1:n, k = 1:i)
+      aa(i, j) = aa(i, j) + a(k)
+    END DO
+  END DO
+
+  ! Fewer loops than collapse(n): collapse(3) but only 2 levels exist.
+  ! This exercises the loop-nest depth check.
+  !ERROR: Not enough perfectly nested loops for COLLAPSE(3) clause, found 2, expected 1 more
+  !$acc loop collapse(3)
+  DO CONCURRENT (i = 1:n, j = 1:n)
+    aa(i, j) = 3.14d0
+  END DO
+
 contains
 
   subroutine sub1()


        


More information about the flang-commits mailing list