[spirv-reader] Find if-selection internal headers

Finds the "then", the "else", and "premerge" nodes.
The premerge node, if it exists, is the first block where
the normal forward flow of the "then" and "else" clauses
converge, but before the merge block.

Finds error case where there a block has both an if-break
edge and a forward-to-premerge.  There is no good way
to model that in a high level language.

Bug: tint:3
Change-Id: I759fc539f3480e38d091041db6a9abd15f3df769
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/21240
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index b3756b6..9889e18 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -451,7 +451,9 @@
   if (!ClassifyCFGEdges()) {
     return false;
   }
-  // TODO(dneto): FindIfSelectionHeaders
+  if (!FindIfSelectionInternalHeaders()) {
+    return false;
+  }
   // TODO(dneto): FindInvalidNestingBetweenSelections
 
   if (!EmitFunctionVariables()) {
@@ -956,7 +958,8 @@
   // For each branch encountered, classify each edge (S,T) as:
   //    - a back-edge
   //    - a structured exit (specific ways of branching to enclosing construct)
-  //    - a normal (forward) edge
+  //    - a normal (forward) edge, either natural control flow or a case
+  //    fallthrough
   //
   // If more than one block is targeted by a normal edge, then S must be a
   // structured header.
@@ -990,7 +993,8 @@
     // There should only be one backedge per backedge block.
     uint32_t num_backedges = 0;
 
-    // Track destinations for normal forward edges.
+    // Track destinations for normal forward edges, either kForward
+    // or kCaseFallThrough
     std::vector<uint32_t> normal_forward_edges;
 
     if (successors.empty() && src_construct.enclosing_continue) {
@@ -1167,6 +1171,128 @@
   return success();
 }
 
+bool FunctionEmitter::FindIfSelectionInternalHeaders() {
+  if (failed()) {
+    return false;
+  }
+  for (auto& construct : constructs_) {
+    if (construct->kind != Construct::kIfSelection) {
+      continue;
+    }
+    const auto* branch =
+        GetBlockInfo(construct->begin_id)->basic_block->terminator();
+    const auto true_head = branch->GetSingleWordInOperand(1);
+    const auto false_head = branch->GetSingleWordInOperand(2);
+
+    auto* true_head_info = GetBlockInfo(true_head);
+    auto* false_head_info = GetBlockInfo(false_head);
+    const auto true_head_pos = true_head_info->pos;
+    const auto false_head_pos = false_head_info->pos;
+
+    const bool contains_true = construct->ContainsPos(true_head_pos);
+    const bool contains_false = construct->ContainsPos(false_head_pos);
+
+    if (contains_true) {
+      true_head_info->true_head_for = construct.get();
+    }
+    if (contains_false) {
+      false_head_info->false_head_for = construct.get();
+    }
+    if ((!contains_true) && contains_false) {
+      false_head_info->exclusive_false_head_for = construct.get();
+    }
+
+    if (contains_true && contains_false && (true_head_pos != false_head_pos)) {
+      // This construct has both a "then" clause and an "else" clause.
+      //
+      // We have this structure:
+      //
+      //   Option 1:
+      //
+      //     * condbranch
+      //        * true-head (start of then-clause)
+      //        ...
+      //        * end-then-clause
+      //        * false-head (start of else-clause)
+      //        ...
+      //        * end-false-clause
+      //        * premerge-head
+      //        ...
+      //     * selection merge
+      //
+      //   Option 2:
+      //
+      //     * condbranch
+      //        * true-head (start of then-clause)
+      //        ...
+      //        * end-then-clause
+      //        * false-head (start of else-clause) and also premerge-head
+      //        ...
+      //        * end-false-clause
+      //     * selection merge
+      //
+      //   Option 3:
+      //
+      //     * condbranch
+      //        * false-head (start of else-clause)
+      //        ...
+      //        * end-else-clause
+      //        * true-head (start of then-clause) and also premerge-head
+      //        ...
+      //        * end-then-clause
+      //     * selection merge
+      //
+      // The premerge-head exists if there is a kForward branch from the end
+      // of the first clause to a block within the surrounding selection.
+      // The first clause might be a then-clause or an else-clause.
+      const auto second_head = std::max(true_head_pos, false_head_pos);
+      const auto end_first_clause_pos = second_head - 1;
+      assert(end_first_clause_pos < block_order_.size());
+      const auto end_first_clause = block_order_[end_first_clause_pos];
+      uint32_t premerge_id = 0;
+      uint32_t if_break_id = 0;
+      for (auto& then_succ_iter : GetBlockInfo(end_first_clause)->succ_edge) {
+        const uint32_t dest_id = then_succ_iter.first;
+        const auto edge_kind = then_succ_iter.second;
+        switch (edge_kind) {
+          case EdgeKind::kIfBreak:
+            if_break_id = dest_id;
+            break;
+          case EdgeKind::kForward: {
+            if (construct->ContainsPos(GetBlockInfo(dest_id)->pos)) {
+              // It's a premerge.
+              if (premerge_id != 0) {
+                // TODO(dneto): I think this is impossible to trigger at this
+                // point in the flow. It would require a merge instruction to
+                // get past the check of "at-most-one-forward-edge".
+                return Fail()
+                       << "invalid structure: then-clause headed by block "
+                       << true_head << " ending at block " << end_first_clause
+                       << " has two forward edges to within selection"
+                       << " going to " << premerge_id << " and " << dest_id;
+              }
+              premerge_id = dest_id;
+              GetBlockInfo(dest_id)->premerge_head_for = construct.get();
+            }
+            break;
+          }
+          default:
+            break;
+        }
+      }
+      if (if_break_id != 0 && premerge_id != 0) {
+        return Fail() << "Block " << end_first_clause
+                      << " in if-selection headed at block "
+                      << construct->begin_id
+                      << " branches to both the merge block " << if_break_id
+                      << " and also to block " << premerge_id
+                      << " later in the selection";
+      }
+    }
+  }
+  return success();
+}
+
 bool FunctionEmitter::EmitFunctionVariables() {
   if (failed()) {
     return false;
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 00a8641..86a9a6f 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -101,6 +101,9 @@
   /// The immediately enclosing structured construct.
   const Construct* construct = nullptr;
 
+  /// Maps the ID of a successor block (in the CFG) to its edge classification.
+  std::unordered_map<uint32_t, EdgeKind> succ_edge;
+
   /// The following fields record relationships among blocks in a selection
   /// construct for an OpSwitch instruction.
 
@@ -118,8 +121,24 @@
   /// The list of switch values that cause a branch to this block.
   std::unique_ptr<std::vector<uint64_t>> case_values;
 
-  /// Maps the ID of a successor block (in the CFG) to its edge classification.
-  std::unordered_map<uint32_t, EdgeKind> succ_edge;
+  /// The following fields record relationships among blocks in a selection
+  /// construct for an OpBranchConditional instruction.
+
+  /// If not null, then the pointed-at construct is a selection for an
+  /// OpBranchConditional and this block is the "true" target for it.  We say
+  /// this block "heads" the true case.
+  const Construct* true_head_for = nullptr;
+  /// If not null, then the pointed-at construct is a selection for an
+  /// OpBranchConditional and this block is the "false" target for it.  We say
+  /// this block "heads" the false case.
+  const Construct* false_head_for = nullptr;
+  /// If not null, then the pointed-at construct is the first block at which
+  /// control reconverges between the "then" and "else" clauses, but before
+  /// the merge block for that selection.
+  const Construct* premerge_head_for = nullptr;
+  /// The construct for which this block is the false head, and that construct
+  /// does not have a true head.
+  const Construct* exclusive_false_head_for = nullptr;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
@@ -228,6 +247,16 @@
   /// @returns false on failure
   bool ClassifyCFGEdges();
 
+  /// Marks the blocks within a selection construct that are the first blocks
+  /// in the "then" clause, the "else" clause, and the "premerge" clause.
+  /// The head of the premerge clause is the block, if it exists, at which
+  /// control flow reconverges from the "then" and "else" clauses, but before
+  /// before the merge block for that selection.   The existence of a premerge
+  /// should be an exceptional case, but is allowed by the structured control
+  /// flow rules.
+  /// @returns false if bad nesting has been detected.
+  bool FindIfSelectionInternalHeaders();
+
   /// Emits declarations of function variables.
   /// @returns false if emission failed.
   bool EmitFunctionVariables();
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 8fe36fe..125cb73 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -73,6 +73,14 @@
   return fe->ClassifyCFGEdges();
 }
 
+/// Runs the necessary flow until and including finding if-selection
+/// internal headers.
+/// @returns the result of classify CFG edges.
+bool FlowFindIfSelectionInternalHeaders(FunctionEmitter* fe) {
+  EXPECT_TRUE(FlowClassifyCFGEdges(fe)) << fe->parser()->error();
+  return fe->FindIfSelectionInternalHeaders();
+}
+
 TEST_F(SpvParserTest, TerminatorsAreSane_SingleBlock) {
   auto* p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
@@ -4571,7 +4579,7 @@
 
   auto* bi = fe.GetBlockInfo(20);
   ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kIfBreak);
 }
 
@@ -4600,13 +4608,13 @@
   // Then clause
   auto* bi20 = fe.GetBlockInfo(20);
   ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
   EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
 
   // Else clause
   auto* bi50 = fe.GetBlockInfo(50);
   ASSERT_NE(bi50, nullptr);
-  EXPECT_EQ(bi50->succ_edge.count(99), 1);
+  EXPECT_EQ(bi50->succ_edge.count(99), 1u);
   EXPECT_EQ(bi50->succ_edge[99], EdgeKind::kIfBreak);
 }
 
@@ -4741,7 +4749,7 @@
 
   auto* bi = fe.GetBlockInfo(30);
   ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
 }
 
@@ -4773,7 +4781,7 @@
 
   auto* bi = fe.GetBlockInfo(30);
   ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
 }
 
@@ -4973,7 +4981,7 @@
 
   auto* bi = fe.GetBlockInfo(40);
   ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
 }
 
@@ -5012,7 +5020,7 @@
 
   auto* bi = fe.GetBlockInfo(40);
   ASSERT_NE(bi, nullptr);
-  EXPECT_EQ(bi->succ_edge.count(99), 1);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
 }
 
@@ -6011,7 +6019,7 @@
 
   auto* bi60 = fe.GetBlockInfo(60);
   ASSERT_NE(bi60, nullptr);
-  EXPECT_EQ(bi60->succ_edge.count(99), 1);
+  EXPECT_EQ(bi60->succ_edge.count(99), 1u);
   EXPECT_EQ(bi60->succ_edge[99], EdgeKind::kIfBreak);
 }
 
@@ -6042,10 +6050,533 @@
 
   auto* bi20 = fe.GetBlockInfo(20);
   ASSERT_NE(bi20, nullptr);
-  EXPECT_EQ(bi20->succ_edge.count(99), 1);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
   EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
 }
 
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_NoIf) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
+
+  auto* bi = fe.GetBlockInfo(10);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->true_head_for, nullptr);
+  EXPECT_EQ(bi->false_head_for, nullptr);
+  EXPECT_EQ(bi->premerge_head_for, nullptr);
+  EXPECT_EQ(bi->premerge_head_for, nullptr);
+  EXPECT_EQ(bi->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ThenElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->true_head_for, nullptr);
+  EXPECT_EQ(bi20->true_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->false_head_for, nullptr);
+  EXPECT_EQ(bi20->premerge_head_for, nullptr);
+  EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head_for, nullptr);
+  ASSERT_NE(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->false_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->premerge_head_for, nullptr);
+  EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_IfOnly) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  ASSERT_NE(bi30->true_head_for, nullptr);
+  EXPECT_EQ(bi30->true_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->premerge_head_for, nullptr);
+  EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ElseOnly) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %30 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head_for, nullptr);
+  ASSERT_NE(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->false_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->premerge_head_for, nullptr);
+  ASSERT_NE(bi30->exclusive_false_head_for, nullptr);
+  EXPECT_EQ(bi30->exclusive_false_head_for->begin_id, 10);
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Regardless) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %20 ; same target
+
+     %20 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->true_head_for, nullptr);
+  EXPECT_EQ(bi20->true_head_for->begin_id, 10);
+  ASSERT_NE(bi20->false_head_for, nullptr);
+  EXPECT_EQ(bi20->false_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->premerge_head_for, nullptr);
+  EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Premerge_Simple) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %80
+
+     %30 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  ASSERT_NE(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for->begin_id, 10);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %30
+
+     %30 = OpLabel
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->true_head_for, nullptr);
+  EXPECT_EQ(bi20->true_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->false_head_for, nullptr);
+  EXPECT_EQ(bi20->premerge_head_for, nullptr);
+  EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head_for, nullptr);
+  ASSERT_NE(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->false_head_for->begin_id, 10);
+  ASSERT_NE(bi30->premerge_head_for, nullptr);
+  EXPECT_EQ(bi30->premerge_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %80 ; branches to premerge
+
+     %30 = OpLabel ; else
+     OpBranch %20  ; branches to then
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->true_head_for, nullptr);
+  EXPECT_EQ(bi20->true_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->false_head_for, nullptr);
+  ASSERT_NE(bi20->premerge_head_for, nullptr);
+  EXPECT_EQ(bi20->premerge_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head_for, nullptr);
+  ASSERT_NE(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->false_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->premerge_head_for, nullptr);
+  EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_Premerge_MultiCandidate_Error) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     ; Try to force several branches down into "else" territory,
+     ; but we error out earlier in the flow due to lack of merge
+     ; instruction.
+     OpBranchConditional %cond2  %70 %80
+
+     %30 = OpLabel
+     OpBranch %70
+
+     %70 = OpLabel ; candidate premerge
+     OpBranch %80
+
+     %80 = OpLabel ; canddiate premerge
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  // Error out sooner in the flow
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 20 (to 70, 80) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_IfBreak_FromThen_ForwardWithinThen) {
+  // TODO(dneto): We can make this case work, if we injected
+  //    if (!cond2) { rest-of-then-body }
+  // at block 30
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
+
+  auto* bi20 = fe.GetBlockInfo(20);
+  ASSERT_NE(bi20, nullptr);
+  ASSERT_NE(bi20->true_head_for, nullptr);
+  EXPECT_EQ(bi20->true_head_for->begin_id, 10);
+  EXPECT_EQ(bi20->false_head_for, nullptr);
+  EXPECT_EQ(bi20->premerge_head_for, nullptr);
+  EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
+  EXPECT_EQ(bi20->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
+  EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+  EXPECT_EQ(bi80->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi80->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_IfBreak_FromElse_ForwardWithinElse) {
+  // TODO(dneto): We can make this case work, if we injected
+  //    if (!cond2) { rest-of-else-body }
+  // at block 30
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel ; else clause
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+
+  auto* bi30 = fe.GetBlockInfo(30);
+  ASSERT_NE(bi30, nullptr);
+  EXPECT_EQ(bi30->true_head_for, nullptr);
+  ASSERT_NE(bi30->false_head_for, nullptr);
+  EXPECT_EQ(bi30->false_head_for->begin_id, 10);
+  EXPECT_EQ(bi30->premerge_head_for, nullptr);
+  EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
+  EXPECT_EQ(bi30->succ_edge.count(80), 1u);
+  EXPECT_EQ(bi30->succ_edge[80], EdgeKind::kForward);
+  EXPECT_EQ(bi30->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
+
+  auto* bi80 = fe.GetBlockInfo(80);
+  ASSERT_NE(bi80, nullptr);
+  EXPECT_EQ(bi80->true_head_for, nullptr);
+  EXPECT_EQ(bi80->false_head_for, nullptr);
+  EXPECT_EQ(bi80->premerge_head_for, nullptr);
+  EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
+  EXPECT_EQ(bi80->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi80->succ_edge[99], EdgeKind::kIfBreak);
+}
+
+TEST_F(SpvParserTest,
+       FindIfSelectionInternalHeaders_IfBreak_WithForwardToPremerge_Error) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel ; then
+     OpBranchConditional %cond2 %99 %80 ; break with forward to premerge is error
+
+     %30 = OpLabel ; else
+     OpBranch %80
+
+     %80 = OpLabel ; premerge node
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+  auto* p = parser(test::Assemble(assembly));
+  std::cout << assembly;
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Block 20 in if-selection headed at block 10 branches to both the "
+         "merge block 99 and also to block 80 later in the selection"));
+}
+
+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 }
+  // at block 30
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+}
+
+TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromElse_ForwardWithinElse) {
+  // TODO(dneto): We can make this case work, if we injected
+  //    if (!cond2) { rest-of-else-body }
+  // at block 30
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %30
+
+     %20 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel ; else clause
+     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+
+     %80 = OpLabel ; still in then clause
+     OpBranch %99
+
+     %99 = OpLabel
+     OpReturn
+     OpFunctionEnd
+)";
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader