Skip to content

Commit ec91d6b

Browse files
authored
[Flang] Add perfect-nest and rectangular-loop semantic tests (#160283)
Add semantic tests of currently unsupported OpenMP canonical loops: * non-perfectly nested canonical loop nests * non-rectangular canonical loop nests Both were introduced in OpenMP 5.0 and are not yet supported by Flang. The message "Trip count must be computable and invariant" is the same that OpenACC emits for non-rectangular loops in `AccAttributeVisitor::CheckAssociatedLoop`. I considered reusing the code, but calls OpenACC-only methods and has different behavior (e.g. symbol resolution and does not check the step operand)
1 parent 45ce887 commit ec91d6b

File tree

4 files changed

+226
-6
lines changed

4 files changed

+226
-6
lines changed

flang/lib/Semantics/resolve-directives.cpp

Lines changed: 151 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,24 @@ template <typename T> class DirectiveAttributeVisitor {
149149
dataSharingAttributeObjects_.clear();
150150
}
151151
bool HasDataSharingAttributeObject(const Symbol &);
152+
153+
/// Extract the iv and bounds of a DO loop:
154+
/// 1. The loop index/induction variable
155+
/// 2. The lower bound
156+
/// 3. The upper bound
157+
/// 4. The step/increment (or nullptr if not present)
158+
///
159+
/// Each returned tuple value can be nullptr if not present. Diagnoses an
160+
/// error if the the DO loop is a DO WHILE or DO CONCURRENT loop.
161+
std::tuple<const parser::Name *, const parser::ScalarExpr *,
162+
const parser::ScalarExpr *, const parser::ScalarExpr *>
163+
GetLoopBounds(const parser::DoConstruct &);
164+
165+
/// Extract the loop index/induction variable from a DO loop. Diagnoses an
166+
/// error if the the DO loop is a DO WHILE or DO CONCURRENT loop and returns
167+
/// nullptr.
152168
const parser::Name *GetLoopIndex(const parser::DoConstruct &);
169+
153170
const parser::DoConstruct *GetDoConstructIf(
154171
const parser::ExecutionPartConstruct &);
155172
Symbol *DeclareNewAccessEntity(const Symbol &, Symbol::Flag, Scope &);
@@ -953,6 +970,13 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
953970
privateDataSharingAttributeObjects_.clear();
954971
}
955972

973+
/// Check that loops in the loop nest are perfectly nested, as well that lower
974+
/// bound, upper bound, and step expressions do not use the iv
975+
/// of a surrounding loop of the associated loops nest.
976+
/// We do not support non-perfectly nested loops not non-rectangular loops yet
977+
/// (both introduced in OpenMP 5.0)
978+
void CheckPerfectNestAndRectangularLoop(const parser::OpenMPLoopConstruct &x);
979+
956980
// Predetermined DSA rules
957981
void PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
958982
const parser::OpenMPLoopConstruct &);
@@ -1028,23 +1052,30 @@ bool DirectiveAttributeVisitor<T>::HasDataSharingAttributeObject(
10281052
}
10291053

10301054
template <typename T>
1031-
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
1032-
const parser::DoConstruct &x) {
1055+
std::tuple<const parser::Name *, const parser::ScalarExpr *,
1056+
const parser::ScalarExpr *, const parser::ScalarExpr *>
1057+
DirectiveAttributeVisitor<T>::GetLoopBounds(const parser::DoConstruct &x) {
10331058
using Bounds = parser::LoopControl::Bounds;
10341059
if (x.GetLoopControl()) {
10351060
if (const Bounds * b{std::get_if<Bounds>(&x.GetLoopControl()->u)}) {
1036-
return &b->name.thing;
1037-
} else {
1038-
return nullptr;
1061+
auto &step = b->step;
1062+
return {&b->name.thing, &b->lower, &b->upper,
1063+
step.has_value() ? &step.value() : nullptr};
10391064
}
10401065
} else {
10411066
context_
10421067
.Say(std::get<parser::Statement<parser::NonLabelDoStmt>>(x.t).source,
10431068
"Loop control is not present in the DO LOOP"_err_en_US)
10441069
.Attach(GetContext().directiveSource,
10451070
"associated with the enclosing LOOP construct"_en_US);
1046-
return nullptr;
10471071
}
1072+
return {nullptr, nullptr, nullptr, nullptr};
1073+
}
1074+
1075+
template <typename T>
1076+
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
1077+
const parser::DoConstruct &x) {
1078+
return std::get<const parser::Name *>(GetLoopBounds(x));
10481079
}
10491080

10501081
template <typename T>
@@ -1990,6 +2021,10 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
19902021
}
19912022
}
19922023
}
2024+
2025+
// Must be done before iv privatization
2026+
CheckPerfectNestAndRectangularLoop(x);
2027+
19932028
PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
19942029
ordCollapseLevel = GetNumAffectedLoopsFromLoopConstruct(x) + 1;
19952030
return true;
@@ -2185,6 +2220,116 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses(
21852220
}
21862221
}
21872222

2223+
void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
2224+
const parser::OpenMPLoopConstruct &x) {
2225+
auto &dirContext{GetContext()};
2226+
std::int64_t dirDepth{dirContext.associatedLoopLevel};
2227+
if (dirDepth <= 0)
2228+
return;
2229+
2230+
auto checkExprHasSymbols = [&](llvm::SmallVector<Symbol *> &ivs,
2231+
const parser::ScalarExpr *bound) {
2232+
if (ivs.empty())
2233+
return;
2234+
auto boundExpr{semantics::AnalyzeExpr(context_, *bound)};
2235+
if (!boundExpr)
2236+
return;
2237+
semantics::UnorderedSymbolSet boundSyms{
2238+
evaluate::CollectSymbols(*boundExpr)};
2239+
if (boundSyms.empty())
2240+
return;
2241+
for (Symbol *iv : ivs) {
2242+
if (boundSyms.count(*iv) != 0) {
2243+
// TODO: Point to occurence of iv in boundExpr, directiveSource as a
2244+
// note
2245+
context_.Say(dirContext.directiveSource,
2246+
"Trip count must be computable and invariant"_err_en_US);
2247+
}
2248+
}
2249+
};
2250+
2251+
// Find the associated region by skipping nested loop-associated constructs
2252+
// such as loop transformations
2253+
const parser::NestedConstruct *innermostAssocRegion{nullptr};
2254+
const parser::OpenMPLoopConstruct *innermostConstruct{&x};
2255+
while (const auto &innerAssocStmt{
2256+
std::get<std::optional<parser::NestedConstruct>>(
2257+
innermostConstruct->t)}) {
2258+
innermostAssocRegion = &(innerAssocStmt.value());
2259+
if (const auto *innerConstruct{
2260+
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
2261+
innermostAssocRegion)}) {
2262+
innermostConstruct = &innerConstruct->value();
2263+
} else {
2264+
break;
2265+
}
2266+
}
2267+
2268+
if (!innermostAssocRegion)
2269+
return;
2270+
const auto &outer{std::get_if<parser::DoConstruct>(innermostAssocRegion)};
2271+
if (!outer)
2272+
return;
2273+
2274+
llvm::SmallVector<Symbol *> ivs;
2275+
int curLevel{0};
2276+
const parser::DoConstruct *loop{outer};
2277+
while (true) {
2278+
auto [iv, lb, ub, step] = GetLoopBounds(*loop);
2279+
2280+
if (lb)
2281+
checkExprHasSymbols(ivs, lb);
2282+
if (ub)
2283+
checkExprHasSymbols(ivs, ub);
2284+
if (step)
2285+
checkExprHasSymbols(ivs, step);
2286+
if (iv) {
2287+
if (auto *symbol{currScope().FindSymbol(iv->source)})
2288+
ivs.push_back(symbol);
2289+
}
2290+
2291+
// Stop after processing all affected loops
2292+
if (curLevel + 1 >= dirDepth)
2293+
break;
2294+
2295+
// Recurse into nested loop
2296+
const auto &block{std::get<parser::Block>(loop->t)};
2297+
if (block.empty()) {
2298+
// Insufficient number of nested loops already reported by
2299+
// CheckAssocLoopLevel()
2300+
break;
2301+
}
2302+
2303+
loop = GetDoConstructIf(block.front());
2304+
if (!loop) {
2305+
// Insufficient number of nested loops already reported by
2306+
// CheckAssocLoopLevel()
2307+
break;
2308+
}
2309+
2310+
auto checkPerfectNest = [&, this]() {
2311+
auto blockSize = block.size();
2312+
if (blockSize <= 1)
2313+
return;
2314+
2315+
if (parser::Unwrap<parser::ContinueStmt>(x))
2316+
blockSize -= 1;
2317+
2318+
if (blockSize <= 1)
2319+
return;
2320+
2321+
// Non-perfectly nested loop
2322+
// TODO: Point to non-DO statement, directiveSource as a note
2323+
context_.Say(dirContext.directiveSource,
2324+
"Canonical loop nest must be perfectly nested."_err_en_US);
2325+
};
2326+
2327+
checkPerfectNest();
2328+
2329+
++curLevel;
2330+
}
2331+
}
2332+
21882333
// 2.15.1.1 Data-sharing Attribute Rules - Predetermined
21892334
// - The loop iteration variable(s) in the associated do-loop(s) of a do,
21902335
// parallel do, taskloop, or distribute construct is (are) private.

flang/test/Semantics/OpenMP/do08.f90

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ program omp
6161
!$omp end do
6262

6363

64+
!ERROR: Canonical loop nest must be perfectly nested.
6465
!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
6566
!$omp do collapse(3)
6667
do 60 i=2,200,2

flang/test/Semantics/OpenMP/do13.f90

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ program omp
5959
!$omp end do
6060

6161

62+
!ERROR: Canonical loop nest must be perfectly nested.
6263
!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
6364
!$omp do collapse(3)
6465
do 60 i=1,10
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
! RUN: %python %S/../test_errors.py %s %flang -fopenmp
2+
! Check for existence of loop following a DO directive
3+
4+
subroutine do_imperfectly_nested_before
5+
integer i, j
6+
7+
!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
8+
!$omp do collapse(2)
9+
do i = 1, 10
10+
print *, i
11+
do j = 1, 10
12+
print *, i, j
13+
end do
14+
end do
15+
!$omp end do
16+
end subroutine
17+
18+
19+
subroutine do_imperfectly_nested_behind
20+
integer i, j
21+
22+
!ERROR: Canonical loop nest must be perfectly nested.
23+
!$omp do collapse(2)
24+
do i = 1, 10
25+
do j = 1, 10
26+
print *, i, j
27+
end do
28+
print *, i
29+
end do
30+
!$omp end do
31+
end subroutine
32+
33+
34+
subroutine do_nonrectangular_lb
35+
integer i, j
36+
37+
!ERROR: Trip count must be computable and invariant
38+
!$omp do collapse(2)
39+
do i = 1, 10
40+
do j = i, 10
41+
print *, i, j
42+
end do
43+
end do
44+
!$omp end do
45+
end subroutine
46+
47+
48+
subroutine do_nonrectangular_ub
49+
integer i, j
50+
51+
!ERROR: Trip count must be computable and invariant
52+
!$omp do collapse(2)
53+
do i = 1, 10
54+
do j = 0, i
55+
print *, i, j
56+
end do
57+
end do
58+
!$omp end do
59+
end subroutine
60+
61+
62+
subroutine do_nonrectangular_step
63+
integer i, j
64+
65+
!ERROR: Trip count must be computable and invariant
66+
!$omp do collapse(2)
67+
do i = 1, 10
68+
do j = 1, 10, i
69+
print *, i, j
70+
end do
71+
end do
72+
!$omp end do
73+
end subroutine

0 commit comments

Comments
 (0)