[spirv-reader] Check some merge block dominance

A merge block must be dominated by its own header.
This CL checks the cases where that fails because the
merge block is also the:
- the default or case header for a switch (a different header)
- the true-head, false-head, or premerge-head for an if-selection
  with a different header

Bug: tint:3
Change-Id: I6dd1fae162e9d33bd9a0b43d3ca9558cebed058b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22680
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index d326ead..2322589 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -943,6 +943,17 @@
                     << default_block->default_head_for->begin_id << " and "
                     << construct->begin_id;
     }
+    if ((default_block->header_for_merge != 0) &&
+        (default_block->header_for_merge != construct->begin_id)) {
+      // The switch instruction for this default block is an alternate path to
+      // the merge block, and hence the merge block is not dominated by its own
+      // (different) header.
+      return Fail() << "Block " << default_block->id
+                    << " is the default block for switch-selection header "
+                    << construct->begin_id << " and also the merge block for "
+                    << default_block->header_for_merge
+                    << " (violates dominance rule)";
+    }
 
     default_block->default_head_for = construct.get();
     default_block->default_is_merge = default_block->pos == construct->end_pos;
@@ -991,6 +1002,17 @@
                       << " to case target block " << case_target_id
                       << " escapes the selection construct";
       }
+      if (case_block->header_for_merge != 0 &&
+          case_block->header_for_merge != construct->begin_id) {
+        // The switch instruction for this case block is an alternate path to
+        // the merge block, and hence the merge block is not dominated by its
+        // own (different) header.
+        return Fail() << "Block " << case_block->id
+                      << " is a case block for switch-selection header "
+                      << construct->begin_id << " and also the merge block for "
+                      << case_block->header_for_merge
+                      << " (violates dominance rule)";
+      }
 
       // Mark the target as a case target.
       if (case_block->case_head_for) {
@@ -1309,6 +1331,29 @@
       false_head_info->exclusive_false_head_for = construct.get();
     }
 
+    if ((true_head_info->header_for_merge != 0) &&
+        (true_head_info->header_for_merge != construct->begin_id)) {
+      // The OpBranchConditional instruction for the true head block is an
+      // alternate path to the merge block, and hence the merge block is not
+      // dominated by its own (different) header.
+      return Fail() << "Block " << true_head
+                    << " is the true branch for if-selection header "
+                    << construct->begin_id << " and also the merge block for header block "
+                    << true_head_info->header_for_merge
+                    << " (violates dominance rule)";
+    }
+    if ((false_head_info->header_for_merge != 0) &&
+        (false_head_info->header_for_merge != construct->begin_id)) {
+      // The OpBranchConditional instruction for the false head block is an
+      // alternate path to the merge block, and hence the merge block is not
+      // dominated by its own (different) header.
+      return Fail() << "Block " << false_head
+                    << " is the false branch for if-selection header "
+                    << construct->begin_id << " and also the merge block for header block "
+                    << false_head_info->header_for_merge
+                    << " (violates dominance rule)";
+    }
+
     if (contains_true && contains_false && (true_head_pos != false_head_pos)) {
       // This construct has both a "then" clause and an "else" clause.
       //
@@ -1382,6 +1427,23 @@
               auto* dest_block_info = GetBlockInfo(dest_id);
               dest_block_info->premerge_head_for = construct.get();
               if_header_info->premerge_head = dest_block_info;
+              if (dest_block_info->header_for_merge != 0) {
+                // Premerge has two edges coming into it, from the then-clause
+                // and the else-clause. It's also, by construction, not the
+                // merge block of the if-selection.  So it must not be a merge
+                // block itself. The OpBranchConditional instruction for the
+                // false head block is an alternate path to the merge block, and
+                // hence the merge block is not dominated by its own (different)
+                // header.
+                return Fail()
+                       << "Block " << premerge_id
+                       << " is the merge block for " << dest_block_info->header_for_merge
+                       << " but has alternate paths reaching it, starting from"
+                       << " blocks " << true_head << " and " << false_head
+                       << " which are the true and false branches for the"
+                       << " if-selection header block " << construct->begin_id
+                       << " (violates dominance rule)";
+              }
             }
             break;
           }
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 447c259..d3b7efc 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -79,15 +79,22 @@
   )";
 }
 
+/// Runs the necessary flow until and including finding switch case
+/// headers.
+/// @returns the result of finding switch case headers.
+bool FlowFindSwitchCaseHeaders(FunctionEmitter* fe) {
+  fe->RegisterBasicBlocks();
+  EXPECT_TRUE(fe->RegisterMerges()) << fe->parser()->error();
+  fe->ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe->VerifyHeaderContinueMergeOrder()) << fe->parser()->error();
+  EXPECT_TRUE(fe->LabelControlFlowConstructs()) << fe->parser()->error();
+  return fe->FindSwitchCaseHeaders();
+}
+
 /// Runs the necessary flow until and including classify CFG edges,
 /// @returns the result of classify CFG edges.
 bool FlowClassifyCFGEdges(FunctionEmitter* fe) {
-  fe->RegisterBasicBlocks();
-  fe->ComputeBlockOrderAndPositions();
-  EXPECT_TRUE(fe->VerifyHeaderContinueMergeOrder()) << fe->parser()->error();
-  EXPECT_TRUE(fe->RegisterMerges()) << fe->parser()->error();
-  EXPECT_TRUE(fe->LabelControlFlowConstructs()) << fe->parser()->error();
-  EXPECT_TRUE(fe->FindSwitchCaseHeaders()) << fe->parser()->error();
+  EXPECT_TRUE(FlowFindSwitchCaseHeaders(fe)) << fe->parser()->error();
   return fe->ClassifyCFGEdges();
 }
 
@@ -3676,7 +3683,7 @@
                              "escapes the selection construct"));
 }
 
-TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultForTwoSwitches) {
+TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultForTwoSwitches_AsMerge) {
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -3711,7 +3718,51 @@
   fe.RegisterMerges();
   fe.LabelControlFlowConstructs();
   EXPECT_FALSE(fe.FindSwitchCaseHeaders());
-  EXPECT_THAT(p->error(), Eq("Block 89 is declared as the default target for "
+  EXPECT_THAT(p->error(),
+              Eq("Block 89 is the default block for switch-selection header 10 "
+                 "and also the merge block for 50 (violates dominance rule)"));
+}
+
+TEST_F(SpvParserTest,
+       FindSwitchCaseHeaders_DefaultForTwoSwitches_AsCaseClause) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %80 20 %20
+
+     %20 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel
+     OpSelectionMerge %89 None
+     OpSwitch %selector %80 60 %60
+
+     %60 = OpLabel
+     OpBranch %89 ; fallthrough
+
+     %80 = OpLabel ; default for both switches
+     OpBranch %89
+
+     %89 = OpLabel ; inner selection merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer selection mege
+     OpReturn
+
+     OpFunctionEnd
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  fe.RegisterBasicBlocks();
+  fe.ComputeBlockOrderAndPositions();
+  EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
+  fe.RegisterMerges();
+  fe.LabelControlFlowConstructs();
+  EXPECT_FALSE(fe.FindSwitchCaseHeaders());
+  EXPECT_THAT(p->error(), Eq("Block 80 is declared as the default target for "
                              "two OpSwitch instructions, at blocks 10 and 50"));
 }
 
@@ -5994,6 +6045,70 @@
          "construct starting at block 50; branch bypasses merge block 80"));
 }
 
+TEST_F(
+    SpvParserTest,
+    FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; case and merge block. Error
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is a case block for switch-selection header 10 and "
+                 "also the merge block for 20 (violates dominance rule)"));
+}
+
+TEST_F(
+    SpvParserTest,
+    ClassifyCFGEdges_DomViolation_SwitchDefault_CantBeMergeForOtherConstruct) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpSwitch %selector %50 20 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %30 %50
+
+     %30 = OpLabel
+     OpBranch %50
+
+     %50 = OpLabel ; default-case and merge block. Error
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Block 50 is the default block for switch-selection header 10 "
+                 "and also the merge block for 20 (violates dominance rule)"));
+}
+
 TEST_F(SpvParserTest, ClassifyCFGEdges_TooManyBackedges) {
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
@@ -6653,6 +6768,96 @@
                  "a structured header (it has no merge instruction)"));
 }
 
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_DomViolation_Merge_CantBeTrueHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %40 %20
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner merge, and true-head for outer if-selection
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq("Block 40 is the true branch for if-selection header 10 and also the merge block for header block 20 (violates dominance rule)"));
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_DomViolation_Merge_CantBeFalseHeader) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %40
+
+     %20 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond %30 %40
+
+     %30 = OpLabel
+     OpBranch %40
+
+     %40 = OpLabel ; inner merge, and true-head for outer if-selection
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq("Block 40 is the false branch for if-selection header 10 and also the merge block for header block 20 (violates dominance rule)"));
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_DomViolation_Merge_CantBePremerge) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel ; outer if-header
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel
+     OpBranch %70
+
+     %50 = OpLabel ; inner if-header
+     OpSelectionMerge %70 None
+     OpBranchConditional %cond %60 %70
+
+     %60 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel ; inner merge, and premerge for outer if-selection
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel ; outer merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(p->error(), Eq("Block 70 is the merge block for 50 but has alternate paths reaching it, starting from blocks 20 and 50 which are the true and false branches for the if-selection header block 10 (violates dominance rule)"));
+}
+
 TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromThen_ForwardWithinThen) {
   // TODO(dneto): We can make this case work, if we injected
   //    if (!cond2) { rest-of-then-body }