Skip to content

Commit 2d318c6

Browse files
committed
[clang][OpenMP] Diagnose badly-formed collapsed imperfect loop nests (#60678)
This patch fixes a couple of cases where Clang aborts with loop nests that are being collapsed (via the relevant OpenMP clause) into a new, combined loop. The problematic cases happen when a variable declared within the loop nest is used in the (init, condition, iter) statement of a more deeply-nested loop. I don't think these cases (generally?) fall under the non-rectangular loop nest rules as defined in OpenMP 5.0+, but I could be wrong (and anyway, emitting an error is better than crashing). In terms of implementation: the crash happens because (to a first approximation) all the loop bounds calculations are pulled out to the start of the new, combined loop, but variables declared in the loop nest "haven't been seen yet". I believe there is special handling for iteration variables declared in "for" init statements, but not for variables declared elsewhere in the "imperfect" parts of a loop nest. So, this patch tries to diagnose the troublesome cases before they can cause a crash. This is slightly awkward because at the point where we want to do the diagnosis (SemaOpenMP.cpp), we don't have scope information readily available. Instead we "manually" scan through the AST of the loop nest looking for var decls (ForVarDeclFinder), then we ensure we're not using any of those in loop control subexprs (ForSubExprChecker). All that is only done when we have a "collapse" clause. Range-for loops can also cause crashes at present without this patch, so are handled too.
1 parent feeb833 commit 2d318c6

File tree

5 files changed

+256
-9
lines changed

5 files changed

+256
-9
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11152,6 +11152,8 @@ def err_omp_loop_diff_cxx : Error<
1115211152
"upper and lower loop bounds">;
1115311153
def err_omp_loop_cannot_use_stmt : Error<
1115411154
"'%0' statement cannot be used in OpenMP for loop">;
11155+
def err_omp_loop_bad_collapse_var : Error<
11156+
"cannot use variable %1 in collapsed imperfectly-nested loop %select{init|condition|increment}0 statement">;
1115511157
def err_omp_simd_region_cannot_use_stmt : Error<
1115611158
"'%0' statement cannot be used in OpenMP simd region">;
1115711159
def warn_omp_loop_64_bit_var : Warning<

clang/lib/AST/StmtOpenMP.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#include "clang/AST/ASTContext.h"
1413
#include "clang/AST/StmtOpenMP.h"
14+
#include "clang/AST/ASTContext.h"
15+
#include "clang/AST/RecursiveASTVisitor.h"
1516

1617
using namespace clang;
1718
using namespace llvm::omp;

clang/lib/Sema/SemaOpenMP.cpp

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/AST/DeclCXX.h"
2222
#include "clang/AST/DeclOpenMP.h"
2323
#include "clang/AST/OpenMPClause.h"
24+
#include "clang/AST/RecursiveASTVisitor.h"
2425
#include "clang/AST/StmtCXX.h"
2526
#include "clang/AST/StmtOpenMP.h"
2627
#include "clang/AST/StmtVisitor.h"
@@ -7668,6 +7669,52 @@ struct LoopIterationSpace final {
76687669
Expr *FinalCondition = nullptr;
76697670
};
76707671

7672+
/// Scan an AST subtree, checking that no decls in the CollapsedLoopVarDecls
7673+
/// set are referenced. Used for verifying loop nest structure before
7674+
/// performing a loop collapse operation.
7675+
class ForSubExprChecker final : public RecursiveASTVisitor<ForSubExprChecker> {
7676+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls;
7677+
VarDecl *ForbiddenVar = nullptr;
7678+
SourceRange ErrLoc;
7679+
7680+
public:
7681+
explicit ForSubExprChecker(
7682+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls)
7683+
: CollapsedLoopVarDecls(CollapsedLoopVarDecls) {}
7684+
7685+
// We want to visit implicit code, i.e. synthetic initialisation statements
7686+
// created during range-for lowering.
7687+
bool shouldVisitImplicitCode() const { return true; }
7688+
7689+
bool VisitDeclRefExpr(DeclRefExpr *E) {
7690+
ValueDecl *VD = E->getDecl();
7691+
if (!isa<VarDecl, BindingDecl>(VD))
7692+
return true;
7693+
VarDecl *V = VD->getPotentiallyDecomposedVarDecl();
7694+
if (V->getType()->isReferenceType()) {
7695+
VarDecl *VD = V->getDefinition();
7696+
if (VD->hasInit()) {
7697+
Expr *I = VD->getInit();
7698+
DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(I);
7699+
if (!DRE)
7700+
return true;
7701+
V = DRE->getDecl()->getPotentiallyDecomposedVarDecl();
7702+
}
7703+
}
7704+
Decl *Canon = V->getCanonicalDecl();
7705+
if (CollapsedLoopVarDecls.contains(Canon)) {
7706+
ForbiddenVar = V;
7707+
ErrLoc = E->getSourceRange();
7708+
return false;
7709+
}
7710+
7711+
return true;
7712+
}
7713+
7714+
VarDecl *getForbiddenVar() const { return ForbiddenVar; }
7715+
SourceRange getErrRange() const { return ErrLoc; }
7716+
};
7717+
76717718
/// Helper class for checking canonical form of the OpenMP loops and
76727719
/// extracting iteration space of each loop in the loop nest, that will be used
76737720
/// for IR generation.
@@ -7682,6 +7729,8 @@ class OpenMPIterationSpaceChecker {
76827729
SourceLocation DefaultLoc;
76837730
/// A location for diagnostics (when increment is not compatible).
76847731
SourceLocation ConditionLoc;
7732+
/// The set of variables declared within the (to be collapsed) loop nest.
7733+
const llvm::SmallPtrSetImpl<const Decl *> *CollapsedLoopVarDecls;
76857734
/// A source location for referring to loop init later.
76867735
SourceRange InitSrcRange;
76877736
/// A source location for referring to condition later.
@@ -7725,10 +7774,13 @@ class OpenMPIterationSpaceChecker {
77257774
Expr *Condition = nullptr;
77267775

77277776
public:
7728-
OpenMPIterationSpaceChecker(Sema &SemaRef, bool SupportsNonRectangular,
7729-
DSAStackTy &Stack, SourceLocation DefaultLoc)
7777+
OpenMPIterationSpaceChecker(
7778+
Sema &SemaRef, bool SupportsNonRectangular, DSAStackTy &Stack,
7779+
SourceLocation DefaultLoc,
7780+
const llvm::SmallPtrSetImpl<const Decl *> *CollapsedLoopDecls)
77307781
: SemaRef(SemaRef), SupportsNonRectangular(SupportsNonRectangular),
7731-
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc) {}
7782+
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc),
7783+
CollapsedLoopVarDecls(CollapsedLoopDecls) {}
77327784
/// Check init-expr for canonical loop form and save loop counter
77337785
/// variable - #Var and its initialization value - #LB.
77347786
bool checkAndSetInit(Stmt *S, bool EmitDiags = true);
@@ -8049,6 +8101,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
80498101
if (!ExprTemp->cleanupsHaveSideEffects())
80508102
S = ExprTemp->getSubExpr();
80518103

8104+
if (CollapsedLoopVarDecls) {
8105+
ForSubExprChecker FSEC{*CollapsedLoopVarDecls};
8106+
if (!FSEC.TraverseStmt(S)) {
8107+
SourceRange Range = FSEC.getErrRange();
8108+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8109+
<< Range.getEnd() << 0 << FSEC.getForbiddenVar();
8110+
return true;
8111+
}
8112+
}
8113+
80528114
InitSrcRange = S->getSourceRange();
80538115
if (Expr *E = dyn_cast<Expr>(S))
80548116
S = E->IgnoreParens();
@@ -8152,6 +8214,17 @@ bool OpenMPIterationSpaceChecker::checkAndSetCond(Expr *S) {
81528214
}
81538215
Condition = S;
81548216
S = getExprAsWritten(S);
8217+
8218+
if (CollapsedLoopVarDecls) {
8219+
ForSubExprChecker FSEC{*CollapsedLoopVarDecls};
8220+
if (!FSEC.TraverseStmt(S)) {
8221+
SourceRange Range = FSEC.getErrRange();
8222+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8223+
<< Range.getEnd() << 1 << FSEC.getForbiddenVar();
8224+
return true;
8225+
}
8226+
}
8227+
81558228
SourceLocation CondLoc = S->getBeginLoc();
81568229
auto &&CheckAndSetCond =
81578230
[this, IneqCondIsCanonical](BinaryOperatorKind Opcode, const Expr *LHS,
@@ -8250,6 +8323,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInc(Expr *S) {
82508323
if (!ExprTemp->cleanupsHaveSideEffects())
82518324
S = ExprTemp->getSubExpr();
82528325

8326+
if (CollapsedLoopVarDecls) {
8327+
ForSubExprChecker FSEC{*CollapsedLoopVarDecls};
8328+
if (!FSEC.TraverseStmt(S)) {
8329+
SourceRange Range = FSEC.getErrRange();
8330+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8331+
<< Range.getEnd() << 2 << FSEC.getForbiddenVar();
8332+
return true;
8333+
}
8334+
}
8335+
82538336
IncrementSrcRange = S->getSourceRange();
82548337
S = S->IgnoreParens();
82558338
if (auto *UO = dyn_cast<UnaryOperator>(S)) {
@@ -8972,7 +9055,7 @@ void SemaOpenMP::ActOnOpenMPLoopInitialization(SourceLocation ForLoc,
89729055

89739056
DSAStack->loopStart();
89749057
OpenMPIterationSpaceChecker ISC(SemaRef, /*SupportsNonRectangular=*/true,
8975-
*DSAStack, ForLoc);
9058+
*DSAStack, ForLoc, nullptr);
89769059
if (!ISC.checkAndSetInit(Init, /*EmitDiags=*/false)) {
89779060
if (ValueDecl *D = ISC.getLoopDecl()) {
89789061
auto *VD = dyn_cast<VarDecl>(D);
@@ -9069,7 +9152,8 @@ static bool checkOpenMPIterationSpace(
90699152
Expr *OrderedLoopCountExpr,
90709153
SemaOpenMP::VarsWithInheritedDSAType &VarsWithImplicitDSA,
90719154
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
9072-
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures) {
9155+
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
9156+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls) {
90739157
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
90749158
// OpenMP [2.9.1, Canonical Loop Form]
90759159
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9108,7 +9192,8 @@ static bool checkOpenMPIterationSpace(
91089192
return false;
91099193

91109194
OpenMPIterationSpaceChecker ISC(SemaRef, SupportsNonRectangular, DSA,
9111-
For ? For->getForLoc() : CXXFor->getForLoc());
9195+
For ? For->getForLoc() : CXXFor->getForLoc(),
9196+
&CollapsedLoopVarDecls);
91129197

91139198
// Check init.
91149199
Stmt *Init = For ? For->getInit() : CXXFor->getBeginStmt();
@@ -9475,6 +9560,39 @@ static Expr *buildPostUpdate(Sema &S, ArrayRef<Expr *> PostUpdates) {
94759560
return PostUpdate;
94769561
}
94779562

9563+
/// Look for variables declared in the body parts of a for-loop nest. Used
9564+
/// for verifying loop nest structure before performing a loop collapse
9565+
/// operation.
9566+
class ForVarDeclFinder final : public RecursiveASTVisitor<ForVarDeclFinder> {
9567+
int NestingDepth = 0;
9568+
llvm::SmallPtrSetImpl<const Decl *> &VarDecls;
9569+
9570+
public:
9571+
explicit ForVarDeclFinder(llvm::SmallPtrSetImpl<const Decl *> &VD)
9572+
: VarDecls(VD) {}
9573+
9574+
bool VisitForStmt(ForStmt *F) {
9575+
++NestingDepth;
9576+
TraverseStmt(F->getBody());
9577+
--NestingDepth;
9578+
return false;
9579+
}
9580+
9581+
bool VisitCXXForRangeStmt(CXXForRangeStmt *RF) {
9582+
++NestingDepth;
9583+
TraverseStmt(RF->getBody());
9584+
--NestingDepth;
9585+
return false;
9586+
}
9587+
9588+
bool VisitVarDecl(VarDecl *D) {
9589+
Decl *C = D->getCanonicalDecl();
9590+
if (NestingDepth > 0)
9591+
VarDecls.insert(C);
9592+
return true;
9593+
}
9594+
};
9595+
94789596
/// Called on a for stmt to check itself and nested loops (if any).
94799597
/// \return Returns 0 if one of the collapsed stmts is not canonical for loop,
94809598
/// number of collapsed loops otherwise.
@@ -9487,13 +9605,17 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
94879605
unsigned NestedLoopCount = 1;
94889606
bool SupportsNonPerfectlyNested = (SemaRef.LangOpts.OpenMP >= 50) &&
94899607
!isOpenMPLoopTransformationDirective(DKind);
9608+
llvm::SmallPtrSet<const Decl *, 4> CollapsedLoopVarDecls{};
94909609

94919610
if (CollapseLoopCountExpr) {
94929611
// Found 'collapse' clause - calculate collapse number.
94939612
Expr::EvalResult Result;
94949613
if (!CollapseLoopCountExpr->isValueDependent() &&
94959614
CollapseLoopCountExpr->EvaluateAsInt(Result, SemaRef.getASTContext())) {
94969615
NestedLoopCount = Result.Val.getInt().getLimitedValue();
9616+
9617+
ForVarDeclFinder FVDF{CollapsedLoopVarDecls};
9618+
FVDF.TraverseStmt(AStmt);
94979619
} else {
94989620
Built.clear(/*Size=*/1);
94999621
return 1;
@@ -9531,11 +9653,13 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
95319653
SupportsNonPerfectlyNested, NumLoops,
95329654
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
95339655
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
9534-
&IterSpaces, &Captures](unsigned Cnt, Stmt *CurStmt) {
9656+
&IterSpaces, &Captures,
9657+
&CollapsedLoopVarDecls](unsigned Cnt, Stmt *CurStmt) {
95359658
if (checkOpenMPIterationSpace(
95369659
DKind, CurStmt, SemaRef, DSA, Cnt, NestedLoopCount,
95379660
NumLoops, CollapseLoopCountExpr, OrderedLoopCountExpr,
9538-
VarsWithImplicitDSA, IterSpaces, Captures))
9661+
VarsWithImplicitDSA, IterSpaces, Captures,
9662+
CollapsedLoopVarDecls))
95399663
return true;
95409664
if (Cnt > 0 && Cnt >= NestedLoopCount &&
95419665
IterSpaces[Cnt].CounterVar) {

clang/test/OpenMP/loop_collapse_1.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %clang_cc1 -fopenmp -fopenmp-version=50 -verify %s
2+
3+
void func( double *A, int N, int M, int NB ) {
4+
#pragma omp parallel
5+
{
6+
int nblks = (N-1)/NB;
7+
int lnb = ((N-1)/NB)*NB;
8+
9+
#pragma omp for collapse(2)
10+
for (int jblk = 0 ; jblk < nblks ; jblk++ ) {
11+
int jb = (jblk == nblks - 1 ? lnb : NB);
12+
for (int jk = 0; jk < N; jk+=jb) { // expected-error{{cannot use variable 'jb' in collapsed imperfectly-nested loop increment statement}}
13+
}
14+
}
15+
16+
#pragma omp for collapse(2)
17+
for (int a = 0; a < N; a++) {
18+
for (int b = 0; b < M; b++) {
19+
int cx = a+b < NB ? a : b;
20+
for (int c = 0; c < cx; c++) {
21+
}
22+
}
23+
}
24+
25+
#pragma omp for collapse(3)
26+
for (int a = 0; a < N; a++) {
27+
for (int b = 0; b < M; b++) {
28+
int cx = a+b < NB ? a : b;
29+
for (int c = 0; c < cx; c++) { // expected-error{{cannot use variable 'cx' in collapsed imperfectly-nested loop condition statement}}
30+
}
31+
}
32+
}
33+
}
34+
}
35+
36+
int main(void) {
37+
double arr[256];
38+
func (arr, 16, 16, 16);
39+
return 0;
40+
}

clang/test/OpenMP/loop_collapse_2.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %clang_cc1 -fopenmp -fopenmp-version=50 -verify %s
2+
3+
// We just want to try out a range for statement... this seems a bit OTT.
4+
template<typename T>
5+
class fakevector {
6+
T *contents;
7+
long size;
8+
public:
9+
fakevector(long sz) : size(sz) {
10+
contents = new T[sz];
11+
}
12+
~fakevector() {
13+
delete[] contents;
14+
}
15+
T& operator[](long x) { return contents[x]; }
16+
typedef T *iterator;
17+
fakevector<T>::iterator begin() {
18+
return &contents[0];
19+
}
20+
fakevector<T>::iterator end() {
21+
return &contents[size];
22+
}
23+
};
24+
25+
void func( double *A, int N, int M, int NB ) {
26+
#pragma omp parallel
27+
{
28+
int nblks = (N-1)/NB;
29+
int lnb = ((N-1)/NB)*NB;
30+
#pragma omp for collapse(2)
31+
for (int jblk = 0 ; jblk < nblks ; jblk++ ) {
32+
int jb = (jblk == nblks - 1 ? lnb : NB);
33+
for (int jk = 0; jk < N; jk+=jb) { // expected-error{{cannot use variable 'jb' in collapsed imperfectly-nested loop increment statement}}
34+
}
35+
}
36+
37+
#pragma omp for collapse(2)
38+
for (int a = 0; a < N; a++) {
39+
for (int b = 0; b < M; b++) {
40+
int cx = a+b < NB ? a : b;
41+
for (int c = 0; c < cx; c++) {
42+
}
43+
}
44+
}
45+
46+
fakevector<float> myvec{N};
47+
#pragma omp for collapse(2)
48+
for (auto &a : myvec) {
49+
fakevector<float> myvec3{M};
50+
for (auto &b : myvec3) { // expected-error{{cannot use variable 'myvec3' in collapsed imperfectly-nested loop init statement}}
51+
}
52+
}
53+
54+
fakevector<float> myvec2{M};
55+
56+
#pragma omp for collapse(3)
57+
for (auto &a : myvec) {
58+
for (auto &b : myvec2) {
59+
int cx = a < b ? N : M;
60+
for (int c = 0; c < cx; c++) { // expected-error {{cannot use variable 'cx' in collapsed imperfectly-nested loop condition statement}}
61+
}
62+
}
63+
}
64+
65+
#pragma omp for collapse(3)
66+
for (auto &a : myvec) {
67+
int cx = a < 5 ? M : N;
68+
for (auto &b : myvec2) {
69+
for (int c = 0; c < cx; c++) { // expected-error{{cannot use variable 'cx' in collapsed imperfectly-nested loop condition statement}}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
int main(void) {
77+
double arr[256];
78+
func (arr, 16, 16, 16);
79+
return 0;
80+
}

0 commit comments

Comments
 (0)