[IR] Add support for `for` statements

This CL adds the ability to convert a for statement into a loop control
flow node.

Bug: tint:1718
Change-Id: Ibd55ae3b202518d3362267eaa1f507dce6a9fe56
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107804
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 99733f8..224d1ae 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -20,6 +20,7 @@
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/continue_statement.h"
 #include "src/tint/ast/fallthrough_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/function.h"
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ast/loop_statement.h"
@@ -210,7 +211,7 @@
         [&](const ast::FallthroughStatement*) { return EmitFallthrough(); },
         [&](const ast::IfStatement* i) { return EmitIf(i); },
         [&](const ast::LoopStatement* l) { return EmitLoop(l); },
-        //        [&](const ast::ForLoopStatement* l) { },
+        [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
         [&](const ast::WhileStatement* l) { return EmitWhile(l); },
         [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
         [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
@@ -352,6 +353,58 @@
     return true;
 }
 
+bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+    auto* loop_node = builder_.CreateLoop(stmt);
+    builder_.Branch(loop_node->continuing_target, loop_node->start_target);
+
+    if (stmt->initializer) {
+        // Emit the for initializer before branching to the loop
+        if (!EmitStatement(stmt->initializer)) {
+            return false;
+        }
+    }
+
+    BranchTo(loop_node);
+
+    ast_to_flow_[stmt] = loop_node;
+
+    {
+        FlowStackScope scope(this, loop_node);
+
+        current_flow_block_ = loop_node->start_target;
+
+        if (stmt->condition) {
+            // TODO(dsinclair): Emit the instructions for the condition
+
+            // Create an if (cond) {} else {break;} control flow
+            auto* if_node = builder_.CreateIf(nullptr);
+            builder_.Branch(if_node->true_target, if_node->merge_target);
+            builder_.Branch(if_node->false_target, loop_node->merge_target);
+            // TODO(dsinclair): set if condition register into if flow node
+
+            BranchTo(if_node);
+            current_flow_block_ = if_node->merge_target;
+        }
+
+        if (!EmitStatement(stmt->body)) {
+            return false;
+        }
+
+        BranchToIfNeeded(loop_node->continuing_target);
+
+        if (stmt->continuing) {
+            current_flow_block_ = loop_node->continuing_target;
+            if (!EmitStatement(stmt->continuing)) {
+                return false;
+            }
+        }
+    }
+    // The while loop always has a path to the merge target as the break statement comes before
+    // anything inside the loop.
+    current_flow_block_ = loop_node->merge_target;
+    return true;
+}
+
 bool BuilderImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
     auto* switch_node = builder_.CreateSwitch(stmt);
 
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 7aa2b11..d6c4bc4 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -34,6 +34,7 @@
 class BreakIfStatement;
 class BreakStatement;
 class ContinueStatement;
+class ForLoopStatement;
 class Function;
 class IfStatement;
 class LoopStatement;
@@ -108,6 +109,11 @@
     /// @returns true if successful, false otherwise.
     bool EmitWhile(const ast::WhileStatement* stmt);
 
+    /// Emits a loop control node to the IR.
+    /// @param stmt the for loop statement
+    /// @returns true if successful, false otherwise.
+    bool EmitForLoop(const ast::ForLoopStatement* stmt);
+
     /// Emits a switch statement
     /// @param stmt the switch statement
     /// @returns true if successful, false otherwise.
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 2f5fba2..1e2325e 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -942,6 +942,103 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 }
 
+// TODO(dsinclair): Enable when variable declarations and increment are supported
+TEST_F(IRBuilderImplTest, DISABLED_For) {
+    // for(var i: 0; i < 10; i++) {
+    // }
+    //
+    // func -> loop -> loop start -> if true
+    //                            -> if false
+    //
+    //   [if true] -> if merge
+    //   [if false] -> loop merge
+    //   [if merge] -> loop continuing
+    //   [loop continuing] -> loop start
+    //   [loop merge] -> func end
+    //
+    auto* ast_for = For(Decl(Var("i", ty.i32())), LessThan("i", 10_a), Increment("i"), Block());
+    WrapInFunction(ast_for);
+    auto& b = Build();
+
+    auto r = b.Build();
+    ASSERT_TRUE(r) << b.error();
+    auto m = r.Move();
+
+    auto* ir_for = b.FlowNodeForAstNode(ast_for);
+    ASSERT_NE(ir_for, nullptr);
+    ASSERT_TRUE(ir_for->Is<ir::Loop>());
+
+    auto* flow = ir_for->As<ir::Loop>();
+    ASSERT_NE(flow->start_target, nullptr);
+    ASSERT_NE(flow->continuing_target, nullptr);
+    ASSERT_NE(flow->merge_target, nullptr);
+
+    ASSERT_NE(flow->start_target->branch_target, nullptr);
+    ASSERT_TRUE(flow->start_target->branch_target->Is<ir::If>());
+    auto* if_flow = flow->start_target->branch_target->As<ir::If>();
+    ASSERT_NE(if_flow->true_target, nullptr);
+    ASSERT_NE(if_flow->false_target, nullptr);
+    ASSERT_NE(if_flow->merge_target, nullptr);
+
+    ASSERT_EQ(1u, m.functions.Length());
+    auto* func = m.functions[0];
+
+    EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
+    EXPECT_EQ(1u, flow->inbound_branches.Length());
+    EXPECT_EQ(2u, flow->start_target->inbound_branches.Length());
+    EXPECT_EQ(1u, flow->continuing_target->inbound_branches.Length());
+    EXPECT_EQ(1u, flow->merge_target->inbound_branches.Length());
+    EXPECT_EQ(1u, if_flow->true_target->inbound_branches.Length());
+    EXPECT_EQ(1u, if_flow->false_target->inbound_branches.Length());
+    EXPECT_EQ(1u, if_flow->merge_target->inbound_branches.Length());
+
+    EXPECT_EQ(func->start_target->branch_target, flow);
+    EXPECT_EQ(flow->start_target->branch_target, if_flow);
+    EXPECT_EQ(if_flow->true_target->branch_target, if_flow->merge_target);
+    EXPECT_EQ(if_flow->false_target->branch_target, flow->merge_target);
+    EXPECT_EQ(if_flow->merge_target->branch_target, flow->continuing_target);
+    EXPECT_EQ(flow->continuing_target->branch_target, flow->start_target);
+    EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, For_NoInitCondOrContinuing) {
+    // for (;;) {
+    //   break;
+    // }
+    //
+    // func -> loop -> loop start -> loop merge -> func end
+    //
+    auto* ast_for = For(nullptr, nullptr, nullptr, Block(Break()));
+    WrapInFunction(ast_for);
+    auto& b = Build();
+
+    auto r = b.Build();
+    ASSERT_TRUE(r) << b.error();
+    auto m = r.Move();
+
+    auto* ir_for = b.FlowNodeForAstNode(ast_for);
+    ASSERT_NE(ir_for, nullptr);
+    ASSERT_TRUE(ir_for->Is<ir::Loop>());
+
+    auto* flow = ir_for->As<ir::Loop>();
+    ASSERT_NE(flow->start_target, nullptr);
+    ASSERT_NE(flow->continuing_target, nullptr);
+    ASSERT_NE(flow->merge_target, nullptr);
+
+    ASSERT_EQ(1u, m.functions.Length());
+    auto* func = m.functions[0];
+
+    EXPECT_EQ(1u, flow->inbound_branches.Length());
+    EXPECT_EQ(2u, flow->start_target->inbound_branches.Length());
+    EXPECT_EQ(0u, flow->continuing_target->inbound_branches.Length());
+    EXPECT_EQ(1u, flow->merge_target->inbound_branches.Length());
+    EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
+
+    EXPECT_EQ(flow->start_target->branch_target, flow->merge_target);
+    EXPECT_EQ(flow->continuing_target->branch_target, flow->start_target);
+    EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
 TEST_F(IRBuilderImplTest, Switch) {
     // func -> switch -> case 1
     //                -> case 2