diff --git a/Android.mk b/Android.mk index 5f5c756634..f9e2360fff 100644 --- a/Android.mk +++ b/Android.mk @@ -53,6 +53,7 @@ SPVTOOLS_SRC_FILES := \ source/val/validate_debug.cpp \ source/val/validate_decorations.cpp \ source/val/validate_derivatives.cpp \ + source/val/validate_dot_product.cpp \ source/val/validate_extensions.cpp \ source/val/validate_execution_limitations.cpp \ source/val/validate_function.cpp \ diff --git a/BUILD.gn b/BUILD.gn index 08ca5fef25..7e0aa44de6 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -346,6 +346,7 @@ static_library("spvtools_val") { "source/val/validate_debug.cpp", "source/val/validate_decorations.cpp", "source/val/validate_derivatives.cpp", + "source/val/validate_dot_product.cpp", "source/val/validate_execution_limitations.cpp", "source/val/validate_extensions.cpp", "source/val/validate_function.cpp", diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index fd7fa28ca5..f82981781d 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -260,6 +260,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_debug.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_decorations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_derivatives.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_dot_product.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_extensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_execution_limitations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_function.cpp diff --git a/source/val/validate.cpp b/source/val/validate.cpp index 1141968f98..ecc6b80034 100644 --- a/source/val/validate.cpp +++ b/source/val/validate.cpp @@ -390,6 +390,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( if (auto error = AtomicsPass(*vstate, &instruction)) return error; if (auto error = PrimitivesPass(*vstate, &instruction)) return error; if (auto error = BarriersPass(*vstate, &instruction)) return error; + if (auto error = DotProductPass(*vstate, &instruction)) return error; if (auto error = GroupPass(*vstate, &instruction)) return error; // Device-Side Enqueue // Pipe diff --git a/source/val/validate.h b/source/val/validate.h index 1c2328ec3b..10025d9749 100644 --- a/source/val/validate.h +++ b/source/val/validate.h @@ -180,6 +180,9 @@ spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst); /// Validates correctness of barrier instructions. spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst); +/// Validates correctness of DotProduct instructions. +spv_result_t DotProductPass(ValidationState_t& _, const Instruction* inst); + /// Validates correctness of Group (Kernel) instructions. spv_result_t GroupPass(ValidationState_t& _, const Instruction* inst); diff --git a/source/val/validate_dot_product.cpp b/source/val/validate_dot_product.cpp new file mode 100644 index 0000000000..21cc4be247 --- /dev/null +++ b/source/val/validate_dot_product.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2026 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "source/val/instruction.h" +#include "source/val/validate.h" +#include "source/val/validate_scopes.h" +#include "source/val/validation_state.h" + +namespace spvtools { +namespace val { +namespace { + +spv_result_t ValidateSameSignedDot(ValidationState_t& _, + const Instruction* inst) { + const uint32_t result_id = inst->type_id(); + if (!_.IsIntScalarType(result_id)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Result must be an int scalar type."; + } + + const spv::Op opcode = inst->opcode(); + const bool has_accumulator = opcode == spv::Op::OpSDotAccSat || + opcode == spv::Op::OpUDotAccSat || + opcode == spv::Op::OpSUDotAccSat; + if (has_accumulator) { + const uint32_t accumulator_type = _.GetOperandTypeId(inst, 4); + if (accumulator_type != result_id) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Result must be the same as the Accumulator type."; + } + } + + if (opcode == spv::Op::OpUDot || opcode == spv::Op::OpUDotAccSat) { + if (!_.IsIntScalarTypeWithSignedness(result_id, 0)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Result must be an unsigned int scalar type."; + } + } + + const uint32_t vec_1_id = _.GetOperandTypeId(inst, 2); + const uint32_t vec_2_id = _.GetOperandTypeId(inst, 3); + + const bool is_vec_1_scalar = _.IsIntScalarType(vec_1_id, 32); + const bool is_vec_2_scalar = _.IsIntScalarType(vec_2_id, 32); + if (is_vec_1_scalar != is_vec_2_scalar) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "'Vector 1' and 'Vector 2' must be the same type."; + } else if (is_vec_1_scalar && is_vec_2_scalar) { + // If both are scalar, spec doesn't say Signedness needs to match + const uint32_t vec_1_width = _.GetBitWidth(vec_1_id); + const uint32_t vec_2_width = _.GetBitWidth(vec_2_id); + if (vec_1_width != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 1' to be 32-bit when a scalar."; + } else if (vec_2_width != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 2' to be 32-bit when a scalar."; + } + + // When packed, the result can be 8-bit + const uint32_t result_width = _.GetBitWidth(result_id); + if (result_width < 8) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Result width (" << result_width + << ") must be greater than or equal to the packed vector width of " + "8"; + } + + // PackedVectorFormat4x8Bit is used when the "Vector" operand are really + // scalar + const uint32_t packed_operand = has_accumulator ? 6 : 5; + const bool has_packed_vec_format = + inst->operands().size() == packed_operand; + if (!has_packed_vec_format) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "'Vector 1' and 'Vector 2' are a 32-bit int scalar, but no " + "Packed Vector " + "Format was provided."; + } + } else { + // both should be vectors + + if (!_.IsVectorType(vec_1_id)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 1' to be an int scalar or vector."; + } else if (!_.IsVectorType(vec_2_id)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 2' to be an int scalar or vector."; + } + + const uint32_t vec_1_length = _.GetDimension(vec_1_id); + const uint32_t vec_2_length = _.GetDimension(vec_2_id); + // If using OpTypeVectorIdEXT with a spec constant, this can be evaluated + // when spec constants are frozen + if (vec_1_length != 0 && vec_2_length != 0 && + vec_1_length != vec_2_length) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "'Vector 1' is " << vec_1_length + << " components but 'Vector 2' is " << vec_2_length + << " components"; + } + + const uint32_t vec_1_type = _.GetComponentType(vec_1_id); + const uint32_t vec_2_type = _.GetComponentType(vec_2_id); + if (!_.IsIntScalarType(vec_1_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 1' to be a vector of integers."; + } else if (!_.IsIntScalarType(vec_2_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 2' to be a vector of integers."; + } + + const uint32_t vec_1_width = _.GetBitWidth(vec_1_type); + const uint32_t vec_2_width = _.GetBitWidth(vec_2_type); + if (vec_1_width != vec_2_width) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "'Vector 1' component is " << vec_1_width + << "-bit but 'Vector 2' component is " << vec_2_width << "-bit"; + } + + const uint32_t result_width = _.GetBitWidth(result_id); + if (result_width < vec_1_width) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Result width (" << result_width + << ") must be greater than or equal to the vectors width (" + << vec_1_width << ")."; + } + + if (opcode == spv::Op::OpUDot || opcode == spv::Op::OpUDotAccSat) { + const bool vec_1_unsigned = + _.IsIntScalarTypeWithSignedness(vec_1_type, 0); + const bool vec_2_unsigned = + _.IsIntScalarTypeWithSignedness(vec_2_type, 0); + if (!vec_1_unsigned) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 1' to be an vector of unsigned integers."; + } else if (!vec_2_unsigned) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 2' to be an vector of unsigned integers."; + } + } else if (opcode == spv::Op::OpSUDot || opcode == spv::Op::OpSUDotAccSat) { + const bool vec_2_unsigned = + _.IsIntScalarTypeWithSignedness(vec_2_type, 0); + if (!vec_2_unsigned) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 'Vector 2' to be an vector of unsigned integers."; + } + } + } + + return SPV_SUCCESS; +} + +} // namespace + +spv_result_t DotProductPass(ValidationState_t& _, const Instruction* inst) { + const spv::Op opcode = inst->opcode(); + + switch (opcode) { + case spv::Op::OpSDot: + case spv::Op::OpUDot: + case spv::Op::OpSUDot: + case spv::Op::OpSDotAccSat: + case spv::Op::OpUDotAccSat: + case spv::Op::OpSUDotAccSat: + return ValidateSameSignedDot(_, inst); + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace val +} // namespace spvtools diff --git a/test/val/val_extension_spv_khr_integer_dot_product_test.cpp b/test/val/val_extension_spv_khr_integer_dot_product_test.cpp index 5b3a309612..df5df125ce 100644 --- a/test/val/val_extension_spv_khr_integer_dot_product_test.cpp +++ b/test/val/val_extension_spv_khr_integer_dot_product_test.cpp @@ -18,9 +18,7 @@ #include #include "gmock/gmock.h" -#include "source/extensions.h" -#include "source/spirv_target_env.h" -#include "test/unit_spirv.h" +#include "spirv-tools/libspirv.h" #include "test/val/val_fixtures.h" namespace spvtools { @@ -739,7 +737,7 @@ INSTANTIATE_TEST_SUITE_P( "%uchar", // match width "%v4uchar", "%v4uchar", - "%uint", + "%uchar", false, ""}, Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"}, @@ -1247,80 +1245,249 @@ INSTANTIATE_TEST_SUITE_P( true, ""})); -using ValidateSpvKHRIntegerDotProductSimple = ::testing::Test; +using ValidateIntegerDotProductSimple = spvtest::ValidateBase; + +TEST_F(ValidateIntegerDotProductSimple, RequiresExtension) { + const std::string ss = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%fn = OpTypeFunction %void +%int = OpTypeInt 32 1 +%v2int = OpTypeVector %int 2 +%int_2 = OpConstant %int 2 +%vec_a = OpConstantComposite %v2int %int_2 %int_2 +%vec_b = OpConstantComposite %v2int %int_2 %int_2 +%main = OpFunction %void None %fn +%label = OpLabel +%x = OpSDot %int %vec_a %vec_b +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(ss); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Opcode SDot requires one of these capabilities: DotProduct")); +} + +std::string GenerateShaderCode(const std::string& body) { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability DotProductKHR +OpCapability Int16 +OpCapability Int64 +OpCapability LongVectorEXT +OpExtension "SPV_EXT_long_vector" +OpExtension "SPV_KHR_integer_dot_product" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %spec_5 SpecId 0 +OpDecorate %spec_6 SpecId 1 + +%void = OpTypeVoid +%fn = OpTypeFunction %void + +%float = OpTypeFloat 32 +%int16 = OpTypeInt 16 1 +%uint16 = OpTypeInt 16 0 +%uint = OpTypeInt 32 0 +%int = OpTypeInt 32 1 +%int64 = OpTypeInt 64 1 +%v2int = OpTypeVector %int 2 +%v3int = OpTypeVector %int 3 +%v2uint = OpTypeVector %uint 2 +%v2int16 = OpTypeVector %int16 2 +%v2uint16 = OpTypeVector %uint16 2 +%v2int64 = OpTypeVector %int64 2 + +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%uint_1 = OpConstant %uint 1 +%int64_1 = OpConstant %int64 1 +%int16_1 = OpConstant %int16 1 +%uint16_1 = OpConstant %uint16 1 -TEST(ValidateSpvKHRIntegerDotProductSimple, DISABLED_RequiresExtension) { - FAIL(); +%ivec2_1 = OpConstantComposite %v2int %int_1 %int_1 +%ivec3_1 = OpConstantComposite %v3int %int_1 %int_1 %int_1 +%uvec2_1 = OpConstantComposite %v2uint %uint_1 %uint_1 +%i16vec2_1 = OpConstantComposite %v2int16 %int16_1 %int16_1 +%u16vec2_1 = OpConstantComposite %v2uint16 %uint16_1 %uint16_1 +%i64vec2_1 = OpConstantComposite %v2int64 %int64_1 %int64_1 + +%uint_5 = OpConstant %uint 5 +%uint_6 = OpConstant %uint 6 +%spec_5 = OpSpecConstant %uint 5 +%spec_6 = OpSpecConstant %uint 6 + +%uvec5 = OpTypeVectorIdEXT %uint %uint_5 +%uvec6 = OpTypeVectorIdEXT %uint %uint_6 +%spec_uvec5 = OpTypeVectorIdEXT %uint %spec_5 +%spec_uvec6 = OpTypeVectorIdEXT %uint %spec_6 + +%uvec5_1 = OpConstantComposite %uvec5 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 +%uvec6_1 = OpConstantComposite %uvec6 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 +%spec_uvec5_1 = OpConstantComposite %spec_uvec5 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 +%spec_uvec6_1 = OpConstantComposite %spec_uvec6 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 + +%main = OpFunction %void None %fn +%label = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + return ss.str(); } -TEST(ValidateSpvKHRIntegerDotProductSimple, DISABLED_Invalid_ResultTooNarrow) { - // Test across all the instructions. - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, Dot16BitGood) { + const std::string ss = R"( + %x = OpSDot %int %i16vec2_1 %i16vec2_1 + %y = OpUDot %uint %u16vec2_1 %u16vec2_1 + %z = OpSUDot %uint %i16vec2_1 %u16vec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_UDot_OperandTypesMatch) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotDifferentVectors) { + const std::string ss = R"( + %x = OpSDot %int %ivec2_1 %ivec3_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("'Vector 1' is 2 components but 'Vector 2' is 3 components")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SDot_OperandTypesMatchExceptSignedness) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotFloat) { + const std::string ss = R"( + %x = OpSDot %int %float_1 %float_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected 'Vector 1' to be an int scalar or vector")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SUDot_OperandTypesMatchExceptSignedness) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDot16BitScalar) { + const std::string ss = R"( + %x = OpSDot %int %int16_1 %int16_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected 'Vector 1' to be an int scalar or vector")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_UDotAccSat_OperandTypesMatch) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotResultVector) { + const std::string ss = R"( + %x = OpSDot %v2int %ivec2_1 %ivec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result must be an int scalar type")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SDotAccSat_OperandTypesMatchExceptSignedness) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotResultSmall) { + const std::string ss = R"( + %x = OpSDot %int %i64vec2_1 %i64vec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result width (32) must be greater than or equal to " + "the vectors width (64)")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SUDotAccSat_OperandTypesMatchExceptSignedness) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotNoPackedVectorFormat) { + const std::string ss = R"( + %x = OpSDot %int %int_1 %int_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("'Vector 1' and 'Vector 2' are a 32-bit int scalar, " + "but no Packed Vector Format was provided")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_UDot_RequiresUnsigned) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, UDotResultSigned) { + const std::string ss = R"( + %x = OpUDot %int %uvec2_1 %uvec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result must be an unsigned int scalar type")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SUDot_RequiresUnsignedSecondArg) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, UDotVectorSigned) { + const std::string ss = R"( + %x = OpUDot %uint %ivec2_1 %ivec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected 'Vector 1' to be an vector of unsigned integers")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_UDotAccSat_RequiresUnsigned) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SUDotVectorSigned) { + const std::string ss = R"( + %x = OpSUDot %uint %ivec2_1 %ivec2_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected 'Vector 2' to be an vector of unsigned integers")); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_SUDotAccSat_RequiresUnsignedSecondArg) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotLongVectorGood) { + const std::string ss = R"( + %x = OpSDot %uint %uvec5_1 %uvec5_1 + %y = OpSDot %uint %uvec6_1 %uvec6_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_VectorOperandsDisallowPackedFormat) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotLongVectorSpec) { + const std::string ss = R"( + %x = OpSDot %uint %spec_uvec5_1 %spec_uvec6_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } -TEST(ValidateSpvKHRIntegerDotProductSimple, - DISABLED_Invalid_ScalarOperandsRequirePackedFormat) { - FAIL(); +TEST_F(ValidateIntegerDotProductSimple, SDotLongVector) { + const std::string ss = R"( + %x = OpSDot %uint %uvec5_1 %uvec6_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("'Vector 1' is 5 components but 'Vector 2' is 6 components")); } -// TODO(dneto): Test valid cases with other scalar integer types -// TODO(dneto): Test valid cases of length-8 vectors -// TODO(dneto): Test valid cases of length-16 vectors +TEST_F(ValidateIntegerDotProductSimple, UDotAccSat) { + const std::string ss = R"( + %x = OpUDotAccSat %uint %uvec2_1 %uvec2_1 %int_1 + )"; + CompileSuccessfully(GenerateShaderCode(ss)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result must be the same as the Accumulator type")); +} } // namespace } // namespace val