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 // Validation tests for Control Flow Graph
16
17 #include <array>
18 #include <functional>
19 #include <iterator>
20 #include <sstream>
21 #include <string>
22 #include <utility>
23 #include <vector>
24
25 #include "gmock/gmock.h"
26 #include "source/diagnostic.h"
27 #include "source/spirv_target_env.h"
28 #include "source/val/validate.h"
29 #include "test/test_fixture.h"
30 #include "test/unit_spirv.h"
31 #include "test/val/val_fixtures.h"
32
33 namespace spvtools {
34 namespace val {
35 namespace {
36
37 using ::testing::HasSubstr;
38 using ::testing::MatchesRegex;
39
40 using ValidateCFG = spvtest::ValidateBase<SpvCapability>;
41 using spvtest::ScopedContext;
42
nameOps()43 std::string nameOps() { return ""; }
44
45 template <typename... Args>
nameOps(std::pair<std::string,std::string> head,Args...names)46 std::string nameOps(std::pair<std::string, std::string> head, Args... names) {
47 return "OpName %" + head.first + " \"" + head.second + "\"\n" +
48 nameOps(names...);
49 }
50
51 template <typename... Args>
nameOps(std::string head,Args...names)52 std::string nameOps(std::string head, Args... names) {
53 return "OpName %" + head + " \"" + head + "\"\n" + nameOps(names...);
54 }
55
56 /// This class allows the easy creation of complex control flow without writing
57 /// SPIR-V. This class is used in the test cases below.
58 class Block {
59 std::string label_;
60 std::string body_;
61 SpvOp type_;
62 std::vector<Block> successors_;
63
64 public:
65 /// Creates a Block with a given label
66 ///
67 /// @param[in]: label the label id of the block
68 /// @param[in]: type the branch instruciton that ends the block
Block(std::string label,SpvOp type=SpvOpBranch)69 explicit Block(std::string label, SpvOp type = SpvOpBranch)
70 : label_(label), body_(), type_(type), successors_() {}
71
72 /// Sets the instructions which will appear in the body of the block
SetBody(std::string body)73 Block& SetBody(std::string body) {
74 body_ = body;
75 return *this;
76 }
77
AppendBody(std::string body)78 Block& AppendBody(std::string body) {
79 body_ += body;
80 return *this;
81 }
82
83 /// Converts the block into a SPIR-V string
operator std::string()84 operator std::string() {
85 std::stringstream out;
86 out << std::setw(8) << "%" + label_ + " = OpLabel \n";
87 if (!body_.empty()) {
88 out << body_;
89 }
90
91 switch (type_) {
92 case SpvOpBranchConditional:
93 out << "OpBranchConditional %cond ";
94 for (Block& b : successors_) {
95 out << "%" + b.label_ + " ";
96 }
97 break;
98 case SpvOpSwitch: {
99 out << "OpSwitch %one %" + successors_.front().label_;
100 std::stringstream ss;
101 for (size_t i = 1; i < successors_.size(); i++) {
102 ss << " " << i << " %" << successors_[i].label_;
103 }
104 out << ss.str();
105 } break;
106 case SpvOpLoopMerge: {
107 assert(successors_.size() == 2);
108 out << "OpLoopMerge %" + successors_[0].label_ + " %" +
109 successors_[0].label_ + "None";
110 } break;
111
112 case SpvOpReturn:
113 assert(successors_.size() == 0);
114 out << "OpReturn\n";
115 break;
116 case SpvOpUnreachable:
117 assert(successors_.size() == 0);
118 out << "OpUnreachable\n";
119 break;
120 case SpvOpBranch:
121 assert(successors_.size() == 1);
122 out << "OpBranch %" + successors_.front().label_;
123 break;
124 case SpvOpKill:
125 assert(successors_.size() == 0);
126 out << "OpKill\n";
127 break;
128 default:
129 assert(1 == 0 && "Unhandled");
130 }
131 out << "\n";
132
133 return out.str();
134 }
135 friend Block& operator>>(Block& curr, std::vector<Block> successors);
136 friend Block& operator>>(Block& lhs, Block& successor);
137 };
138
139 /// Assigns the successors for the Block on the lhs
operator >>(Block & lhs,std::vector<Block> successors)140 Block& operator>>(Block& lhs, std::vector<Block> successors) {
141 if (lhs.type_ == SpvOpBranchConditional) {
142 assert(successors.size() == 2);
143 } else if (lhs.type_ == SpvOpSwitch) {
144 assert(successors.size() > 1);
145 }
146 lhs.successors_ = successors;
147 return lhs;
148 }
149
150 /// Assigns the successor for the Block on the lhs
operator >>(Block & lhs,Block & successor)151 Block& operator>>(Block& lhs, Block& successor) {
152 assert(lhs.type_ == SpvOpBranch);
153 lhs.successors_.push_back(successor);
154 return lhs;
155 }
156
GetDefaultHeader(SpvCapability cap)157 const std::string& GetDefaultHeader(SpvCapability cap) {
158 static const std::string shader_header =
159 "OpCapability Shader\n"
160 "OpCapability Linkage\n"
161 "OpMemoryModel Logical GLSL450\n";
162
163 static const std::string kernel_header =
164 "OpCapability Kernel\n"
165 "OpCapability Linkage\n"
166 "OpMemoryModel Logical OpenCL\n";
167
168 return (cap == SpvCapabilityShader) ? shader_header : kernel_header;
169 }
170
GetWebGPUHeader()171 const std::string& GetWebGPUHeader() {
172 static const std::string header =
173 "OpCapability Shader\n"
174 "OpCapability VulkanMemoryModelKHR\n"
175 "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
176 "OpMemoryModel Logical VulkanKHR\n";
177 return header;
178 }
179
types_consts()180 const std::string& types_consts() {
181 static const std::string types =
182 "%voidt = OpTypeVoid\n"
183 "%boolt = OpTypeBool\n"
184 "%intt = OpTypeInt 32 0\n"
185 "%one = OpConstant %intt 1\n"
186 "%two = OpConstant %intt 2\n"
187 "%ptrt = OpTypePointer Function %intt\n"
188 "%funct = OpTypeFunction %voidt\n";
189 return types;
190 }
191
192 INSTANTIATE_TEST_SUITE_P(StructuredControlFlow, ValidateCFG,
193 ::testing::Values(SpvCapabilityShader,
194 SpvCapabilityKernel));
195
TEST_P(ValidateCFG,LoopReachableFromEntryButNeverLeadingToReturn)196 TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) {
197 // In this case, the loop is reachable from a node without a predecessor,
198 // but never reaches a node with a return.
199 //
200 // This motivates the need for the pseudo-exit node to have a node
201 // from a cycle in its predecessors list. Otherwise the validator's
202 // post-dominance calculation will go into an infinite loop.
203 //
204 // For more motivation, see
205 // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
206 std::string str = R"(
207 OpCapability Shader
208 OpCapability Linkage
209 OpMemoryModel Logical GLSL450
210
211 OpName %entry "entry"
212 OpName %loop "loop"
213 OpName %exit "exit"
214
215 %voidt = OpTypeVoid
216 %funct = OpTypeFunction %voidt
217
218 %main = OpFunction %voidt None %funct
219 %entry = OpLabel
220 OpBranch %loop
221 %loop = OpLabel
222 OpLoopMerge %exit %loop None
223 OpBranch %loop
224 %exit = OpLabel
225 OpReturn
226 OpFunctionEnd
227 )";
228 CompileSuccessfully(str);
229 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
230 }
231
TEST_P(ValidateCFG,LoopUnreachableFromEntryButLeadingToReturn)232 TEST_P(ValidateCFG, LoopUnreachableFromEntryButLeadingToReturn) {
233 // In this case, the loop is not reachable from a node without a
234 // predecessor, but eventually reaches a node with a return.
235 //
236 // This motivates the need for the pseudo-entry node to have a node
237 // from a cycle in its successors list. Otherwise the validator's
238 // dominance calculation will go into an infinite loop.
239 //
240 // For more motivation, see
241 // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
242 // Before that fix, we'd have an infinite loop when calculating
243 // post-dominators.
244 std::string str = R"(
245 OpCapability Shader
246 OpCapability Linkage
247 OpMemoryModel Logical GLSL450
248
249 OpName %entry "entry"
250 OpName %loop "loop"
251 OpName %cont "cont"
252 OpName %exit "exit"
253
254 %voidt = OpTypeVoid
255 %funct = OpTypeFunction %voidt
256 %boolt = OpTypeBool
257 %false = OpConstantFalse %boolt
258
259 %main = OpFunction %voidt None %funct
260 %entry = OpLabel
261 OpReturn
262
263 %loop = OpLabel
264 OpLoopMerge %exit %cont None
265 OpBranch %cont
266
267 %cont = OpLabel
268 OpBranchConditional %false %loop %exit
269
270 %exit = OpLabel
271 OpReturn
272 OpFunctionEnd
273 )";
274 CompileSuccessfully(str);
275 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions())
276 << str << getDiagnosticString();
277 }
278
TEST_P(ValidateCFG,Simple)279 TEST_P(ValidateCFG, Simple) {
280 bool is_shader = GetParam() == SpvCapabilityShader;
281 Block entry("entry");
282 Block loop("loop", SpvOpBranchConditional);
283 Block cont("cont");
284 Block merge("merge", SpvOpReturn);
285
286 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
287 if (is_shader) {
288 loop.SetBody("OpLoopMerge %merge %cont None\n");
289 }
290
291 std::string str = GetDefaultHeader(GetParam()) +
292 nameOps("loop", "entry", "cont", "merge",
293 std::make_pair("func", "Main")) +
294 types_consts() +
295 "%func = OpFunction %voidt None %funct\n";
296
297 str += entry >> loop;
298 str += loop >> std::vector<Block>({cont, merge});
299 str += cont >> loop;
300 str += merge;
301 str += "OpFunctionEnd\n";
302
303 CompileSuccessfully(str);
304 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
305 }
306
TEST_P(ValidateCFG,Variable)307 TEST_P(ValidateCFG, Variable) {
308 Block entry("entry");
309 Block cont("cont");
310 Block exit("exit", SpvOpReturn);
311
312 entry.SetBody("%var = OpVariable %ptrt Function\n");
313
314 std::string str = GetDefaultHeader(GetParam()) +
315 nameOps(std::make_pair("func", "Main")) + types_consts() +
316 " %func = OpFunction %voidt None %funct\n";
317 str += entry >> cont;
318 str += cont >> exit;
319 str += exit;
320 str += "OpFunctionEnd\n";
321
322 CompileSuccessfully(str);
323 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
324 }
325
TEST_P(ValidateCFG,VariableNotInFirstBlockBad)326 TEST_P(ValidateCFG, VariableNotInFirstBlockBad) {
327 Block entry("entry");
328 Block cont("cont");
329 Block exit("exit", SpvOpReturn);
330
331 // This operation should only be performed in the entry block
332 cont.SetBody("%var = OpVariable %ptrt Function\n");
333
334 std::string str = GetDefaultHeader(GetParam()) +
335 nameOps(std::make_pair("func", "Main")) + types_consts() +
336 " %func = OpFunction %voidt None %funct\n";
337
338 str += entry >> cont;
339 str += cont >> exit;
340 str += exit;
341 str += "OpFunctionEnd\n";
342
343 CompileSuccessfully(str);
344 ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
345 EXPECT_THAT(getDiagnosticString(),
346 HasSubstr("All OpVariable instructions in a function must be the "
347 "first instructions in the first block"));
348 }
349
TEST_P(ValidateCFG,BlockSelfLoopIsOk)350 TEST_P(ValidateCFG, BlockSelfLoopIsOk) {
351 bool is_shader = GetParam() == SpvCapabilityShader;
352 Block entry("entry");
353 Block loop("loop", SpvOpBranchConditional);
354 Block merge("merge", SpvOpReturn);
355
356 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
357 if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
358
359 std::string str = GetDefaultHeader(GetParam()) +
360 nameOps("loop", "merge", std::make_pair("func", "Main")) +
361 types_consts() +
362 "%func = OpFunction %voidt None %funct\n";
363
364 str += entry >> loop;
365 // loop branches to itself, but does not trigger an error.
366 str += loop >> std::vector<Block>({merge, loop});
367 str += merge;
368 str += "OpFunctionEnd\n";
369
370 CompileSuccessfully(str);
371 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
372 }
373
TEST_P(ValidateCFG,BlockAppearsBeforeDominatorBad)374 TEST_P(ValidateCFG, BlockAppearsBeforeDominatorBad) {
375 bool is_shader = GetParam() == SpvCapabilityShader;
376 Block entry("entry");
377 Block cont("cont");
378 Block branch("branch", SpvOpBranchConditional);
379 Block merge("merge", SpvOpReturn);
380
381 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
382 if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
383
384 std::string str = GetDefaultHeader(GetParam()) +
385 nameOps("cont", "branch", std::make_pair("func", "Main")) +
386 types_consts() +
387 "%func = OpFunction %voidt None %funct\n";
388
389 str += entry >> branch;
390 str += cont >> merge; // cont appears before its dominator
391 str += branch >> std::vector<Block>({cont, merge});
392 str += merge;
393 str += "OpFunctionEnd\n";
394
395 CompileSuccessfully(str);
396 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
397 EXPECT_THAT(getDiagnosticString(),
398 MatchesRegex("Block .\\[%cont\\] appears in the binary "
399 "before its dominator .\\[%branch\\]\n"
400 " %branch = OpLabel\n"));
401 }
402
TEST_P(ValidateCFG,MergeBlockTargetedByMultipleHeaderBlocksBad)403 TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksBad) {
404 bool is_shader = GetParam() == SpvCapabilityShader;
405 Block entry("entry");
406 Block loop("loop");
407 Block selection("selection", SpvOpBranchConditional);
408 Block merge("merge", SpvOpReturn);
409
410 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
411 if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
412
413 // cannot share the same merge
414 if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n");
415
416 std::string str = GetDefaultHeader(GetParam()) +
417 nameOps("merge", std::make_pair("func", "Main")) +
418 types_consts() +
419 "%func = OpFunction %voidt None %funct\n";
420
421 str += entry >> loop;
422 str += loop >> selection;
423 str += selection >> std::vector<Block>({loop, merge});
424 str += merge;
425 str += "OpFunctionEnd\n";
426
427 CompileSuccessfully(str);
428 if (is_shader) {
429 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
430 EXPECT_THAT(getDiagnosticString(),
431 MatchesRegex("Block .\\[%merge\\] is already a merge block "
432 "for another header\n"
433 " %Main = OpFunction %void None %9\n"));
434 } else {
435 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
436 }
437 }
438
TEST_P(ValidateCFG,MergeBlockTargetedByMultipleHeaderBlocksSelectionBad)439 TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksSelectionBad) {
440 bool is_shader = GetParam() == SpvCapabilityShader;
441 Block entry("entry");
442 Block loop("loop", SpvOpBranchConditional);
443 Block selection("selection", SpvOpBranchConditional);
444 Block merge("merge", SpvOpReturn);
445
446 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
447 if (is_shader) selection.SetBody(" OpSelectionMerge %merge None\n");
448
449 // cannot share the same merge
450 if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
451
452 std::string str = GetDefaultHeader(GetParam()) +
453 nameOps("merge", std::make_pair("func", "Main")) +
454 types_consts() +
455 "%func = OpFunction %voidt None %funct\n";
456
457 str += entry >> selection;
458 str += selection >> std::vector<Block>({merge, loop});
459 str += loop >> std::vector<Block>({loop, merge});
460 str += merge;
461 str += "OpFunctionEnd\n";
462
463 CompileSuccessfully(str);
464 if (is_shader) {
465 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
466 EXPECT_THAT(getDiagnosticString(),
467 MatchesRegex("Block .\\[%merge\\] is already a merge block "
468 "for another header\n"
469 " %Main = OpFunction %void None %9\n"));
470 } else {
471 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
472 }
473 }
474
TEST_P(ValidateCFG,BranchTargetFirstBlockBadSinceEntryBlock)475 TEST_P(ValidateCFG, BranchTargetFirstBlockBadSinceEntryBlock) {
476 Block entry("entry");
477 Block bad("bad");
478 Block end("end", SpvOpReturn);
479 std::string str = GetDefaultHeader(GetParam()) +
480 nameOps("entry", "bad", std::make_pair("func", "Main")) +
481 types_consts() +
482 "%func = OpFunction %voidt None %funct\n";
483
484 str += entry >> bad;
485 str += bad >> entry; // Cannot target entry block
486 str += end;
487 str += "OpFunctionEnd\n";
488
489 CompileSuccessfully(str);
490 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
491 EXPECT_THAT(getDiagnosticString(),
492 MatchesRegex("First block .\\[%entry\\] of function "
493 ".\\[%Main\\] is targeted by block .\\[%bad\\]\n"
494 " %Main = OpFunction %void None %10\n"));
495 }
496
TEST_P(ValidateCFG,BranchTargetFirstBlockBadSinceValue)497 TEST_P(ValidateCFG, BranchTargetFirstBlockBadSinceValue) {
498 Block entry("entry");
499 entry.SetBody("%undef = OpUndef %voidt\n");
500 Block bad("bad");
501 Block end("end", SpvOpReturn);
502 Block badvalue("undef"); // This referenes the OpUndef.
503 std::string str = GetDefaultHeader(GetParam()) +
504 nameOps("entry", "bad", std::make_pair("func", "Main")) +
505 types_consts() +
506 "%func = OpFunction %voidt None %funct\n";
507
508 str += entry >> bad;
509 str +=
510 bad >> badvalue; // Check branch to a function value (it's not a block!)
511 str += end;
512 str += "OpFunctionEnd\n";
513
514 CompileSuccessfully(str);
515 ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
516 EXPECT_THAT(getDiagnosticString(),
517 HasSubstr("'Target Label' operands for OpBranch must "
518 "be the ID of an OpLabel instruction"));
519 }
520
TEST_P(ValidateCFG,BranchConditionalTrueTargetFirstBlockBad)521 TEST_P(ValidateCFG, BranchConditionalTrueTargetFirstBlockBad) {
522 Block entry("entry");
523 Block bad("bad", SpvOpBranchConditional);
524 Block exit("exit", SpvOpReturn);
525
526 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
527 bad.SetBody(" OpLoopMerge %entry %exit None\n");
528
529 std::string str = GetDefaultHeader(GetParam()) +
530 nameOps("entry", "bad", std::make_pair("func", "Main")) +
531 types_consts() +
532 "%func = OpFunction %voidt None %funct\n";
533
534 str += entry >> bad;
535 str += bad >> std::vector<Block>({entry, exit}); // cannot target entry block
536 str += exit;
537 str += "OpFunctionEnd\n";
538
539 CompileSuccessfully(str);
540 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
541 EXPECT_THAT(getDiagnosticString(),
542 MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
543 "is targeted by block .\\[%bad\\]\n"
544 " %Main = OpFunction %void None %10\n"));
545 }
546
TEST_P(ValidateCFG,BranchConditionalFalseTargetFirstBlockBad)547 TEST_P(ValidateCFG, BranchConditionalFalseTargetFirstBlockBad) {
548 Block entry("entry");
549 Block bad("bad", SpvOpBranchConditional);
550 Block t("t");
551 Block merge("merge");
552 Block end("end", SpvOpReturn);
553
554 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
555 bad.SetBody("OpLoopMerge %merge %cont None\n");
556
557 std::string str = GetDefaultHeader(GetParam()) +
558 nameOps("entry", "bad", std::make_pair("func", "Main")) +
559 types_consts() +
560 "%func = OpFunction %voidt None %funct\n";
561
562 str += entry >> bad;
563 str += bad >> std::vector<Block>({t, entry});
564 str += merge >> end;
565 str += end;
566 str += "OpFunctionEnd\n";
567
568 CompileSuccessfully(str);
569 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
570 EXPECT_THAT(getDiagnosticString(),
571 MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
572 "is targeted by block .\\[%bad\\]\n"
573 " %Main = OpFunction %void None %10\n"));
574 }
575
TEST_P(ValidateCFG,SwitchTargetFirstBlockBad)576 TEST_P(ValidateCFG, SwitchTargetFirstBlockBad) {
577 Block entry("entry");
578 Block bad("bad", SpvOpSwitch);
579 Block block1("block1");
580 Block block2("block2");
581 Block block3("block3");
582 Block def("def"); // default block
583 Block merge("merge");
584 Block end("end", SpvOpReturn);
585
586 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
587 bad.SetBody("OpSelectionMerge %merge None\n");
588
589 std::string str = GetDefaultHeader(GetParam()) +
590 nameOps("entry", "bad", std::make_pair("func", "Main")) +
591 types_consts() +
592 "%func = OpFunction %voidt None %funct\n";
593
594 str += entry >> bad;
595 str += bad >> std::vector<Block>({def, block1, block2, block3, entry});
596 str += def >> merge;
597 str += block1 >> merge;
598 str += block2 >> merge;
599 str += block3 >> merge;
600 str += merge >> end;
601 str += end;
602 str += "OpFunctionEnd\n";
603
604 CompileSuccessfully(str);
605 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
606 EXPECT_THAT(getDiagnosticString(),
607 MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
608 "is targeted by block .\\[%bad\\]\n"
609 " %Main = OpFunction %void None %10\n"));
610 }
611
TEST_P(ValidateCFG,BranchToBlockInOtherFunctionBad)612 TEST_P(ValidateCFG, BranchToBlockInOtherFunctionBad) {
613 Block entry("entry");
614 Block middle("middle", SpvOpBranchConditional);
615 Block end("end", SpvOpReturn);
616
617 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
618 middle.SetBody("OpSelectionMerge %end None\n");
619
620 Block entry2("entry2");
621 Block middle2("middle2");
622 Block end2("end2", SpvOpReturn);
623
624 std::string str = GetDefaultHeader(GetParam()) +
625 nameOps("middle2", std::make_pair("func", "Main")) +
626 types_consts() +
627 "%func = OpFunction %voidt None %funct\n";
628
629 str += entry >> middle;
630 str += middle >> std::vector<Block>({end, middle2});
631 str += end;
632 str += "OpFunctionEnd\n";
633
634 str += "%func2 = OpFunction %voidt None %funct\n";
635 str += entry2 >> middle2;
636 str += middle2 >> end2;
637 str += end2;
638 str += "OpFunctionEnd\n";
639
640 CompileSuccessfully(str);
641 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
642 EXPECT_THAT(
643 getDiagnosticString(),
644 MatchesRegex("Block\\(s\\) \\{.\\[%middle2\\]\\} are referenced but not "
645 "defined in function .\\[%Main\\]\n"
646 " %Main = OpFunction %void None %9\n"));
647 }
648
TEST_P(ValidateCFG,HeaderDoesntDominatesMergeBad)649 TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) {
650 bool is_shader = GetParam() == SpvCapabilityShader;
651 Block entry("entry");
652 Block head("head", SpvOpBranchConditional);
653 Block f("f");
654 Block merge("merge", SpvOpReturn);
655
656 head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
657
658 if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
659
660 std::string str = GetDefaultHeader(GetParam()) +
661 nameOps("head", "merge", std::make_pair("func", "Main")) +
662 types_consts() +
663 "%func = OpFunction %voidt None %funct\n";
664
665 str += entry >> merge;
666 str += head >> std::vector<Block>({merge, f});
667 str += f >> merge;
668 str += merge;
669 str += "OpFunctionEnd\n";
670
671 CompileSuccessfully(str);
672 if (is_shader) {
673 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
674 EXPECT_THAT(
675 getDiagnosticString(),
676 MatchesRegex("The selection construct with the selection header "
677 ".\\[%head\\] does not dominate the merge block "
678 ".\\[%merge\\]\n %merge = OpLabel\n"));
679 } else {
680 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
681 }
682 }
683
TEST_P(ValidateCFG,HeaderDoesntStrictlyDominateMergeBad)684 TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) {
685 // If a merge block is reachable, then it must be strictly dominated by
686 // its header block.
687 bool is_shader = GetParam() == SpvCapabilityShader;
688 Block head("head", SpvOpBranchConditional);
689 Block exit("exit", SpvOpReturn);
690
691 head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
692
693 if (is_shader) head.AppendBody("OpSelectionMerge %head None\n");
694
695 std::string str = GetDefaultHeader(GetParam()) +
696 nameOps("head", "exit", std::make_pair("func", "Main")) +
697 types_consts() +
698 "%func = OpFunction %voidt None %funct\n";
699
700 str += head >> std::vector<Block>({exit, exit});
701 str += exit;
702 str += "OpFunctionEnd\n";
703
704 CompileSuccessfully(str);
705 if (is_shader) {
706 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
707 EXPECT_THAT(
708 getDiagnosticString(),
709 MatchesRegex("The selection construct with the selection header "
710 ".\\[%head\\] does not strictly dominate the merge block "
711 ".\\[%head\\]\n %head = OpLabel\n"));
712 } else {
713 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
714 }
715 }
716
GetUnreachableMergeNoMergeInst(SpvCapability cap,spv_target_env env)717 std::string GetUnreachableMergeNoMergeInst(SpvCapability cap,
718 spv_target_env env) {
719 std::string header =
720 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
721 Block entry("entry");
722 Block branch("branch", SpvOpBranchConditional);
723 Block t("t", SpvOpReturn);
724 Block f("f", SpvOpReturn);
725 Block merge("merge", SpvOpReturn);
726
727 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
728 if (!spvIsWebGPUEnv(env) && cap == SpvCapabilityShader)
729 branch.AppendBody("OpSelectionMerge %merge None\n");
730
731 std::string str = header;
732 if (spvIsWebGPUEnv(env)) {
733 str +=
734 "OpEntryPoint Fragment %func \"func\"\n"
735 "OpExecutionMode %func OriginUpperLeft\n";
736 }
737 if (!spvIsWebGPUEnv(env))
738 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
739 str += types_consts() + "%func = OpFunction %voidt None %funct\n";
740 str += entry >> branch;
741 str += branch >> std::vector<Block>({t, f});
742 str += t;
743 str += f;
744 str += merge;
745 str += "OpFunctionEnd\n";
746
747 return str;
748 }
749
TEST_P(ValidateCFG,UnreachableMergeNoMergeInst)750 TEST_P(ValidateCFG, UnreachableMergeNoMergeInst) {
751 CompileSuccessfully(
752 GetUnreachableMergeNoMergeInst(GetParam(), SPV_ENV_UNIVERSAL_1_0));
753 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
754 }
755
TEST_F(ValidateCFG,WebGPUUnreachableMergeNoMergeInst)756 TEST_F(ValidateCFG, WebGPUUnreachableMergeNoMergeInst) {
757 CompileSuccessfully(
758 GetUnreachableMergeNoMergeInst(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
759 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
760 EXPECT_THAT(getDiagnosticString(),
761 HasSubstr("For WebGPU, all blocks must be reachable"));
762 }
763
GetUnreachableMergeTerminatedBy(SpvCapability cap,spv_target_env env,SpvOp op)764 std::string GetUnreachableMergeTerminatedBy(SpvCapability cap,
765 spv_target_env env, SpvOp op) {
766 std::string header =
767 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
768
769 Block entry("entry");
770 Block branch("branch", SpvOpBranchConditional);
771 Block t("t", SpvOpReturn);
772 Block f("f", SpvOpReturn);
773 Block merge("merge", op);
774
775 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
776 std::string str = header;
777 if (spvIsWebGPUEnv(env)) {
778 str +=
779 "OpEntryPoint Fragment %func \"func\"\n"
780 "OpExecutionMode %func OriginUpperLeft\n";
781 }
782 if (cap == SpvCapabilityShader)
783 branch.AppendBody("OpSelectionMerge %merge None\n");
784 if (!spvIsWebGPUEnv(env))
785 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
786
787 str += types_consts();
788 str += "%func = OpFunction %voidt None %funct\n";
789 str += entry >> branch;
790 str += branch >> std::vector<Block>({t, f});
791 str += t;
792 str += f;
793 str += merge;
794 str += "OpFunctionEnd\n";
795
796 return str;
797 }
798
TEST_P(ValidateCFG,UnreachableMergeTerminatedByOpUnreachable)799 TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpUnreachable) {
800 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
801 GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
802 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
803 }
804
TEST_F(ValidateCFG,UnreachableMergeTerminatedByOpKill)805 TEST_F(ValidateCFG, UnreachableMergeTerminatedByOpKill) {
806 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
807 SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
808 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
809 }
810
TEST_P(ValidateCFG,UnreachableMergeTerminatedByOpReturn)811 TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpReturn) {
812 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
813 GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
814 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
815 }
816
TEST_F(ValidateCFG,WebGPUUnreachableMergeTerminatedByOpUnreachable)817 TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpUnreachable) {
818 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
819 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
820 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
821 }
822
TEST_F(ValidateCFG,WebGPUUnreachableMergeTerminatedByOpKill)823 TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpKill) {
824 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
825 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
826 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
827 EXPECT_THAT(getDiagnosticString(),
828 HasSubstr("must terminate with OpUnreachable"));
829 }
830
TEST_P(ValidateCFG,WebGPUUnreachableMergeTerminatedByOpReturn)831 TEST_P(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpReturn) {
832 CompileSuccessfully(GetUnreachableMergeTerminatedBy(
833 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
834 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
835 EXPECT_THAT(getDiagnosticString(),
836 HasSubstr("must terminate with OpUnreachable"));
837 }
838
GetUnreachableContinueTerminatedBy(SpvCapability cap,spv_target_env env,SpvOp op)839 std::string GetUnreachableContinueTerminatedBy(SpvCapability cap,
840 spv_target_env env, SpvOp op) {
841 std::string header =
842 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
843
844 Block entry("entry");
845 Block branch("branch", SpvOpBranch);
846 Block merge("merge", SpvOpReturn);
847 Block target("target", op);
848
849 if (op == SpvOpBranch) target >> branch;
850
851 std::string str = header;
852 if (spvIsWebGPUEnv(env)) {
853 str +=
854 "OpEntryPoint Fragment %func \"func\"\n"
855 "OpExecutionMode %func OriginUpperLeft\n";
856 }
857 if (cap == SpvCapabilityShader)
858 branch.AppendBody("OpLoopMerge %merge %target None\n");
859 if (!spvIsWebGPUEnv(env))
860 str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
861
862 str += types_consts();
863 str += "%func = OpFunction %voidt None %funct\n";
864 str += entry >> branch;
865 str += branch >> std::vector<Block>({merge});
866 str += merge;
867 str += target;
868 str += "OpFunctionEnd\n";
869
870 return str;
871 }
872
TEST_P(ValidateCFG,UnreachableContinueTerminatedBySpvOpUnreachable)873 TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpUnreachable) {
874 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
875 GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
876 if (GetParam() == SpvCapabilityShader) {
877 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
878 EXPECT_THAT(getDiagnosticString(),
879 HasSubstr("targeted by 0 back-edge blocks"));
880 } else {
881 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
882 }
883 }
884
TEST_F(ValidateCFG,UnreachableContinueTerminatedBySpvOpKill)885 TEST_F(ValidateCFG, UnreachableContinueTerminatedBySpvOpKill) {
886 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
887 SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
888 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
889 EXPECT_THAT(getDiagnosticString(),
890 HasSubstr("targeted by 0 back-edge blocks"));
891 }
892
TEST_P(ValidateCFG,UnreachableContinueTerminatedBySpvOpReturn)893 TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpReturn) {
894 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
895 GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
896 if (GetParam() == SpvCapabilityShader) {
897 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
898 EXPECT_THAT(getDiagnosticString(),
899 HasSubstr("targeted by 0 back-edge blocks"));
900 } else {
901 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
902 }
903 }
904
TEST_P(ValidateCFG,UnreachableContinueTerminatedBySpvOpBranch)905 TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpBranch) {
906 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
907 GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpBranch));
908 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
909 }
910
TEST_F(ValidateCFG,WebGPUUnreachableContinueTerminatedBySpvOpUnreachable)911 TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpUnreachable) {
912 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
913 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
914 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
915 EXPECT_THAT(getDiagnosticString(),
916 HasSubstr("For WebGPU, unreachable continue-target must "
917 "terminate with OpBranch.\n %12 = OpLabel\n"));
918 }
919
TEST_F(ValidateCFG,WebGPUUnreachableContinueTerminatedBySpvOpKill)920 TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpKill) {
921 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
922 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
923 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
924 EXPECT_THAT(getDiagnosticString(),
925 HasSubstr("For WebGPU, unreachable continue-target must "
926 "terminate with OpBranch.\n %12 = OpLabel\n"));
927 }
928
TEST_F(ValidateCFG,WebGPUUnreachableContinueTerminatedBySpvOpReturn)929 TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpReturn) {
930 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
931 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
932 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
933 EXPECT_THAT(getDiagnosticString(),
934 HasSubstr("For WebGPU, unreachable continue-target must "
935 "terminate with OpBranch.\n %12 = OpLabel\n"));
936 }
937
TEST_F(ValidateCFG,WebGPUUnreachableContinueTerminatedBySpvOpBranch)938 TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpBranch) {
939 CompileSuccessfully(GetUnreachableContinueTerminatedBy(
940 SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpBranch));
941 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
942 }
943
GetUnreachableMergeUnreachableMergeInst(SpvCapability cap,spv_target_env env)944 std::string GetUnreachableMergeUnreachableMergeInst(SpvCapability cap,
945 spv_target_env env) {
946 std::string header =
947 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
948
949 Block body("body", SpvOpReturn);
950 Block entry("entry");
951 Block branch("branch", SpvOpBranchConditional);
952 Block t("t", SpvOpReturn);
953 Block f("f", SpvOpReturn);
954 Block merge("merge", SpvOpUnreachable);
955
956 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
957 std::string str = header;
958 if (spvIsWebGPUEnv(env)) {
959 str +=
960 "OpEntryPoint Fragment %func \"func\"\n"
961 "OpExecutionMode %func OriginUpperLeft\n";
962 }
963 if (cap == SpvCapabilityShader)
964 branch.AppendBody("OpSelectionMerge %merge None\n");
965 if (!spvIsWebGPUEnv(env))
966 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
967
968 str += types_consts();
969 str += "%func = OpFunction %voidt None %funct\n";
970 str += body;
971 str += merge;
972 str += entry >> branch;
973 str += branch >> std::vector<Block>({t, f});
974 str += t;
975 str += f;
976 str += "OpFunctionEnd\n";
977
978 return str;
979 }
980
TEST_P(ValidateCFG,UnreachableMergeUnreachableMergeInst)981 TEST_P(ValidateCFG, UnreachableMergeUnreachableMergeInst) {
982 CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
983 GetParam(), SPV_ENV_UNIVERSAL_1_0));
984 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
985 }
986
TEST_F(ValidateCFG,WebGPUUnreachableMergeUnreachableMergeInst)987 TEST_F(ValidateCFG, WebGPUUnreachableMergeUnreachableMergeInst) {
988 CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
989 SpvCapabilityShader, SPV_ENV_WEBGPU_0));
990 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
991 EXPECT_THAT(getDiagnosticString(),
992 HasSubstr("must be referenced by a reachable merge instruction"));
993 }
994
GetUnreachableContinueUnreachableLoopInst(SpvCapability cap,spv_target_env env)995 std::string GetUnreachableContinueUnreachableLoopInst(SpvCapability cap,
996 spv_target_env env) {
997 std::string header =
998 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
999
1000 Block body("body", SpvOpReturn);
1001 Block entry("entry");
1002 Block branch("branch", SpvOpBranch);
1003 Block merge("merge", SpvOpReturn);
1004 Block target("target", SpvOpBranch);
1005
1006 target >> branch;
1007
1008 std::string str = header;
1009 if (spvIsWebGPUEnv(env)) {
1010 str +=
1011 "OpEntryPoint Fragment %func \"func\"\n"
1012 "OpExecutionMode %func OriginUpperLeft\n";
1013 }
1014 if (cap == SpvCapabilityShader)
1015 branch.AppendBody("OpLoopMerge %merge %target None\n");
1016 if (!spvIsWebGPUEnv(env))
1017 str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
1018
1019 str += types_consts();
1020 str += "%func = OpFunction %voidt None %funct\n";
1021 str += body;
1022 str += target;
1023 str += merge;
1024 str += entry >> branch;
1025 str += branch >> std::vector<Block>({merge});
1026 str += "OpFunctionEnd\n";
1027
1028 return str;
1029 }
1030
TEST_P(ValidateCFG,UnreachableContinueUnreachableLoopInst)1031 TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
1032 CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
1033 GetParam(), SPV_ENV_UNIVERSAL_1_0));
1034 if (GetParam() == SpvCapabilityShader) {
1035 // Shader causes additional structured CFG checks that cause a failure.
1036 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1037 EXPECT_THAT(getDiagnosticString(),
1038 HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be "
1039 "formed between a block and a loop header."));
1040
1041 } else {
1042 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1043 }
1044 }
1045
TEST_F(ValidateCFG,WebGPUUnreachableContinueUnreachableLoopInst)1046 TEST_F(ValidateCFG, WebGPUUnreachableContinueUnreachableLoopInst) {
1047 CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
1048 SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1049 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1050 EXPECT_THAT(getDiagnosticString(),
1051 HasSubstr("must be referenced by a reachable loop instruction"));
1052 }
1053
GetUnreachableMergeWithComplexBody(SpvCapability cap,spv_target_env env)1054 std::string GetUnreachableMergeWithComplexBody(SpvCapability cap,
1055 spv_target_env env) {
1056 std::string header =
1057 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1058
1059 Block entry("entry");
1060 Block branch("branch", SpvOpBranchConditional);
1061 Block t("t", SpvOpReturn);
1062 Block f("f", SpvOpReturn);
1063 Block merge("merge", SpvOpUnreachable);
1064
1065 entry.AppendBody(spvIsWebGPUEnv(env)
1066 ? "%dummy = OpVariable %intptrt Function %two\n"
1067 : "%dummy = OpVariable %intptrt Function\n");
1068 entry.AppendBody("%cond = OpSLessThan %boolt %one %two\n");
1069 merge.AppendBody("OpStore %dummy %one\n");
1070
1071 std::string str = header;
1072 if (spvIsWebGPUEnv(env)) {
1073 str +=
1074 "OpEntryPoint Fragment %func \"func\"\n"
1075 "OpExecutionMode %func OriginUpperLeft\n";
1076 }
1077 if (cap == SpvCapabilityShader)
1078 branch.AppendBody("OpSelectionMerge %merge None\n");
1079 if (!spvIsWebGPUEnv(env))
1080 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
1081
1082 str += types_consts();
1083 str += "%intptrt = OpTypePointer Function %intt\n";
1084 str += "%func = OpFunction %voidt None %funct\n";
1085 str += entry >> branch;
1086 str += branch >> std::vector<Block>({t, f});
1087 str += t;
1088 str += f;
1089 str += merge;
1090 str += "OpFunctionEnd\n";
1091
1092 return str;
1093 }
1094
TEST_P(ValidateCFG,UnreachableMergeWithComplexBody)1095 TEST_P(ValidateCFG, UnreachableMergeWithComplexBody) {
1096 CompileSuccessfully(
1097 GetUnreachableMergeWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1098 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1099 }
1100
TEST_F(ValidateCFG,WebGPUUnreachableMergeWithComplexBody)1101 TEST_F(ValidateCFG, WebGPUUnreachableMergeWithComplexBody) {
1102 CompileSuccessfully(GetUnreachableMergeWithComplexBody(SpvCapabilityShader,
1103 SPV_ENV_WEBGPU_0));
1104 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1105 EXPECT_THAT(
1106 getDiagnosticString(),
1107 HasSubstr("must only contain an OpLabel and OpUnreachable instruction"));
1108 }
1109
GetUnreachableContinueWithComplexBody(SpvCapability cap,spv_target_env env)1110 std::string GetUnreachableContinueWithComplexBody(SpvCapability cap,
1111 spv_target_env env) {
1112 std::string header =
1113 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1114
1115 Block entry("entry");
1116 Block branch("branch", SpvOpBranch);
1117 Block merge("merge", SpvOpReturn);
1118 Block target("target", SpvOpBranch);
1119
1120 target >> branch;
1121
1122 entry.AppendBody(spvIsWebGPUEnv(env)
1123 ? "%dummy = OpVariable %intptrt Function %two\n"
1124 : "%dummy = OpVariable %intptrt Function\n");
1125 target.AppendBody("OpStore %dummy %one\n");
1126
1127 std::string str = header;
1128 if (spvIsWebGPUEnv(env)) {
1129 str +=
1130 "OpEntryPoint Fragment %func \"func\"\n"
1131 "OpExecutionMode %func OriginUpperLeft\n";
1132 }
1133 if (cap == SpvCapabilityShader)
1134 branch.AppendBody("OpLoopMerge %merge %target None\n");
1135 if (!spvIsWebGPUEnv(env))
1136 str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
1137
1138 str += types_consts();
1139 str += "%intptrt = OpTypePointer Function %intt\n";
1140 str += "%func = OpFunction %voidt None %funct\n";
1141 str += entry >> branch;
1142 str += branch >> std::vector<Block>({merge});
1143 str += merge;
1144 str += target;
1145 str += "OpFunctionEnd\n";
1146
1147 return str;
1148 }
1149
TEST_P(ValidateCFG,UnreachableContinueWithComplexBody)1150 TEST_P(ValidateCFG, UnreachableContinueWithComplexBody) {
1151 CompileSuccessfully(
1152 GetUnreachableContinueWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1153 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1154 }
1155
TEST_F(ValidateCFG,WebGPUUnreachableContinueWithComplexBody)1156 TEST_F(ValidateCFG, WebGPUUnreachableContinueWithComplexBody) {
1157 CompileSuccessfully(GetUnreachableContinueWithComplexBody(SpvCapabilityShader,
1158 SPV_ENV_WEBGPU_0));
1159 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1160 EXPECT_THAT(
1161 getDiagnosticString(),
1162 HasSubstr("must only contain an OpLabel and an OpBranch instruction"));
1163 }
1164
GetUnreachableMergeWithBranchUse(SpvCapability cap,spv_target_env env)1165 std::string GetUnreachableMergeWithBranchUse(SpvCapability cap,
1166 spv_target_env env) {
1167 std::string header =
1168 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1169
1170 Block entry("entry");
1171 Block branch("branch", SpvOpBranchConditional);
1172 Block t("t", SpvOpBranch);
1173 Block f("f", SpvOpReturn);
1174 Block merge("merge", SpvOpUnreachable);
1175
1176 entry.AppendBody("%cond = OpSLessThan %boolt %one %two\n");
1177
1178 std::string str = header;
1179 if (spvIsWebGPUEnv(env)) {
1180 str +=
1181 "OpEntryPoint Fragment %func \"func\"\n"
1182 "OpExecutionMode %func OriginUpperLeft\n";
1183 }
1184 if (cap == SpvCapabilityShader)
1185 branch.AppendBody("OpSelectionMerge %merge None\n");
1186 if (!spvIsWebGPUEnv(env))
1187 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
1188
1189 str += types_consts();
1190 str += "%func = OpFunction %voidt None %funct\n";
1191 str += entry >> branch;
1192 str += branch >> std::vector<Block>({t, f});
1193 str += t >> merge;
1194 str += f;
1195 str += merge;
1196 str += "OpFunctionEnd\n";
1197
1198 return str;
1199 }
1200
TEST_P(ValidateCFG,UnreachableMergeWithBranchUse)1201 TEST_P(ValidateCFG, UnreachableMergeWithBranchUse) {
1202 CompileSuccessfully(
1203 GetUnreachableMergeWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1204 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
1205 }
1206
TEST_F(ValidateCFG,WebGPUUnreachableMergeWithBranchUse)1207 TEST_F(ValidateCFG, WebGPUUnreachableMergeWithBranchUse) {
1208 CompileSuccessfully(
1209 GetUnreachableMergeWithBranchUse(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1210 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1211 EXPECT_THAT(getDiagnosticString(),
1212 HasSubstr("cannot be the target of a branch."));
1213 }
1214
GetUnreachableMergeWithMultipleUses(SpvCapability cap,spv_target_env env)1215 std::string GetUnreachableMergeWithMultipleUses(SpvCapability cap,
1216 spv_target_env env) {
1217 std::string header =
1218 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1219
1220 Block entry("entry");
1221 Block branch("branch", SpvOpBranchConditional);
1222 Block t("t", SpvOpReturn);
1223 Block f("f", SpvOpReturn);
1224 Block merge("merge", SpvOpUnreachable);
1225 Block duplicate("duplicate", SpvOpBranchConditional);
1226
1227 entry.AppendBody("%cond = OpSLessThan %boolt %one %two\n");
1228
1229 std::string str = header;
1230 if (spvIsWebGPUEnv(env)) {
1231 str +=
1232 "OpEntryPoint Fragment %func \"func\"\n"
1233 "OpExecutionMode %func OriginUpperLeft\n";
1234 }
1235 if (cap == SpvCapabilityShader) {
1236 branch.AppendBody("OpSelectionMerge %merge None\n");
1237 duplicate.AppendBody("OpSelectionMerge %merge None\n");
1238 }
1239 if (!spvIsWebGPUEnv(env))
1240 str += nameOps("branch", "merge", std::make_pair("func", "Main"));
1241
1242 str += types_consts();
1243 str += "%func = OpFunction %voidt None %funct\n";
1244 str += entry >> branch;
1245 str += branch >> std::vector<Block>({t, f});
1246 str += duplicate >> std::vector<Block>({t, f});
1247 str += t;
1248 str += f;
1249 str += merge;
1250 str += "OpFunctionEnd\n";
1251
1252 return str;
1253 }
1254
TEST_P(ValidateCFG,UnreachableMergeWithMultipleUses)1255 TEST_P(ValidateCFG, UnreachableMergeWithMultipleUses) {
1256 CompileSuccessfully(
1257 GetUnreachableMergeWithMultipleUses(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1258 if (GetParam() == SpvCapabilityShader) {
1259 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1260 EXPECT_THAT(getDiagnosticString(),
1261 HasSubstr("is already a merge block for another header"));
1262 } else {
1263 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1264 }
1265 }
1266
TEST_F(ValidateCFG,WebGPUUnreachableMergeWithMultipleUses)1267 TEST_F(ValidateCFG, WebGPUUnreachableMergeWithMultipleUses) {
1268 CompileSuccessfully(GetUnreachableMergeWithMultipleUses(SpvCapabilityShader,
1269 SPV_ENV_WEBGPU_0));
1270 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1271 EXPECT_THAT(getDiagnosticString(),
1272 HasSubstr("is already a merge block for another header"));
1273 }
1274
GetUnreachableContinueWithBranchUse(SpvCapability cap,spv_target_env env)1275 std::string GetUnreachableContinueWithBranchUse(SpvCapability cap,
1276 spv_target_env env) {
1277 std::string header =
1278 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1279
1280 Block entry("entry");
1281 Block foo("foo", SpvOpBranch);
1282 Block branch("branch", SpvOpBranch);
1283 Block merge("merge", SpvOpReturn);
1284 Block target("target", SpvOpBranch);
1285
1286 foo >> target;
1287 target >> branch;
1288
1289 entry.AppendBody(spvIsWebGPUEnv(env)
1290 ? "%dummy = OpVariable %intptrt Function %two\n"
1291 : "%dummy = OpVariable %intptrt Function\n");
1292
1293 std::string str = header;
1294 if (spvIsWebGPUEnv(env)) {
1295 str +=
1296 "OpEntryPoint Fragment %func \"func\"\n"
1297 "OpExecutionMode %func OriginUpperLeft\n";
1298 }
1299 if (cap == SpvCapabilityShader)
1300 branch.AppendBody("OpLoopMerge %merge %target None\n");
1301 if (!spvIsWebGPUEnv(env))
1302 str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
1303
1304 str += types_consts();
1305 str += "%intptrt = OpTypePointer Function %intt\n";
1306 str += "%func = OpFunction %voidt None %funct\n";
1307 str += entry >> branch;
1308 str += branch >> std::vector<Block>({merge});
1309 str += merge;
1310 str += target;
1311 str += foo;
1312 str += "OpFunctionEnd\n";
1313
1314 return str;
1315 }
1316
TEST_P(ValidateCFG,UnreachableContinueWithBranchUse)1317 TEST_P(ValidateCFG, UnreachableContinueWithBranchUse) {
1318 CompileSuccessfully(
1319 GetUnreachableContinueWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1320 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1321 }
1322
TEST_F(ValidateCFG,WebGPUUnreachableContinueWithBranchUse)1323 TEST_F(ValidateCFG, WebGPUUnreachableContinueWithBranchUse) {
1324 CompileSuccessfully(GetUnreachableContinueWithBranchUse(SpvCapabilityShader,
1325 SPV_ENV_WEBGPU_0));
1326 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1327 EXPECT_THAT(getDiagnosticString(),
1328 HasSubstr("cannot be the target of a branch."));
1329 }
1330
GetReachableMergeAndContinue(SpvCapability cap,spv_target_env env)1331 std::string GetReachableMergeAndContinue(SpvCapability cap,
1332 spv_target_env env) {
1333 std::string header =
1334 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1335
1336 Block entry("entry");
1337 Block branch("branch", SpvOpBranch);
1338 Block merge("merge", SpvOpReturn);
1339 Block target("target", SpvOpBranch);
1340 Block body("body", SpvOpBranchConditional);
1341 Block t("t", SpvOpBranch);
1342 Block f("f", SpvOpBranch);
1343
1344 target >> branch;
1345 body.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1346 t >> merge;
1347 f >> target;
1348
1349 std::string str = header;
1350 if (spvIsWebGPUEnv(env)) {
1351 str +=
1352 "OpEntryPoint Fragment %func \"func\"\n"
1353 "OpExecutionMode %func OriginUpperLeft\n";
1354 }
1355 if (cap == SpvCapabilityShader) {
1356 branch.AppendBody("OpLoopMerge %merge %target None\n");
1357 body.AppendBody("OpSelectionMerge %f None\n");
1358 }
1359
1360 if (!spvIsWebGPUEnv(env))
1361 str += nameOps("branch", "merge", "target", "body", "t", "f",
1362 std::make_pair("func", "Main"));
1363
1364 str += types_consts();
1365 str += "%func = OpFunction %voidt None %funct\n";
1366 str += entry >> branch;
1367 str += branch >> std::vector<Block>({body});
1368 str += body >> std::vector<Block>({t, f});
1369 str += t;
1370 str += f;
1371 str += merge;
1372 str += target;
1373 str += "OpFunctionEnd\n";
1374
1375 return str;
1376 }
1377
TEST_P(ValidateCFG,ReachableMergeAndContinue)1378 TEST_P(ValidateCFG, ReachableMergeAndContinue) {
1379 CompileSuccessfully(
1380 GetReachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1381 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1382 }
1383
TEST_F(ValidateCFG,WebGPUReachableMergeAndContinue)1384 TEST_F(ValidateCFG, WebGPUReachableMergeAndContinue) {
1385 CompileSuccessfully(
1386 GetReachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1387 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
1388 }
1389
GetUnreachableMergeAndContinue(SpvCapability cap,spv_target_env env)1390 std::string GetUnreachableMergeAndContinue(SpvCapability cap,
1391 spv_target_env env) {
1392 std::string header =
1393 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1394
1395 Block entry("entry");
1396 Block branch("branch", SpvOpBranch);
1397 Block merge("merge", SpvOpReturn);
1398 Block target("target", SpvOpBranch);
1399 Block body("body", SpvOpBranchConditional);
1400 Block t("t", SpvOpReturn);
1401 Block f("f", SpvOpReturn);
1402
1403 target >> branch;
1404 body.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1405
1406 std::string str = header;
1407 if (spvIsWebGPUEnv(env)) {
1408 str +=
1409 "OpEntryPoint Fragment %func \"func\"\n"
1410 "OpExecutionMode %func OriginUpperLeft\n";
1411 }
1412 if (cap == SpvCapabilityShader) {
1413 branch.AppendBody("OpLoopMerge %merge %target None\n");
1414 body.AppendBody("OpSelectionMerge %target None\n");
1415 }
1416
1417 if (!spvIsWebGPUEnv(env))
1418 str += nameOps("branch", "merge", "target", "body", "t", "f",
1419 std::make_pair("func", "Main"));
1420
1421 str += types_consts();
1422 str += "%func = OpFunction %voidt None %funct\n";
1423 str += entry >> branch;
1424 str += branch >> std::vector<Block>({body});
1425 str += body >> std::vector<Block>({t, f});
1426 str += t;
1427 str += f;
1428 str += merge;
1429 str += target;
1430 str += "OpFunctionEnd\n";
1431
1432 return str;
1433 }
1434
TEST_P(ValidateCFG,UnreachableMergeAndContinue)1435 TEST_P(ValidateCFG, UnreachableMergeAndContinue) {
1436 CompileSuccessfully(
1437 GetUnreachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1438 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1439 }
1440
TEST_F(ValidateCFG,WebGPUUnreachableMergeAndContinue)1441 TEST_F(ValidateCFG, WebGPUUnreachableMergeAndContinue) {
1442 CompileSuccessfully(
1443 GetUnreachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1444 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1445 EXPECT_THAT(
1446 getDiagnosticString(),
1447 HasSubstr("unreachable merge-blocks must terminate with OpUnreachable"));
1448 }
1449
GetUnreachableBlock(SpvCapability cap,spv_target_env env)1450 std::string GetUnreachableBlock(SpvCapability cap, spv_target_env env) {
1451 std::string header =
1452 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1453
1454 Block entry("entry");
1455 Block unreachable("unreachable");
1456 Block exit("exit", SpvOpReturn);
1457
1458 std::string str = header;
1459 if (spvIsWebGPUEnv(env)) {
1460 str +=
1461 "OpEntryPoint Fragment %func \"func\"\n"
1462 "OpExecutionMode %func OriginUpperLeft\n";
1463 }
1464 if (!spvIsWebGPUEnv(env))
1465 str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
1466 str += types_consts();
1467 str += "%func = OpFunction %voidt None %funct\n";
1468 str += entry >> exit;
1469 str += unreachable >> exit;
1470 str += exit;
1471 str += "OpFunctionEnd\n";
1472
1473 return str;
1474 }
1475
TEST_P(ValidateCFG,UnreachableBlock)1476 TEST_P(ValidateCFG, UnreachableBlock) {
1477 CompileSuccessfully(GetUnreachableBlock(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1478 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1479 }
1480
TEST_F(ValidateCFG,WebGPUUnreachableBlock)1481 TEST_F(ValidateCFG, WebGPUUnreachableBlock) {
1482 CompileSuccessfully(
1483 GetUnreachableBlock(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1484 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1485 EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
1486 }
1487
GetUnreachableBranch(SpvCapability cap,spv_target_env env)1488 std::string GetUnreachableBranch(SpvCapability cap, spv_target_env env) {
1489 std::string header =
1490 spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
1491
1492 Block entry("entry");
1493 Block unreachable("unreachable", SpvOpBranchConditional);
1494 Block unreachablechildt("unreachablechildt");
1495 Block unreachablechildf("unreachablechildf");
1496 Block merge("merge");
1497 Block exit("exit", SpvOpReturn);
1498
1499 unreachable.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1500 if (cap == SpvCapabilityShader)
1501 unreachable.AppendBody("OpSelectionMerge %merge None\n");
1502
1503 std::string str = header;
1504 if (spvIsWebGPUEnv(env)) {
1505 str +=
1506 "OpEntryPoint Fragment %func \"func\"\n"
1507 "OpExecutionMode %func OriginUpperLeft\n";
1508 }
1509 if (!spvIsWebGPUEnv(env))
1510 str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
1511 str += types_consts();
1512 str += "%func = OpFunction %voidt None %funct\n";
1513
1514 str += entry >> exit;
1515 str +=
1516 unreachable >> std::vector<Block>({unreachablechildt, unreachablechildf});
1517 str += unreachablechildt >> merge;
1518 str += unreachablechildf >> merge;
1519 str += merge >> exit;
1520 str += exit;
1521 str += "OpFunctionEnd\n";
1522
1523 return str;
1524 }
1525
TEST_P(ValidateCFG,UnreachableBranch)1526 TEST_P(ValidateCFG, UnreachableBranch) {
1527 CompileSuccessfully(GetUnreachableBranch(GetParam(), SPV_ENV_UNIVERSAL_1_0));
1528 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1529 }
1530
TEST_F(ValidateCFG,WebGPUUnreachableBranch)1531 TEST_F(ValidateCFG, WebGPUUnreachableBranch) {
1532 CompileSuccessfully(
1533 GetUnreachableBranch(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
1534 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
1535 EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
1536 }
1537
TEST_P(ValidateCFG,EmptyFunction)1538 TEST_P(ValidateCFG, EmptyFunction) {
1539 std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
1540 R"(%func = OpFunction %voidt None %funct
1541 %l = OpLabel
1542 OpReturn
1543 OpFunctionEnd)";
1544
1545 CompileSuccessfully(str);
1546 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1547 }
1548
TEST_P(ValidateCFG,SingleBlockLoop)1549 TEST_P(ValidateCFG, SingleBlockLoop) {
1550 bool is_shader = GetParam() == SpvCapabilityShader;
1551 Block entry("entry");
1552 Block loop("loop", SpvOpBranchConditional);
1553 Block exit("exit", SpvOpReturn);
1554
1555 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1556 if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
1557
1558 std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
1559 "%func = OpFunction %voidt None %funct\n";
1560
1561 str += entry >> loop;
1562 str += loop >> std::vector<Block>({loop, exit});
1563 str += exit;
1564 str += "OpFunctionEnd";
1565
1566 CompileSuccessfully(str);
1567 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1568 }
1569
TEST_P(ValidateCFG,NestedLoops)1570 TEST_P(ValidateCFG, NestedLoops) {
1571 bool is_shader = GetParam() == SpvCapabilityShader;
1572 Block entry("entry");
1573 Block loop1("loop1");
1574 Block loop1_cont_break_block("loop1_cont_break_block",
1575 SpvOpBranchConditional);
1576 Block loop2("loop2", SpvOpBranchConditional);
1577 Block loop2_merge("loop2_merge");
1578 Block loop1_merge("loop1_merge");
1579 Block exit("exit", SpvOpReturn);
1580
1581 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1582 if (is_shader) {
1583 loop1.SetBody("OpLoopMerge %loop1_merge %loop2 None\n");
1584 loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
1585 }
1586
1587 std::string str = GetDefaultHeader(GetParam()) +
1588 nameOps("loop2", "loop2_merge") + types_consts() +
1589 "%func = OpFunction %voidt None %funct\n";
1590
1591 str += entry >> loop1;
1592 str += loop1 >> loop1_cont_break_block;
1593 str += loop1_cont_break_block >> std::vector<Block>({loop1_merge, loop2});
1594 str += loop2 >> std::vector<Block>({loop2, loop2_merge});
1595 str += loop2_merge >> loop1;
1596 str += loop1_merge >> exit;
1597 str += exit;
1598 str += "OpFunctionEnd";
1599
1600 CompileSuccessfully(str);
1601 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1602 }
1603
TEST_P(ValidateCFG,NestedSelection)1604 TEST_P(ValidateCFG, NestedSelection) {
1605 bool is_shader = GetParam() == SpvCapabilityShader;
1606 Block entry("entry");
1607 const int N = 256;
1608 std::vector<Block> if_blocks;
1609 std::vector<Block> merge_blocks;
1610 Block inner("inner");
1611
1612 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1613
1614 if_blocks.emplace_back("if0", SpvOpBranchConditional);
1615
1616 if (is_shader) if_blocks[0].SetBody("OpSelectionMerge %if_merge0 None\n");
1617 merge_blocks.emplace_back("if_merge0", SpvOpReturn);
1618
1619 for (int i = 1; i < N; i++) {
1620 std::stringstream ss;
1621 ss << i;
1622 if_blocks.emplace_back("if" + ss.str(), SpvOpBranchConditional);
1623 if (is_shader)
1624 if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
1625 merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch);
1626 }
1627 std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
1628 "%func = OpFunction %voidt None %funct\n";
1629
1630 str += entry >> if_blocks[0];
1631 for (int i = 0; i < N - 1; i++) {
1632 str +=
1633 if_blocks[i] >> std::vector<Block>({if_blocks[i + 1], merge_blocks[i]});
1634 }
1635 str += if_blocks.back() >> std::vector<Block>({inner, merge_blocks.back()});
1636 str += inner >> merge_blocks.back();
1637 for (int i = N - 1; i > 0; i--) {
1638 str += merge_blocks[i] >> merge_blocks[i - 1];
1639 }
1640 str += merge_blocks[0];
1641 str += "OpFunctionEnd";
1642
1643 CompileSuccessfully(str);
1644 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1645 }
1646
TEST_P(ValidateCFG,BackEdgeBlockDoesntPostDominateContinueTargetBad)1647 TEST_P(ValidateCFG, BackEdgeBlockDoesntPostDominateContinueTargetBad) {
1648 bool is_shader = GetParam() == SpvCapabilityShader;
1649 Block entry("entry");
1650 Block loop1("loop1", SpvOpBranchConditional);
1651 Block loop2("loop2", SpvOpBranchConditional);
1652 Block loop2_merge("loop2_merge");
1653 Block loop1_cont("loop1_cont", SpvOpBranchConditional);
1654 Block be_block("be_block");
1655 Block exit("exit", SpvOpReturn);
1656
1657 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1658 if (is_shader) {
1659 loop1.SetBody("OpLoopMerge %exit %loop1_cont None\n");
1660 loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
1661 }
1662
1663 std::string str =
1664 GetDefaultHeader(GetParam()) +
1665 nameOps("loop1", "loop2", "be_block", "loop1_cont", "loop2_merge") +
1666 types_consts() + "%func = OpFunction %voidt None %funct\n";
1667
1668 str += entry >> loop1;
1669 str += loop1 >> std::vector<Block>({loop2, exit});
1670 str += loop2 >> std::vector<Block>({loop2, loop2_merge});
1671 str += loop2_merge >> loop1_cont;
1672 str += loop1_cont >> std::vector<Block>({be_block, exit});
1673 str += be_block >> loop1;
1674 str += exit;
1675 str += "OpFunctionEnd";
1676
1677 CompileSuccessfully(str);
1678 if (GetParam() == SpvCapabilityShader) {
1679 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1680 EXPECT_THAT(getDiagnosticString(),
1681 MatchesRegex("The continue construct with the continue target "
1682 ".\\[%loop1_cont\\] is not post dominated by the "
1683 "back-edge block .\\[%be_block\\]\n"
1684 " %be_block = OpLabel\n"));
1685 } else {
1686 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1687 }
1688 }
1689
TEST_P(ValidateCFG,BranchingToNonLoopHeaderBlockBad)1690 TEST_P(ValidateCFG, BranchingToNonLoopHeaderBlockBad) {
1691 bool is_shader = GetParam() == SpvCapabilityShader;
1692 Block entry("entry");
1693 Block split("split", SpvOpBranchConditional);
1694 Block t("t");
1695 Block f("f");
1696 Block exit("exit", SpvOpReturn);
1697
1698 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1699 if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
1700
1701 std::string str = GetDefaultHeader(GetParam()) + nameOps("split", "f") +
1702 types_consts() +
1703 "%func = OpFunction %voidt None %funct\n";
1704
1705 str += entry >> split;
1706 str += split >> std::vector<Block>({t, f});
1707 str += t >> exit;
1708 str += f >> split;
1709 str += exit;
1710 str += "OpFunctionEnd";
1711
1712 CompileSuccessfully(str);
1713 if (is_shader) {
1714 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1715 EXPECT_THAT(
1716 getDiagnosticString(),
1717 MatchesRegex("Back-edges \\(.\\[%f\\] -> .\\[%split\\]\\) can only "
1718 "be formed between a block and a loop header.\n"
1719 " %f = OpLabel\n"));
1720 } else {
1721 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1722 }
1723 }
1724
TEST_P(ValidateCFG,BranchingToSameNonLoopHeaderBlockBad)1725 TEST_P(ValidateCFG, BranchingToSameNonLoopHeaderBlockBad) {
1726 bool is_shader = GetParam() == SpvCapabilityShader;
1727 Block entry("entry");
1728 Block split("split", SpvOpBranchConditional);
1729 Block exit("exit", SpvOpReturn);
1730
1731 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1732 if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
1733
1734 std::string str = GetDefaultHeader(GetParam()) + nameOps("split") +
1735 types_consts() +
1736 "%func = OpFunction %voidt None %funct\n";
1737
1738 str += entry >> split;
1739 str += split >> std::vector<Block>({split, exit});
1740 str += exit;
1741 str += "OpFunctionEnd";
1742
1743 CompileSuccessfully(str);
1744 if (is_shader) {
1745 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1746 EXPECT_THAT(
1747 getDiagnosticString(),
1748 MatchesRegex(
1749 "Back-edges \\(.\\[%split\\] -> .\\[%split\\]\\) can only be "
1750 "formed between a block and a loop header.\n %split = OpLabel\n"));
1751 } else {
1752 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1753 }
1754 }
1755
TEST_P(ValidateCFG,MultipleBackEdgeBlocksToLoopHeaderBad)1756 TEST_P(ValidateCFG, MultipleBackEdgeBlocksToLoopHeaderBad) {
1757 bool is_shader = GetParam() == SpvCapabilityShader;
1758 Block entry("entry");
1759 Block loop("loop", SpvOpBranchConditional);
1760 Block back0("back0");
1761 Block back1("back1");
1762 Block merge("merge", SpvOpReturn);
1763
1764 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1765 if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
1766
1767 std::string str = GetDefaultHeader(GetParam()) +
1768 nameOps("loop", "back0", "back1") + types_consts() +
1769 "%func = OpFunction %voidt None %funct\n";
1770
1771 str += entry >> loop;
1772 str += loop >> std::vector<Block>({back0, back1});
1773 str += back0 >> loop;
1774 str += back1 >> loop;
1775 str += merge;
1776 str += "OpFunctionEnd";
1777
1778 CompileSuccessfully(str);
1779 if (is_shader) {
1780 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1781 EXPECT_THAT(
1782 getDiagnosticString(),
1783 MatchesRegex(
1784 "Loop header .\\[%loop\\] is targeted by 2 back-edge blocks but "
1785 "the standard requires exactly one\n %loop = OpLabel\n"))
1786 << str;
1787 } else {
1788 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1789 }
1790 }
1791
TEST_P(ValidateCFG,ContinueTargetMustBePostDominatedByBackEdge)1792 TEST_P(ValidateCFG, ContinueTargetMustBePostDominatedByBackEdge) {
1793 bool is_shader = GetParam() == SpvCapabilityShader;
1794 Block entry("entry");
1795 Block loop("loop", SpvOpBranchConditional);
1796 Block cheader("cheader", SpvOpBranchConditional);
1797 Block be_block("be_block");
1798 Block merge("merge", SpvOpReturn);
1799 Block exit("exit", SpvOpReturn);
1800
1801 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1802 if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
1803
1804 std::string str = GetDefaultHeader(GetParam()) +
1805 nameOps("cheader", "be_block") + types_consts() +
1806 "%func = OpFunction %voidt None %funct\n";
1807
1808 str += entry >> loop;
1809 str += loop >> std::vector<Block>({cheader, merge});
1810 str += cheader >> std::vector<Block>({exit, be_block});
1811 str += exit; // Branches out of a continue construct
1812 str += be_block >> loop;
1813 str += merge;
1814 str += "OpFunctionEnd";
1815
1816 CompileSuccessfully(str);
1817 if (is_shader) {
1818 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1819 EXPECT_THAT(getDiagnosticString(),
1820 MatchesRegex("The continue construct with the continue target "
1821 ".\\[%cheader\\] is not post dominated by the "
1822 "back-edge block .\\[%be_block\\]\n"
1823 " %be_block = OpLabel\n"));
1824 } else {
1825 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1826 }
1827 }
1828
TEST_P(ValidateCFG,BranchOutOfConstructToMergeBad)1829 TEST_P(ValidateCFG, BranchOutOfConstructToMergeBad) {
1830 bool is_shader = GetParam() == SpvCapabilityShader;
1831 Block entry("entry");
1832 Block loop("loop", SpvOpBranchConditional);
1833 Block cont("cont", SpvOpBranchConditional);
1834 Block merge("merge", SpvOpReturn);
1835
1836 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1837 if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
1838
1839 std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
1840 types_consts() +
1841 "%func = OpFunction %voidt None %funct\n";
1842
1843 str += entry >> loop;
1844 str += loop >> std::vector<Block>({cont, merge});
1845 str += cont >> std::vector<Block>({loop, merge});
1846 str += merge;
1847 str += "OpFunctionEnd";
1848
1849 CompileSuccessfully(str);
1850 if (is_shader) {
1851 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1852 EXPECT_THAT(getDiagnosticString(),
1853 MatchesRegex("The continue construct with the continue target "
1854 ".\\[%loop\\] is not post dominated by the "
1855 "back-edge block .\\[%cont\\]\n"
1856 " %cont = OpLabel\n"))
1857 << str;
1858 } else {
1859 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1860 }
1861 }
1862
TEST_P(ValidateCFG,BranchOutOfConstructBad)1863 TEST_P(ValidateCFG, BranchOutOfConstructBad) {
1864 bool is_shader = GetParam() == SpvCapabilityShader;
1865 Block entry("entry");
1866 Block loop("loop", SpvOpBranchConditional);
1867 Block cont("cont", SpvOpBranchConditional);
1868 Block merge("merge");
1869 Block exit("exit", SpvOpReturn);
1870
1871 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
1872 if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
1873
1874 std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
1875 types_consts() +
1876 "%func = OpFunction %voidt None %funct\n";
1877
1878 str += entry >> loop;
1879 str += loop >> std::vector<Block>({cont, merge});
1880 str += cont >> std::vector<Block>({loop, exit});
1881 str += merge >> exit;
1882 str += exit;
1883 str += "OpFunctionEnd";
1884
1885 CompileSuccessfully(str);
1886 if (is_shader) {
1887 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1888 EXPECT_THAT(getDiagnosticString(),
1889 MatchesRegex("The continue construct with the continue target "
1890 ".\\[%loop\\] is not post dominated by the "
1891 "back-edge block .\\[%cont\\]\n"
1892 " %cont = OpLabel\n"));
1893 } else {
1894 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
1895 }
1896 }
1897
TEST_F(ValidateCFG,OpSwitchToUnreachableBlock)1898 TEST_F(ValidateCFG, OpSwitchToUnreachableBlock) {
1899 Block entry("entry", SpvOpSwitch);
1900 Block case0("case0");
1901 Block case1("case1");
1902 Block case2("case2");
1903 Block def("default", SpvOpUnreachable);
1904 Block phi("phi", SpvOpReturn);
1905
1906 std::string str = R"(
1907 OpCapability Shader
1908 OpMemoryModel Logical GLSL450
1909 OpEntryPoint GLCompute %main "main" %id
1910 OpExecutionMode %main LocalSize 1 1 1
1911 OpSource GLSL 430
1912 OpName %main "main"
1913 OpDecorate %id BuiltIn GlobalInvocationId
1914 %void = OpTypeVoid
1915 %voidf = OpTypeFunction %void
1916 %u32 = OpTypeInt 32 0
1917 %f32 = OpTypeFloat 32
1918 %uvec3 = OpTypeVector %u32 3
1919 %fvec3 = OpTypeVector %f32 3
1920 %uvec3ptr = OpTypePointer Input %uvec3
1921 %id = OpVariable %uvec3ptr Input
1922 %one = OpConstant %u32 1
1923 %three = OpConstant %u32 3
1924 %main = OpFunction %void None %voidf
1925 )";
1926
1927 entry.SetBody(
1928 "%idval = OpLoad %uvec3 %id\n"
1929 "%x = OpCompositeExtract %u32 %idval 0\n"
1930 "%selector = OpUMod %u32 %x %three\n"
1931 "OpSelectionMerge %phi None\n");
1932 str += entry >> std::vector<Block>({def, case0, case1, case2});
1933 str += case1 >> phi;
1934 str += def;
1935 str += phi;
1936 str += case0 >> phi;
1937 str += case2 >> phi;
1938 str += "OpFunctionEnd";
1939
1940 CompileSuccessfully(str);
1941 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
1942 }
1943
TEST_F(ValidateCFG,LoopWithZeroBackEdgesBad)1944 TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
1945 std::string str = R"(
1946 OpCapability Shader
1947 OpMemoryModel Logical GLSL450
1948 OpEntryPoint Fragment %main "main"
1949 OpExecutionMode %main OriginUpperLeft
1950 OpName %loop "loop"
1951 %voidt = OpTypeVoid
1952 %funct = OpTypeFunction %voidt
1953 %main = OpFunction %voidt None %funct
1954 %loop = OpLabel
1955 OpLoopMerge %exit %loop None
1956 OpBranch %exit
1957 %exit = OpLabel
1958 OpReturn
1959 OpFunctionEnd
1960 )";
1961 CompileSuccessfully(str);
1962 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
1963 EXPECT_THAT(
1964 getDiagnosticString(),
1965 MatchesRegex("Loop header .\\[%loop\\] is targeted by "
1966 "0 back-edge blocks but the standard requires exactly "
1967 "one\n %loop = OpLabel\n"));
1968 }
1969
TEST_F(ValidateCFG,LoopWithBackEdgeFromUnreachableContinueConstructGood)1970 TEST_F(ValidateCFG, LoopWithBackEdgeFromUnreachableContinueConstructGood) {
1971 std::string str = R"(
1972 OpCapability Shader
1973 OpMemoryModel Logical GLSL450
1974 OpEntryPoint Fragment %main "main"
1975 OpExecutionMode %main OriginUpperLeft
1976 OpName %loop "loop"
1977 %voidt = OpTypeVoid
1978 %funct = OpTypeFunction %voidt
1979 %floatt = OpTypeFloat 32
1980 %boolt = OpTypeBool
1981 %one = OpConstant %floatt 1
1982 %two = OpConstant %floatt 2
1983 %main = OpFunction %voidt None %funct
1984 %entry = OpLabel
1985 OpBranch %loop
1986 %loop = OpLabel
1987 OpLoopMerge %exit %cont None
1988 OpBranch %16
1989 %16 = OpLabel
1990 %cond = OpFOrdLessThan %boolt %one %two
1991 OpBranchConditional %cond %body %exit
1992 %body = OpLabel
1993 OpReturn
1994 %cont = OpLabel ; Reachable only from OpLoopMerge ContinueTarget parameter
1995 OpBranch %loop ; Should be considered a back-edge
1996 %exit = OpLabel
1997 OpReturn
1998 OpFunctionEnd
1999 )";
2000
2001 CompileSuccessfully(str);
2002 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
2003 }
2004
TEST_P(ValidateCFG,NestedConstructWithUnreachableMergeBlockBranchingToOuterMergeBlock)2005 TEST_P(ValidateCFG,
2006 NestedConstructWithUnreachableMergeBlockBranchingToOuterMergeBlock) {
2007 // Test for https://github.com/KhronosGroup/SPIRV-Tools/issues/297
2008 // The nested construct has an unreachable merge block. In the
2009 // augmented CFG that merge block
2010 // we still determine that the
2011 bool is_shader = GetParam() == SpvCapabilityShader;
2012 Block entry("entry", SpvOpBranchConditional);
2013 Block inner_head("inner_head", SpvOpBranchConditional);
2014 Block inner_true("inner_true", SpvOpReturn);
2015 Block inner_false("inner_false", SpvOpReturn);
2016 Block inner_merge("inner_merge");
2017 Block exit("exit", SpvOpReturn);
2018
2019 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
2020 if (is_shader) {
2021 entry.AppendBody("OpSelectionMerge %exit None\n");
2022 inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
2023 }
2024
2025 std::string str = GetDefaultHeader(GetParam()) +
2026 nameOps("entry", "inner_merge", "exit") + types_consts() +
2027 "%func = OpFunction %voidt None %funct\n";
2028
2029 str += entry >> std::vector<Block>({inner_head, exit});
2030 str += inner_head >> std::vector<Block>({inner_true, inner_false});
2031 str += inner_true;
2032 str += inner_false;
2033 str += inner_merge >> exit;
2034 str += exit;
2035 str += "OpFunctionEnd";
2036
2037 CompileSuccessfully(str);
2038 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
2039 }
2040
TEST_P(ValidateCFG,ContinueTargetCanBeMergeBlockForNestedStructure)2041 TEST_P(ValidateCFG, ContinueTargetCanBeMergeBlockForNestedStructure) {
2042 // The continue construct cannot be the merge target of a nested selection
2043 // because the loop construct must contain "if_merge" because it contains
2044 // "if_head".
2045 bool is_shader = GetParam() == SpvCapabilityShader;
2046 Block entry("entry");
2047 Block loop("loop");
2048 Block if_head("if_head", SpvOpBranchConditional);
2049 Block if_true("if_true");
2050 Block if_merge("if_merge", SpvOpBranchConditional);
2051 Block merge("merge", SpvOpReturn);
2052
2053 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
2054 if (is_shader) {
2055 loop.SetBody("OpLoopMerge %merge %if_merge None\n");
2056 if_head.SetBody("OpSelectionMerge %if_merge None\n");
2057 }
2058
2059 std::string str =
2060 GetDefaultHeader(GetParam()) +
2061 nameOps("entry", "loop", "if_head", "if_true", "if_merge", "merge") +
2062 types_consts() + "%func = OpFunction %voidt None %funct\n";
2063
2064 str += entry >> loop;
2065 str += loop >> if_head;
2066 str += if_head >> std::vector<Block>({if_true, if_merge});
2067 str += if_true >> if_merge;
2068 str += if_merge >> std::vector<Block>({loop, merge});
2069 str += merge;
2070 str += "OpFunctionEnd";
2071
2072 CompileSuccessfully(str);
2073 if (is_shader) {
2074 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2075 EXPECT_THAT(
2076 getDiagnosticString(),
2077 HasSubstr("Header block 3[%if_head] is contained in the loop construct "
2078 "headed "
2079 "by 2[%loop], but its merge block 5[%if_merge] is not"));
2080 } else {
2081 EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
2082 }
2083 }
2084
TEST_P(ValidateCFG,SingleLatchBlockMultipleBranchesToLoopHeader)2085 TEST_P(ValidateCFG, SingleLatchBlockMultipleBranchesToLoopHeader) {
2086 // This test case ensures we allow both branches of a loop latch block
2087 // to go back to the loop header. It still counts as a single back edge.
2088 bool is_shader = GetParam() == SpvCapabilityShader;
2089 Block entry("entry");
2090 Block loop("loop", SpvOpBranchConditional);
2091 Block latch("latch", SpvOpBranchConditional);
2092 Block merge("merge", SpvOpReturn);
2093
2094 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
2095 if (is_shader) {
2096 loop.SetBody("OpLoopMerge %merge %latch None\n");
2097 }
2098
2099 std::string str = GetDefaultHeader(GetParam()) +
2100 nameOps("entry", "loop", "latch", "merge") +
2101 types_consts() +
2102 "%func = OpFunction %voidt None %funct\n";
2103
2104 str += entry >> loop;
2105 str += loop >> std::vector<Block>({latch, merge});
2106 str += latch >> std::vector<Block>({loop, loop}); // This is the key
2107 str += merge;
2108 str += "OpFunctionEnd";
2109
2110 CompileSuccessfully(str);
2111 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions())
2112 << str << getDiagnosticString();
2113 }
2114
TEST_P(ValidateCFG,SingleLatchBlockHeaderContinueTargetIsItselfGood)2115 TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) {
2116 // This test case ensures we don't count a Continue Target from a loop
2117 // header to itself as a self-loop when computing back edges.
2118 // Also, it detects that there is an edge from %latch to the pseudo-exit
2119 // node, rather than from %loop. In particular, it detects that we
2120 // have used the *reverse* textual order of blocks when computing
2121 // predecessor traversal roots.
2122 bool is_shader = GetParam() == SpvCapabilityShader;
2123 Block entry("entry");
2124 Block loop("loop");
2125 Block latch("latch");
2126 Block merge("merge", SpvOpReturn);
2127
2128 entry.SetBody("%cond = OpSLessThan %boolt %one %two\n");
2129 if (is_shader) {
2130 loop.SetBody("OpLoopMerge %merge %loop None\n");
2131 }
2132
2133 std::string str = GetDefaultHeader(GetParam()) +
2134 nameOps("entry", "loop", "latch", "merge") +
2135 types_consts() +
2136 "%func = OpFunction %voidt None %funct\n";
2137
2138 str += entry >> loop;
2139 str += loop >> latch;
2140 str += latch >> loop;
2141 str += merge;
2142 str += "OpFunctionEnd";
2143
2144 CompileSuccessfully(str);
2145 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions())
2146 << str << getDiagnosticString();
2147 }
2148
2149 // Unit test to check the case where a basic block is the entry block of 2
2150 // different constructs. In this case, the basic block is the entry block of a
2151 // continue construct as well as a selection construct. See issue# 517 for more
2152 // details.
TEST_F(ValidateCFG,BasicBlockIsEntryBlockOfTwoConstructsGood)2153 TEST_F(ValidateCFG, BasicBlockIsEntryBlockOfTwoConstructsGood) {
2154 std::string spirv = R"(
2155 OpCapability Shader
2156 OpCapability Linkage
2157 OpMemoryModel Logical GLSL450
2158 %void = OpTypeVoid
2159 %bool = OpTypeBool
2160 %int = OpTypeInt 32 1
2161 %void_func = OpTypeFunction %void
2162 %int_0 = OpConstant %int 0
2163 %testfun = OpFunction %void None %void_func
2164 %label_1 = OpLabel
2165 OpBranch %start
2166 %start = OpLabel
2167 %cond = OpSLessThan %bool %int_0 %int_0
2168 ;
2169 ; Note: In this case, the "target" block is both the entry block of
2170 ; the continue construct of the loop as well as the entry block of
2171 ; the selection construct.
2172 ;
2173 OpLoopMerge %loop_merge %target None
2174 OpBranchConditional %cond %target %loop_merge
2175 %loop_merge = OpLabel
2176 OpReturn
2177 %target = OpLabel
2178 OpSelectionMerge %selection_merge None
2179 OpBranchConditional %cond %do_stuff %do_other_stuff
2180 %do_other_stuff = OpLabel
2181 OpBranch %selection_merge
2182 %selection_merge = OpLabel
2183 OpBranch %start
2184 %do_stuff = OpLabel
2185 OpBranch %selection_merge
2186 OpFunctionEnd
2187 )";
2188 CompileSuccessfully(spirv);
2189 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
2190 }
2191
TEST_F(ValidateCFG,OpReturnInNonVoidFunc)2192 TEST_F(ValidateCFG, OpReturnInNonVoidFunc) {
2193 std::string spirv = R"(
2194 OpCapability Shader
2195 OpCapability Linkage
2196 OpMemoryModel Logical GLSL450
2197 %int = OpTypeInt 32 1
2198 %int_func = OpTypeFunction %int
2199 %testfun = OpFunction %int None %int_func
2200 %label_1 = OpLabel
2201 OpReturn
2202 OpFunctionEnd
2203 )";
2204 CompileSuccessfully(spirv);
2205 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2206 EXPECT_THAT(
2207 getDiagnosticString(),
2208 HasSubstr(
2209 "OpReturn can only be called from a function with void return type.\n"
2210 " OpReturn"));
2211 }
2212
TEST_F(ValidateCFG,StructuredCFGBranchIntoSelectionBody)2213 TEST_F(ValidateCFG, StructuredCFGBranchIntoSelectionBody) {
2214 std::string spirv = R"(
2215 OpCapability Shader
2216 OpMemoryModel Logical GLSL450
2217 OpEntryPoint Fragment %func "func"
2218 OpExecutionMode %func OriginUpperLeft
2219 %void = OpTypeVoid
2220 %bool = OpTypeBool
2221 %true = OpConstantTrue %bool
2222 %functy = OpTypeFunction %void
2223 %func = OpFunction %void None %functy
2224 %entry = OpLabel
2225 OpSelectionMerge %merge None
2226 OpBranchConditional %true %then %merge
2227 %merge = OpLabel
2228 OpBranch %then
2229 %then = OpLabel
2230 OpReturn
2231 OpFunctionEnd
2232 )";
2233
2234 CompileSuccessfully(spirv);
2235 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2236 EXPECT_THAT(getDiagnosticString(),
2237 HasSubstr("branches to the selection construct, but not to the "
2238 "selection header <ID> 6\n %7 = OpLabel"));
2239 }
2240
TEST_F(ValidateCFG,SwitchDefaultOnly)2241 TEST_F(ValidateCFG, SwitchDefaultOnly) {
2242 std::string text = R"(
2243 OpCapability Shader
2244 OpCapability Linkage
2245 OpMemoryModel Logical GLSL450
2246 %1 = OpTypeVoid
2247 %2 = OpTypeInt 32 0
2248 %3 = OpConstant %2 0
2249 %4 = OpTypeFunction %1
2250 %5 = OpFunction %1 None %4
2251 %6 = OpLabel
2252 OpSelectionMerge %7 None
2253 OpSwitch %3 %7
2254 %7 = OpLabel
2255 OpReturn
2256 OpFunctionEnd
2257 )";
2258
2259 CompileSuccessfully(text);
2260 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
2261 }
2262
TEST_F(ValidateCFG,SwitchSingleCase)2263 TEST_F(ValidateCFG, SwitchSingleCase) {
2264 std::string text = R"(
2265 OpCapability Shader
2266 OpCapability Linkage
2267 OpMemoryModel Logical GLSL450
2268 %1 = OpTypeVoid
2269 %2 = OpTypeInt 32 0
2270 %3 = OpConstant %2 0
2271 %4 = OpTypeFunction %1
2272 %5 = OpFunction %1 None %4
2273 %6 = OpLabel
2274 OpSelectionMerge %7 None
2275 OpSwitch %3 %7 0 %8
2276 %8 = OpLabel
2277 OpBranch %7
2278 %7 = OpLabel
2279 OpReturn
2280 OpFunctionEnd
2281 )";
2282
2283 CompileSuccessfully(text);
2284 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
2285 }
2286
TEST_F(ValidateCFG,MultipleFallThroughBlocks)2287 TEST_F(ValidateCFG, MultipleFallThroughBlocks) {
2288 std::string text = R"(
2289 OpCapability Shader
2290 OpCapability Linkage
2291 OpMemoryModel Logical GLSL450
2292 %1 = OpTypeVoid
2293 %2 = OpTypeInt 32 0
2294 %3 = OpConstant %2 0
2295 %4 = OpTypeFunction %1
2296 %5 = OpTypeBool
2297 %6 = OpConstantTrue %5
2298 %7 = OpFunction %1 None %4
2299 %8 = OpLabel
2300 OpSelectionMerge %9 None
2301 OpSwitch %3 %10 0 %11 1 %12
2302 %10 = OpLabel
2303 OpBranchConditional %6 %11 %12
2304 %11 = OpLabel
2305 OpBranch %9
2306 %12 = OpLabel
2307 OpBranch %9
2308 %9 = OpLabel
2309 OpReturn
2310 OpFunctionEnd
2311 )";
2312
2313 CompileSuccessfully(text);
2314 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2315 EXPECT_THAT(
2316 getDiagnosticString(),
2317 HasSubstr(
2318 "Case construct that targets 10[%10] has branches to multiple other "
2319 "case construct targets 12[%12] and 11[%11]\n %10 = OpLabel"));
2320 }
2321
TEST_F(ValidateCFG,MultipleFallThroughToDefault)2322 TEST_F(ValidateCFG, MultipleFallThroughToDefault) {
2323 std::string text = R"(
2324 OpCapability Shader
2325 OpCapability Linkage
2326 OpMemoryModel Logical GLSL450
2327 %1 = OpTypeVoid
2328 %2 = OpTypeInt 32 0
2329 %3 = OpConstant %2 0
2330 %4 = OpTypeFunction %1
2331 %5 = OpTypeBool
2332 %6 = OpConstantTrue %5
2333 %7 = OpFunction %1 None %4
2334 %8 = OpLabel
2335 OpSelectionMerge %9 None
2336 OpSwitch %3 %10 0 %11 1 %12
2337 %10 = OpLabel
2338 OpBranch %9
2339 %11 = OpLabel
2340 OpBranch %10
2341 %12 = OpLabel
2342 OpBranch %10
2343 %9 = OpLabel
2344 OpReturn
2345 OpFunctionEnd
2346 )";
2347
2348 CompileSuccessfully(text);
2349 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2350 EXPECT_THAT(
2351 getDiagnosticString(),
2352 HasSubstr("Multiple case constructs have branches to the case construct "
2353 "that targets 10[%10]\n %10 = OpLabel"));
2354 }
2355
TEST_F(ValidateCFG,MultipleFallThroughToNonDefault)2356 TEST_F(ValidateCFG, MultipleFallThroughToNonDefault) {
2357 std::string text = R"(
2358 OpCapability Shader
2359 OpCapability Linkage
2360 OpMemoryModel Logical GLSL450
2361 %1 = OpTypeVoid
2362 %2 = OpTypeInt 32 0
2363 %3 = OpConstant %2 0
2364 %4 = OpTypeFunction %1
2365 %5 = OpTypeBool
2366 %6 = OpConstantTrue %5
2367 %7 = OpFunction %1 None %4
2368 %8 = OpLabel
2369 OpSelectionMerge %9 None
2370 OpSwitch %3 %10 0 %11 1 %12
2371 %10 = OpLabel
2372 OpBranch %12
2373 %11 = OpLabel
2374 OpBranch %12
2375 %12 = OpLabel
2376 OpBranch %9
2377 %9 = OpLabel
2378 OpReturn
2379 OpFunctionEnd
2380 )";
2381
2382 CompileSuccessfully(text);
2383 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2384 EXPECT_THAT(
2385 getDiagnosticString(),
2386 HasSubstr("Multiple case constructs have branches to the case construct "
2387 "that targets 12[%12]\n %12 = OpLabel"));
2388 }
2389
TEST_F(ValidateCFG,DuplicateTargetWithFallThrough)2390 TEST_F(ValidateCFG, DuplicateTargetWithFallThrough) {
2391 std::string text = R"(
2392 OpCapability Shader
2393 OpCapability Linkage
2394 OpMemoryModel Logical GLSL450
2395 %1 = OpTypeVoid
2396 %2 = OpTypeInt 32 0
2397 %3 = OpConstant %2 0
2398 %4 = OpTypeFunction %1
2399 %5 = OpTypeBool
2400 %6 = OpConstantTrue %5
2401 %7 = OpFunction %1 None %4
2402 %8 = OpLabel
2403 OpSelectionMerge %9 None
2404 OpSwitch %3 %10 0 %10 1 %11
2405 %10 = OpLabel
2406 OpBranch %11
2407 %11 = OpLabel
2408 OpBranch %9
2409 %9 = OpLabel
2410 OpReturn
2411 OpFunctionEnd
2412 )";
2413
2414 CompileSuccessfully(text);
2415 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
2416 }
2417
TEST_F(ValidateCFG,WrongOperandList)2418 TEST_F(ValidateCFG, WrongOperandList) {
2419 std::string text = R"(
2420 OpCapability Shader
2421 OpCapability Linkage
2422 OpMemoryModel Logical GLSL450
2423 %1 = OpTypeVoid
2424 %2 = OpTypeInt 32 0
2425 %3 = OpConstant %2 0
2426 %4 = OpTypeFunction %1
2427 %5 = OpTypeBool
2428 %6 = OpConstantTrue %5
2429 %7 = OpFunction %1 None %4
2430 %8 = OpLabel
2431 OpSelectionMerge %9 None
2432 OpSwitch %3 %10 0 %11 1 %12
2433 %10 = OpLabel
2434 OpBranch %9
2435 %12 = OpLabel
2436 OpBranch %11
2437 %11 = OpLabel
2438 OpBranch %9
2439 %9 = OpLabel
2440 OpReturn
2441 OpFunctionEnd
2442 )";
2443
2444 CompileSuccessfully(text);
2445 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2446 EXPECT_THAT(
2447 getDiagnosticString(),
2448 HasSubstr("Case construct that targets 12[%12] has branches to the case "
2449 "construct that targets 11[%11], but does not immediately "
2450 "precede it in the OpSwitch's target list\n"
2451 " OpSwitch %uint_0 %10 0 %11 1 %12"));
2452 }
2453
TEST_F(ValidateCFG,WrongOperandListThroughDefault)2454 TEST_F(ValidateCFG, WrongOperandListThroughDefault) {
2455 std::string text = R"(
2456 OpCapability Shader
2457 OpCapability Linkage
2458 OpMemoryModel Logical GLSL450
2459 %1 = OpTypeVoid
2460 %2 = OpTypeInt 32 0
2461 %3 = OpConstant %2 0
2462 %4 = OpTypeFunction %1
2463 %5 = OpTypeBool
2464 %6 = OpConstantTrue %5
2465 %7 = OpFunction %1 None %4
2466 %8 = OpLabel
2467 OpSelectionMerge %9 None
2468 OpSwitch %3 %10 0 %11 1 %12
2469 %10 = OpLabel
2470 OpBranch %11
2471 %12 = OpLabel
2472 OpBranch %10
2473 %11 = OpLabel
2474 OpBranch %9
2475 %9 = OpLabel
2476 OpReturn
2477 OpFunctionEnd
2478 )";
2479
2480 CompileSuccessfully(text);
2481 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2482 EXPECT_THAT(
2483 getDiagnosticString(),
2484 HasSubstr("Case construct that targets 12[%12] has branches to the case "
2485 "construct that targets 11[%11], but does not immediately "
2486 "precede it in the OpSwitch's target list\n"
2487 " OpSwitch %uint_0 %10 0 %11 1 %12"));
2488 }
2489
TEST_F(ValidateCFG,WrongOperandListNotLast)2490 TEST_F(ValidateCFG, WrongOperandListNotLast) {
2491 std::string text = R"(
2492 OpCapability Shader
2493 OpCapability Linkage
2494 OpMemoryModel Logical GLSL450
2495 %1 = OpTypeVoid
2496 %2 = OpTypeInt 32 0
2497 %3 = OpConstant %2 0
2498 %4 = OpTypeFunction %1
2499 %5 = OpTypeBool
2500 %6 = OpConstantTrue %5
2501 %7 = OpFunction %1 None %4
2502 %8 = OpLabel
2503 OpSelectionMerge %9 None
2504 OpSwitch %3 %10 0 %11 1 %12 2 %13
2505 %10 = OpLabel
2506 OpBranch %9
2507 %12 = OpLabel
2508 OpBranch %11
2509 %11 = OpLabel
2510 OpBranch %9
2511 %13 = OpLabel
2512 OpBranch %9
2513 %9 = OpLabel
2514 OpReturn
2515 OpFunctionEnd
2516 )";
2517
2518 CompileSuccessfully(text);
2519 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2520 EXPECT_THAT(
2521 getDiagnosticString(),
2522 HasSubstr("Case construct that targets 12[%12] has branches to the case "
2523 "construct that targets 11[%11], but does not immediately "
2524 "precede it in the OpSwitch's target list\n"
2525 " OpSwitch %uint_0 %10 0 %11 1 %12 2 %13"));
2526 }
2527
TEST_F(ValidateCFG,GoodUnreachableSwitch)2528 TEST_F(ValidateCFG, GoodUnreachableSwitch) {
2529 const std::string text = R"(
2530 OpCapability Shader
2531 OpMemoryModel Logical GLSL450
2532 OpEntryPoint Fragment %2 "main"
2533 OpExecutionMode %2 OriginUpperLeft
2534 %3 = OpTypeVoid
2535 %4 = OpTypeFunction %3
2536 %5 = OpTypeBool
2537 %6 = OpConstantTrue %5
2538 %7 = OpTypeInt 32 1
2539 %9 = OpConstant %7 0
2540 %2 = OpFunction %3 None %4
2541 %10 = OpLabel
2542 OpSelectionMerge %11 None
2543 OpBranchConditional %6 %12 %13
2544 %12 = OpLabel
2545 OpReturn
2546 %13 = OpLabel
2547 OpReturn
2548 %11 = OpLabel
2549 OpSelectionMerge %14 None
2550 OpSwitch %9 %14 0 %15
2551 %15 = OpLabel
2552 OpBranch %14
2553 %14 = OpLabel
2554 OpReturn
2555 OpFunctionEnd
2556 )";
2557
2558 CompileSuccessfully(text);
2559 EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
2560 }
2561
TEST_F(ValidateCFG,InvalidCaseExit)2562 TEST_F(ValidateCFG, InvalidCaseExit) {
2563 const std::string text = R"(
2564 OpCapability Shader
2565 OpMemoryModel Logical GLSL450
2566 OpEntryPoint Fragment %1 "func"
2567 OpExecutionMode %1 OriginUpperLeft
2568 %2 = OpTypeVoid
2569 %3 = OpTypeInt 32 0
2570 %4 = OpTypeFunction %2
2571 %5 = OpConstant %3 0
2572 %1 = OpFunction %2 None %4
2573 %6 = OpLabel
2574 OpSelectionMerge %7 None
2575 OpSwitch %5 %7 0 %8 1 %9
2576 %8 = OpLabel
2577 OpBranch %10
2578 %9 = OpLabel
2579 OpBranch %10
2580 %10 = OpLabel
2581 OpReturn
2582 %7 = OpLabel
2583 OpReturn
2584 OpFunctionEnd
2585 )";
2586
2587 CompileSuccessfully(text);
2588 ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2589 EXPECT_THAT(getDiagnosticString(),
2590 HasSubstr("Case construct that targets 8[%8] has invalid branch "
2591 "to block 10[%10] (not another case construct, "
2592 "corresponding merge, outer loop merge or outer loop "
2593 "continue)"));
2594 }
2595
TEST_F(ValidateCFG,GoodCaseExitsToOuterConstructs)2596 TEST_F(ValidateCFG, GoodCaseExitsToOuterConstructs) {
2597 const std::string text = R"(
2598 OpCapability Shader
2599 OpMemoryModel Logical GLSL450
2600 OpEntryPoint Fragment %func "func"
2601 OpExecutionMode %func OriginUpperLeft
2602 %void = OpTypeVoid
2603 %bool = OpTypeBool
2604 %true = OpConstantTrue %bool
2605 %int = OpTypeInt 32 0
2606 %int0 = OpConstant %int 0
2607 %func_ty = OpTypeFunction %void
2608 %func = OpFunction %void None %func_ty
2609 %1 = OpLabel
2610 OpBranch %2
2611 %2 = OpLabel
2612 OpLoopMerge %7 %6 None
2613 OpBranch %3
2614 %3 = OpLabel
2615 OpSelectionMerge %5 None
2616 OpSwitch %int0 %5 0 %4
2617 %4 = OpLabel
2618 OpBranchConditional %true %6 %7
2619 %5 = OpLabel
2620 OpBranchConditional %true %6 %7
2621 %6 = OpLabel
2622 OpBranch %2
2623 %7 = OpLabel
2624 OpReturn
2625 OpFunctionEnd
2626 )";
2627
2628 CompileSuccessfully(text);
2629 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
2630 }
2631
TEST_F(ValidateCFG,SwitchCaseOrderingBad1)2632 TEST_F(ValidateCFG, SwitchCaseOrderingBad1) {
2633 const std::string text = R"(
2634 OpCapability Shader
2635 OpCapability Linkage
2636 OpMemoryModel Logical GLSL450
2637 OpName %default "default"
2638 OpName %other "other"
2639 %void = OpTypeVoid
2640 %int = OpTypeInt 32 0
2641 %undef = OpUndef %int
2642 %void_fn = OpTypeFunction %void
2643 %func = OpFunction %void None %void_fn
2644 %entry = OpLabel
2645 OpSelectionMerge %merge None
2646 OpSwitch %undef %default 0 %other 1 %default
2647 %default = OpLabel
2648 OpBranch %other
2649 %other = OpLabel
2650 OpBranch %merge
2651 %merge = OpLabel
2652 OpReturn
2653 OpFunctionEnd
2654 )";
2655
2656 CompileSuccessfully(text);
2657 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2658 EXPECT_THAT(
2659 getDiagnosticString(),
2660 HasSubstr("Case construct that targets 1[%default] has branches to the "
2661 "case construct that targets 2[%other], but does not "
2662 "immediately precede it in the OpSwitch's target list"));
2663 }
2664
TEST_F(ValidateCFG,SwitchCaseOrderingBad2)2665 TEST_F(ValidateCFG, SwitchCaseOrderingBad2) {
2666 const std::string text = R"(
2667 OpCapability Shader
2668 OpCapability Linkage
2669 OpMemoryModel Logical GLSL450
2670 OpName %default "default"
2671 OpName %other "other"
2672 %void = OpTypeVoid
2673 %int = OpTypeInt 32 0
2674 %undef = OpUndef %int
2675 %void_fn = OpTypeFunction %void
2676 %func = OpFunction %void None %void_fn
2677 %entry = OpLabel
2678 OpSelectionMerge %merge None
2679 OpSwitch %undef %default 0 %default 1 %other
2680 %other = OpLabel
2681 OpBranch %default
2682 %default = OpLabel
2683 OpBranch %merge
2684 %merge = OpLabel
2685 OpReturn
2686 OpFunctionEnd
2687 )";
2688
2689 CompileSuccessfully(text);
2690 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2691 EXPECT_THAT(
2692 getDiagnosticString(),
2693 HasSubstr("Case construct that targets 2[%other] has branches to the "
2694 "case construct that targets 1[%default], but does not "
2695 "immediately precede it in the OpSwitch's target list"));
2696 }
2697
TEST_F(ValidateCFG,SwitchMultipleDefaultWithFallThroughGood)2698 TEST_F(ValidateCFG, SwitchMultipleDefaultWithFallThroughGood) {
2699 const std::string text = R"(
2700 OpCapability Shader
2701 OpCapability Linkage
2702 OpMemoryModel Logical GLSL450
2703 OpName %first "first"
2704 OpName %second "second"
2705 OpName %third "third"
2706 %void = OpTypeVoid
2707 %int = OpTypeInt 32 0
2708 %undef = OpUndef %int
2709 %void_fn = OpTypeFunction %void
2710 %func = OpFunction %void None %void_fn
2711 %entry = OpLabel
2712 OpSelectionMerge %merge None
2713 OpSwitch %undef %second 0 %first 1 %second 2 %third
2714 %first = OpLabel
2715 OpBranch %second
2716 %second = OpLabel
2717 OpBranch %third
2718 %third = OpLabel
2719 OpBranch %merge
2720 %merge = OpLabel
2721 OpReturn
2722 OpFunctionEnd
2723 )";
2724
2725 CompileSuccessfully(text);
2726 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
2727 }
2728
TEST_F(ValidateCFG,SwitchMultipleDefaultWithFallThroughBad)2729 TEST_F(ValidateCFG, SwitchMultipleDefaultWithFallThroughBad) {
2730 const std::string text = R"(
2731 OpCapability Shader
2732 OpCapability Linkage
2733 OpMemoryModel Logical GLSL450
2734 OpName %first "first"
2735 OpName %second "second"
2736 OpName %third "third"
2737 %void = OpTypeVoid
2738 %int = OpTypeInt 32 0
2739 %undef = OpUndef %int
2740 %void_fn = OpTypeFunction %void
2741 %func = OpFunction %void None %void_fn
2742 %entry = OpLabel
2743 OpSelectionMerge %merge None
2744 OpSwitch %undef %second 0 %second 1 %first 2 %third
2745 %first = OpLabel
2746 OpBranch %second
2747 %second = OpLabel
2748 OpBranch %third
2749 %third = OpLabel
2750 OpBranch %merge
2751 %merge = OpLabel
2752 OpReturn
2753 OpFunctionEnd
2754 )";
2755
2756 CompileSuccessfully(text);
2757 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
2758 }
2759
TEST_F(ValidateCFG,GoodUnreachableSelection)2760 TEST_F(ValidateCFG, GoodUnreachableSelection) {
2761 const std::string text = R"(
2762 OpCapability Shader
2763 %1 = OpExtInstImport "GLSL.std.450"
2764 OpMemoryModel Logical GLSL450
2765 OpEntryPoint Fragment %main "main"
2766 OpExecutionMode %main OriginUpperLeft
2767 %void = OpTypeVoid
2768 %8 = OpTypeFunction %void
2769 %bool = OpTypeBool
2770 %false = OpConstantFalse %bool
2771 %main = OpFunction %void None %8
2772 %15 = OpLabel
2773 OpBranch %16
2774 %16 = OpLabel
2775 OpLoopMerge %17 %18 None
2776 OpBranch %19
2777 %19 = OpLabel
2778 OpBranchConditional %false %21 %17
2779 %21 = OpLabel
2780 OpSelectionMerge %22 None
2781 OpBranchConditional %false %23 %22
2782 %23 = OpLabel
2783 OpBranch %24
2784 %24 = OpLabel
2785 OpLoopMerge %25 %26 None
2786 OpBranch %27
2787 %27 = OpLabel
2788 OpReturn
2789 %26 = OpLabel
2790 OpBranchConditional %false %24 %25
2791 %25 = OpLabel
2792 OpSelectionMerge %28 None
2793 OpBranchConditional %false %18 %28
2794 %28 = OpLabel
2795 OpBranch %22
2796 %22 = OpLabel
2797 OpBranch %18
2798 %18 = OpLabel
2799 OpBranch %16
2800 %17 = OpLabel
2801 OpReturn
2802 OpFunctionEnd
2803 )";
2804
2805 CompileSuccessfully(text);
2806 ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
2807 }
2808
TEST_F(ValidateCFG,ShaderWithPhiPtr)2809 TEST_F(ValidateCFG, ShaderWithPhiPtr) {
2810 const std::string text = R"(
2811 OpCapability Shader
2812 OpMemoryModel Logical GLSL450
2813 OpEntryPoint GLCompute %1 "main"
2814 OpExecutionMode %1 LocalSize 1 1 1
2815 OpSource HLSL 600
2816 %bool = OpTypeBool
2817 %_ptr_Function_bool = OpTypePointer Function %bool
2818 %void = OpTypeVoid
2819 %5 = OpTypeFunction %void
2820 %1 = OpFunction %void None %5
2821 %6 = OpLabel
2822 %7 = OpVariable %_ptr_Function_bool Function
2823 %8 = OpVariable %_ptr_Function_bool Function
2824 %9 = OpUndef %bool
2825 OpSelectionMerge %10 None
2826 OpBranchConditional %9 %11 %10
2827 %11 = OpLabel
2828 OpBranch %10
2829 %10 = OpLabel
2830 %12 = OpPhi %_ptr_Function_bool %7 %6 %8 %11
2831 OpReturn
2832 OpFunctionEnd
2833 )";
2834
2835 CompileSuccessfully(text);
2836 EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
2837 EXPECT_THAT(getDiagnosticString(),
2838 HasSubstr("Using pointers with OpPhi requires capability "
2839 "VariablePointers or VariablePointersStorageBuffer"));
2840 }
2841
TEST_F(ValidateCFG,VarPtrShaderWithPhiPtr)2842 TEST_F(ValidateCFG, VarPtrShaderWithPhiPtr) {
2843 const std::string text = R"(
2844 OpCapability Shader
2845 OpCapability VariablePointers
2846 OpExtension "SPV_KHR_variable_pointers"
2847 OpMemoryModel Logical GLSL450
2848 OpEntryPoint GLCompute %1 "main"
2849 OpExecutionMode %1 LocalSize 1 1 1
2850 OpSource HLSL 600
2851 %bool = OpTypeBool
2852 %_ptr_Function_bool = OpTypePointer Function %bool
2853 %void = OpTypeVoid
2854 %5 = OpTypeFunction %void
2855 %1 = OpFunction %void None %5
2856 %6 = OpLabel
2857 %7 = OpVariable %_ptr_Function_bool Function
2858 %8 = OpVariable %_ptr_Function_bool Function
2859 %9 = OpUndef %bool
2860 OpSelectionMerge %10 None
2861 OpBranchConditional %9 %11 %10
2862 %11 = OpLabel
2863 OpBranch %10
2864 %10 = OpLabel
2865 %12 = OpPhi %_ptr_Function_bool %7 %6 %8 %11
2866 OpReturn
2867 OpFunctionEnd
2868 )";
2869
2870 CompileSuccessfully(text);
2871 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
2872 }
2873
TEST_F(ValidateCFG,VarPtrStgBufShaderWithPhiStgBufPtr)2874 TEST_F(ValidateCFG, VarPtrStgBufShaderWithPhiStgBufPtr) {
2875 const std::string text = R"(
2876 OpCapability Shader
2877 OpCapability VariablePointersStorageBuffer
2878 OpExtension "SPV_KHR_variable_pointers"
2879 OpMemoryModel Logical GLSL450
2880 OpEntryPoint GLCompute %1 "main"
2881 OpExecutionMode %1 LocalSize 1 1 1
2882 OpSource HLSL 600
2883 %bool = OpTypeBool
2884 %float = OpTypeFloat 32
2885 %_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
2886 %7 = OpVariable %_ptr_StorageBuffer_float StorageBuffer
2887 %8 = OpVariable %_ptr_StorageBuffer_float StorageBuffer
2888 %void = OpTypeVoid
2889 %5 = OpTypeFunction %void
2890 %1 = OpFunction %void None %5
2891 %6 = OpLabel
2892 %9 = OpUndef %bool
2893 OpSelectionMerge %10 None
2894 OpBranchConditional %9 %11 %10
2895 %11 = OpLabel
2896 OpBranch %10
2897 %10 = OpLabel
2898 %12 = OpPhi %_ptr_StorageBuffer_float %7 %6 %8 %11
2899 OpReturn
2900 OpFunctionEnd
2901 )";
2902
2903 CompileSuccessfully(text);
2904 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
2905 }
2906
TEST_F(ValidateCFG,KernelWithPhiPtr)2907 TEST_F(ValidateCFG, KernelWithPhiPtr) {
2908 const std::string text = R"(
2909 OpCapability Kernel
2910 OpCapability Addresses
2911 OpMemoryModel Physical32 OpenCL
2912 OpEntryPoint Kernel %1 "main"
2913 OpExecutionMode %1 LocalSize 1 1 1
2914 OpSource HLSL 600
2915 %bool = OpTypeBool
2916 %_ptr_Function_bool = OpTypePointer Function %bool
2917 %void = OpTypeVoid
2918 %5 = OpTypeFunction %void
2919 %1 = OpFunction %void None %5
2920 %6 = OpLabel
2921 %7 = OpVariable %_ptr_Function_bool Function
2922 %8 = OpVariable %_ptr_Function_bool Function
2923 %9 = OpUndef %bool
2924 OpSelectionMerge %10 None
2925 OpBranchConditional %9 %11 %10
2926 %11 = OpLabel
2927 OpBranch %10
2928 %10 = OpLabel
2929 %12 = OpPhi %_ptr_Function_bool %7 %6 %8 %11
2930 OpReturn
2931 OpFunctionEnd
2932 )";
2933
2934 CompileSuccessfully(text);
2935 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
2936 }
2937
TEST_F(ValidateCFG,SwitchTargetMustBeLabel)2938 TEST_F(ValidateCFG, SwitchTargetMustBeLabel) {
2939 const std::string text = R"(
2940 OpCapability Shader
2941 OpMemoryModel Logical GLSL450
2942 OpEntryPoint GLCompute %1 "foo"
2943 %uint = OpTypeInt 32 0
2944 %uint_0 = OpConstant %uint 0
2945 %void = OpTypeVoid
2946 %5 = OpTypeFunction %void
2947 %1 = OpFunction %void None %5
2948 %6 = OpLabel
2949 %7 = OpCopyObject %uint %uint_0
2950 OpSelectionMerge %8 None
2951 OpSwitch %uint_0 %8 0 %7
2952 %8 = OpLabel
2953 OpReturn
2954 OpFunctionEnd
2955 )";
2956
2957 CompileSuccessfully(text);
2958 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
2959 EXPECT_THAT(getDiagnosticString(),
2960 HasSubstr("'Target Label' operands for OpSwitch must "
2961 "be IDs of an OpLabel instruction"));
2962 }
2963
TEST_F(ValidateCFG,BranchTargetMustBeLabel)2964 TEST_F(ValidateCFG, BranchTargetMustBeLabel) {
2965 const std::string text = R"(
2966 OpCapability Shader
2967 OpMemoryModel Logical GLSL450
2968 OpEntryPoint GLCompute %1 "foo"
2969 %uint = OpTypeInt 32 0
2970 %uint_0 = OpConstant %uint 0
2971 %void = OpTypeVoid
2972 %5 = OpTypeFunction %void
2973 %1 = OpFunction %void None %5
2974 %2 = OpLabel
2975 %7 = OpCopyObject %uint %uint_0
2976 OpBranch %7
2977 %8 = OpLabel
2978 OpReturn
2979 OpFunctionEnd
2980 )";
2981
2982 CompileSuccessfully(text);
2983 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
2984 EXPECT_THAT(getDiagnosticString(),
2985 HasSubstr("'Target Label' operands for OpBranch must "
2986 "be the ID of an OpLabel instruction"));
2987 }
2988
TEST_F(ValidateCFG,ReachableOpUnreachableOneBlock)2989 TEST_F(ValidateCFG, ReachableOpUnreachableOneBlock) {
2990 const std::string text = R"(
2991 OpCapability Shader
2992 OpCapability Linkage
2993 OpMemoryModel Logical GLSL450
2994 %void = OpTypeVoid
2995 %void_fn = OpTypeFunction %void
2996 %func = OpFunction %void None %void_fn
2997 %entry = OpLabel
2998 OpUnreachable
2999 OpFunctionEnd
3000 )";
3001
3002 CompileSuccessfully(text);
3003 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3004 }
3005
TEST_F(ValidateCFG,ReachableOpUnreachableOpBranch)3006 TEST_F(ValidateCFG, ReachableOpUnreachableOpBranch) {
3007 const std::string text = R"(
3008 OpCapability Shader
3009 OpCapability Linkage
3010 OpMemoryModel Logical GLSL450
3011 %void = OpTypeVoid
3012 %void_fn = OpTypeFunction %void
3013 %func = OpFunction %void None %void_fn
3014 %entry = OpLabel
3015 OpBranch %block
3016 %block = OpLabel
3017 OpUnreachable
3018 OpFunctionEnd
3019 )";
3020
3021 CompileSuccessfully(text);
3022 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3023 }
3024
TEST_F(ValidateCFG,ReachableOpUnreachableOpBranchConditional)3025 TEST_F(ValidateCFG, ReachableOpUnreachableOpBranchConditional) {
3026 const std::string text = R"(
3027 OpCapability Shader
3028 OpCapability Linkage
3029 OpMemoryModel Logical GLSL450
3030 %void = OpTypeVoid
3031 %void_fn = OpTypeFunction %void
3032 %bool = OpTypeBool
3033 %undef = OpUndef %bool
3034 %func = OpFunction %void None %void_fn
3035 %entry = OpLabel
3036 OpSelectionMerge %block None
3037 OpBranchConditional %undef %block %unreachable
3038 %block = OpLabel
3039 OpReturn
3040 %unreachable = OpLabel
3041 OpUnreachable
3042 OpFunctionEnd
3043 )";
3044
3045 CompileSuccessfully(text);
3046 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3047 }
3048
TEST_F(ValidateCFG,ReachableOpUnreachableOpSwitch)3049 TEST_F(ValidateCFG, ReachableOpUnreachableOpSwitch) {
3050 const std::string text = R"(
3051 OpCapability Shader
3052 OpCapability Linkage
3053 OpMemoryModel Logical GLSL450
3054 %void = OpTypeVoid
3055 %void_fn = OpTypeFunction %void
3056 %int = OpTypeInt 32 0
3057 %undef = OpUndef %int
3058 %func = OpFunction %void None %void_fn
3059 %entry = OpLabel
3060 OpSelectionMerge %block1 None
3061 OpSwitch %undef %block1 0 %unreachable 1 %block2
3062 %block1 = OpLabel
3063 OpReturn
3064 %unreachable = OpLabel
3065 OpUnreachable
3066 %block2 = OpLabel
3067 OpReturn
3068 OpFunctionEnd
3069 )";
3070
3071 CompileSuccessfully(text);
3072 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3073 }
3074
TEST_F(ValidateCFG,ReachableOpUnreachableLoop)3075 TEST_F(ValidateCFG, ReachableOpUnreachableLoop) {
3076 const std::string text = R"(
3077 OpCapability Shader
3078 OpCapability Linkage
3079 OpMemoryModel Logical GLSL450
3080 %void = OpTypeVoid
3081 %void_fn = OpTypeFunction %void
3082 %bool = OpTypeBool
3083 %undef = OpUndef %bool
3084 %func = OpFunction %void None %void_fn
3085 %entry = OpLabel
3086 OpBranch %loop
3087 %loop = OpLabel
3088 OpLoopMerge %unreachable %loop None
3089 OpBranchConditional %undef %loop %unreachable
3090 %unreachable = OpLabel
3091 OpUnreachable
3092 OpFunctionEnd
3093 )";
3094
3095 CompileSuccessfully(text);
3096 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3097 }
3098
TEST_F(ValidateCFG,UnreachableLoopBadBackedge)3099 TEST_F(ValidateCFG, UnreachableLoopBadBackedge) {
3100 const std::string text = R"(
3101 OpCapability Shader
3102 OpMemoryModel Logical GLSL450
3103 OpEntryPoint Fragment %2 "main"
3104 OpExecutionMode %2 OriginUpperLeft
3105 %4 = OpTypeVoid
3106 %5 = OpTypeFunction %4
3107 %8 = OpTypeBool
3108 %13 = OpConstantTrue %8
3109 %2 = OpFunction %4 None %5
3110 %14 = OpLabel
3111 OpSelectionMerge %15 None
3112 OpBranchConditional %13 %15 %15
3113 %16 = OpLabel
3114 OpLoopMerge %17 %18 None
3115 OpBranch %17
3116 %18 = OpLabel
3117 OpBranch %17
3118 %17 = OpLabel
3119 OpBranch %15
3120 %15 = OpLabel
3121 OpReturn
3122 OpFunctionEnd
3123 )";
3124
3125 // The back-edge in this test is bad, but the validator fails to identify it
3126 // because it is in an entirely unreachable section of code. Prior to #2488
3127 // this code failed an assert in Construct::blocks().
3128 CompileSuccessfully(text);
3129 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3130 }
3131
TEST_F(ValidateCFG,OneContinueTwoBackedges)3132 TEST_F(ValidateCFG, OneContinueTwoBackedges) {
3133 const std::string text = R"(
3134 OpCapability Shader
3135 OpMemoryModel Logical GLSL450
3136 OpEntryPoint GLCompute %1 "main"
3137 OpExecutionMode %1 LocalSize 1 1 1
3138 %void = OpTypeVoid
3139 %bool = OpTypeBool
3140 %true = OpConstantTrue %bool
3141 %5 = OpTypeFunction %void
3142 %1 = OpFunction %void None %5
3143 %6 = OpLabel
3144 OpBranch %7
3145 %7 = OpLabel
3146 OpLoopMerge %8 %9 None
3147 OpBranch %10
3148 %10 = OpLabel
3149 OpLoopMerge %11 %9 None
3150 OpBranchConditional %true %11 %9
3151 %9 = OpLabel
3152 OpBranchConditional %true %10 %7
3153 %11 = OpLabel
3154 OpBranch %8
3155 %8 = OpLabel
3156 OpReturn
3157 OpFunctionEnd
3158 )";
3159
3160 CompileSuccessfully(text);
3161 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3162 EXPECT_THAT(getDiagnosticString(),
3163 HasSubstr("block <ID> 9 branches to the loop construct, but not "
3164 "to the loop header <ID> 7"));
3165 }
3166
TEST_F(ValidateCFG,LoopMergeMergeBlockNotLabel)3167 TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
3168 const std::string text = R"(
3169 OpCapability Shader
3170 OpCapability Linkage
3171 OpMemoryModel Logical GLSL450
3172 OpName %undef "undef"
3173 %void = OpTypeVoid
3174 %bool = OpTypeBool
3175 %undef = OpUndef %bool
3176 %void_fn = OpTypeFunction %void
3177 %func = OpFunction %void None %void_fn
3178 %1 = OpLabel
3179 OpLoopMerge %undef %2 None
3180 OpBranchConditional %undef %2 %2
3181 %2 = OpLabel
3182 OpReturn
3183 OpFunctionEnd
3184 )";
3185
3186 CompileSuccessfully(text);
3187 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
3188 EXPECT_THAT(getDiagnosticString(),
3189 HasSubstr("Merge Block 1[%undef] must be an OpLabel"));
3190 }
3191
TEST_F(ValidateCFG,LoopMergeContinueTargetNotLabel)3192 TEST_F(ValidateCFG, LoopMergeContinueTargetNotLabel) {
3193 const std::string text = R"(
3194 OpCapability Shader
3195 OpCapability Linkage
3196 OpMemoryModel Logical GLSL450
3197 OpName %undef "undef"
3198 %void = OpTypeVoid
3199 %bool = OpTypeBool
3200 %undef = OpUndef %bool
3201 %void_fn = OpTypeFunction %void
3202 %func = OpFunction %void None %void_fn
3203 %1 = OpLabel
3204 OpLoopMerge %2 %undef None
3205 OpBranchConditional %undef %2 %2
3206 %2 = OpLabel
3207 OpReturn
3208 OpFunctionEnd
3209 )";
3210
3211 CompileSuccessfully(text);
3212 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
3213 EXPECT_THAT(getDiagnosticString(),
3214 HasSubstr("Continue Target 1[%undef] must be an OpLabel"));
3215 }
3216
TEST_F(ValidateCFG,LoopMergeMergeBlockContinueTargetSameLabel)3217 TEST_F(ValidateCFG, LoopMergeMergeBlockContinueTargetSameLabel) {
3218 const std::string text = R"(
3219 OpCapability Shader
3220 OpCapability Linkage
3221 OpMemoryModel Logical GLSL450
3222 OpName %undef "undef"
3223 %void = OpTypeVoid
3224 %bool = OpTypeBool
3225 %undef = OpUndef %bool
3226 %void_fn = OpTypeFunction %void
3227 %func = OpFunction %void None %void_fn
3228 %1 = OpLabel
3229 OpLoopMerge %2 %2 None
3230 OpBranchConditional %undef %2 %2
3231 %2 = OpLabel
3232 OpReturn
3233 OpFunctionEnd
3234 )";
3235
3236 CompileSuccessfully(text);
3237 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
3238 EXPECT_THAT(
3239 getDiagnosticString(),
3240 HasSubstr("Merge Block and Continue Target must be different ids"));
3241 }
3242
TEST_F(ValidateCFG,LoopMergeUnrollAndDontUnroll)3243 TEST_F(ValidateCFG, LoopMergeUnrollAndDontUnroll) {
3244 const std::string text = R"(
3245 OpCapability Shader
3246 OpCapability Linkage
3247 OpMemoryModel Logical GLSL450
3248 OpName %undef "undef"
3249 %void = OpTypeVoid
3250 %bool = OpTypeBool
3251 %undef = OpUndef %bool
3252 %void_fn = OpTypeFunction %void
3253 %func = OpFunction %void None %void_fn
3254 %5 = OpLabel
3255 OpBranch %1
3256 %1 = OpLabel
3257 OpLoopMerge %2 %3 Unroll|DontUnroll
3258 OpBranchConditional %undef %2 %3
3259 %3 = OpLabel
3260 OpBranch %1
3261 %2 = OpLabel
3262 OpReturn
3263 OpFunctionEnd
3264 )";
3265
3266 CompileSuccessfully(text);
3267 EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
3268 EXPECT_THAT(
3269 getDiagnosticString(),
3270 HasSubstr(
3271 "Unroll and DontUnroll loop controls must not both be specified"));
3272 }
3273
TEST_F(ValidateCFG,LoopMergePeelCountAndDontUnroll)3274 TEST_F(ValidateCFG, LoopMergePeelCountAndDontUnroll) {
3275 const std::string text = R"(
3276 OpCapability Shader
3277 OpCapability Linkage
3278 OpMemoryModel Logical GLSL450
3279 OpName %undef "undef"
3280 %void = OpTypeVoid
3281 %bool = OpTypeBool
3282 %undef = OpUndef %bool
3283 %void_fn = OpTypeFunction %void
3284 %func = OpFunction %void None %void_fn
3285 %5 = OpLabel
3286 OpBranch %1
3287 %1 = OpLabel
3288 OpLoopMerge %2 %3 DontUnroll|PeelCount 1
3289 OpBranchConditional %undef %2 %3
3290 %3 = OpLabel
3291 OpBranch %1
3292 %2 = OpLabel
3293 OpReturn
3294 OpFunctionEnd
3295 )";
3296
3297 CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
3298 EXPECT_EQ(SPV_ERROR_INVALID_DATA,
3299 ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
3300 EXPECT_THAT(
3301 getDiagnosticString(),
3302 HasSubstr(
3303 "PeelCount and DontUnroll loop controls must not both be specified"));
3304 }
3305
TEST_F(ValidateCFG,LoopMergePartialCountAndDontUnroll)3306 TEST_F(ValidateCFG, LoopMergePartialCountAndDontUnroll) {
3307 const std::string text = R"(
3308 OpCapability Shader
3309 OpCapability Linkage
3310 OpMemoryModel Logical GLSL450
3311 OpName %undef "undef"
3312 %void = OpTypeVoid
3313 %bool = OpTypeBool
3314 %undef = OpUndef %bool
3315 %void_fn = OpTypeFunction %void
3316 %func = OpFunction %void None %void_fn
3317 %5 = OpLabel
3318 OpBranch %1
3319 %1 = OpLabel
3320 OpLoopMerge %2 %3 DontUnroll|PartialCount 1
3321 OpBranchConditional %undef %2 %3
3322 %3 = OpLabel
3323 OpBranch %1
3324 %2 = OpLabel
3325 OpReturn
3326 OpFunctionEnd
3327 )";
3328
3329 CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
3330 EXPECT_EQ(SPV_ERROR_INVALID_DATA,
3331 ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
3332 EXPECT_THAT(getDiagnosticString(),
3333 HasSubstr("PartialCount and DontUnroll loop controls must not "
3334 "both be specified"));
3335 }
3336
TEST_F(ValidateCFG,LoopMergeIterationMultipleZero)3337 TEST_F(ValidateCFG, LoopMergeIterationMultipleZero) {
3338 const std::string text = R"(
3339 OpCapability Shader
3340 OpCapability Linkage
3341 OpMemoryModel Logical GLSL450
3342 OpName %undef "undef"
3343 %void = OpTypeVoid
3344 %bool = OpTypeBool
3345 %undef = OpUndef %bool
3346 %void_fn = OpTypeFunction %void
3347 %func = OpFunction %void None %void_fn
3348 %5 = OpLabel
3349 OpBranch %1
3350 %1 = OpLabel
3351 OpLoopMerge %2 %3 IterationMultiple 0
3352 OpBranchConditional %undef %2 %3
3353 %3 = OpLabel
3354 OpBranch %1
3355 %2 = OpLabel
3356 OpReturn
3357 OpFunctionEnd
3358 )";
3359
3360 CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
3361 EXPECT_EQ(SPV_ERROR_INVALID_DATA,
3362 ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
3363 EXPECT_THAT(
3364 getDiagnosticString(),
3365 HasSubstr(
3366 "IterationMultiple loop control operand must be greater than zero"));
3367 }
3368
TEST_F(ValidateCFG,LoopMergeIterationMultipleZeroMoreOperands)3369 TEST_F(ValidateCFG, LoopMergeIterationMultipleZeroMoreOperands) {
3370 const std::string text = R"(
3371 OpCapability Shader
3372 OpCapability Linkage
3373 OpMemoryModel Logical GLSL450
3374 OpName %undef "undef"
3375 %void = OpTypeVoid
3376 %bool = OpTypeBool
3377 %undef = OpUndef %bool
3378 %void_fn = OpTypeFunction %void
3379 %func = OpFunction %void None %void_fn
3380 %5 = OpLabel
3381 OpBranch %1
3382 %1 = OpLabel
3383 OpLoopMerge %2 %3 MaxIterations|IterationMultiple 4 0
3384 OpBranchConditional %undef %2 %3
3385 %3 = OpLabel
3386 OpBranch %1
3387 %2 = OpLabel
3388 OpReturn
3389 OpFunctionEnd
3390 )";
3391
3392 CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
3393 EXPECT_EQ(SPV_ERROR_INVALID_DATA,
3394 ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
3395 EXPECT_THAT(
3396 getDiagnosticString(),
3397 HasSubstr(
3398 "IterationMultiple loop control operand must be greater than zero"));
3399 }
3400
TEST_F(ValidateCFG,LoopMergeTargetsHeader)3401 TEST_F(ValidateCFG, LoopMergeTargetsHeader) {
3402 const std::string text = R"(
3403 OpCapability Shader
3404 OpCapability Linkage
3405 OpMemoryModel Logical GLSL450
3406 %void = OpTypeVoid
3407 %bool = OpTypeBool
3408 %undef = OpUndef %bool
3409 %void_fn = OpTypeFunction %void
3410 %fn = OpFunction %void None %void_fn
3411 %entry = OpLabel
3412 OpBranch %loop
3413 %loop = OpLabel
3414 OpLoopMerge %loop %continue None
3415 OpBranch %body
3416 %continue = OpLabel
3417 OpBranch %loop
3418 %body = OpLabel
3419 OpReturn
3420 OpFunctionEnd
3421 )";
3422
3423 CompileSuccessfully(text);
3424 EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
3425 EXPECT_THAT(
3426 getDiagnosticString(),
3427 HasSubstr("Merge Block may not be the block containing the OpLoopMerge"));
3428 }
3429
TEST_F(ValidateCFG,InvalidSelectionExit)3430 TEST_F(ValidateCFG, InvalidSelectionExit) {
3431 const std::string text = R"(
3432 OpCapability Shader
3433 OpMemoryModel Logical GLSL450
3434 OpEntryPoint Fragment %1 "main"
3435 OpExecutionMode %1 OriginUpperLeft
3436 %2 = OpTypeVoid
3437 %3 = OpTypeBool
3438 %4 = OpConstantTrue %3
3439 %5 = OpTypeFunction %2
3440 %1 = OpFunction %2 None %5
3441 %6 = OpLabel
3442 OpSelectionMerge %7 None
3443 OpBranchConditional %4 %7 %8
3444 %8 = OpLabel
3445 OpSelectionMerge %9 None
3446 OpBranchConditional %4 %10 %9
3447 %10 = OpLabel
3448 OpBranch %7
3449 %9 = OpLabel
3450 OpBranch %7
3451 %7 = OpLabel
3452 OpReturn
3453 OpFunctionEnd
3454 )";
3455
3456 CompileSuccessfully(text);
3457 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3458 EXPECT_THAT(getDiagnosticString(),
3459 HasSubstr("block <ID> 10[%10] exits the selection headed by <ID> "
3460 "8[%8], but not via a structured exit"));
3461 }
3462
TEST_F(ValidateCFG,InvalidLoopExit)3463 TEST_F(ValidateCFG, InvalidLoopExit) {
3464 const std::string text = R"(
3465 OpCapability Shader
3466 OpMemoryModel Logical GLSL450
3467 OpEntryPoint Fragment %1 "main"
3468 OpExecutionMode %1 OriginUpperLeft
3469 %2 = OpTypeVoid
3470 %3 = OpTypeBool
3471 %4 = OpConstantTrue %3
3472 %5 = OpTypeFunction %2
3473 %1 = OpFunction %2 None %5
3474 %6 = OpLabel
3475 OpSelectionMerge %7 None
3476 OpBranchConditional %4 %7 %8
3477 %8 = OpLabel
3478 OpLoopMerge %9 %10 None
3479 OpBranchConditional %4 %9 %11
3480 %11 = OpLabel
3481 OpBranchConditional %4 %7 %10
3482 %10 = OpLabel
3483 OpBranch %8
3484 %9 = OpLabel
3485 OpBranch %7
3486 %7 = OpLabel
3487 OpReturn
3488 OpFunctionEnd
3489 )";
3490
3491 CompileSuccessfully(text);
3492 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3493 EXPECT_THAT(getDiagnosticString(),
3494 HasSubstr("block <ID> 11[%11] exits the loop headed by <ID> "
3495 "8[%8], but not via a structured exit"));
3496 }
3497
TEST_F(ValidateCFG,InvalidContinueExit)3498 TEST_F(ValidateCFG, InvalidContinueExit) {
3499 const std::string text = R"(
3500 OpCapability Shader
3501 OpMemoryModel Logical GLSL450
3502 OpEntryPoint Fragment %1 "main"
3503 OpExecutionMode %1 OriginUpperLeft
3504 %2 = OpTypeVoid
3505 %3 = OpTypeBool
3506 %4 = OpConstantTrue %3
3507 %5 = OpTypeFunction %2
3508 %1 = OpFunction %2 None %5
3509 %6 = OpLabel
3510 OpSelectionMerge %7 None
3511 OpBranchConditional %4 %7 %8
3512 %8 = OpLabel
3513 OpLoopMerge %9 %10 None
3514 OpBranchConditional %4 %9 %10
3515 %10 = OpLabel
3516 OpBranch %11
3517 %11 = OpLabel
3518 OpBranchConditional %4 %8 %7
3519 %9 = OpLabel
3520 OpBranch %7
3521 %7 = OpLabel
3522 OpReturn
3523 OpFunctionEnd
3524 )";
3525
3526 CompileSuccessfully(text);
3527 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3528 EXPECT_THAT(getDiagnosticString(),
3529 HasSubstr("block <ID> 11[%11] exits the continue headed by <ID> "
3530 "10[%10], but not via a structured exit"));
3531 }
3532
TEST_F(ValidateCFG,InvalidSelectionExitBackedge)3533 TEST_F(ValidateCFG, InvalidSelectionExitBackedge) {
3534 const std::string text = R"(
3535 OpCapability Shader
3536 OpCapability Linkage
3537 OpMemoryModel Logical GLSL450
3538 %1 = OpTypeVoid
3539 %2 = OpTypeBool
3540 %3 = OpUndef %2
3541 %4 = OpTypeFunction %1
3542 %5 = OpFunction %1 None %4
3543 %6 = OpLabel
3544 OpBranch %7
3545 %7 = OpLabel
3546 OpLoopMerge %8 %9 None
3547 OpBranchConditional %3 %8 %9
3548 %9 = OpLabel
3549 OpSelectionMerge %10 None
3550 OpBranchConditional %3 %11 %12
3551 %11 = OpLabel
3552 OpBranch %13
3553 %12 = OpLabel
3554 OpBranch %13
3555 %13 = OpLabel
3556 OpBranch %7
3557 %10 = OpLabel
3558 OpUnreachable
3559 %8 = OpLabel
3560 OpReturn
3561 OpFunctionEnd
3562 )";
3563
3564 CompileSuccessfully(text);
3565 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3566 EXPECT_THAT(getDiagnosticString(),
3567 HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> "
3568 "9[%9], but not via a structured exit"));
3569 }
3570
TEST_F(ValidateCFG,BreakFromSwitch)3571 TEST_F(ValidateCFG, BreakFromSwitch) {
3572 const std::string text = R"(
3573 OpCapability Shader
3574 OpCapability Linkage
3575 OpMemoryModel Logical GLSL450
3576 %1 = OpTypeVoid
3577 %2 = OpTypeBool
3578 %3 = OpTypeInt 32 0
3579 %4 = OpUndef %2
3580 %5 = OpUndef %3
3581 %6 = OpTypeFunction %1
3582 %7 = OpFunction %1 None %6
3583 %8 = OpLabel
3584 OpSelectionMerge %9 None
3585 OpSwitch %5 %9 0 %10
3586 %10 = OpLabel
3587 OpSelectionMerge %11 None
3588 OpBranchConditional %4 %11 %12
3589 %12 = OpLabel
3590 OpBranch %9
3591 %11 = OpLabel
3592 OpBranch %9
3593 %9 = OpLabel
3594 OpReturn
3595 OpFunctionEnd
3596 )";
3597
3598 CompileSuccessfully(text);
3599 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3600 }
3601
TEST_F(ValidateCFG,InvalidBreakFromSwitch)3602 TEST_F(ValidateCFG, InvalidBreakFromSwitch) {
3603 const std::string text = R"(
3604 OpCapability Shader
3605 OpCapability Linkage
3606 OpMemoryModel Logical GLSL450
3607 %1 = OpTypeVoid
3608 %2 = OpTypeBool
3609 %3 = OpTypeInt 32 0
3610 %4 = OpUndef %2
3611 %5 = OpUndef %3
3612 %6 = OpTypeFunction %1
3613 %7 = OpFunction %1 None %6
3614 %8 = OpLabel
3615 OpSelectionMerge %9 None
3616 OpSwitch %5 %9 0 %10
3617 %10 = OpLabel
3618 OpSelectionMerge %11 None
3619 OpSwitch %5 %11 0 %12
3620 %12 = OpLabel
3621 OpBranch %9
3622 %11 = OpLabel
3623 OpBranch %9
3624 %9 = OpLabel
3625 OpReturn
3626 OpFunctionEnd
3627 )";
3628
3629 CompileSuccessfully(text);
3630 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3631 EXPECT_THAT(getDiagnosticString(),
3632 HasSubstr("block <ID> 12[%12] exits the selection headed by <ID> "
3633 "10[%10], but not via a structured exit"));
3634 }
3635
TEST_F(ValidateCFG,BreakToOuterSwitch)3636 TEST_F(ValidateCFG, BreakToOuterSwitch) {
3637 const std::string text = R"(
3638 OpCapability Shader
3639 OpCapability Linkage
3640 OpMemoryModel Logical GLSL450
3641 %1 = OpTypeVoid
3642 %2 = OpTypeBool
3643 %3 = OpTypeInt 32 0
3644 %4 = OpUndef %2
3645 %5 = OpUndef %3
3646 %6 = OpTypeFunction %1
3647 %7 = OpFunction %1 None %6
3648 %8 = OpLabel
3649 OpSelectionMerge %9 None
3650 OpSwitch %5 %9 0 %10
3651 %10 = OpLabel
3652 OpSelectionMerge %11 None
3653 OpSwitch %5 %11 0 %12
3654 %12 = OpLabel
3655 OpSelectionMerge %13 None
3656 OpBranchConditional %4 %13 %14
3657 %14 = OpLabel
3658 OpBranch %9
3659 %13 = OpLabel
3660 OpBranch %11
3661 %11 = OpLabel
3662 OpBranch %9
3663 %9 = OpLabel
3664 OpReturn
3665 OpFunctionEnd
3666 )";
3667
3668 CompileSuccessfully(text);
3669 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3670 EXPECT_THAT(getDiagnosticString(),
3671 HasSubstr("block <ID> 14[%14] exits the selection headed by <ID> "
3672 "10[%10], but not via a structured exit"));
3673 }
3674
TEST_F(ValidateCFG,BreakToOuterLoop)3675 TEST_F(ValidateCFG, BreakToOuterLoop) {
3676 const std::string text = R"(
3677 OpCapability Shader
3678 OpCapability Linkage
3679 OpMemoryModel Logical GLSL450
3680 %1 = OpTypeVoid
3681 %2 = OpTypeBool
3682 %3 = OpUndef %2
3683 %4 = OpTypeFunction %1
3684 %5 = OpFunction %1 None %4
3685 %6 = OpLabel
3686 OpBranch %7
3687 %7 = OpLabel
3688 OpLoopMerge %8 %9 None
3689 OpBranch %10
3690 %10 = OpLabel
3691 OpLoopMerge %11 %12 None
3692 OpBranch %13
3693 %13 = OpLabel
3694 OpSelectionMerge %14 None
3695 OpBranchConditional %3 %14 %15
3696 %15 = OpLabel
3697 OpBranch %8
3698 %14 = OpLabel
3699 OpBranch %12
3700 %12 = OpLabel
3701 OpBranchConditional %3 %10 %11
3702 %11 = OpLabel
3703 OpBranch %9
3704 %9 = OpLabel
3705 OpBranchConditional %3 %7 %8
3706 %8 = OpLabel
3707 OpReturn
3708 OpFunctionEnd
3709 )";
3710
3711 CompileSuccessfully(text);
3712 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3713 EXPECT_THAT(getDiagnosticString(),
3714 HasSubstr("block <ID> 15[%15] exits the loop headed by <ID> "
3715 "10[%10], but not via a structured exit"));
3716 }
3717
TEST_F(ValidateCFG,ContinueFromNestedSelection)3718 TEST_F(ValidateCFG, ContinueFromNestedSelection) {
3719 const std::string text = R"(
3720 OpCapability Shader
3721 OpCapability Linkage
3722 OpMemoryModel Logical GLSL450
3723 %void = OpTypeVoid
3724 %void_fn = OpTypeFunction %void
3725 %bool = OpTypeBool
3726 %undef = OpUndef %bool
3727 %4 = OpFunction %void None %void_fn
3728 %5 = OpLabel
3729 OpBranch %48
3730 %48 = OpLabel
3731 OpLoopMerge %47 %50 None
3732 OpBranch %10
3733 %10 = OpLabel
3734 OpLoopMerge %12 %37 None
3735 OpBranchConditional %undef %11 %12
3736 %11 = OpLabel
3737 OpSelectionMerge %31 None
3738 OpBranchConditional %undef %30 %31
3739 %30 = OpLabel
3740 OpSelectionMerge %38 None
3741 OpBranchConditional %undef %36 %38
3742 %36 = OpLabel
3743 OpBranch %38
3744 %38 = OpLabel
3745 OpBranch %37
3746 %37 = OpLabel
3747 OpBranch %10
3748 %31 = OpLabel
3749 OpBranch %12
3750 %12 = OpLabel
3751 OpSelectionMerge %55 None
3752 OpBranchConditional %undef %47 %55
3753 %55 = OpLabel
3754 OpBranch %47
3755 %50 = OpLabel
3756 OpBranch %48
3757 %47 = OpLabel
3758 OpReturn
3759 OpFunctionEnd
3760 )";
3761
3762 CompileSuccessfully(text);
3763 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3764 }
3765
TEST_F(ValidateCFG,MissingMergeConditionalBranchBad)3766 TEST_F(ValidateCFG, MissingMergeConditionalBranchBad) {
3767 const std::string text = R"(
3768 OpCapability Shader
3769 OpCapability Linkage
3770 OpMemoryModel Logical GLSL450
3771 %void = OpTypeVoid
3772 %void_fn = OpTypeFunction %void
3773 %bool = OpTypeBool
3774 %undef = OpUndef %bool
3775 %func = OpFunction %void None %void_fn
3776 %entry = OpLabel
3777 OpBranchConditional %undef %then %else
3778 %then = OpLabel
3779 OpReturn
3780 %else = OpLabel
3781 OpReturn
3782 OpFunctionEnd
3783 )";
3784
3785 CompileSuccessfully(text);
3786 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3787 EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
3788 }
3789
TEST_F(ValidateCFG,MissingMergeSwitchBad)3790 TEST_F(ValidateCFG, MissingMergeSwitchBad) {
3791 const std::string text = R"(
3792 OpCapability Shader
3793 OpCapability Linkage
3794 OpMemoryModel Logical GLSL450
3795 %void = OpTypeVoid
3796 %void_fn = OpTypeFunction %void
3797 %int = OpTypeInt 32 0
3798 %undef = OpUndef %int
3799 %func = OpFunction %void None %void_fn
3800 %entry = OpLabel
3801 OpSwitch %undef %then 0 %else
3802 %then = OpLabel
3803 OpReturn
3804 %else = OpLabel
3805 OpReturn
3806 OpFunctionEnd
3807 )";
3808
3809 CompileSuccessfully(text);
3810 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3811 EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
3812 }
3813
TEST_F(ValidateCFG,MissingMergeSwitchBad2)3814 TEST_F(ValidateCFG, MissingMergeSwitchBad2) {
3815 const std::string text = R"(
3816 OpCapability Shader
3817 OpCapability Linkage
3818 OpMemoryModel Logical GLSL450
3819 %void = OpTypeVoid
3820 %void_fn = OpTypeFunction %void
3821 %int = OpTypeInt 32 0
3822 %undef = OpUndef %int
3823 %func = OpFunction %void None %void_fn
3824 %entry = OpLabel
3825 OpSwitch %undef %then 0 %then 1 %then 2 %else
3826 %then = OpLabel
3827 OpReturn
3828 %else = OpLabel
3829 OpReturn
3830 OpFunctionEnd
3831 )";
3832
3833 CompileSuccessfully(text);
3834 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
3835 EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
3836 }
3837
TEST_F(ValidateCFG,MissingMergeOneBranchToMergeGood)3838 TEST_F(ValidateCFG, MissingMergeOneBranchToMergeGood) {
3839 const std::string text = R"(
3840 OpCapability Shader
3841 OpCapability Linkage
3842 OpMemoryModel Logical GLSL450
3843 %void = OpTypeVoid
3844 %void_fn = OpTypeFunction %void
3845 %bool = OpTypeBool
3846 %undef = OpUndef %bool
3847 %func = OpFunction %void None %void_fn
3848 %entry = OpLabel
3849 OpSelectionMerge %b3 None
3850 OpBranchConditional %undef %b1 %b2
3851 %b1 = OpLabel
3852 OpBranchConditional %undef %b2 %b3
3853 %b2 = OpLabel
3854 OpBranch %b3
3855 %b3 = OpLabel
3856 OpReturn
3857 OpFunctionEnd
3858 )";
3859
3860 CompileSuccessfully(text);
3861 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3862 }
3863
TEST_F(ValidateCFG,MissingMergeSameTargetConditionalBranchGood)3864 TEST_F(ValidateCFG, MissingMergeSameTargetConditionalBranchGood) {
3865 const std::string text = R"(
3866 OpCapability Shader
3867 OpCapability Linkage
3868 OpMemoryModel Logical GLSL450
3869 %void = OpTypeVoid
3870 %void_fn = OpTypeFunction %void
3871 %bool = OpTypeBool
3872 %undef = OpUndef %bool
3873 %func = OpFunction %void None %void_fn
3874 %entry = OpLabel
3875 OpBranchConditional %undef %then %then
3876 %then = OpLabel
3877 OpReturn
3878 OpFunctionEnd
3879 )";
3880
3881 CompileSuccessfully(text);
3882 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3883 }
3884
TEST_F(ValidateCFG,MissingMergeOneTargetSwitchGood)3885 TEST_F(ValidateCFG, MissingMergeOneTargetSwitchGood) {
3886 const std::string text = R"(
3887 OpCapability Shader
3888 OpCapability Linkage
3889 OpMemoryModel Logical GLSL450
3890 %void = OpTypeVoid
3891 %void_fn = OpTypeFunction %void
3892 %int = OpTypeInt 32 0
3893 %undef = OpUndef %int
3894 %func = OpFunction %void None %void_fn
3895 %entry = OpLabel
3896 OpSwitch %undef %then 0 %then 1 %then
3897 %then = OpLabel
3898 OpReturn
3899 OpFunctionEnd
3900 )";
3901
3902 CompileSuccessfully(text);
3903 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3904 }
3905
TEST_F(ValidateCFG,MissingMergeOneUnseenTargetSwitchGood)3906 TEST_F(ValidateCFG, MissingMergeOneUnseenTargetSwitchGood) {
3907 const std::string text = R"(
3908 OpCapability Shader
3909 OpCapability Linkage
3910 OpMemoryModel Logical GLSL450
3911 %void = OpTypeVoid
3912 %void_fn = OpTypeFunction %void
3913 %int = OpTypeInt 32 0
3914 %undef_int = OpUndef %int
3915 %bool = OpTypeBool
3916 %undef_bool = OpUndef %bool
3917 %func = OpFunction %void None %void_fn
3918 %entry = OpLabel
3919 OpSelectionMerge %merge None
3920 OpBranchConditional %undef_bool %merge %b1
3921 %b1 = OpLabel
3922 OpSwitch %undef_int %b2 0 %b2 1 %merge 2 %b2
3923 %b2 = OpLabel
3924 OpBranch %merge
3925 %merge = OpLabel
3926 OpReturn
3927 OpFunctionEnd
3928 )";
3929
3930 CompileSuccessfully(text);
3931 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3932 }
3933
TEST_F(ValidateCFG,MissingMergeLoopBreakGood)3934 TEST_F(ValidateCFG, MissingMergeLoopBreakGood) {
3935 const std::string text = R"(
3936 OpCapability Shader
3937 OpCapability Linkage
3938 OpMemoryModel Logical GLSL450
3939 %void = OpTypeVoid
3940 %void_fn = OpTypeFunction %void
3941 %bool = OpTypeBool
3942 %undef = OpUndef %bool
3943 %func = OpFunction %void None %void_fn
3944 %entry = OpLabel
3945 OpBranch %loop
3946 %loop = OpLabel
3947 OpLoopMerge %exit %continue None
3948 OpBranch %body
3949 %body = OpLabel
3950 OpBranchConditional %undef %body2 %exit
3951 %body2 = OpLabel
3952 OpBranch %continue
3953 %continue = OpLabel
3954 OpBranch %loop
3955 %exit = OpLabel
3956 OpReturn
3957 OpFunctionEnd
3958 )";
3959
3960 CompileSuccessfully(text);
3961 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3962 }
3963
TEST_F(ValidateCFG,MissingMergeLoopContinueGood)3964 TEST_F(ValidateCFG, MissingMergeLoopContinueGood) {
3965 const std::string text = R"(
3966 OpCapability Shader
3967 OpCapability Linkage
3968 OpMemoryModel Logical GLSL450
3969 %void = OpTypeVoid
3970 %void_fn = OpTypeFunction %void
3971 %bool = OpTypeBool
3972 %undef = OpUndef %bool
3973 %func = OpFunction %void None %void_fn
3974 %entry = OpLabel
3975 OpBranch %loop
3976 %loop = OpLabel
3977 OpLoopMerge %exit %continue None
3978 OpBranch %body
3979 %body = OpLabel
3980 OpBranchConditional %undef %body2 %continue
3981 %body2 = OpLabel
3982 OpBranch %continue
3983 %continue = OpLabel
3984 OpBranch %loop
3985 %exit = OpLabel
3986 OpReturn
3987 OpFunctionEnd
3988 )";
3989
3990 CompileSuccessfully(text);
3991 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
3992 }
3993
TEST_F(ValidateCFG,MissingMergeSwitchBreakGood)3994 TEST_F(ValidateCFG, MissingMergeSwitchBreakGood) {
3995 const std::string text = R"(
3996 OpCapability Shader
3997 OpCapability Linkage
3998 OpMemoryModel Logical GLSL450
3999 %void = OpTypeVoid
4000 %void_fn = OpTypeFunction %void
4001 %bool = OpTypeBool
4002 %undef = OpUndef %bool
4003 %int = OpTypeInt 32 0
4004 %int_0 = OpConstant %int 0
4005 %func = OpFunction %void None %void_fn
4006 %entry = OpLabel
4007 OpSelectionMerge %merge None
4008 OpSwitch %int_0 %merge 1 %b1
4009 %b1 = OpLabel
4010 OpBranchConditional %undef %merge %b2
4011 %b2 = OpLabel
4012 OpBranch %merge
4013 %merge = OpLabel
4014 OpReturn
4015 OpFunctionEnd
4016 )";
4017
4018 CompileSuccessfully(text);
4019 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
4020 }
4021
TEST_F(ValidateCFG,MissingMergeSwitchFallThroughGood)4022 TEST_F(ValidateCFG, MissingMergeSwitchFallThroughGood) {
4023 const std::string text = R"(
4024 OpCapability Shader
4025 OpCapability Linkage
4026 OpMemoryModel Logical GLSL450
4027 %void = OpTypeVoid
4028 %void_fn = OpTypeFunction %void
4029 %bool = OpTypeBool
4030 %undef = OpUndef %bool
4031 %int = OpTypeInt 32 0
4032 %int_0 = OpConstant %int 0
4033 %func = OpFunction %void None %void_fn
4034 %entry = OpLabel
4035 OpSelectionMerge %merge None
4036 OpSwitch %int_0 %b1 1 %b2
4037 %b1 = OpLabel
4038 OpBranchConditional %undef %b3 %b2
4039 %b2 = OpLabel
4040 OpBranch %merge
4041 %b3 = OpLabel
4042 OpBranch %merge
4043 %merge = OpLabel
4044 OpReturn
4045 OpFunctionEnd
4046 )";
4047
4048 CompileSuccessfully(text);
4049 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
4050 }
4051
TEST_F(ValidateCFG,MissingMergeInALoopBad)4052 TEST_F(ValidateCFG, MissingMergeInALoopBad) {
4053 const std::string text = R"(
4054 OpCapability Shader
4055 OpCapability Linkage
4056 OpMemoryModel Logical GLSL450
4057 %void = OpTypeVoid
4058 %void_fn = OpTypeFunction %void
4059 %bool = OpTypeBool
4060 %undef = OpUndef %bool
4061 %func = OpFunction %void None %void_fn
4062 %entry = OpLabel
4063 OpBranch %loop
4064 %loop = OpLabel
4065 OpLoopMerge %exit %continue None
4066 OpBranch %body
4067 %body = OpLabel
4068 OpBranchConditional %undef %b1 %b2
4069 %b1 = OpLabel
4070 OpBranch %exit
4071 %b2 = OpLabel
4072 OpBranch %continue
4073 %continue = OpLabel
4074 OpBranch %loop
4075 %exit = OpLabel
4076 OpReturn
4077 OpFunctionEnd
4078 )";
4079
4080 CompileSuccessfully(text);
4081 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
4082 EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
4083 }
4084
TEST_F(ValidateCFG,MissingMergeCrissCrossBad)4085 TEST_F(ValidateCFG, MissingMergeCrissCrossBad) {
4086 const std::string text = R"(
4087 OpCapability Shader
4088 OpCapability Linkage
4089 OpMemoryModel Logical GLSL450
4090 %void = OpTypeVoid
4091 %void_fn = OpTypeFunction %void
4092 %bool = OpTypeBool
4093 %undef = OpUndef %bool
4094 %func = OpFunction %void None %void_fn
4095 %entry = OpLabel
4096 OpSelectionMerge %merge None
4097 OpBranchConditional %undef %b1 %b2
4098 %b1 = OpLabel
4099 OpBranchConditional %undef %b3 %b4
4100 %b2 = OpLabel
4101 OpBranchConditional %undef %b3 %b4
4102 %b3 = OpLabel
4103 OpBranch %merge
4104 %b4 = OpLabel
4105 OpBranch %merge
4106 %merge = OpLabel
4107 OpReturn
4108 OpFunctionEnd
4109 )";
4110
4111 CompileSuccessfully(text);
4112 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
4113 EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
4114 }
4115
TEST_F(ValidateCFG,ContinueCannotBeSelectionMergeTarget)4116 TEST_F(ValidateCFG, ContinueCannotBeSelectionMergeTarget) {
4117 const std::string text = R"(
4118 OpCapability Shader
4119 OpCapability Linkage
4120 OpMemoryModel Logical GLSL450
4121 OpName %loop "loop"
4122 OpName %continue "continue"
4123 OpName %body "body"
4124 %void = OpTypeVoid
4125 %void_fn = OpTypeFunction %void
4126 %bool = OpTypeBool
4127 %undef = OpUndef %bool
4128 %func = OpFunction %void None %void_fn
4129 %entry = OpLabel
4130 OpBranch %loop
4131 %loop = OpLabel
4132 OpLoopMerge %exit %continue None
4133 OpBranch %body
4134 %body = OpLabel
4135 OpSelectionMerge %continue None
4136 OpBranchConditional %undef %exit %continue
4137 %continue = OpLabel
4138 OpBranch %loop
4139 %exit = OpLabel
4140 OpReturn
4141 OpFunctionEnd
4142 )";
4143
4144 CompileSuccessfully(text);
4145 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
4146 EXPECT_THAT(
4147 getDiagnosticString(),
4148 HasSubstr(
4149 "Header block 3[%body] is contained in the loop construct headed by "
4150 "1[%loop], but its merge block 2[%continue] is not"));
4151 }
4152
TEST_F(ValidateCFG,ContinueCannotBeLoopMergeTarget)4153 TEST_F(ValidateCFG, ContinueCannotBeLoopMergeTarget) {
4154 const std::string text = R"(
4155 OpCapability Shader
4156 OpCapability Linkage
4157 OpMemoryModel Logical GLSL450
4158 OpName %loop "loop"
4159 OpName %continue "continue"
4160 OpName %inner "inner"
4161 %void = OpTypeVoid
4162 %void_fn = OpTypeFunction %void
4163 %bool = OpTypeBool
4164 %undef = OpUndef %bool
4165 %func = OpFunction %void None %void_fn
4166 %entry = OpLabel
4167 OpBranch %loop
4168 %loop = OpLabel
4169 OpLoopMerge %exit %continue None
4170 OpBranchConditional %undef %exit %inner
4171 %inner = OpLabel
4172 OpLoopMerge %continue %inner None
4173 OpBranchConditional %undef %inner %continue
4174 %continue = OpLabel
4175 OpBranch %loop
4176 %exit = OpLabel
4177 OpReturn
4178 OpFunctionEnd
4179 )";
4180
4181 CompileSuccessfully(text);
4182 EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
4183 EXPECT_THAT(
4184 getDiagnosticString(),
4185 HasSubstr(
4186 "Header block 3[%inner] is contained in the loop construct headed by "
4187 "1[%loop], but its merge block 2[%continue] is not"));
4188 }
4189
TEST_F(ValidateCFG,ExitFromConstructWhoseHeaderIsAMerge)4190 TEST_F(ValidateCFG, ExitFromConstructWhoseHeaderIsAMerge) {
4191 const std::string text = R"(
4192 OpCapability Shader
4193 OpCapability Linkage
4194 OpMemoryModel Logical GLSL450
4195 %void = OpTypeVoid
4196 %2 = OpTypeFunction %void
4197 %int = OpTypeInt 32 1
4198 %4 = OpUndef %int
4199 %bool = OpTypeBool
4200 %6 = OpUndef %bool
4201 %7 = OpFunction %void None %2
4202 %8 = OpLabel
4203 OpSelectionMerge %9 None
4204 OpSwitch %4 %10 0 %11
4205 %10 = OpLabel
4206 OpBranch %9
4207 %11 = OpLabel
4208 OpBranch %12
4209 %12 = OpLabel
4210 OpLoopMerge %13 %14 None
4211 OpBranch %15
4212 %15 = OpLabel
4213 OpSelectionMerge %16 None
4214 OpSwitch %4 %17 1 %18 2 %19
4215 %17 = OpLabel
4216 OpBranch %16
4217 %18 = OpLabel
4218 OpBranch %14
4219 %19 = OpLabel
4220 OpBranch %16
4221 %16 = OpLabel
4222 OpBranch %14
4223 %14 = OpLabel
4224 OpBranchConditional %6 %12 %13
4225 %13 = OpLabel
4226 OpSelectionMerge %20 None
4227 OpBranchConditional %6 %21 %20
4228 %21 = OpLabel
4229 OpBranch %9
4230 %20 = OpLabel
4231 OpBranch %10
4232 %9 = OpLabel
4233 OpReturn
4234 OpFunctionEnd
4235 )";
4236
4237 CompileSuccessfully(text);
4238 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
4239 }
4240
TEST_F(ValidateCFG,ExitFromConstructWhoseHeaderIsAMerge2)4241 TEST_F(ValidateCFG, ExitFromConstructWhoseHeaderIsAMerge2) {
4242 const std::string text = R"(
4243 OpCapability Shader
4244 %1 = OpExtInstImport "GLSL.std.450"
4245 OpMemoryModel Logical GLSL450
4246 OpEntryPoint Fragment %2 "main"
4247 OpExecutionMode %2 OriginUpperLeft
4248 %void = OpTypeVoid
4249 %4 = OpTypeFunction %void
4250 %int = OpTypeInt 32 1
4251 %6 = OpUndef %int
4252 %bool = OpTypeBool
4253 %8 = OpUndef %bool
4254 %2 = OpFunction %void None %4
4255 %9 = OpLabel
4256 OpSelectionMerge %10 None
4257 OpSwitch %6 %11 0 %12
4258 %11 = OpLabel
4259 OpBranch %10
4260 %12 = OpLabel
4261 OpBranch %13
4262 %13 = OpLabel
4263 OpLoopMerge %14 %15 None
4264 OpBranch %16
4265 %16 = OpLabel
4266 OpSelectionMerge %17 None
4267 OpSwitch %6 %18 1 %19 2 %20
4268 %18 = OpLabel
4269 OpBranch %17
4270 %19 = OpLabel
4271 OpBranch %15
4272 %20 = OpLabel
4273 OpBranch %17
4274 %17 = OpLabel
4275 OpBranch %15
4276 %15 = OpLabel
4277 OpBranchConditional %8 %13 %14
4278 %14 = OpLabel
4279 OpSelectionMerge %21 None
4280 OpBranchConditional %8 %22 %21
4281 %22 = OpLabel
4282 OpSelectionMerge %23 None
4283 OpBranchConditional %8 %24 %23
4284 %24 = OpLabel
4285 OpBranch %10
4286 %23 = OpLabel
4287 OpBranch %21
4288 %21 = OpLabel
4289 OpBranch %11
4290 %10 = OpLabel
4291 OpReturn
4292 OpFunctionEnd
4293 )";
4294
4295 CompileSuccessfully(text);
4296 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
4297 }
4298
4299 } // namespace
4300 } // namespace val
4301 } // namespace spvtools
4302