1 // Copyright (c) 2015-2016 The Khronos Group Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Assembler tests for instructions in the "Control Flow" section of the
16 // SPIR-V spec.
17
18 #include <sstream>
19 #include <string>
20 #include <tuple>
21 #include <vector>
22
23 #include "gmock/gmock.h"
24 #include "test/test_fixture.h"
25 #include "test/unit_spirv.h"
26
27 namespace spvtools {
28 namespace {
29
30 using spvtest::Concatenate;
31 using spvtest::EnumCase;
32 using spvtest::MakeInstruction;
33 using spvtest::TextToBinaryTest;
34 using ::testing::Combine;
35 using ::testing::Eq;
36 using ::testing::TestWithParam;
37 using ::testing::Values;
38 using ::testing::ValuesIn;
39
40 // Test OpSelectionMerge
41
42 using OpSelectionMergeTest = spvtest::TextToBinaryTestBase<
43 TestWithParam<EnumCase<SpvSelectionControlMask>>>;
44
TEST_P(OpSelectionMergeTest,AnySingleSelectionControlMask)45 TEST_P(OpSelectionMergeTest, AnySingleSelectionControlMask) {
46 const std::string input = "OpSelectionMerge %1 " + GetParam().name();
47 EXPECT_THAT(
48 CompiledInstructions(input),
49 Eq(MakeInstruction(SpvOpSelectionMerge, {1, GetParam().value()})));
50 }
51
52 // clang-format off
53 #define CASE(VALUE,NAME) { SpvSelectionControl##VALUE, NAME}
54 INSTANTIATE_TEST_SUITE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
55 ValuesIn(std::vector<EnumCase<SpvSelectionControlMask>>{
56 CASE(MaskNone, "None"),
57 CASE(FlattenMask, "Flatten"),
58 CASE(DontFlattenMask, "DontFlatten"),
59 }));
60 #undef CASE
61 // clang-format on
62
TEST_F(OpSelectionMergeTest,CombinedSelectionControlMask)63 TEST_F(OpSelectionMergeTest, CombinedSelectionControlMask) {
64 const std::string input = "OpSelectionMerge %1 Flatten|DontFlatten";
65 const uint32_t expected_mask =
66 SpvSelectionControlFlattenMask | SpvSelectionControlDontFlattenMask;
67 EXPECT_THAT(CompiledInstructions(input),
68 Eq(MakeInstruction(SpvOpSelectionMerge, {1, expected_mask})));
69 }
70
TEST_F(OpSelectionMergeTest,WrongSelectionControl)71 TEST_F(OpSelectionMergeTest, WrongSelectionControl) {
72 // Case sensitive: "flatten" != "Flatten" and thus wrong.
73 EXPECT_THAT(CompileFailure("OpSelectionMerge %1 flatten|DontFlatten"),
74 Eq("Invalid selection control operand 'flatten|DontFlatten'."));
75 }
76
77 // Test OpLoopMerge
78
79 using OpLoopMergeTest = spvtest::TextToBinaryTestBase<
80 TestWithParam<std::tuple<spv_target_env, EnumCase<int>>>>;
81
TEST_P(OpLoopMergeTest,AnySingleLoopControlMask)82 TEST_P(OpLoopMergeTest, AnySingleLoopControlMask) {
83 const auto ctrl = std::get<1>(GetParam());
84 std::ostringstream input;
85 input << "OpLoopMerge %merge %continue " << ctrl.name();
86 for (auto num : ctrl.operands()) input << " " << num;
87 EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
88 Eq(MakeInstruction(SpvOpLoopMerge, {1, 2, ctrl.value()},
89 ctrl.operands())));
90 }
91
92 #define CASE(VALUE, NAME) \
93 { SpvLoopControl##VALUE, NAME }
94 #define CASE1(VALUE, NAME, PARM) \
95 { \
96 SpvLoopControl##VALUE, NAME, { PARM } \
97 }
98 INSTANTIATE_TEST_SUITE_P(
99 TextToBinaryLoopMerge, OpLoopMergeTest,
100 Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
101 ValuesIn(std::vector<EnumCase<int>>{
102 // clang-format off
103 CASE(MaskNone, "None"),
104 CASE(UnrollMask, "Unroll"),
105 CASE(DontUnrollMask, "DontUnroll"),
106 // clang-format on
107 })));
108
109 INSTANTIATE_TEST_SUITE_P(
110 TextToBinaryLoopMergeV11, OpLoopMergeTest,
111 Combine(Values(SPV_ENV_UNIVERSAL_1_1),
112 ValuesIn(std::vector<EnumCase<int>>{
113 // clang-format off
114 CASE(DependencyInfiniteMask, "DependencyInfinite"),
115 CASE1(DependencyLengthMask, "DependencyLength", 234),
116 {SpvLoopControlUnrollMask|SpvLoopControlDependencyLengthMask,
117 "DependencyLength|Unroll", {33}},
118 // clang-format on
119 })));
120 #undef CASE
121 #undef CASE1
122
TEST_F(OpLoopMergeTest,CombinedLoopControlMask)123 TEST_F(OpLoopMergeTest, CombinedLoopControlMask) {
124 const std::string input = "OpLoopMerge %merge %continue Unroll|DontUnroll";
125 const uint32_t expected_mask =
126 SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask;
127 EXPECT_THAT(CompiledInstructions(input),
128 Eq(MakeInstruction(SpvOpLoopMerge, {1, 2, expected_mask})));
129 }
130
TEST_F(OpLoopMergeTest,WrongLoopControl)131 TEST_F(OpLoopMergeTest, WrongLoopControl) {
132 EXPECT_THAT(CompileFailure("OpLoopMerge %m %c none"),
133 Eq("Invalid loop control operand 'none'."));
134 }
135
136 // Test OpSwitch
137
TEST_F(TextToBinaryTest,SwitchGoodZeroTargets)138 TEST_F(TextToBinaryTest, SwitchGoodZeroTargets) {
139 EXPECT_THAT(CompiledInstructions("OpSwitch %selector %default"),
140 Eq(MakeInstruction(SpvOpSwitch, {1, 2})));
141 }
142
TEST_F(TextToBinaryTest,SwitchGoodOneTarget)143 TEST_F(TextToBinaryTest, SwitchGoodOneTarget) {
144 EXPECT_THAT(CompiledInstructions("%1 = OpTypeInt 32 0\n"
145 "%2 = OpConstant %1 52\n"
146 "OpSwitch %2 %default 12 %target0"),
147 Eq(Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
148 MakeInstruction(SpvOpConstant, {1, 2, 52}),
149 MakeInstruction(SpvOpSwitch, {2, 3, 12, 4})})));
150 }
151
TEST_F(TextToBinaryTest,SwitchGoodTwoTargets)152 TEST_F(TextToBinaryTest, SwitchGoodTwoTargets) {
153 EXPECT_THAT(
154 CompiledInstructions("%1 = OpTypeInt 32 0\n"
155 "%2 = OpConstant %1 52\n"
156 "OpSwitch %2 %default 12 %target0 42 %target1"),
157 Eq(Concatenate({
158 MakeInstruction(SpvOpTypeInt, {1, 32, 0}),
159 MakeInstruction(SpvOpConstant, {1, 2, 52}),
160 MakeInstruction(SpvOpSwitch, {2, 3, 12, 4, 42, 5}),
161 })));
162 }
163
TEST_F(TextToBinaryTest,SwitchBadMissingSelector)164 TEST_F(TextToBinaryTest, SwitchBadMissingSelector) {
165 EXPECT_THAT(CompileFailure("OpSwitch"),
166 Eq("Expected operand, found end of stream."));
167 }
168
TEST_F(TextToBinaryTest,SwitchBadInvalidSelector)169 TEST_F(TextToBinaryTest, SwitchBadInvalidSelector) {
170 EXPECT_THAT(CompileFailure("OpSwitch 12"),
171 Eq("Expected id to start with %."));
172 }
173
TEST_F(TextToBinaryTest,SwitchBadMissingDefault)174 TEST_F(TextToBinaryTest, SwitchBadMissingDefault) {
175 EXPECT_THAT(CompileFailure("OpSwitch %selector"),
176 Eq("Expected operand, found end of stream."));
177 }
178
TEST_F(TextToBinaryTest,SwitchBadInvalidDefault)179 TEST_F(TextToBinaryTest, SwitchBadInvalidDefault) {
180 EXPECT_THAT(CompileFailure("OpSwitch %selector 12"),
181 Eq("Expected id to start with %."));
182 }
183
TEST_F(TextToBinaryTest,SwitchBadInvalidLiteral)184 TEST_F(TextToBinaryTest, SwitchBadInvalidLiteral) {
185 // The assembler recognizes "OpSwitch %selector %default" as a complete
186 // instruction. Then it tries to parse "%abc" as the start of a new
187 // instruction, but can't since it hits the end of stream.
188 const auto input = R"(%i32 = OpTypeInt 32 0
189 %selector = OpConstant %i32 42
190 OpSwitch %selector %default %abc)";
191 EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream."));
192 }
193
TEST_F(TextToBinaryTest,SwitchBadMissingTarget)194 TEST_F(TextToBinaryTest, SwitchBadMissingTarget) {
195 EXPECT_THAT(CompileFailure("%1 = OpTypeInt 32 0\n"
196 "%2 = OpConstant %1 52\n"
197 "OpSwitch %2 %default 12"),
198 Eq("Expected operand, found end of stream."));
199 }
200
201 // A test case for an OpSwitch.
202 // It is also parameterized to test encodings OpConstant
203 // integer literals. This can capture both single and multi-word
204 // integer literal tests.
205 struct SwitchTestCase {
206 std::string constant_type_args;
207 std::string constant_value_arg;
208 std::string case_value_arg;
209 std::vector<uint32_t> expected_instructions;
210 };
211
212 using OpSwitchValidTest =
213 spvtest::TextToBinaryTestBase<TestWithParam<SwitchTestCase>>;
214
215 // Tests the encoding of OpConstant literal values, and also
216 // the literal integer cases in an OpSwitch. This can
217 // test both single and multi-word integer literal encodings.
TEST_P(OpSwitchValidTest,ValidTypes)218 TEST_P(OpSwitchValidTest, ValidTypes) {
219 const std::string input = "%1 = OpTypeInt " + GetParam().constant_type_args +
220 "\n"
221 "%2 = OpConstant %1 " +
222 GetParam().constant_value_arg +
223 "\n"
224 "OpSwitch %2 %default " +
225 GetParam().case_value_arg + " %4\n";
226 std::vector<uint32_t> instructions;
227 EXPECT_THAT(CompiledInstructions(input),
228 Eq(GetParam().expected_instructions));
229 }
230
231 // Constructs a SwitchTestCase from the given integer_width, signedness,
232 // constant value string, and expected encoded constant.
MakeSwitchTestCase(uint32_t integer_width,uint32_t integer_signedness,std::string constant_str,std::vector<uint32_t> encoded_constant,std::string case_value_str,std::vector<uint32_t> encoded_case_value)233 SwitchTestCase MakeSwitchTestCase(uint32_t integer_width,
234 uint32_t integer_signedness,
235 std::string constant_str,
236 std::vector<uint32_t> encoded_constant,
237 std::string case_value_str,
238 std::vector<uint32_t> encoded_case_value) {
239 std::stringstream ss;
240 ss << integer_width << " " << integer_signedness;
241 return SwitchTestCase{
242 ss.str(),
243 constant_str,
244 case_value_str,
245 {Concatenate(
246 {MakeInstruction(SpvOpTypeInt,
247 {1, integer_width, integer_signedness}),
248 MakeInstruction(SpvOpConstant,
249 Concatenate({{1, 2}, encoded_constant})),
250 MakeInstruction(SpvOpSwitch,
251 Concatenate({{2, 3}, encoded_case_value, {4}}))})}};
252 }
253
254 INSTANTIATE_TEST_SUITE_P(
255 TextToBinaryOpSwitchValid1Word, OpSwitchValidTest,
256 ValuesIn(std::vector<SwitchTestCase>({
257 MakeSwitchTestCase(32, 0, "42", {42}, "100", {100}),
258 MakeSwitchTestCase(32, 1, "-1", {0xffffffff}, "100", {100}),
259 // SPIR-V 1.0 Rev 1 clarified that for an integer narrower than 32-bits,
260 // its bits will appear in the lower order bits of the 32-bit word, and
261 // a signed integer is sign-extended.
262 MakeSwitchTestCase(7, 0, "127", {127}, "100", {100}),
263 MakeSwitchTestCase(14, 0, "99", {99}, "100", {100}),
264 MakeSwitchTestCase(16, 0, "65535", {65535}, "100", {100}),
265 MakeSwitchTestCase(16, 1, "101", {101}, "100", {100}),
266 // Demonstrate sign extension
267 MakeSwitchTestCase(16, 1, "-2", {0xfffffffe}, "100", {100}),
268 // Hex cases
269 MakeSwitchTestCase(16, 1, "0x7ffe", {0x7ffe}, "0x1234", {0x1234}),
270 MakeSwitchTestCase(16, 1, "0x8000", {0xffff8000}, "0x8100",
271 {0xffff8100}),
272 MakeSwitchTestCase(16, 0, "0x8000", {0x00008000}, "0x8100", {0x8100}),
273 })));
274
275 // NB: The words LOW ORDER bits show up first.
276 INSTANTIATE_TEST_SUITE_P(
277 TextToBinaryOpSwitchValid2Words, OpSwitchValidTest,
278 ValuesIn(std::vector<SwitchTestCase>({
279 MakeSwitchTestCase(33, 0, "101", {101, 0}, "500", {500, 0}),
280 MakeSwitchTestCase(48, 1, "-1", {0xffffffff, 0xffffffff}, "900",
281 {900, 0}),
282 MakeSwitchTestCase(64, 1, "-2", {0xfffffffe, 0xffffffff}, "-5",
283 {0xfffffffb, uint32_t(-1)}),
284 // Hex cases
285 MakeSwitchTestCase(48, 1, "0x7fffffffffff", {0xffffffff, 0x00007fff},
286 "100", {100, 0}),
287 MakeSwitchTestCase(48, 1, "0x800000000000", {0x00000000, 0xffff8000},
288 "0x800000000000", {0x00000000, 0xffff8000}),
289 MakeSwitchTestCase(48, 0, "0x800000000000", {0x00000000, 0x00008000},
290 "0x800000000000", {0x00000000, 0x00008000}),
291 MakeSwitchTestCase(63, 0, "0x500000000", {0, 5}, "12", {12, 0}),
292 MakeSwitchTestCase(64, 0, "0x600000000", {0, 6}, "12", {12, 0}),
293 MakeSwitchTestCase(64, 1, "0x700000123", {0x123, 7}, "12", {12, 0}),
294 })));
295
296 using ControlFlowRoundTripTest = RoundTripTest;
297
TEST_P(ControlFlowRoundTripTest,DisassemblyEqualsAssemblyInput)298 TEST_P(ControlFlowRoundTripTest, DisassemblyEqualsAssemblyInput) {
299 const std::string assembly = GetParam();
300 EXPECT_THAT(EncodeAndDecodeSuccessfully(assembly), Eq(assembly)) << assembly;
301 }
302
303 INSTANTIATE_TEST_SUITE_P(
304 OpSwitchRoundTripUnsignedIntegers, ControlFlowRoundTripTest,
305 ValuesIn(std::vector<std::string>({
306 // Unsigned 16-bit.
307 "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 65535\nOpSwitch %2 %3\n",
308 // Unsigned 32-bit, three non-default cases.
309 "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 123456\n"
310 "OpSwitch %2 %3 100 %4 102 %5 1000000 %6\n",
311 // Unsigned 48-bit, three non-default cases.
312 "%1 = OpTypeInt 48 0\n%2 = OpConstant %1 5000000000\n"
313 "OpSwitch %2 %3 100 %4 102 %5 6000000000 %6\n",
314 // Unsigned 64-bit, three non-default cases.
315 "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 9223372036854775807\n"
316 "OpSwitch %2 %3 100 %4 102 %5 9000000000000000000 %6\n",
317 })));
318
319 INSTANTIATE_TEST_SUITE_P(
320 OpSwitchRoundTripSignedIntegers, ControlFlowRoundTripTest,
321 ValuesIn(std::vector<std::string>{
322 // Signed 16-bit, with two non-default cases
323 "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 32767\n"
324 "OpSwitch %2 %3 99 %4 -102 %5\n",
325 "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 -32768\n"
326 "OpSwitch %2 %3 99 %4 -102 %5\n",
327 // Signed 32-bit, two non-default cases.
328 "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 -123456\n"
329 "OpSwitch %2 %3 100 %4 -123456 %5\n",
330 "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 123456\n"
331 "OpSwitch %2 %3 100 %4 123456 %5\n",
332 // Signed 48-bit, three non-default cases.
333 "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 5000000000\n"
334 "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n",
335 "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 -5000000000\n"
336 "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n",
337 // Signed 64-bit, three non-default cases.
338 "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 9223372036854775807\n"
339 "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
340 "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n"
341 "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
342 }));
343
344 using OpSwitchInvalidTypeTestCase =
345 spvtest::TextToBinaryTestBase<TestWithParam<std::string>>;
346
TEST_P(OpSwitchInvalidTypeTestCase,InvalidTypes)347 TEST_P(OpSwitchInvalidTypeTestCase, InvalidTypes) {
348 const std::string input =
349 "%1 = " + GetParam() +
350 "\n"
351 "%3 = OpCopyObject %1 %2\n" // We only care the type of the expression
352 " OpSwitch %3 %default 32 %c\n";
353 EXPECT_THAT(CompileFailure(input),
354 Eq("The selector operand for OpSwitch must be the result of an "
355 "instruction that generates an integer scalar"));
356 }
357
358 // clang-format off
359 INSTANTIATE_TEST_SUITE_P(
360 TextToBinaryOpSwitchInvalidTests, OpSwitchInvalidTypeTestCase,
361 ValuesIn(std::vector<std::string>{
362 {"OpTypeVoid",
363 "OpTypeBool",
364 "OpTypeFloat 32",
365 "OpTypeVector %a 32",
366 "OpTypeMatrix %a 32",
367 "OpTypeImage %a 1D 0 0 0 0 Unknown",
368 "OpTypeSampler",
369 "OpTypeSampledImage %a",
370 "OpTypeArray %a %b",
371 "OpTypeRuntimeArray %a",
372 "OpTypeStruct %a",
373 "OpTypeOpaque \"Foo\"",
374 "OpTypePointer UniformConstant %a",
375 "OpTypeFunction %a %b",
376 "OpTypeEvent",
377 "OpTypeDeviceEvent",
378 "OpTypeReserveId",
379 "OpTypeQueue",
380 "OpTypePipe ReadOnly",
381
382 // Skip OpTypeForwardPointer becasuse it doesn't even produce a result
383 // ID.
384
385 // At least one thing that isn't a type at all
386 "OpNot %a %b"
387 },
388 }));
389 // clang-format on
390
391 using OpKillTest = spvtest::TextToBinaryTest;
392
393 INSTANTIATE_TEST_SUITE_P(OpKillTest, ControlFlowRoundTripTest,
394 Values("OpKill\n"));
395
TEST_F(OpKillTest,ExtraArgsAssemblyError)396 TEST_F(OpKillTest, ExtraArgsAssemblyError) {
397 const std::string input = "OpKill 1";
398 EXPECT_THAT(CompileFailure(input),
399 Eq("Expected <opcode> or <result-id> at the beginning of an "
400 "instruction, found '1'."));
401 }
402
403 using OpTerminateInvocationTest = spvtest::TextToBinaryTest;
404
405 INSTANTIATE_TEST_SUITE_P(OpTerminateInvocationTest, ControlFlowRoundTripTest,
406 Values("OpTerminateInvocation\n"));
407
TEST_F(OpTerminateInvocationTest,ExtraArgsAssemblyError)408 TEST_F(OpTerminateInvocationTest, ExtraArgsAssemblyError) {
409 const std::string input = "OpTerminateInvocation 1";
410 EXPECT_THAT(CompileFailure(input),
411 Eq("Expected <opcode> or <result-id> at the beginning of an "
412 "instruction, found '1'."));
413 }
414
415 // TODO(dneto): OpPhi
416 // TODO(dneto): OpLoopMerge
417 // TODO(dneto): OpLabel
418 // TODO(dneto): OpBranch
419 // TODO(dneto): OpSwitch
420 // TODO(dneto): OpReturn
421 // TODO(dneto): OpReturnValue
422 // TODO(dneto): OpUnreachable
423 // TODO(dneto): OpLifetimeStart
424 // TODO(dneto): OpLifetimeStop
425
426 } // namespace
427 } // namespace spvtools
428