[spirv-reader]: Support OpBranch

Bug: tint:3
Change-Id: I39d03f4fc29c7b60dc09d0bafc3afaec754671a0
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22425
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index e3ef474..eb1e6fc 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -27,7 +27,10 @@
 #include "src/ast/as_expression.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/binary_expression.h"
+#include "src/ast/break_statement.h"
+#include "src/ast/continue_statement.h"
 #include "src/ast/else_statement.h"
+#include "src/ast/fallthrough_statement.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/kill_statement.h"
@@ -1786,6 +1789,39 @@
         }
       }
       return true;
+    case SpvOpBranch: {
+      const auto dest = terminator.GetSingleWordInOperand(0);
+      const auto kind = block_info.succ_edge.find(dest)->second;
+      switch (kind) {
+        case EdgeKind::kBack:
+          // Nothing to do. The loop backedge is implicit.
+          return true;
+        case EdgeKind::kSwitchBreak:
+        case EdgeKind::kLoopBreak:
+          AddStatement(std::make_unique<ast::BreakStatement>());
+          return true;
+        case EdgeKind::kLoopContinue:
+          // An unconditional continue to the next block is redundant and ugly.
+          // Skip it in that case.
+          if (GetBlockInfo(dest)->pos == 1 + block_info.pos) {
+            return true;
+          }
+          // Otherwise, emit a regular continue statement.
+          AddStatement(std::make_unique<ast::ContinueStatement>());
+          return true;
+        case EdgeKind::kIfBreak:
+          // For an unconditional branch, the break out to an if-selection
+          // merge block is implicit.
+          return true;
+        case EdgeKind::kCaseFallThrough:
+          AddStatement(std::make_unique<ast::FallthroughStatement>());
+          return true;
+        case EdgeKind::kForward:
+          // Unconditional forward branch is implicit.
+          return true;
+      }
+      break;
+    }
     default:
       break;
   }
@@ -1842,8 +1878,8 @@
   auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
   if (combinatorial_expr.expr != nullptr) {
     if (def_use_mgr_->NumUses(&inst) == 1) {
-      // If it's used once, then defer emitting the expression until it's used.
-      // Any supporting statements have already been emitted.
+      // If it's used once, then defer emitting the expression until it's
+      // used. Any supporting statements have already been emitted.
       singly_used_values_.insert(
           std::make_pair(inst.result_id(), std::move(combinatorial_expr)));
       return success();
@@ -1984,10 +2020,9 @@
   }
 
   // A SPIR-V access chain is a single instruction with multiple indices
-  // walking down into composites.  The Tint AST represents this as ever-deeper
-  // nested indexing expresions.
-  // Start off with an expression for the base, and then bury that inside
-  // nested indexing expressions.
+  // walking down into composites.  The Tint AST represents this as
+  // ever-deeper nested indexing expresions. Start off with an expression
+  // for the base, and then bury that inside nested indexing expressions.
   TypedExpression current_expr(MakeOperand(inst, 0));
 
   const auto constants = constant_mgr_->GetOperandConstants(&inst);
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index b9bc306..4a71050 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -7631,12 +7631,117 @@
   // TODO(dneto): Needs break-if
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_NestedIfContinue) {
-  // TODO(dneto): Needs "continue" terminator support
+TEST_F(SpvParserTest, EmitBody_Loop_NestedIfContinue) {
+  // By construction, it has to come from nested code.
+  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
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80 ; continue edge
+
+     %50 = OpLabel ; inner selection merge
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %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{
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{1}
+      }
+      Continue{}
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{3}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyAlwaysBreaks) {
-  // TODO(dneto): Needs "continue" terminator support
+TEST_F(SpvParserTest, EmitBody_Loop_BodyAlwaysBreaks) {
+  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
+     OpBranch %99 ; break is here
+
+     %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}
+  }
+  Break{}
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
 }
 
 TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyConditionallyBreaks) {
@@ -8062,6 +8167,436 @@
 )")) << ToString(fe.ast_body());
 }
 
+TEST_F(SpvParserTest, EmitBody_Branch_BackEdge_MultiBlockLoop) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %20 ; here is one
+
+     %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{
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{1}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_BackEdge_SingleBlockLoop) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpStore %var %uint_1
+     OpLoopMerge %99 %20 None
+     OpBranch %20 ; backedge in single block loop
+
+     %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}
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_SwitchBreak) {
+  // 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
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99 ; break is here
+
+     %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}
+  }
+  Break{}
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(
+    SpvParserTest,
+    EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructConditional) {
+  // This case is invalid because the backedge block doesn't post-dominate the
+  // continue target.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %30 None
+     OpBranch %30
+
+     %30 = OpLabel ; continue target; also an if-header
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond %40 %80
+
+     %40 = OpLabel
+     OpBranch %99 ; break, inside a nested if.
+
+     %80 = OpLabel
+     OpBranch %20 ; backedge
+
+     %99 = OpLabel
+     OpReturn
+
+     OpFunctionEnd
+  )"));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+  FunctionEmitter fe(p, *spirv_function(100));
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              Eq("Invalid exit (40->99) from continue construct: 40 is not the "
+                 "last block in the continue construct starting at 30 "
+                 "(violates post-dominance rule)"));
+}
+
+TEST_F(SpvParserTest,
+       EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpBranch %20
+
+     %20 = OpLabel
+     OpLoopMerge %99 %80 None
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_1
+     OpBranch %99  ; should be a 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{
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{1}
+    }
+    Break{}
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_LastInLoopConstruct) {
+  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
+     OpBranch %80 ; continue edge from last block before continue target
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_2
+     OpBranch %20
+
+     %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}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{2}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_BeforeLast) {
+  // By construction, it has to come from nested code.
+  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
+     OpSelectionMerge %50 None
+     OpBranchConditional %cond %40 %50
+
+     %40 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %80 ; continue edge
+
+     %50 = OpLabel ; inner selection merge
+     OpStore %var %uint_2
+     OpBranch %80
+
+     %80 = OpLabel ; continue target
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %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{
+  If{
+    (
+      ScalarConstructor{false}
+    )
+    {
+      Assignment{
+        Identifier{var}
+        ScalarConstructor{1}
+      }
+      Continue{}
+    }
+  }
+  Else{
+    {
+    }
+  }
+  Assignment{
+    Identifier{var}
+    ScalarConstructor{2}
+  }
+  continuing {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{3}
+    }
+  }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_IfBreak_FromThen) {
+  // When unconditional, the if-break must be last in the then clause.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %30 %99
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     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"(If{
+  (
+    ScalarConstructor{false}
+  )
+  {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{1}
+    }
+  }
+}
+Else{
+  {
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{2}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_IfBreak_FromElse) {
+  // When unconditional, the if-break must be last in the else clause.
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpSelectionMerge %99 None
+     OpBranchConditional %cond %99 %30
+
+     %30 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     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"(If{
+  (
+    ScalarConstructor{false}
+  )
+  {
+  }
+}
+Else{
+  {
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{1}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{2}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_CaseFallthrough) {
+  // TODO(dneto): support switch first.
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_Forward) {
+  auto* p = parser(test::Assemble(CommonTypes() + R"(
+     %100 = OpFunction %void None %voidfn
+
+     %10 = OpLabel
+     OpStore %var %uint_1
+     OpBranch %99 ; forward
+
+     %99 = OpLabel
+     OpStore %var %uint_2
+     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{1}
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{2}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader