[tint][ir][ToProgram] Begin flow node traversal

Traverse the block nodes, and if statements.

Bug: tint:1902
Change-Id: Ie5533acdc65378bfea91b46a62090c4d3216b303
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/133100
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/ir/to_program.cc b/src/tint/ir/to_program.cc
index 215490e..a59146e 100644
--- a/src/tint/ir/to_program.cc
+++ b/src/tint/ir/to_program.cc
@@ -19,6 +19,7 @@
 #include "src/tint/ir/block.h"
 #include "src/tint/ir/call.h"
 #include "src/tint/ir/constant.h"
+#include "src/tint/ir/function_terminator.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/module.h"
@@ -67,55 +68,75 @@
         // TODO(crbug.com/tint/1915): Properly implement this when we've fleshed out Function
         utils::Vector<const ast::Parameter*, 1> params{};
         ast::Type ret_ty;
-        auto* body = Block(fn->start_target);
+        auto* body = FlowNodeGraph(fn->start_target, fn->end_target);
         utils::Vector<const ast::Attribute*, 1> attrs{};
         utils::Vector<const ast::Attribute*, 1> ret_attrs{};
         b.Func(name, std::move(params), ret_ty, body, std::move(attrs), std::move(ret_attrs));
     }
 
-    const ast::BlockStatement* Block(const ir::Block* block) {
+    const ast::BlockStatement* FlowNodeGraph(const ir::FlowNode* node,
+                                             const ir::FlowNode* stop_at) {
         // TODO(crbug.com/tint/1902): Check if the block is dead
-        utils::Vector<const ast::Statement*, decltype(ir::Block::instructions)::static_length>
+        utils::Vector<const ast::Statement*,
+                      decltype(ast::BlockStatement::statements)::static_length>
             stmts;
-        for (auto* inst : block->instructions) {
-            auto* stmt = Stmt(inst);
-            if (!stmt) {
+        while (node != stop_at) {
+            if (!node) {
                 return nullptr;
             }
-            stmts.Push(stmt);
+            node = Switch(
+                node,  //
+                [&](const ir::Block* block) -> const ir::FlowNode* {
+                    for (auto* inst : block->instructions) {
+                        if (auto* stmt = Stmt(inst); TINT_LIKELY(stmt)) {
+                            stmts.Push(stmt);
+                        } else {
+                            return nullptr;
+                        }
+                    }
+                    return block->branch.target;
+                },
+                [&](const ir::If* if_) -> const ir::FlowNode* {
+                    if (auto* stmt = If(if_); TINT_LIKELY(stmt)) {
+                        stmts.Push(stmt);
+                        return if_->merge.target;
+                    }
+                    return nullptr;
+                },
+                [&](Default) {
+                    TINT_UNIMPLEMENTED(IR, b.Diagnostics())
+                        << "unhandled case in Switch(): " << node->TypeInfo().name;
+                    return nullptr;
+                });
         }
+
         return b.Block(std::move(stmts));
     }
 
-    const ast::Statement* FlowNode(const ir::FlowNode* node) {
-        // TODO(crbug.com/tint/1902): Check the node is connected
-        return Switch(
-            node,  //
-            [&](const ir::If* i) {
-                auto* cond = Expr(i->condition);
-                auto* t = Branch(i->true_);
-                if (auto* f = Branch(i->false_)) {
-                    return b.If(cond, t, b.Else(f));
-                }
-                // TODO(crbug.com/tint/1902): Emit merge block
-                return b.If(cond, t);
-            },
-            [&](Default) {
-                TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                    << "unhandled case in Switch(): " << node->TypeInfo().name;
-                return nullptr;
-            });
+    const ast::IfStatement* If(const ir::If* i) {
+        auto* cond = Expr(i->condition);
+        auto* t = FlowNodeGraph(i->true_.target, i->merge.target);
+        if (!IsEmpty(i->false_.target, i->merge.target)) {
+            // TODO(crbug.com/tint/1902): Merge if else
+            auto* f = FlowNodeGraph(i->false_.target, i->merge.target);
+            return b.If(cond, t, b.Else(f));
+        }
+        return b.If(cond, t);
     }
 
-    const ast::BlockStatement* Branch(const ir::Branch& branch) {
-        auto* stmt = FlowNode(branch.target);
-        if (!stmt) {
-            return nullptr;
+    /// @return true if there are no instructions between @p node and and @p stop_at
+    bool IsEmpty(const ir::FlowNode* node, const ir::FlowNode* stop_at) {
+        while (node != stop_at) {
+            if (auto* block = node->As<ir::Block>()) {
+                if (block->instructions.Length() > 0) {
+                    return false;
+                }
+                node = block->branch.target;
+            } else {
+                return false;
+            }
         }
-        if (auto* block = stmt->As<ast::BlockStatement>()) {
-            return block;
-        }
-        return b.Block(stmt);
+        return true;
     }
 
     const ast::Statement* Stmt(const ir::Instruction* inst) {
diff --git a/src/tint/ir/to_program_roundtrip_test.cc b/src/tint/ir/to_program_roundtrip_test.cc
index 1620b5f..e62646c 100644
--- a/src/tint/ir/to_program_roundtrip_test.cc
+++ b/src/tint/ir/to_program_roundtrip_test.cc
@@ -67,6 +67,9 @@
 )");
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Function-scope var
+////////////////////////////////////////////////////////////////////////////////
 TEST_F(IRToProgramRoundtripTest, FunctionScopeVar_i32) {
     Test(R"(
 fn f() {
@@ -93,5 +96,85 @@
 )");
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// If
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(IRToProgramRoundtripTest, If_CallFn) {
+    Test(R"(
+fn a() {
+}
+
+fn f() {
+  var cond : bool = true;
+  if (cond) {
+    a();
+  }
+}
+)");
+}
+
+TEST_F(IRToProgramRoundtripTest, If_CallFn_Else_CallFn) {
+    Test(R"(
+fn a() {
+}
+
+fn b() {
+}
+
+fn f() {
+  var cond : bool = true;
+  if (cond) {
+    a();
+  } else {
+    b();
+  }
+}
+)");
+}
+
+TEST_F(IRToProgramRoundtripTest, If_CallFn_ElseIf_CallFn) {
+    Test(R"(
+fn a() {
+}
+
+fn b() {
+}
+
+fn c() {
+}
+
+fn f() {
+  var cond_a : bool = true;
+  var cond_b : bool = true;
+  if (cond_a) {
+    a();
+  } else if (cond_b) {
+    b();
+  }
+}
+)",
+         R"(
+fn a() {
+}
+
+fn b() {
+}
+
+fn c() {
+}
+
+fn f() {
+  var cond_a : bool = true;
+  var cond_b : bool = true;
+  if (cond_a) {
+    a();
+  } else {
+    if (cond_b) {
+      b();
+    }
+  }
+}
+)");
+}
 }  // namespace
 }  // namespace tint::ir