writer/[hlsl,msl]: Emit a implicit return for entrypoints

WGSL no longer requires a return statement at the end of each function, and the generated entrypoint functions are not valid without a return.

Fixed: tint:446
Change-Id: I702e4217f4ac41013e30927d532895c6835f6ca9
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/37704
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 4c6067b..fe191ab 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -1576,6 +1576,14 @@
       return false;
     }
   }
+  auto* last_statement = func->get_last_statement();
+  if (last_statement == nullptr ||
+      !last_statement->Is<ast::ReturnStatement>()) {
+    ast::ReturnStatement ret(Source{});
+    if (!EmitStatement(out, &ret)) {
+      return false;
+    }
+  }
   generating_entry_point_ = false;
 
   decrement_indent();
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 49d4530..af20ff1 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -111,6 +111,74 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Function,
+       Emit_FunctionDecoration_EntryPoint_NoReturn_Void) {
+  auto* func =
+      Func("main", ast::VariableList{}, ty.void_,
+           ast::StatementList{/* no explicit return */},
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
+
+  mod->AddFunction(func);
+
+  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_EQ(result(), R"(void main() {
+  return;
+}
+
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
+       Emit_FunctionDecoration_EntryPoint_NoReturn_InOut) {
+  auto* foo_var = Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+                      ast::VariableDecorationList{
+                          create<ast::LocationDecoration>(0),
+                      });
+
+  auto* bar_var = Var("bar", ast::StorageClass::kOutput, ty.f32, nullptr,
+                      ast::VariableDecorationList{
+                          create<ast::LocationDecoration>(1),
+                      });
+
+  td.RegisterVariableForTesting(foo_var);
+  td.RegisterVariableForTesting(bar_var);
+
+  mod->AddGlobalVariable(foo_var);
+  mod->AddGlobalVariable(bar_var);
+
+  auto* func =
+      Func("main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::AssignmentStatement>(Expr("bar"), Expr("foo")),
+               /* no explicit return */},
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+           });
+
+  mod->AddFunction(func);
+
+  ASSERT_TRUE(td.Determine()) << td.error();
+  ASSERT_TRUE(gen.Generate(out)) << gen.error();
+  EXPECT_EQ(result(), R"(struct main_in {
+  float foo : TEXCOORD0;
+};
+
+struct main_out {
+  float bar : SV_Target1;
+};
+
+main_out main(main_in tint_in) {
+  main_out tint_out;
+  tint_out.bar = tint_in.foo;
+  return tint_out;
+}
+
+)");
+}
+
+TEST_F(HlslGeneratorImplTest_Function,
        Emit_FunctionDecoration_EntryPoint_WithInOutVars) {
   auto* foo_var = Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
                       ast::VariableDecorationList{
@@ -821,6 +889,7 @@
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
   EXPECT_EQ(result(), R"(void GeometryShader_tint_0() {
+  return;
 }
 
 )");
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index a9509a8..fa072e9 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -1515,6 +1515,14 @@
       return false;
     }
   }
+  auto* last_statement = func->get_last_statement();
+  if (last_statement == nullptr ||
+      !last_statement->Is<ast::ReturnStatement>()) {
+    ast::ReturnStatement ret(Source{});
+    if (!EmitStatement(&ret)) {
+      return false;
+    }
+  }
   generating_entry_point_ = false;
 
   decrement_indent();
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index 7ae2080..210d202 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -119,6 +119,74 @@
 )");
 }
 
+TEST_F(MslGeneratorImplTest, Emit_FunctionDecoration_EntryPoint_NoReturn_Void) {
+  auto* func = Func("main", ast::VariableList{}, ty.void_,
+                    ast::StatementList{/* no explicit return */},
+                    ast::FunctionDecorationList{create<ast::StageDecoration>(
+                        ast::PipelineStage::kFragment)});
+
+  mod->AddFunction(func);
+
+  ASSERT_TRUE(td.Determine()) << td.error();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+fragment void main_tint_0() {
+  return;
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionDecoration_EntryPoint_NoReturn_InOut) {
+  auto* foo_var =
+      Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(0)});
+
+  auto* bar_var =
+      Var("bar", ast::StorageClass::kOutput, ty.f32, nullptr,
+          ast::VariableDecorationList{create<ast::LocationDecoration>(1)});
+
+  td.RegisterVariableForTesting(foo_var);
+  td.RegisterVariableForTesting(bar_var);
+
+  mod->AddGlobalVariable(foo_var);
+  mod->AddGlobalVariable(bar_var);
+
+  auto* func =
+      Func("main", ast::VariableList{}, ty.void_,
+           ast::StatementList{
+               create<ast::AssignmentStatement>(Expr("bar"), Expr("foo")),
+               /* no explicit return */},
+           ast::FunctionDecorationList{
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
+
+  mod->AddFunction(func);
+
+  ASSERT_TRUE(td.Determine()) << td.error();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+struct main_in {
+  float foo [[user(locn0)]];
+};
+
+struct main_out {
+  float bar [[color(1)]];
+};
+
+fragment main_out main_tint_0(main_in tint_in [[stage_in]]) {
+  main_out tint_out = {};
+  tint_out.bar = tint_in.foo;
+  return tint_out;
+}
+
+)");
+}
+
 TEST_F(MslGeneratorImplTest, Emit_FunctionDecoration_EntryPoint_WithInOutVars) {
   auto* foo_var =
       Var("foo", ast::StorageClass::kInput, ty.f32, nullptr,
@@ -811,6 +879,7 @@
   EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
 
 kernel void main_tint_0() {
+  return;
 }
 
 )");
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index b5f4dd1..978eba8 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -59,6 +59,7 @@
   EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
 
 kernel void my_func() {
+  return;
 }
 
 )");