Skip to content

Commit 303ca72

Browse files
Meinersburronlieb
authored andcommitted
[Flang] Add perfect-nest and rectangular-loop semantic tests (llvm#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 f1b559d commit 303ca72

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 &);
@@ -1023,23 +1047,30 @@ bool DirectiveAttributeVisitor<T>::HasDataSharingAttributeObject(
10231047
}
10241048

10251049
template <typename T>
1026-
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
1027-
const parser::DoConstruct &x) {
1050+
std::tuple<const parser::Name *, const parser::ScalarExpr *,
1051+
const parser::ScalarExpr *, const parser::ScalarExpr *>
1052+
DirectiveAttributeVisitor<T>::GetLoopBounds(const parser::DoConstruct &x) {
10281053
using Bounds = parser::LoopControl::Bounds;
10291054
if (x.GetLoopControl()) {
10301055
if (const Bounds * b{std::get_if<Bounds>(&x.GetLoopControl()->u)}) {
1031-
return &b->name.thing;
1032-
} else {
1033-
return nullptr;
1056+
auto &step = b->step;
1057+
return {&b->name.thing, &b->lower, &b->upper,
1058+
step.has_value() ? &step.value() : nullptr};
10341059
}
10351060
} else {
10361061
context_
10371062
.Say(std::get<parser::Statement<parser::NonLabelDoStmt>>(x.t).source,
10381063
"Loop control is not present in the DO LOOP"_err_en_US)
10391064
.Attach(GetContext().directiveSource,
10401065
"associated with the enclosing LOOP construct"_en_US);
1041-
return nullptr;
10421066
}
1067+
return {nullptr, nullptr, nullptr, nullptr};
1068+
}
1069+
1070+
template <typename T>
1071+
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
1072+
const parser::DoConstruct &x) {
1073+
return std::get<const parser::Name *>(GetLoopBounds(x));
10431074
}
10441075

10451076
template <typename T>
@@ -1985,6 +2016,10 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
19852016
}
19862017
}
19872018
}
2019+
2020+
// Must be done before iv privatization
2021+
CheckPerfectNestAndRectangularLoop(x);
2022+
19882023
PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
19892024
ordCollapseLevel = GetNumAffectedLoopsFromLoopConstruct(x) + 1;
19902025
return true;
@@ -2180,6 +2215,116 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses(
21802215
}
21812216
}
21822217

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