-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[flang][OpenMP] Generalize checks of loop construct structure #170735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/kparzysz/r01-loop-range
Are you sure you want to change the base?
[flang][OpenMP] Generalize checks of loop construct structure #170735
Conversation
For an OpenMP loop construct, count how many loops will effectively be contained in its associated block. For constructs that are loop-nest associated this number should be 1. Report cases where this number is different. Take into account that the block associated with a loop construct can contain compiler directives.
|
@llvm/pr-subscribers-flang-parser @llvm/pr-subscribers-flang-openmp Author: Krzysztof Parzyszek (kparzysz) ChangesFor an OpenMP loop construct, count how many loops will effectively be contained in its associated block. For constructs that are loop-nest associated this number should be 1. Report cases where this number is different. Take into account that the block associated with a loop construct can contain compiler directives. Patch is 21.23 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170735.diff 9 Files Affected:
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index fc4b9222d91b3..6414f0028e008 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -37,6 +37,14 @@
#include <tuple>
#include <variant>
+namespace Fortran::semantics {
+static bool IsLoopTransforming(llvm::omp::Directive dir);
+static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x);
+static std::optional<size_t> CountGeneratedLoops(
+ const parser::ExecutionPartConstruct &epc);
+static std::optional<size_t> CountGeneratedLoops(const parser::Block &block);
+} // namespace Fortran::semantics
+
namespace {
using namespace Fortran;
@@ -263,22 +271,19 @@ static bool IsLoopTransforming(llvm::omp::Directive dir) {
}
void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
- const parser::Block &body, size_t &nestedCount) {
+ const parser::Block &body) {
for (auto &stmt : body) {
if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) {
context_.Say(dir->source,
"Compiler directives are not allowed inside OpenMP loop constructs"_warn_en_US);
- } else if (parser::Unwrap<parser::DoConstruct>(stmt)) {
- ++nestedCount;
} else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(stmt)}) {
if (!IsLoopTransforming(omp->BeginDir().DirName().v)) {
context_.Say(omp->source,
"Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs"_err_en_US);
}
- ++nestedCount;
} else if (auto *block{parser::Unwrap<parser::BlockConstruct>(stmt)}) {
- CheckNestedBlock(x, std::get<parser::Block>(block->t), nestedCount);
- } else {
+ CheckNestedBlock(x, std::get<parser::Block>(block->t));
+ } else if (!parser::Unwrap<parser::DoConstruct>(stmt)) {
parser::CharBlock source{parser::GetSource(stmt).value_or(x.source)};
context_.Say(source,
"OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs"_err_en_US);
@@ -286,16 +291,96 @@ void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
}
}
+static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+
+ if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
+ return llvm::none_of(beginSpec.Clauses().v, [](const parser::OmpClause &c) {
+ return c.Id() == llvm::omp::Clause::OMPC_partial;
+ });
+ }
+ return false;
+}
+
+static std::optional<size_t> CountGeneratedLoops(
+ const parser::ExecutionPartConstruct &epc) {
+ if (parser::Unwrap<parser::DoConstruct>(epc)) {
+ return 1;
+ }
+
+ auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(epc))};
+ const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()};
+ llvm::omp::Directive dir{beginSpec.DirName().v};
+
+ // TODO: Handle split, apply.
+ if (IsFullUnroll(omp)) {
+ return std::nullopt;
+ }
+ if (dir == llvm::omp::Directive::OMPD_fuse) {
+ auto rangeAt{
+ llvm::find_if(beginSpec.Clauses().v, [](const parser::OmpClause &c) {
+ return c.Id() == llvm::omp::Clause::OMPC_looprange;
+ })};
+ if (rangeAt == beginSpec.Clauses().v.end()) {
+ return std::nullopt;
+ }
+
+ auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*rangeAt)};
+ std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))};
+ if (!count || *count <= 0) {
+ return std::nullopt;
+ }
+ if (auto nestedCount{CountGeneratedLoops(std::get<parser::Block>(omp.t))}) {
+ return 1 + *nestedCount - static_cast<size_t>(*count);
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ // For every other loop construct return 1.
+ return 1;
+}
+
+static std::optional<size_t> CountGeneratedLoops(const parser::Block &block) {
+ // Count the number of loops in the associated block. If there are any
+ // malformed construct in there, getting the number may be meaningless.
+ // These issues will be diagnosed elsewhere, and we should not emit any
+ // messages about a potentially incorrect loop count.
+ // In such cases reset the count to nullopt. Once it becomes nullopt,
+ // keep it that way.
+ std::optional<size_t> numLoops{0};
+ for (auto &epc : parser::omp::LoopRange(block)) {
+ if (auto genCount{CountGeneratedLoops(epc)}) {
+ *numLoops += *genCount;
+ } else {
+ numLoops = std::nullopt;
+ break;
+ }
+ }
+ return numLoops;
+}
+
void OmpStructureChecker::CheckNestedConstruct(
const parser::OpenMPLoopConstruct &x) {
- size_t nestedCount{0};
-
+ const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
auto &body{std::get<parser::Block>(x.t)};
- if (body.empty()) {
- context_.Say(x.source,
- "OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
- } else {
- CheckNestedBlock(x, body, nestedCount);
+
+ CheckNestedBlock(x, body);
+
+ // Check if a loop-nest-associated construct has only one top-level loop
+ // in it.
+ if (std::optional<size_t> numLoops{CountGeneratedLoops(body)}) {
+ if (*numLoops == 0) {
+ context_.Say(beginSpec.DirName().source,
+ "This construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US);
+ } else {
+ auto assoc{llvm::omp::getDirectiveAssociation(beginSpec.DirName().v)};
+ if (*numLoops > 1 && assoc == llvm::omp::Association::LoopNest) {
+ context_.Say(beginSpec.DirName().source,
+ "This construct applies to a loop nest, but has a loop sequence of length %zu"_err_en_US,
+ *numLoops);
+ }
+ }
}
}
@@ -304,16 +389,9 @@ void OmpStructureChecker::CheckFullUnroll(
// If the nested construct is a full unroll, then this construct is invalid
// since it won't contain a loop.
if (const parser::OpenMPLoopConstruct *nested{x.GetNestedConstruct()}) {
- auto &nestedSpec{nested->BeginDir()};
- if (nestedSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) {
- bool isPartial{
- llvm::any_of(nestedSpec.Clauses().v, [](const parser::OmpClause &c) {
- return c.Id() == llvm::omp::Clause::OMPC_partial;
- })};
- if (!isPartial) {
- context_.Say(x.source,
- "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
- }
+ if (IsFullUnroll(*nested)) {
+ context_.Say(x.source,
+ "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US);
}
}
}
@@ -387,11 +465,6 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
CheckDistLinear(x);
}
- if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
- CheckLooprangeBounds(x);
- } else {
- CheckNestedFuse(x);
- }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -531,57 +604,20 @@ void OmpStructureChecker::CheckDistLinear(
void OmpStructureChecker::CheckLooprangeBounds(
const parser::OpenMPLoopConstruct &x) {
- const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
- if (clauseList.v.empty()) {
- return;
- }
- for (auto &clause : clauseList.v) {
- if (const auto *lrClause{
- std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
- auto first{GetIntValue(std::get<0>((lrClause->v).t))};
- auto count{GetIntValue(std::get<1>((lrClause->v).t))};
- if (!first || !count) {
- return;
- }
- auto &loopConsList{std::get<parser::Block>(x.t)};
- if (*first > 0 && *count > 0 &&
- loopConsList.size() < (unsigned)(*first + *count - 1)) {
- context_.Say(clause.source,
- "The loop range indicated in the %s clause must not be out of the bounds of the Loop Sequence following the construct."_err_en_US,
- parser::ToUpperCaseLetters(clause.source.ToString()));
- }
- return;
- }
- }
-}
-
-void OmpStructureChecker::CheckNestedFuse(
- const parser::OpenMPLoopConstruct &x) {
- auto &loopConsList{std::get<parser::Block>(x.t)};
- if (loopConsList.empty()) {
- return;
- }
- const auto *ompConstruct{parser::omp::GetOmpLoop(loopConsList.front())};
- if (!ompConstruct) {
- return;
- }
- const parser::OmpClauseList &clauseList{ompConstruct->BeginDir().Clauses()};
- if (clauseList.v.empty()) {
- return;
- }
- for (auto &clause : clauseList.v) {
- if (const auto *lrClause{
- std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
- auto count{GetIntValue(std::get<1>((lrClause->v).t))};
- if (!count) {
+ for (const parser::OmpClause &clause : x.BeginDir().Clauses().v) {
+ if (auto *lrClause{parser::Unwrap<parser::OmpLooprangeClause>(clause)}) {
+ auto first{GetIntValue(std::get<0>(lrClause->t))};
+ auto count{GetIntValue(std::get<1>(lrClause->t))};
+ if (!first || !count || *first <= 0 || *count <= 0) {
return;
}
- auto &nestedLoopConsList{std::get<parser::Block>(ompConstruct->t)};
- if (nestedLoopConsList.size() > (unsigned)(*count)) {
- context_.Say(x.BeginDir().DirName().source,
- "The loop sequence following the %s construct must be fully fused first."_err_en_US,
- parser::ToUpperCaseLetters(
- x.BeginDir().DirName().source.ToString()));
+ auto requiredCount{static_cast<size_t>(*first + *count - 1)};
+ if (auto loopCount{CountGeneratedLoops(std::get<parser::Block>(x.t))}) {
+ if (*loopCount < requiredCount) {
+ context_.Say(clause.source,
+ "The specified loop range requires %zu loops, but the loop sequence has a length of %zu"_err_en_US,
+ requiredCount, *loopCount);
+ }
}
return;
}
@@ -625,18 +661,21 @@ void OmpStructureChecker::CheckScanModifier(
}
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
- const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
+ const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
// A few semantic checks for InScan reduction are performed below as SCAN
// constructs inside LOOP may add the relevant information. Scan reduction is
// supported only in loop constructs, so same checks are not applicable to
// other directives.
- for (const auto &clause : clauseList.v) {
+ for (const auto &clause : beginSpec.Clauses().v) {
if (auto *reduction{std::get_if<parser::OmpClause::Reduction>(&clause.u)}) {
CheckScanModifier(*reduction);
}
}
- if (llvm::omp::allSimdSet.test(GetContext().directive)) {
+ if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_fuse) {
+ CheckLooprangeBounds(x);
+ }
+ if (llvm::omp::allSimdSet.test(beginSpec.DirName().v)) {
ExitDirectiveNest(SIMDNest);
}
dirContext_.pop_back();
@@ -782,8 +821,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Sizes &c) {
void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
auto &[first, count]{x.v.t};
- RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
+ RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
}
void OmpStructureChecker::Enter(const parser::DoConstruct &x) {
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 5bd5ae050be64..267362b6325f1 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -323,11 +323,10 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void CheckScanModifier(const parser::OmpClause::Reduction &x);
void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
- void CheckNestedFuse(const parser::OpenMPLoopConstruct &x);
void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
void CheckSIMDNest(const parser::OpenMPConstruct &x);
void CheckNestedBlock(const parser::OpenMPLoopConstruct &x,
- const parser::Block &body, size_t &nestedCount);
+ const parser::Block &body);
void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x);
void CheckFullUnroll(const parser::OpenMPLoopConstruct &x);
void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/test/Parser/OpenMP/tile-fail.f90 b/flang/test/Parser/OpenMP/tile-fail.f90
index a69261a927961..d5ff39cd1037c 100644
--- a/flang/test/Parser/OpenMP/tile-fail.f90
+++ b/flang/test/Parser/OpenMP/tile-fail.f90
@@ -1,7 +1,7 @@
! RUN: split-file %s %t
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90
-! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90
!--- stray_end1.f90
@@ -25,7 +25,7 @@ subroutine stray_end2
!--- stray_begin.f90
subroutine stray_begin
- !CHECK: error: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !CHECK: error: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp tile sizes(2)
end subroutine
diff --git a/flang/test/Semantics/OpenMP/do21.f90 b/flang/test/Semantics/OpenMP/do21.f90
index e6fe7dd39dd3e..683118a5b2182 100644
--- a/flang/test/Semantics/OpenMP/do21.f90
+++ b/flang/test/Semantics/OpenMP/do21.f90
@@ -2,26 +2,26 @@
! Check for existence of loop following a DO directive
subroutine do1
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp do
end subroutine
subroutine do2
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp parallel do
end subroutine
subroutine do3
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp simd
end subroutine
subroutine do4
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp do simd
end subroutine
subroutine do5
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp loop
end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90
index 4e63cafb3fda1..4e6cf4ce13486 100644
--- a/flang/test/Semantics/OpenMP/loop-association.f90
+++ b/flang/test/Semantics/OpenMP/loop-association.f90
@@ -103,7 +103,7 @@
!$omp parallel do private(c)
do i = 1, N
do j = 1, N
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp parallel do shared(b)
a = 3.14
enddo
@@ -123,7 +123,7 @@
!ERROR: Misplaced OpenMP end-directive
!$omp end parallel do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp parallel do private(c)
5 FORMAT (1PE12.4, I10)
do i=1, N
@@ -140,7 +140,7 @@
!ERROR: Misplaced OpenMP end-directive
!$omp end parallel do simd
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp simd
a = i + 1
!ERROR: Misplaced OpenMP end-directive
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 9ca0e8cfc9af1..5e3d32d7c6eff 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -20,7 +20,7 @@ subroutine loop_transformation_construct1
end do
!$omp end fuse
- !ERROR: The loop range indicated in the LOOPRANGE(5,2) clause must not be out of the bounds of the Loop Sequence following the construct.
+ !ERROR: The specified loop range requires 6 loops, but the loop sequence has a length of 2
!$omp fuse looprange(5,2)
do x = 1, i
v(x) = x * 2
@@ -63,4 +63,18 @@ subroutine loop_transformation_construct1
v(x) = x * 2
end do
!$omp end fuse
+
+ ! This is ok aside from the warnings about compiler directives
+ !$omp fuse looprange(1,3)
+ do x = 1, 10; end do ! 1 loop
+ !WARNING: Compiler directives are not allowed inside OpenMP loop constructs
+ !dir$ novector
+ !$omp fuse looprange(1,2) ! 2 loops
+ do x = 1, 10; end do
+ !WARNING: Compiler directives are not allowed inside OpenMP loop constructs
+ !dir$ nounroll
+ do x = 1, 10; end do
+ do x = 1, 10; end do
+ !$omp end fuse
+ !$omp end fuse
end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index caa8f3f216fec..4eeb7330ea589 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -7,7 +7,7 @@ subroutine loop_transformation_construct1
!ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
!$omp do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp unroll
end subroutine
@@ -51,7 +51,7 @@ subroutine loop_transformation_construct4
do x = 1, i
v(x) = v(x) * 2
end do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!ERROR: At least one of SIZES clause must appear on the TILE directive
!$omp tile
end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 1b15c938915cd..25247c3896cae 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -7,7 +7,7 @@ subroutine loop_transformation_construct1
implicit none
!$omp do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp fuse
end subroutine
@@ -15,7 +15,7 @@ subroutine loop_transformation_construct2
implicit none
!$omp do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp fuse
!$omp end fuse
end subroutine
@@ -50,7 +50,7 @@ subroutine loop_transformation_construct4
do x = 1, i
v(x) = v(x) * 2
end do
- !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+ !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
!$omp fuse
!$omp end fuse
end subroutine
@@ -80,7 +80,7 @@ subroutine loop_transformation_const...
[truncated]
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
…loop-sequence-length
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
|
Ping |
For an OpenMP loop construct, count how many loops will effectively be contained in its associated block. For constructs that are loop-nest associated this number should be 1. Report cases where this number is different.
Take into account that the block associated with a loop construct can contain compiler directives.