blob: 25e0bd96b8de71e99830c5f6eee9bc0a152420e7 [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,
48 CustomDiagnosticTotal
49 };
50 std::vector<unsigned> CustomDiagnosticsIDMap;
51
52 bool IsSupportedType(QualType QT, SourceRange SR) {
53 auto *Ty = QT.getTypePtr();
54
55 // First check if we have a pointer type.
56 if (Ty->isPointerType()) {
57 const Type *pointeeTy = Ty->getPointeeType().getTypePtr();
58 if (pointeeTy && pointeeTy->isVoidType()) {
59 // We don't support void pointers.
60 Instance.getDiagnostics().Report(
61 SR.getBegin(), CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer]);
62 return false;
63 }
64 // Otherwise check recursively.
65 return IsSupportedType(Ty->getPointeeType(), SR);
66 }
67
68 const auto &canonicalType = QT.getCanonicalType();
69 if (auto *VT = llvm::dyn_cast<ExtVectorType>(canonicalType)) {
70 // We don't support vectors with more than 4 elements.
71 if (4 < VT->getNumElements()) {
72 Instance.getDiagnostics().Report(
73 SR.getBegin(),
74 CustomDiagnosticsIDMap[CustomDiagnosticVectorsMoreThan4Elements]);
75 return false;
76 }
77 }
78
79 return true;
80 }
81
82 // Returns the alignment of |QT| to satisfy standard Uniform buffer layout
83 // rules.
84 uint64_t GetAlignment(const QualType QT, const ASTContext &context) const {
85 const auto canonical = QT.getCanonicalType();
86 uint64_t alignment = context.getTypeAlignInChars(canonical).getQuantity();
87 if (canonical->isRecordType() || canonical->isArrayType()) {
88 return llvm::alignTo(alignment, 16);
89 }
90 return alignment;
91 }
92
93 // Returns true if |QT| is a valid layout for a Uniform buffer. Refer to
94 // 14.5.4 in the Vulkan specification.
95 bool IsSupportedUniformLayout(QualType QT, uint64_t offset,
96 ASTContext &context, SourceRange SR) {
97 const auto canonical = QT.getCanonicalType();
98 if (canonical->isScalarType()) {
99 if (!IsSupportedUniformScalarLayout(canonical, offset, context, SR))
100 return false;
101 } else if (canonical->isExtVectorType()) {
102 if (!IsSupportedUniformVectorLayout(canonical, offset, context, SR))
103 return false;
104 } else if (canonical->isArrayType()) {
105 if (!IsSupportedUniformArrayLayout(canonical, offset, context, SR))
106 return false;
107 } else if (canonical->isRecordType()) {
108 if (!IsSupportedUniformRecordLayout(canonical, offset, context, SR))
109 return false;
110 }
111
112 // TODO(alan-baker): Find a way to avoid this restriction.
113 // Don't allow padding. This prevents structs like:
114 // struct {
115 // int x[2];
116 // int y __attribute((aligned(16)));
117 // };
118 //
119 // This would map in LLVM to { [2 x i32], [8 x i8], i32, [12 xi8] }.
120 // There is no easy way to manipulate the padding after the array to
121 // satisfy the standard Uniform buffer layout rules in this case. The usual
122 // trick is replacing the i8 arrays with an i32 element, but the i32 would
123 // still be laid out too close to the array.
124 const auto type_size = context.getTypeSizeInChars(canonical).getQuantity();
125 const auto type_align = GetAlignment(canonical, context);
126 if (type_size % type_align != 0) {
127 Instance.getDiagnostics().Report(
128 SR.getBegin(),
129 CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedSize]);
130 return false;
131 }
132
133 return true;
134 }
135
136 bool IsSupportedUniformScalarLayout(QualType QT, uint64_t offset,
137 ASTContext &context, SourceRange SR) {
138 // A scalar type of size N has a base alignment on N.
139 const unsigned type_size = context.getTypeSizeInChars(QT).getQuantity();
140 if (offset % type_size != 0) {
141 Instance.getDiagnostics().Report(
142 SR.getBegin(),
143 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedScalar]);
144 return false;
145 }
146
147 return true;
148 }
149
150 bool IsSupportedUniformVectorLayout(QualType QT, uint64_t offset,
151 ASTContext &context, SourceRange SR) {
152 // 2-component vectors have a base alignment of 2 * (size of element).
153 // 3- and 4-component vectors hae a base alignment of 4 * (size of
154 // element).
155 const auto *VT = llvm::cast<VectorType>(QT);
156 const auto ele_size =
157 context.getTypeSizeInChars(VT->getElementType()).getQuantity();
158 if (VT->getNumElements() == 2) {
159 if (offset % (ele_size * 2) != 0) {
160 Instance.getDiagnostics().Report(
161 SR.getBegin(),
162 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec2]);
163 return false;
164 }
165 } else if (offset % (ele_size * 4) != 0) {
166 // Other vector sizes cause errors elsewhere.
167 Instance.getDiagnostics().Report(
168 SR.getBegin(),
169 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec4]);
170 return false;
171 }
172
173 // Straddling rules:
174 // * If total vector size is less than 16 bytes, the offset must place the
175 // entire vector within the same 16 bytes.
176 // * If total vector size is greater than 16 bytes, the offset must be a
177 // multiple of 16.
178 const auto size = context.getTypeSizeInChars(QT).getQuantity();
179 if (size <= 16 && (offset / 16 != (offset + size - 1) / 16)) {
180 Instance.getDiagnostics().Report(
181 SR.getBegin(),
182 CustomDiagnosticsIDMap[CustomDiagnosticUBOSmallStraddle]);
183 return false;
184 } else if (size > 16 && (offset % 16 != 0)) {
185 Instance.getDiagnostics().Report(
186 SR.getBegin(),
187 CustomDiagnosticsIDMap[CustomDiagnosticUBOLargeStraddle]);
188 return false;
189 }
190
191 return IsSupportedUniformLayout(VT->getElementType(), offset, context, SR);
192 }
193
194 bool IsSupportedUniformArrayLayout(QualType QT, uint64_t offset,
195 ASTContext &context, SourceRange SR) {
196 // An array has a base alignment of is element type, rounded up to a
197 // multiple of 16.
198 const auto *AT = llvm::cast<ArrayType>(QT);
199 const auto type_align =
200 llvm::alignTo(GetAlignment(AT->getElementType(), context), 16);
201 if (offset % type_align != 0) {
202 Instance.getDiagnostics().Report(
203 SR.getBegin(),
204 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedArray]);
205 return false;
206 }
207
208 return IsSupportedUniformLayout(AT->getElementType(), offset, context, SR);
209 }
210
211 bool IsSupportedUniformRecordLayout(QualType QT, uint64_t offset,
212 ASTContext &context, SourceRange SR) {
213 // A structure has a base alignment of its largest member, rounded up to a
214 // multiple of 16.
215 const auto *RT = llvm::cast<RecordType>(QT);
216 const auto type_alignment = GetAlignment(QT, context);
217 if (offset % type_alignment != 0) {
218 Instance.getDiagnostics().Report(
219 SR.getBegin(),
220 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStruct]);
221 return false;
222 }
223
224 const auto &layout = context.getASTRecordLayout(RT->getDecl());
225 const FieldDecl *prev = nullptr;
226 for (auto field_decl : RT->getDecl()->fields()) {
227 const auto field_type = field_decl->getType();
228 const auto field_alignment = GetAlignment(field_type, context);
229 const unsigned field_no = field_decl->getFieldIndex();
230 const uint64_t field_offset =
231 layout.getFieldOffset(field_no) / context.getCharWidth();
232
233 // Rules must be checked recursively.
234 if (!IsSupportedUniformLayout(field_type, field_offset, context, SR)) {
235 return false;
236 }
237
238 if (prev) {
239 const auto prev_canonical = prev->getType().getCanonicalType();
240 const uint64_t prev_offset =
241 layout.getFieldOffset(field_no - 1) / context.getCharWidth();
242 const auto prev_size =
243 context.getTypeSizeInChars(prev_canonical).getQuantity();
244 const auto prev_alignment = GetAlignment(prev_canonical, context);
245 const auto next_available =
246 prev_offset + llvm::alignTo(prev_size, prev_alignment);
247 if (prev_canonical->isArrayType() || prev_canonical->isRecordType()) {
248 // The next element after an array or struct must be placed on or
249 // after the next multiple of the alignment of that array or
250 // struct.
251 // Both arrays and structs must be aligned to a multiple of 16 bytes.
252 if (llvm::alignTo(next_available, 16) > field_offset) {
253 Instance.getDiagnostics().Report(
254 SR.getBegin(), CustomDiagnosticsIDMap
255 [CustomDiagnosticUBOUnalignedStructMember]);
256 return false;
257 }
258 }
259 }
260
261 prev = field_decl;
262 }
263
264 return true;
265 }
266
267 // This will be used to check the inside of function bodies.
268 class DeclVisitor : public RecursiveASTVisitor<DeclVisitor> {
269 private:
270 ExtraValidationConsumer &consumer;
271
272 public:
273 explicit DeclVisitor(ExtraValidationConsumer &VC) : consumer(VC) {}
274
275 // Visits a declaration. Emits a diagnostic and returns false if the
276 // declaration represents an unsupported vector value or vector type.
277 // Otherwise returns true.
278 bool VisitDecl(Decl *D) {
279 // Looking at the Decl class hierarchy, it seems ValueDecl and TypeDecl
280 // are the only two that might represent an unsupported vector type.
281 if (auto *VD = dyn_cast<ValueDecl>(D)) {
282 return consumer.IsSupportedType(VD->getType(), D->getSourceRange());
283 } else if (auto *TD = dyn_cast<TypeDecl>(D)) {
284 QualType DefinedType = TD->getASTContext().getTypeDeclType(TD);
285 return consumer.IsSupportedType(DefinedType, TD->getSourceRange());
286 }
287 return true;
288 }
289 };
290
291 DeclVisitor Visitor;
292
293public:
294 explicit ExtraValidationConsumer(CompilerInstance &Instance,
295 llvm::StringRef InFile)
296 : Instance(Instance), InFile(InFile),
297 CustomDiagnosticsIDMap(CustomDiagnosticTotal), Visitor(*this) {
298 auto &DE = Instance.getDiagnostics();
299
300 CustomDiagnosticsIDMap[CustomDiagnosticVectorsMoreThan4Elements] =
301 DE.getCustomDiagID(
302 DiagnosticsEngine::Error,
303 "vectors with more than 4 elements are not supported");
304 CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer] = DE.getCustomDiagID(
305 DiagnosticsEngine::Error, "pointer-to-void is not supported");
306 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedScalar] =
307 DE.getCustomDiagID(
308 DiagnosticsEngine::Error,
309 "in an UBO, scalar elements must be aligned to their size");
310 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec2] =
311 DE.getCustomDiagID(DiagnosticsEngine::Error,
312 "in an UBO, two-component vectors must be aligned "
313 "to 2 times their element size");
314 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedVec4] =
315 DE.getCustomDiagID(DiagnosticsEngine::Error,
316 "in an UBO, three- and four-component vectors must "
317 "be aligned to 4 times their element size");
318 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedArray] =
319 DE.getCustomDiagID(DiagnosticsEngine::Error,
320 "in an UBO, arrays must be aligned to their element "
321 "alignment, rounded up to a multiple of 16 bytes");
322 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStruct] =
323 DE.getCustomDiagID(
324 DiagnosticsEngine::Error,
325 "in an UBO, structs must be aligned to their "
326 "largest element alignment, rounded up to a multiple of "
327 "16 bytes");
328 CustomDiagnosticsIDMap[CustomDiagnosticUBOSmallStraddle] =
329 DE.getCustomDiagID(DiagnosticsEngine::Error,
330 "in an UBO, vectors with a total size less than or "
331 "equal to 16 bytes must be placed entirely within a "
332 "16 byte aligned region");
333 CustomDiagnosticsIDMap[CustomDiagnosticUBOLargeStraddle] =
334 DE.getCustomDiagID(DiagnosticsEngine::Error,
335 "in an UBO, vectors with a total size greater than "
336 "16 bytes must aligned to 16 bytes");
337 CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStructMember] =
338 DE.getCustomDiagID(DiagnosticsEngine::Error,
339 "in an UBO, a structure member must not be placed "
340 "between the end of a structure or array and the "
341 "next multiple of the base alignment of that "
342 "structure or array");
343 CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedSize] =
344 DE.getCustomDiagID(DiagnosticsEngine::Error,
345 "clspv restriction: UBO element size must be a "
346 "multiple of that element's alignment");
347 CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedStruct] =
348 DE.getCustomDiagID(
349 DiagnosticsEngine::Error,
350 "clspv restriction: UBO structures may not have implicit padding");
351 }
352
353 virtual bool HandleTopLevelDecl(DeclGroupRef DG) override {
354 for (auto *D : DG) {
355 if (auto *FD = llvm::dyn_cast<FunctionDecl>(D)) {
356 // If the function has a body it means we are not an OpenCL builtin
357 // function.
358 if (FD->hasBody()) {
359 if (!IsSupportedType(FD->getReturnType(),
360 FD->getReturnTypeSourceRange())) {
361 return false;
362 }
363
364 bool is_opencl_kernel = false;
365 if (FD->hasAttrs()) {
366 for (auto *attr : FD->attrs()) {
367 if (attr->getKind() == attr::Kind::OpenCLKernel) {
368 is_opencl_kernel = true;
369 }
370 }
371 }
372
373 for (auto *P : FD->parameters()) {
374 auto type = P->getType();
375 if (!IsSupportedType(P->getOriginalType(), P->getSourceRange())) {
376 return false;
377 }
378
379 if (is_opencl_kernel &&
380 clspv::Option::ConstantArgsInUniformBuffer() &&
381 type->isPointerType() &&
382 type->getPointeeType().getAddressSpace() ==
383 LangAS::opencl_constant) {
384 if (!IsSupportedUniformLayout(type->getPointeeType(), 0,
385 FD->getASTContext(),
386 P->getSourceRange())) {
387 return false;
388 }
389 }
390 }
391
392 // Check for unsupported vector types.
393 Visitor.TraverseDecl(FD);
394 }
395 }
396 }
397
398 return true;
399 }
400};
401} // namespace
402
403namespace clspv {
404std::unique_ptr<ASTConsumer>
405ExtraValidationASTAction::CreateASTConsumer(CompilerInstance &CI,
406 llvm::StringRef InFile) {
407 return std::unique_ptr<ASTConsumer>(new ExtraValidationConsumer(CI, InFile));
408}
409} // namespace clspv