Allows breaks selection breaks to switches (#2605)

Fixes #2604

* Allow selection constructs to branch to the nearest selection merge
whose header is terminated by an OpSwitch
  * Cleanup break and continue checks generally
  * add tests
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 7e106e6..1564449 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -133,6 +133,7 @@
   // - Selection:
   //  - branch to its merge
   //  - branch to nearest enclosing loop merge or continue
+  //  - branch to nearest enclosing switch selection merge
   // - Loop:
   //  - branch to its merge
   //  - branch to its continue
@@ -168,13 +169,17 @@
       return true;
     }
 
+    bool seen_switch = false;
     auto header = entry_block();
-    auto block = header;
+    auto block = header->immediate_dominator();
     while (block) {
       auto terminator = block->terminator();
       auto index = terminator - &_.ordered_instructions()[0];
       auto merge_inst = &_.ordered_instructions()[index - 1];
-      if (merge_inst->opcode() == SpvOpLoopMerge) {
+      if (merge_inst->opcode() == SpvOpLoopMerge ||
+          (header->terminator()->opcode() != SpvOpSwitch &&
+           merge_inst->opcode() == SpvOpSelectionMerge &&
+           terminator->opcode() == SpvOpSwitch)) {
         auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
         auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
         if (merge_block->dominates(*header)) {
@@ -182,10 +187,20 @@
           continue;
         }
 
-        auto continue_target = merge_inst->GetOperandAs<uint32_t>(1u);
-        if (dest->id() == merge_target || dest->id() == continue_target) {
+        if ((!seen_switch || merge_inst->opcode() == SpvOpLoopMerge) &&
+            dest->id() == merge_target) {
           return true;
+        } else if (merge_inst->opcode() == SpvOpLoopMerge) {
+          auto continue_target = merge_inst->GetOperandAs<uint32_t>(1u);
+          if (dest->id() == continue_target) {
+            return true;
+          }
         }
+
+        if (terminator->opcode() == SpvOpSwitch) seen_switch = true;
+
+        // Hit an enclosing loop and didn't break or continue.
+        if (merge_inst->opcode() == SpvOpLoopMerge) return false;
       }
 
       block = block->immediate_dominator();
diff --git a/source/val/construct.h b/source/val/construct.h
index 172976d..9476760 100644
--- a/source/val/construct.h
+++ b/source/val/construct.h
@@ -116,7 +116,9 @@
   //  * branch to the associated merge
   //  * branch to the merge or continue of the innermost loop containing the
   //  selection
-  // Loop:
+  //  * branch to the merge block of the innermost switch containing the
+  //  selection
+  //  Loop:
   //  * branch to the associated merge or continue
   // Continue:
   //  * back-edge to the associated loop header
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 0bb8fec..73b2bbc 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -3403,6 +3403,149 @@
                         "9[%9], but not via a structured exit"));
 }
 
+TEST_F(ValidateCFG, BreakFromSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %4 %11 %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, InvalidBreakFromSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpSwitch %5 %11 0 %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 12[%12] exits the selection headed by <ID> "
+                        "10[%10], but not via a structured exit"));
+}
+
+TEST_F(ValidateCFG, BreakToOuterSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpSwitch %5 %11 0 %12
+%12 = OpLabel
+OpSelectionMerge %13 None
+OpBranchConditional %4 %13 %14
+%14 = OpLabel
+OpBranch %9
+%13 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 14[%14] exits the selection headed by <ID> "
+                        "10[%10], but not via a structured exit"));
+}
+
+TEST_F(ValidateCFG, BreakToOuterLoop) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpUndef %2
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %9 %11 None
+OpBranch %12
+%12 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %3 %11 %13
+%13 = OpLabel
+OpBranch %8
+%11 = OpLabel
+OpBranchConditional %3 %9 %10
+%9 = OpLabel
+OpBranchConditional %3 %8 %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 13[%13] exits the loop headed by <ID> "
+                        "10[%10], but not via a structured exit"));
+}
+
 /// TODO(umar): Nested CFG constructs
 
 }  // namespace