[ir][glsl] Basic function emitting

Create the basis for function emission for the GLSL IR printer.

Bug: tint:1993
Change-Id: Ief2acaf12b9f08554f3921d5748591c9a7f0f516
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/156600
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/lang/glsl/writer/printer/BUILD.bazel b/src/tint/lang/glsl/writer/printer/BUILD.bazel
index 525314d..f54faec 100644
--- a/src/tint/lang/glsl/writer/printer/BUILD.bazel
+++ b/src/tint/lang/glsl/writer/printer/BUILD.bazel
@@ -64,7 +64,12 @@
     "//src/tint/utils/symbol",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
-  ],
+  ] + select({
+    ":tint_build_glsl_writer": [
+      "//src/tint/lang/glsl/writer/common",
+    ],
+    "//conditions:default": [],
+  }),
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
@@ -73,6 +78,7 @@
   alwayslink = True,
   srcs = [
     "constant_test.cc",
+    "function_test.cc",
     "helper_test.h",
   ],
   deps = [
@@ -100,6 +106,7 @@
     "@gtest",
   ] + select({
     ":tint_build_glsl_writer": [
+      "//src/tint/lang/glsl/writer/common",
       "//src/tint/lang/glsl/writer/printer",
     ],
     "//conditions:default": [],
diff --git a/src/tint/lang/glsl/writer/printer/BUILD.cmake b/src/tint/lang/glsl/writer/printer/BUILD.cmake
index 7f291bcb..73f0081 100644
--- a/src/tint/lang/glsl/writer/printer/BUILD.cmake
+++ b/src/tint/lang/glsl/writer/printer/BUILD.cmake
@@ -67,6 +67,12 @@
   tint_utils_traits
 )
 
+if(TINT_BUILD_GLSL_WRITER)
+  tint_target_add_dependencies(tint_lang_glsl_writer_printer lib
+    tint_lang_glsl_writer_common
+  )
+endif(TINT_BUILD_GLSL_WRITER)
+
 endif(TINT_BUILD_GLSL_WRITER)
 if(TINT_BUILD_GLSL_WRITER)
 ################################################################################
@@ -76,6 +82,7 @@
 ################################################################################
 tint_add_target(tint_lang_glsl_writer_printer_test test
   lang/glsl/writer/printer/constant_test.cc
+  lang/glsl/writer/printer/function_test.cc
   lang/glsl/writer/printer/helper_test.h
 )
 
@@ -109,6 +116,7 @@
 
 if(TINT_BUILD_GLSL_WRITER)
   tint_target_add_dependencies(tint_lang_glsl_writer_printer_test test
+    tint_lang_glsl_writer_common
     tint_lang_glsl_writer_printer
   )
 endif(TINT_BUILD_GLSL_WRITER)
diff --git a/src/tint/lang/glsl/writer/printer/BUILD.gn b/src/tint/lang/glsl/writer/printer/BUILD.gn
index 3c9aca8..20efd60 100644
--- a/src/tint/lang/glsl/writer/printer/BUILD.gn
+++ b/src/tint/lang/glsl/writer/printer/BUILD.gn
@@ -68,6 +68,10 @@
       "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
+
+    if (tint_build_glsl_writer) {
+      deps += [ "${tint_src_dir}/lang/glsl/writer/common" ]
+    }
   }
 }
 if (tint_build_unittests) {
@@ -75,6 +79,7 @@
     tint_unittests_source_set("unittests") {
       sources = [
         "constant_test.cc",
+        "function_test.cc",
         "helper_test.h",
       ]
       deps = [
@@ -103,7 +108,10 @@
       ]
 
       if (tint_build_glsl_writer) {
-        deps += [ "${tint_src_dir}/lang/glsl/writer/printer" ]
+        deps += [
+          "${tint_src_dir}/lang/glsl/writer/common",
+          "${tint_src_dir}/lang/glsl/writer/printer",
+        ]
       }
     }
   }
diff --git a/src/tint/lang/glsl/writer/printer/function_test.cc b/src/tint/lang/glsl/writer/printer/function_test.cc
new file mode 100644
index 0000000..763a051
--- /dev/null
+++ b/src/tint/lang/glsl/writer/printer/function_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/glsl/writer/printer/helper_test.h"
+
+namespace tint::glsl::writer {
+namespace {
+
+TEST_F(GlslPrinterTest, Function_Empty) {
+    auto* func = b.Function("foo", ty.void_());
+    func->Block()->Append(b.Return(func));
+
+    ASSERT_TRUE(Generate()) << err_ << output_;
+    EXPECT_EQ(output_, GlslHeader() + R"(
+void foo() {
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::glsl::writer
diff --git a/src/tint/lang/glsl/writer/printer/helper_test.h b/src/tint/lang/glsl/writer/printer/helper_test.h
index 8aa5b0c..709fb17 100644
--- a/src/tint/lang/glsl/writer/printer/helper_test.h
+++ b/src/tint/lang/glsl/writer/printer/helper_test.h
@@ -50,6 +50,8 @@
     core::ir::Builder b{mod};
     /// The type manager.
     core::type::Manager& ty{mod.Types()};
+    /// The GLSL version
+    Version version{};
 
   protected:
     /// The GLSL writer.
@@ -70,7 +72,7 @@
             return false;
         }
 
-        auto result = writer_.Generate();
+        auto result = writer_.Generate(version);
         if (!result) {
             err_ = result.Failure().reason.str();
             return false;
@@ -79,6 +81,17 @@
 
         return true;
     }
+
+    /// @returns the metal header string
+    std::string GlslHeader() const {
+        std::stringstream ver;
+        ver << "#version " << version.major_version << version.minor_version << "0";
+        if (version.IsES()) {
+            ver << " es";
+        }
+        ver << "\n";
+        return ver.str();
+    }
 };
 
 using GlslPrinterTest = GlslPrinterTestHelperBase<testing::Test>;
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index e2f47d1..6351b29 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -29,22 +29,49 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/return.h"
+#include "src/tint/lang/core/ir/unreachable.h"
 #include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/utils/macros/scoped_assignment.h"
+#include "src/tint/utils/rtti/switch.h"
 
 using namespace tint::core::fluent_types;  // NOLINT
 
 namespace tint::glsl::writer {
 
+// Helper for calling TINT_UNIMPLEMENTED() from a Switch(object_ptr) default case.
+#define UNHANDLED_CASE(object_ptr)                         \
+    TINT_UNIMPLEMENTED() << "unhandled case in Switch(): " \
+                         << (object_ptr ? object_ptr->TypeInfo().name : "<null>")
+
 Printer::Printer(core::ir::Module& module) : ir_(module) {}
 
 Printer::~Printer() = default;
 
-tint::Result<SuccessType> Printer::Generate() {
+tint::Result<SuccessType> Printer::Generate(Version version) {
     auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "GLSL writer");
     if (!valid) {
         return std::move(valid.Failure());
     }
 
+    {
+        TINT_SCOPED_ASSIGNMENT(current_buffer_, &preamble_buffer_);
+
+        auto out = Line();
+        out << "#version " << version.major_version << version.minor_version << "0";
+        if (version.IsES()) {
+            out << " es";
+        }
+    }
+
+    // Emit module-scope declarations.
+    EmitBlockInstructions(ir_.root_block);
+
+    // Emit functions.
+    for (auto* func : ir_.functions) {
+        EmitFunction(func);
+    }
+
     return Success;
 }
 
@@ -54,4 +81,69 @@
     return ss.str();
 }
 
+void Printer::EmitFunction(core::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(core::ir::Block* block) {
+    // TODO(dsinclair): Handle marking inline
+    // MarkInlinable(block);
+
+    EmitBlockInstructions(block);
+}
+
+void Printer::EmitBlockInstructions(core::ir::Block* block) {
+    TINT_SCOPED_ASSIGNMENT(current_block_, block);
+
+    for (auto* inst : *block) {
+        Switch(
+            inst,                                                //
+            [&](core::ir::Return* r) { EmitReturn(r); },         //
+            [&](core::ir::Unreachable*) { EmitUnreachable(); },  //
+            [&](Default) { UNHANDLED_CASE(inst); });
+    }
+}
+
+void Printer::EmitType(StringStream& out, [[maybe_unused]] const core::type::Type* ty) {
+    out << "void";
+}
+
+void Printer::EmitReturn(core::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";
+    // TODO(dsinclair): Handle return args
+    // if (!r->Args().IsEmpty()) {
+    //     out << " " << Expr(r->Args().Front());
+    // }
+    out << ";";
+}
+
+void Printer::EmitUnreachable() {
+    Line() << "/* unreachable */";
+}
+
 }  // namespace tint::glsl::writer
diff --git a/src/tint/lang/glsl/writer/printer/printer.h b/src/tint/lang/glsl/writer/printer/printer.h
index b04908d..5df46df 100644
--- a/src/tint/lang/glsl/writer/printer/printer.h
+++ b/src/tint/lang/glsl/writer/printer/printer.h
@@ -31,6 +31,7 @@
 #include <string>
 
 #include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/glsl/writer/common/version.h"
 #include "src/tint/utils/generator/text_generator.h"
 
 // Forward declarations
@@ -55,17 +56,45 @@
     explicit Printer(core::ir::Module& module);
     ~Printer() override;
 
+    /// @param version the GLSL version information
     /// @returns success or failure
-    tint::Result<SuccessType> Generate();
+    tint::Result<SuccessType> Generate(Version version);
 
     /// @copydoc tint::TextGenerator::Result
     std::string Result() const override;
 
   private:
+    /// Emit the function
+    /// @param func the function to emit
+    void EmitFunction(core::ir::Function* func);
+
+    /// Emit a block
+    /// @param block the block to emit
+    void EmitBlock(core::ir::Block* block);
+    /// Emit the instructions in a block
+    /// @param block the block with the instructions to emit
+    void EmitBlockInstructions(core::ir::Block* block);
+
+    /// Emit a return instruction
+    /// @param r the return instruction
+    void EmitReturn(core::ir::Return* r);
+    /// Emit an unreachable instruction
+    void EmitUnreachable();
+
+    /// Emit a type
+    /// @param out the stream to emit too
+    /// @param ty the type to emit
+    void EmitType(StringStream& out, const core::type::Type* ty);
+
     core::ir::Module& ir_;
 
     /// The buffer holding preamble text
     TextBuffer preamble_buffer_;
+
+    /// The current function being emitted
+    core::ir::Function* current_function_ = nullptr;
+    /// The current block being emitted
+    core::ir::Block* current_block_ = nullptr;
 };
 
 }  // namespace tint::glsl::writer
diff --git a/src/tint/lang/glsl/writer/writer.cc b/src/tint/lang/glsl/writer/writer.cc
index f5743d5..bc0f045 100644
--- a/src/tint/lang/glsl/writer/writer.cc
+++ b/src/tint/lang/glsl/writer/writer.cc
@@ -72,7 +72,7 @@
 
         // Generate the GLSL code.
         auto impl = std::make_unique<Printer>(ir);
-        auto result = impl->Generate();
+        auto result = impl->Generate(options.version);
         if (!result) {
             return result.Failure();
         }