// Copyright (c) 2018 Google LLC. // // 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. // Ensures type declarations are unique unless allowed by the specification. #include "source/opcode.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { namespace val { namespace { // Returns, as an int64_t, the literal value from an OpConstant or the // default value of an OpSpecConstant, assuming it is an integral type. // For signed integers, relies the rule that literal value is sign extended // to fill out to word granularity. Assumes that the constant value // has int64_t ConstantLiteralAsInt64(uint32_t width, const std::vector& const_words) { const uint32_t lo_word = const_words[3]; if (width <= 32) return int32_t(lo_word); assert(width <= 64); assert(const_words.size() > 4); const uint32_t hi_word = const_words[4]; // Must exist, per spec. return static_cast(uint64_t(lo_word) | uint64_t(hi_word) << 32); } // Validates that type declarations are unique, unless multiple declarations // of the same data type are allowed by the specification. // (see section 2.8 Types and Variables) // Doesn't do anything if SPV_VAL_ignore_type_decl_unique was declared in the // module. spv_result_t ValidateUniqueness(ValidationState_t& _, const Instruction* inst) { if (_.HasExtension(Extension::kSPV_VALIDATOR_ignore_type_decl_unique)) return SPV_SUCCESS; const auto opcode = inst->opcode(); if (opcode != spv::Op::OpTypeArray && opcode != spv::Op::OpTypeRuntimeArray && opcode != spv::Op::OpTypeStruct && opcode != spv::Op::OpTypePointer && !_.RegisterUniqueTypeDeclaration(inst)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Duplicate non-aggregate type declarations are not allowed. " "Opcode: " << spvOpcodeString(opcode) << " id: " << inst->id(); } return SPV_SUCCESS; } spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) { // Validates that the number of bits specified for an Int type is valid. // Scalar integer types can be parameterized only with 32-bits. // Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit // integers, respectively. auto num_bits = inst->GetOperandAs(1); if (num_bits != 32) { if (num_bits == 8) { if (_.features().declare_int8_type) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Using an 8-bit integer type requires the Int8 capability," " or an extension that explicitly enables 8-bit integers."; } else if (num_bits == 16) { if (_.features().declare_int16_type) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Using a 16-bit integer type requires the Int16 capability," " or an extension that explicitly enables 16-bit integers."; } else if (num_bits == 64) { if (_.HasCapability(spv::Capability::Int64)) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Using a 64-bit integer type requires the Int64 capability."; } else { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Invalid number of bits (" << num_bits << ") used for OpTypeInt."; } } const auto signedness_index = 2; const auto signedness = inst->GetOperandAs(signedness_index); if (signedness != 0 && signedness != 1) { return _.diag(SPV_ERROR_INVALID_VALUE, inst) << "OpTypeInt has invalid signedness:"; } // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The // Signedness in OpTypeInt must always be 0. if (spv::Op::OpTypeInt == inst->opcode() && _.HasCapability(spv::Capability::Kernel) && inst->GetOperandAs(2) != 0u) { return _.diag(SPV_ERROR_INVALID_BINARY, inst) << "The Signedness in OpTypeInt " "must always be 0 when Kernel " "capability is used."; } return SPV_SUCCESS; } spv_result_t ValidateTypeFloat(ValidationState_t& _, const Instruction* inst) { // Validates that the number of bits specified for an Int type is valid. // Scalar integer types can be parameterized only with 32-bits. // Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit // integers, respectively. auto num_bits = inst->GetOperandAs(1); if (num_bits == 32) { return SPV_SUCCESS; } if (num_bits == 16) { if (_.features().declare_float16_type) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Using a 16-bit floating point " << "type requires the Float16 or Float16Buffer capability," " or an extension that explicitly enables 16-bit floating point."; } if (num_bits == 64) { if (_.HasCapability(spv::Capability::Float64)) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Using a 64-bit floating point " << "type requires the Float64 capability."; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Invalid number of bits (" << num_bits << ") used for OpTypeFloat."; } spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) { const auto component_index = 1; const auto component_id = inst->GetOperandAs(component_index); const auto component_type = _.FindDef(component_id); if (!component_type || !spvOpcodeIsScalarType(component_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeVector Component Type " << _.getIdName(component_id) << " is not a scalar type."; } // Validates that the number of components in the vector is valid. // Vector types can only be parameterized as having 2, 3, or 4 components. // If the Vector16 capability is added, 8 and 16 components are also allowed. auto num_components = inst->GetOperandAs(2); if (num_components == 2 || num_components == 3 || num_components == 4) { return SPV_SUCCESS; } else if (num_components == 8 || num_components == 16) { if (_.HasCapability(spv::Capability::Vector16)) { return SPV_SUCCESS; } return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Having " << num_components << " components for " << spvOpcodeString(inst->opcode()) << " requires the Vector16 capability"; } else { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Illegal number of components (" << num_components << ") for " << spvOpcodeString(inst->opcode()); } return SPV_SUCCESS; } spv_result_t ValidateTypeMatrix(ValidationState_t& _, const Instruction* inst) { const auto column_type_index = 1; const auto column_type_id = inst->GetOperandAs(column_type_index); const auto column_type = _.FindDef(column_type_id); if (!column_type || spv::Op::OpTypeVector != column_type->opcode()) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Columns in a matrix must be of type vector."; } // Trace back once more to find out the type of components in the vector. // Operand 1 is the of the type of data in the vector. const auto comp_type_id = column_type->GetOperandAs(1); auto comp_type_instruction = _.FindDef(comp_type_id); if (comp_type_instruction->opcode() != spv::Op::OpTypeFloat) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be " "parameterized with " "floating-point types."; } // Validates that the matrix has 2,3, or 4 columns. auto num_cols = inst->GetOperandAs(2); if (num_cols != 2 && num_cols != 3 && num_cols != 4) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be " "parameterized as having " "only 2, 3, or 4 columns."; } return SPV_SUCCESS; } spv_result_t ValidateTypeArray(ValidationState_t& _, const Instruction* inst) { const auto element_type_index = 1; const auto element_type_id = inst->GetOperandAs(element_type_index); const auto element_type = _.FindDef(element_type_id); if (!element_type || !spvOpcodeGeneratesType(element_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Element Type " << _.getIdName(element_type_id) << " is not a type."; } if (element_type->opcode() == spv::Op::OpTypeVoid) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Element Type " << _.getIdName(element_type_id) << " is a void type."; } if (spvIsVulkanEnv(_.context()->target_env) && element_type->opcode() == spv::Op::OpTypeRuntimeArray) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4680) << "OpTypeArray Element Type " << _.getIdName(element_type_id) << " is not valid in " << spvLogStringForEnv(_.context()->target_env) << " environments."; } const auto length_index = 2; const auto length_id = inst->GetOperandAs(length_index); const auto length = _.FindDef(length_id); if (!length || !spvOpcodeIsConstant(length->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Length " << _.getIdName(length_id) << " is not a scalar constant type."; } // NOTE: Check the initialiser value of the constant const auto const_inst = length->words(); const auto const_result_type_index = 1; const auto const_result_type = _.FindDef(const_inst[const_result_type_index]); if (!const_result_type || spv::Op::OpTypeInt != const_result_type->opcode()) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Length " << _.getIdName(length_id) << " is not a constant integer type."; } switch (length->opcode()) { case spv::Op::OpSpecConstant: case spv::Op::OpConstant: { auto& type_words = const_result_type->words(); const bool is_signed = type_words[3] > 0; const uint32_t width = type_words[2]; const int64_t ivalue = ConstantLiteralAsInt64(width, length->words()); if (ivalue == 0 || (ivalue < 0 && is_signed)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Length " << _.getIdName(length_id) << " default value must be at least 1: found " << ivalue; } } break; case spv::Op::OpConstantNull: return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Length " << _.getIdName(length_id) << " default value must be at least 1."; case spv::Op::OpSpecConstantOp: // Assume it's OK, rather than try to evaluate the operation. break; default: assert(0 && "bug in spvOpcodeIsConstant() or result type isn't int"); } return SPV_SUCCESS; } spv_result_t ValidateTypeRuntimeArray(ValidationState_t& _, const Instruction* inst) { const auto element_type_index = 1; const auto element_id = inst->GetOperandAs(element_type_index); const auto element_type = _.FindDef(element_id); if (!element_type || !spvOpcodeGeneratesType(element_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeRuntimeArray Element Type " << _.getIdName(element_id) << " is not a type."; } if (element_type->opcode() == spv::Op::OpTypeVoid) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeRuntimeArray Element Type " << _.getIdName(element_id) << " is a void type."; } if (spvIsVulkanEnv(_.context()->target_env) && element_type->opcode() == spv::Op::OpTypeRuntimeArray) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4680) << "OpTypeRuntimeArray Element Type " << _.getIdName(element_id) << " is not valid in " << spvLogStringForEnv(_.context()->target_env) << " environments."; } return SPV_SUCCESS; } spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { const uint32_t struct_id = inst->GetOperandAs(0); for (size_t member_type_index = 1; member_type_index < inst->operands().size(); ++member_type_index) { auto member_type_id = inst->GetOperandAs(member_type_index); if (member_type_id == inst->id()) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Structure members may not be self references"; } auto member_type = _.FindDef(member_type_id); if (!member_type || !spvOpcodeGeneratesType(member_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeStruct Member Type " << _.getIdName(member_type_id) << " is not a type."; } if (member_type->opcode() == spv::Op::OpTypeVoid) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Structures cannot contain a void type."; } if (spv::Op::OpTypeStruct == member_type->opcode() && _.IsStructTypeWithBuiltInMember(member_type_id)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Structure " << _.getIdName(member_type_id) << " contains members with BuiltIn decoration. Therefore this " << "structure may not be contained as a member of another " << "structure " << "type. Structure " << _.getIdName(struct_id) << " contains structure " << _.getIdName(member_type_id) << "."; } if (spvIsVulkanEnv(_.context()->target_env) && member_type->opcode() == spv::Op::OpTypeRuntimeArray) { const bool is_last_member = member_type_index == inst->operands().size() - 1; if (!is_last_member) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4680) << "In " << spvLogStringForEnv(_.context()->target_env) << ", OpTypeRuntimeArray must only be used for the last member " "of an OpTypeStruct"; } } } bool has_nested_blockOrBufferBlock_struct = false; // Struct members start at word 2 of OpTypeStruct instruction. for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) { auto member = inst->word(word_i); auto memberTypeInstr = _.FindDef(member); if (memberTypeInstr && spv::Op::OpTypeStruct == memberTypeInstr->opcode()) { if (_.HasDecoration(memberTypeInstr->id(), spv::Decoration::Block) || _.HasDecoration(memberTypeInstr->id(), spv::Decoration::BufferBlock) || _.GetHasNestedBlockOrBufferBlockStruct(memberTypeInstr->id())) has_nested_blockOrBufferBlock_struct = true; } } _.SetHasNestedBlockOrBufferBlockStruct(inst->id(), has_nested_blockOrBufferBlock_struct); if (_.GetHasNestedBlockOrBufferBlockStruct(inst->id()) && (_.HasDecoration(inst->id(), spv::Decoration::BufferBlock) || _.HasDecoration(inst->id(), spv::Decoration::Block))) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "rules: A Block or BufferBlock cannot be nested within another " "Block or BufferBlock. "; } std::unordered_set built_in_members; for (auto decoration : _.id_decorations(struct_id)) { if (decoration.dec_type() == spv::Decoration::BuiltIn && decoration.struct_member_index() != Decoration::kInvalidMember) { built_in_members.insert(decoration.struct_member_index()); } } int num_struct_members = static_cast(inst->operands().size() - 1); int num_builtin_members = static_cast(built_in_members.size()); if (num_builtin_members > 0 && num_builtin_members != num_struct_members) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "When BuiltIn decoration is applied to a structure-type member, " << "all members of that structure type must also be decorated with " << "BuiltIn (No allowed mixing of built-in variables and " << "non-built-in variables within a single structure). Structure id " << struct_id << " does not meet this requirement."; } if (num_builtin_members > 0) { _.RegisterStructTypeWithBuiltInMember(struct_id); } const auto isOpaqueType = [&_](const Instruction* opaque_inst) { auto opcode = opaque_inst->opcode(); if (_.HasCapability(spv::Capability::BindlessTextureNV) && (opcode == spv::Op::OpTypeImage || opcode == spv::Op::OpTypeSampler || opcode == spv::Op::OpTypeSampledImage)) { return false; } else if (spvOpcodeIsBaseOpaqueType(opcode)) { return true; } return false; }; if (spvIsVulkanEnv(_.context()->target_env) && !_.options()->before_hlsl_legalization && _.ContainsType(inst->id(), isOpaqueType)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4667) << "In " << spvLogStringForEnv(_.context()->target_env) << ", OpTypeStruct must not contain an opaque type."; } return SPV_SUCCESS; } spv_result_t ValidateTypePointer(ValidationState_t& _, const Instruction* inst) { auto type_id = inst->GetOperandAs(2); auto type = _.FindDef(type_id); if (!type || !spvOpcodeGeneratesType(type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypePointer Type " << _.getIdName(type_id) << " is not a type."; } // See if this points to a storage image. const auto storage_class = inst->GetOperandAs(1); if (storage_class == spv::StorageClass::UniformConstant) { // Unpack an optional level of arraying. if (type->opcode() == spv::Op::OpTypeArray || type->opcode() == spv::Op::OpTypeRuntimeArray) { type_id = type->GetOperandAs(1); type = _.FindDef(type_id); } if (type->opcode() == spv::Op::OpTypeImage) { const auto sampled = type->GetOperandAs(6); // 2 indicates this image is known to be be used without a sampler, i.e. // a storage image. if (sampled == 2) _.RegisterPointerToStorageImage(inst->id()); } } if (!_.IsValidStorageClass(storage_class)) { return _.diag(SPV_ERROR_INVALID_BINARY, inst) << _.VkErrorID(4643) << "Invalid storage class for target environment"; } return SPV_SUCCESS; } spv_result_t ValidateTypeFunction(ValidationState_t& _, const Instruction* inst) { const auto return_type_id = inst->GetOperandAs(1); const auto return_type = _.FindDef(return_type_id); if (!return_type || !spvOpcodeGeneratesType(return_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeFunction Return Type " << _.getIdName(return_type_id) << " is not a type."; } size_t num_args = 0; for (size_t param_type_index = 2; param_type_index < inst->operands().size(); ++param_type_index, ++num_args) { const auto param_id = inst->GetOperandAs(param_type_index); const auto param_type = _.FindDef(param_id); if (!param_type || !spvOpcodeGeneratesType(param_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeFunction Parameter Type " << _.getIdName(param_id) << " is not a type."; } if (param_type->opcode() == spv::Op::OpTypeVoid) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeFunction Parameter Type " << _.getIdName(param_id) << " cannot be OpTypeVoid."; } } const uint32_t num_function_args_limit = _.options()->universal_limits_.max_function_args; if (num_args > num_function_args_limit) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeFunction may not take more than " << num_function_args_limit << " arguments. OpTypeFunction " << _.getIdName(inst->GetOperandAs(0)) << " has " << num_args << " arguments."; } // The only valid uses of OpTypeFunction are in an OpFunction, debugging, or // decoration instruction. for (auto& pair : inst->uses()) { const auto* use = pair.first; if (use->opcode() != spv::Op::OpFunction && !spvOpcodeIsDebug(use->opcode()) && !use->IsNonSemantic() && !spvOpcodeIsDecoration(use->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, use) << "Invalid use of function type result id " << _.getIdName(inst->id()) << "."; } } return SPV_SUCCESS; } spv_result_t ValidateTypeForwardPointer(ValidationState_t& _, const Instruction* inst) { const auto pointer_type_id = inst->GetOperandAs(0); const auto pointer_type_inst = _.FindDef(pointer_type_id); if (pointer_type_inst->opcode() != spv::Op::OpTypePointer) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Pointer type in OpTypeForwardPointer is not a pointer type."; } const auto storage_class = inst->GetOperandAs(1); if (storage_class != pointer_type_inst->GetOperandAs(1)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Storage class in OpTypeForwardPointer does not match the " << "pointer definition."; } const auto pointee_type_id = pointer_type_inst->GetOperandAs(2); const auto pointee_type = _.FindDef(pointee_type_id); if (!pointee_type || pointee_type->opcode() != spv::Op::OpTypeStruct) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Forward pointers must point to a structure"; } if (spvIsVulkanEnv(_.context()->target_env)) { if (storage_class != spv::StorageClass::PhysicalStorageBuffer) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4711) << "In Vulkan, OpTypeForwardPointer must have " << "a storage class of PhysicalStorageBuffer."; } } return SPV_SUCCESS; } spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _, const Instruction* inst) { const auto component_type_index = 1; const auto component_type_id = inst->GetOperandAs(component_type_index); const auto component_type = _.FindDef(component_type_id); if (!component_type || (spv::Op::OpTypeFloat != component_type->opcode() && spv::Op::OpTypeInt != component_type->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeCooperativeMatrixNV Component Type " << _.getIdName(component_type_id) << " is not a scalar numerical type."; } const auto scope_index = 2; const auto scope_id = inst->GetOperandAs(scope_index); const auto scope = _.FindDef(scope_id); if (!scope || !_.IsIntScalarType(scope->type_id()) || !spvOpcodeIsConstant(scope->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeCooperativeMatrixNV Scope " << _.getIdName(scope_id) << " is not a constant instruction with scalar integer type."; } const auto rows_index = 3; const auto rows_id = inst->GetOperandAs(rows_index); const auto rows = _.FindDef(rows_id); if (!rows || !_.IsIntScalarType(rows->type_id()) || !spvOpcodeIsConstant(rows->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeCooperativeMatrixNV Rows " << _.getIdName(rows_id) << " is not a constant instruction with scalar integer type."; } const auto cols_index = 4; const auto cols_id = inst->GetOperandAs(cols_index); const auto cols = _.FindDef(cols_id); if (!cols || !_.IsIntScalarType(cols->type_id()) || !spvOpcodeIsConstant(cols->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeCooperativeMatrixNV Cols " << _.getIdName(cols_id) << " is not a constant instruction with scalar integer type."; } return SPV_SUCCESS; } } // namespace spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) { if (!spvOpcodeGeneratesType(inst->opcode()) && inst->opcode() != spv::Op::OpTypeForwardPointer) { return SPV_SUCCESS; } if (auto error = ValidateUniqueness(_, inst)) return error; switch (inst->opcode()) { case spv::Op::OpTypeInt: if (auto error = ValidateTypeInt(_, inst)) return error; break; case spv::Op::OpTypeFloat: if (auto error = ValidateTypeFloat(_, inst)) return error; break; case spv::Op::OpTypeVector: if (auto error = ValidateTypeVector(_, inst)) return error; break; case spv::Op::OpTypeMatrix: if (auto error = ValidateTypeMatrix(_, inst)) return error; break; case spv::Op::OpTypeArray: if (auto error = ValidateTypeArray(_, inst)) return error; break; case spv::Op::OpTypeRuntimeArray: if (auto error = ValidateTypeRuntimeArray(_, inst)) return error; break; case spv::Op::OpTypeStruct: if (auto error = ValidateTypeStruct(_, inst)) return error; break; case spv::Op::OpTypePointer: if (auto error = ValidateTypePointer(_, inst)) return error; break; case spv::Op::OpTypeFunction: if (auto error = ValidateTypeFunction(_, inst)) return error; break; case spv::Op::OpTypeForwardPointer: if (auto error = ValidateTypeForwardPointer(_, inst)) return error; break; case spv::Op::OpTypeCooperativeMatrixNV: if (auto error = ValidateTypeCooperativeMatrixNV(_, inst)) return error; break; default: break; } return SPV_SUCCESS; } } // namespace val } // namespace spvtools