Skip to content

Commit 52e8f58

Browse files
committed
[SYCL] Diagnose uses of zero length arrays
Adds diagnosing on attempt to use zero length arrays, pointers, refs, arrays of them and structs/classes containing all of it. In case a struct/class with zero length array is used this emits a set of notes pointing out how zero length array got into used struct, like this: ``` struct ContainsArr { int A[0]; // note: field of illegal type declared here }; struct Wrapper { ContainsArr F; // note: within field of type ContainsArr declared here // ... } // Device code Wrapper W; W.use(); // error: zero-length arrays are not permitted ``` Total deep check of each used declaration may result in double diagnosing at the same location. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D114080
1 parent 3ad32df commit 52e8f58

File tree

6 files changed

+237
-2
lines changed

6 files changed

+237
-2
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5785,7 +5785,7 @@ def err_typecheck_invalid_restrict_invalid_pointee : Error<
57855785
def ext_typecheck_zero_array_size : Extension<
57865786
"zero size arrays are an extension">, InGroup<ZeroLengthArray>;
57875787
def err_typecheck_zero_array_size : Error<
5788-
"zero-length arrays are not permitted in C++">;
5788+
"zero-length arrays are not permitted in %select{C++|SYCL device code}0">;
57895789
def err_array_size_non_int : Error<"size of array has non-integer type %0">;
57905790
def err_init_element_not_constant : Error<
57915791
"initializer element is not a compile-time constant">;

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13142,6 +13142,9 @@ class Sema final {
1314213142
/// Adds Callee to DeviceCallGraph if we don't know if its caller will be
1314313143
/// codegen'ed yet.
1314413144
bool checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee);
13145+
void deepTypeCheckForSYCLDevice(SourceLocation UsedAt,
13146+
llvm::DenseSet<QualType> Visited,
13147+
ValueDecl *DeclToCheck);
1314513148
};
1314613149

1314713150
/// RAII object that enters a new expression evaluation context.

clang/lib/Sema/Sema.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,6 +1858,15 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
18581858
if (isUnevaluatedContext() || Ty.isNull())
18591859
return;
18601860

1861+
// The original idea behind checkTypeSupport function is that unused
1862+
// declarations can be replaced with an array of bytes of the same size during
1863+
// codegen, such replacement doesn't seem to be possible for types without
1864+
// constant byte size like zero length arrays. So, do a deep check for SYCL.
1865+
if (D && LangOpts.SYCLIsDevice) {
1866+
llvm::DenseSet<QualType> Visited;
1867+
deepTypeCheckForSYCLDevice(Loc, Visited, D);
1868+
}
1869+
18611870
Decl *C = cast<Decl>(getCurLexicalContext());
18621871

18631872
// Memcpy operations for structs containing a member with unsupported type

clang/lib/Sema/SemaSYCL.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,101 @@ bool Sema::checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee) {
4848
return DiagKind != SemaDiagnosticBuilder::K_Immediate &&
4949
DiagKind != SemaDiagnosticBuilder::K_ImmediateWithCallStack;
5050
}
51+
52+
static bool isZeroSizedArray(Sema &SemaRef, QualType Ty) {
53+
if (const auto *CAT = SemaRef.getASTContext().getAsConstantArrayType(Ty))
54+
return CAT->getSize() == 0;
55+
return false;
56+
}
57+
58+
void Sema::deepTypeCheckForSYCLDevice(SourceLocation UsedAt,
59+
llvm::DenseSet<QualType> Visited,
60+
ValueDecl *DeclToCheck) {
61+
assert(getLangOpts().SYCLIsDevice &&
62+
"Should only be called during SYCL compilation");
63+
// Emit notes only for the first discovered declaration of unsupported type
64+
// to avoid mess of notes. This flag is to track that error already happened.
65+
bool NeedToEmitNotes = true;
66+
67+
auto Check = [&](QualType TypeToCheck, const ValueDecl *D) {
68+
bool ErrorFound = false;
69+
if (isZeroSizedArray(*this, TypeToCheck)) {
70+
SYCLDiagIfDeviceCode(UsedAt, diag::err_typecheck_zero_array_size) << 1;
71+
ErrorFound = true;
72+
}
73+
// Checks for other types can also be done here.
74+
if (ErrorFound) {
75+
if (NeedToEmitNotes) {
76+
if (auto *FD = dyn_cast<FieldDecl>(D))
77+
SYCLDiagIfDeviceCode(FD->getLocation(),
78+
diag::note_illegal_field_declared_here)
79+
<< FD->getType()->isPointerType() << FD->getType();
80+
else
81+
SYCLDiagIfDeviceCode(D->getLocation(), diag::note_declared_at);
82+
}
83+
}
84+
85+
return ErrorFound;
86+
};
87+
88+
// In case we have a Record used do the DFS for a bad field.
89+
SmallVector<const ValueDecl *, 4> StackForRecursion;
90+
StackForRecursion.push_back(DeclToCheck);
91+
92+
// While doing DFS save how we get there to emit a nice set of notes.
93+
SmallVector<const FieldDecl *, 4> History;
94+
History.push_back(nullptr);
95+
96+
do {
97+
const ValueDecl *Next = StackForRecursion.pop_back_val();
98+
if (!Next) {
99+
assert(!History.empty());
100+
// Found a marker, we have gone up a level.
101+
History.pop_back();
102+
continue;
103+
}
104+
QualType NextTy = Next->getType();
105+
106+
if (!Visited.insert(NextTy).second)
107+
continue;
108+
109+
auto EmitHistory = [&]() {
110+
// The first element is always nullptr.
111+
for (uint64_t Index = 1; Index < History.size(); ++Index) {
112+
SYCLDiagIfDeviceCode(History[Index]->getLocation(),
113+
diag::note_within_field_of_type)
114+
<< History[Index]->getType();
115+
}
116+
};
117+
118+
if (Check(NextTy, Next)) {
119+
if (NeedToEmitNotes)
120+
EmitHistory();
121+
NeedToEmitNotes = false;
122+
}
123+
124+
// In case pointer/array/reference type is met get pointee type, then
125+
// proceed with that type.
126+
while (NextTy->isAnyPointerType() || NextTy->isArrayType() ||
127+
NextTy->isReferenceType()) {
128+
if (NextTy->isArrayType())
129+
NextTy = QualType{NextTy->getArrayElementTypeNoTypeQual(), 0};
130+
else
131+
NextTy = NextTy->getPointeeType();
132+
if (Check(NextTy, Next)) {
133+
if (NeedToEmitNotes)
134+
EmitHistory();
135+
NeedToEmitNotes = false;
136+
}
137+
}
138+
139+
if (const auto *RecDecl = NextTy->getAsRecordDecl()) {
140+
if (auto *NextFD = dyn_cast<FieldDecl>(Next))
141+
History.push_back(NextFD);
142+
// When nullptr is discovered, this means we've gone back up a level, so
143+
// the history should be cleaned.
144+
StackForRecursion.push_back(nullptr);
145+
llvm::copy(RecDecl->fields(), std::back_inserter(StackForRecursion));
146+
}
147+
} while (!StackForRecursion.empty());
148+
}

clang/lib/Sema/SemaType.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2515,7 +2515,7 @@ QualType Sema::BuildArrayType(QualType T, ArrayType::ArraySizeModifier ASM,
25152515
Diag(ArraySize->getBeginLoc(),
25162516
isSFINAEContext() ? diag::err_typecheck_zero_array_size
25172517
: diag::ext_typecheck_zero_array_size)
2518-
<< ArraySize->getSourceRange();
2518+
<< 0 << ArraySize->getSourceRange();
25192519
}
25202520

25212521
// Is the array too large?
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// RUN: %clang_cc1 -fsycl-is-device -triple spir64 -fsyntax-only -verify %s
2+
//
3+
// This test checks if compiler reports compilation error on an attempt to use
4+
// a zero-length array inside device code.
5+
6+
template <typename Name, typename Func>
7+
__attribute__((sycl_kernel)) void kernel(const Func &kernelFunc) {
8+
// expected-note@+1 5{{called by 'kernel}}
9+
kernelFunc(); // #KernelObjCall
10+
}
11+
12+
typedef float ZEROARR[0];
13+
14+
struct Wrapper {
15+
int A;
16+
int BadArray[0]; // expected-note 3{{field of illegal type 'int[0]' declared here}}
17+
};
18+
19+
struct WrapperOfWrapper { // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
20+
Wrapper F; // expected-note 2{{within field of type 'Wrapper' declared here}}
21+
ZEROARR *Ptr; //expected-note 5{{field of illegal pointer type 'ZEROARR *' (aka 'float (*)[0]') declared here}}
22+
};
23+
24+
template <unsigned Size> struct InnerTemplated {
25+
double Array[Size]; // expected-note 8{{field of illegal type 'double[0]' declared here}}
26+
};
27+
28+
template <unsigned Size, typename Ty> struct Templated {
29+
unsigned A;
30+
Ty Arr[Size];
31+
InnerTemplated<Size> Array[Size + 1]; // expected-note 8{{within field of type 'InnerTemplated<0U>[1]' declared here}}
32+
};
33+
34+
struct KernelSt {
35+
int A;
36+
int BadArray[0]; // expected-note {{field of illegal type 'int[0]' declared here}}
37+
void operator()() const {}
38+
};
39+
40+
WrapperOfWrapper offendingFoo() {
41+
// expected-note@+1 {{called by 'offendingFoo'}}
42+
return WrapperOfWrapper{};
43+
}
44+
45+
template <unsigned Size>
46+
void templatedContext() {
47+
Templated<Size, float> Var;
48+
// expected-error@#KernelObjCall 2{{zero-length arrays are not permitted in SYCL device code}}
49+
// expected-note@#KernelObjCall {{called by 'kernel<TempContext, (lambda at}}
50+
// expected-note@+1 {{in instantiation of function template specialization}}
51+
kernel<class TempContext>([=] {
52+
// expected-note@+1 {{within field of type 'Templated<0U, float>' declared here}}
53+
(void)Var; // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
54+
});
55+
// expected-error@#KernelObjCall {{zero-length arrays are not permitted in SYCL device code}}
56+
// expected-note@+2 {{in instantiation of function template specialization}}
57+
// expected-note@+1 {{within field of type 'Templated<0U, float>' declared here}}
58+
kernel<class TempContext1>([Var] {
59+
});
60+
}
61+
62+
void foo(const unsigned X) {
63+
int Arr[0]; // expected-note 2{{declared here}}
64+
ZEROARR TypeDef; // expected-note {{declared here}}
65+
ZEROARR *Ptr; // expected-note {{declared here}}
66+
// expected-error@#KernelObjCall 3{{zero-length arrays are not permitted in SYCL device code}}
67+
// expected-note@+1 {{in instantiation of function template specialization}}
68+
kernel<class Simple>([=]() {
69+
(void)Arr; // expected-error {{zero-length arrays are not permitted in SYCL device code}}
70+
(void)TypeDef; // expected-error {{zero-length arrays are not permitted in SYCL device code}}
71+
// expected-note@+1 {{field of illegal pointer type 'ZEROARR *' (aka 'float (*)[0]') declared here}}
72+
(void)Ptr; // expected-error {{zero-length arrays are not permitted in SYCL device code}}
73+
});
74+
// expected-error@#KernelObjCall {{zero-length arrays are not permitted in SYCL device code}}
75+
// expected-note@+2 {{in instantiation of function template specialization}}
76+
// expected-note@+1 {{field of illegal type 'int[0]' declared here}}
77+
kernel<class Simple1>([Arr] { // expected-error {{zero-length arrays are not permitted in SYCL device code}}
78+
});
79+
WrapperOfWrapper St;
80+
// expected-error@#KernelObjCall 2{{zero-length arrays are not permitted in SYCL device code}}
81+
// expected-note@+1 {{in instantiation of function template specialization}}
82+
kernel<class SimpleStruct>([=] {
83+
// expected-note@+1 {{within field of type 'WrapperOfWrapper' declared here}}
84+
(void)St.F.BadArray; // expected-error 4{{zero-length arrays are not permitted in SYCL device code}}
85+
});
86+
// expected-error@#KernelObjCall 2{{zero-length arrays are not permitted in SYCL device code}}
87+
// expected-note@+2 {{in instantiation of function template specialization}}
88+
// expected-note@+1 {{within field of type 'WrapperOfWrapper' declared here}}
89+
kernel<class SimpleStruct1>([St] { // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
90+
});
91+
92+
Templated<1, int> OK;
93+
Templated<1 - 1, double> Weirdo;
94+
Templated<0, float> Zero;
95+
// expected-error@#KernelObjCall 4{{zero-length arrays are not permitted in SYCL device code}}
96+
// expected-note@+1 {{in instantiation of function template specialization}}
97+
kernel<class UseTemplated>([=] {
98+
(void)OK; // No errors expected
99+
(void)Zero; // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
100+
// expected-note@+1 {{within field of type 'Templated<1 - 1, double>' declared here}}
101+
int A = Weirdo.A; // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
102+
});
103+
104+
// expected-note@#KernelObjCall {{called by 'kernel<UseTemplated1, (lambda at}}
105+
// expected-error@#KernelObjCall 2{{zero-length arrays are not permitted in SYCL device code}}
106+
// expected-note@+2 {{in instantiation of function template specialization}}
107+
// expected-note@+1 {{within field of type 'Templated<0, float>' declared here}}
108+
kernel<class UseTemplated1>([Zero] { // expected-error 2{{zero-length arrays are not permitted in SYCL device code}}
109+
});
110+
111+
templatedContext<10>();
112+
// expected-note@+1 2{{in instantiation of function template specialization}}
113+
templatedContext<0>();
114+
115+
KernelSt K;
116+
// expected-error@#KernelObjCall {{zero-length arrays are not permitted in SYCL device code}}
117+
// expected-note@+1 {{in instantiation of function template specialization}}
118+
kernel<class UseFunctor>(K);
119+
120+
// expected-note@#KernelObjCall {{called by 'kernel<ReturnFromFunc, (lambda at}}
121+
kernel<class ReturnFromFunc>([=] {
122+
// expected-note@+1 {{called by 'operator()'}}
123+
offendingFoo();
124+
});
125+
}

0 commit comments

Comments
 (0)