[ir][msl] Add `return` support

Add support for `return` in the MSL IR generator.

Bug: tint:1967
Change-Id: Ib38994ef97119a1b39b35ae3d157d91b3a741a1d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144201
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index e570fe8..371d351 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -2329,6 +2329,7 @@
         "lang/msl/writer/printer/constant_test.cc",
         "lang/msl/writer/printer/function_test.cc",
         "lang/msl/writer/printer/helper_test.h",
+        "lang/msl/writer/printer/return_test.cc",
         "lang/msl/writer/printer/type_test.cc",
       ]
       deps += [
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index d9e69b7..f1dbdaf 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -1556,6 +1556,7 @@
         lang/msl/writer/printer/constant_test.cc
         lang/msl/writer/printer/function_test.cc
         lang/msl/writer/printer/helper_test.h
+        lang/msl/writer/printer/return_test.cc
         lang/msl/writer/printer/type_test.cc
       )
     endif()
diff --git a/src/tint/lang/msl/writer/printer/helper_test.h b/src/tint/lang/msl/writer/printer/helper_test.h
index aaf7e15..d6c9f8b 100644
--- a/src/tint/lang/msl/writer/printer/helper_test.h
+++ b/src/tint/lang/msl/writer/printer/helper_test.h
@@ -18,13 +18,17 @@
 #include <iostream>
 #include <string>
 
-#include "gtest/gtest.h"
+#include "gmock/gmock.h"
 #include "src/tint/lang/core/ir/builder.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/msl/writer/printer/printer.h"
 
 namespace tint::msl::writer {
 
+constexpr auto kMetalHeader = R"(#include <metal_stdlib>
+using namespace metal;
+)";
+
 /// Base helper class for testing the MSL generator implementation.
 template <typename BASE>
 class MslPrinterTestHelperBase : public BASE {
@@ -38,6 +42,9 @@
     /// The type manager.
     type::Manager& ty{mod.Types()};
 
+    /// @returns the metal header string
+    std::string MetalHeader() const { return kMetalHeader; }
+
   protected:
     /// The MSL generator.
     Printer generator_;
diff --git a/src/tint/lang/msl/writer/printer/printer.cc b/src/tint/lang/msl/writer/printer/printer.cc
index f29bc65..5294f89 100644
--- a/src/tint/lang/msl/writer/printer/printer.cc
+++ b/src/tint/lang/msl/writer/printer/printer.cc
@@ -17,6 +17,8 @@
 #include "src/tint/lang/core/constant/composite.h"
 #include "src/tint/lang/core/constant/splat.h"
 #include "src/tint/lang/core/ir/constant.h"
+#include "src/tint/lang/core/ir/multi_in_block.h"
+#include "src/tint/lang/core/ir/return.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/type/array.h"
 #include "src/tint/lang/core/type/atomic.h"
@@ -71,7 +73,6 @@
     {
         TINT_SCOPED_ASSIGNMENT(current_buffer_, &preamble_buffer_);
         Line() << "#include <metal_stdlib>";
-        Line();
         Line() << "using namespace metal;";
     }
 
@@ -98,15 +99,6 @@
     return ss.str();
 }
 
-void Printer::EmitFunction(ir::Function* func) {
-    {
-        auto out = Line();
-        EmitType(out, func->ReturnType());
-        out << " " << ir_->NameOf(func).Name() << "() {";
-    }
-    Line() << "}";
-}
-
 const std::string& Printer::ArrayTemplateName() {
     if (!array_template_name_.empty()) {
         return array_template_name_;
@@ -134,6 +126,74 @@
     return array_template_name_;
 }
 
+void Printer::EmitFunction(ir::Function* func) {
+    TINT_SCOPED_ASSIGNMENT(current_function_, func);
+
+    {
+        auto out = Line();
+
+        // TODO(dsinclair): Emit function stage if any
+        // TODO(dsinclair): Handle return type attributes
+
+        EmitType(out, func->ReturnType());
+        out << " " << ir_->NameOf(func).Name() << "() {";
+
+        // TODO(dsinclair): Emit Function parameters
+    }
+    {
+        ScopedIndent si(current_buffer_);
+        EmitBlock(func->Block());
+    }
+
+    Line() << "}";
+}
+
+void Printer::EmitBlock(ir::Block* block) {
+    if (block->As<ir::MultiInBlock>()) {
+        // TODO(dsinclair): Emit variables to used by the PHIs.
+    }
+
+    // TODO(dsinclair): Handle inline things
+    // MarkInlinable(block);
+
+    EmitBlockInstructions(block);
+}
+
+void Printer::EmitBlockInstructions(ir::Block* block) {
+    TINT_SCOPED_ASSIGNMENT(current_block_, block);
+
+    for (auto* inst : *block) {
+        Switch(
+            inst,                                   //
+            [&](ir::Return* r) { EmitReturn(r); },  //
+            [&](Default) { TINT_ICE() << "unimplemented instruction: " << inst->TypeInfo().name; });
+    }
+}
+
+void Printer::EmitReturn(ir::Return* r) {
+    // If this return has no arguments and the current block is for the function which is being
+    // returned, skip the return.
+    if (current_block_ == current_function_->Block() && r->Args().IsEmpty()) {
+        return;
+    }
+
+    auto out = Line();
+
+    out << "return";
+    if (!r->Args().IsEmpty()) {
+        // TODO(dsinclair): This should emit the expression instead of just assuming it's a constant
+        // value
+        if (!r->Args().Front()->Is<ir::Constant>()) {
+            TINT_ICE() << "return only handles constants";
+            return;
+        }
+
+        out << " ";  // << Expr(out, r->Args().Front());
+        EmitConstant(out, r->Args().Front()->As<ir::Constant>());
+    }
+    out << ";";
+}
+
 void Printer::EmitAddressSpace(StringStream& out, builtin::AddressSpace sc) {
     switch (sc) {
         case builtin::AddressSpace::kFunction:
diff --git a/src/tint/lang/msl/writer/printer/printer.h b/src/tint/lang/msl/writer/printer/printer.h
index 92f7ffd..59c87ac 100644
--- a/src/tint/lang/msl/writer/printer/printer.h
+++ b/src/tint/lang/msl/writer/printer/printer.h
@@ -25,6 +25,11 @@
 #include "src/tint/utils/generator/text_generator.h"
 #include "src/tint/utils/text/string_stream.h"
 
+// Forward declarations
+namespace tint::ir {
+class Return;
+}  // namespace tint::ir
+
 namespace tint::msl::writer {
 
 /// Implementation class for the MSL generator
@@ -45,6 +50,17 @@
     /// @param func the function to emit
     void EmitFunction(ir::Function* func);
 
+    /// Emit a block
+    /// @param block the block to emit
+    void EmitBlock(ir::Block* block);
+    /// Emit the instructions in a block
+    /// @param block the block with the instructions to emit
+    void EmitBlockInstructions(ir::Block* block);
+
+    /// Emit a return instruction
+    /// @param r the return instruction
+    void EmitReturn(ir::Return* r);
+
     /// Emit a type
     /// @param out the stream to emit too
     /// @param ty the type to emit
@@ -125,6 +141,11 @@
     std::string invariant_define_name_;
 
     std::unordered_set<const type::Struct*> emitted_structs_;
+
+    /// The current function being emitted
+    ir::Function* current_function_ = nullptr;
+    /// The current block being emitted
+    ir::Block* current_block_ = nullptr;
 };
 
 }  // namespace tint::msl::writer
diff --git a/src/tint/lang/msl/writer/printer/return_test.cc b/src/tint/lang/msl/writer/printer/return_test.cc
new file mode 100644
index 0000000..ae8fdab
--- /dev/null
+++ b/src/tint/lang/msl/writer/printer/return_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/lang/msl/writer/printer/helper_test.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::msl::writer {
+namespace {
+
+// TODO(dsinclair): Requires if emission in MSL generator
+TEST_F(MslPrinterTest, DISABLED_Return) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* if_ = b.If(true);
+        b.Append(if_->True(), [&] { b.Return(func); });
+    });
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(generator_.Result(), MetalHeader() + R"(
+void foo() {
+  if (true) {
+    return;
+  }
+}
+)");
+}
+
+TEST_F(MslPrinterTest, ReturnAtEndOfVoidDropped) {
+    auto* func = b.Function("foo", ty.void_());
+    func->Block()->Append(b.Return(func));
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(generator_.Result(), MetalHeader() + R"(
+void foo() {
+}
+)");
+}
+
+TEST_F(MslPrinterTest, ReturnWithValue) {
+    auto* func = b.Function("foo", ty.i32());
+    func->Block()->Append(b.Return(func, 123_i));
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(generator_.Result(), MetalHeader() + R"(
+int foo() {
+  return 123;
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::msl::writer