Automate pod arg implementation decision (#575)
Contributes to #529
* Decision made on a per-kernel basis
* New pass assigns metadata to kernels
* passes use that metadata through new utliities to get the right
ArgKinds
* currently only decides based on command line options (so NFC)
* Refactored layout validation into new files
* Refactored some global push constant methods
* new enum for how pod args are implemented
* kGlobalPushConstant will be implemented in the future
* added new metadata to test that required it
* changed arg kind utility to take the arg instead of just the type
* Priorities per-kernel push constants then ubo then ssbo
* checks for compatibility
* Added new variant of isValidExplicitLayout that checks the whole
struct
diff --git a/lib/Layout.cpp b/lib/Layout.cpp
new file mode 100644
index 0000000..4d53aa9
--- /dev/null
+++ b/lib/Layout.cpp
@@ -0,0 +1,229 @@
+// Copyright 2020 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 "clspv/Option.h"
+
+#include "Layout.h"
+
+using namespace llvm;
+
+namespace {
+bool isScalarType(Type *type) {
+ return type->isIntegerTy() || type->isFloatTy();
+}
+
+uint64_t structAlignment(StructType *type,
+ std::function<uint64_t(Type *)> alignFn) {
+ uint64_t maxAlign = 1;
+ for (unsigned i = 0; i < type->getStructNumElements(); i++) {
+ uint64_t align = alignFn(type->getStructElementType(i));
+ maxAlign = std::max(align, maxAlign);
+ }
+ return maxAlign;
+}
+
+uint64_t scalarAlignment(Type *type) {
+ // A scalar of size N has a scalar alignment of N.
+ if (isScalarType(type)) {
+ return type->getScalarSizeInBits() / 8;
+ }
+
+ // A vector or matrix type has a scalar alignment equal to that of its
+ // component type.
+ if (auto vec_type = dyn_cast<VectorType>(type)) {
+ return scalarAlignment(vec_type->getElementType());
+ }
+
+ // An array type has a scalar alignment equal to that of its element type.
+ if (type->isArrayTy()) {
+ return scalarAlignment(type->getArrayElementType());
+ }
+
+ // A structure has a scalar alignment equal to the largest scalar alignment of
+ // any of its members.
+ if (type->isStructTy()) {
+ return structAlignment(cast<StructType>(type), scalarAlignment);
+ }
+
+ llvm_unreachable("Unsupported type");
+}
+
+uint64_t baseAlignment(Type *type) {
+ // A scalar has a base alignment equal to its scalar alignment.
+ if (isScalarType(type)) {
+ return scalarAlignment(type);
+ }
+
+ if (auto vec_type = dyn_cast<VectorType>(type)) {
+ unsigned numElems = vec_type->getNumElements();
+
+ // A two-component vector has a base alignment equal to twice its scalar
+ // alignment.
+ if (numElems == 2) {
+ return 2 * scalarAlignment(type);
+ }
+ // A three- or four-component vector has a base alignment equal to four
+ // times its scalar alignment.
+ if ((numElems == 3) || (numElems == 4)) {
+ return 4 * scalarAlignment(type);
+ }
+ }
+
+ // An array has a base alignment equal to the base alignment of its element
+ // type.
+ if (type->isArrayTy()) {
+ return baseAlignment(type->getArrayElementType());
+ }
+
+ // A structure has a base alignment equal to the largest base alignment of any
+ // of its members.
+ if (type->isStructTy()) {
+ return structAlignment(cast<StructType>(type), baseAlignment);
+ }
+
+ // TODO A row-major matrix of C columns has a base alignment equal to the base
+ // alignment of a vector of C matrix components.
+ // TODO A column-major matrix has a base alignment equal to the base alignment
+ // of the matrix column type.
+
+ llvm_unreachable("Unsupported type");
+}
+
+uint64_t extendedAlignment(Type *type) {
+ // A scalar, vector or matrix type has an extended alignment equal to its base
+ // alignment.
+ // TODO matrix type
+ if (isScalarType(type) || type->isVectorTy()) {
+ return baseAlignment(type);
+ }
+
+ // An array or structure type has an extended alignment equal to the largest
+ // extended alignment of any of its members, rounded up to a multiple of 16
+ if (type->isStructTy()) {
+ auto salign = structAlignment(cast<StructType>(type), extendedAlignment);
+ return alignTo(salign, 16);
+ }
+
+ if (type->isArrayTy()) {
+ auto salign = extendedAlignment(type->getArrayElementType());
+ return alignTo(salign, 16);
+ }
+
+ llvm_unreachable("Unsupported type");
+}
+
+uint64_t standardAlignment(Type *type, spv::StorageClass sclass) {
+ // If the scalarBlockLayout feature is enabled on the device then every member
+ // must be aligned according to its scalar alignment
+ if (clspv::Option::ScalarBlockLayout()) {
+ return scalarAlignment(type);
+ }
+
+ // All vectors must be aligned according to their scalar alignment
+ if (type->isVectorTy()) {
+ return scalarAlignment(type);
+ }
+
+ // If the uniformBufferStandardLayout feature is not enabled on the device,
+ // then any member of an OpTypeStruct with a storage class of Uniform and a
+ // decoration of Block must be aligned according to its extended alignment.
+ if (!clspv::Option::Std430UniformBufferLayout() &&
+ sclass == spv::StorageClassUniform) {
+ return extendedAlignment(type);
+ }
+
+ // Every other member must be aligned according to its base alignment
+ return baseAlignment(type);
+}
+
+bool improperlyStraddles(const DataLayout &DL, Type *type, unsigned offset) {
+ assert(type->isVectorTy());
+
+ auto size = DL.getTypeStoreSize(type);
+
+ // It is a vector with total size less than or equal to 16 bytes, and has
+ // Offset decorations placing its first byte at F and its last byte at L,
+ // where floor(F / 16) != floor(L / 16).
+ if ((size <= 16) && (offset % 16 + size > 16)) {
+ return true;
+ }
+
+ // It is a vector with total size greater than 16 bytes and has its Offset
+ // decorations placing its first byte at a non-integer multiple of 16
+ if ((size > 16) && (offset % 16 != 0)) {
+ return true;
+ }
+
+ return false;
+}
+} // namespace
+
+namespace clspv {
+
+// See 14.5 Shader Resource Interface in Vulkan spec
+bool isValidExplicitLayout(Module &M, StructType *STy, unsigned Member,
+ spv::StorageClass SClass, unsigned Offset,
+ unsigned PreviousMemberOffset) {
+
+ auto MemberType = STy->getElementType(Member);
+ auto Align = standardAlignment(MemberType, SClass);
+ auto &DL = M.getDataLayout();
+
+ // The Offset decoration of any member must be a multiple of its alignment
+ if (Offset % Align != 0) {
+ return false;
+ }
+
+ // TODO Any ArrayStride or MatrixStride decoration must be a multiple of the
+ // alignment of the array or matrix as defined above
+
+ if (!clspv::Option::ScalarBlockLayout()) {
+ // Vectors must not improperly straddle, as defined above
+ if (MemberType->isVectorTy() &&
+ improperlyStraddles(DL, MemberType, Offset)) {
+ return true;
+ }
+
+ // The Offset decoration of a member must not place it between the end
+ // of a structure or an array and the next multiple of the alignment of that
+ // structure or array
+ if (Member > 0) {
+ auto PType = STy->getElementType(Member - 1);
+ if (PType->isStructTy() || PType->isArrayTy()) {
+ auto PAlign = standardAlignment(PType, SClass);
+ if (Offset - PreviousMemberOffset < PAlign) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool isValidExplicitLayout(llvm::Module &M, llvm::StructType *STy,
+ spv::StorageClass SClass) {
+ auto const &DL = M.getDataLayout();
+ const auto StructLayout = DL.getStructLayout(STy);
+ bool ok = true;
+ auto previous_offset = 0;
+ for (unsigned i = 0; ok && i < STy->getNumElements(); i++) {
+ auto offset = StructLayout->getElementOffset(i);
+ ok &= isValidExplicitLayout(M, STy, i, SClass, offset, previous_offset);
+ previous_offset = offset;
+ }
+
+ return ok;
+}
+} // namespace clspv