[flang-commits] [flang] [flang][acc] Add a semantic check for the validity of nested parallelism (PR #152225)
via flang-commits
flang-commits at lists.llvm.org
Tue Aug 5 16:58:48 PDT 2025
https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/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
>From 41c188dded3c1d438c38fa4e59e17dda64e4a01b Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 5 Aug 2025 16:14:35 -0700
Subject: [PATCH 1/3] [flang][acc] Add a semantic check for the validity of
nested parallelism
---
flang/lib/Semantics/check-acc-structure.cpp | 109 ++++++++++++++++--
flang/lib/Semantics/check-acc-structure.h | 6 +
.../lib/Semantics/check-directive-structure.h | 2 +-
flang/test/Semantics/OpenACC/acc-loop.f90 | 93 ++++++++++++++-
4 files changed, 200 insertions(+), 10 deletions(-)
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index 77e2b01578641..03e2a307b1288 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::IsInsideKernelsConstruct() const {
+ if (auto directive = getParentComputeConstruct()) {
+ if (*directive == llvm::acc::ACCD_kernels ||
+ *directive == llvm::acc::ACCD_kernels_loop) {
+ return true;
+ }
+ }
+ return false;
+}
+
void AccStructureChecker::Enter(const parser::AccClause &x) {
SetContextClause(x);
}
@@ -250,6 +270,77 @@ 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 1;
+ }
+ }
+ 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 (IsInsideKernelsConstruct()) {
+ auto parentDim = getGangDimensionSize(parent);
+ auto currentDim = getGangDimensionSize(GetContext());
+ if (*parentDim <= *currentDim) {
+ context_.Say(GetContext().clauseSource,
+ "gang(dim:%ld) clause is not allowed in the region of a loop with the gang(dim:%ld) clause"_err_en_US,
+ *currentDim, *parentDim);
+ 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 +358,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 +663,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..09399297ca4be 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 IsInsideKernelsConstruct() 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..25cac90a8e494 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,97 @@ 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 kernels 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 kernels loop
+
+ !$acc kernels 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 kernels loop
+
+ !$acc parallel 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 parallel loop
+
!ERROR: Clause IF is not allowed after clause DEVICE_TYPE on the PARALLEL directive
!$acc parallel device_type(*) if(.TRUE.)
!$acc loop
>From 321e834de499a35e27935685c64e62e24636ac7c Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 5 Aug 2025 16:32:13 -0700
Subject: [PATCH 2/3] [acc] IsInsideKernelsConstruct->IsInsideParallelConstruct
---
flang/lib/Semantics/check-acc-structure.cpp | 8 ++++----
flang/lib/Semantics/check-acc-structure.h | 2 +-
flang/test/Semantics/OpenACC/acc-loop.f90 | 13 +++++++------
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index 03e2a307b1288..d5f6c88794e38 100644
--- a/flang/lib/Semantics/check-acc-structure.cpp
+++ b/flang/lib/Semantics/check-acc-structure.cpp
@@ -138,10 +138,10 @@ void AccStructureChecker::CheckNotInComputeConstruct() {
}
}
-bool AccStructureChecker::IsInsideKernelsConstruct() const {
+bool AccStructureChecker::IsInsideParallelConstruct() const {
if (auto directive = getParentComputeConstruct()) {
- if (*directive == llvm::acc::ACCD_kernels ||
- *directive == llvm::acc::ACCD_kernels_loop) {
+ if (*directive == llvm::acc::ACCD_parallel ||
+ *directive == llvm::acc::ACCD_parallel_loop) {
return true;
}
}
@@ -302,7 +302,7 @@ void AccStructureChecker::CheckNotInSameOrSubLevelLoopConstruct() {
bool invalid{false};
if (parentClause == llvm::acc::Clause::ACCC_gang &&
cl == llvm::acc::Clause::ACCC_gang) {
- if (IsInsideKernelsConstruct()) {
+ if (IsInsideParallelConstruct()) {
auto parentDim = getGangDimensionSize(parent);
auto currentDim = getGangDimensionSize(GetContext());
if (*parentDim <= *currentDim) {
diff --git a/flang/lib/Semantics/check-acc-structure.h b/flang/lib/Semantics/check-acc-structure.h
index 09399297ca4be..711d0326349a4 100644
--- a/flang/lib/Semantics/check-acc-structure.h
+++ b/flang/lib/Semantics/check-acc-structure.h
@@ -101,7 +101,7 @@ class AccStructureChecker
bool IsLoopConstruct(llvm::acc::Directive directive) const;
std::optional<llvm::acc::Directive> getParentComputeConstruct() const;
bool IsInsideComputeConstruct() const;
- bool IsInsideKernelsConstruct() const;
+ bool IsInsideParallelConstruct() const;
void CheckNotInComputeConstruct();
std::optional<std::int64_t> getGangDimensionSize(
DirectiveContext &dirContext);
diff --git a/flang/test/Semantics/OpenACC/acc-loop.f90 b/flang/test/Semantics/OpenACC/acc-loop.f90
index 25cac90a8e494..586ee721d066d 100644
--- a/flang/test/Semantics/OpenACC/acc-loop.f90
+++ b/flang/test/Semantics/OpenACC/acc-loop.f90
@@ -302,7 +302,8 @@ program openacc_loop_validity
end do
!$acc end kernels
- !$acc kernels loop gang(dim:1)
+ !$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)
@@ -324,9 +325,9 @@ program openacc_loop_validity
end do
end do
end do
- !$acc end kernels loop
+ !$acc end parallel
- !$acc kernels loop gang(dim:3)
+ !$acc parallel loop gang(dim:3)
do i = 1, n
!$acc loop gang(dim:2)
do j = 1, n
@@ -335,9 +336,9 @@ program openacc_loop_validity
end do
end do
end do
- !$acc end kernels loop
+ !$acc end parallel loop
- !$acc parallel loop gang(dim:3)
+ !$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)
@@ -348,7 +349,7 @@ program openacc_loop_validity
end do
end do
end do
- !$acc end parallel loop
+ !$acc end kernels loop
!ERROR: Clause IF is not allowed after clause DEVICE_TYPE on the PARALLEL directive
!$acc parallel device_type(*) if(.TRUE.)
>From 9868f9c4f6da2e7a4e9ad351535650f7d0ab3fa2 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 5 Aug 2025 16:50:28 -0700
Subject: [PATCH 3/3] [acc] Better format
---
flang/lib/Semantics/check-acc-structure.cpp | 26 +++++++++++++++++----
flang/test/Semantics/OpenACC/acc-loop.f90 | 14 +++++------
2 files changed, 29 insertions(+), 11 deletions(-)
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index d5f6c88794e38..ebfaa01660523 100644
--- a/flang/lib/Semantics/check-acc-structure.cpp
+++ b/flang/lib/Semantics/check-acc-structure.cpp
@@ -287,7 +287,6 @@ std::optional<std::int64_t> AccStructureChecker::getGangDimensionSize(
}
}
}
- return 1;
}
}
return std::nullopt;
@@ -305,10 +304,29 @@ void AccStructureChecker::CheckNotInSameOrSubLevelLoopConstruct() {
if (IsInsideParallelConstruct()) {
auto parentDim = getGangDimensionSize(parent);
auto currentDim = getGangDimensionSize(GetContext());
- if (*parentDim <= *currentDim) {
+ 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,
- "gang(dim:%ld) clause is not allowed in the region of a loop with the gang(dim:%ld) clause"_err_en_US,
- *currentDim, *parentDim);
+ "%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 {
diff --git a/flang/test/Semantics/OpenACC/acc-loop.f90 b/flang/test/Semantics/OpenACC/acc-loop.f90
index 586ee721d066d..9301cf85305d4 100644
--- a/flang/test/Semantics/OpenACC/acc-loop.f90
+++ b/flang/test/Semantics/OpenACC/acc-loop.f90
@@ -305,19 +305,19 @@ program openacc_loop_validity
!$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
+ !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
+ !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
+ !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
+ !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
More information about the flang-commits
mailing list