| // Copyright 2018-2021 The Clspv Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "clang/AST/RecordLayout.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/Basic/TargetInfo.h" |
| #include "clang/CodeGen/CodeGenAction.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/FrontendPluginRegistry.h" |
| #include "clang/Frontend/TextDiagnosticPrinter.h" |
| |
| #include "llvm/Support/Debug.h" |
| |
| #include "clspv/Option.h" |
| |
| #include "FrontendPlugin.h" |
| |
| #include <unordered_set> |
| |
| using namespace clang; |
| |
| namespace { |
| |
| static uint32_t kClusteredCount = 0; |
| |
| struct ExtraValidationConsumer final : public ASTConsumer { |
| private: |
| CompilerInstance &Instance; |
| llvm::StringRef InFile; |
| |
| enum Layout { UBO, SSBO }; |
| |
| enum CustomDiagnosticType { |
| CustomDiagnosticVectorsMoreThan4Elements, |
| CustomDiagnosticVoidPointer, |
| CustomDiagnosticUnalignedScalar, |
| CustomDiagnosticUnalignedVec2, |
| CustomDiagnosticUnalignedVec4, |
| CustomDiagnosticUBOUnalignedArray, |
| CustomDiagnosticUBOUnalignedStruct, |
| CustomDiagnosticSmallStraddle, |
| CustomDiagnosticLargeStraddle, |
| CustomDiagnosticUnalignedStructMember, |
| CustomDiagnosticUBORestrictedSize, |
| CustomDiagnosticUBORestrictedStruct, |
| CustomDiagnosticUBOArrayStride, |
| CustomDiagnosticLocationInfo, |
| CustomDiagnosticSSBOUnalignedArray, |
| CustomDiagnosticSSBOUnalignedStruct, |
| CustomDiagnosticOverloadedKernel, |
| CustomDiagnosticStructContainsPointer, |
| CustomDiagnosticRecursiveStruct, |
| CustomDiagnosticPushConstantSizeExceeded, |
| CustomDiagnosticPushConstantContainsArray, |
| CustomDiagnosticUnsupported16BitStorage, |
| CustomDiagnosticUnsupported8BitStorage, |
| CustomDiagnosticTotal |
| }; |
| std::vector<unsigned> CustomDiagnosticsIDMap; |
| |
| clspv::Option::StorageClass ConvertToStorageClass(clang::LangAS aspace) { |
| switch (aspace) { |
| case LangAS::opencl_constant: |
| if (clspv::Option::ConstantArgsInUniformBuffer()) { |
| return clspv::Option::StorageClass::kUBO; |
| } else { |
| return clspv::Option::StorageClass::kSSBO; |
| } |
| case LangAS::opencl_global: |
| default: |
| return clspv::Option::StorageClass::kSSBO; |
| } |
| } |
| |
| bool ContainsSizedType(QualType QT, uint32_t width) { |
| auto canonical = QT.getCanonicalType(); |
| if (auto *BT = dyn_cast<BuiltinType>(canonical)) { |
| switch (BT->getKind()) { |
| case BuiltinType::UShort: |
| case BuiltinType::Short: |
| case BuiltinType::Half: |
| case BuiltinType::Float16: |
| return width == 16; |
| case BuiltinType::UChar: |
| case BuiltinType::Char_U: |
| case BuiltinType::SChar: |
| case BuiltinType::Char_S: |
| return width == 8; |
| default: |
| return false; |
| } |
| } else if (auto *PT = dyn_cast<PointerType>(canonical)) { |
| return ContainsSizedType(PT->getPointeeType(), width); |
| } else if (auto *AT = dyn_cast<ArrayType>(canonical)) { |
| return ContainsSizedType(AT->getElementType(), width); |
| } else if (auto *VT = dyn_cast<VectorType>(canonical)) { |
| return ContainsSizedType(VT->getElementType(), width); |
| } else if (auto *RT = dyn_cast<RecordType>(canonical)) { |
| for (auto field_decl : RT->getDecl()->fields()) { |
| if (ContainsSizedType(field_decl->getType(), width)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool ContainsPointerType(QualType QT) { |
| auto canonical = QT.getCanonicalType(); |
| if (canonical->isPointerType()) { |
| return true; |
| } else if (auto *AT = dyn_cast<ArrayType>(canonical)) { |
| return ContainsPointerType(AT->getElementType()); |
| } else if (auto *RT = dyn_cast<RecordType>(canonical)) { |
| for (auto field_decl : RT->getDecl()->fields()) { |
| if (ContainsPointerType(field_decl->getType())) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool ContainsArrayType(QualType QT) { |
| auto canonical = QT.getCanonicalType(); |
| if (auto *PT = dyn_cast<PointerType>(canonical)) { |
| return ContainsArrayType(PT->getPointeeType()); |
| } else if (isa<ArrayType>(canonical)) { |
| return true; |
| } else if (auto *RT = dyn_cast<RecordType>(canonical)) { |
| for (auto field_decl : RT->getDecl()->fields()) { |
| if (ContainsArrayType(field_decl->getType())) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool IsRecursiveType(QualType QT, llvm::DenseSet<const Type *> *seen) { |
| auto canonical = QT.getCanonicalType(); |
| if (canonical->isRecordType() && |
| !seen->insert(canonical.getTypePtr()).second) { |
| return true; |
| } |
| |
| if (auto *PT = dyn_cast<PointerType>(canonical)) { |
| return IsRecursiveType(PT->getPointeeType(), seen); |
| } else if (auto *AT = dyn_cast<ArrayType>(canonical)) { |
| return IsRecursiveType(AT->getElementType(), seen); |
| } else if (auto *RT = dyn_cast<RecordType>(canonical)) { |
| for (auto field_decl : RT->getDecl()->fields()) { |
| if (IsRecursiveType(field_decl->getType(), seen)) |
| return true; |
| } |
| } |
| |
| seen->erase(canonical.getTypePtr()); |
| return false; |
| } |
| |
| bool IsSupportedType(QualType QT, SourceRange SR, bool IsKernelParameter) { |
| auto *Ty = QT.getTypePtr(); |
| |
| // First check if we have a pointer type. |
| if (Ty->isPointerType()) { |
| const Type *pointeeTy = Ty->getPointeeType().getTypePtr(); |
| if (pointeeTy && pointeeTy->isVoidType()) { |
| // We don't support void pointers. |
| Instance.getDiagnostics().Report( |
| SR.getBegin(), CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer]); |
| return false; |
| } |
| // Otherwise check recursively. |
| return IsSupportedType(Ty->getPointeeType(), SR, IsKernelParameter); |
| } |
| |
| const auto &canonicalType = QT.getCanonicalType(); |
| if (auto *VT = llvm::dyn_cast<ExtVectorType>(canonicalType)) { |
| // We don't support vectors with more than 4 elements under all |
| // circumstances. |
| if (4 < VT->getNumElements() && !clspv::Option::LongVectorSupport()) { |
| Report(CustomDiagnosticVectorsMoreThan4Elements, SR, SR); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| if (auto *RT = llvm::dyn_cast<RecordType>(canonicalType)) { |
| // Do not allow recursive struct definitions. |
| llvm::DenseSet<const Type *> seen; |
| if (IsRecursiveType(canonicalType, &seen)) { |
| Instance.getDiagnostics().Report( |
| SR.getBegin(), |
| CustomDiagnosticsIDMap[CustomDiagnosticRecursiveStruct]); |
| return false; |
| } |
| |
| // To avoid infinite recursion, first verify that the record is not |
| // recursive and then that its fields are supported. |
| for (auto *field_decl : RT->getDecl()->fields()) { |
| if (!IsSupportedType(field_decl->getType(), SR, IsKernelParameter)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| if (auto *AT = llvm::dyn_cast<ArrayType>(canonicalType)) { |
| return IsSupportedType(AT->getElementType(), SR, IsKernelParameter); |
| } |
| |
| // For function prototypes, recurse on return type and parameter types. |
| if (auto *FT = llvm::dyn_cast<FunctionProtoType>(canonicalType)) { |
| IsKernelParameter = |
| IsKernelParameter || (FT->getCallConv() == CC_OpenCLKernel); |
| for (auto param : FT->getParamTypes()) { |
| if (!IsSupportedType(param, SR, IsKernelParameter)) { |
| return false; |
| } |
| } |
| |
| if (!IsSupportedType(FT->getReturnType(), SR, IsKernelParameter)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| if (QT->isBuiltinType()) { |
| return true; |
| } |
| |
| if (QT->isAtomicType()) { |
| return true; |
| } |
| |
| #ifndef NDEBUG |
| llvm::dbgs() << "IsSupportedType lacks support for QualType: " |
| << QT.getAsString() << '\n'; |
| #endif |
| llvm_unreachable("Type not covered by IsSupportedType."); |
| } |
| |
| // Report a diagnostic using |diag|. If |arg_range| and |specific_range| |
| // differ, also issue a note with the specific location of the error. |
| void Report(const CustomDiagnosticType &diag, SourceRange arg_range, |
| SourceRange specific_range) { |
| Instance.getDiagnostics().Report(arg_range.getBegin(), |
| CustomDiagnosticsIDMap[diag]); |
| if (arg_range != specific_range) { |
| Instance.getDiagnostics().Report( |
| specific_range.getBegin(), |
| CustomDiagnosticsIDMap[CustomDiagnosticLocationInfo]); |
| } |
| } |
| |
| // Returns the alignment of |QT| to satisfy |layout|'s rules. |
| uint64_t GetAlignment(const QualType QT, const Layout &layout, |
| const ASTContext &context) const { |
| const auto canonical = QT.getCanonicalType(); |
| uint64_t alignment = context.getTypeAlignInChars(canonical).getQuantity(); |
| if (layout == UBO && |
| (canonical->isRecordType() || canonical->isArrayType())) { |
| return llvm::alignTo(alignment, 16); |
| } |
| return alignment; |
| } |
| |
| // Returns true if |QT| is a valid layout for a Uniform buffer. Refer to |
| // 14.5.4 in the Vulkan specification. |
| bool IsSupportedLayout(QualType QT, uint64_t offset, const Layout &layout, |
| ASTContext &context, SourceRange arg_range, |
| SourceRange specific_range) { |
| const auto canonical = QT.getCanonicalType(); |
| if (canonical->isScalarType()) { |
| if (!IsSupportedScalarLayout(canonical, offset, layout, context, |
| arg_range, specific_range)) |
| return false; |
| } else if (canonical->isExtVectorType()) { |
| if (!IsSupportedVectorLayout(canonical, offset, layout, context, |
| arg_range, specific_range)) |
| return false; |
| } else if (canonical->isArrayType()) { |
| if (!IsSupportedArrayLayout(canonical, offset, layout, context, arg_range, |
| specific_range)) |
| return false; |
| } else if (canonical->isRecordType()) { |
| if (!IsSupportedRecordLayout(canonical, offset, layout, context, |
| arg_range, specific_range)) |
| return false; |
| } |
| |
| // TODO(alan-baker): Find a way to avoid this restriction. |
| // Don't allow padding. This prevents structs like: |
| // struct { |
| // int x[2]; |
| // int y __attribute((aligned(16))); |
| // }; |
| // |
| // This would map in LLVM to { [2 x i32], [8 x i8], i32, [12 xi8] }. |
| // There is no easy way to manipulate the padding after the array to |
| // satisfy the standard Uniform buffer layout rules in this case. The usual |
| // trick is replacing the i8 arrays with an i32 element, but the i32 would |
| // still be laid out too close to the array. |
| const auto type_size = context.getTypeSizeInChars(canonical).getQuantity(); |
| const auto type_align = GetAlignment(canonical, layout, context); |
| if (layout == UBO && (type_size % type_align != 0)) { |
| Report(CustomDiagnosticUBORestrictedSize, arg_range, specific_range); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsSupportedScalarLayout(QualType QT, uint64_t offset, |
| const Layout & /*layout*/, ASTContext &context, |
| SourceRange arg_range, |
| SourceRange specific_range) { |
| // A scalar type of size N has a base alignment on N. |
| const unsigned type_size = context.getTypeSizeInChars(QT).getQuantity(); |
| if (offset % type_size != 0) { |
| Report(CustomDiagnosticUnalignedScalar, arg_range, specific_range); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsSupportedVectorLayout(QualType QT, uint64_t offset, |
| const Layout &layout, ASTContext &context, |
| SourceRange arg_range, |
| SourceRange specific_range) { |
| // 2-component vectors have a base alignment of 2 * (size of element). |
| // 3- and 4-component vectors hae a base alignment of 4 * (size of |
| // element). |
| const auto *VT = llvm::cast<VectorType>(QT); |
| const auto ele_size = |
| context.getTypeSizeInChars(VT->getElementType()).getQuantity(); |
| if (VT->getNumElements() == 2) { |
| if (offset % (ele_size * 2) != 0) { |
| Report(CustomDiagnosticUnalignedVec2, arg_range, specific_range); |
| return false; |
| } |
| } else if (offset % (ele_size * 4) != 0) { |
| // Other vector sizes cause errors elsewhere. |
| Report(CustomDiagnosticUnalignedVec4, arg_range, specific_range); |
| return false; |
| } |
| |
| // Straddling rules: |
| // * If total vector size is less than 16 bytes, the offset must place the |
| // entire vector within the same 16 bytes. |
| // * If total vector size is greater than 16 bytes, the offset must be a |
| // multiple of 16. |
| const auto size = context.getTypeSizeInChars(QT).getQuantity(); |
| if (size <= 16 && (offset / 16 != (offset + size - 1) / 16)) { |
| Report(CustomDiagnosticSmallStraddle, arg_range, specific_range); |
| return false; |
| } else if (size > 16 && (offset % 16 != 0)) { |
| Report(CustomDiagnosticLargeStraddle, arg_range, specific_range); |
| return false; |
| } |
| |
| return IsSupportedLayout(VT->getElementType(), offset, layout, context, |
| arg_range, specific_range); |
| } |
| |
| bool IsSupportedArrayLayout(QualType QT, uint64_t offset, |
| const Layout &layout, ASTContext &context, |
| SourceRange arg_range, |
| SourceRange specific_range) { |
| // An array has a base alignment of is element type. |
| // If the layout is UBO, the alignment is rounded up to a multiple of 16. |
| const auto *AT = llvm::cast<ArrayType>(QT); |
| const auto element_align = |
| GetAlignment(AT->getElementType(), layout, context); |
| const auto type_align = |
| layout == UBO ? llvm::alignTo(element_align, 16) : element_align; |
| if (offset % type_align != 0) { |
| auto diag_id = layout == UBO ? CustomDiagnosticUBOUnalignedArray |
| : CustomDiagnosticSSBOUnalignedArray; |
| Report(diag_id, arg_range, specific_range); |
| return false; |
| } |
| if (layout == UBO && !clspv::Option::RelaxedUniformBufferLayout()) { |
| // The ArrayStride must be a multiple of the base alignment of the array |
| // (i.e. a multiple of 16). This means that the element size must be |
| // restricted to be the base alignment of the array. |
| const auto element_size = |
| context.getTypeSizeInChars(AT->getElementType()).getQuantity(); |
| if (element_size % type_align != 0) { |
| Report(CustomDiagnosticUBOArrayStride, arg_range, specific_range); |
| return false; |
| } |
| } |
| |
| return IsSupportedLayout(AT->getElementType(), offset, layout, context, |
| arg_range, specific_range); |
| } |
| |
| bool IsSupportedRecordLayout(QualType QT, uint64_t offset, |
| const Layout &layout, ASTContext &context, |
| SourceRange arg_range, |
| SourceRange specific_range) { |
| // A structure has a base alignment of its largest member. For UBO layouts, |
| // alignment is rounded up to a multiple of 16. |
| const auto *RT = llvm::cast<RecordType>(QT); |
| auto type_alignment = GetAlignment(QT, layout, context); |
| if (layout == UBO) |
| llvm::alignTo(type_alignment, 16); |
| if (offset % type_alignment != 0) { |
| auto diag_id = layout == UBO ? CustomDiagnosticUBOUnalignedStruct |
| : CustomDiagnosticSSBOUnalignedStruct; |
| Report(diag_id, arg_range, specific_range); |
| return false; |
| } |
| |
| const auto &record_layout = context.getASTRecordLayout(RT->getDecl()); |
| const FieldDecl *prev = nullptr; |
| for (auto field_decl : RT->getDecl()->fields()) { |
| const auto field_type = field_decl->getType(); |
| const unsigned field_no = field_decl->getFieldIndex(); |
| const uint64_t field_offset = |
| record_layout.getFieldOffset(field_no) / context.getCharWidth(); |
| |
| // Rules must be checked recursively. |
| if (!IsSupportedLayout(field_type, field_offset + offset, layout, context, |
| arg_range, field_decl->getSourceRange())) { |
| return false; |
| } |
| |
| if (prev) { |
| const auto prev_canonical = prev->getType().getCanonicalType(); |
| const uint64_t prev_offset = |
| record_layout.getFieldOffset(field_no - 1) / context.getCharWidth(); |
| const auto prev_size = |
| context.getTypeSizeInChars(prev_canonical).getQuantity(); |
| const auto prev_alignment = |
| GetAlignment(prev_canonical, layout, context); |
| const auto next_available = |
| prev_offset + llvm::alignTo(prev_size, prev_alignment); |
| if (prev_canonical->isArrayType() || prev_canonical->isRecordType()) { |
| // The next element after an array or struct must be placed on or |
| // after the next multiple of the alignment of that array or |
| // struct. |
| // For UBO layouts, both arrays and structs must be aligned to a |
| // multiple of 16 bytes. |
| const uint64_t final_align = layout == UBO |
| ? llvm::alignTo(next_available, 16) |
| : next_available; |
| if (final_align > field_offset) { |
| Report(CustomDiagnosticUnalignedStructMember, arg_range, |
| field_decl->getSourceRange()); |
| return false; |
| } |
| } |
| } |
| |
| prev = field_decl; |
| } |
| |
| return true; |
| } |
| |
| // This will be used to check the inside of function bodies. |
| class DeclVisitor : public RecursiveASTVisitor<DeclVisitor> { |
| private: |
| ExtraValidationConsumer &consumer; |
| |
| public: |
| explicit DeclVisitor(ExtraValidationConsumer &VC) : consumer(VC) {} |
| |
| // Visits a declaration. Emits a diagnostic and returns false if the |
| // declaration represents an unsupported vector value or vector type. |
| // Otherwise returns true. |
| // |
| // Looking at the Decl class hierarchy, it seems ValueDecl and TypeDecl |
| // are the only two that might represent an unsupported vector type. |
| bool VisitValueDecl(ValueDecl *VD) { |
| return consumer.IsSupportedType(VD->getType(), VD->getSourceRange(), |
| false); |
| } |
| bool VisitValueDecl(TypeDecl *TD) { |
| QualType DefinedType = TD->getASTContext().getTypeDeclType(TD); |
| return consumer.IsSupportedType(DefinedType, TD->getSourceRange(), false); |
| } |
| }; |
| |
| DeclVisitor Visitor; |
| std::unordered_set<std::string> Kernels; |
| |
| public: |
| explicit ExtraValidationConsumer(CompilerInstance &Instance, |
| llvm::StringRef InFile) |
| : Instance(Instance), InFile(InFile), |
| CustomDiagnosticsIDMap(CustomDiagnosticTotal), Visitor(*this) { |
| auto &DE = Instance.getDiagnostics(); |
| |
| CustomDiagnosticsIDMap[CustomDiagnosticVectorsMoreThan4Elements] = |
| DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "vectors with more than 4 elements are not supported"); |
| CustomDiagnosticsIDMap[CustomDiagnosticVoidPointer] = DE.getCustomDiagID( |
| DiagnosticsEngine::Error, "pointer-to-void is not supported"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnalignedScalar] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "scalar elements must be aligned to their size"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnalignedVec2] = DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "two-component vectors must be aligned to 2 times their element size"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnalignedVec4] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "three- and four-component vectors must be aligned " |
| "to 4 times their element size"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedArray] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "in an UBO, arrays must be aligned to their element " |
| "alignment, rounded up to a multiple of 16 bytes"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUBOUnalignedStruct] = |
| DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "in an UBO, structs must be aligned to their " |
| "largest element alignment, rounded up to a multiple of " |
| "16 bytes"); |
| CustomDiagnosticsIDMap[CustomDiagnosticSmallStraddle] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "vectors with a total size less than or equal to 16 " |
| "bytes must be placed entirely within a 16 byte " |
| "aligned region"); |
| CustomDiagnosticsIDMap[CustomDiagnosticLargeStraddle] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "vectors with a total size greater than 16 bytes " |
| "must aligned to 16 bytes"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnalignedStructMember] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "a structure member must not be placed between the " |
| "end of a structure or array and the next multiple " |
| "of the base alignment of that structure or array"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedSize] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "clspv restriction: UBO element size must be a " |
| "multiple of that element's alignment"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUBORestrictedStruct] = |
| DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "clspv restriction: UBO structures may not have implicit padding"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUBOArrayStride] = DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "clspv restriction: to satisfy UBO ArrayStride restrictions, element " |
| "size must be a multiple of array alignment"); |
| CustomDiagnosticsIDMap[CustomDiagnosticLocationInfo] = |
| DE.getCustomDiagID(DiagnosticsEngine::Note, "here"); |
| CustomDiagnosticsIDMap[CustomDiagnosticSSBOUnalignedArray] = |
| DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "in a SSBO, arrays must be aligned to their element alignment"); |
| CustomDiagnosticsIDMap[CustomDiagnosticSSBOUnalignedStruct] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "in a SSBO, structs must be aligned to their " |
| "largest element alignment"); |
| CustomDiagnosticsIDMap[CustomDiagnosticOverloadedKernel] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "kernel functions can't be overloaded"); |
| CustomDiagnosticsIDMap[CustomDiagnosticStructContainsPointer] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "structures may not contain pointers"); |
| CustomDiagnosticsIDMap[CustomDiagnosticRecursiveStruct] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "recursive structures are not supported"); |
| CustomDiagnosticsIDMap[CustomDiagnosticPushConstantSizeExceeded] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "max push constant size exceeded"); |
| CustomDiagnosticsIDMap[CustomDiagnosticPushConstantContainsArray] = |
| DE.getCustomDiagID( |
| DiagnosticsEngine::Error, |
| "arrays are not supported in push constants currently"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnsupported16BitStorage] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "16-bit storage is not supported for " |
| "%select{SSBOs|UBOs|push constants}0"); |
| CustomDiagnosticsIDMap[CustomDiagnosticUnsupported8BitStorage] = |
| DE.getCustomDiagID(DiagnosticsEngine::Error, |
| "8-bit storage is not supported for " |
| "%select{SSBOs|UBOs|push constants}0"); |
| } |
| |
| virtual bool HandleTopLevelDecl(DeclGroupRef DG) override { |
| for (auto *D : DG) { |
| if (auto *FD = llvm::dyn_cast<FunctionDecl>(D)) { |
| // If the function has a body it means we are not an OpenCL builtin |
| // function. |
| if (FD->hasBody()) { |
| if (!IsSupportedType(FD->getReturnType(), |
| FD->getReturnTypeSourceRange(), false)) { |
| return false; |
| } |
| |
| bool is_opencl_kernel = false; |
| if (FD->hasAttrs()) { |
| for (auto *attr : FD->attrs()) { |
| if (attr->getKind() == attr::Kind::OpenCLKernel) { |
| is_opencl_kernel = true; |
| } |
| } |
| } |
| |
| if (is_opencl_kernel) { |
| if (Kernels.count(FD->getName().str()) != 0) { |
| auto srcRange = FD->getSourceRange(); |
| Report(CustomDiagnosticOverloadedKernel, srcRange, srcRange); |
| } else { |
| Kernels.insert(FD->getName().str()); |
| } |
| } |
| |
| RecordDecl *clustered_args = nullptr; |
| if (is_opencl_kernel && clspv::Option::PodArgsInPushConstants()) { |
| clustered_args = FD->getASTContext().buildImplicitRecord( |
| "__clspv.clustered_args." + std::to_string(kClusteredCount++)); |
| clustered_args->startDefinition(); |
| } |
| for (auto *P : FD->parameters()) { |
| auto type = P->getType(); |
| if (!IsSupportedType(P->getOriginalType(), P->getSourceRange(), |
| is_opencl_kernel)) { |
| return false; |
| } |
| |
| if (is_opencl_kernel && type->isPointerType() && |
| ((type->getPointeeType().getAddressSpace() == |
| LangAS::opencl_constant) || |
| (type->getPointeeType().getAddressSpace() == |
| LangAS::opencl_global))) { |
| // The argument will be generated as an array within a block. |
| // Generate an array type to check the validity for the generated |
| // case. |
| Layout layout = SSBO; |
| if (clspv::Option::ConstantArgsInUniformBuffer() && |
| !clspv::Option::Std430UniformBufferLayout() && |
| type->getPointeeType().getAddressSpace() == |
| LangAS::opencl_constant) { |
| layout = UBO; |
| } |
| auto array_type = FD->getASTContext().getIncompleteArrayType( |
| type->getPointeeType(), clang::ArrayType::Normal, 0); |
| if (!IsSupportedLayout(array_type, 0, layout, FD->getASTContext(), |
| P->getSourceRange(), |
| P->getSourceRange())) { |
| return false; |
| } |
| } |
| |
| // Check if storage capabilities are supported. |
| if (is_opencl_kernel) { |
| bool contains_16bit = |
| ContainsSizedType(type.getCanonicalType(), 16); |
| bool contains_8bit = |
| ContainsSizedType(type.getCanonicalType(), 8); |
| auto sc = clspv::Option::StorageClass::kSSBO; |
| if (type->isPointerType()) { |
| sc = ConvertToStorageClass( |
| type->getPointeeType().getAddressSpace()); |
| } else if (clspv::Option::PodArgsInUniformBuffer()) { |
| sc = clspv::Option::StorageClass::kUBO; |
| } else if (clspv::Option::PodArgsInPushConstants()) { |
| sc = clspv::Option::StorageClass::kPushConstant; |
| } |
| |
| if (type->isPointerType() || |
| sc != clspv::Option::StorageClass::kSSBO || |
| !clspv::Option::ClusterPodKernelArgs()) { |
| // For clustered pod args, assume we can fall back on |
| // type-mangling. |
| if (contains_16bit && |
| !clspv::Option::Supports16BitStorageClass(sc)) { |
| Instance.getDiagnostics().Report( |
| P->getSourceRange().getBegin(), |
| CustomDiagnosticsIDMap |
| [CustomDiagnosticUnsupported16BitStorage]) |
| << static_cast<int>(sc); |
| } |
| if (contains_8bit && |
| !clspv::Option::Supports8BitStorageClass(sc)) { |
| Instance.getDiagnostics().Report( |
| P->getSourceRange().getBegin(), |
| CustomDiagnosticsIDMap |
| [CustomDiagnosticUnsupported8BitStorage]) |
| << static_cast<int>(sc); |
| } |
| } |
| } |
| |
| if (is_opencl_kernel && type->isPointerType()) { |
| auto pointee_type = type->getPointeeType().getCanonicalType(); |
| if (ContainsPointerType(pointee_type)) { |
| Instance.getDiagnostics().Report( |
| P->getSourceRange().getBegin(), |
| CustomDiagnosticsIDMap |
| [CustomDiagnosticStructContainsPointer]); |
| return false; |
| } |
| } |
| |
| if (is_opencl_kernel && !type->isPointerType()) { |
| if (clspv::Option::PodArgsInPushConstants()) { |
| // Don't allow arrays in push constants currently. |
| if (ContainsArrayType(type)) { |
| Report(CustomDiagnosticPushConstantContainsArray, |
| P->getSourceRange(), P->getSourceRange()); |
| return false; |
| } |
| FieldDecl *field_decl = FieldDecl::Create( |
| FD->getASTContext(), |
| Decl::castToDeclContext(clustered_args), |
| P->getSourceRange().getBegin(), |
| P->getSourceRange().getEnd(), P->getIdentifier(), |
| P->getType(), nullptr, nullptr, false, ICIS_NoInit); |
| field_decl->setAccess(AS_public); |
| clustered_args->addDecl(field_decl); |
| } else { |
| Layout layout = SSBO; |
| if (clspv::Option::PodArgsInUniformBuffer() && |
| !clspv::Option::Std430UniformBufferLayout()) |
| layout = UBO; |
| |
| if (!IsSupportedLayout(type, 0, layout, FD->getASTContext(), |
| P->getSourceRange(), |
| P->getSourceRange())) { |
| return false; |
| } |
| } |
| } |
| } |
| |
| if (clustered_args) { |
| clustered_args->completeDefinition(); |
| if (!clustered_args->field_empty()) { |
| auto record_type = |
| FD->getASTContext().getRecordType(clustered_args); |
| if (!IsSupportedLayout(record_type, 0, SSBO, FD->getASTContext(), |
| FD->getSourceRange(), |
| FD->getSourceRange())) { |
| return false; |
| } |
| |
| if (FD->getASTContext() |
| .getTypeSizeInChars(record_type) |
| .getQuantity() > clspv::Option::MaxPushConstantsSize()) { |
| Report(CustomDiagnosticPushConstantSizeExceeded, |
| FD->getSourceRange(), FD->getSourceRange()); |
| return false; |
| } |
| } |
| } |
| |
| // Check for unsupported vector types. |
| Visitor.TraverseDecl(FD); |
| } |
| } |
| } |
| |
| return true; |
| } |
| }; |
| } // namespace |
| |
| namespace clspv { |
| std::unique_ptr<ASTConsumer> |
| ExtraValidationASTAction::CreateASTConsumer(CompilerInstance &CI, |
| llvm::StringRef InFile) { |
| return std::unique_ptr<ASTConsumer>(new ExtraValidationConsumer(CI, InFile)); |
| } |
| } // namespace clspv |