| // Copyright (c) 2026 Google Inc. | |
| // | |
| // 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 <algorithm> | |
| #include <cassert> | |
| #include <ostream> | |
| #include <sstream> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "source/spirv_constant.h" | |
| #include "source/spirv_target_env.h" | |
| #include "source/util/hash_combine.h" | |
| #include "source/val/validation_state.h" | |
| namespace spvtools { | |
| namespace val { | |
| namespace { | |
| enum class LayoutMode : uint8_t { | |
| // Vulkan scalar block rules | |
| kScalar, | |
| // Vulkan standard alignment rules (i.e. std430) | |
| kStandard, | |
| // Vulkan extended alignment rules (i.e. std140) | |
| kExtended, | |
| }; | |
| std::ostream& operator<<(std::ostream& str, const LayoutMode& mode) { | |
| switch (mode) { | |
| case LayoutMode::kScalar: | |
| str << "scalar"; | |
| break; | |
| case LayoutMode::kStandard: | |
| str << "standard"; | |
| break; | |
| case LayoutMode::kExtended: | |
| str << "extended"; | |
| break; | |
| } | |
| return str; | |
| } | |
| enum class LayoutRequirement : uint8_t { | |
| // Must be laid out | |
| kRequired, | |
| // Must not be laid out | |
| kProhibited, | |
| // Either laid or not | |
| kAllowed, | |
| }; | |
| struct Impl { | |
| ValidationState_t& vstate; | |
| // Relevant information to describe a memory instruction for the purposes of | |
| // layout validation. | |
| struct MemoryReference { | |
| // The data type of the memory instruction (after stripping any descriptor | |
| // array). | |
| uint32_t type_id = 0; | |
| // The descriptor array type id (if there is one). | |
| uint32_t descriptor_array_id = 0; | |
| // The storage class for the memory instruction. | |
| // Only used for error messages | |
| spv::StorageClass storage_class; | |
| // The layout mode (only relevant if a layout is required). | |
| LayoutMode layout; | |
| // The layout requirement for the instruction. | |
| LayoutRequirement requirement; | |
| // Whether it is an untyped pointer base. | |
| bool untyped = false; | |
| }; | |
| // Matrix constraints from a struct member to carry to the actual matrix type. | |
| struct MatrixConstraints { | |
| uint32_t stride = 0; | |
| bool col_major = true; | |
| }; | |
| // Cache valid checks for types that should have no layout. | |
| std::unordered_set<uint32_t> no_layout_cache_; | |
| // Struct member info | |
| struct MemberInfo { | |
| // Structure member index (note: index + 1 is instruction index). | |
| uint32_t index; | |
| // Whether or not an Offset/OffsetIdEXT decoration is present. | |
| bool has_offset = false; | |
| // Offset value. Max uint32_t is used for no evaluation (e.g. spec | |
| // constant). | |
| uint32_t offset = std::numeric_limits<uint32_t>::max(); | |
| // Whether or not RowMajor or ColMajor decoration is present. | |
| bool has_matrix = false; | |
| // Matrix constraints (stride and majorness). | |
| MatrixConstraints matrix_constraints; | |
| }; | |
| // Cache of structure member information. | |
| std::unordered_map<uint32_t, std::vector<MemberInfo>> struct_members_; | |
| struct LayoutKey { | |
| uint32_t type_id = 0; | |
| LayoutMode layout; | |
| uint32_t incoming_offset = 0; | |
| MatrixConstraints matrix_constraints{}; | |
| bool operator==(const LayoutKey& other) const { | |
| return type_id == other.type_id && layout == other.layout && | |
| incoming_offset == other.incoming_offset && | |
| matrix_constraints.stride == other.matrix_constraints.stride && | |
| matrix_constraints.col_major == other.matrix_constraints.col_major; | |
| } | |
| }; | |
| struct LayoutKeyHash { | |
| size_t operator()(const LayoutKey& key) const noexcept { | |
| return spvtools::utils::hash_combine( | |
| 0, key.type_id, static_cast<uint32_t>(key.layout), | |
| key.incoming_offset, key.matrix_constraints.stride, | |
| key.matrix_constraints.col_major); | |
| } | |
| }; | |
| // Caches valid results of CheckLayout calls. | |
| std::unordered_set<LayoutKey, LayoutKeyHash> layout_cache_; | |
| // Returns the layout requirements for `sc`. | |
| // Workgroup is expected to be explicitly laid out if `is_block` is true. | |
| // UniformConstant is expected to be explicitly laid out if `descriptor_heap` | |
| // is true. | |
| LayoutRequirement GetStorageClassRequirement(spv::StorageClass sc, | |
| bool is_block, | |
| bool descriptor_heap) { | |
| switch (sc) { | |
| case spv::StorageClass::Workgroup: | |
| return is_block ? LayoutRequirement::kRequired | |
| : LayoutRequirement::kProhibited; | |
| case spv::StorageClass::StorageBuffer: | |
| case spv::StorageClass::Uniform: | |
| case spv::StorageClass::PushConstant: | |
| case spv::StorageClass::PhysicalStorageBuffer: | |
| return LayoutRequirement::kRequired; | |
| case spv::StorageClass::UniformConstant: | |
| return descriptor_heap ? LayoutRequirement::kRequired | |
| : LayoutRequirement::kProhibited; | |
| case spv::StorageClass::Function: | |
| case spv::StorageClass::Private: | |
| return vstate.version() <= SPV_SPIRV_VERSION_WORD(1, 4) | |
| ? LayoutRequirement::kAllowed | |
| : LayoutRequirement::kProhibited; | |
| case spv::StorageClass::Input: | |
| case spv::StorageClass::Output: | |
| // Block is used generally and mesh shaders use Offset. | |
| // TODO: This is a little over permissive. | |
| return LayoutRequirement::kAllowed; | |
| default: | |
| // TODO: Some storage classes in ray tracing use explicit layout | |
| // decorations, but it is not well documented which. For now treat | |
| // other storage classes as allowed to be laid out. See Vulkan | |
| // internal issue 4192. | |
| return LayoutRequirement::kAllowed; | |
| } | |
| } | |
| // Returns the layout rules for `sc`. | |
| LayoutMode GetStorageClassLayout(spv::StorageClass sc, bool is_buffer_block) { | |
| switch (sc) { | |
| case spv::StorageClass::Workgroup: | |
| return vstate.options()->workgroup_scalar_block_layout | |
| ? LayoutMode::kScalar | |
| : LayoutMode::kStandard; | |
| break; | |
| case spv::StorageClass::StorageBuffer: | |
| case spv::StorageClass::PushConstant: | |
| case spv::StorageClass::UniformConstant: | |
| case spv::StorageClass::PhysicalStorageBuffer: | |
| return vstate.options()->scalar_block_layout ? LayoutMode::kScalar | |
| : LayoutMode::kStandard; | |
| break; | |
| case spv::StorageClass::Uniform: | |
| return vstate.options()->scalar_block_layout | |
| ? LayoutMode::kScalar | |
| : ((is_buffer_block || | |
| vstate.options()->uniform_buffer_standard_layout) | |
| ? LayoutMode::kStandard | |
| : LayoutMode::kExtended); | |
| break; | |
| default: | |
| break; | |
| } | |
| return LayoutMode::kStandard; | |
| } | |
| // Returns true if `inst` is a memory reference instruction | |
| // Populates `reference` with the necessary information. | |
| // | |
| // The following are interesting memory references: | |
| // For typed pointers: | |
| // * OpVariable | |
| // * Memory instructions on PhysicalStorageBuffer | |
| // * OpBufferPointerEXT | |
| // For untyped pointers: | |
| // * All memory instructions | |
| bool GetMemoryReference(const Instruction* inst, MemoryReference* reference) { | |
| auto* type_inst = vstate.FindDef(inst->type_id()); | |
| if (inst->opcode() == spv::Op::OpVariable) { | |
| auto sc = type_inst->GetOperandAs<spv::StorageClass>(1u); | |
| const bool is_descriptor_heap = vstate.IsDescriptorHeapBaseVariable(inst); | |
| reference->storage_class = sc; | |
| reference->type_id = type_inst->GetOperandAs<uint32_t>(2u); | |
| const bool is_block_decorated = | |
| vstate.GetIdOpcode(reference->type_id) == spv::Op::OpTypeStruct && | |
| vstate.HasDecoration(reference->type_id, spv::Decoration::Block); | |
| reference->requirement = GetStorageClassRequirement( | |
| sc, is_block_decorated, is_descriptor_heap); | |
| // Unwrap the descriptor array. | |
| if (sc == spv::StorageClass::StorageBuffer || | |
| sc == spv::StorageClass::Uniform || | |
| sc == spv::StorageClass::UniformConstant) { | |
| const auto* data_type = vstate.FindDef(reference->type_id); | |
| if (data_type->opcode() == spv::Op::OpTypeArray || | |
| data_type->opcode() == spv::Op::OpTypeRuntimeArray) { | |
| reference->descriptor_array_id = reference->type_id; | |
| reference->type_id = data_type->GetOperandAs<uint32_t>(1u); | |
| } | |
| } | |
| const bool buffer_block = | |
| vstate.GetIdOpcode(reference->type_id) == spv::Op::OpTypeStruct && | |
| vstate.HasDecoration(reference->type_id, | |
| spv::Decoration::BufferBlock); | |
| reference->layout = GetStorageClassLayout(sc, buffer_block); | |
| return true; | |
| } else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer && | |
| type_inst->GetOperandAs<spv::StorageClass>(1u) == | |
| spv::StorageClass::PhysicalStorageBuffer) { | |
| reference->storage_class = spv::StorageClass::PhysicalStorageBuffer; | |
| reference->type_id = type_inst->GetOperandAs<uint32_t>(2u); | |
| reference->layout = GetStorageClassLayout( | |
| spv::StorageClass::PhysicalStorageBuffer, false); | |
| reference->requirement = LayoutRequirement::kRequired; | |
| return true; | |
| } else if (inst->opcode() == spv::Op::OpBufferPointerEXT && | |
| type_inst->opcode() == spv::Op::OpTypePointer) { | |
| auto sc = type_inst->GetOperandAs<spv::StorageClass>(1u); | |
| reference->storage_class = sc; | |
| uint32_t pointee_ty_id = type_inst->GetOperandAs<uint32_t>(2u); | |
| const bool buffer_block = | |
| vstate.GetIdOpcode(pointee_ty_id) == spv::Op::OpTypeStruct && | |
| vstate.HasDecoration(pointee_ty_id, spv::Decoration::BufferBlock); | |
| reference->layout = GetStorageClassLayout(sc, buffer_block); | |
| reference->requirement = LayoutRequirement::kRequired; | |
| reference->type_id = pointee_ty_id; | |
| return true; | |
| } else if (vstate.HasCapability(spv::Capability::UntypedPointersKHR) && | |
| spvIsVulkanEnv(vstate.context()->target_env)) { | |
| uint32_t ptr_ty_id = 0; | |
| uint32_t data_ty_id = 0; | |
| switch (inst->opcode()) { | |
| case spv::Op::OpUntypedVariableKHR: | |
| if (inst->operands().size() > 3) { | |
| ptr_ty_id = inst->type_id(); | |
| data_ty_id = inst->GetOperandAs<uint32_t>(3u); | |
| } else { | |
| return false; | |
| } | |
| break; | |
| case spv::Op::OpUntypedAccessChainKHR: | |
| case spv::Op::OpUntypedInBoundsAccessChainKHR: | |
| case spv::Op::OpUntypedPtrAccessChainKHR: | |
| case spv::Op::OpUntypedInBoundsPtrAccessChainKHR: | |
| ptr_ty_id = inst->type_id(); | |
| data_ty_id = inst->GetOperandAs<uint32_t>(2); | |
| break; | |
| case spv::Op::OpLoad: | |
| if (vstate.GetIdOpcode(vstate.GetOperandTypeId(inst, 2)) == | |
| spv::Op::OpTypeUntypedPointerKHR) { | |
| const auto ptr_id = inst->GetOperandAs<uint32_t>(2); | |
| ptr_ty_id = vstate.FindDef(ptr_id)->type_id(); | |
| data_ty_id = inst->type_id(); | |
| } else { | |
| return false; | |
| } | |
| break; | |
| case spv::Op::OpStore: | |
| if (vstate.GetIdOpcode(vstate.GetOperandTypeId(inst, 0)) == | |
| spv::Op::OpTypeUntypedPointerKHR) { | |
| const auto ptr_id = inst->GetOperandAs<uint32_t>(0); | |
| ptr_ty_id = vstate.FindDef(ptr_id)->type_id(); | |
| data_ty_id = vstate.GetOperandTypeId(inst, 1); | |
| } else { | |
| return false; | |
| } | |
| break; | |
| case spv::Op::OpUntypedArrayLengthKHR: | |
| ptr_ty_id = | |
| vstate.FindDef(inst->GetOperandAs<uint32_t>(3))->type_id(); | |
| data_ty_id = inst->GetOperandAs<uint32_t>(2); | |
| break; | |
| default: | |
| return false; | |
| } | |
| // If the data type is an array that contains a Block- or | |
| // BufferBlock-decorated struct, then use the struct for layout checks | |
| // instead of the array. In this case, the array represents a descriptor | |
| // array which should not have an explicit layout. | |
| const auto* data_type = vstate.FindDef(data_ty_id); | |
| if (data_type->opcode() == spv::Op::OpTypeArray || | |
| data_type->opcode() == spv::Op::OpTypeRuntimeArray) { | |
| uint32_t ele_ty_id = data_type->GetOperandAs<uint32_t>(1u); | |
| if (vstate.HasDecoration(ele_ty_id, spv::Decoration::Block) || | |
| vstate.HasDecoration(ele_ty_id, spv::Decoration::BufferBlock)) { | |
| reference->descriptor_array_id = data_ty_id; | |
| data_ty_id = ele_ty_id; | |
| } | |
| } | |
| auto sc = vstate.FindDef(ptr_ty_id)->GetOperandAs<spv::StorageClass>(1u); | |
| reference->storage_class = sc; | |
| reference->type_id = data_ty_id; | |
| reference->requirement = LayoutRequirement::kRequired; | |
| reference->untyped = true; | |
| const bool buffer_block = | |
| vstate.GetIdOpcode(data_ty_id) == spv::Op::OpTypeStruct && | |
| vstate.HasDecoration(data_ty_id, spv::Decoration::BufferBlock); | |
| reference->layout = GetStorageClassLayout(sc, buffer_block); | |
| return true; | |
| } else if (inst->opcode() == spv::Op::OpAbortKHR) { | |
| reference->type_id = inst->GetOperandAs<uint32_t>(0u); | |
| // Abort messages doesn't have a storage class | |
| reference->storage_class = spv::StorageClass::Max; | |
| // The abort message is always packed with scalar rules | |
| // See https://gitlab.khronos.org/vulkan/vulkan/-/work_items/4954 | |
| reference->layout = LayoutMode::kScalar; | |
| reference->requirement = LayoutRequirement::kRequired; | |
| return true; | |
| } | |
| // Not a memory reference. | |
| return false; | |
| } | |
| // Checks that no instruction in the type tree of `type_id` has any explicit | |
| // layout decorations. | |
| spv_result_t CheckNoLayout(const Instruction* inst, uint32_t type_id, | |
| spv::StorageClass sc) { | |
| if (no_layout_cache_.count(type_id)) { | |
| return SPV_SUCCESS; | |
| } | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| if (type_inst->opcode() == spv::Op::OpTypePointer) { | |
| // PhysicalStorageBuffer and variable pointers can have ArrayStride | |
| // decorations even if they are stored in a non-laid storage class (e.g. | |
| // Function). | |
| auto ptr_sc = type_inst->GetOperandAs<spv::StorageClass>(1u); | |
| if (GetStorageClassRequirement(ptr_sc, true, false) != | |
| LayoutRequirement::kProhibited) { | |
| return SPV_SUCCESS; | |
| } | |
| } | |
| const auto& id_decs = vstate.id_decorations(); | |
| const auto iter = id_decs.find(type_id); | |
| if (iter != id_decs.end()) { | |
| for (const auto& d : iter->second) { | |
| const spv::Decoration dec = d.dec_type(); | |
| if (dec == spv::Decoration::Block || | |
| dec == spv::Decoration::BufferBlock || | |
| dec == spv::Decoration::Offset || | |
| dec == spv::Decoration::OffsetIdEXT || | |
| dec == spv::Decoration::ArrayStride || | |
| dec == spv::Decoration::ArrayStrideIdEXT || | |
| dec == spv::Decoration::MatrixStride || | |
| dec == spv::Decoration::RowMajor || | |
| dec == spv::Decoration::ColMajor) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, inst) | |
| << vstate.VkErrorID(10684) | |
| << "Invalid explicit layout decorations on type " | |
| << vstate.getIdName(type_id) << ", the " | |
| << spvtools::StorageClassToString(sc) | |
| << " storage class has an explicit layout from the " | |
| << vstate.SpvDecorationString(dec) << " decoration"; | |
| } | |
| } | |
| } | |
| switch (type_inst->opcode()) { | |
| case spv::Op::OpTypeStruct: | |
| for (uint32_t i = 1; i < type_inst->operands().size(); i++) { | |
| if (auto error = CheckNoLayout( | |
| inst, type_inst->GetOperandAs<uint32_t>(i), sc)) { | |
| return error; | |
| } | |
| } | |
| break; | |
| case spv::Op::OpTypeRuntimeArray: | |
| case spv::Op::OpTypeArray: | |
| if (auto error = CheckNoLayout( | |
| inst, type_inst->GetOperandAs<uint32_t>(1u), sc)) { | |
| return error; | |
| } | |
| break; | |
| case spv::Op::OpTypePointer: { | |
| auto ptr_sc = type_inst->GetOperandAs<spv::StorageClass>(1u); | |
| if (auto error = CheckNoLayout( | |
| inst, type_inst->GetOperandAs<uint32_t>(2u), ptr_sc)) { | |
| return error; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| no_layout_cache_.insert(type_id); | |
| return SPV_SUCCESS; | |
| } | |
| // Returns true if type_id contains a matrix. Only looks through arrays. | |
| bool ContainsMatrix(uint32_t type_id) { | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| switch (type_inst->opcode()) { | |
| case spv::Op::OpTypeMatrix: | |
| return true; | |
| case spv::Op::OpTypeArray: | |
| case spv::Op::OpTypeRuntimeArray: | |
| return ContainsMatrix(type_inst->GetOperandAs<uint32_t>(1u)); | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| // Gets the value for an id-based layout decoration (e.g. ArrayStrideIdEXT and | |
| // OffsetIdEXT). Returns max uint32_t if the value cannot be evaluated (e.g. | |
| // spec constant). | |
| uint32_t GetIdDecorationValue(uint32_t id) { | |
| const auto* inst = vstate.FindDef(id); | |
| if (!spvOpcodeIsConstant(inst->opcode())) { | |
| return std::numeric_limits<uint32_t>::max(); | |
| } | |
| uint64_t value = 0; | |
| if (vstate.EvalConstantValUint64(id, &value)) { | |
| return static_cast<uint32_t>(value); | |
| } | |
| return std::numeric_limits<uint32_t>::max(); | |
| } | |
| // Returns whether an array stride decoration is present on the array and its | |
| // value. | |
| std::pair<bool, uint32_t> GetArrayStride(uint32_t array_id) { | |
| uint32_t stride = std::numeric_limits<uint32_t>::max(); | |
| bool has_stride = false; | |
| for (auto& d : vstate.id_decorations(array_id)) { | |
| if (d.dec_type() == spv::Decoration::ArrayStride) { | |
| stride = d.params()[0]; | |
| has_stride = true; | |
| break; | |
| } else if (d.dec_type() == spv::Decoration::ArrayStrideIdEXT) { | |
| stride = GetIdDecorationValue(d.params()[0]); | |
| has_stride = true; | |
| break; | |
| } | |
| } | |
| return std::make_pair(has_stride, stride); | |
| } | |
| // Gets the offset value from an offset decoration. | |
| uint32_t GetOffset(spv::Decoration dec, uint32_t param) { | |
| // param is a literal value | |
| if (dec == spv::Decoration::Offset) { | |
| return param; | |
| } | |
| // param is an id | |
| return GetIdDecorationValue(param); | |
| } | |
| // Returns true if value is aligned to align. | |
| bool IsAlignedTo(uint32_t value, uint32_t align) { | |
| if (align == 0) return value == 0; | |
| return (value % align) == 0; | |
| } | |
| // Rounds up value to next multiple of align. | |
| uint32_t AlignTo(uint32_t value, uint32_t align) { | |
| return (value + align - 1) & ~(align - 1); | |
| } | |
| // A member is defined to improperly straddle if either of the following are | |
| // true: | |
| // - 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). | |
| // - 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. | |
| bool HasImproperStraddle(uint32_t offset, uint32_t size) { | |
| const auto F = offset; | |
| const auto L = offset + size - 1; | |
| if (size <= 16) { | |
| if ((F >> 4) != (L >> 4)) return true; | |
| } else { | |
| if (F % 16 != 0) return true; | |
| } | |
| return false; | |
| } | |
| // Used to map the option alignment to the Vulkan VUID | |
| uint32_t GetAlignVkErrorId(spv::Op opcode) { | |
| if (opcode == spv::Op::OpTypeSampler) { | |
| return 11476; | |
| } else if (opcode == spv::Op::OpTypeSampledImage || | |
| opcode == spv::Op::OpTypeImage) { | |
| return 11477; | |
| } else if (opcode == spv::Op::OpTypeBufferEXT) { | |
| return 11478; | |
| } else if (opcode == spv::Op::OpTypeAccelerationStructureKHR) { | |
| return 11479; | |
| } else if (opcode == spv::Op::OpTypeTensorARM) { | |
| return 11480; | |
| } | |
| return 0; | |
| } | |
| // Returns the alignment for type_id for the given layout rules. | |
| uint32_t GetAlign(uint32_t type_id, LayoutMode mode, | |
| const MatrixConstraints& matrix_constraints, | |
| bool allow_relaxed = true) { | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| uint32_t align = 1; | |
| switch (type_inst->opcode()) { | |
| case spv::Op::OpTypeSampledImage: | |
| case spv::Op::OpTypeSampler: | |
| case spv::Op::OpTypeImage: | |
| if (vstate.HasCapability(spv::Capability::BindlessTextureNV)) { | |
| return vstate.samplerimage_variable_address_mode() / 8; | |
| } | |
| if (type_inst->opcode() == spv::Op::OpTypeSampler) { | |
| return vstate.options()->sampler_descriptor_layout.alignment; | |
| } | |
| if (type_inst->opcode() == spv::Op::OpTypeImage) { | |
| return vstate.options()->image_descriptor_layout.alignment; | |
| } | |
| break; | |
| case spv::Op::OpTypeBufferEXT: | |
| case spv::Op::OpTypeAccelerationStructureKHR: | |
| return vstate.options()->buffer_descriptor_layout.alignment; | |
| case spv::Op::OpTypeInt: | |
| case spv::Op::OpTypeFloat: | |
| return type_inst->GetOperandAs<uint32_t>(1u) / 8; | |
| case spv::Op::OpTypeVector: | |
| case spv::Op::OpTypeVectorIdEXT: { | |
| const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u); | |
| const auto num_eles = vstate.GetDimension(type_id); | |
| align = GetAlign(ele_id, mode, {}); | |
| // Relaxed layout only applies to vectors as direct members of structs. | |
| // Arrays and matrices are not relaxed. | |
| if (mode == LayoutMode::kScalar || | |
| (allow_relaxed && vstate.IsRelaxedBlockLayout())) { | |
| return align; | |
| } | |
| return align * ((num_eles == 3 || num_eles > 4) ? 4 : num_eles); | |
| } | |
| case spv::Op::OpTypeMatrix: | |
| if (mode == LayoutMode::kScalar) { | |
| const auto* vec_inst = | |
| vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u)); | |
| const auto ele_id = vec_inst->GetOperandAs<uint32_t>(1u); | |
| return GetAlign(ele_id, mode, {}); | |
| } | |
| if (matrix_constraints.col_major) { | |
| align = GetAlign(type_inst->GetOperandAs<uint32_t>(1u), mode, {}, | |
| /* allow_relaxed = */ false); | |
| } else { | |
| // A row-major matrix of C columns has a base alignment equal to the | |
| // base alignment of a vector of C matrix components. | |
| const auto num_cols = type_inst->GetOperandAs<uint32_t>(2u); | |
| const auto* col_inst = | |
| vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u)); | |
| const auto ele_id = col_inst->GetOperandAs<uint32_t>(1u); | |
| align = GetAlign(ele_id, mode, {}); | |
| // The equivalent vector may not exist so we replicate the vector rule | |
| // here. | |
| if (mode != LayoutMode::kScalar) { | |
| align = align * (num_cols == 3 ? 4 : num_cols); | |
| } | |
| } | |
| if (mode == LayoutMode::kExtended) { | |
| align = AlignTo(align, 16u); | |
| } | |
| return align; | |
| case spv::Op::OpTypeArray: | |
| case spv::Op::OpTypeRuntimeArray: | |
| align = GetAlign(type_inst->GetOperandAs<uint32_t>(1u), mode, | |
| matrix_constraints, /* allow_relaxed = */ false); | |
| if (mode == LayoutMode::kExtended) { | |
| align = AlignTo(align, 16u); | |
| } | |
| return align; | |
| case spv::Op::OpTypeStruct: | |
| for (uint32_t i = 1; i < type_inst->operands().size(); i++) { | |
| const auto member_id = type_inst->GetOperandAs<uint32_t>(i); | |
| MatrixConstraints mat_constraints; | |
| GetMatrixConstraints(type_id, i - 1, &mat_constraints); | |
| align = std::max(align, GetAlign(member_id, mode, mat_constraints)); | |
| if (mode == LayoutMode::kExtended) { | |
| align = AlignTo(align, 16u); | |
| } | |
| } | |
| return align; | |
| case spv::Op::OpTypePointer: | |
| case spv::Op::OpTypeUntypedPointerKHR: | |
| return vstate.pointer_size_and_alignment(); | |
| default: | |
| break; | |
| } | |
| assert(0 && "unhandled type"); | |
| return 1; | |
| } | |
| // Returns the size of the given type. | |
| uint32_t GetSize(uint32_t type_id, | |
| const MatrixConstraints& matrix_constraints) { | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| switch (type_inst->opcode()) { | |
| case spv::Op::OpTypeSampledImage: | |
| case spv::Op::OpTypeSampler: | |
| case spv::Op::OpTypeImage: | |
| if (vstate.HasCapability(spv::Capability::BindlessTextureNV)) { | |
| return vstate.samplerimage_variable_address_mode() / 8; | |
| } | |
| if (type_inst->opcode() == spv::Op::OpTypeSampler) { | |
| return vstate.options()->sampler_descriptor_layout.size; | |
| } | |
| if (type_inst->opcode() == spv::Op::OpTypeImage) { | |
| return vstate.options()->image_descriptor_layout.size; | |
| } | |
| break; | |
| case spv::Op::OpTypeBufferEXT: | |
| case spv::Op::OpTypeAccelerationStructureKHR: | |
| return vstate.options()->buffer_descriptor_layout.size; | |
| case spv::Op::OpTypeInt: | |
| case spv::Op::OpTypeFloat: | |
| return type_inst->GetOperandAs<uint32_t>(1u) / 8; | |
| case spv::Op::OpTypeVector: | |
| case spv::Op::OpTypeVectorIdEXT: { | |
| const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u); | |
| const auto num_eles = vstate.GetDimension(type_id); | |
| return GetSize(ele_id, {}) * num_eles; | |
| } | |
| case spv::Op::OpTypeArray: { | |
| const auto count_id = type_inst->GetOperandAs<uint32_t>(2u); | |
| uint64_t count = 0; | |
| if (!vstate.EvalConstantValUint64(count_id, &count)) { | |
| return 0; | |
| } | |
| const auto [has_stride, stride] = GetArrayStride(type_id); | |
| const auto ele_size = | |
| GetSize(type_inst->GetOperandAs<uint32_t>(1u), matrix_constraints); | |
| // uint32 max is a marker for unevaluatable. | |
| if (stride == std::numeric_limits<uint32_t>::max()) { | |
| return ele_size; | |
| } | |
| return (static_cast<uint32_t>(count) - 1) * stride + ele_size; | |
| } | |
| case spv::Op::OpTypeRuntimeArray: | |
| return 0; | |
| case spv::Op::OpTypeMatrix: { | |
| const auto num_cols = type_inst->GetOperandAs<uint32_t>(2u); | |
| if (matrix_constraints.col_major) { | |
| return num_cols * matrix_constraints.stride; | |
| } else { | |
| const auto* col_inst = | |
| vstate.FindDef(type_inst->GetOperandAs<uint32_t>(1u)); | |
| const auto ele_id = col_inst->GetOperandAs<uint32_t>(1u); | |
| const auto num_rows = col_inst->GetOperandAs<uint32_t>(2u); | |
| return (num_rows - 1) * matrix_constraints.stride + | |
| num_cols * GetSize(ele_id, {}); | |
| } | |
| } | |
| case spv::Op::OpTypeStruct: { | |
| const auto& members = GetStructMembers(type_id); | |
| if (members.empty()) return 0; | |
| const auto& last = members.back(); | |
| if (last.offset == std::numeric_limits<uint32_t>::max()) { | |
| return 0; | |
| } | |
| return last.offset + | |
| GetSize(type_inst->GetOperandAs<uint32_t>(last.index + 1), | |
| last.matrix_constraints); | |
| } | |
| case spv::Op::OpTypePointer: | |
| case spv::Op::OpTypeUntypedPointerKHR: | |
| return vstate.pointer_size_and_alignment(); | |
| default: | |
| break; | |
| } | |
| assert(0 && "unhandled type"); | |
| return 0; | |
| } | |
| // Returns true if type_id has a matrix and populates matrix_constraints. | |
| bool GetMatrixConstraints(uint32_t type_id, uint32_t index, | |
| MatrixConstraints* matrix_constraints) { | |
| bool has_matrix = false; | |
| auto member_decorations = vstate.id_member_decorations(type_id, index); | |
| for (auto decoration = member_decorations.begin; | |
| decoration != member_decorations.end; ++decoration) { | |
| if (decoration->dec_type() == spv::Decoration::ColMajor || | |
| decoration->dec_type() == spv::Decoration::RowMajor) { | |
| has_matrix = true; | |
| matrix_constraints->col_major = | |
| decoration->dec_type() == spv::Decoration::ColMajor; | |
| } | |
| if (decoration->dec_type() == spv::Decoration::MatrixStride) { | |
| matrix_constraints->stride = decoration->params()[0]; | |
| } | |
| } | |
| return has_matrix; | |
| } | |
| // Gathers (and caches) structure members and their decorations. | |
| const std::vector<MemberInfo>& GetStructMembers(uint32_t type_id) { | |
| if (struct_members_.count(type_id)) { | |
| return struct_members_[type_id]; | |
| } | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| std::vector<MemberInfo> member_info; | |
| member_info.reserve(type_inst->operands().size() - 1); | |
| for (uint32_t i = 1; i < type_inst->operands().size(); i++) { | |
| auto member_idx = i - 1; | |
| auto member_decorations = | |
| vstate.id_member_decorations(type_id, member_idx); | |
| member_info.push_back(MemberInfo{member_idx}); | |
| auto& member = member_info.back(); | |
| member.has_matrix = | |
| GetMatrixConstraints(type_id, member_idx, &member.matrix_constraints); | |
| for (auto decoration = member_decorations.begin; | |
| decoration != member_decorations.end; ++decoration) { | |
| switch (decoration->dec_type()) { | |
| case spv::Decoration::Offset: | |
| case spv::Decoration::OffsetIdEXT: | |
| member.has_offset = true; | |
| member.offset = | |
| GetOffset(decoration->dec_type(), decoration->params()[0]); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| // Sort by offset value. | |
| std::stable_sort(member_info.begin(), member_info.end(), | |
| [](const MemberInfo& lhs, const MemberInfo& rhs) { | |
| return lhs.offset < rhs.offset; | |
| }); | |
| struct_members_[type_id] = std::move(member_info); | |
| return struct_members_[type_id]; | |
| } | |
| // Returns some common messaging to improve diagnostics. | |
| std::string CommonError(const Instruction* inst, | |
| spv::StorageClass storage_class, LayoutMode mode) { | |
| std::string s; | |
| std::ostringstream str(s); | |
| str << " Instantiated via " << vstate.getIdName(inst->id()); | |
| // OpAbortKHR is an example where there is not storage class | |
| if (storage_class != spv::StorageClass::Max) { | |
| str << " in the " << spvtools::StorageClassToString(storage_class) | |
| << " storage class"; | |
| } | |
| str << " using " << mode << " layout rules."; | |
| if (mode != LayoutMode::kScalar) { | |
| if (storage_class == spv::StorageClass::Workgroup) { | |
| str << vstate.MissingFeature( | |
| "workgroupMemoryExplicitLayoutScalarBlockLayout feature", | |
| "--workgroup-scalar-block-layout", true); | |
| } else if (!vstate.IsRelaxedBlockLayout()) { | |
| str << vstate.MissingFeature("VK_KHR_relaxed_block_layout extension", | |
| "--relax-block-layout", true); | |
| } else if (storage_class == spv::StorageClass::Uniform && | |
| mode != LayoutMode::kStandard) { | |
| str << vstate.MissingFeature("uniformBufferStandardLayout feature", | |
| "--uniform-buffer-standard-layout", true); | |
| } else { | |
| str << vstate.MissingFeature("scalarBlockLayout feature", | |
| "--scalar-block-layout", true); | |
| } | |
| } | |
| return str.str(); | |
| } | |
| // Checks struct layouts | |
| // * Each member must | |
| // * Have an offset decoration | |
| // * If the member is a matrix or array(s) of matrices | |
| // * Must have majorness and matrix stride decorations | |
| // * If member is a runtime array, it must be last by offset | |
| // * Offset must be aligned | |
| // * Total offset must be aligned | |
| // * Offset + size < next elements offset | |
| // * If member is a vector, it must have a valid straddle | |
| // * The member has a valid layout | |
| spv_result_t CheckStructLayout(const Instruction* inst, uint32_t type_id, | |
| spv::StorageClass storage_class, | |
| LayoutMode mode, uint32_t incoming_offset, | |
| const MatrixConstraints& matrix_constraints) { | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| const auto& member_info = GetStructMembers(type_id); | |
| for (auto& member : member_info) { | |
| const auto member_id = | |
| type_inst->GetOperandAs<uint32_t>(member.index + 1); | |
| if (!member.has_offset) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Structure member " << member.index | |
| << " must be explicitly laid out with Offset or OffsetIdEXT " | |
| "decorations." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| if (ContainsMatrix(member_id)) { | |
| if (!member.has_matrix) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Structure member " << member.index | |
| << " containing a matrix must be explicitly laid out " | |
| "with RowMajor or ColMajor decorations." | |
| << CommonError(inst, storage_class, mode); | |
| } else if (member.matrix_constraints.stride == 0) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Structure member " << member.index | |
| << " containing a matrix must be explicitly laid out " | |
| "with MatrixStride decorations." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| } | |
| } | |
| uint32_t next_offset = 0; | |
| uint32_t ordered_index = 0; | |
| for (const auto& member : member_info) { | |
| auto member_idx = member.index; | |
| auto offset = member.offset; | |
| // We have no information | |
| if (offset == std::numeric_limits<uint32_t>::max()) { | |
| continue; | |
| } | |
| auto member_id = type_inst->GetOperandAs<uint32_t>(member_idx + 1); | |
| auto member_inst = vstate.FindDef(member_id); | |
| if (member_inst->opcode() == spv::Op::OpTypeRuntimeArray && | |
| ordered_index != member_info.size() - 1) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << vstate.VkErrorID(4680) | |
| << "Structure has a runtime array at offset " << offset | |
| << ", but other members at larger offsets." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| ordered_index++; | |
| MatrixConstraints mat_constraints = matrix_constraints; | |
| if (member.has_matrix) { | |
| mat_constraints = member.matrix_constraints; | |
| } | |
| uint32_t align = GetAlign(member_id, mode, mat_constraints); | |
| if (!IsAlignedTo(offset, align)) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << vstate.VkErrorID(GetAlignVkErrorId(member_inst->opcode())) | |
| << "Structure member " << member_idx << " at offset " << offset | |
| << " is not aligned to " << align << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| if (!IsAlignedTo(offset + incoming_offset, align)) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << vstate.VkErrorID(GetAlignVkErrorId(member_inst->opcode())) | |
| << "Structure member " << member_idx << " at offset " << offset | |
| << " plus incoming offset " << incoming_offset | |
| << " is not aligned to " << align << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| if (offset < next_offset) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Structure member " << member_idx << " at offset " << offset | |
| << " overlaps previous member ending at offset " | |
| << next_offset - 1 << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| if (mode != LayoutMode::kScalar && vstate.IsRelaxedBlockLayout()) { | |
| if (member_inst->opcode() == spv::Op::OpTypeVector || | |
| member_inst->opcode() == spv::Op::OpTypeVectorIdEXT) { | |
| uint32_t size = GetSize(member_id, {}); | |
| if (HasImproperStraddle(incoming_offset + offset, size)) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Structure member " << member_idx | |
| << ": Vector has improper straddle due to offset " | |
| << incoming_offset + offset << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| } | |
| } | |
| if (auto error = CheckLayout(inst, member_id, storage_class, mode, | |
| incoming_offset + offset, mat_constraints)) { | |
| return error; | |
| } | |
| uint32_t size = GetSize(member_id, mat_constraints); | |
| next_offset = size + offset; | |
| if (mode != LayoutMode::kScalar && | |
| (member_inst->opcode() == spv::Op::OpTypeArray || | |
| member_inst->opcode() == spv::Op::OpTypeStruct)) { | |
| next_offset = AlignTo(next_offset, align); | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| // Checks array layouts | |
| // * Arrays must have stride decoration | |
| // * Stride must be non-zero | |
| // * Stride must be aligned | |
| // * Stride must be greater or equal to element size | |
| // * Elements have valid layout | |
| spv_result_t CheckArrayLayout(const Instruction* inst, uint32_t type_id, | |
| spv::StorageClass storage_class, | |
| LayoutMode mode, uint32_t incoming_offset, | |
| const MatrixConstraints& matrix_constraints) { | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| auto [has_stride, stride] = GetArrayStride(type_id); | |
| if (!has_stride) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Array must be explicitly laid out with ArrayStride or " | |
| "ArrayStrideIdEXT decorations." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| uint32_t ele_id = type_inst->GetOperandAs<uint32_t>(1u); | |
| uint32_t ele_size = GetSize(ele_id, matrix_constraints); | |
| uint32_t align = GetAlign(type_id, mode, matrix_constraints); | |
| if (stride == 0) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Array must not have a stride of 0." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| // uint32 max stride is unevaluatable (e.g. spec constant). | |
| if (stride == std::numeric_limits<uint32_t>::max()) { | |
| return SPV_SUCCESS; | |
| } | |
| if (!IsAlignedTo(stride, align)) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Array stride " << stride << " must satisfy alignment " << align | |
| << "." << CommonError(inst, storage_class, mode); | |
| } | |
| if (stride != std::numeric_limits<uint32_t>::max() && stride < ele_size) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Array stride " << stride | |
| << " is smaller than element type size " << ele_size << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| uint32_t num_elements = 0; | |
| if (type_inst->opcode() == spv::Op::OpTypeArray) { | |
| uint64_t count = 0; | |
| if (vstate.EvalConstantValUint64(type_inst->GetOperandAs<uint32_t>(2u), | |
| &count)) { | |
| num_elements = static_cast<uint32_t>(count); | |
| } | |
| } | |
| num_elements = std::max(1u, num_elements); | |
| std::vector<bool> seen(16, false); | |
| for (uint32_t i = 0; i < num_elements; ++i) { | |
| uint32_t next_offset = i * stride + incoming_offset; | |
| // Stop checking if offsets repeat in terms of 16-byte multiples. | |
| if (seen[next_offset % 16]) { | |
| break; | |
| } | |
| if (auto error = CheckLayout(inst, ele_id, storage_class, mode, | |
| next_offset, matrix_constraints)) { | |
| return error; | |
| } | |
| seen[next_offset % 16] = true; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| // Checks the layout matrices | |
| // * Stride must be a multiple of align | |
| // * Stride must be greater or equal to minor size | |
| spv_result_t CheckMatrixLayout(const Instruction* inst, uint32_t type_id, | |
| spv::StorageClass storage_class, | |
| LayoutMode mode, | |
| const MatrixConstraints& matrix_constraints) { | |
| // We already checked that any struct containing a matrix has a non-zero | |
| // stride so if we have 0 stride here then it will come from the result | |
| // of an access chain or other instruction. Other rules are meant to | |
| // catch any misuse we can skip it here. | |
| if (matrix_constraints.stride == 0) { | |
| return SPV_SUCCESS; | |
| } | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| const auto align = GetAlign(type_id, mode, matrix_constraints); | |
| if (!IsAlignedTo(matrix_constraints.stride, align)) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Matrix with a stride " << matrix_constraints.stride | |
| << " not satisfying alignment to " << align << "." | |
| << CommonError(inst, storage_class, mode) << "\n"; | |
| } | |
| const auto ele_id = type_inst->GetOperandAs<uint32_t>(1u); | |
| uint32_t size = 0; | |
| if (matrix_constraints.col_major) { | |
| size = GetSize(ele_id, {}); | |
| } else { | |
| // Element size is # cols * ele size. | |
| const auto* ele_inst = vstate.FindDef(ele_id); | |
| const auto scalar_id = ele_inst->GetOperandAs<uint32_t>(1u); | |
| size = GetSize(scalar_id, {}) * type_inst->GetOperandAs<uint32_t>(2u); | |
| } | |
| if (matrix_constraints.stride < size) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, type_inst) | |
| << "Matrix stride " << matrix_constraints.stride | |
| << " is smaller than column size " << size << "." | |
| << CommonError(inst, storage_class, mode); | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| // Returns true if type_id satisfies the given layout rules. | |
| spv_result_t CheckLayout(const Instruction* inst, uint32_t type_id, | |
| spv::StorageClass storage_class, LayoutMode mode, | |
| uint32_t incoming_offset, | |
| const MatrixConstraints& matrix_constraints) { | |
| if (vstate.options()->skip_block_layout) { | |
| return SPV_SUCCESS; | |
| } | |
| LayoutKey key{type_id, mode, incoming_offset, matrix_constraints}; | |
| if (layout_cache_.count(key)) { | |
| return SPV_SUCCESS; | |
| } | |
| const auto* type_inst = vstate.FindDef(type_id); | |
| switch (type_inst->opcode()) { | |
| case spv::Op::OpTypeStruct: | |
| if (auto error = | |
| CheckStructLayout(inst, type_id, storage_class, mode, | |
| incoming_offset, matrix_constraints)) { | |
| return error; | |
| } | |
| break; | |
| case spv::Op::OpTypeArray: | |
| case spv::Op::OpTypeRuntimeArray: | |
| if (auto error = | |
| CheckArrayLayout(inst, type_id, storage_class, mode, | |
| incoming_offset, matrix_constraints)) { | |
| return error; | |
| } | |
| break; | |
| case spv::Op::OpTypeMatrix: | |
| if (auto error = CheckMatrixLayout(inst, type_id, storage_class, mode, | |
| matrix_constraints)) { | |
| return error; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| layout_cache_.insert(key); | |
| return SPV_SUCCESS; | |
| } | |
| // Performs a single pass over the IR and for each memory reference determines | |
| // what validation is necessary. | |
| spv_result_t Run() { | |
| if (!spvIsVulkanEnv(vstate.context()->target_env)) { | |
| return SPV_SUCCESS; | |
| } | |
| for (const auto& inst : vstate.ordered_instructions()) { | |
| MemoryReference reference; | |
| if (!GetMemoryReference(&inst, &reference)) { | |
| continue; | |
| } | |
| // Descriptor arrays shouldn't have a stride. Check for that here since | |
| // most descriptors require a layout. | |
| if (reference.descriptor_array_id != 0) { | |
| const bool array_stride = vstate.HasDecoration( | |
| reference.descriptor_array_id, spv::Decoration::ArrayStride); | |
| const bool array_stride_id = vstate.HasDecoration( | |
| reference.descriptor_array_id, spv::Decoration::ArrayStrideIdEXT); | |
| if (array_stride || array_stride_id) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, &inst) | |
| << vstate.VkErrorID(10684) | |
| << "Invalid explicit layout decorations on type " | |
| << vstate.getIdName(reference.descriptor_array_id) << ", the " | |
| << spvtools::StorageClassToString(reference.storage_class) | |
| << " storage class has an explicit layout from the " | |
| << vstate.SpvDecorationString( | |
| array_stride ? spv::Decoration::ArrayStride | |
| : spv::Decoration::ArrayStrideIdEXT) | |
| << " decoration"; | |
| } | |
| } | |
| // Untyped pointers require a layout. Workgroup variables must be blocks | |
| // to have a layout. | |
| if (reference.untyped && | |
| reference.storage_class == spv::StorageClass::Workgroup && | |
| inst.opcode() == spv::Op::OpUntypedVariableKHR && | |
| (vstate.GetIdOpcode(reference.type_id) != spv::Op::OpTypeStruct || | |
| !vstate.HasDecoration(reference.type_id, spv::Decoration::Block))) { | |
| return vstate.diag(SPV_ERROR_INVALID_ID, &inst) | |
| << vstate.VkErrorID(10684) | |
| << "Untyped variables in Workgroup storage class must be " | |
| "block-decorated structs"; | |
| } | |
| if (reference.requirement == LayoutRequirement::kRequired) { | |
| if (auto error = | |
| CheckLayout(&inst, reference.type_id, reference.storage_class, | |
| reference.layout, 0, {})) { | |
| return error; | |
| } | |
| } else if (reference.requirement == LayoutRequirement::kProhibited) { | |
| if (auto error = CheckNoLayout(&inst, reference.type_id, | |
| reference.storage_class)) { | |
| return error; | |
| } | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| }; | |
| } // namespace | |
| spv_result_t ValidateExplicitLayout(ValidationState_t& vstate) { | |
| return Impl{vstate}.Run(); | |
| } | |
| } // namespace val | |
| } // namespace spvtools |