[flang-commits] [flang] [flang][acc] Warn when a loop is above its routine parallelism level (PR #227041)

Razvan Lupusoru via flang-commits flang-commits at lists.llvm.org
Mon Sep 28 09:50:21 PDT 2026


https://github.com/razvanlupusoru created https://github.com/llvm/llvm-project/pull/227041

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.

>From 7c97e602861e082eeb14043037aad6cc06d4e00a Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Mon, 28 Sep 2026 09:23:50 -0700
Subject: [PATCH] [flang][acc] Warn when a loop is above its routine
 parallelism level

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.
---
 flang/lib/Semantics/check-acc-structure.cpp   | 197 +++++++++++++++---
 flang/lib/Semantics/check-acc-structure.h     |   5 +
 .../OpenACC/acc-routine-loop-nesting.f90      | 122 +++++++++++
 3 files changed, 291 insertions(+), 33 deletions(-)
 create mode 100644 flang/test/Semantics/OpenACC/acc-routine-loop-nesting.f90

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



More information about the flang-commits mailing list