Skip to content

Commit 0d9ee78

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 9dadb1f commit 0d9ee78

File tree

4 files changed

+255
-8
lines changed

4 files changed

+255
-8
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/Sema/SemaOpenMP.cpp

Lines changed: 133 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.empty()) {
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.empty()) {
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.empty()) {
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)) {
@@ -8971,8 +9054,9 @@ void SemaOpenMP::ActOnOpenMPLoopInitialization(SourceLocation ForLoc,
89719054
return;
89729055

89739056
DSAStack->loopStart();
9057+
llvm::SmallPtrSet<const Decl *, 1> EmptyDeclSet;
89749058
OpenMPIterationSpaceChecker ISC(SemaRef, /*SupportsNonRectangular=*/true,
8975-
*DSAStack, ForLoc);
9059+
*DSAStack, ForLoc, EmptyDeclSet);
89769060
if (!ISC.checkAndSetInit(Init, /*EmitDiags=*/false)) {
89779061
if (ValueDecl *D = ISC.getLoopDecl()) {
89789062
auto *VD = dyn_cast<VarDecl>(D);
@@ -9069,7 +9153,8 @@ static bool checkOpenMPIterationSpace(
90699153
Expr *OrderedLoopCountExpr,
90709154
SemaOpenMP::VarsWithInheritedDSAType &VarsWithImplicitDSA,
90719155
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
9072-
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures) {
9156+
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
9157+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls) {
90739158
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
90749159
// OpenMP [2.9.1, Canonical Loop Form]
90759160
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9108,7 +9193,8 @@ static bool checkOpenMPIterationSpace(
91089193
return false;
91099194

91109195
OpenMPIterationSpaceChecker ISC(SemaRef, SupportsNonRectangular, DSA,
9111-
For ? For->getForLoc() : CXXFor->getForLoc());
9196+
For ? For->getForLoc() : CXXFor->getForLoc(),
9197+
CollapsedLoopVarDecls);
91129198

91139199
// Check init.
91149200
Stmt *Init = For ? For->getInit() : CXXFor->getBeginStmt();
@@ -9475,6 +9561,39 @@ static Expr *buildPostUpdate(Sema &S, ArrayRef<Expr *> PostUpdates) {
94759561
return PostUpdate;
94769562
}
94779563

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

94919611
if (CollapseLoopCountExpr) {
94929612
// Found 'collapse' clause - calculate collapse number.
94939613
Expr::EvalResult Result;
94949614
if (!CollapseLoopCountExpr->isValueDependent() &&
94959615
CollapseLoopCountExpr->EvaluateAsInt(Result, SemaRef.getASTContext())) {
94969616
NestedLoopCount = Result.Val.getInt().getLimitedValue();
9617+
9618+
ForVarDeclFinder FVDF{CollapsedLoopVarDecls};
9619+
FVDF.TraverseStmt(AStmt);
94979620
} else {
94989621
Built.clear(/*Size=*/1);
94999622
return 1;
@@ -9531,11 +9654,13 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
95319654
SupportsNonPerfectlyNested, NumLoops,
95329655
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
95339656
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
9534-
&IterSpaces, &Captures](unsigned Cnt, Stmt *CurStmt) {
9657+
&IterSpaces, &Captures,
9658+
&CollapsedLoopVarDecls](unsigned Cnt, Stmt *CurStmt) {
95359659
if (checkOpenMPIterationSpace(
95369660
DKind, CurStmt, SemaRef, DSA, Cnt, NestedLoopCount,
95379661
NumLoops, CollapseLoopCountExpr, OrderedLoopCountExpr,
9538-
VarsWithImplicitDSA, IterSpaces, Captures))
9662+
VarsWithImplicitDSA, IterSpaces, Captures,
9663+
CollapsedLoopVarDecls))
95399664
return true;
95409665
if (Cnt > 0 && Cnt >= NestedLoopCount &&
95419666
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)