diff options
author | Alan Baker <alanbaker@google.com> | 2018-08-01 14:44:56 -0400 |
---|---|---|
committer | Alan Baker <alanbaker@google.com> | 2018-08-01 16:10:11 -0400 |
commit | d49bedcaa68a907e2b201bc372d69256e50f2ab5 (patch) | |
tree | 25ecc096a35df8bab1d1cadba9de2f32aa0881a2 /source | |
parent | a5a5ea0e2dfce9c755a88af1074ebe68a44d2ed9 (diff) |
Move memory class instructions to new pass
* Refactored the Memory class of instructions in the spec out Id
validation and into a new pass
* Tests unmodified
* some minor disassembly changes
* minor style changes
Diffstat (limited to 'source')
-rw-r--r-- | source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/val/validate.cpp | 1 | ||||
-rw-r--r-- | source/val/validate.h | 6 | ||||
-rw-r--r-- | source/val/validate_id.cpp | 562 | ||||
-rw-r--r-- | source/val/validate_memory.cpp | 600 |
5 files changed, 608 insertions, 562 deletions
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index b48850c7..8dc4cb91 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -303,6 +303,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_layout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_literals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type_unique.cpp diff --git a/source/val/validate.cpp b/source/val/validate.cpp index 7c586c36..3ba458d0 100644 --- a/source/val/validate.cpp +++ b/source/val/validate.cpp @@ -280,6 +280,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( if (auto error = CheckIdDefinitionDominateUse(*vstate)) return error; if (auto error = ValidateDecorations(*vstate)) return error; if (auto error = ValidateInterfaces(*vstate)) return error; + if (auto error = ValidateMemoryInstructions(*vstate)) return error; // Entry point validation. Based on 2.16.1 (Universal Validation Rules) of the // SPIRV spec: diff --git a/source/val/validate.h b/source/val/validate.h index 50fb6448..33742e5c 100644 --- a/source/val/validate.h +++ b/source/val/validate.h @@ -87,6 +87,12 @@ spv_result_t ValidateAdjacency(ValidationState_t& _); /// @return SPV_SUCCESS if no errors are found. spv_result_t ValidateInterfaces(ValidationState_t& _); +/// @brief Validates memory instructions +/// +/// @param[in] _ the validation state of the module +/// @return SPV_SUCCESS if no errors are found. +spv_result_t ValidateMemoryInstructions(ValidationState_t& _); + /// @brief Updates the immediate dominator for each of the block edges /// /// Updates the immediate dominator of the blocks for each of the edges diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp index d00cef55..46583844 100644 --- a/source/val/validate_id.cpp +++ b/source/val/validate_id.cpp @@ -77,26 +77,6 @@ class idUsage { const MessageConsumer& consumer_; const ValidationState_t& module_; std::vector<uint32_t> entry_points_; - - // Returns true if the two instructions represent structs that, as far as the - // validator can tell, have the exact same data layout. - bool AreLayoutCompatibleStructs(const Instruction* type1, - const Instruction* type2); - - // Returns true if the operands to the OpTypeStruct instruction defining the - // types are the same or are layout compatible types. |type1| and |type2| must - // be OpTypeStruct instructions. - bool HaveLayoutCompatibleMembers(const Instruction* type1, - const Instruction* type2); - - // Returns true if all decorations that affect the data layout of the struct - // (like Offset), are the same for the two types. |type1| and |type2| must be - // OpTypeStruct instructions. - bool HaveSameLayoutDecorations(const Instruction* type1, - const Instruction* type2); - bool HasConflictingMemberOffsets( - const std::vector<Decoration>& type1_decorations, - const std::vector<Decoration>& type2_decorations) const; }; #define DIAG(inst) \ @@ -1123,443 +1103,6 @@ bool idUsage::isValid<SpvOpSpecConstantComposite>(const spv_instruction_t* inst, } template <> -bool idUsage::isValid<SpvOpVariable>(const spv_instruction_t* inst, - const spv_opcode_desc) { - auto resultTypeIndex = 1; - auto resultType = module_.FindDef(inst->words[resultTypeIndex]); - if (!resultType || SpvOpTypePointer != resultType->opcode()) { - DIAG(resultType) << "OpVariable Result Type <id> '" - << module_.getIdName(inst->words[resultTypeIndex]) - << "' is not a pointer type."; - return false; - } - const auto initialiserIndex = 4; - if (initialiserIndex < inst->words.size()) { - const auto initialiser = module_.FindDef(inst->words[initialiserIndex]); - const auto storageClassIndex = 3; - const auto is_module_scope_var = - initialiser && (initialiser->opcode() == SpvOpVariable) && - (initialiser->word(storageClassIndex) != SpvStorageClassFunction); - const auto is_constant = - initialiser && spvOpcodeIsConstant(initialiser->opcode()); - if (!initialiser || !(is_constant || is_module_scope_var)) { - DIAG(initialiser) << "OpVariable Initializer <id> '" - << module_.getIdName(inst->words[initialiserIndex]) - << "' is not a constant or module-scope variable."; - return false; - } - } - return true; -} - -template <> -bool idUsage::isValid<SpvOpLoad>(const spv_instruction_t* inst, - const spv_opcode_desc) { - auto resultTypeIndex = 1; - auto resultType = module_.FindDef(inst->words[resultTypeIndex]); - if (!resultType) { - DIAG(resultType) << "OpLoad Result Type <id> '" - << module_.getIdName(inst->words[resultTypeIndex]) - << "' is not defind."; - return false; - } - const bool uses_variable_pointer = - module_.features().variable_pointers || - module_.features().variable_pointers_storage_buffer; - auto pointerIndex = 3; - auto pointer = module_.FindDef(inst->words[pointerIndex]); - if (!pointer || - (addressingModel == SpvAddressingModelLogical && - ((!uses_variable_pointer && - !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || - (uses_variable_pointer && - !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { - DIAG(pointer) << "OpLoad Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' is not a logical pointer."; - return false; - } - auto pointerType = module_.FindDef(pointer->type_id()); - if (!pointerType || pointerType->opcode() != SpvOpTypePointer) { - DIAG(pointer) << "OpLoad type for pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' is not a pointer type."; - return false; - } - auto pointeeType = module_.FindDef(pointerType->words()[3]); - if (!pointeeType || resultType->id() != pointeeType->id()) { - DIAG(resultType) << "OpLoad Result Type <id> '" - << module_.getIdName(inst->words[resultTypeIndex]) - << "' does not match Pointer <id> '" - << module_.getIdName(pointer->id()) << "'s type."; - return false; - } - return true; -} - -template <> -bool idUsage::isValid<SpvOpStore>(const spv_instruction_t* inst, - const spv_opcode_desc) { - const bool uses_variable_pointer = - module_.features().variable_pointers || - module_.features().variable_pointers_storage_buffer; - const auto pointerIndex = 1; - auto pointer = module_.FindDef(inst->words[pointerIndex]); - if (!pointer || - (addressingModel == SpvAddressingModelLogical && - ((!uses_variable_pointer && - !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || - (uses_variable_pointer && - !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' is not a logical pointer."; - return false; - } - auto pointerType = module_.FindDef(pointer->type_id()); - if (!pointer || pointerType->opcode() != SpvOpTypePointer) { - DIAG(pointer) << "OpStore type for pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' is not a pointer type."; - return false; - } - auto type = module_.FindDef(pointerType->words()[3]); - assert(type); - if (SpvOpTypeVoid == type->opcode()) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "'s type is void."; - return false; - } - - // validate storage class - { - uint32_t dataType; - uint32_t storageClass; - if (!module_.GetPointerTypeInfo(pointerType->id(), &dataType, - &storageClass)) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' is not pointer type"; - return false; - } - - if (storageClass == SpvStorageClassUniformConstant || - storageClass == SpvStorageClassInput || - storageClass == SpvStorageClassPushConstant) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "' storage class is read-only"; - return false; - } - } - - auto objectIndex = 2; - auto object = module_.FindDef(inst->words[objectIndex]); - if (!object || !object->type_id()) { - DIAG(object) << "OpStore Object <id> '" - << module_.getIdName(inst->words[objectIndex]) - << "' is not an object."; - return false; - } - auto objectType = module_.FindDef(object->type_id()); - assert(objectType); - if (SpvOpTypeVoid == objectType->opcode()) { - DIAG(object) << "OpStore Object <id> '" - << module_.getIdName(inst->words[objectIndex]) - << "'s type is void."; - return false; - } - - if (type->id() != objectType->id()) { - if (!module_.options()->relax_struct_store || - type->opcode() != SpvOpTypeStruct || - objectType->opcode() != SpvOpTypeStruct) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "'s type does not match Object <id> '" - << module_.getIdName(object->id()) << "'s type."; - return false; - } - - // TODO: Check for layout compatible matricies and arrays as well. - if (!AreLayoutCompatibleStructs(type, objectType)) { - DIAG(pointer) << "OpStore Pointer <id> '" - << module_.getIdName(inst->words[pointerIndex]) - << "'s layout does not match Object <id> '" - << module_.getIdName(object->id()) << "'s layout."; - return false; - } - } - return true; -} - -template <> -bool idUsage::isValid<SpvOpCopyMemory>(const spv_instruction_t* inst, - const spv_opcode_desc) { - auto targetIndex = 1; - auto target = module_.FindDef(inst->words[targetIndex]); - if (!target) return false; - auto sourceIndex = 2; - auto source = module_.FindDef(inst->words[sourceIndex]); - if (!source) return false; - auto targetPointerType = module_.FindDef(target->type_id()); - assert(targetPointerType); - auto targetType = module_.FindDef(targetPointerType->words()[3]); - assert(targetType); - auto sourcePointerType = module_.FindDef(source->type_id()); - assert(sourcePointerType); - auto sourceType = module_.FindDef(sourcePointerType->words()[3]); - assert(sourceType); - if (targetType->id() != sourceType->id()) { - DIAG(source) << "OpCopyMemory Target <id> '" - << module_.getIdName(inst->words[sourceIndex]) - << "'s type does not match Source <id> '" - << module_.getIdName(sourceType->id()) << "'s type."; - return false; - } - return true; -} - -template <> -bool idUsage::isValid<SpvOpCopyMemorySized>(const spv_instruction_t* inst, - const spv_opcode_desc) { - auto targetIndex = 1; - auto target = module_.FindDef(inst->words[targetIndex]); - if (!target) return false; - auto sourceIndex = 2; - auto source = module_.FindDef(inst->words[sourceIndex]); - if (!source) return false; - auto sizeIndex = 3; - auto size = module_.FindDef(inst->words[sizeIndex]); - if (!size) return false; - auto targetPointerType = module_.FindDef(target->type_id()); - if (!targetPointerType || SpvOpTypePointer != targetPointerType->opcode()) { - DIAG(target) << "OpCopyMemorySized Target <id> '" - << module_.getIdName(inst->words[targetIndex]) - << "' is not a pointer."; - return false; - } - auto sourcePointerType = module_.FindDef(source->type_id()); - if (!sourcePointerType || SpvOpTypePointer != sourcePointerType->opcode()) { - DIAG(source) << "OpCopyMemorySized Source <id> '" - << module_.getIdName(inst->words[sourceIndex]) - << "' is not a pointer."; - return false; - } - switch (size->opcode()) { - // TODO: The following opcode's are assumed to be valid, refer to the - // following bug https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13871 for - // clarification - case SpvOpConstant: - case SpvOpSpecConstant: { - auto sizeType = module_.FindDef(size->type_id()); - assert(sizeType); - if (SpvOpTypeInt != sizeType->opcode()) { - DIAG(size) << "OpCopyMemorySized Size <id> '" - << module_.getIdName(inst->words[sizeIndex]) - << "'s type is not an integer type."; - return false; - } - } break; - case SpvOpVariable: { - auto pointerType = module_.FindDef(size->type_id()); - assert(pointerType); - auto sizeType = module_.FindDef(pointerType->type_id()); - if (!sizeType || SpvOpTypeInt != sizeType->opcode()) { - DIAG(size) << "OpCopyMemorySized Size <id> '" - << module_.getIdName(inst->words[sizeIndex]) - << "'s variable type is not an integer type."; - return false; - } - } break; - default: - DIAG(size) << "OpCopyMemorySized Size <id> '" - << module_.getIdName(inst->words[sizeIndex]) - << "' is not a constant or variable."; - return false; - } - // TODO: Check that consant is a least size 1, see the same bug as above for - // clarification? - return true; -} - -template <> -bool idUsage::isValid<SpvOpAccessChain>(const spv_instruction_t* inst, - const spv_opcode_desc) { - std::string instr_name = - "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode))); - - // The result type must be OpTypePointer. Result Type is at word 1. - auto resultTypeIndex = 1; - auto resultTypeInstr = module_.FindDef(inst->words[resultTypeIndex]); - if (SpvOpTypePointer != resultTypeInstr->opcode()) { - DIAG(resultTypeInstr) << "The Result Type of " << instr_name << " <id> '" - << module_.getIdName(inst->words[2]) - << "' must be OpTypePointer. Found Op" - << spvOpcodeString( - static_cast<SpvOp>(resultTypeInstr->opcode())) - << "."; - return false; - } - - // Result type is a pointer. Find out what it's pointing to. - // This will be used to make sure the indexing results in the same type. - // OpTypePointer word 3 is the type being pointed to. - auto resultTypePointedTo = module_.FindDef(resultTypeInstr->word(3)); - - // Base must be a pointer, pointing to the base of a composite object. - auto baseIdIndex = 3; - auto baseInstr = module_.FindDef(inst->words[baseIdIndex]); - auto baseTypeInstr = module_.FindDef(baseInstr->type_id()); - if (!baseTypeInstr || SpvOpTypePointer != baseTypeInstr->opcode()) { - DIAG(baseInstr) << "The Base <id> '" - << module_.getIdName(inst->words[baseIdIndex]) << "' in " - << instr_name << " instruction must be a pointer."; - return false; - } - - // The result pointer storage class and base pointer storage class must match. - // Word 2 of OpTypePointer is the Storage Class. - auto resultTypeStorageClass = resultTypeInstr->word(2); - auto baseTypeStorageClass = baseTypeInstr->word(2); - if (resultTypeStorageClass != baseTypeStorageClass) { - DIAG(resultTypeInstr) << "The result pointer storage class and base " - "pointer storage class in " - << instr_name << " do not match."; - return false; - } - - // The type pointed to by OpTypePointer (word 3) must be a composite type. - auto typePointedTo = module_.FindDef(baseTypeInstr->word(3)); - - // Check Universal Limit (SPIR-V Spec. Section 2.17). - // The number of indexes passed to OpAccessChain may not exceed 255 - // The instruction includes 4 words + N words (for N indexes) - const size_t num_indexes = inst->words.size() - 4; - const size_t num_indexes_limit = - module_.options()->universal_limits_.max_access_chain_indexes; - if (num_indexes > num_indexes_limit) { - DIAG(resultTypeInstr) << "The number of indexes in " << instr_name - << " may not exceed " << num_indexes_limit - << ". Found " << num_indexes << " indexes."; - return false; - } - // Indexes walk the type hierarchy to the desired depth, potentially down to - // scalar granularity. The first index in Indexes will select the top-level - // member/element/component/element of the base composite. All composite - // constituents use zero-based numbering, as described by their OpType... - // instruction. The second index will apply similarly to that result, and so - // on. Once any non-composite type is reached, there must be no remaining - // (unused) indexes. - for (size_t i = 4; i < inst->words.size(); ++i) { - const uint32_t cur_word = inst->words[i]; - // Earlier ID checks ensure that cur_word definition exists. - auto cur_word_instr = module_.FindDef(cur_word); - // The index must be a scalar integer type (See OpAccessChain in the Spec.) - auto indexTypeInstr = module_.FindDef(cur_word_instr->type_id()); - if (!indexTypeInstr || SpvOpTypeInt != indexTypeInstr->opcode()) { - DIAG(module_.FindDef(cur_word)) - << "Indexes passed to " << instr_name << " must be of type integer."; - return false; - } - switch (typePointedTo->opcode()) { - case SpvOpTypeMatrix: - case SpvOpTypeVector: - case SpvOpTypeArray: - case SpvOpTypeRuntimeArray: { - // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray, - // word 2 is the Element Type. - typePointedTo = module_.FindDef(typePointedTo->word(2)); - break; - } - case SpvOpTypeStruct: { - // In case of structures, there is an additional constraint on the - // index: the index must be an OpConstant. - if (SpvOpConstant != cur_word_instr->opcode()) { - DIAG(cur_word_instr) << "The <id> passed to " << instr_name - << " to index into a " - "structure must be an OpConstant."; - return false; - } - // Get the index value from the OpConstant (word 3 of OpConstant). - // OpConstant could be a signed integer. But it's okay to treat it as - // unsigned because a negative constant int would never be seen as - // correct as a struct offset, since structs can't have more than 2 - // billion members. - const uint32_t cur_index = cur_word_instr->word(3); - // The index points to the struct member we want, therefore, the index - // should be less than the number of struct members. - const uint32_t num_struct_members = - static_cast<uint32_t>(typePointedTo->words().size() - 2); - if (cur_index >= num_struct_members) { - DIAG(cur_word_instr) << "Index is out of bounds: " << instr_name - << " can not find index " << cur_index - << " into the structure <id> '" - << module_.getIdName(typePointedTo->id()) - << "'. This structure has " << num_struct_members - << " members. Largest valid index is " - << num_struct_members - 1 << "."; - return false; - } - // Struct members IDs start at word 2 of OpTypeStruct. - auto structMemberId = typePointedTo->word(cur_index + 2); - typePointedTo = module_.FindDef(structMemberId); - break; - } - default: { - // Give an error. reached non-composite type while indexes still remain. - DIAG(cur_word_instr) << instr_name - << " reached non-composite type while indexes " - "still remain to be traversed."; - return false; - } - } - } - // At this point, we have fully walked down from the base using the indeces. - // The type being pointed to should be the same as the result type. - if (typePointedTo->id() != resultTypePointedTo->id()) { - DIAG(resultTypeInstr) - << instr_name << " result type (Op" - << spvOpcodeString(static_cast<SpvOp>(resultTypePointedTo->opcode())) - << ") does not match the type that results from indexing into the base " - "<id> (Op" - << spvOpcodeString(static_cast<SpvOp>(typePointedTo->opcode())) << ")."; - return false; - } - - return true; -} - -template <> -bool idUsage::isValid<SpvOpInBoundsAccessChain>( - const spv_instruction_t* inst, const spv_opcode_desc opcodeEntry) { - return isValid<SpvOpAccessChain>(inst, opcodeEntry); -} - -template <> -bool idUsage::isValid<SpvOpPtrAccessChain>(const spv_instruction_t* inst, - const spv_opcode_desc opcodeEntry) { - // OpPtrAccessChain's validation rules are similar to OpAccessChain, with one - // difference: word 4 must be id of an integer (Element <id>). - // The grammar guarantees that there are at least 5 words in the instruction - // (i.e. if there are fewer than 5 words, the SPIR-V code will not compile.) - int elem_index = 4; - // We can remove the Element <id> from the instruction words, and simply call - // the validation code of OpAccessChain. - spv_instruction_t new_inst = *inst; - new_inst.words.erase(new_inst.words.begin() + elem_index); - return isValid<SpvOpAccessChain>(&new_inst, opcodeEntry); -} - -template <> -bool idUsage::isValid<SpvOpInBoundsPtrAccessChain>( - const spv_instruction_t* inst, const spv_opcode_desc opcodeEntry) { - // Has the same validation rules as OpPtrAccessChain - return isValid<SpvOpPtrAccessChain>(inst, opcodeEntry); -} - -template <> bool idUsage::isValid<SpvOpFunction>(const spv_instruction_t* inst, const spv_opcode_desc) { const auto* thisInst = module_.FindDef(inst->words[2u]); @@ -1915,15 +1458,6 @@ bool idUsage::isValid(const spv_instruction_t* inst) { CASE(OpSpecConstantFalse) CASE(OpSpecConstantComposite) CASE(OpSampledImage) - CASE(OpVariable) - CASE(OpLoad) - CASE(OpStore) - CASE(OpCopyMemory) - CASE(OpCopyMemorySized) - CASE(OpAccessChain) - CASE(OpInBoundsAccessChain) - CASE(OpPtrAccessChain) - CASE(OpInBoundsPtrAccessChain) CASE(OpFunction) CASE(OpFunctionParameter) CASE(OpFunctionCall) @@ -1944,102 +1478,6 @@ bool idUsage::isValid(const spv_instruction_t* inst) { #undef CASE } -bool idUsage::AreLayoutCompatibleStructs(const Instruction* type1, - const Instruction* type2) { - if (type1->opcode() != SpvOpTypeStruct) { - return false; - } - if (type2->opcode() != SpvOpTypeStruct) { - return false; - } - - if (!HaveLayoutCompatibleMembers(type1, type2)) return false; - - return HaveSameLayoutDecorations(type1, type2); -} - -bool idUsage::HaveLayoutCompatibleMembers(const Instruction* type1, - const Instruction* type2) { - assert(type1->opcode() == SpvOpTypeStruct && - "type1 must be and OpTypeStruct instruction."); - assert(type2->opcode() == SpvOpTypeStruct && - "type2 must be and OpTypeStruct instruction."); - const auto& type1_operands = type1->operands(); - const auto& type2_operands = type2->operands(); - if (type1_operands.size() != type2_operands.size()) { - return false; - } - - for (size_t operand = 2; operand < type1_operands.size(); ++operand) { - if (type1->word(operand) != type2->word(operand)) { - auto def1 = module_.FindDef(type1->word(operand)); - auto def2 = module_.FindDef(type2->word(operand)); - if (!AreLayoutCompatibleStructs(def1, def2)) { - return false; - } - } - } - return true; -} - -bool idUsage::HaveSameLayoutDecorations(const Instruction* type1, - const Instruction* type2) { - assert(type1->opcode() == SpvOpTypeStruct && - "type1 must be and OpTypeStruct instruction."); - assert(type2->opcode() == SpvOpTypeStruct && - "type2 must be and OpTypeStruct instruction."); - const std::vector<Decoration>& type1_decorations = - module_.id_decorations(type1->id()); - const std::vector<Decoration>& type2_decorations = - module_.id_decorations(type2->id()); - - // TODO: Will have to add other check for arrays an matricies if we want to - // handle them. - if (HasConflictingMemberOffsets(type1_decorations, type2_decorations)) { - return false; - } - - return true; -} - -bool idUsage::HasConflictingMemberOffsets( - const std::vector<Decoration>& type1_decorations, - const std::vector<Decoration>& type2_decorations) const { - { - // We are interested in conflicting decoration. If a decoration is in one - // list but not the other, then we will assume the code is correct. We are - // looking for things we know to be wrong. - // - // We do not have to traverse type2_decoration because, after traversing - // type1_decorations, anything new will not be found in - // type1_decoration. Therefore, it cannot lead to a conflict. - for (const Decoration& decoration : type1_decorations) { - switch (decoration.dec_type()) { - case SpvDecorationOffset: { - // Since these affect the layout of the struct, they must be present - // in both structs. - auto compare = [&decoration](const Decoration& rhs) { - if (rhs.dec_type() != SpvDecorationOffset) return false; - return decoration.struct_member_index() == - rhs.struct_member_index(); - }; - auto i = find_if(type2_decorations.begin(), type2_decorations.end(), - compare); - if (i != type2_decorations.end() && - decoration.params().front() != i->params().front()) { - return true; - } - } break; - default: - // This decoration does not affect the layout of the structure, so - // just moving on. - break; - } - } - } - return false; -} - } // namespace spv_result_t UpdateIdUse(ValidationState_t& _) { diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp new file mode 100644 index 00000000..1a503fd6 --- /dev/null +++ b/source/val/validate_memory.cpp @@ -0,0 +1,600 @@ +// 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. + +#include "source/val/validate.h" + +#include <algorithm> + +#include "source/opcode.h" +#include "source/val/instruction.h" +#include "source/val/validation_state.h" + +namespace spvtools { +namespace val { +namespace { + +bool AreLayoutCompatibleStructs(ValidationState_t&, const Instruction*, + const Instruction*); +bool HaveLayoutCompatibleMembers(ValidationState_t&, const Instruction*, + const Instruction*); +bool HaveSameLayoutDecorations(ValidationState_t&, const Instruction*, + const Instruction*); +bool HasConflictingMemberOffsets(const std::vector<Decoration>&, + const std::vector<Decoration>&); + +// Returns true if the two instructions represent structs that, as far as the +// validator can tell, have the exact same data layout. +bool AreLayoutCompatibleStructs(ValidationState_t& _, const Instruction* type1, + const Instruction* type2) { + if (type1->opcode() != SpvOpTypeStruct) { + return false; + } + if (type2->opcode() != SpvOpTypeStruct) { + return false; + } + + if (!HaveLayoutCompatibleMembers(_, type1, type2)) return false; + + return HaveSameLayoutDecorations(_, type1, type2); +} + +// Returns true if the operands to the OpTypeStruct instruction defining the +// types are the same or are layout compatible types. |type1| and |type2| must +// be OpTypeStruct instructions. +bool HaveLayoutCompatibleMembers(ValidationState_t& _, const Instruction* type1, + const Instruction* type2) { + assert(type1->opcode() == SpvOpTypeStruct && + "type1 must be and OpTypeStruct instruction."); + assert(type2->opcode() == SpvOpTypeStruct && + "type2 must be and OpTypeStruct instruction."); + const auto& type1_operands = type1->operands(); + const auto& type2_operands = type2->operands(); + if (type1_operands.size() != type2_operands.size()) { + return false; + } + + for (size_t operand = 2; operand < type1_operands.size(); ++operand) { + if (type1->word(operand) != type2->word(operand)) { + auto def1 = _.FindDef(type1->word(operand)); + auto def2 = _.FindDef(type2->word(operand)); + if (!AreLayoutCompatibleStructs(_, def1, def2)) { + return false; + } + } + } + return true; +} + +// Returns true if all decorations that affect the data layout of the struct +// (like Offset), are the same for the two types. |type1| and |type2| must be +// OpTypeStruct instructions. +bool HaveSameLayoutDecorations(ValidationState_t& _, const Instruction* type1, + const Instruction* type2) { + assert(type1->opcode() == SpvOpTypeStruct && + "type1 must be and OpTypeStruct instruction."); + assert(type2->opcode() == SpvOpTypeStruct && + "type2 must be and OpTypeStruct instruction."); + const std::vector<Decoration>& type1_decorations = + _.id_decorations(type1->id()); + const std::vector<Decoration>& type2_decorations = + _.id_decorations(type2->id()); + + // TODO: Will have to add other check for arrays an matricies if we want to + // handle them. + if (HasConflictingMemberOffsets(type1_decorations, type2_decorations)) { + return false; + } + + return true; +} + +bool HasConflictingMemberOffsets( + const std::vector<Decoration>& type1_decorations, + const std::vector<Decoration>& type2_decorations) { + { + // We are interested in conflicting decoration. If a decoration is in one + // list but not the other, then we will assume the code is correct. We are + // looking for things we know to be wrong. + // + // We do not have to traverse type2_decoration because, after traversing + // type1_decorations, anything new will not be found in + // type1_decoration. Therefore, it cannot lead to a conflict. + for (const Decoration& decoration : type1_decorations) { + switch (decoration.dec_type()) { + case SpvDecorationOffset: { + // Since these affect the layout of the struct, they must be present + // in both structs. + auto compare = [&decoration](const Decoration& rhs) { + if (rhs.dec_type() != SpvDecorationOffset) return false; + return decoration.struct_member_index() == + rhs.struct_member_index(); + }; + auto i = std::find_if(type2_decorations.begin(), + type2_decorations.end(), compare); + if (i != type2_decorations.end() && + decoration.params().front() != i->params().front()) { + return true; + } + } break; + default: + // This decoration does not affect the layout of the structure, so + // just moving on. + break; + } + } + } + return false; +} + +spv_result_t ValidateVariable(ValidationState_t& _, const Instruction& inst) { + auto result_type = _.FindDef(inst.type_id()); + if (!result_type || result_type->opcode() != SpvOpTypePointer) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpVariable Result Type <id> '" << _.getIdName(inst.type_id()) + << "' is not a pointer type."; + } + + const auto initializer_index = 3; + if (initializer_index < inst.operands().size()) { + const auto initializer_id = inst.GetOperandAs<uint32_t>(initializer_index); + const auto initializer = _.FindDef(initializer_id); + const auto storage_class_index = 2; + const auto is_module_scope_var = + initializer && (initializer->opcode() == SpvOpVariable) && + (initializer->GetOperandAs<uint32_t>(storage_class_index) != + SpvStorageClassFunction); + const auto is_constant = + initializer && spvOpcodeIsConstant(initializer->opcode()); + if (!initializer || !(is_constant || is_module_scope_var)) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpVariable Initializer <id> '" << _.getIdName(initializer_id) + << "' is not a constant or module-scope variable."; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateLoad(ValidationState_t& _, const Instruction& inst) { + const auto result_type = _.FindDef(inst.type_id()); + if (!result_type) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpLoad Result Type <id> '" << _.getIdName(inst.type_id()) + << "' is not defined."; + } + + const bool uses_variable_pointers = + _.features().variable_pointers || + _.features().variable_pointers_storage_buffer; + const auto pointer_index = 2; + const auto pointer_id = inst.GetOperandAs<uint32_t>(pointer_index); + const auto pointer = _.FindDef(pointer_id); + if (!pointer || + ((_.addressing_model() == SpvAddressingModelLogical) && + ((!uses_variable_pointers && + !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || + (uses_variable_pointers && + !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpLoad Pointer <id> '" << _.getIdName(pointer_id) + << "' is not a logical pointer."; + } + + const auto pointer_type = _.FindDef(pointer->type_id()); + if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpLoad type for pointer <id> '" << _.getIdName(pointer_id) + << "' is not a pointer type."; + } + + const auto pointee_type = _.FindDef(pointer_type->GetOperandAs<uint32_t>(2)); + if (!pointee_type || result_type->id() != pointee_type->id()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpLoad Result Type <id> '" << _.getIdName(inst.type_id()) + << "' does not match Pointer <id> '" << _.getIdName(pointer->id()) + << "'s type."; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateStore(ValidationState_t& _, const Instruction& inst) { + const bool uses_variable_pointer = + _.features().variable_pointers || + _.features().variable_pointers_storage_buffer; + const auto pointer_index = 0; + const auto pointer_id = inst.GetOperandAs<uint32_t>(pointer_index); + const auto pointer = _.FindDef(pointer_id); + if (!pointer || + (_.addressing_model() == SpvAddressingModelLogical && + ((!uses_variable_pointer && + !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || + (uses_variable_pointer && + !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "' is not a logical pointer."; + } + const auto pointer_type = _.FindDef(pointer->type_id()); + if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore type for pointer <id> '" << _.getIdName(pointer_id) + << "' is not a pointer type."; + } + const auto type_id = pointer_type->GetOperandAs<uint32_t>(2); + const auto type = _.FindDef(type_id); + if (!type || SpvOpTypeVoid == type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "'s type is void."; + } + + // validate storage class + { + uint32_t data_type; + uint32_t storage_class; + if (!_.GetPointerTypeInfo(pointer_type->id(), &data_type, &storage_class)) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "' is not pointer type"; + } + + if (storage_class == SpvStorageClassUniformConstant || + storage_class == SpvStorageClassInput || + storage_class == SpvStorageClassPushConstant) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "' storage class is read-only"; + } + } + + const auto object_index = 1; + const auto object_id = inst.GetOperandAs<uint32_t>(object_index); + const auto object = _.FindDef(object_id); + if (!object || !object->type_id()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Object <id> '" << _.getIdName(object_id) + << "' is not an object."; + } + const auto object_type = _.FindDef(object->type_id()); + if (!object_type || SpvOpTypeVoid == object_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Object <id> '" << _.getIdName(object_id) + << "'s type is void."; + } + + if (type->id() != object_type->id()) { + if (!_.options()->relax_struct_store || type->opcode() != SpvOpTypeStruct || + object_type->opcode() != SpvOpTypeStruct) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "'s type does not match Object <id> '" + << _.getIdName(object->id()) << "'s type."; + } + + // TODO: Check for layout compatible matricies and arrays as well. + if (!AreLayoutCompatibleStructs(_, type, object_type)) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpStore Pointer <id> '" << _.getIdName(pointer_id) + << "'s layout does not match Object <id> '" + << _.getIdName(object->id()) << "'s layout."; + } + } + return SPV_SUCCESS; +} + +spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction& inst) { + const auto target_index = 0; + const auto target_id = inst.GetOperandAs<uint32_t>(target_index); + const auto target = _.FindDef(target_id); + if (!target) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Target operand <id> '" << _.getIdName(target_id) + << "' is not defined."; + } + + const auto source_index = 1; + const auto source_id = inst.GetOperandAs<uint32_t>(source_index); + const auto source = _.FindDef(source_id); + if (!source) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Source operand <id> '" << _.getIdName(source_id) + << "' is not defined."; + } + + const auto target_pointer_type = _.FindDef(target->type_id()); + assert(target_pointer_type); + const auto target_type = _.FindDef(target_pointer_type->words()[3]); + assert(target_type); + const auto source_pointer_type = _.FindDef(source->type_id()); + assert(source_pointer_type); + const auto source_type = _.FindDef(source_pointer_type->words()[3]); + assert(source_type); + if (target_type->id() != source_type->id()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemory Target <id> '" << _.getIdName(source_id) + << "'s type does not match Source <id> '" + << _.getIdName(source_type->id()) << "'s type."; + } + return SPV_SUCCESS; +} + +spv_result_t ValidateCopyMemorySized(ValidationState_t& _, + const Instruction& inst) { + const auto target_index = 0; + const auto target_id = inst.GetOperandAs<uint32_t>(target_index); + const auto target = _.FindDef(target_id); + if (!target) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Target operand <id> '" << _.getIdName(target_id) + << "' is not defined."; + } + + const auto source_index = 1; + const auto source_id = inst.GetOperandAs<uint32_t>(source_index); + const auto source = _.FindDef(source_id); + if (!source) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Source operand <id> '" << _.getIdName(source_id) + << "' is not defined."; + } + + const auto size_index = 2; + const auto size_id = inst.GetOperandAs<uint32_t>(size_index); + const auto size = _.FindDef(size_id); + if (!size) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Size operand <id> '" << _.getIdName(size_id) + << "' is not defined."; + } + + const auto target_pointer_type = _.FindDef(target->type_id()); + if (!target_pointer_type || + SpvOpTypePointer != target_pointer_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemorySized Target <id> '" << _.getIdName(target_id) + << "' is not a pointer."; + } + const auto source_pointer_type = _.FindDef(source->type_id()); + if (!source_pointer_type || + SpvOpTypePointer != source_pointer_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemorySized Source <id> '" << _.getIdName(source_id) + << "' is not a pointer."; + } + switch (size->opcode()) { + // TODO: The following opcode's are assumed to be valid, refer to the + // following bug https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13871 for + // clarification + case SpvOpConstant: + case SpvOpSpecConstant: { + auto size_type = _.FindDef(size->type_id()); + assert(size_type); + if (SpvOpTypeInt != size_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemorySized Size <id> '" << _.getIdName(size_id) + << "'s type is not an integer type."; + } + } break; + case SpvOpVariable: { + auto pointer_type = _.FindDef(size->type_id()); + assert(pointer_type); + auto size_type = _.FindDef(pointer_type->type_id()); + if (!size_type || SpvOpTypeInt != size_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemorySized Size <id> '" << _.getIdName(size_id) + << "'s variable type is not an integer type."; + } + } break; + default: + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "OpCopyMemorySized Size <id> '" << _.getIdName(size_id) + << "' is not a constant or variable."; + } + // TODO: Check that consant is a least size 1, see the same bug as above for + // clarification? + return SPV_SUCCESS; +} + +spv_result_t ValidateAccessChain(ValidationState_t& _, + const Instruction& inst) { + std::string instr_name = + "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst.opcode()))); + + // The result type must be OpTypePointer. + auto result_type = _.FindDef(inst.type_id()); + if (SpvOpTypePointer != result_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "The Result Type of " << instr_name << " <id> '" + << _.getIdName(inst.id()) << "' must be OpTypePointer. Found Op" + << spvOpcodeString(static_cast<SpvOp>(result_type->opcode())) << "."; + } + + // Result type is a pointer. Find out what it's pointing to. + // This will be used to make sure the indexing results in the same type. + // OpTypePointer word 3 is the type being pointed to. + const auto result_type_pointee = _.FindDef(result_type->word(3)); + + // Base must be a pointer, pointing to the base of a composite object. + const auto base_index = 2; + const auto base_id = inst.GetOperandAs<uint32_t>(base_index); + const auto base = _.FindDef(base_id); + const auto base_type = _.FindDef(base->type_id()); + if (!base_type || SpvOpTypePointer != base_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "The Base <id> '" << _.getIdName(base_id) << "' in " << instr_name + << " instruction must be a pointer."; + } + + // The result pointer storage class and base pointer storage class must match. + // Word 2 of OpTypePointer is the Storage Class. + auto result_type_storage_class = result_type->word(2); + auto base_type_storage_class = base_type->word(2); + if (result_type_storage_class != base_type_storage_class) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "The result pointer storage class and base " + "pointer storage class in " + << instr_name << " do not match."; + } + + // The type pointed to by OpTypePointer (word 3) must be a composite type. + auto type_pointee = _.FindDef(base_type->word(3)); + + // Check Universal Limit (SPIR-V Spec. Section 2.17). + // The number of indexes passed to OpAccessChain may not exceed 255 + // The instruction includes 4 words + N words (for N indexes) + size_t num_indexes = inst.words().size() - 4; + if (inst.opcode() == SpvOpPtrAccessChain || + inst.opcode() == SpvOpInBoundsPtrAccessChain) { + // In pointer access chains, the element operand is required, but not + // counted as an index. + --num_indexes; + } + const size_t num_indexes_limit = + _.options()->universal_limits_.max_access_chain_indexes; + if (num_indexes > num_indexes_limit) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "The number of indexes in " << instr_name << " may not exceed " + << num_indexes_limit << ". Found " << num_indexes << " indexes."; + } + // Indexes walk the type hierarchy to the desired depth, potentially down to + // scalar granularity. The first index in Indexes will select the top-level + // member/element/component/element of the base composite. All composite + // constituents use zero-based numbering, as described by their OpType... + // instruction. The second index will apply similarly to that result, and so + // on. Once any non-composite type is reached, there must be no remaining + // (unused) indexes. + auto starting_index = 4; + if (inst.opcode() == SpvOpPtrAccessChain || + inst.opcode() == SpvOpInBoundsPtrAccessChain) { + ++starting_index; + } + for (size_t i = starting_index; i < inst.words().size(); ++i) { + const uint32_t cur_word = inst.words()[i]; + // Earlier ID checks ensure that cur_word definition exists. + auto cur_word_instr = _.FindDef(cur_word); + // The index must be a scalar integer type (See OpAccessChain in the Spec.) + auto index_type = _.FindDef(cur_word_instr->type_id()); + if (!index_type || SpvOpTypeInt != index_type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << "Indexes passed to " << instr_name + << " must be of type integer."; + } + switch (type_pointee->opcode()) { + case SpvOpTypeMatrix: + case SpvOpTypeVector: + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: { + // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray, + // word 2 is the Element Type. + type_pointee = _.FindDef(type_pointee->word(2)); + break; + } + case SpvOpTypeStruct: { + // In case of structures, there is an additional constraint on the + // index: the index must be an OpConstant. + if (SpvOpConstant != cur_word_instr->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr) + << "The <id> passed to " << instr_name + << " to index into a " + "structure must be an OpConstant."; + } + // Get the index value from the OpConstant (word 3 of OpConstant). + // OpConstant could be a signed integer. But it's okay to treat it as + // unsigned because a negative constant int would never be seen as + // correct as a struct offset, since structs can't have more than 2 + // billion members. + const uint32_t cur_index = cur_word_instr->word(3); + // The index points to the struct member we want, therefore, the index + // should be less than the number of struct members. + const uint32_t num_struct_members = + static_cast<uint32_t>(type_pointee->words().size() - 2); + if (cur_index >= num_struct_members) { + return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr) + << "Index is out of bounds: " << instr_name + << " can not find index " << cur_index + << " into the structure <id> '" + << _.getIdName(type_pointee->id()) << "'. This structure has " + << num_struct_members << " members. Largest valid index is " + << num_struct_members - 1 << "."; + } + // Struct members IDs start at word 2 of OpTypeStruct. + auto structMemberId = type_pointee->word(cur_index + 2); + type_pointee = _.FindDef(structMemberId); + break; + } + default: { + // Give an error. reached non-composite type while indexes still remain. + return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr) + << instr_name + << " reached non-composite type while indexes " + "still remain to be traversed."; + } + } + } + // At this point, we have fully walked down from the base using the indeces. + // The type being pointed to should be the same as the result type. + if (type_pointee->id() != result_type_pointee->id()) { + return _.diag(SPV_ERROR_INVALID_ID, &inst) + << instr_name << " result type (Op" + << spvOpcodeString(static_cast<SpvOp>(result_type_pointee->opcode())) + << ") does not match the type that results from indexing into the " + "base " + "<id> (Op" + << spvOpcodeString(static_cast<SpvOp>(type_pointee->opcode())) + << ")."; + } + + return SPV_SUCCESS; +} + +} // namespace + +spv_result_t ValidateMemoryInstructions(ValidationState_t& _) { + for (auto& inst : _.ordered_instructions()) { + switch (inst.opcode()) { + case SpvOpVariable: + if (auto error = ValidateVariable(_, inst)) return error; + break; + case SpvOpLoad: + if (auto error = ValidateLoad(_, inst)) return error; + break; + case SpvOpStore: + if (auto error = ValidateStore(_, inst)) return error; + break; + case SpvOpCopyMemory: + if (auto error = ValidateCopyMemory(_, inst)) return error; + break; + case SpvOpCopyMemorySized: + if (auto error = ValidateCopyMemorySized(_, inst)) return error; + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + if (auto error = ValidateAccessChain(_, inst)) return error; + break; + case SpvOpImageTexelPointer: + case SpvOpArrayLength: + case SpvOpGenericPtrMemSemantics: + default: + break; + } + } + + return SPV_SUCCESS; +} + +} // namespace val +} // namespace spvtools |