[tint][ir][ToProgram] Emit returns without values

Returns with values requires fleshing out of functions, which comes next

Bug: tint:1902
Change-Id: I5f956805441b99038f2d48758d1bd767ea4e1a1d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/133141
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ir/to_program.cc b/src/tint/ir/to_program.cc
index 972f526..5e46e92 100644
--- a/src/tint/ir/to_program.cc
+++ b/src/tint/ir/to_program.cc
@@ -41,6 +41,10 @@
 #include "src/tint/utils/transform.h"
 #include "src/tint/utils/vector.h"
 
+#define UNHANDLED_CASE(object_ptr)          \
+    TINT_UNIMPLEMENTED(IR, b.Diagnostics()) \
+        << "unhandled case in Switch(): " << (object_ptr ? object_ptr->TypeInfo().name : "<null>")
+
 namespace tint::ir {
 
 namespace {
@@ -81,33 +85,43 @@
                       decltype(ast::BlockStatement::statements)::static_length>
             stmts;
         while (node != stop_at) {
-            if (!node) {
-                return nullptr;
-            }
-            node = Switch(
+            enum Status { kContinue, kStop, kError };
+            Status status = Switch(
                 node,  //
-                [&](const ir::Block* block) -> const ir::FlowNode* {
+                [&](const ir::Block* block) {
                     for (auto* inst : block->instructions) {
                         if (auto* stmt = Stmt(inst); TINT_LIKELY(stmt)) {
                             stmts.Push(stmt);
                         } else {
-                            return nullptr;
+                            return kError;
                         }
                     }
-                    return block->branch.target;
+                    node = block->branch.target;
+                    return kContinue;
                 },
-                [&](const ir::If* if_) -> const ir::FlowNode* {
+                [&](const ir::If* if_) {
                     if (auto* stmt = If(if_); TINT_LIKELY(stmt)) {
                         stmts.Push(stmt);
-                        return if_->merge.target;
+                        node = if_->merge.target;
+                        return node->inbound_branches.IsEmpty() ? kStop : kContinue;
                     }
-                    return nullptr;
+                    return kError;
+                },
+                [&](const ir::FunctionTerminator*) {
+                    stmts.Push(b.Return());
+                    return kStop;
                 },
                 [&](Default) {
-                    TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                        << "unhandled case in Switch(): " << node->TypeInfo().name;
-                    return nullptr;
+                    UNHANDLED_CASE(node);
+                    return kError;
                 });
+
+            if (TINT_UNLIKELY(status == kError)) {
+                return nullptr;
+            }
+            if (status == kStop) {
+                break;
+            }
         }
 
         return b.Block(std::move(stmts));
@@ -178,8 +192,7 @@
             [&](const ir::Var* i) { return Var(i); },        //
             [&](const ir::Store* i) { return Store(i); },
             [&](Default) {
-                TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                    << "unhandled case in Switch(): " << inst->TypeInfo().name;
+                UNHANDLED_CASE(inst);
                 return nullptr;
             });
     }
@@ -226,8 +239,7 @@
             call,  //
             [&](const ir::UserCall* c) { return b.Call(Sym(c->name), std::move(args)); },
             [&](Default) {
-                TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                    << "unhandled case in Switch(): " << call->TypeInfo().name;
+                UNHANDLED_CASE(call);
                 return nullptr;
             });
     }
@@ -238,8 +250,7 @@
             [&](const ir::Constant* c) { return ConstExpr(c); },
             [&](const ir::Var* v) { return VarExpr(v); },
             [&](Default) {
-                TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                    << "unhandled case in Switch(): " << val->TypeInfo().name;
+                UNHANDLED_CASE(val);
                 return nullptr;
             });
     }
@@ -253,8 +264,7 @@
             [&](const type::F16*) { return b.Expr(c->value->ValueAs<f16>()); },
             [&](const type::Bool*) { return b.Expr(c->value->ValueAs<bool>()); },
             [&](Default) {
-                TINT_UNIMPLEMENTED(IR, b.Diagnostics())
-                    << "unhandled case in Switch(): " << c->TypeInfo().name;
+                UNHANDLED_CASE(c);
                 return nullptr;
             });
     }
@@ -327,7 +337,7 @@
             },
             [&](const type::Reference* r) { return Type(r->StoreType()); },
             [&](Default) {
-                TINT_UNREACHABLE(IR, b.Diagnostics()) << "unhandled type: " << ty->TypeInfo().name;
+                UNHANDLED_CASE(ty);
                 return ast::Type{};
             });
     }
diff --git a/src/tint/ir/to_program_roundtrip_test.cc b/src/tint/ir/to_program_roundtrip_test.cc
index dd04ab3..171f1f2 100644
--- a/src/tint/ir/to_program_roundtrip_test.cc
+++ b/src/tint/ir/to_program_roundtrip_test.cc
@@ -60,13 +60,25 @@
     Test("");
 }
 
-TEST_F(IRToProgramRoundtripTest, EmptySingleFunction) {
+TEST_F(IRToProgramRoundtripTest, SingleFunction_Empty) {
     Test(R"(
 fn f() {
 }
 )");
 }
 
+TEST_F(IRToProgramRoundtripTest, SingleFunction_Return) {
+    Test(R"(
+fn f() {
+  return;
+}
+)",
+         R"(
+fn f() {
+}
+)");
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Function-scope var
 ////////////////////////////////////////////////////////////////////////////////
@@ -113,6 +125,20 @@
 )");
 }
 
+TEST_F(IRToProgramRoundtripTest, If_Return) {
+    Test(R"(
+fn a() {
+}
+
+fn f() {
+  var cond : bool = true;
+  if (cond) {
+    return;
+  }
+}
+)");
+}
+
 TEST_F(IRToProgramRoundtripTest, If_CallFn_Else_CallFn) {
     Test(R"(
 fn a() {
@@ -132,6 +158,25 @@
 )");
 }
 
+TEST_F(IRToProgramRoundtripTest, If_Return_Else_Return) {
+    Test(R"(
+fn a() {
+}
+
+fn b() {
+}
+
+fn f() {
+  var cond : bool = true;
+  if (cond) {
+    return;
+  } else {
+    return;
+  }
+}
+)");
+}
+
 TEST_F(IRToProgramRoundtripTest, If_CallFn_ElseIf_CallFn) {
     Test(R"(
 fn a() {