// Copyright (c) 2015-2016 The Khronos Group 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. // Assembler tests for instructions in the "Annotation" section of the // SPIR-V spec. #include #include #include #include #include "gmock/gmock.h" #include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" namespace spvtools { namespace { using spvtest::EnumCase; using spvtest::MakeInstruction; using utils::MakeVector; using spvtest::TextToBinaryTest; using ::testing::Combine; using ::testing::Eq; using ::testing::Values; using ::testing::ValuesIn; // Test OpDecorate using OpDecorateSimpleTest = spvtest::TextToBinaryTestBase<::testing::TestWithParam< std::tuple>>>; TEST_P(OpDecorateSimpleTest, AnySimpleDecoration) { // This string should assemble, but should not validate. std::stringstream input; input << "OpDecorate %1 " << std::get<1>(GetParam()).name(); for (auto operand : std::get<1>(GetParam()).operands()) input << " " << operand; input << std::endl; EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())), Eq(MakeInstruction(spv::Op::OpDecorate, {1, uint32_t(std::get<1>(GetParam()).value())}, std::get<1>(GetParam()).operands()))); // Also check disassembly. EXPECT_THAT( EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE, std::get<0>(GetParam())), Eq(input.str())); } // Like above, but parameters to the decoration are IDs. using OpDecorateSimpleIdTest = spvtest::TextToBinaryTestBase<::testing::TestWithParam< std::tuple>>>; TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) { // This string should assemble, but should not validate. std::stringstream input; input << "OpDecorateId %1 " << std::get<1>(GetParam()).name(); for (auto operand : std::get<1>(GetParam()).operands()) input << " %" << operand; input << std::endl; EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())), Eq(MakeInstruction(spv::Op::OpDecorateId, {1, uint32_t(std::get<1>(GetParam()).value())}, std::get<1>(GetParam()).operands()))); // Also check disassembly. EXPECT_THAT( EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE, std::get<0>(GetParam())), Eq(input.str())); } #define CASE(NAME) spv::Decoration::NAME, #NAME INSTANTIATE_TEST_SUITE_P( TextToBinaryDecorateSimple, OpDecorateSimpleTest, Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), ValuesIn(std::vector>{ // The operand literal values are arbitrarily chosen, // but there are the right number of them. {CASE(RelaxedPrecision), {}}, {CASE(SpecId), {100}}, {CASE(Block), {}}, {CASE(BufferBlock), {}}, {CASE(RowMajor), {}}, {CASE(ColMajor), {}}, {CASE(ArrayStride), {4}}, {CASE(MatrixStride), {16}}, {CASE(GLSLShared), {}}, {CASE(GLSLPacked), {}}, {CASE(CPacked), {}}, // Placeholder line for enum value 12 {CASE(NoPerspective), {}}, {CASE(Flat), {}}, {CASE(Patch), {}}, {CASE(Centroid), {}}, {CASE(Sample), {}}, {CASE(Invariant), {}}, {CASE(Restrict), {}}, {CASE(Aliased), {}}, {CASE(Volatile), {}}, {CASE(Constant), {}}, {CASE(Coherent), {}}, {CASE(NonWritable), {}}, {CASE(NonReadable), {}}, {CASE(Uniform), {}}, {CASE(SaturatedConversion), {}}, {CASE(Stream), {2}}, {CASE(Location), {6}}, {CASE(Component), {3}}, {CASE(Index), {14}}, {CASE(Binding), {19}}, {CASE(DescriptorSet), {7}}, {CASE(Offset), {12}}, {CASE(XfbBuffer), {1}}, {CASE(XfbStride), {8}}, {CASE(NoContraction), {}}, {CASE(InputAttachmentIndex), {102}}, {CASE(Alignment), {16}}, }))); INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest, Combine(Values(SPV_ENV_UNIVERSAL_1_1), Values(EnumCase{ CASE(MaxByteOffset), {128}}))); INSTANTIATE_TEST_SUITE_P( TextToBinaryDecorateSimpleV14, OpDecorateSimpleTest, Combine(Values(SPV_ENV_UNIVERSAL_1_4), ValuesIn(std::vector>{ {CASE(Uniform), {}}, }))); INSTANTIATE_TEST_SUITE_P( TextToBinaryDecorateSimpleIdV14, OpDecorateSimpleIdTest, Combine(Values(SPV_ENV_UNIVERSAL_1_4), ValuesIn(std::vector>{ // In 1.4, UniformId decoration takes a // scope Id. {CASE(UniformId), {1}}, }))); #undef CASE TEST_F(OpDecorateSimpleTest, WrongDecoration) { EXPECT_THAT(CompileFailure("OpDecorate %1 xxyyzz"), Eq("Invalid decoration 'xxyyzz'.")); } TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) { EXPECT_THAT(CompileFailure("OpDecorate %1 RelaxedPrecision 99"), Eq("Expected or at the beginning of an " "instruction, found '99'.")); } TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) { EXPECT_THAT(CompileFailure("OpDecorate %1 SpecId 99 100"), Eq("Expected or at the beginning of an " "instruction, found '100'.")); } TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) { EXPECT_THAT( CompileFailure("OpDecorate %1 LinkageAttributes \"abc\" Import 42"), Eq("Expected or at the beginning of an " "instruction, found '42'.")); } // A single test case for an enum decoration. struct DecorateEnumCase { // Place the enum value first, so it's easier to read the binary dumps when // the test fails. uint32_t value; // The value within the enum, e.g. Position std::string name; uint32_t enum_value; // Which enum, e.g. BuiltIn std::string enum_name; }; using OpDecorateEnumTest = spvtest::TextToBinaryTestBase<::testing::TestWithParam>; TEST_P(OpDecorateEnumTest, AnyEnumDecoration) { // This string should assemble, but should not validate. const std::string input = "OpDecorate %1 " + GetParam().enum_name + " " + GetParam().name; EXPECT_THAT(CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpDecorate, {1, GetParam().enum_value, GetParam().value}))); } // Test OpDecorate BuiltIn. // clang-format off #define CASE(NAME) \ { uint32_t(spv::BuiltIn::NAME), #NAME, uint32_t(spv::Decoration::BuiltIn), "BuiltIn" } INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest, ::testing::ValuesIn(std::vector{ CASE(Position), CASE(PointSize), CASE(ClipDistance), CASE(CullDistance), CASE(VertexId), CASE(InstanceId), CASE(PrimitiveId), CASE(InvocationId), CASE(Layer), CASE(ViewportIndex), CASE(TessLevelOuter), CASE(TessLevelInner), CASE(TessCoord), CASE(PatchVertices), CASE(FragCoord), CASE(PointCoord), CASE(FrontFacing), CASE(SampleId), CASE(SamplePosition), CASE(SampleMask), // Value 21 intentionally missing. CASE(FragDepth), CASE(HelperInvocation), CASE(NumWorkgroups), CASE(WorkgroupSize), CASE(WorkgroupId), CASE(LocalInvocationId), CASE(GlobalInvocationId), CASE(LocalInvocationIndex), CASE(WorkDim), CASE(GlobalSize), CASE(EnqueuedWorkgroupSize), CASE(GlobalOffset), CASE(GlobalLinearId), // Value 35 intentionally missing. CASE(SubgroupSize), CASE(SubgroupMaxSize), CASE(NumSubgroups), CASE(NumEnqueuedSubgroups), CASE(SubgroupId), CASE(SubgroupLocalInvocationId), CASE(VertexIndex), CASE(InstanceIndex), })); #undef CASE // clang-format on TEST_F(OpDecorateEnumTest, WrongBuiltIn) { EXPECT_THAT(CompileFailure("OpDecorate %1 BuiltIn xxyyzz"), Eq("Invalid built-in 'xxyyzz'.")); } // Test OpDecorate FuncParamAttr // clang-format off #define CASE(NAME) \ { uint32_t(spv::FunctionParameterAttribute::NAME), #NAME, uint32_t(spv::Decoration::FuncParamAttr), "FuncParamAttr" } INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest, ::testing::ValuesIn(std::vector{ CASE(Zext), CASE(Sext), CASE(ByVal), CASE(Sret), CASE(NoAlias), CASE(NoCapture), CASE(NoWrite), CASE(NoReadWrite), })); #undef CASE // clang-format on TEST_F(OpDecorateEnumTest, WrongFuncParamAttr) { EXPECT_THAT(CompileFailure("OpDecorate %1 FuncParamAttr xxyyzz"), Eq("Invalid function parameter attribute 'xxyyzz'.")); } // Test OpDecorate FPRoundingMode // clang-format off #define CASE(NAME) \ { uint32_t(spv::FPRoundingMode::NAME), #NAME, uint32_t(spv::Decoration::FPRoundingMode), "FPRoundingMode" } INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest, ::testing::ValuesIn(std::vector{ CASE(RTE), CASE(RTZ), CASE(RTP), CASE(RTN), })); #undef CASE // clang-format on TEST_F(OpDecorateEnumTest, WrongFPRoundingMode) { EXPECT_THAT(CompileFailure("OpDecorate %1 FPRoundingMode xxyyzz"), Eq("Invalid floating-point rounding mode 'xxyyzz'.")); } // Test OpDecorate FPFastMathMode. // These can by named enums for the single-bit masks. However, we don't support // symbolic combinations of the masks. Rather, they can use ! // syntax, e.g. !0x3 // clang-format off #define CASE(ENUM,NAME) \ { uint32_t(spv::FPFastMathModeMask::ENUM), #NAME, uint32_t(spv::Decoration::FPFastMathMode), "FPFastMathMode" } INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest, ::testing::ValuesIn(std::vector{ CASE(MaskNone, None), CASE(NotNaN, NotNaN), CASE(NotInf, NotInf), CASE(NSZ, NSZ), CASE(AllowRecip, AllowRecip), CASE(Fast, Fast), })); #undef CASE // clang-format on TEST_F(OpDecorateEnumTest, CombinedFPFastMathMask) { // Sample a single combination. This ensures we've integrated // the instruction parsing logic with spvTextParseMask. const std::string input = "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ"; const uint32_t expected_enum = uint32_t(spv::Decoration::FPFastMathMode); const uint32_t expected_mask = uint32_t(spv::FPFastMathModeMask::NotNaN) | uint32_t(spv::FPFastMathModeMask::NotInf) | uint32_t(spv::FPFastMathModeMask::NSZ); EXPECT_THAT(CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpDecorate, {1, expected_enum, expected_mask}))); } TEST_F(OpDecorateEnumTest, WrongFPFastMathMode) { EXPECT_THAT( CompileFailure("OpDecorate %1 FPFastMathMode NotNaN|xxyyzz"), Eq("Invalid floating-point fast math mode operand 'NotNaN|xxyyzz'.")); } // Test OpDecorate Linkage // A single test case for a linkage struct DecorateLinkageCase { uint32_t linkage_type_value; std::string linkage_type_name; std::string external_name; }; using OpDecorateLinkageTest = spvtest::TextToBinaryTestBase< ::testing::TestWithParam>; TEST_P(OpDecorateLinkageTest, AnyLinkageDecoration) { // This string should assemble, but should not validate. const std::string input = "OpDecorate %1 LinkageAttributes \"" + GetParam().external_name + "\" " + GetParam().linkage_type_name; std::vector expected_operands{ 1, uint32_t(spv::Decoration::LinkageAttributes)}; std::vector encoded_external_name = MakeVector(GetParam().external_name); expected_operands.insert(expected_operands.end(), encoded_external_name.begin(), encoded_external_name.end()); expected_operands.push_back(GetParam().linkage_type_value); EXPECT_THAT(CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpDecorate, expected_operands))); } // clang-format off #define CASE(ENUM) uint32_t(spv::LinkageType::ENUM), #ENUM INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest, ::testing::ValuesIn(std::vector{ { CASE(Import), "a" }, { CASE(Export), "foo" }, { CASE(Import), "some kind of long name with spaces etc." }, // TODO(dneto): utf-8, escaping, quoting cases. })); #undef CASE // clang-format on TEST_F(OpDecorateLinkageTest, WrongType) { EXPECT_THAT(CompileFailure("OpDecorate %1 LinkageAttributes \"foo\" xxyyzz"), Eq("Invalid linkage type 'xxyyzz'.")); } // Test OpGroupMemberDecorate TEST_F(TextToBinaryTest, GroupMemberDecorateGoodOneTarget) { EXPECT_THAT(CompiledInstructions("OpGroupMemberDecorate %group %id0 42"), Eq(MakeInstruction(spv::Op::OpGroupMemberDecorate, {1, 2, 42}))); } TEST_F(TextToBinaryTest, GroupMemberDecorateGoodTwoTargets) { EXPECT_THAT( CompiledInstructions("OpGroupMemberDecorate %group %id0 96 %id1 42"), Eq(MakeInstruction(spv::Op::OpGroupMemberDecorate, {1, 2, 96, 3, 42}))); } TEST_F(TextToBinaryTest, GroupMemberDecorateMissingGroupId) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate"), Eq("Expected operand for OpGroupMemberDecorate instruction, but " "found the end of the stream.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidGroupId) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate 16"), Eq("Expected id to start with %.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetId) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group 12"), Eq("Expected id to start with %.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateMissingTargetMemberNumber) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0"), Eq("Expected operand for OpGroupMemberDecorate instruction, but " "found the end of the stream.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetMemberNumber) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 %id1"), Eq("Invalid unsigned integer literal: %id1")); } TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetId) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id1 42 12"), Eq("Expected id to start with %.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateMissingSecondTargetMemberNumber) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1"), Eq("Expected operand for OpGroupMemberDecorate instruction, but " "found the end of the stream.")); } TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetMemberNumber) { EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1 %id2"), Eq("Invalid unsigned integer literal: %id2")); } // Test OpMemberDecorate using OpMemberDecorateSimpleTest = spvtest::TextToBinaryTestBase<::testing::TestWithParam< std::tuple>>>; TEST_P(OpMemberDecorateSimpleTest, AnySimpleDecoration) { // This string should assemble, but should not validate. std::stringstream input; input << "OpMemberDecorate %1 42 " << std::get<1>(GetParam()).name(); for (auto operand : std::get<1>(GetParam()).operands()) input << " " << operand; input << std::endl; EXPECT_THAT( CompiledInstructions(input.str(), std::get<0>(GetParam())), Eq(MakeInstruction(spv::Op::OpMemberDecorate, {1, 42, uint32_t(std::get<1>(GetParam()).value())}, std::get<1>(GetParam()).operands()))); // Also check disassembly. EXPECT_THAT( EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE, std::get<0>(GetParam())), Eq(input.str())); } #define CASE(NAME) spv::Decoration::NAME, #NAME INSTANTIATE_TEST_SUITE_P( TextToBinaryDecorateSimple, OpMemberDecorateSimpleTest, Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), ValuesIn(std::vector>{ // The operand literal values are arbitrarily chosen, // but there are the right number of them. {CASE(RelaxedPrecision), {}}, {CASE(SpecId), {100}}, {CASE(Block), {}}, {CASE(BufferBlock), {}}, {CASE(RowMajor), {}}, {CASE(ColMajor), {}}, {CASE(ArrayStride), {4}}, {CASE(MatrixStride), {16}}, {CASE(GLSLShared), {}}, {CASE(GLSLPacked), {}}, {CASE(CPacked), {}}, // Placeholder line for enum value 12 {CASE(NoPerspective), {}}, {CASE(Flat), {}}, {CASE(Patch), {}}, {CASE(Centroid), {}}, {CASE(Sample), {}}, {CASE(Invariant), {}}, {CASE(Restrict), {}}, {CASE(Aliased), {}}, {CASE(Volatile), {}}, {CASE(Constant), {}}, {CASE(Coherent), {}}, {CASE(NonWritable), {}}, {CASE(NonReadable), {}}, {CASE(Uniform), {}}, {CASE(SaturatedConversion), {}}, {CASE(Stream), {2}}, {CASE(Location), {6}}, {CASE(Component), {3}}, {CASE(Index), {14}}, {CASE(Binding), {19}}, {CASE(DescriptorSet), {7}}, {CASE(Offset), {12}}, {CASE(XfbBuffer), {1}}, {CASE(XfbStride), {8}}, {CASE(NoContraction), {}}, {CASE(InputAttachmentIndex), {102}}, {CASE(Alignment), {16}}, }))); INSTANTIATE_TEST_SUITE_P( TextToBinaryDecorateSimpleV11, OpMemberDecorateSimpleTest, Combine(Values(SPV_ENV_UNIVERSAL_1_1), Values(EnumCase{CASE(MaxByteOffset), {128}}))); #undef CASE TEST_F(OpMemberDecorateSimpleTest, WrongDecoration) { EXPECT_THAT(CompileFailure("OpMemberDecorate %1 9 xxyyzz"), Eq("Invalid decoration 'xxyyzz'.")); } TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) { EXPECT_THAT(CompileFailure("OpMemberDecorate %1 12 RelaxedPrecision 99"), Eq("Expected or at the beginning of an " "instruction, found '99'.")); } TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) { EXPECT_THAT(CompileFailure("OpMemberDecorate %1 0 SpecId 99 100"), Eq("Expected or at the beginning of an " "instruction, found '100'.")); } TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) { EXPECT_THAT(CompileFailure( "OpMemberDecorate %1 1 LinkageAttributes \"abc\" Import 42"), Eq("Expected or at the beginning of an " "instruction, found '42'.")); } // TODO(dneto): OpMemberDecorate cases for decorations with parameters which // are: not just lists of literal numbers. // TODO(dneto): OpDecorationGroup // TODO(dneto): OpGroupDecorate } // namespace } // namespace spvtools