// 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 "Control Flow" section of the // SPIR-V spec. #include #include #include #include #include "gmock/gmock.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" namespace spvtools { namespace { using spvtest::Concatenate; using spvtest::EnumCase; using spvtest::MakeInstruction; using spvtest::TextToBinaryTest; using ::testing::Combine; using ::testing::Eq; using ::testing::TestWithParam; using ::testing::Values; using ::testing::ValuesIn; // Test OpSelectionMerge using OpSelectionMergeTest = spvtest::TextToBinaryTestBase< TestWithParam>>; TEST_P(OpSelectionMergeTest, AnySingleSelectionControlMask) { const std::string input = "OpSelectionMerge %1 " + GetParam().name(); EXPECT_THAT(CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpSelectionMerge, {1, uint32_t(GetParam().value())}))); } // clang-format off #define CASE(VALUE,NAME) { spv::SelectionControlMask::VALUE, NAME} INSTANTIATE_TEST_SUITE_P(TextToBinarySelectionMerge, OpSelectionMergeTest, ValuesIn(std::vector>{ CASE(MaskNone, "None"), CASE(Flatten, "Flatten"), CASE(DontFlatten, "DontFlatten"), })); #undef CASE // clang-format on TEST_F(OpSelectionMergeTest, CombinedSelectionControlMask) { const std::string input = "OpSelectionMerge %1 Flatten|DontFlatten"; const uint32_t expected_mask = uint32_t(spv::SelectionControlMask::Flatten | spv::SelectionControlMask::DontFlatten); EXPECT_THAT( CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpSelectionMerge, {1, expected_mask}))); } TEST_F(OpSelectionMergeTest, WrongSelectionControl) { // Case sensitive: "flatten" != "Flatten" and thus wrong. EXPECT_THAT(CompileFailure("OpSelectionMerge %1 flatten|DontFlatten"), Eq("Invalid selection control operand 'flatten|DontFlatten'.")); } // Test OpLoopMerge using OpLoopMergeTest = spvtest::TextToBinaryTestBase< TestWithParam>>>; TEST_P(OpLoopMergeTest, AnySingleLoopControlMask) { const auto ctrl = std::get<1>(GetParam()); std::ostringstream input; input << "OpLoopMerge %merge %continue " << ctrl.name(); for (auto num : ctrl.operands()) input << " " << num; EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())), Eq(MakeInstruction(spv::Op::OpLoopMerge, {1, 2, ctrl.value()}, ctrl.operands()))); } #define CASE(VALUE, NAME) \ { int32_t(spv::LoopControlMask::VALUE), NAME } #define CASE1(VALUE, NAME, PARM) \ { \ int32_t(spv::LoopControlMask::VALUE), NAME, { PARM } \ } INSTANTIATE_TEST_SUITE_P( TextToBinaryLoopMerge, OpLoopMergeTest, Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), ValuesIn(std::vector>{ // clang-format off CASE(MaskNone, "None"), CASE(Unroll, "Unroll"), CASE(DontUnroll, "DontUnroll"), // clang-format on }))); INSTANTIATE_TEST_SUITE_P( TextToBinaryLoopMergeV11, OpLoopMergeTest, Combine(Values(SPV_ENV_UNIVERSAL_1_1), ValuesIn(std::vector>{ // clang-format off CASE(DependencyInfinite, "DependencyInfinite"), CASE1(DependencyLength, "DependencyLength", 234), {int32_t(spv::LoopControlMask::Unroll|spv::LoopControlMask::DependencyLength), "DependencyLength|Unroll", {33}}, // clang-format on }))); #undef CASE #undef CASE1 TEST_F(OpLoopMergeTest, CombinedLoopControlMask) { const std::string input = "OpLoopMerge %merge %continue Unroll|DontUnroll"; const uint32_t expected_mask = uint32_t(spv::LoopControlMask::Unroll | spv::LoopControlMask::DontUnroll); EXPECT_THAT(CompiledInstructions(input), Eq(MakeInstruction(spv::Op::OpLoopMerge, {1, 2, expected_mask}))); } TEST_F(OpLoopMergeTest, WrongLoopControl) { EXPECT_THAT(CompileFailure("OpLoopMerge %m %c none"), Eq("Invalid loop control operand 'none'.")); } // Test OpSwitch TEST_F(TextToBinaryTest, SwitchGoodZeroTargets) { EXPECT_THAT(CompiledInstructions("OpSwitch %selector %default"), Eq(MakeInstruction(spv::Op::OpSwitch, {1, 2}))); } TEST_F(TextToBinaryTest, SwitchGoodOneTarget) { EXPECT_THAT( CompiledInstructions("%1 = OpTypeInt 32 0\n" "%2 = OpConstant %1 52\n" "OpSwitch %2 %default 12 %target0"), Eq(Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}), MakeInstruction(spv::Op::OpConstant, {1, 2, 52}), MakeInstruction(spv::Op::OpSwitch, {2, 3, 12, 4})}))); } TEST_F(TextToBinaryTest, SwitchGoodTwoTargets) { EXPECT_THAT( CompiledInstructions("%1 = OpTypeInt 32 0\n" "%2 = OpConstant %1 52\n" "OpSwitch %2 %default 12 %target0 42 %target1"), Eq(Concatenate({ MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}), MakeInstruction(spv::Op::OpConstant, {1, 2, 52}), MakeInstruction(spv::Op::OpSwitch, {2, 3, 12, 4, 42, 5}), }))); } TEST_F(TextToBinaryTest, SwitchBadMissingSelector) { EXPECT_THAT(CompileFailure("OpSwitch"), Eq("Expected operand for OpSwitch instruction, but found the end " "of the stream.")); } TEST_F(TextToBinaryTest, SwitchBadInvalidSelector) { EXPECT_THAT(CompileFailure("OpSwitch 12"), Eq("Expected id to start with %.")); } TEST_F(TextToBinaryTest, SwitchBadMissingDefault) { EXPECT_THAT(CompileFailure("OpSwitch %selector"), Eq("Expected operand for OpSwitch instruction, but found the end " "of the stream.")); } TEST_F(TextToBinaryTest, SwitchBadInvalidDefault) { EXPECT_THAT(CompileFailure("OpSwitch %selector 12"), Eq("Expected id to start with %.")); } TEST_F(TextToBinaryTest, SwitchBadInvalidLiteral) { // The assembler recognizes "OpSwitch %selector %default" as a complete // instruction. Then it tries to parse "%abc" as the start of a new // instruction, but can't since it hits the end of stream. const auto input = R"(%i32 = OpTypeInt 32 0 %selector = OpConstant %i32 42 OpSwitch %selector %default %abc)"; EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream.")); } TEST_F(TextToBinaryTest, SwitchBadMissingTarget) { EXPECT_THAT(CompileFailure("%1 = OpTypeInt 32 0\n" "%2 = OpConstant %1 52\n" "OpSwitch %2 %default 12"), Eq("Expected operand for OpSwitch instruction, but found the end " "of the stream.")); } // A test case for an OpSwitch. // It is also parameterized to test encodings OpConstant // integer literals. This can capture both single and multi-word // integer literal tests. struct SwitchTestCase { std::string constant_type_args; std::string constant_value_arg; std::string case_value_arg; std::vector expected_instructions; }; using OpSwitchValidTest = spvtest::TextToBinaryTestBase>; // Tests the encoding of OpConstant literal values, and also // the literal integer cases in an OpSwitch. This can // test both single and multi-word integer literal encodings. TEST_P(OpSwitchValidTest, ValidTypes) { const std::string input = "%1 = OpTypeInt " + GetParam().constant_type_args + "\n" "%2 = OpConstant %1 " + GetParam().constant_value_arg + "\n" "OpSwitch %2 %default " + GetParam().case_value_arg + " %4\n"; std::vector instructions; EXPECT_THAT(CompiledInstructions(input), Eq(GetParam().expected_instructions)); } // Constructs a SwitchTestCase from the given integer_width, signedness, // constant value string, and expected encoded constant. SwitchTestCase MakeSwitchTestCase(uint32_t integer_width, uint32_t integer_signedness, std::string constant_str, std::vector encoded_constant, std::string case_value_str, std::vector encoded_case_value) { std::stringstream ss; ss << integer_width << " " << integer_signedness; return SwitchTestCase{ ss.str(), constant_str, case_value_str, {Concatenate( {MakeInstruction(spv::Op::OpTypeInt, {1, integer_width, integer_signedness}), MakeInstruction(spv::Op::OpConstant, Concatenate({{1, 2}, encoded_constant})), MakeInstruction(spv::Op::OpSwitch, Concatenate({{2, 3}, encoded_case_value, {4}}))})}}; } INSTANTIATE_TEST_SUITE_P( TextToBinaryOpSwitchValid1Word, OpSwitchValidTest, ValuesIn(std::vector({ MakeSwitchTestCase(32, 0, "42", {42}, "100", {100}), MakeSwitchTestCase(32, 1, "-1", {0xffffffff}, "100", {100}), // SPIR-V 1.0 Rev 1 clarified that for an integer narrower than 32-bits, // its bits will appear in the lower order bits of the 32-bit word, and // a signed integer is sign-extended. MakeSwitchTestCase(7, 0, "127", {127}, "100", {100}), MakeSwitchTestCase(14, 0, "99", {99}, "100", {100}), MakeSwitchTestCase(16, 0, "65535", {65535}, "100", {100}), MakeSwitchTestCase(16, 1, "101", {101}, "100", {100}), // Demonstrate sign extension MakeSwitchTestCase(16, 1, "-2", {0xfffffffe}, "100", {100}), // Hex cases MakeSwitchTestCase(16, 1, "0x7ffe", {0x7ffe}, "0x1234", {0x1234}), MakeSwitchTestCase(16, 1, "0x8000", {0xffff8000}, "0x8100", {0xffff8100}), MakeSwitchTestCase(16, 0, "0x8000", {0x00008000}, "0x8100", {0x8100}), }))); // NB: The words LOW ORDER bits show up first. INSTANTIATE_TEST_SUITE_P( TextToBinaryOpSwitchValid2Words, OpSwitchValidTest, ValuesIn(std::vector({ MakeSwitchTestCase(33, 0, "101", {101, 0}, "500", {500, 0}), MakeSwitchTestCase(48, 1, "-1", {0xffffffff, 0xffffffff}, "900", {900, 0}), MakeSwitchTestCase(64, 1, "-2", {0xfffffffe, 0xffffffff}, "-5", {0xfffffffb, uint32_t(-1)}), // Hex cases MakeSwitchTestCase(48, 1, "0x7fffffffffff", {0xffffffff, 0x00007fff}, "100", {100, 0}), MakeSwitchTestCase(48, 1, "0x800000000000", {0x00000000, 0xffff8000}, "0x800000000000", {0x00000000, 0xffff8000}), MakeSwitchTestCase(48, 0, "0x800000000000", {0x00000000, 0x00008000}, "0x800000000000", {0x00000000, 0x00008000}), MakeSwitchTestCase(63, 0, "0x500000000", {0, 5}, "12", {12, 0}), MakeSwitchTestCase(64, 0, "0x600000000", {0, 6}, "12", {12, 0}), MakeSwitchTestCase(64, 1, "0x700000123", {0x123, 7}, "12", {12, 0}), }))); using ControlFlowRoundTripTest = RoundTripTest; TEST_P(ControlFlowRoundTripTest, DisassemblyEqualsAssemblyInput) { const std::string assembly = GetParam(); EXPECT_THAT(EncodeAndDecodeSuccessfully(assembly), Eq(assembly)) << assembly; } INSTANTIATE_TEST_SUITE_P( OpSwitchRoundTripUnsignedIntegers, ControlFlowRoundTripTest, ValuesIn(std::vector({ // Unsigned 16-bit. "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 65535\nOpSwitch %2 %3\n", // Unsigned 32-bit, three non-default cases. "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 123456\n" "OpSwitch %2 %3 100 %4 102 %5 1000000 %6\n", // Unsigned 48-bit, three non-default cases. "%1 = OpTypeInt 48 0\n%2 = OpConstant %1 5000000000\n" "OpSwitch %2 %3 100 %4 102 %5 6000000000 %6\n", // Unsigned 64-bit, three non-default cases. "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 9223372036854775807\n" "OpSwitch %2 %3 100 %4 102 %5 9000000000000000000 %6\n", }))); INSTANTIATE_TEST_SUITE_P( OpSwitchRoundTripSignedIntegers, ControlFlowRoundTripTest, ValuesIn(std::vector{ // Signed 16-bit, with two non-default cases "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 32767\n" "OpSwitch %2 %3 99 %4 -102 %5\n", "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 -32768\n" "OpSwitch %2 %3 99 %4 -102 %5\n", // Signed 32-bit, two non-default cases. "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 -123456\n" "OpSwitch %2 %3 100 %4 -123456 %5\n", "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 123456\n" "OpSwitch %2 %3 100 %4 123456 %5\n", // Signed 48-bit, three non-default cases. "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 5000000000\n" "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n", "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 -5000000000\n" "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n", // Signed 64-bit, three non-default cases. "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 9223372036854775807\n" "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n", "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n" "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n", })); using OpSwitchInvalidTypeTestCase = spvtest::TextToBinaryTestBase>; TEST_P(OpSwitchInvalidTypeTestCase, InvalidTypes) { const std::string input = "%1 = " + GetParam() + "\n" "%3 = OpCopyObject %1 %2\n" // We only care the type of the expression " OpSwitch %3 %default 32 %c\n"; EXPECT_THAT(CompileFailure(input), Eq("The selector operand for OpSwitch must be the result of an " "instruction that generates an integer scalar")); } // clang-format off INSTANTIATE_TEST_SUITE_P( TextToBinaryOpSwitchInvalidTests, OpSwitchInvalidTypeTestCase, ValuesIn(std::vector{ {"OpTypeVoid", "OpTypeBool", "OpTypeFloat 32", "OpTypeVector %a 32", "OpTypeMatrix %a 32", "OpTypeImage %a 1D 0 0 0 0 Unknown", "OpTypeSampler", "OpTypeSampledImage %a", "OpTypeArray %a %b", "OpTypeRuntimeArray %a", "OpTypeStruct %a", "OpTypeOpaque \"Foo\"", "OpTypePointer UniformConstant %a", "OpTypeFunction %a %b", "OpTypeEvent", "OpTypeDeviceEvent", "OpTypeReserveId", "OpTypeQueue", "OpTypePipe ReadOnly", // Skip OpTypeForwardPointer because it doesn't even produce a result // ID. // At least one thing that isn't a type at all "OpNot %a %b" }, })); // clang-format on using OpKillTest = spvtest::TextToBinaryTest; INSTANTIATE_TEST_SUITE_P(OpKillTest, ControlFlowRoundTripTest, Values("OpKill\n")); TEST_F(OpKillTest, ExtraArgsAssemblyError) { const std::string input = "OpKill 1"; EXPECT_THAT(CompileFailure(input), Eq("Expected or at the beginning of an " "instruction, found '1'.")); } using OpTerminateInvocationTest = spvtest::TextToBinaryTest; INSTANTIATE_TEST_SUITE_P(OpTerminateInvocationTest, ControlFlowRoundTripTest, Values("OpTerminateInvocation\n")); TEST_F(OpTerminateInvocationTest, ExtraArgsAssemblyError) { const std::string input = "OpTerminateInvocation 1"; EXPECT_THAT(CompileFailure(input), Eq("Expected or at the beginning of an " "instruction, found '1'.")); } // TODO(dneto): OpPhi // TODO(dneto): OpLoopMerge // TODO(dneto): OpLabel // TODO(dneto): OpBranch // TODO(dneto): OpSwitch // TODO(dneto): OpReturn // TODO(dneto): OpReturnValue // TODO(dneto): OpUnreachable // TODO(dneto): OpLifetimeStart // TODO(dneto): OpLifetimeStop } // namespace } // namespace spvtools