Skip to content

Commit eb60fc7

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 5ef087b commit eb60fc7

File tree

5 files changed

+248
-9
lines changed

5 files changed

+248
-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: 124 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,47 @@ struct LoopIterationSpace final {
76687669
Expr *FinalCondition = nullptr;
76697670
};
76707671

7672+
class ForSubExprChecker : public RecursiveASTVisitor<ForSubExprChecker> {
7673+
const llvm::SmallSet<Decl *, 4> *CollapsedLoopVarDecls;
7674+
VarDecl *ForbiddenVar;
7675+
SourceRange ErrLoc;
7676+
7677+
public:
7678+
explicit ForSubExprChecker(
7679+
const llvm::SmallSet<Decl *, 4> *CollapsedLoopVarDecls)
7680+
: CollapsedLoopVarDecls(CollapsedLoopVarDecls), ForbiddenVar(nullptr) {}
7681+
7682+
bool shouldVisitImplicitCode() const { return true; }
7683+
7684+
bool VisitDeclRefExpr(DeclRefExpr *E) {
7685+
ValueDecl *VD = E->getDecl();
7686+
if (!isa<VarDecl, BindingDecl>(VD))
7687+
return true;
7688+
VarDecl *V = VD->getPotentiallyDecomposedVarDecl();
7689+
if (V->getType()->isReferenceType()) {
7690+
VarDecl *VD = V->getDefinition();
7691+
if (VD->hasInit()) {
7692+
Expr *I = VD->getInit();
7693+
DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(I);
7694+
if (!DRE)
7695+
return true;
7696+
V = DRE->getDecl()->getPotentiallyDecomposedVarDecl();
7697+
}
7698+
}
7699+
Decl *Canon = V->getCanonicalDecl();
7700+
if (CollapsedLoopVarDecls->contains(Canon)) {
7701+
ForbiddenVar = V;
7702+
ErrLoc = E->getSourceRange();
7703+
return false;
7704+
}
7705+
7706+
return true;
7707+
}
7708+
7709+
VarDecl *getForbiddenVar() { return ForbiddenVar; }
7710+
SourceRange &getErrRange() { return ErrLoc; }
7711+
};
7712+
76717713
/// Helper class for checking canonical form of the OpenMP loops and
76727714
/// extracting iteration space of each loop in the loop nest, that will be used
76737715
/// for IR generation.
@@ -7682,6 +7724,8 @@ class OpenMPIterationSpaceChecker {
76827724
SourceLocation DefaultLoc;
76837725
/// A location for diagnostics (when increment is not compatible).
76847726
SourceLocation ConditionLoc;
7727+
/// The set of variables declared within the (to be collapsed) loop nest.
7728+
const llvm::SmallSet<Decl *, 4> *CollapsedLoopVarDecls;
76857729
/// A source location for referring to loop init later.
76867730
SourceRange InitSrcRange;
76877731
/// A source location for referring to condition later.
@@ -7725,10 +7769,13 @@ class OpenMPIterationSpaceChecker {
77257769
Expr *Condition = nullptr;
77267770

77277771
public:
7728-
OpenMPIterationSpaceChecker(Sema &SemaRef, bool SupportsNonRectangular,
7729-
DSAStackTy &Stack, SourceLocation DefaultLoc)
7772+
OpenMPIterationSpaceChecker(
7773+
Sema &SemaRef, bool SupportsNonRectangular, DSAStackTy &Stack,
7774+
SourceLocation DefaultLoc,
7775+
const llvm::SmallSet<Decl *, 4> *CollapsedLoopDecls)
77307776
: SemaRef(SemaRef), SupportsNonRectangular(SupportsNonRectangular),
7731-
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc) {}
7777+
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc),
7778+
CollapsedLoopVarDecls(CollapsedLoopDecls) {}
77327779
/// Check init-expr for canonical loop form and save loop counter
77337780
/// variable - #Var and its initialization value - #LB.
77347781
bool checkAndSetInit(Stmt *S, bool EmitDiags = true);
@@ -8049,6 +8096,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
80498096
if (!ExprTemp->cleanupsHaveSideEffects())
80508097
S = ExprTemp->getSubExpr();
80518098

8099+
if (CollapsedLoopVarDecls) {
8100+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8101+
if (!FSEC.TraverseStmt(S)) {
8102+
SourceRange &Range = FSEC.getErrRange();
8103+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8104+
<< Range.getEnd() << 0 << FSEC.getForbiddenVar();
8105+
return true;
8106+
}
8107+
}
8108+
80528109
InitSrcRange = S->getSourceRange();
80538110
if (Expr *E = dyn_cast<Expr>(S))
80548111
S = E->IgnoreParens();
@@ -8152,6 +8209,17 @@ bool OpenMPIterationSpaceChecker::checkAndSetCond(Expr *S) {
81528209
}
81538210
Condition = S;
81548211
S = getExprAsWritten(S);
8212+
8213+
if (CollapsedLoopVarDecls) {
8214+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8215+
if (!FSEC.TraverseStmt(S)) {
8216+
SourceRange &Range = FSEC.getErrRange();
8217+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8218+
<< Range.getEnd() << 1 << FSEC.getForbiddenVar();
8219+
return true;
8220+
}
8221+
}
8222+
81558223
SourceLocation CondLoc = S->getBeginLoc();
81568224
auto &&CheckAndSetCond =
81578225
[this, IneqCondIsCanonical](BinaryOperatorKind Opcode, const Expr *LHS,
@@ -8250,6 +8318,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInc(Expr *S) {
82508318
if (!ExprTemp->cleanupsHaveSideEffects())
82518319
S = ExprTemp->getSubExpr();
82528320

8321+
if (CollapsedLoopVarDecls) {
8322+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8323+
if (!FSEC.TraverseStmt(S)) {
8324+
SourceRange &Range = FSEC.getErrRange();
8325+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8326+
<< Range.getEnd() << 2 << FSEC.getForbiddenVar();
8327+
return true;
8328+
}
8329+
}
8330+
82538331
IncrementSrcRange = S->getSourceRange();
82548332
S = S->IgnoreParens();
82558333
if (auto *UO = dyn_cast<UnaryOperator>(S)) {
@@ -8972,7 +9050,7 @@ void SemaOpenMP::ActOnOpenMPLoopInitialization(SourceLocation ForLoc,
89729050

89739051
DSAStack->loopStart();
89749052
OpenMPIterationSpaceChecker ISC(SemaRef, /*SupportsNonRectangular=*/true,
8975-
*DSAStack, ForLoc);
9053+
*DSAStack, ForLoc, nullptr);
89769054
if (!ISC.checkAndSetInit(Init, /*EmitDiags=*/false)) {
89779055
if (ValueDecl *D = ISC.getLoopDecl()) {
89789056
auto *VD = dyn_cast<VarDecl>(D);
@@ -9069,7 +9147,8 @@ static bool checkOpenMPIterationSpace(
90699147
Expr *OrderedLoopCountExpr,
90709148
SemaOpenMP::VarsWithInheritedDSAType &VarsWithImplicitDSA,
90719149
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
9072-
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures) {
9150+
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
9151+
const llvm::SmallSet<Decl *, 4> *CollapsedLoopVarDecls) {
90739152
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
90749153
// OpenMP [2.9.1, Canonical Loop Form]
90759154
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9108,7 +9187,8 @@ static bool checkOpenMPIterationSpace(
91089187
return false;
91099188

91109189
OpenMPIterationSpaceChecker ISC(SemaRef, SupportsNonRectangular, DSA,
9111-
For ? For->getForLoc() : CXXFor->getForLoc());
9190+
For ? For->getForLoc() : CXXFor->getForLoc(),
9191+
CollapsedLoopVarDecls);
91129192

91139193
// Check init.
91149194
Stmt *Init = For ? For->getInit() : CXXFor->getBeginStmt();
@@ -9475,6 +9555,36 @@ static Expr *buildPostUpdate(Sema &S, ArrayRef<Expr *> PostUpdates) {
94759555
return PostUpdate;
94769556
}
94779557

9558+
class ForVarDeclFinder : public RecursiveASTVisitor<ForVarDeclFinder> {
9559+
int NestingDepth;
9560+
llvm::SmallSet<Decl *, 4> &VarDecls;
9561+
9562+
public:
9563+
explicit ForVarDeclFinder(llvm::SmallSet<Decl *, 4> &VD)
9564+
: NestingDepth(0), VarDecls(VD) {}
9565+
9566+
bool VisitForStmt(ForStmt *F) {
9567+
++NestingDepth;
9568+
TraverseStmt(F->getBody());
9569+
--NestingDepth;
9570+
return false;
9571+
}
9572+
9573+
bool VisitCXXForRangeStmt(CXXForRangeStmt *RF) {
9574+
++NestingDepth;
9575+
TraverseStmt(RF->getBody());
9576+
--NestingDepth;
9577+
return false;
9578+
}
9579+
9580+
bool VisitVarDecl(VarDecl *D) {
9581+
Decl *C = D->getCanonicalDecl();
9582+
if (NestingDepth > 0)
9583+
VarDecls.insert(C);
9584+
return true;
9585+
}
9586+
};
9587+
94789588
/// Called on a for stmt to check itself and nested loops (if any).
94799589
/// \return Returns 0 if one of the collapsed stmts is not canonical for loop,
94809590
/// number of collapsed loops otherwise.
@@ -9487,13 +9597,17 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
94879597
unsigned NestedLoopCount = 1;
94889598
bool SupportsNonPerfectlyNested = (SemaRef.LangOpts.OpenMP >= 50) &&
94899599
!isOpenMPLoopTransformationDirective(DKind);
9600+
llvm::SmallSet<Decl *, 4> CollapsedLoopVarDecls{};
94909601

94919602
if (CollapseLoopCountExpr) {
94929603
// Found 'collapse' clause - calculate collapse number.
94939604
Expr::EvalResult Result;
94949605
if (!CollapseLoopCountExpr->isValueDependent() &&
94959606
CollapseLoopCountExpr->EvaluateAsInt(Result, SemaRef.getASTContext())) {
94969607
NestedLoopCount = Result.Val.getInt().getLimitedValue();
9608+
9609+
ForVarDeclFinder FVDF{CollapsedLoopVarDecls};
9610+
FVDF.TraverseStmt(AStmt);
94979611
} else {
94989612
Built.clear(/*Size=*/1);
94999613
return 1;
@@ -9531,11 +9645,13 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
95319645
SupportsNonPerfectlyNested, NumLoops,
95329646
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
95339647
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
9534-
&IterSpaces, &Captures](unsigned Cnt, Stmt *CurStmt) {
9648+
&IterSpaces, &Captures,
9649+
CollapsedLoopVarDecls](unsigned Cnt, Stmt *CurStmt) {
95359650
if (checkOpenMPIterationSpace(
95369651
DKind, CurStmt, SemaRef, DSA, Cnt, NestedLoopCount,
95379652
NumLoops, CollapseLoopCountExpr, OrderedLoopCountExpr,
9538-
VarsWithImplicitDSA, IterSpaces, Captures))
9653+
VarsWithImplicitDSA, IterSpaces, Captures,
9654+
&CollapsedLoopVarDecls))
95399655
return true;
95409656
if (Cnt > 0 && Cnt >= NestedLoopCount &&
95419657
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)