blob: 400789e7d04fdedd0d54694e37c26ce43f91f266 [file] [log] [blame]
alan-bakerfec0a472018-11-08 18:09:40 -05001// Copyright 2018 The Clspv Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "clang/AST/RecordLayout.h"
16#include "clang/AST/RecursiveASTVisitor.h"
17#include "clang/Basic/TargetInfo.h"
18#include "clang/CodeGen/CodeGenAction.h"
19#include "clang/Frontend/CompilerInstance.h"
20#include "clang/Frontend/FrontendPluginRegistry.h"
21#include "clang/Frontend/TextDiagnosticPrinter.h"
22
23#include "clspv/Option.h"
24
25#include "FrontendPlugin.h"
26
27using namespace clang;
28
29namespace {
30struct ExtraValidationConsumer final : public ASTConsumer {
31private:
32 CompilerInstance &Instance;
33 llvm::StringRef InFile;
34
35 enum CustomDiagnosticType {
36 CustomDiagnosticVectorsMoreThan4Elements = 0,
37 CustomDiagnosticVoidPointer = 1,
38 CustomDiagnosticUBOUnalignedScalar = 2,
39 CustomDiagnosticUBOUnalignedVec2 = 3,
40 CustomDiagnosticUBOUnalignedVec4 = 4,
41 CustomDiagnosticUBOUnalignedArray = 5,
42 CustomDiagnosticUBOUnalignedStruct = 6,
43 CustomDiagnosticUBOSmallStraddle = 7,
44 CustomDiagnosticUBOLargeStraddle = 8,
45 CustomDiagnosticUBOUnalignedStructMember = 9,
46 CustomDiagnosticUBORestrictedSize = 10,
47 CustomDiagnosticUBORestrictedStruct = 11,
alan-baker3d9e2012019-01-11 14:55:30 -050048 CustomDiagnosticUBOArrayStride = 12,
49 CustomDiagnosticLocationInfo = 13,
alan-bakerfec0a472018-11-08 18:09:40 -050050 CustomDiagnosticTotal
51 };
52 std::vector<unsigned> CustomDiagnosticsIDMap;
53
54 bool IsSupportedType(QualType QT, SourceRange SR) {
55 auto *Ty = QT.getTypePtr();
56
57 // First check if we have a pointer type.
58 if (Ty->isPointerType()) {
59 const Type *pointeeTy = Ty->getPointeeType().getTypePtr();
60 if (pointeeTy && pointeeTy->isVoidType()) {
61 // We don't support void pointers.
62 Instance.getDiagnostics().Report(
63 SR.getBegin(), CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer]);
64 return false;
65 }
66 // Otherwise check recursively.
67 return IsSupportedType(Ty->getPointeeType(), SR);
68 }
69
70 const auto &canonicalType = QT.getCanonicalType();
71 if (auto *VT = llvm::dyn_cast<ExtVectorType>(canonicalType)) {
72 // We don't support vectors with more than 4 elements.
73 if (4 < VT->getNumElements()) {
74 Instance.getDiagnostics().Report(
75 SR.getBegin(),
76 CustomDiagnosticsIDMap[CustomDiagnosticVectorsMoreThan4Elements]);
77 return false;
78 }
79 }
80
81 return true;
82 }
83
alan-baker3d9e2012019-01-11 14:55:30 -050084 // Report a diagnostic using |diag|. If |arg_range| and |specific_range|
85 // differ, also issue a note with the specific location of the error.
86 void Report(const CustomDiagnosticType &diag, SourceRange arg_range,
87 SourceRange specific_range) {
88 Instance.getDiagnostics().Report(arg_range.getBegin(),
89 CustomDiagnosticsIDMap[diag]);
90 if (arg_range != specific_range) {
91 Instance.getDiagnostics().Report(
92 specific_range.getBegin(),
93 CustomDiagnosticsIDMap[CustomDiagnosticLocationInfo]);
94 }
95 }
96
alan-bakerfec0a472018-11-08 18:09:40 -050097 // Returns the alignment of |QT| to satisfy standard Uniform buffer layout
98 // rules.
99 uint64_t GetAlignment(const QualType QT, const ASTContext &context) const {
100 const auto canonical = QT.getCanonicalType();
101 uint64_t alignment = context.getTypeAlignInChars(canonical).getQuantity();
102 if (canonical->isRecordType() || canonical->isArrayType()) {
103 return llvm::alignTo(alignment, 16);
104 }
105 return alignment;
106 }
107
108 // Returns true if |QT| is a valid layout for a Uniform buffer. Refer to
109 // 14.5.4 in the Vulkan specification.
110 bool IsSupportedUniformLayout(QualType QT, uint64_t offset,
alan-baker3d9e2012019-01-11 14:55:30 -0500111 ASTContext &context, SourceRange arg_range,
112 SourceRange specific_range) {
alan-bakerfec0a472018-11-08 18:09:40 -0500113 const auto canonical = QT.getCanonicalType();
114 if (canonical->isScalarType()) {
alan-baker3d9e2012019-01-11 14:55:30 -0500115 if (!IsSupportedUniformScalarLayout(canonical, offset, context, arg_range,
116 specific_range))
alan-bakerfec0a472018-11-08 18:09:40 -0500117 return false;
118 } else if (canonical->isExtVectorType()) {
alan-baker3d9e2012019-01-11 14:55:30 -0500119 if (!IsSupportedUniformVectorLayout(canonical, offset, context, arg_range,
120 specific_range))
alan-bakerfec0a472018-11-08 18:09:40 -0500121 return false;
122 } else if (canonical->isArrayType()) {
alan-baker3d9e2012019-01-11 14:55:30 -0500123 if (!IsSupportedUniformArrayLayout(canonical, offset, context, arg_range,
124 specific_range))
alan-bakerfec0a472018-11-08 18:09:40 -0500125 return false;
126 } else if (canonical->isRecordType()) {
alan-baker3d9e2012019-01-11 14:55:30 -0500127 if (!IsSupportedUniformRecordLayout(canonical, offset, context, arg_range,
128 specific_range))
alan-bakerfec0a472018-11-08 18:09:40 -0500129 return false;
130 }
131
132 // TODO(alan-baker): Find a way to avoid this restriction.
133 // Don't allow padding. This prevents structs like:
134 // struct {
135 // int x[2];
136 // int y __attribute((aligned(16)));
137 // };
138 //
139 // This would map in LLVM to { [2 x i32], [8 x i8], i32, [12 xi8] }.
140 // There is no easy way to manipulate the padding after the array to
141 // satisfy the standard Uniform buffer layout rules in this case. The usual
142 // trick is replacing the i8 arrays with an i32 element, but the i32 would
143 // still be laid out too close to the array.
144 const auto type_size = context.getTypeSizeInChars(canonical).getQuantity();
145 const auto type_align = GetAlignment(canonical, context);
146 if (type_size % type_align != 0) {
alan-baker3d9e2012019-01-11 14:55:30 -0500147 Report(CustomDiagnosticUBORestrictedSize, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500148 return false;
149 }
150
151 return true;
152 }
153
154 bool IsSupportedUniformScalarLayout(QualType QT, uint64_t offset,
alan-baker3d9e2012019-01-11 14:55:30 -0500155 ASTContext &context,
156 SourceRange arg_range,
157 SourceRange specific_range) {
alan-bakerfec0a472018-11-08 18:09:40 -0500158 // A scalar type of size N has a base alignment on N.
159 const unsigned type_size = context.getTypeSizeInChars(QT).getQuantity();
160 if (offset % type_size != 0) {
alan-baker3d9e2012019-01-11 14:55:30 -0500161 Report(CustomDiagnosticUBOUnalignedScalar, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500162 return false;
163 }
164
165 return true;
166 }
167
168 bool IsSupportedUniformVectorLayout(QualType QT, uint64_t offset,
alan-baker3d9e2012019-01-11 14:55:30 -0500169 ASTContext &context,
170 SourceRange arg_range,
171 SourceRange specific_range) {
alan-bakerfec0a472018-11-08 18:09:40 -0500172 // 2-component vectors have a base alignment of 2 * (size of element).
173 // 3- and 4-component vectors hae a base alignment of 4 * (size of
174 // element).
175 const auto *VT = llvm::cast<VectorType>(QT);
176 const auto ele_size =
177 context.getTypeSizeInChars(VT->getElementType()).getQuantity();
178 if (VT->getNumElements() == 2) {
179 if (offset % (ele_size * 2) != 0) {
alan-baker3d9e2012019-01-11 14:55:30 -0500180 Report(CustomDiagnosticUBOUnalignedVec2, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500181 return false;
182 }
183 } else if (offset % (ele_size * 4) != 0) {
184 // Other vector sizes cause errors elsewhere.
alan-baker3d9e2012019-01-11 14:55:30 -0500185 Report(CustomDiagnosticUBOUnalignedVec4, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500186 return false;
187 }
188
189 // Straddling rules:
190 // * If total vector size is less than 16 bytes, the offset must place the
191 // entire vector within the same 16 bytes.
192 // * If total vector size is greater than 16 bytes, the offset must be a
193 // multiple of 16.
194 const auto size = context.getTypeSizeInChars(QT).getQuantity();
195 if (size <= 16 && (offset / 16 != (offset + size - 1) / 16)) {
alan-baker3d9e2012019-01-11 14:55:30 -0500196 Report(CustomDiagnosticUBOSmallStraddle, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500197 return false;
198 } else if (size > 16 && (offset % 16 != 0)) {
alan-baker3d9e2012019-01-11 14:55:30 -0500199 Report(CustomDiagnosticUBOLargeStraddle, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500200 return false;
201 }
202
alan-baker3d9e2012019-01-11 14:55:30 -0500203 return IsSupportedUniformLayout(VT->getElementType(), offset, context,
204 arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500205 }
206
207 bool IsSupportedUniformArrayLayout(QualType QT, uint64_t offset,
alan-baker3d9e2012019-01-11 14:55:30 -0500208 ASTContext &context, SourceRange arg_range,
209 SourceRange specific_range) {
alan-bakerfec0a472018-11-08 18:09:40 -0500210 // An array has a base alignment of is element type, rounded up to a
211 // multiple of 16.
212 const auto *AT = llvm::cast<ArrayType>(QT);
alan-baker3d9e2012019-01-11 14:55:30 -0500213 const auto element_align = GetAlignment(AT->getElementType(), context);
214 const auto type_align = llvm::alignTo(element_align, 16);
alan-bakerfec0a472018-11-08 18:09:40 -0500215 if (offset % type_align != 0) {
alan-baker3d9e2012019-01-11 14:55:30 -0500216 Report(CustomDiagnosticUBOUnalignedArray, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500217 return false;
218 }
alan-baker3d9e2012019-01-11 14:55:30 -0500219 if (!clspv::Option::RelaxedUniformBufferLayout()) {
220 // The ArrayStride must be a multiple of the base alignment of the array
221 // (i.e. a multiple of 16). This means that the element size must be
222 // restricted to be the base alignment of the array.
223 const auto element_size =
224 context.getTypeSizeInChars(AT->getElementType()).getQuantity();
225 if (element_size % type_align != 0) {
226 Report(CustomDiagnosticUBOArrayStride, arg_range, specific_range);
227 return false;
228 }
229 }
alan-bakerfec0a472018-11-08 18:09:40 -0500230
alan-baker3d9e2012019-01-11 14:55:30 -0500231 return IsSupportedUniformLayout(AT->getElementType(), offset, context,
232 arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500233 }
234
235 bool IsSupportedUniformRecordLayout(QualType QT, uint64_t offset,
alan-baker3d9e2012019-01-11 14:55:30 -0500236 ASTContext &context,
237 SourceRange arg_range,
238 SourceRange specific_range) {
alan-bakerfec0a472018-11-08 18:09:40 -0500239 // A structure has a base alignment of its largest member, rounded up to a
240 // multiple of 16.
241 const auto *RT = llvm::cast<RecordType>(QT);
242 const auto type_alignment = GetAlignment(QT, context);
243 if (offset % type_alignment != 0) {
alan-baker3d9e2012019-01-11 14:55:30 -0500244 Report(CustomDiagnosticUBOUnalignedStruct, arg_range, specific_range);
alan-bakerfec0a472018-11-08 18:09:40 -0500245 return false;
246 }
247
248 const auto &layout = context.getASTRecordLayout(RT->getDecl());
249 const FieldDecl *prev = nullptr;
250 for (auto field_decl : RT->getDecl()->fields()) {
251 const auto field_type = field_decl->getType();
252 const auto field_alignment = GetAlignment(field_type, context);
253 const unsigned field_no = field_decl->getFieldIndex();
254 const uint64_t field_offset =
255 layout.getFieldOffset(field_no) / context.getCharWidth();
256
257 // Rules must be checked recursively.
alan-baker3d9e2012019-01-11 14:55:30 -0500258 if (!IsSupportedUniformLayout(field_type, field_offset, context,
259 arg_range, field_decl->getSourceRange())) {
alan-bakerfec0a472018-11-08 18:09:40 -0500260 return false;
261 }
262
263 if (prev) {
264 const auto prev_canonical = prev->getType().getCanonicalType();
265 const uint64_t prev_offset =
266 layout.getFieldOffset(field_no - 1) / context.getCharWidth();
267 const auto prev_size =
268 context.getTypeSizeInChars(prev_canonical).getQuantity();
269 const auto prev_alignment = GetAlignment(prev_canonical, context);
270 const auto next_available =
271 prev_offset + llvm::alignTo(prev_size, prev_alignment);
272 if (prev_canonical->isArrayType() || prev_canonical->isRecordType()) {
273 // The next element after an array or struct must be placed on or
274 // after the next multiple of the alignment of that array or
275 // struct.
276 // Both arrays and structs must be aligned to a multiple of 16 bytes.
277 if (llvm::alignTo(next_available, 16) > field_offset) {
alan-baker3d9e2012019-01-11 14:55:30 -0500278 Report(CustomDiagnosticUBOUnalignedStructMember, arg_range,
279 field_decl->getSourceRange());
alan-bakerfec0a472018-11-08 18:09:40 -0500280 return false;
281 }
282 }
283 }
284
285 prev = field_decl;
286 }
287
288 return true;
289 }
290
291 // This will be used to check the inside of function bodies.
292 class DeclVisitor : public RecursiveASTVisitor<DeclVisitor> {
293 private:
294 ExtraValidationConsumer &consumer;
295
296 public:
297 explicit DeclVisitor(ExtraValidationConsumer &VC) : consumer(VC) {}
298
299 // Visits a declaration. Emits a diagnostic and returns false if the
300 // declaration represents an unsupported vector value or vector type.
301 // Otherwise returns true.
302 bool VisitDecl(Decl *D) {
303 // Looking at the Decl class hierarchy, it seems ValueDecl and TypeDecl
304 // are the only two that might represent an unsupported vector type.
305 if (auto *VD = dyn_cast<ValueDecl>(D)) {
306 return consumer.IsSupportedType(VD->getType(), D->getSourceRange());
307 } else if (auto *TD = dyn_cast<TypeDecl>(D)) {
308 QualType DefinedType = TD->getASTContext().getTypeDeclType(TD);
309 return consumer.IsSupportedType(DefinedType, TD->getSourceRange());
310 }
311 return true;
312 }
313 };
314
315 DeclVisitor Visitor;
316
317public:
318 explicit ExtraValidationConsumer(CompilerInstance &Instance,
319 llvm::StringRef InFile)
320 : Instance(Instance), InFile(InFile),
321 CustomDiagnosticsIDMap(CustomDiagnosticTotal), Visitor(*this) {
322 auto &DE = Instance.getDiagnostics();
323
324 CustomDiagnosticsIDMap[CustomDiagnosticVectorsMoreThan4Elements] =
325 DE.getCustomDiagID(
326 DiagnosticsEngine::Error,
327 "vectors with more than 4 elements are not supported");
328 CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer] = DE.getCustomDiagID(
329 DiagnosticsEngine::Error, "pointer-to-void is not supported");
330 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedScalar] =
331 DE.getCustomDiagID(
332 DiagnosticsEngine::Error,
333 "in an UBO, scalar elements must be aligned to their size");
334 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec2] =
335 DE.getCustomDiagID(DiagnosticsEngine::Error,
336 "in an UBO, two-component vectors must be aligned "
337 "to 2 times their element size");
338 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec4] =
339 DE.getCustomDiagID(DiagnosticsEngine::Error,
340 "in an UBO, three- and four-component vectors must "
341 "be aligned to 4 times their element size");
342 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedArray] =
343 DE.getCustomDiagID(DiagnosticsEngine::Error,
344 "in an UBO, arrays must be aligned to their element "
345 "alignment, rounded up to a multiple of 16 bytes");
346 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStruct] =
347 DE.getCustomDiagID(
348 DiagnosticsEngine::Error,
349 "in an UBO, structs must be aligned to their "
350 "largest element alignment, rounded up to a multiple of "
351 "16 bytes");
352 CustomDiagnosticsIDMap[CustomDiagnosticUBOSmallStraddle] =
353 DE.getCustomDiagID(DiagnosticsEngine::Error,
354 "in an UBO, vectors with a total size less than or "
355 "equal to 16 bytes must be placed entirely within a "
356 "16 byte aligned region");
357 CustomDiagnosticsIDMap[CustomDiagnosticUBOLargeStraddle] =
358 DE.getCustomDiagID(DiagnosticsEngine::Error,
359 "in an UBO, vectors with a total size greater than "
360 "16 bytes must aligned to 16 bytes");
361 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStructMember] =
362 DE.getCustomDiagID(DiagnosticsEngine::Error,
363 "in an UBO, a structure member must not be placed "
364 "between the end of a structure or array and the "
365 "next multiple of the base alignment of that "
366 "structure or array");
367 CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedSize] =
368 DE.getCustomDiagID(DiagnosticsEngine::Error,
369 "clspv restriction: UBO element size must be a "
370 "multiple of that element's alignment");
371 CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedStruct] =
372 DE.getCustomDiagID(
373 DiagnosticsEngine::Error,
374 "clspv restriction: UBO structures may not have implicit padding");
alan-baker3d9e2012019-01-11 14:55:30 -0500375 CustomDiagnosticsIDMap[CustomDiagnosticUBOArrayStride] = DE.getCustomDiagID(
376 DiagnosticsEngine::Error,
377 "clspv restriction: to satisfy UBO ArrayStride restrictions, element "
378 "size must be a multiple of array alignment");
379 CustomDiagnosticsIDMap[CustomDiagnosticLocationInfo] =
380 DE.getCustomDiagID(DiagnosticsEngine::Note, "here");
alan-bakerfec0a472018-11-08 18:09:40 -0500381 }
382
383 virtual bool HandleTopLevelDecl(DeclGroupRef DG) override {
384 for (auto *D : DG) {
385 if (auto *FD = llvm::dyn_cast<FunctionDecl>(D)) {
386 // If the function has a body it means we are not an OpenCL builtin
387 // function.
388 if (FD->hasBody()) {
389 if (!IsSupportedType(FD->getReturnType(),
390 FD->getReturnTypeSourceRange())) {
391 return false;
392 }
393
394 bool is_opencl_kernel = false;
395 if (FD->hasAttrs()) {
396 for (auto *attr : FD->attrs()) {
397 if (attr->getKind() == attr::Kind::OpenCLKernel) {
398 is_opencl_kernel = true;
399 }
400 }
401 }
402
403 for (auto *P : FD->parameters()) {
404 auto type = P->getType();
405 if (!IsSupportedType(P->getOriginalType(), P->getSourceRange())) {
406 return false;
407 }
408
409 if (is_opencl_kernel &&
410 clspv::Option::ConstantArgsInUniformBuffer() &&
411 type->isPointerType() &&
412 type->getPointeeType().getAddressSpace() ==
413 LangAS::opencl_constant) {
alan-baker3d9e2012019-01-11 14:55:30 -0500414 // The argument will be generated as an array within a block.
415 // Generate an array type to check the validity for the generated
416 // case.
417 auto array_type = FD->getASTContext().getIncompleteArrayType(
418 type->getPointeeType(), clang::ArrayType::Normal, 0);
419 if (!IsSupportedUniformLayout(array_type, 0, FD->getASTContext(),
420 P->getSourceRange(),
alan-bakerfec0a472018-11-08 18:09:40 -0500421 P->getSourceRange())) {
422 return false;
423 }
424 }
425 }
426
427 // Check for unsupported vector types.
428 Visitor.TraverseDecl(FD);
429 }
430 }
431 }
432
433 return true;
434 }
435};
436} // namespace
437
438namespace clspv {
439std::unique_ptr<ASTConsumer>
440ExtraValidationASTAction::CreateASTConsumer(CompilerInstance &CI,
441 llvm::StringRef InFile) {
442 return std::unique_ptr<ASTConsumer>(new ExtraValidationConsumer(CI, InFile));
443}
444} // namespace clspv