diff --git a/source/validate_id.cpp b/source/validate_id.cpp index 1ab7327a2..34a5b7a87 100644 --- a/source/validate_id.cpp +++ b/source/validate_id.cpp @@ -1612,17 +1612,205 @@ bool idUsage::isValid( const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} #endif -#if 0 -template <> -bool idUsage::isValid( - const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} -#endif +// Walks the composite type hierarchy starting from the base. +// At each step, the iterator is dereferenced to get the next literal index. +// 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. +// Returns true on success and false otherwise. +// If successful, the final type reached by indexing is returned by reference. +// If an error occurs, the error string is returned by reference. +bool walkCompositeTypeHierarchy( + const ValidationState_t& module, + std::vector::const_iterator word_iter, + std::vector::const_iterator word_iter_end, + const libspirv::Instruction* base, + const libspirv::Instruction** result_type_instr, + std::function instr_name, std::ostream* error) { + auto cur_type = base; + for (; word_iter != word_iter_end; ++word_iter) { + switch (cur_type->opcode()) { + case SpvOpTypeMatrix: + case SpvOpTypeVector: + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: { + // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray, + // word 2 is the Element Type. + cur_type = module.FindDef(cur_type->word(2)); + break; + } + case SpvOpTypeStruct: { + // Get the index into the structure. + const uint32_t cur_index = *word_iter; + // 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(cur_type->words().size() - 2); + if (cur_index >= num_struct_members) { + *error << "Index is out of bound: " << instr_name() + << " can not find index " << cur_index + << " into the structure '" << cur_type->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 = cur_type->word(cur_index + 2); + cur_type = module.FindDef(structMemberId); + break; + } + default: { + // Give an error. reached non-composite type while indexes still remain. + *error << instr_name() << " reached non-composite type while indexes " + "still remain to be traversed."; + return false; + } + } + } + *result_type_instr = cur_type; + return true; +} -#if 0 template <> -bool idUsage::isValid( - const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} -#endif +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto instr_name = [&inst]() { + std::string name = + "Op" + std::string(spvOpcodeString(static_cast(inst->opcode))); + return name; + }; + + // Remember the result type. Result Type is at word 1. + // This will be used to make sure the indexing results in the same type. + const size_t resultTypeIndex = 1; + auto resultTypeInstr = module_.FindDef(inst->words[resultTypeIndex]); + + // The Composite is at word 3. ID definition checks ensure this id is + // already defined. + auto baseInstr = module_.FindDef(inst->words[3]); + auto curTypeInstr = module_.FindDef(baseInstr->type_id()); + + // Check Universal Limit (SPIR-V Spec. Section 2.17). + // The number of indexes passed to OpCompositeExtract 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 = 255; + if (num_indexes > num_indexes_limit) { + DIAG(resultTypeIndex) << "The number of indexes in " << instr_name() + << " may not exceed " << num_indexes_limit + << ". Found " << num_indexes << " indexes."; + return false; + } + if (num_indexes <= 0) { + DIAG(resultTypeIndex) << "No Indexes were passed to " << instr_name() + << "."; + return false; + } + + // Walk down the composite type structure. Indexes start at word 4. + const libspirv::Instruction* indexedTypeInstr = nullptr; + std::ostringstream error; + bool success = walkCompositeTypeHierarchy( + module_, inst->words.begin() + 4, inst->words.end(), curTypeInstr, + &indexedTypeInstr, instr_name, &error); + if (!success) { + DIAG(resultTypeIndex) << error.str(); + return success; + } + + // At this point, we have fully walked down from the base using the indexes. + // The type being pointed to should be the same as the result type. + if (indexedTypeInstr->id() != resultTypeInstr->id()) { + DIAG(resultTypeIndex) + << instr_name() << " result type (Op" + << spvOpcodeString(static_cast(resultTypeInstr->opcode())) + << ") does not match the type that results from indexing into the " + "composite (Op" + << spvOpcodeString(static_cast(indexedTypeInstr->opcode())) + << ")."; + return false; + } + + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto instr_name = [&inst]() { + std::string name = + "Op" + std::string(spvOpcodeString(static_cast(inst->opcode))); + return name; + }; + + // Result Type must be the same as Composite type. Result Type is the + // word at index 1. Composite is at word 4. + // The grammar guarantees that the instruction has at least 5 words. + // ID definition checks ensure these IDs are already defined. + const size_t resultTypeIndex = 1; + const size_t compositeIndex = 4; + auto resultTypeInstr = module_.FindDef(inst->words[resultTypeIndex]); + auto compositeInstr = module_.FindDef(inst->words[compositeIndex]); + auto compositeTypeInstr = module_.FindDef(compositeInstr->type_id()); + if (resultTypeInstr != compositeTypeInstr) { + DIAG(resultTypeIndex) + << "The Result Type must be the same as Composite type in " + << instr_name() << "."; + return false; + } + + // Check Universal Limit (SPIR-V Spec. Section 2.17). + // The number of indexes passed to OpCompositeInsert may not exceed 255. + // The instruction includes 5 words + N words (for N indexes) + const size_t num_indexes = inst->words.size() - 5; + const size_t num_indexes_limit = 255; + if (num_indexes > num_indexes_limit) { + DIAG(resultTypeIndex) << "The number of indexes in " << instr_name() + << " may not exceed " << num_indexes_limit + << ". Found " << num_indexes << " indexes."; + return false; + } + if (num_indexes <= 0) { + DIAG(resultTypeIndex) << "No Indexes were passed to " << instr_name() + << "."; + return false; + } + + // Walk the composite type structure. Indexes start at word 5. + const libspirv::Instruction* indexedTypeInstr = nullptr; + std::ostringstream error; + bool success = walkCompositeTypeHierarchy( + module_, inst->words.begin() + 5, inst->words.end(), compositeTypeInstr, + &indexedTypeInstr, instr_name, &error); + if (!success) { + DIAG(resultTypeIndex) << error.str(); + return success; + } + + // At this point, we have fully walked down from the base using the indexes. + // The type being pointed to should be the same as the object type that is + // about to be inserted. + auto objectIdIndex = 3; + auto objectInstr = module_.FindDef(inst->words[objectIdIndex]); + auto objectTypeInstr = module_.FindDef(objectInstr->type_id()); + if (indexedTypeInstr->id() != objectTypeInstr->id()) { + DIAG(objectIdIndex) + << "The Object type (Op" + << spvOpcodeString(static_cast(objectTypeInstr->opcode())) + << ") in " << instr_name() << " does not match the type that results " + "from indexing into the Composite (Op" + << spvOpcodeString(static_cast(indexedTypeInstr->opcode())) + << ")."; + return false; + } + + return true; +} #if 0 template <> @@ -2619,8 +2807,8 @@ bool idUsage::isValid(const spv_instruction_t* inst) { TODO(OpVectorInsertDynamic) TODO(OpVectorShuffle) TODO(OpCompositeConstruct) - TODO(OpCompositeExtract) - TODO(OpCompositeInsert) + CASE(OpCompositeExtract) + CASE(OpCompositeInsert) TODO(OpCopyObject) TODO(OpTranspose) TODO(OpSNegate) diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp index bc26b3332..fa58ea51d 100644 --- a/test/val/val_id_test.cpp +++ b/test/val/val_id_test.cpp @@ -1837,7 +1837,7 @@ TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) { EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); } -string opAccessChainSpirvSetup = R"( +const char kDeeplyNestedStructureSetup[] = R"( %void = OpTypeVoid %void_f = OpTypeFunction %void %int = OpTypeInt 32 0 @@ -1855,7 +1855,7 @@ string opAccessChainSpirvSetup = R"( %int_3 = OpConstant %int 3 %int_5 = OpConstant %int 5 -; Let's make the following structures to test OpAccessChain +; Making the following nested structures. ; ; struct S { ; bool b; @@ -1905,7 +1905,7 @@ bool AccessChainRequiresElemId(const std::string& instr) { TEST_P(AccessChainInstructionTest, AccessChainGood) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + "%float_entry = " + instr + R"( %_ptr_Private_float %my_matrix )" + elem + R"(%int_0 %int_1 OpReturn @@ -1919,7 +1919,7 @@ TEST_P(AccessChainInstructionTest, AccessChainGood) { TEST_P(AccessChainInstructionTest, AccessChainResultTypeBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %float_entry = )" + instr + R"( %float %my_matrix )" + elem + R"(%int_0 %int_1 OpReturn @@ -1938,7 +1938,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainBaseTypeVoidBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %float_entry = )" + instr + " %_ptr_Private_float %void " + elem + R"(%int_0 %int_1 OpReturn @@ -1956,7 +1956,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainBaseTypeNonPtrVariableBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_float %_ptr_Private_float )" + elem + R"(%int_0 %int_1 @@ -1976,7 +1976,7 @@ TEST_P(AccessChainInstructionTest, AccessChainResultAndBaseStorageClassDoesntMatchBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Function_float %my_matrix )" + elem + R"(%int_0 %int_1 @@ -1997,7 +1997,7 @@ TEST_P(AccessChainInstructionTest, AccessChainBasePtrNotPointingToCompositeBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_float %my_float_var )" + elem + R"(%int_0 OpReturn @@ -2015,7 +2015,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainMissingIndexesBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_float %my_float_var )" + elem + R"( OpReturn @@ -2032,7 +2032,7 @@ TEST_P(AccessChainInstructionTest, AccessChainTooManyIndexesGood) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; int depth = 255; - std::string header = kGLSL450MemoryModel + opAccessChainSpirvSetup; + std::string header = kGLSL450MemoryModel + kDeeplyNestedStructureSetup; header.erase(header.find("%func")); std::ostringstream spirv; spirv << header << "\n"; @@ -2074,7 +2074,7 @@ TEST_P(AccessChainInstructionTest, AccessChainTooManyIndexesBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; std::ostringstream spirv; - spirv << kGLSL450MemoryModel << opAccessChainSpirvSetup; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup; spirv << "%entry = " << instr << " %_ptr_Private_float %my_matrix" << elem; for (int i = 0; i < 256; ++i) { spirv << " %int_0"; @@ -2095,7 +2095,7 @@ TEST_P(AccessChainInstructionTest, AccessChainTooManyIndexesBad) { TEST_P(AccessChainInstructionTest, AccessChainUndefinedIndexBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_float %my_matrix )" + elem + R"(%float %int_1 OpReturn @@ -2113,7 +2113,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainStructIndexNotConstantBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %f = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + R"(%int_0 %spec_int %int_2 OpReturn @@ -2132,7 +2132,7 @@ TEST_P(AccessChainInstructionTest, AccessChainStructResultTypeDoesntMatchIndexedTypeBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + R"(%int_0 %int_1 %int_2 @@ -2152,7 +2152,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainStructTooManyIndexesBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + R"(%int_0 %int_2 %int_2 @@ -2171,7 +2171,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainStructIndexOutOfBoundBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + R"(%int_3 %int_2 %int_2 @@ -2198,7 +2198,7 @@ TEST_P(AccessChainInstructionTest, AccessChainIndexIntoAllTypesGood) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; ostringstream spirv; - spirv << kGLSL450MemoryModel << opAccessChainSpirvSetup << std::endl; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; spirv << "%ss = " << instr << " %_ptr_Uniform_struct_s %blockName_var " << elem << "%int_0" << std::endl; spirv << "%sa = " << instr << " %_ptr_Uniform_array5_mat4x3 %blockName_var " @@ -2221,7 +2221,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainIndexIntoRuntimeArrayGood) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %runtime_arr_entry = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + @@ -2237,7 +2237,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainIndexIntoRuntimeArrayBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %runtime_arr_entry = )" + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + @@ -2258,7 +2258,7 @@ OpFunctionEnd TEST_P(AccessChainInstructionTest, AccessChainMatrixMoreArgsThanNeededBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_float %my_matrix )" + elem + R"(%int_0 %int_1 %int_0 @@ -2278,7 +2278,7 @@ TEST_P(AccessChainInstructionTest, AccessChainResultTypeDoesntMatchIndexedTypeBad) { const std::string instr = GetParam(); const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; - string spirv = kGLSL450MemoryModel + opAccessChainSpirvSetup + R"( + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( %entry = )" + instr + R"( %_ptr_Private_mat4x3 %my_matrix )" + elem + R"(%int_0 %int_1 @@ -2531,6 +2531,327 @@ OpFunctionEnd)"; "as an operand of '24'.")); } +// Valid: Get a float in a matrix using CompositeExtract. +// Valid: Insert float into a matrix using CompositeInsert. +TEST_F(ValidateIdWithMessage, CompositeExtractInsertGood) { + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix 0 1" << std::endl; + + // To test CompositeInsert, insert the object back in after extraction. + spirv << "%new_composite = OpCompositeInsert %mat4x3 %float_entry %matrix 0 1" + << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Valid. Tests both CompositeExtract and CompositeInsert with 255 indexes. +TEST_F(ValidateIdWithMessage, CompositeExtractInsertLimitsGood) { + int depth = 255; + std::string header = kGLSL450MemoryModel + kDeeplyNestedStructureSetup; + header.erase(header.find("%func")); + std::ostringstream spirv; + spirv << header << std::endl; + + // Build nested structures. Struct 'i' contains struct 'i-1' + spirv << "%s_depth_1 = OpTypeStruct %float\n"; + for (int i = 2; i <= depth; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %s_depth_" << i - 1 << "\n"; + } + + // Define Pointer and Variable to use for CompositeExtract/Insert. + spirv << "%_ptr_Uniform_deep_struct = OpTypePointer Uniform %s_depth_" + << depth << "\n"; + spirv << "%deep_var = OpVariable %_ptr_Uniform_deep_struct Uniform\n"; + + // Function Start + spirv << R"( + %func = OpFunction %void None %void_f + %my_label = OpLabel + )"; + + // OpCompositeExtract/Insert with 'n' indexes (n = depth) + spirv << "%deep = OpLoad %s_depth_" << depth << " %deep_var" << std::endl; + spirv << "%entry = OpCompositeExtract %float %deep"; + for (int i = 0; i < depth; ++i) { + spirv << " 0"; + } + spirv << std::endl; + spirv << "%new_composite = OpCompositeInsert %s_depth_" << depth + << " %entry %deep"; + for (int i = 0; i < depth; ++i) { + spirv << " 0"; + } + spirv << std::endl; + + // Function end + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: 256 indexes passed to OpCompositeExtract. Limit is 255. +TEST_F(ValidateIdWithMessage, CompositeExtractArgCountExceededLimitBad) { + std::ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%entry = OpCompositeExtract %float %matrix"; + for (int i = 0; i < 256; ++i) { + spirv << " 0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The number of indexes in OpCompositeExtract may not " + "exceed 255. Found 256 indexes.")); +} + +// Invalid: 256 indexes passed to OpCompositeInsert. Limit is 255. +TEST_F(ValidateIdWithMessage, CompositeInsertArgCountExceededLimitBad) { + std::ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%new_composite = OpCompositeInsert %mat4x3 %int_0 %matrix"; + for (int i = 0; i < 256; ++i) { + spirv << " 0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The number of indexes in OpCompositeInsert may not " + "exceed 255. Found 256 indexes.")); +} + +// Invalid: In OpCompositeInsert, result type must be the same as composite type +TEST_F(ValidateIdWithMessage, CompositeInsertWrongResultTypeBad) { + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix 0 1" << std::endl; + spirv << "%new_composite = OpCompositeInsert %float %float_entry %matrix 0 1" + << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Result Type must be the same as Composite type")); +} + +// Invalid: No Indexes were passed to OpCompositeExtract. +TEST_F(ValidateIdWithMessage, CompositeExtractMissingIndexesBad) { + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix" << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("No Indexes were passed to OpCompositeExtract")); +} + +// Invalid: No Indexes were passed to OpCompositeInsert. +TEST_F(ValidateIdWithMessage, CompositeInsertMissingIndexesBad) { + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%new_composite = OpCompositeInsert %mat4x3 %int_0 %matrix"; + spirv << R"( + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("No Indexes were passed to OpCompositeInsert")); +} + +// Valid: Tests that we can index into Struct, Array, Matrix, and Vector! +TEST_F(ValidateIdWithMessage, CompositeExtractInsertIndexIntoAllTypesGood) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 0 + %sa = OpCompositeExtract %array5_mat4x3 %myblock 0 3 + %sm = OpCompositeExtract %mat4x3 %myblock 0 3 1 + %sc = OpCompositeExtract %v3float %myblock 0 3 1 2 + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 + ; + ; Now let's insert back at different levels... + ; + %b1 = OpCompositeInsert %struct_blockName %ss %myblock 0 + %b2 = OpCompositeInsert %struct_blockName %sa %myblock 0 3 + %b3 = OpCompositeInsert %struct_blockName %sm %myblock 0 3 1 + %b4 = OpCompositeInsert %struct_blockName %sc %myblock 0 3 1 2 + %b5 = OpCompositeInsert %struct_blockName %fl %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid. More indexes are provided than needed for OpCompositeExtract. +TEST_F(ValidateIdWithMessage, CompositeExtractReachedScalarBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 1 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract reached non-composite type while " + "indexes still remain to be traversed.")); +} + +// Invalid. More indexes are provided than needed for OpCompositeInsert. +TEST_F(ValidateIdWithMessage, CompositeInsertReachedScalarBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 + %b5 = OpCompositeInsert %struct_blockName %fl %myblock 0 3 1 2 0 1 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeInsert reached non-composite type while " + "indexes still remain to be traversed.")); +} + +// Invalid. Result type doesn't match the type we get from indexing into +// the composite. +TEST_F(ValidateIdWithMessage, + CompositeExtractResultTypeDoesntMatchIndexedTypeBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %int %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeInt) does not " + "match the type that results from indexing into the " + "composite (OpTypeFloat).")); +} + +// Invalid. Given object type doesn't match the type we get from indexing into +// the composite. +TEST_F(ValidateIdWithMessage, + CompositeInsertObjectTypeDoesntMatchIndexedTypeBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + // We are trying to insert an integer where we should be inserting a float. + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %b5 = OpCompositeInsert %struct_blockName %int_0 %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("he Object type (OpTypeInt) in OpCompositeInsert does " + "not match the type that results from indexing into " + "the Composite (OpTypeFloat).")); +} + +// Invalid. Index into a struct is larger than the number of struct members. +TEST_F(ValidateIdWithMessage, CompositeExtractStructIndexOutOfBoundBad) { + // struct_blockName has 3 members (index 0,1,2). We'll try to access index 3. + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index is out of bound: OpCompositeExtract can not " + "find index 3 into the structure '26'. This " + "structure has 3 members. Largest valid index is 2.")); +} + +// Invalid. Index into a struct is larger than the number of struct members. +TEST_F(ValidateIdWithMessage, CompositeInsertStructIndexOutOfBoundBad) { + // struct_blockName has 3 members (index 0,1,2). We'll try to access index 3. + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 0 + %new_composite = OpCompositeInsert %struct_blockName %ss %myblock 3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index is out of bound: OpCompositeInsert can not find " + "index 3 into the structure '26'. This structure " + "has 3 members. Largest valid index is 2.")); +} + #if 0 TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentCountBar) { const char *spirv = R"(