[spirv-reader] Emit loop and continuing

Bug: tint:3
Change-Id: Iaced5ee41f6b27ab350432fc1c2cdff6042ba191
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22423
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 50c947a..d6a25bd 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1556,7 +1556,9 @@
     auto outer_kind = entering_constructs[1]->kind;
     if (outer_kind != Construct::kContinue) {
       return Fail() << "internal error: bad construct nesting. Only Continue "
-                       "construct can be outer construct on same block";
+                       "construct can be outer construct on same block.  Got "
+                       "outer kind "
+                    << int(outer_kind) << " inner kind " << int(inner_kind);
     }
     if (inner_kind == Construct::kContinue) {
       return Fail() << "internal error: unsupported construct nesting: "
@@ -1588,10 +1590,28 @@
         return Fail() << "internal error: nested function construct";
 
       case Construct::kLoop:
-        return Fail() << "unhandled: loop construct";
+        if (!EmitLoopStart(construct)) {
+          return false;
+        }
+        if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+          return false;
+        }
+        break;
 
       case Construct::kContinue:
-        return Fail() << "unhandled: continue construct";
+        if (block_info.is_single_block_loop) {
+          if (!EmitLoopStart(construct)) {
+            return false;
+          }
+          if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+            return false;
+          }
+        } else {
+          if (!EmitContinuingStart(construct)) {
+            return false;
+          }
+        }
+        break;
 
       case Construct::kIfSelection:
         if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
@@ -1709,6 +1729,30 @@
   return success();
 }
 
+bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
+  auto* loop = AddStatement(std::make_unique<ast::LoopStatement>())->AsLoop();
+  PushNewStatementBlock(
+      construct, construct->end_id,
+      [loop](StatementBlock* s) { loop->set_body(std::move(s->statements)); });
+  return success();
+}
+
+bool FunctionEmitter::EmitContinuingStart(const Construct* construct) {
+  // A continue construct has the same depth as its associated loop
+  // construct. Start a continue construct.
+  auto* loop_candidate = LastStatement();
+  if (!loop_candidate->IsLoop()) {
+    return Fail() << "internal error: starting continue construct, "
+                     "expected loop on top of stack";
+  }
+  auto* loop = loop_candidate->AsLoop();
+  PushNewStatementBlock(construct, construct->end_id,
+                        [loop](StatementBlock* s) {
+                          loop->set_continuing(std::move(s->statements));
+                        });
+  return success();
+}
+
 bool FunctionEmitter::EmitNormalTerminator(const BlockInfo&) {
   // TODO(dneto): emit fallthrough, break, continue, return, kill
   return true;
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index a7fec4c..f53dbef 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -297,6 +297,20 @@
   /// @returns false if emission failed.
   bool EmitIfStart(const BlockInfo& block_info);
 
+  /// Emits a LoopStatement, and pushes a new StatementBlock to accumulate
+  /// the remaining instructions in the current block and subsequent blocks
+  /// in the loop.
+  /// @param construct the loop construct
+  /// @returns false if emission failed.
+  bool EmitLoopStart(const Construct* construct);
+
+  /// Emits a ContinuingStatement, and pushes a new StatementBlock to accumulate
+  /// the remaining instructions in the current block and subsequent blocks
+  /// in the continue construct.
+  /// @param construct the continue construct
+  /// @returns false if emission failed.
+  bool EmitContinuingStart(const Construct* construct);
+
   /// Emits the non-control-flow parts of a basic block, but only once.
   /// The |already_emitted| parameter indicates whether the code has already
   /// been emitted, and is used to signal that this invocation actually emitted
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index e10b3b0..1460556 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -7226,6 +7226,406 @@
 )"));
 }
 
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
+  // TODO(dneto): emit conditional break
+  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 %999
+     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{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_SingleBlock_FalseBackedge) {
+  // TODO(dneto): emit conditional break
+  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 %999
+     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{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_BothBackedge) {
+  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
+     OpStore %var %999
+     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{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
+  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
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     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{999}
+}
+)"));
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_SingleBlockContinue) {
+  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 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     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{3}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_MultiBlockContinue) {
+  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 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel
+     OpStore %var %uint_3
+     OpBranch %60
+
+     %60 = OpLabel
+     OpStore %var %uint_4
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     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{3}
+    }
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{4}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_ContinueNestIf) {
+  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 %50 None
+     OpBranch %30
+
+     %30 = OpLabel
+     OpStore %var %uint_2
+     OpBranch %50
+
+     %50 = OpLabel ; continue target; also if-header
+     OpStore %var %uint_3
+     OpSelectionMerge %80 None
+     OpBranchConditional %cond2 %60 %80
+
+     %60 = OpLabel
+     OpStore %var %uint_4
+     OpBranch %80
+
+     %80 = OpLabel
+     OpStore %var %uint_5
+     OpBranch %20
+
+     %99 = OpLabel
+     OpStore %var %999
+     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{3}
+    }
+    If{
+      (
+        ScalarConstructor{true}
+      )
+      {
+        Assignment{
+          Identifier{var}
+          ScalarConstructor{4}
+        }
+      }
+    }
+    Else{
+      {
+      }
+    }
+    Assignment{
+      Identifier{var}
+      ScalarConstructor{5}
+    }
+  }
+}
+Assignment{
+  Identifier{var}
+  ScalarConstructor{999}
+}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_Never) {
+  // Test case where both branches exit. e.g both go to merge.
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_HeaderBreakAndContinue) {
+  // Header block branches to merge, and to an outer continue.
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_TrueToBody) {
+  // TODO(dneto): Needs break-unless
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_FalseToBody) {
+  // TODO(dneto): Needs break-if
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_NestedIfContinue) {
+  // TODO(dneto): Needs "continue" terminator support
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyAlwaysBreaks) {
+  // TODO(dneto): Needs "continue" terminator support
+}
+
+TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyConditionallyBreaks) {
+  // TODO(dneto): Needs "break" support
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader