[flang-commits] [flang] [flang][acc] Warn when a loop is above its routine parallelism level (PR #227041)
via flang-commits
flang-commits at lists.llvm.org
Mon Sep 28 09:51:03 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-openacc
Author: Razvan Lupusoru (razvanlupusoru)
<details>
<summary>Changes</summary>
OpenACC spec allows a routine to parent a loop at its own parallelism level or below. A higher level is not allowed: gang inside a worker routine, gang or worker inside a vector routine, gang, worker, or vector inside a seq routine, and a gang dimension above the routine's gang dimension. A device-specific routine clause counts the same way.
Flang accepted these loops with no diagnostic. Warn that the clause is ignored. A loop at the same level or below is left alone, and illegal nesting of loops remains an error.
---
Full diff: https://github.com/llvm/llvm-project/pull/227041.diff
3 Files Affected:
- (modified) flang/lib/Semantics/check-acc-structure.cpp (+164-33)
- (modified) flang/lib/Semantics/check-acc-structure.h (+5)
- (added) flang/test/Semantics/OpenACC/acc-routine-loop-nesting.f90 (+122)
``````````diff
diff --git a/flang/lib/Semantics/check-acc-structure.cpp b/flang/lib/Semantics/check-acc-structure.cpp
index 5ef60459a5dfc..ff11617235dd0 100644
--- a/flang/lib/Semantics/check-acc-structure.cpp
+++ b/flang/lib/Semantics/check-acc-structure.cpp
@@ -456,24 +456,126 @@ void AccStructureChecker::CheckNotInSameOrSubLevelLoopConstruct() {
}
}
-void AccStructureChecker::CheckRoutineCallInLoop(const Symbol &symbol) {
- if (dirContext_.empty()) {
- return;
+struct RoutineParallelism {
+ bool isGang{false};
+ bool isWorker{false};
+ bool isVector{false};
+ bool isSeq{false};
+ // Set only for a gang routine. An omitted dim is dimension 1.
+ unsigned gangDim{0};
+ std::string name;
+ Fortran::common::OpenACCDeviceType deviceType{
+ Fortran::common::OpenACCDeviceType::None};
+};
+
+static const std::vector<OpenACCRoutineInfo> *getRoutineInfos(
+ const Symbol &sym) {
+ if (const auto *subp{sym.detailsIf<SubprogramDetails>()}) {
+ return &subp->openACCRoutineInfos();
+ }
+ if (const auto *proc{sym.detailsIf<ProcEntityDetails>()}) {
+ return &proc->openACCRoutineInfos();
}
- // OpenACC routine information can be attached either to a SubprogramDetails
- // (a normal function/subroutine) or to a ProcEntityDetails (a procedure
- // pointer or dummy procedure).
- auto getRoutineInfos =
- [](const Symbol &sym) -> const std::vector<OpenACCRoutineInfo> * {
- if (const auto *subp{sym.detailsIf<SubprogramDetails>()}) {
- return &subp->openACCRoutineInfos();
+ return nullptr;
+}
+
+static RoutineParallelism parallelismFromDeviceInfo(
+ const OpenACCRoutineDeviceTypeInfo &info) {
+ RoutineParallelism result;
+ result.deviceType = info.dType();
+ if (info.isGang()) {
+ result.isGang = true;
+ if (unsigned gangDim{info.gangDim()}) {
+ result.gangDim = gangDim;
+ result.name = "GANG(" + std::to_string(gangDim) + ")";
+ } else {
+ result.gangDim = 1;
+ result.name = "GANG";
}
- if (const auto *proc{sym.detailsIf<ProcEntityDetails>()}) {
- return &proc->openACCRoutineInfos();
+ } else if (info.isWorker()) {
+ result.isWorker = true;
+ result.name = "WORKER";
+ } else if (info.isVector()) {
+ result.isVector = true;
+ result.name = "VECTOR";
+ } else if (info.isSeq()) {
+ result.isSeq = true;
+ result.name = "SEQ";
+ }
+ return result;
+}
+
+// Default clauses only. A later routine directive replaces an earlier one.
+static RoutineParallelism routineParallelismFromInfos(
+ const std::vector<OpenACCRoutineInfo> &infos) {
+ RoutineParallelism result;
+ for (const OpenACCRoutineInfo &ri : infos) {
+ RoutineParallelism next{parallelismFromDeviceInfo(ri)};
+ if (!next.name.empty()) {
+ result = std::move(next);
+ }
+ }
+ return result;
+}
+
+// The default level plus every device-specific level. The warning uses all
+// of them: a clause is reported if it is illegal for any of those levels.
+static void collectRoutineParallelism(
+ const std::vector<OpenACCRoutineInfo> &infos,
+ std::vector<RoutineParallelism> &levels) {
+ for (const OpenACCRoutineInfo &ri : infos) {
+ RoutineParallelism base{parallelismFromDeviceInfo(ri)};
+ if (!base.name.empty()) {
+ levels.push_back(std::move(base));
}
- return nullptr;
- };
+ for (const OpenACCRoutineDeviceTypeInfo &dinfo : ri.deviceTypeInfos()) {
+ RoutineParallelism specific{parallelismFromDeviceInfo(dinfo)};
+ if (!specific.name.empty()) {
+ levels.push_back(std::move(specific));
+ }
+ }
+ }
+}
+
+static void collectEnclosingRoutineParallelism(SemanticsContext &context,
+ const parser::CharBlock &source, std::vector<RoutineParallelism> &levels) {
+ const Scope &progUnit{GetProgramUnitContaining(context.FindScope(source))};
+ const Symbol *symbol{progUnit.symbol()};
+ if (!symbol) {
+ return;
+ }
+ const std::vector<OpenACCRoutineInfo> *infos{getRoutineInfos(*symbol)};
+ if (!infos || infos->empty()) {
+ return;
+ }
+ collectRoutineParallelism(*infos, levels);
+}
+// True when `clause` is above `routine` in the OpenACC parallelism order.
+static bool clauseExceedsRoutine(llvm::acc::Clause clause,
+ std::optional<std::int64_t> gangDim, const RoutineParallelism &routine) {
+ const bool isGangClause{clause == llvm::acc::Clause::ACCC_gang};
+ const bool isWorkerClause{clause == llvm::acc::Clause::ACCC_worker};
+ if (routine.isSeq) {
+ return true;
+ }
+ if (routine.isVector) {
+ return isGangClause || isWorkerClause;
+ }
+ if (routine.isWorker) {
+ return isGangClause;
+ }
+ if (routine.isGang && isGangClause) {
+ const std::int64_t loopDim{gangDim.value_or(1)};
+ return loopDim > static_cast<std::int64_t>(routine.gangDim);
+ }
+ return false;
+}
+
+void AccStructureChecker::CheckRoutineCallInLoop(const Symbol &symbol) {
+ if (dirContext_.empty()) {
+ return;
+ }
const Symbol &ult{symbol.GetUltimate()};
const std::vector<OpenACCRoutineInfo> *infos{getRoutineInfos(ult)};
// For a call made through a procedure pointer or binding whose routine level
@@ -487,25 +589,9 @@ void AccStructureChecker::CheckRoutineCallInLoop(const Symbol &symbol) {
if (!infos || infos->empty()) {
return;
}
- std::string routineParDim;
- unsigned routineGangDim = 0;
- for (const OpenACCRoutineInfo &ri : *infos) {
- if (ri.isGang()) {
- if (unsigned gangDim = ri.gangDim()) {
- routineGangDim = gangDim;
- routineParDim = "GANG(" + std::to_string(gangDim) + ")";
- } else {
- routineGangDim = 1;
- routineParDim = "GANG";
- }
- } else if (ri.isWorker()) {
- routineParDim = "WORKER";
- } else if (ri.isVector()) {
- routineParDim = "VECTOR";
- } else if (ri.isSeq()) {
- routineParDim = "SEQ";
- }
- }
+ const RoutineParallelism routine{routineParallelismFromInfos(*infos)};
+ const std::string &routineParDim{routine.name};
+ const unsigned routineGangDim{routine.gangDim};
DirectiveContext &inner{dirContext_.back()};
for (llvm::acc::Clause cl : inner.actualClauses) {
@@ -1135,6 +1221,7 @@ void AccStructureChecker::Enter(const parser::AccClause::Vector &g) {
CheckLoopLevelClauseValue(
parser::ToUpperCaseLetters(getClauseName(crtClause).str()));
}
+ WarnIfLoopLevelExceedsRoutine(crtClause);
}
void AccStructureChecker::Enter(const parser::AccClause::Worker &g) {
@@ -1151,6 +1238,7 @@ void AccStructureChecker::Enter(const parser::AccClause::Worker &g) {
CheckLoopLevelClauseValue(
parser::ToUpperCaseLetters(getClauseName(crtClause).str()));
}
+ WarnIfLoopLevelExceedsRoutine(crtClause);
}
void AccStructureChecker::Enter(const parser::AccClause::Tile &g) {
@@ -1159,6 +1247,48 @@ void AccStructureChecker::Enter(const parser::AccClause::Tile &g) {
llvm::acc::Clause::ACCC_tile, llvm::acc::Clause::ACCC_device_type);
}
+void AccStructureChecker::WarnIfLoopLevelExceedsRoutine(
+ llvm::acc::Clause clause, std::optional<std::int64_t> gangDim) {
+ if (!IsLoopConstruct(GetContext().directive)) {
+ return;
+ }
+ // OpenACC 3.4 2.15.1. A routine may parent a loop at its own level or
+ // below. Warn if the clause is above any routine level, including a
+ // device_type level, without matching that device_type to the clause.
+ std::vector<RoutineParallelism> levels;
+ collectEnclosingRoutineParallelism(
+ context_, GetContext().clauseSource, levels);
+ const RoutineParallelism *routine{nullptr};
+ for (const RoutineParallelism &level : levels) {
+ if (clauseExceedsRoutine(clause, gangDim, level)) {
+ routine = &level;
+ break;
+ }
+ }
+ if (!routine) {
+ return;
+ }
+ std::string clauseName{
+ parser::ToUpperCaseLetters(getClauseName(clause).str())};
+ if (clause == llvm::acc::Clause::ACCC_gang && gangDim) {
+ clauseName += "(" + std::to_string(*gangDim) + ")";
+ }
+ if (routine->deviceType == Fortran::common::OpenACCDeviceType::None) {
+ context_.Warn(common::UsageWarning::OpenAccUsage, GetContext().clauseSource,
+ "%s clause ignored in ACC ROUTINE %s procedure"_warn_en_US, clauseName,
+ routine->name);
+ return;
+ }
+ const std::string deviceName{
+ routine->deviceType == Fortran::common::OpenACCDeviceType::Star
+ ? std::string{"*"}
+ : parser::ToUpperCaseLetters(
+ common::EnumToString(routine->deviceType))};
+ context_.Warn(common::UsageWarning::OpenAccUsage, GetContext().clauseSource,
+ "%s clause ignored in ACC ROUTINE %s procedure for DEVICE_TYPE(%s)"_warn_en_US,
+ clauseName, routine->name, deviceName);
+}
+
void AccStructureChecker::Enter(const parser::AccClause::Gang &g) {
llvm::acc::Clause crtClause = llvm::acc::Clause::ACCC_gang;
if (GetContext().directive == llvm::acc::Directive::ACCD_routine) {
@@ -1209,6 +1339,7 @@ void AccStructureChecker::Enter(const parser::AccClause::Gang &g) {
parser::ToUpperCaseLetters(getClauseName(crtClause).str()));
}
}
+ WarnIfLoopLevelExceedsRoutine(crtClause, getGangDimensionSize(GetContext()));
}
void AccStructureChecker::Enter(const parser::AccClause::NumGangs &n) {
diff --git a/flang/lib/Semantics/check-acc-structure.h b/flang/lib/Semantics/check-acc-structure.h
index 135f34abc8a3c..0b6b9ac721a21 100644
--- a/flang/lib/Semantics/check-acc-structure.h
+++ b/flang/lib/Semantics/check-acc-structure.h
@@ -120,6 +120,11 @@ class AccStructureChecker
void CheckLoopLevelClauseValue(llvm::StringRef clauseName);
void CheckLoopLevelClauseKernelsConflicts();
void CheckRoutineCallInLoop(const Symbol &);
+ // OpenACC 3.4 2.9 / 2.15.1: a loop parallelism level above the enclosing
+ // routine is ignored. `gangDim` is the loop gang dimension when `clause`
+ // is gang and a dim argument was written.
+ void WarnIfLoopLevelExceedsRoutine(llvm::acc::Clause clause,
+ std::optional<std::int64_t> gangDim = std::nullopt);
void CheckMultipleOccurrenceInDeclare(
const parser::AccObjectList &, llvm::acc::Clause);
void CheckMultipleOccurrenceInDeclare(
diff --git a/flang/test/Semantics/OpenACC/acc-routine-loop-nesting.f90 b/flang/test/Semantics/OpenACC/acc-routine-loop-nesting.f90
new file mode 100644
index 0000000000000..7898040cc830c
--- /dev/null
+++ b/flang/test/Semantics/OpenACC/acc-routine-loop-nesting.f90
@@ -0,0 +1,122 @@
+! RUN: %python %S/../test_errors.py %s %flang -fopenacc -Werror
+
+! OpenACC 3.4 2.15.1: a routine may parent a loop at its level or below.
+! A higher level is ignored. 2.9 uses the same order for nested loops.
+
+subroutine worker_ignores_gang(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine worker
+ !WARNING: GANG clause ignored in ACC ROUTINE WORKER procedure [-Wopenacc-usage]
+ !$acc loop gang worker vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine worker_keeps_worker_vector(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine worker
+ !$acc loop worker vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine vector_ignores_gang_and_worker(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine vector
+ !WARNING: GANG clause ignored in ACC ROUTINE VECTOR procedure [-Wopenacc-usage]
+ !$acc loop gang
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !WARNING: WORKER clause ignored in ACC ROUTINE VECTOR procedure [-Wopenacc-usage]
+ !$acc loop worker
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !$acc loop vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine seq_ignores_all(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine seq
+ !WARNING: GANG clause ignored in ACC ROUTINE SEQ procedure [-Wopenacc-usage]
+ !$acc loop gang
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !WARNING: WORKER clause ignored in ACC ROUTINE SEQ procedure [-Wopenacc-usage]
+ !$acc loop worker
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !WARNING: VECTOR clause ignored in ACC ROUTINE SEQ procedure [-Wopenacc-usage]
+ !$acc loop vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine gang1_ignores_gang2(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine gang(dim:1)
+ !WARNING: GANG(2) clause ignored in ACC ROUTINE GANG(1) procedure [-Wopenacc-usage]
+ !$acc loop gang(dim:2)
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !$acc loop gang
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !$acc loop worker vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine gang2_keeps_gang1(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine gang(dim:2)
+ !$acc loop gang(dim:1)
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+ !WARNING: GANG(3) clause ignored in ACC ROUTINE GANG(2) procedure [-Wopenacc-usage]
+ !$acc loop gang(dim:3)
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine device_type_worker_ignores_gang(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine device_type(nvidia) worker
+ !WARNING: GANG clause ignored in ACC ROUTINE WORKER procedure for DEVICE_TYPE(NVIDIA) [-Wopenacc-usage]
+ !$acc loop gang worker vector
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
+
+subroutine star_vector_ignores_worker(a)
+ real :: a(10)
+ integer :: i
+ !$acc routine device_type(*) vector
+ !WARNING: WORKER clause ignored in ACC ROUTINE VECTOR procedure for DEVICE_TYPE(*) [-Wopenacc-usage]
+ !$acc loop worker
+ do i = 1, 10
+ a(i) = a(i) + 1.0
+ end do
+end subroutine
``````````
</details>
https://github.com/llvm/llvm-project/pull/227041
More information about the flang-commits
mailing list