[spirv-reader] Emit non-header OpBranchConditional

This emits the equivalent of break-if, break-unless, continue-if,
continue-unless.  But we do it via a regular if-then-else.

Adds a test matrix.
Adds all required tests except for those needing OpSwitch.

Bug: tint:3
Change-Id: I960a40aa00f95f394a92a099c8b12104010ad49f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22603
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 2322589..0cd954e 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1656,6 +1656,12 @@
 
   // Have we emitted the statements for this block?
   bool emitted = false;
+
+  // When entering an if-selection or switch-selection, we will emit the WGSL
+  // construct to cause the divergent branching.  But otherwise, we will
+  // emit a "normal" block terminator, which occurs at the end of this method.
+  bool has_normal_terminator = true;
+
   for (auto iter = entering_constructs.rbegin();
        iter != entering_constructs.rend(); ++iter) {
     const Construct* construct = *iter;
@@ -1695,9 +1701,11 @@
         if (!EmitIfStart(block_info)) {
           return false;
         }
+        has_normal_terminator = false;
         break;
 
       case Construct::kSwitchSelection:
+        has_normal_terminator = false;
         return Fail() << "unhandled: switch construct";
     }
   }
@@ -1708,8 +1716,10 @@
     return false;
   }
 
-  if (!EmitNormalTerminator(block_info)) {
-    return false;
+  if (has_normal_terminator) {
+    if (!EmitNormalTerminator(block_info)) {
+      return false;
+    }
   }
   return success();
 }
@@ -1864,10 +1874,54 @@
       AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
       return true;
     }
+    case SpvOpBranchConditional: {
+      // If both destinations are the same, then do the same as we would
+      // for an unconditional branch (OpBranch).
+      const auto true_dest = terminator.GetSingleWordInOperand(1);
+      const auto false_dest = terminator.GetSingleWordInOperand(2);
+      if (true_dest == false_dest) {
+        // This is like an uncondtional branch.
+        AddStatement(MakeBranch(block_info, *GetBlockInfo(true_dest)));
+        return true;
+      }
+
+      const EdgeKind true_kind = block_info.succ_edge.find(true_dest)->second;
+      const EdgeKind false_kind = block_info.succ_edge.find(false_dest)->second;
+      auto* const true_info = GetBlockInfo(true_dest);
+      auto* const false_info = GetBlockInfo(false_dest);
+      auto cond = MakeExpression(terminator.GetSingleWordInOperand(0)).expr;
+
+      // We have two distinct destinations. But we only get here if this
+      // is a normal terminator; in particular the source block is *not* the
+      // start of an if-selection or a switch-selection.  So at most one branch
+      // is a kForward, kCaseFallThrough, or kIfBreak.
+
+      // The fallthrough case is special because WGSL requires the fallthrough
+      // statement to be last in the case clause.
+      if (true_kind == EdgeKind::kCaseFallThrough ||
+          false_kind == EdgeKind::kCaseFallThrough) {
+        return Fail() << "fallthrough is unhandled";
+      }
+
+      // At this point, at most one edge is kForward or kIfBreak.
+
+      // Emit an 'if' statement to express the *other* branch as a conditional
+      // break or continue.  Either or both of these could be nullptr.
+      // (A nullptr is generated for kIfBreak, kForward, or kBack.)
+      auto true_branch = MakeBranch(block_info, *true_info);
+      auto false_branch = MakeBranch(block_info, *false_info);
+
+      AddStatement(MakeSimpleIf(std::move(cond), std::move(true_branch),
+                                std::move(false_branch)));
+      return true;
+    }
+    case SpvOpSwitch:
+      // TODO(dneto)
+      break;
     default:
       break;
   }
-  // TODO(dneto): emit fallthrough, break, continue
+  // TODO(dneto): emit fallthrough
   return success();
 }
 
@@ -1903,6 +1957,31 @@
   return {nullptr};
 }
 
+std::unique_ptr<ast::Statement> FunctionEmitter::MakeSimpleIf(
+    std::unique_ptr<ast::Expression> condition,
+    std::unique_ptr<ast::Statement> then_stmt,
+    std::unique_ptr<ast::Statement> else_stmt) const {
+  if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
+    return nullptr;
+  }
+  auto if_stmt = std::make_unique<ast::IfStatement>();
+  if_stmt->set_condition(std::move(condition));
+  if (then_stmt != nullptr) {
+    ast::StatementList stmts;
+    stmts.emplace_back(std::move(then_stmt));
+    if_stmt->set_body(std::move(stmts));
+  }
+  if (else_stmt != nullptr) {
+    ast::StatementList stmts;
+    stmts.emplace_back(std::move(else_stmt));
+    ast::ElseStatementList else_stmts;
+    else_stmts.emplace_back(
+        std::make_unique<ast::ElseStatement>(nullptr, std::move(stmts)));
+    if_stmt->set_else_statements(std::move(else_stmts));
+  }
+  return if_stmt;
+}
+
 bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
                                                  bool* already_emitted) {
   if (*already_emitted) {
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index b98eedf..246038f 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -341,12 +341,25 @@
 
   /// Returns a new statement to represent the given branch representing a
   /// "normal" terminator, as in the sense of EmitNormalTerminator.  If no
-  /// WGSL statement is required, the statement will nullptr.
+  /// WGSL statement is required, the statement will be nullptr.
   /// @param src_info the source block
   /// @param dest_info the destination block
+  /// @returns the new statement, or a null statement
   std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
                                              const BlockInfo& dest_info) const;
 
+  /// Returns a new if statement with the given statements as the then-clause
+  /// and the else-clause.  Either or both clauses might be nullptr. If both
+  /// are nullptr, then don't make a new statement and instead return nullptr.
+  /// @param condition the branching condition
+  /// @param then_stmt the statement for the then clause of the if, or nullptr
+  /// @param else_stmt the statement for the else clause of the if, or nullptr
+  /// @returns the new statement, or nullptr
+  std::unique_ptr<ast::Statement> MakeSimpleIf(
+      std::unique_ptr<ast::Expression> condition,
+      std::unique_ptr<ast::Statement> then_stmt,
+      std::unique_ptr<ast::Statement> else_stmt) const;
+
   /// Emits a normal instruction: not a terminator, label, or variable
   /// declaration.
   /// @param inst the instruction
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index c2887dc..52b42f1 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -71,6 +71,7 @@
     %uint_4 = OpConstant %uint 4
     %uint_5 = OpConstant %uint 5
     %uint_6 = OpConstant %uint 6
+    %uint_7 = OpConstant %uint 7
 
     %ptr_Private_uint = OpTypePointer Private %uint
     %var = OpVariable %ptr_Private_uint Private
@@ -4540,7 +4541,35 @@
 }
 
 TEST_F(SpvParserTest,
-       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop) {
+       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch) {
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel    ; single block loop
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel ; outer merge
+     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(20);
+  ASSERT_NE(bi, nullptr);
+  EXPECT_EQ(bi->succ_edge.count(99), 1u);
+  EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserTest,
+       ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch) {
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -4563,6 +4592,8 @@
   ASSERT_NE(bi, nullptr);
   EXPECT_EQ(bi->succ_edge.count(99), 1u);
   EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+  EXPECT_EQ(bi->succ_edge.count(20), 1u);
+  EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
 }
 
 TEST_F(SpvParserTest,
@@ -4709,6 +4740,46 @@
          "construct starting at block 10; branch bypasses merge block 50"));
 }
 
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_EscapeSwitchCase_IsError) {
+  // Code generation assumes that you can't have kCaseFallThrough and kIfBreak
+  // from the same OpBranchConditional.
+  // This checks one direction of that, where the IfBreak is shown it can't
+  // escape a switch case.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; Set up if-break to %99
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 30 %30 40 %40
+
+     %30 = OpLabel ; first case
+        ; branch to %99 would be an if-break, but it bypasess the switch merge
+        ; Also has case fall-through
+     OpBranchConditional %cond2 %99 %40
+
+     %40 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch-selection's merge
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
 TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect) {
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
@@ -5557,6 +5628,56 @@
   EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
 }
 
+TEST_F(SpvParserTest,
+       ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError) {
+  // Inner loop header tries to do continue to outer loop continue target.
+  // This is disallowed by the rule:
+  //    "a continue block is valid only for the innermost loop it is nested
+  //    inside of"
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel ; inner loop.
+     OpStore %var %uint_1
+     OpLoopMerge %59 %50 None
+     OpBranchConditional %cond %59 %80  ; break and outer continue
+
+     %50 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %30 ; inner backedge
+
+     %59 = OpLabel ; inner merge
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; outer continue
+     OpStore %var %uint_4
+     OpBranch %20 ; outer backedge
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 80 is an invalid exit from construct "
+         "starting at block 30; branch bypasses merge block 59"));
+}
+
 TEST_F(SpvParserTest, ClassifyCFGEdges_Fallthrough_CaseTailToCase) {
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
@@ -5651,6 +5772,247 @@
 }
 
 TEST_F(SpvParserTest,
+       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_IfBreak_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kIfBreak.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; Set up if-break to %99
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 30 %30 40 %40
+
+     %30 = OpLabel ; first case
+        ; branch to %99 would be an if-break, but it bypasess the switch merge
+        ; Also has case fall-through
+     OpBranchConditional %cond2 %99 %40
+
+     %40 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch-selection's merge
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from "
+         "construct starting at block 20; branch bypasses merge block 80"));
+}
+
+TEST_F(SpvParserTest,
+       ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kForward.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 30 %30
+
+     ; Try to make branch to 35 a kForward branch
+     %20 = OpLabel ; first case
+     OpBranchConditional %cond2 %25 %30
+
+     %25 = OpLabel
+     OpBranch %99
+
+     %30 = OpLabel ; second case
+     OpBranch %99
+
+     %99 = OpLabel ; if-selection's merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Control flow diverges at block 20 (to 25, 30) but it is not "
+                 "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(
+    SpvParserTest,
+    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnOutside_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the outside. The backedge coming from a case
+  // clause means the switch is inside the continue construct, and the nesting
+  // of the switch's merge means the backedge is coming from a block that is not
+  // at the end of the continue construct.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranch %30
+
+     %30 = OpLabel  ; continue target and
+     OpSelectionMerge %80 None ; switch-selection
+     OpSwitch %selector %80 40 %40 50 %50
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel ; first case
+     OpBranchConditional %cond2 %20 %50
+
+     %50 = OpLabel ; second case
+     OpBranch %80
+
+     %80 = OpLabel ; switch merge
+     OpBranch %20  ; also backedge
+
+     %99 = OpLabel ; loop merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (40->20) from continue construct: 40 is not the "
+                 "last block in the continue construct starting at 30 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(
+    SpvParserTest,
+    FindSwitchCaseSelectionHeaders_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsMerge_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The merge block is also the
+  // fallthrough target.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel ; first case, and loop header
+     OpLoopMerge %50 %40 None
+     OpBranch %40
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel
+     OpBranchConditional %cond2 %20 %50
+
+     %50 = OpLabel ; second case.  also the loop merge ; header must dominate its merge block !
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     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(fe.block_order(), ElementsAre(10, 20, 40, 50, 99));
+  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_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsNotMerge_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The merge block is not the merge
+  // target But the block order gets messed up because of the weird
+  // connectivity.
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+     %20 = OpLabel ; first case, and loop header
+     OpLoopMerge %45 %40 None  ; move the merge to an unreachable block
+     OpBranch %40
+
+     ; try to make a back edge with a fallthrough
+     %40 = OpLabel
+     OpBranchConditional %cond2 %20 %50
+
+     %45 = OpLabel ; merge for the loop
+     OpUnreachable
+
+     %50 = OpLabel ; second case. target of fallthrough
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
+                             "(dominance rule violated)"));
+}
+
+TEST_F(
+    SpvParserTest,
+    ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_NestedMerge_IsError) {
+  // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+  // with kBack.
+  //
+  // This test has the loop on the inside. The fallthrough is an invalid exit
+  // from the loop. However, the block order gets all messed up because going
+  // from 40 to 50 ends up pulling in 99
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel  ; continue target and
+     OpSelectionMerge %99 None ; switch-selection
+     OpSwitch %selector %99 20 %20 50 %50
+
+       %20 = OpLabel ; first case, and loop header
+       OpLoopMerge %49 %40 None
+       OpBranch %40
+
+       ; try to make a back edge with a fallthrough
+       %40 = OpLabel
+       OpBranchConditional %cond2 %20 %50
+
+       %49 = OpLabel ; loop merge
+       OpBranch %99
+
+     %50 = OpLabel ; second case
+     OpBranch %99
+
+     %99 = OpLabel ; switch merge
+     OpReturn
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 49, 99));
+  EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
+                             "(dominance rule violated)"));
+}
+
+TEST_F(SpvParserTest,
        ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_TrueBranch) {
   // This is an unusual one, and is an error. Structurally it looks like this:
   //   switch (val) {
@@ -6866,45 +7228,123 @@
      %100 = OpFunction %void None %voidfn
 
      %10 = OpLabel
+     OpStore %var %uint_1
      OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %99
+     OpBranchConditional %cond %20 %50
 
      %20 = OpLabel
-     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+     OpStore %var %uint_2
+     OpBranchConditional %cond2 %99 %30 ; kIfBreak with kForward
 
-     %80 = OpLabel ; still in then clause
+     %30 = OpLabel ; still in then clause
+     OpStore %var %uint_3
+     OpBranch %99
+
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_4
      OpBranch %99
 
      %99 = OpLabel
+     OpStore %var %uint_5
      OpReturn
      OpFunctionEnd
 )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
 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
+  // at block 80
   auto assembly = CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
      %10 = OpLabel
+     OpStore %var %uint_1
      OpSelectionMerge %99 None
-     OpBranchConditional %cond %20 %30
+     OpBranchConditional %cond %20 %50
 
      %20 = OpLabel
+     OpStore %var %uint_2
      OpBranch %99
 
-     %30 = OpLabel ; else clause
-     OpBranchConditional %cond2 %99 %80 ; break with forward edge
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_3
+     OpBranchConditional %cond2 %99 %80 ; kIfBreak with kForward
 
      %80 = OpLabel ; still in then clause
+     OpStore %var %uint_4
      OpBranch %99
 
      %99 = OpLabel
+     OpStore %var %uint_5
      OpReturn
      OpFunctionEnd
 )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(
+    SpvParserTest,
+    DISABLED_Codegen_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
+  // This is a combination of the previous two, but also adding a premrge and
+  //
+  auto assembly = CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %50
+
+     %20 = OpLabel ; then
+     OpStore %var %uint_2
+     OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
+
+     %21 = OpLabel ; still in then clause
+     OpStore %var %uint_2
+     OpBranch %80 ; to premerge
+
+     %50 = OpLabel ; else clause
+     OpStore %var %uint_3
+     OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
+
+     %51 = OpLabel ; still in else clause
+     OpStore %var %uint_4
+     OpBranch %80 ; to premerge
+
+     %80 = OpLabel ; premerge
+     OpStore %var %uint_5
+     OpBranchConditional %cond3 %81 %99
+
+     %81 = OpLabel ; premerge
+     OpStore %var %uint_6
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_7
+     OpReturn
+     OpFunctionEnd
+)";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, BlockIsContinueForMoreThanOneHeader) {
@@ -7434,7 +7874,6 @@
 }
 
 TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
-  // TODO(dneto): emit conditional break
   auto* p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -7466,17 +7905,28 @@
     Identifier{var}
     ScalarConstructor{1}
   }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
 }
 Assignment{
   Identifier{var}
   ScalarConstructor{999}
 }
 Return{}
-)"));
+)")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_SingleBlock_FalseBackedge) {
-  // TODO(dneto): emit conditional break
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_FalseBackedge) {
   auto* p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
 
@@ -7508,13 +7958,21 @@
     Identifier{var}
     ScalarConstructor{1}
   }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Break{}
+    }
+  }
 }
 Assignment{
   Identifier{var}
   ScalarConstructor{999}
 }
 Return{}
-)"));
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_BothBackedge) {
@@ -7555,7 +8013,7 @@
   ScalarConstructor{999}
 }
 Return{}
-)"));
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
@@ -7812,20 +8270,189 @@
 )")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_Never) {
+TEST_F(SpvParserTest, EmitBody_Loop_Never) {
   // Test case where both branches exit. e.g both go to merge.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_2
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_3
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Break{}
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{3}
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_HeaderBreakAndContinue) {
+TEST_F(SpvParserTest, EmitBody_Loop_HeaderBreakAndContinue) {
   // Header block branches to merge, and to an outer continue.
+  // This is disallowed by the rule:
+  //    "a continue block is valid only for the innermost loop it is nested
+  //    inside of"
+  // See test ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_TrueToBody) {
-  // TODO(dneto): Needs break-unless
+TEST_F(SpvParserTest, EmitBody_Loop_TrueToBody_FalseBreaks) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_4
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{3}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{4}
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_FalseToBody) {
-  // TODO(dneto): Needs break-if
+TEST_F(SpvParserTest, EmitBody_Loop_FalseToBody_TrueBreaks) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_4
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{3}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{4}
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitBody_Loop_NestedIfContinue) {
@@ -7941,8 +8568,240 @@
 )")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyConditionallyBreaks) {
-  // TODO(dneto): Needs "break" support
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue) {
+  // The else-branch has a continue but it's skipped because it's from a
+  // block that immediately precedes the continue construct.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Break{}
+    }
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse) {
+  // The else-branch has a continue but it's skipped because it's from a
+  // block that immediately precedes the continue construct.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %80 %99
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %99 %70
+
+     %70 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranchConditional %cond %70 %99
+
+     %70 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitBody_Return_TopLevel) {
@@ -8435,10 +9294,6 @@
   // TODO(dneto): support switch first.
 }
 
-TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_SingleBlockLoop) {
-  // This case is impossible.  The loop must have a backedge,
-}
-
 TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
   auto* p = parser(test::Assemble(CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
@@ -8761,7 +9616,7 @@
 )")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_CaseFallthrough) {
+TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_Fallthrough) {
   // TODO(dneto): support switch first.
 }
 
@@ -8794,27 +9649,1485 @@
 )")) << ToString(fe.ast_body());
 }
 
-// For normal terminator that is OpBranchConditional, the kBack, kForward,
-// kIfBreak are all invalid proven in earlier tests.
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
-  // Can only be to the same target.
-  // TODO(dneto): needs switch support
+// Test matrix for normal OpBranchConditional:
+//
+//    kBack with:
+//      kBack : TESTED dup general case
+//      kSwitchBreak: invalid (invalid escape, or invalid backedge)
+//      kLoopBreak: TESTED in single- and multi block loop configurations
+//      kLoopContinue: invalid
+//                      If single block loop, then the continue is backward
+//                      If continue is forward, then it's a continue from a
+//                      continue which is also invalid.
+//      kIfBreak: invalid: loop and if must have distinct merge blocks
+//      kCaseFallThrough: invalid: loop header must dominate its merge
+//      kForward: impossible; would be a loop break
+//
+//    kSwitchBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: dup general case
+//      kLoopBreak: invalid; only one kind of break allowed
+//      kLoopContinue: TODO(dneto)
+//      kIfBreak: invalid: switch and if must have distinct merge blocks
+//      kCaseFallThrough: TODO(dneto)
+//      kForward: TODO(dneto)
+//
+//    kLoopBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: dup general case
+//      kLoopContinue: TESTED
+//      kIfBreak: invalid: switch and if must have distinct merge blocks
+//      kCaseFallThrough: TODO(dneto)
+//      kForward: TESTED
+//
+//    kLoopContinue with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: dup general case
+//      kIfBreak: TODO(dneto)
+//      kCaseFallThrough: TODO(dneto)
+//      kForward: TESTED
+//
+//    kIfBreak with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: dup general case
+//      kCaseFallThrough: invalid; violates nesting or unique merges
+//      kForward: invalid: needs a merge instruction
+//
+//    kCaseFallThrough with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: symmetry
+//      kCaseFallThrough: dup general case
+//      kForward: invalid (tested)
+//
+//    kForward with:
+//      kBack : symmetry
+//      kSwitchBreak: symmetry
+//      kLoopBreak: symmetry
+//      kLoopContinue: symmetry
+//      kIfBreak: symmetry
+//      kCaseFallThrough: symmetry
+//      kForward: dup general case
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Back_SingleBlock_Back) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %20
+
+     %99 = OpLabel ; dead
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
 }
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
 TEST_F(SpvParserTest,
-       DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
-  // TODO(dneto): needs switch support
+       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
 }
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_LoopBreak) {
-  // TODO(dneto): needs switch support
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Break{}
+    }
+  }
 }
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_SwitchBreak) {
-  // TODO(dneto): needs switch support
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
 }
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_LoopContinue) {
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranchConditional %cond %99 %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  continuing {
+    If{
+      (
+        ScalarConstructor{false}
+      )
+      {
+        Break{}
+      }
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpBranchConditional %cond %20 %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  continuing {
+    If{
+      (
+        ScalarConstructor{false}
+      )
+      {
+      }
+    }
+    Else{
+      {
+        Break{}
+      }
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
   // TODO(dneto): needs switch support
 }
 
-// TODO(dneto): test normal OpBranchConditional with other than Fallthrough
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Break{}
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %99 %99
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  Break{}
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %25
+
+     ; Need this extra selection to make another block between
+     ; %30 and the continue target, so we actually induce a Continue
+     ; statement to exist.
+     %25 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; continue on true
+     OpBranchConditional %cond %80 %99
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{true}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{2}
+      }
+      If{
+        (
+          ScalarConstructor{false}
+        )
+        {
+          Continue{}
+        }
+      }
+      Else{
+        {
+          Break{}
+        }
+      }
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %25
+
+     ; Need this extra selection to make another block between
+     ; %30 and the continue target, so we actually induce a Continue
+     ; statement to exist.
+     %25 = OpLabel
+     OpSelectionMerge %40 None
+     OpBranchConditional %cond2 %30 %40
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; continue on false
+     OpBranchConditional %cond %99 %80
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  If{
+    (
+      ScalarConstructor{true}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{2}
+      }
+      If{
+        (
+          ScalarConstructor{false}
+        )
+        {
+          Break{}
+        }
+      }
+      Else{
+        {
+          Continue{}
+        }
+      }
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnTrue) {
+  // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnFalse) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; forward on true
+     OpBranchConditional %cond %40 %99
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; break; forward on false
+     OpBranchConditional %cond %99 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Break{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Continue_FromHeader) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranchConditional %cond %80 %80 ; to continue
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %80 %80 ; to continue
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional) {
+  // Create an intervening block so we actually require a "continue" statement
+  // instead of just an adjacent fallthrough to the continue target.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranchConditional %cond3 %80 %80 ; to continue
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{true}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{3}
+      }
+      Continue{}
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{4}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{5}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+ ; true to if's merge;  false to continue
+     OpBranchConditional %cond3 %50 %80
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{true}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{3}
+      }
+      If{
+        (
+          ScalarConstructor{false}
+        )
+        {
+        }
+      }
+      Else{
+        {
+          Continue{}
+        }
+      }
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{4}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{5}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond2 %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+ ; false to if's merge;  true to continue
+     OpBranchConditional %cond3 %80 %50
+
+     %50 = OpLabel ; merge for selection
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_6
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{true}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{3}
+      }
+      If{
+        (
+          ScalarConstructor{false}
+        )
+        {
+          Continue{}
+        }
+      }
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{4}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{5}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
+  // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
+  // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; continue; forward on true
+     OpBranchConditional %cond %40 %80
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+    }
+  }
+  Else{
+    {
+      Continue{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnFalse) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+; continue; forward on true
+     OpBranchConditional %cond %80 %40
+
+     %40 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+Loop{
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{1}
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Continue{}
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{3}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_IfBreak_IfBreak_Same) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %99
+
+     %20 = OpLabel ; dead
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+  Identifier{var}
+  ScalarConstructor{0}
+}
+If{
+  (
+    ScalarConstructor{false}
+  )
+  {
+  }
+}
+Else{
+  {
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_BranchConditional_IfBreak_IfBreak_DifferentIsError) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_0
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %20 %99
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpSelectionMerge %89 None
+     OpBranchConditional %cond %30 %89
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranchConditional %cond %89 %99 ; invalid divergence
+
+     %89 = OpLabel ; inner if-merge
+     OpBranch %99
+
+     %99 = OpLabel ; outer if-merge
+     OpStore %var %uint_5
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
+  EXPECT_THAT(
+      p->error(),
+      Eq("Branch from block 30 to block 99 is an invalid exit from construct "
+         "starting at block 20; branch bypasses merge block 89"));
+}
+
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
+  // Can only be to the same target.
+  // TODO(dneto): needs switch support
+}
+TEST_F(
+    SpvParserTest,
+    DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
+  // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_Forward_Forward_Same) {
+  // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+       DISABLED_EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
+  // TODO(dneto): needs switch support
+}
 
 TEST_F(SpvParserTest,
        DISABLED_Switch_NotAsSelectionHeader_NonDefaultBranchesAreContinue) {