Skip to content

Conversation

khaki3
Copy link
Contributor

@khaki3 khaki3 commented Aug 5, 2025

This PR implements a semantic checker to ensure the legality of nested OpenACC parallelism. The following are quotes from Spec 3.3. We need to disallow loops from having parallelism at the same level as or at a sub-level of child loops.

2.9.2 gang clause
[2064] When the parent compute construct is a parallel construct, or on an orphaned loop construct, the gang clause behaves as follows. (...) The associated dimension is the value of the dim argument, if it appears, or is dimension one. The dim argument must be a constant positive integer with value 1, 2, or 3.
[2112] The region of a loop with a gang(dim:d) clause may not contain a loop construct with a gang(dim:e) clause where e >= d unless it appears within a nested compute region.

[2074] When the parent compute construct is a kernels construct, the gang clause behaves as follows. (...)
[2148] The region of a loop with the gang clause may not contain another loop with a gang clause unless within a nested compute region.

2.9.3 worker clause
[2122]/[2129] The region of a loop with the worker clause may not contain a loop with the gang or worker clause unless within a nested compute region.

2.9.4 vector clause
[2141]/[2148] The region of a loop with the vector clause may not contain a loop with a gang, worker, or vector clause unless within a nested compute region.

https://openacc.org/sites/default/files/inline-images/Specification/OpenACC-3.3-final.pdf

@llvmbot llvmbot added flang Flang issues not falling into any other category openacc flang:semantics labels Aug 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 5, 2025

@llvm/pr-subscribers-openacc

Author: None (khaki3)

Changes

This PR implements a semantic checker to ensure the legality of nested OpenACC parallelism. The following are quotes from Spec 3.3. We need to disallow loops from having parallelism at the same level as or at a sub-level of child loops.

>2.9.2 gang clause
>[2064] <ins>When the parent compute construct is a parallel construct</ins>, or on an orphaned loop construct, the gang clause behaves as follows. (...) The associated dimension is the value of the dim argument, if it appears, or is dimension one. The dim argument must be a constant positive integer with value 1, 2, or 3.
>[2112] The region of a loop with a gang(dim:d) clause may not contain a loop construct with a gang(dim:e) clause where e >= d unless it appears within a nested compute region.

>[2074] <ins>When the parent compute construct is a kernels construct</ins>, the gang clause behaves as follows. (...)
>[2148] The region of a loop with the gang clause may not contain another loop with a gang clause unless within a nested compute region.

>2.9.3 worker clause
>[2122]/[2129] The region of a loop with the worker clause may not contain a loop with the gang or worker clause unless within a nested compute region.

>2.9.4 vector clause
>[2141]/[2148] The region of a loop with the vector clause may not contain a loop with a gang, worker, or vector clause unless within a nested compute region.

https://openacc.org/sites/default/files/inline-images/Specification/OpenACC-3.3-final.pdf


Full diff: https://github.com/llvm/llvm-project/pull/152225.diff

4 Files Affected:

  • (modified) flang/lib/Semantics/check-acc-structure.cpp (+119-8)
  • (modified) flang/lib/Semantics/check-acc-structure.h (+6)
  • (modified) flang/lib/Semantics/check-directive-structure.h (+1-1)
  • (modified) flang/test/Semantics/OpenACC/acc-loop.f90 (+93-1)
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index 77e2b01578641..ebfaa01660523 100644
--- a/flang/lib/Semantics/check-acc-structure.cpp
+++ b/flang/lib/Semantics/check-acc-structure.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 #include "check-acc-structure.h"
+#include "resolve-names-utils.h"
 #include "flang/Common/enum-set.h"
 #include "flang/Evaluate/tools.h"
 #include "flang/Parser/parse-tree.h"
@@ -106,18 +107,27 @@ bool AccStructureChecker::IsComputeConstruct(
       directive == llvm::acc::ACCD_kernels_loop;
 }
 
-bool AccStructureChecker::IsInsideComputeConstruct() const {
-  if (dirContext_.size() <= 1) {
-    return false;
-  }
+bool AccStructureChecker::IsLoopConstruct(
+    llvm::acc::Directive directive) const {
+  return directive == llvm::acc::Directive::ACCD_loop ||
+      directive == llvm::acc::ACCD_parallel_loop ||
+      directive == llvm::acc::ACCD_serial_loop ||
+      directive == llvm::acc::ACCD_kernels_loop;
+}
 
+std::optional<llvm::acc::Directive>
+AccStructureChecker::getParentComputeConstruct() const {
   // Check all nested context skipping the first one.
   for (std::size_t i = dirContext_.size() - 1; i > 0; --i) {
     if (IsComputeConstruct(dirContext_[i - 1].directive)) {
-      return true;
+      return dirContext_[i - 1].directive;
     }
   }
-  return false;
+  return std::nullopt;
+}
+
+bool AccStructureChecker::IsInsideComputeConstruct() const {
+  return getParentComputeConstruct().has_value();
 }
 
 void AccStructureChecker::CheckNotInComputeConstruct() {
@@ -128,6 +138,16 @@ void AccStructureChecker::CheckNotInComputeConstruct() {
   }
 }
 
+bool AccStructureChecker::IsInsideParallelConstruct() const {
+  if (auto directive = getParentComputeConstruct()) {
+    if (*directive == llvm::acc::ACCD_parallel ||
+        *directive == llvm::acc::ACCD_parallel_loop) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void AccStructureChecker::Enter(const parser::AccClause &x) {
   SetContextClause(x);
 }
@@ -250,6 +270,95 @@ void AccStructureChecker::Leave(const parser::OpenACCCombinedConstruct &x) {
   dirContext_.pop_back();
 }
 
+std::optional<std::int64_t> AccStructureChecker::getGangDimensionSize(
+    DirectiveContext &dirContext) {
+  for (auto it : dirContext.clauseInfo) {
+    const auto *clause{it.second};
+    if (const auto *gangClause{
+            std::get_if<parser::AccClause::Gang>(&clause->u)}) {
+      if (gangClause->v) {
+        const Fortran::parser::AccGangArgList &x{*gangClause->v};
+        for (const Fortran::parser::AccGangArg &gangArg : x.v) {
+          if (const auto *dim{
+                  std::get_if<Fortran::parser::AccGangArg::Dim>(&gangArg.u)}) {
+            if (const auto v{EvaluateInt64(context_, dim->v)}) {
+              return *v;
+            }
+          }
+        }
+      }
+    }
+  }
+  return std::nullopt;
+}
+
+void AccStructureChecker::CheckNotInSameOrSubLevelLoopConstruct() {
+  for (std::size_t i = dirContext_.size() - 1; i > 0; --i) {
+    auto &parent{dirContext_[i - 1]};
+    if (IsLoopConstruct(parent.directive)) {
+      for (auto parentClause : parent.actualClauses) {
+        for (auto cl : GetContext().actualClauses) {
+          bool invalid{false};
+          if (parentClause == llvm::acc::Clause::ACCC_gang &&
+              cl == llvm::acc::Clause::ACCC_gang) {
+            if (IsInsideParallelConstruct()) {
+              auto parentDim = getGangDimensionSize(parent);
+              auto currentDim = getGangDimensionSize(GetContext());
+              std::int64_t parentDimNum = 1, currentDimNum = 1;
+              if (parentDim) {
+                parentDimNum = *parentDim;
+              }
+              if (currentDim) {
+                currentDimNum = *currentDim;
+              }
+              if (parentDimNum <= currentDimNum) {
+                std::string parentDimStr, currentDimStr;
+                if (parentDim) {
+                  parentDimStr = "(dim:" + std::to_string(parentDimNum) + ")";
+                }
+                if (currentDim) {
+                  currentDimStr = "(dim:" + std::to_string(currentDimNum) + ")";
+                }
+                context_.Say(GetContext().clauseSource,
+                    "%s%s clause is not allowed in the region of a loop with the %s%s clause"_err_en_US,
+                    parser::ToUpperCaseLetters(
+                        llvm::acc::getOpenACCClauseName(cl).str()),
+                    currentDimStr,
+                    parser::ToUpperCaseLetters(
+                        llvm::acc::getOpenACCClauseName(parentClause).str()),
+                    parentDimStr);
+                continue;
+              }
+            } else {
+              invalid = true;
+            }
+          } else if (parentClause == llvm::acc::Clause::ACCC_worker &&
+              (cl == llvm::acc::Clause::ACCC_gang ||
+                  cl == llvm::acc::Clause::ACCC_worker)) {
+            invalid = true;
+          } else if (parentClause == llvm::acc::Clause::ACCC_vector &&
+              (cl == llvm::acc::Clause::ACCC_gang ||
+                  cl == llvm::acc::Clause::ACCC_worker ||
+                  cl == llvm::acc::Clause::ACCC_vector)) {
+            invalid = true;
+          }
+          if (invalid) {
+            context_.Say(GetContext().clauseSource,
+                "%s clause is not allowed in the region of a loop with the %s clause"_err_en_US,
+                parser::ToUpperCaseLetters(
+                    llvm::acc::getOpenACCClauseName(cl).str()),
+                parser::ToUpperCaseLetters(
+                    llvm::acc::getOpenACCClauseName(parentClause).str()));
+          }
+        }
+      }
+    }
+    if (IsComputeConstruct(parent.directive)) {
+      break;
+    }
+  }
+}
+
 void AccStructureChecker::Enter(const parser::OpenACCLoopConstruct &x) {
   const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
   const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)};
@@ -267,6 +376,8 @@ void AccStructureChecker::Leave(const parser::OpenACCLoopConstruct &x) {
     CheckNotAllowedIfClause(llvm::acc::Clause::ACCC_seq,
         {llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_vector,
             llvm::acc::Clause::ACCC_worker});
+    // Restriction - 2.9.2, 2.9.3, 2.9.4
+    CheckNotInSameOrSubLevelLoopConstruct();
   }
   dirContext_.pop_back();
 }
@@ -570,8 +681,8 @@ void AccStructureChecker::Enter(const parser::OpenACCCacheConstruct &x) {
   PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_cache);
   SetContextDirectiveSource(verbatim.source);
   if (loopNestLevel == 0) {
-    context_.Say(verbatim.source,
-          "The CACHE directive must be inside a loop"_err_en_US);
+    context_.Say(
+        verbatim.source, "The CACHE directive must be inside a loop"_err_en_US);
   }
 }
 void AccStructureChecker::Leave(const parser::OpenACCCacheConstruct &x) {
diff --git a/flang/lib/Semantics/check-acc-structure.h b/flang/lib/Semantics/check-acc-structure.h
index 359f1557b62cb..711d0326349a4 100644
--- a/flang/lib/Semantics/check-acc-structure.h
+++ b/flang/lib/Semantics/check-acc-structure.h
@@ -98,8 +98,14 @@ class AccStructureChecker
 
   bool CheckAllowedModifier(llvm::acc::Clause clause);
   bool IsComputeConstruct(llvm::acc::Directive directive) const;
+  bool IsLoopConstruct(llvm::acc::Directive directive) const;
+  std::optional<llvm::acc::Directive> getParentComputeConstruct() const;
   bool IsInsideComputeConstruct() const;
+  bool IsInsideParallelConstruct() const;
   void CheckNotInComputeConstruct();
+  std::optional<std::int64_t> getGangDimensionSize(
+      DirectiveContext &dirContext);
+  void CheckNotInSameOrSubLevelLoopConstruct();
   void CheckMultipleOccurrenceInDeclare(
       const parser::AccObjectList &, llvm::acc::Clause);
   void CheckMultipleOccurrenceInDeclare(
diff --git a/flang/lib/Semantics/check-directive-structure.h b/flang/lib/Semantics/check-directive-structure.h
index b1bf3e550aebc..1b5909ffeb992 100644
--- a/flang/lib/Semantics/check-directive-structure.h
+++ b/flang/lib/Semantics/check-directive-structure.h
@@ -160,7 +160,7 @@ template <typename D> class NoBranchingEnforce {
     if (numDoConstruct_ > 0) {
       return;
     }
-    // did not found an enclosing looping construct within the OpenMP/OpenACC
+    // did not find an enclosing looping construct within the OpenMP/OpenACC
     // directive
     EmitUnlabelledBranchOutError(stmt);
   }
diff --git a/flang/test/Semantics/OpenACC/acc-loop.f90 b/flang/test/Semantics/OpenACC/acc-loop.f90
index 859cf3feec0d6..9301cf85305d4 100644
--- a/flang/test/Semantics/OpenACC/acc-loop.f90
+++ b/flang/test/Semantics/OpenACC/acc-loop.f90
@@ -13,7 +13,7 @@ program openacc_loop_validity
     integer :: n
   end type atype
 
-  integer :: i, j, k, b, gang_size, vector_size, worker_size
+  integer :: i, j, k, l, m, b, gang_size, vector_size, worker_size
   integer, parameter :: N = 256
   integer, dimension(N) :: c
   logical, dimension(N) :: d, e
@@ -259,6 +259,98 @@ program openacc_loop_validity
   end do
   !$acc end parallel
 
+  !$acc parallel
+  !$acc loop gang
+  do i = 1, n
+    !$acc loop worker
+    do j = 1, n
+      !ERROR: GANG clause is not allowed in the region of a loop with the WORKER clause
+      !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+      !$acc loop gang vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end parallel
+
+  !$acc parallel loop vector
+  do  i = 1, n
+    !ERROR: GANG clause is not allowed in the region of a loop with the VECTOR clause
+    !$acc loop gang
+    do j = 1, n
+      !ERROR: WORKER clause is not allowed in the region of a loop with the VECTOR clause
+      !$acc loop worker
+      do k = 1, i
+        !ERROR: VECTOR clause is not allowed in the region of a loop with the VECTOR clause
+        !$acc loop vector
+        do l = 1, 1
+        end do
+      end do
+    end do
+  end do
+  !$acc end parallel loop
+
+  !$acc kernels
+  do  i = 1, n
+    !$acc loop gang worker
+    do j = 1, n
+      !ERROR: WORKER clause is not allowed in the region of a loop with the WORKER clause
+      !$acc loop worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end kernels
+
+  !$acc parallel
+  !$acc loop gang(dim:1)
+  do i = 1, n
+    !ERROR: GANG(dim:1) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+    !$acc loop gang(dim:1)
+    do j = 1, n
+      !ERROR: GANG(dim:2) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+      !$acc loop gang(dim:2)
+      do k = 1, i
+        !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:2) clause
+        !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+        !$acc loop gang(dim:3)
+        do l = 1, 1
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:3) clause
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:2) clause
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+          !$acc loop gang(dim:3)
+          do m = 1, 1
+          end do
+        end do
+      end do
+    end do
+  end do
+  !$acc end parallel
+
+  !$acc parallel loop gang(dim:3)
+  do i = 1, n
+    !$acc loop gang(dim:2)
+    do j = 1, n
+      !$acc loop gang(dim:1) worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end parallel loop
+
+  !$acc kernels loop gang(dim:3)
+  do i = 1, n
+    !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+    !$acc loop gang(dim:2)
+    do j = 1, n
+      !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+      !$acc loop gang(dim:1) worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end kernels loop
+
   !ERROR: Clause IF is not allowed after clause DEVICE_TYPE on the PARALLEL directive
   !$acc parallel device_type(*) if(.TRUE.)
   !$acc loop

@llvmbot
Copy link
Member

llvmbot commented Aug 5, 2025

@llvm/pr-subscribers-flang-semantics

Author: None (khaki3)

Changes

This PR implements a semantic checker to ensure the legality of nested OpenACC parallelism. The following are quotes from Spec 3.3. We need to disallow loops from having parallelism at the same level as or at a sub-level of child loops.

>2.9.2 gang clause
>[2064] <ins>When the parent compute construct is a parallel construct</ins>, or on an orphaned loop construct, the gang clause behaves as follows. (...) The associated dimension is the value of the dim argument, if it appears, or is dimension one. The dim argument must be a constant positive integer with value 1, 2, or 3.
>[2112] The region of a loop with a gang(dim:d) clause may not contain a loop construct with a gang(dim:e) clause where e >= d unless it appears within a nested compute region.

>[2074] <ins>When the parent compute construct is a kernels construct</ins>, the gang clause behaves as follows. (...)
>[2148] The region of a loop with the gang clause may not contain another loop with a gang clause unless within a nested compute region.

>2.9.3 worker clause
>[2122]/[2129] The region of a loop with the worker clause may not contain a loop with the gang or worker clause unless within a nested compute region.

>2.9.4 vector clause
>[2141]/[2148] The region of a loop with the vector clause may not contain a loop with a gang, worker, or vector clause unless within a nested compute region.

https://openacc.org/sites/default/files/inline-images/Specification/OpenACC-3.3-final.pdf


Full diff: https://github.com/llvm/llvm-project/pull/152225.diff

4 Files Affected:

  • (modified) flang/lib/Semantics/check-acc-structure.cpp (+119-8)
  • (modified) flang/lib/Semantics/check-acc-structure.h (+6)
  • (modified) flang/lib/Semantics/check-directive-structure.h (+1-1)
  • (modified) flang/test/Semantics/OpenACC/acc-loop.f90 (+93-1)
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index 77e2b01578641..ebfaa01660523 100644
--- a/flang/lib/Semantics/check-acc-structure.cpp
+++ b/flang/lib/Semantics/check-acc-structure.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 #include "check-acc-structure.h"
+#include "resolve-names-utils.h"
 #include "flang/Common/enum-set.h"
 #include "flang/Evaluate/tools.h"
 #include "flang/Parser/parse-tree.h"
@@ -106,18 +107,27 @@ bool AccStructureChecker::IsComputeConstruct(
       directive == llvm::acc::ACCD_kernels_loop;
 }
 
-bool AccStructureChecker::IsInsideComputeConstruct() const {
-  if (dirContext_.size() <= 1) {
-    return false;
-  }
+bool AccStructureChecker::IsLoopConstruct(
+    llvm::acc::Directive directive) const {
+  return directive == llvm::acc::Directive::ACCD_loop ||
+      directive == llvm::acc::ACCD_parallel_loop ||
+      directive == llvm::acc::ACCD_serial_loop ||
+      directive == llvm::acc::ACCD_kernels_loop;
+}
 
+std::optional<llvm::acc::Directive>
+AccStructureChecker::getParentComputeConstruct() const {
   // Check all nested context skipping the first one.
   for (std::size_t i = dirContext_.size() - 1; i > 0; --i) {
     if (IsComputeConstruct(dirContext_[i - 1].directive)) {
-      return true;
+      return dirContext_[i - 1].directive;
     }
   }
-  return false;
+  return std::nullopt;
+}
+
+bool AccStructureChecker::IsInsideComputeConstruct() const {
+  return getParentComputeConstruct().has_value();
 }
 
 void AccStructureChecker::CheckNotInComputeConstruct() {
@@ -128,6 +138,16 @@ void AccStructureChecker::CheckNotInComputeConstruct() {
   }
 }
 
+bool AccStructureChecker::IsInsideParallelConstruct() const {
+  if (auto directive = getParentComputeConstruct()) {
+    if (*directive == llvm::acc::ACCD_parallel ||
+        *directive == llvm::acc::ACCD_parallel_loop) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void AccStructureChecker::Enter(const parser::AccClause &x) {
   SetContextClause(x);
 }
@@ -250,6 +270,95 @@ void AccStructureChecker::Leave(const parser::OpenACCCombinedConstruct &x) {
   dirContext_.pop_back();
 }
 
+std::optional<std::int64_t> AccStructureChecker::getGangDimensionSize(
+    DirectiveContext &dirContext) {
+  for (auto it : dirContext.clauseInfo) {
+    const auto *clause{it.second};
+    if (const auto *gangClause{
+            std::get_if<parser::AccClause::Gang>(&clause->u)}) {
+      if (gangClause->v) {
+        const Fortran::parser::AccGangArgList &x{*gangClause->v};
+        for (const Fortran::parser::AccGangArg &gangArg : x.v) {
+          if (const auto *dim{
+                  std::get_if<Fortran::parser::AccGangArg::Dim>(&gangArg.u)}) {
+            if (const auto v{EvaluateInt64(context_, dim->v)}) {
+              return *v;
+            }
+          }
+        }
+      }
+    }
+  }
+  return std::nullopt;
+}
+
+void AccStructureChecker::CheckNotInSameOrSubLevelLoopConstruct() {
+  for (std::size_t i = dirContext_.size() - 1; i > 0; --i) {
+    auto &parent{dirContext_[i - 1]};
+    if (IsLoopConstruct(parent.directive)) {
+      for (auto parentClause : parent.actualClauses) {
+        for (auto cl : GetContext().actualClauses) {
+          bool invalid{false};
+          if (parentClause == llvm::acc::Clause::ACCC_gang &&
+              cl == llvm::acc::Clause::ACCC_gang) {
+            if (IsInsideParallelConstruct()) {
+              auto parentDim = getGangDimensionSize(parent);
+              auto currentDim = getGangDimensionSize(GetContext());
+              std::int64_t parentDimNum = 1, currentDimNum = 1;
+              if (parentDim) {
+                parentDimNum = *parentDim;
+              }
+              if (currentDim) {
+                currentDimNum = *currentDim;
+              }
+              if (parentDimNum <= currentDimNum) {
+                std::string parentDimStr, currentDimStr;
+                if (parentDim) {
+                  parentDimStr = "(dim:" + std::to_string(parentDimNum) + ")";
+                }
+                if (currentDim) {
+                  currentDimStr = "(dim:" + std::to_string(currentDimNum) + ")";
+                }
+                context_.Say(GetContext().clauseSource,
+                    "%s%s clause is not allowed in the region of a loop with the %s%s clause"_err_en_US,
+                    parser::ToUpperCaseLetters(
+                        llvm::acc::getOpenACCClauseName(cl).str()),
+                    currentDimStr,
+                    parser::ToUpperCaseLetters(
+                        llvm::acc::getOpenACCClauseName(parentClause).str()),
+                    parentDimStr);
+                continue;
+              }
+            } else {
+              invalid = true;
+            }
+          } else if (parentClause == llvm::acc::Clause::ACCC_worker &&
+              (cl == llvm::acc::Clause::ACCC_gang ||
+                  cl == llvm::acc::Clause::ACCC_worker)) {
+            invalid = true;
+          } else if (parentClause == llvm::acc::Clause::ACCC_vector &&
+              (cl == llvm::acc::Clause::ACCC_gang ||
+                  cl == llvm::acc::Clause::ACCC_worker ||
+                  cl == llvm::acc::Clause::ACCC_vector)) {
+            invalid = true;
+          }
+          if (invalid) {
+            context_.Say(GetContext().clauseSource,
+                "%s clause is not allowed in the region of a loop with the %s clause"_err_en_US,
+                parser::ToUpperCaseLetters(
+                    llvm::acc::getOpenACCClauseName(cl).str()),
+                parser::ToUpperCaseLetters(
+                    llvm::acc::getOpenACCClauseName(parentClause).str()));
+          }
+        }
+      }
+    }
+    if (IsComputeConstruct(parent.directive)) {
+      break;
+    }
+  }
+}
+
 void AccStructureChecker::Enter(const parser::OpenACCLoopConstruct &x) {
   const auto &beginDir{std::get<parser::AccBeginLoopDirective>(x.t)};
   const auto &loopDir{std::get<parser::AccLoopDirective>(beginDir.t)};
@@ -267,6 +376,8 @@ void AccStructureChecker::Leave(const parser::OpenACCLoopConstruct &x) {
     CheckNotAllowedIfClause(llvm::acc::Clause::ACCC_seq,
         {llvm::acc::Clause::ACCC_gang, llvm::acc::Clause::ACCC_vector,
             llvm::acc::Clause::ACCC_worker});
+    // Restriction - 2.9.2, 2.9.3, 2.9.4
+    CheckNotInSameOrSubLevelLoopConstruct();
   }
   dirContext_.pop_back();
 }
@@ -570,8 +681,8 @@ void AccStructureChecker::Enter(const parser::OpenACCCacheConstruct &x) {
   PushContextAndClauseSets(verbatim.source, llvm::acc::Directive::ACCD_cache);
   SetContextDirectiveSource(verbatim.source);
   if (loopNestLevel == 0) {
-    context_.Say(verbatim.source,
-          "The CACHE directive must be inside a loop"_err_en_US);
+    context_.Say(
+        verbatim.source, "The CACHE directive must be inside a loop"_err_en_US);
   }
 }
 void AccStructureChecker::Leave(const parser::OpenACCCacheConstruct &x) {
diff --git a/flang/lib/Semantics/check-acc-structure.h b/flang/lib/Semantics/check-acc-structure.h
index 359f1557b62cb..711d0326349a4 100644
--- a/flang/lib/Semantics/check-acc-structure.h
+++ b/flang/lib/Semantics/check-acc-structure.h
@@ -98,8 +98,14 @@ class AccStructureChecker
 
   bool CheckAllowedModifier(llvm::acc::Clause clause);
   bool IsComputeConstruct(llvm::acc::Directive directive) const;
+  bool IsLoopConstruct(llvm::acc::Directive directive) const;
+  std::optional<llvm::acc::Directive> getParentComputeConstruct() const;
   bool IsInsideComputeConstruct() const;
+  bool IsInsideParallelConstruct() const;
   void CheckNotInComputeConstruct();
+  std::optional<std::int64_t> getGangDimensionSize(
+      DirectiveContext &dirContext);
+  void CheckNotInSameOrSubLevelLoopConstruct();
   void CheckMultipleOccurrenceInDeclare(
       const parser::AccObjectList &, llvm::acc::Clause);
   void CheckMultipleOccurrenceInDeclare(
diff --git a/flang/lib/Semantics/check-directive-structure.h b/flang/lib/Semantics/check-directive-structure.h
index b1bf3e550aebc..1b5909ffeb992 100644
--- a/flang/lib/Semantics/check-directive-structure.h
+++ b/flang/lib/Semantics/check-directive-structure.h
@@ -160,7 +160,7 @@ template <typename D> class NoBranchingEnforce {
     if (numDoConstruct_ > 0) {
       return;
     }
-    // did not found an enclosing looping construct within the OpenMP/OpenACC
+    // did not find an enclosing looping construct within the OpenMP/OpenACC
     // directive
     EmitUnlabelledBranchOutError(stmt);
   }
diff --git a/flang/test/Semantics/OpenACC/acc-loop.f90 b/flang/test/Semantics/OpenACC/acc-loop.f90
index 859cf3feec0d6..9301cf85305d4 100644
--- a/flang/test/Semantics/OpenACC/acc-loop.f90
+++ b/flang/test/Semantics/OpenACC/acc-loop.f90
@@ -13,7 +13,7 @@ program openacc_loop_validity
     integer :: n
   end type atype
 
-  integer :: i, j, k, b, gang_size, vector_size, worker_size
+  integer :: i, j, k, l, m, b, gang_size, vector_size, worker_size
   integer, parameter :: N = 256
   integer, dimension(N) :: c
   logical, dimension(N) :: d, e
@@ -259,6 +259,98 @@ program openacc_loop_validity
   end do
   !$acc end parallel
 
+  !$acc parallel
+  !$acc loop gang
+  do i = 1, n
+    !$acc loop worker
+    do j = 1, n
+      !ERROR: GANG clause is not allowed in the region of a loop with the WORKER clause
+      !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+      !$acc loop gang vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end parallel
+
+  !$acc parallel loop vector
+  do  i = 1, n
+    !ERROR: GANG clause is not allowed in the region of a loop with the VECTOR clause
+    !$acc loop gang
+    do j = 1, n
+      !ERROR: WORKER clause is not allowed in the region of a loop with the VECTOR clause
+      !$acc loop worker
+      do k = 1, i
+        !ERROR: VECTOR clause is not allowed in the region of a loop with the VECTOR clause
+        !$acc loop vector
+        do l = 1, 1
+        end do
+      end do
+    end do
+  end do
+  !$acc end parallel loop
+
+  !$acc kernels
+  do  i = 1, n
+    !$acc loop gang worker
+    do j = 1, n
+      !ERROR: WORKER clause is not allowed in the region of a loop with the WORKER clause
+      !$acc loop worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end kernels
+
+  !$acc parallel
+  !$acc loop gang(dim:1)
+  do i = 1, n
+    !ERROR: GANG(dim:1) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+    !$acc loop gang(dim:1)
+    do j = 1, n
+      !ERROR: GANG(dim:2) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+      !$acc loop gang(dim:2)
+      do k = 1, i
+        !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:2) clause
+        !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+        !$acc loop gang(dim:3)
+        do l = 1, 1
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:3) clause
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:2) clause
+          !ERROR: GANG(dim:3) clause is not allowed in the region of a loop with the GANG(dim:1) clause
+          !$acc loop gang(dim:3)
+          do m = 1, 1
+          end do
+        end do
+      end do
+    end do
+  end do
+  !$acc end parallel
+
+  !$acc parallel loop gang(dim:3)
+  do i = 1, n
+    !$acc loop gang(dim:2)
+    do j = 1, n
+      !$acc loop gang(dim:1) worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end parallel loop
+
+  !$acc kernels loop gang(dim:3)
+  do i = 1, n
+    !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+    !$acc loop gang(dim:2)
+    do j = 1, n
+      !ERROR: GANG clause is not allowed in the region of a loop with the GANG clause
+      !$acc loop gang(dim:1) worker vector
+      do k = 1, i
+      end do
+    end do
+  end do
+  !$acc end kernels loop
+
   !ERROR: Clause IF is not allowed after clause DEVICE_TYPE on the PARALLEL directive
   !$acc parallel device_type(*) if(.TRUE.)
   !$acc loop

@clementval
Copy link
Contributor

Looks ok in general but you have a lot of braces that can be removed.

https://llvm.org/docs/CodingStandards.html#don-t-use-braces-on-simple-single-statement-bodies-of-if-else-loop-statements

Copy link
Contributor

@clementval clementval left a comment

Choose a reason for hiding this comment

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

LGTM. Just two places where braces are still needed.

@khaki3
Copy link
Contributor Author

khaki3 commented Aug 6, 2025

Thank you for your review.

@clementval
Copy link
Contributor

Thank you for your review.

No problem! Thanks for the additional check!

@khaki3 khaki3 merged commit 8470027 into llvm:main Aug 6, 2025
9 checks passed
krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
…ism (llvm#152225)

This PR implements a semantic checker to ensure the legality of nested
OpenACC parallelism. The following are quotes from Spec 3.3. We need to
disallow loops from having parallelism at the same level as or at a
sub-level of child loops.

>**2.9.2 gang clause**
>[2064] <ins>When the parent compute construct is a parallel
construct</ins>, or on an orphaned loop construct, the gang clause
behaves as follows. (...) The associated dimension is the value of the
dim argument, if it appears, or is dimension one. The dim argument must
be a constant positive integer with value 1, 2, or 3.
>[2112] The region of a loop with a gang(dim:d) clause may not contain a
loop construct with a gang(dim:e) clause where e >= d unless it appears
within a nested compute region.

>[2074] <ins>When the parent compute construct is a kernels
construct</ins>, the gang clause behaves as follows. (...)
>[2148] The region of a loop with the gang clause may not contain
another loop with a gang clause unless within a nested compute region.

>**2.9.3 worker clause**
>[2122]/[2129] The region of a loop with the worker clause may not
contain a loop with the gang or worker clause unless within a nested
compute region.

>**2.9.4 vector clause**
>[2141]/[2148] The region of a loop with the vector clause may not
contain a loop with a gang, worker, or vector clause unless within a
nested compute region.

https://openacc.org/sites/default/files/inline-images/Specification/OpenACC-3.3-final.pdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:semantics flang Flang issues not falling into any other category openacc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants