Skip to content

Commit 3188e9b

Browse files
authored
[clang][OpenMP] Diagnose badly-formed collapsed imperfect loop nests (#60678) (#101305)
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 a567d45 commit 3188e9b

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
@@ -11165,6 +11165,8 @@ def err_omp_loop_diff_cxx : Error<
1116511165
"upper and lower loop bounds">;
1116611166
def err_omp_loop_cannot_use_stmt : Error<
1116711167
"'%0' statement cannot be used in OpenMP for loop">;
11168+
def err_omp_loop_bad_collapse_var : Error<
11169+
"cannot use variable %1 in collapsed imperfectly-nested loop %select{init|condition|increment}0 statement">;
1116811170
def err_omp_simd_region_cannot_use_stmt : Error<
1116911171
"'%0' statement cannot be used in OpenMP simd region">;
1117011172
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"
@@ -7695,6 +7696,52 @@ struct LoopIterationSpace final {
76957696
Expr *FinalCondition = nullptr;
76967697
};
76977698

7699+
/// Scan an AST subtree, checking that no decls in the CollapsedLoopVarDecls
7700+
/// set are referenced. Used for verifying loop nest structure before
7701+
/// performing a loop collapse operation.
7702+
class ForSubExprChecker final : public RecursiveASTVisitor<ForSubExprChecker> {
7703+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls;
7704+
VarDecl *ForbiddenVar = nullptr;
7705+
SourceRange ErrLoc;
7706+
7707+
public:
7708+
explicit ForSubExprChecker(
7709+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls)
7710+
: CollapsedLoopVarDecls(CollapsedLoopVarDecls) {}
7711+
7712+
// We want to visit implicit code, i.e. synthetic initialisation statements
7713+
// created during range-for lowering.
7714+
bool shouldVisitImplicitCode() const { return true; }
7715+
7716+
bool VisitDeclRefExpr(DeclRefExpr *E) {
7717+
ValueDecl *VD = E->getDecl();
7718+
if (!isa<VarDecl, BindingDecl>(VD))
7719+
return true;
7720+
VarDecl *V = VD->getPotentiallyDecomposedVarDecl();
7721+
if (V->getType()->isReferenceType()) {
7722+
VarDecl *VD = V->getDefinition();
7723+
if (VD->hasInit()) {
7724+
Expr *I = VD->getInit();
7725+
DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(I);
7726+
if (!DRE)
7727+
return true;
7728+
V = DRE->getDecl()->getPotentiallyDecomposedVarDecl();
7729+
}
7730+
}
7731+
Decl *Canon = V->getCanonicalDecl();
7732+
if (CollapsedLoopVarDecls.contains(Canon)) {
7733+
ForbiddenVar = V;
7734+
ErrLoc = E->getSourceRange();
7735+
return false;
7736+
}
7737+
7738+
return true;
7739+
}
7740+
7741+
VarDecl *getForbiddenVar() const { return ForbiddenVar; }
7742+
SourceRange getErrRange() const { return ErrLoc; }
7743+
};
7744+
76987745
/// Helper class for checking canonical form of the OpenMP loops and
76997746
/// extracting iteration space of each loop in the loop nest, that will be used
77007747
/// for IR generation.
@@ -7709,6 +7756,8 @@ class OpenMPIterationSpaceChecker {
77097756
SourceLocation DefaultLoc;
77107757
/// A location for diagnostics (when increment is not compatible).
77117758
SourceLocation ConditionLoc;
7759+
/// The set of variables declared within the (to be collapsed) loop nest.
7760+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls;
77127761
/// A source location for referring to loop init later.
77137762
SourceRange InitSrcRange;
77147763
/// A source location for referring to condition later.
@@ -7752,10 +7801,13 @@ class OpenMPIterationSpaceChecker {
77527801
Expr *Condition = nullptr;
77537802

77547803
public:
7755-
OpenMPIterationSpaceChecker(Sema &SemaRef, bool SupportsNonRectangular,
7756-
DSAStackTy &Stack, SourceLocation DefaultLoc)
7804+
OpenMPIterationSpaceChecker(
7805+
Sema &SemaRef, bool SupportsNonRectangular, DSAStackTy &Stack,
7806+
SourceLocation DefaultLoc,
7807+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopDecls)
77577808
: SemaRef(SemaRef), SupportsNonRectangular(SupportsNonRectangular),
7758-
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc) {}
7809+
Stack(Stack), DefaultLoc(DefaultLoc), ConditionLoc(DefaultLoc),
7810+
CollapsedLoopVarDecls(CollapsedLoopDecls) {}
77597811
/// Check init-expr for canonical loop form and save loop counter
77607812
/// variable - #Var and its initialization value - #LB.
77617813
bool checkAndSetInit(Stmt *S, bool EmitDiags = true);
@@ -8076,6 +8128,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInit(Stmt *S, bool EmitDiags) {
80768128
if (!ExprTemp->cleanupsHaveSideEffects())
80778129
S = ExprTemp->getSubExpr();
80788130

8131+
if (!CollapsedLoopVarDecls.empty()) {
8132+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8133+
if (!FSEC.TraverseStmt(S)) {
8134+
SourceRange Range = FSEC.getErrRange();
8135+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8136+
<< Range.getEnd() << 0 << FSEC.getForbiddenVar();
8137+
return true;
8138+
}
8139+
}
8140+
80798141
InitSrcRange = S->getSourceRange();
80808142
if (Expr *E = dyn_cast<Expr>(S))
80818143
S = E->IgnoreParens();
@@ -8179,6 +8241,17 @@ bool OpenMPIterationSpaceChecker::checkAndSetCond(Expr *S) {
81798241
}
81808242
Condition = S;
81818243
S = getExprAsWritten(S);
8244+
8245+
if (!CollapsedLoopVarDecls.empty()) {
8246+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8247+
if (!FSEC.TraverseStmt(S)) {
8248+
SourceRange Range = FSEC.getErrRange();
8249+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8250+
<< Range.getEnd() << 1 << FSEC.getForbiddenVar();
8251+
return true;
8252+
}
8253+
}
8254+
81828255
SourceLocation CondLoc = S->getBeginLoc();
81838256
auto &&CheckAndSetCond =
81848257
[this, IneqCondIsCanonical](BinaryOperatorKind Opcode, const Expr *LHS,
@@ -8277,6 +8350,16 @@ bool OpenMPIterationSpaceChecker::checkAndSetInc(Expr *S) {
82778350
if (!ExprTemp->cleanupsHaveSideEffects())
82788351
S = ExprTemp->getSubExpr();
82798352

8353+
if (!CollapsedLoopVarDecls.empty()) {
8354+
ForSubExprChecker FSEC{CollapsedLoopVarDecls};
8355+
if (!FSEC.TraverseStmt(S)) {
8356+
SourceRange Range = FSEC.getErrRange();
8357+
SemaRef.Diag(Range.getBegin(), diag::err_omp_loop_bad_collapse_var)
8358+
<< Range.getEnd() << 2 << FSEC.getForbiddenVar();
8359+
return true;
8360+
}
8361+
}
8362+
82808363
IncrementSrcRange = S->getSourceRange();
82818364
S = S->IgnoreParens();
82828365
if (auto *UO = dyn_cast<UnaryOperator>(S)) {
@@ -8998,8 +9081,9 @@ void SemaOpenMP::ActOnOpenMPLoopInitialization(SourceLocation ForLoc,
89989081
return;
89999082

90009083
DSAStack->loopStart();
9084+
llvm::SmallPtrSet<const Decl *, 1> EmptyDeclSet;
90019085
OpenMPIterationSpaceChecker ISC(SemaRef, /*SupportsNonRectangular=*/true,
9002-
*DSAStack, ForLoc);
9086+
*DSAStack, ForLoc, EmptyDeclSet);
90039087
if (!ISC.checkAndSetInit(Init, /*EmitDiags=*/false)) {
90049088
if (ValueDecl *D = ISC.getLoopDecl()) {
90059089
auto *VD = dyn_cast<VarDecl>(D);
@@ -9096,7 +9180,8 @@ static bool checkOpenMPIterationSpace(
90969180
Expr *OrderedLoopCountExpr,
90979181
SemaOpenMP::VarsWithInheritedDSAType &VarsWithImplicitDSA,
90989182
llvm::MutableArrayRef<LoopIterationSpace> ResultIterSpaces,
9099-
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures) {
9183+
llvm::MapVector<const Expr *, DeclRefExpr *> &Captures,
9184+
const llvm::SmallPtrSetImpl<const Decl *> &CollapsedLoopVarDecls) {
91009185
bool SupportsNonRectangular = !isOpenMPLoopTransformationDirective(DKind);
91019186
// OpenMP [2.9.1, Canonical Loop Form]
91029187
// for (init-expr; test-expr; incr-expr) structured-block
@@ -9135,7 +9220,8 @@ static bool checkOpenMPIterationSpace(
91359220
return false;
91369221

91379222
OpenMPIterationSpaceChecker ISC(SemaRef, SupportsNonRectangular, DSA,
9138-
For ? For->getForLoc() : CXXFor->getForLoc());
9223+
For ? For->getForLoc() : CXXFor->getForLoc(),
9224+
CollapsedLoopVarDecls);
91399225

91409226
// Check init.
91419227
Stmt *Init = For ? For->getInit() : CXXFor->getBeginStmt();
@@ -9502,6 +9588,39 @@ static Expr *buildPostUpdate(Sema &S, ArrayRef<Expr *> PostUpdates) {
95029588
return PostUpdate;
95039589
}
95049590

9591+
/// Look for variables declared in the body parts of a for-loop nest. Used
9592+
/// for verifying loop nest structure before performing a loop collapse
9593+
/// operation.
9594+
class ForVarDeclFinder final : public RecursiveASTVisitor<ForVarDeclFinder> {
9595+
int NestingDepth = 0;
9596+
llvm::SmallPtrSetImpl<const Decl *> &VarDecls;
9597+
9598+
public:
9599+
explicit ForVarDeclFinder(llvm::SmallPtrSetImpl<const Decl *> &VD)
9600+
: VarDecls(VD) {}
9601+
9602+
bool VisitForStmt(ForStmt *F) {
9603+
++NestingDepth;
9604+
TraverseStmt(F->getBody());
9605+
--NestingDepth;
9606+
return false;
9607+
}
9608+
9609+
bool VisitCXXForRangeStmt(CXXForRangeStmt *RF) {
9610+
++NestingDepth;
9611+
TraverseStmt(RF->getBody());
9612+
--NestingDepth;
9613+
return false;
9614+
}
9615+
9616+
bool VisitVarDecl(VarDecl *D) {
9617+
Decl *C = D->getCanonicalDecl();
9618+
if (NestingDepth > 0)
9619+
VarDecls.insert(C);
9620+
return true;
9621+
}
9622+
};
9623+
95059624
/// Called on a for stmt to check itself and nested loops (if any).
95069625
/// \return Returns 0 if one of the collapsed stmts is not canonical for loop,
95079626
/// number of collapsed loops otherwise.
@@ -9514,13 +9633,17 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
95149633
unsigned NestedLoopCount = 1;
95159634
bool SupportsNonPerfectlyNested = (SemaRef.LangOpts.OpenMP >= 50) &&
95169635
!isOpenMPLoopTransformationDirective(DKind);
9636+
llvm::SmallPtrSet<const Decl *, 4> CollapsedLoopVarDecls;
95179637

95189638
if (CollapseLoopCountExpr) {
95199639
// Found 'collapse' clause - calculate collapse number.
95209640
Expr::EvalResult Result;
95219641
if (!CollapseLoopCountExpr->isValueDependent() &&
95229642
CollapseLoopCountExpr->EvaluateAsInt(Result, SemaRef.getASTContext())) {
95239643
NestedLoopCount = Result.Val.getInt().getLimitedValue();
9644+
9645+
ForVarDeclFinder FVDF{CollapsedLoopVarDecls};
9646+
FVDF.TraverseStmt(AStmt);
95249647
} else {
95259648
Built.clear(/*Size=*/1);
95269649
return 1;
@@ -9558,11 +9681,13 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
95589681
SupportsNonPerfectlyNested, NumLoops,
95599682
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
95609683
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
9561-
&IterSpaces, &Captures](unsigned Cnt, Stmt *CurStmt) {
9684+
&IterSpaces, &Captures,
9685+
&CollapsedLoopVarDecls](unsigned Cnt, Stmt *CurStmt) {
95629686
if (checkOpenMPIterationSpace(
95639687
DKind, CurStmt, SemaRef, DSA, Cnt, NestedLoopCount,
95649688
NumLoops, CollapseLoopCountExpr, OrderedLoopCountExpr,
9565-
VarsWithImplicitDSA, IterSpaces, Captures))
9689+
VarsWithImplicitDSA, IterSpaces, Captures,
9690+
CollapsedLoopVarDecls))
95669691
return true;
95679692
if (Cnt > 0 && Cnt >= NestedLoopCount &&
95689693
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)