[glsl] Emit user call statements

Add emitting of user call statements.

Bug: 42251044
Change-Id: Ic30d588472f47d9412ff8c90f5808c85448cbc41
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/200244
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/glsl/writer/BUILD.bazel b/src/tint/lang/glsl/writer/BUILD.bazel
index 111f1a9..9ea3151 100644
--- a/src/tint/lang/glsl/writer/BUILD.bazel
+++ b/src/tint/lang/glsl/writer/BUILD.bazel
@@ -87,6 +87,7 @@
   name = "test",
   alwayslink = True,
   srcs = [
+    "call_test.cc",
     "constant_test.cc",
     "function_test.cc",
   ] + select({
diff --git a/src/tint/lang/glsl/writer/BUILD.cmake b/src/tint/lang/glsl/writer/BUILD.cmake
index 8e0850b..f105f30 100644
--- a/src/tint/lang/glsl/writer/BUILD.cmake
+++ b/src/tint/lang/glsl/writer/BUILD.cmake
@@ -98,6 +98,7 @@
 # Condition: TINT_BUILD_GLSL_WRITER AND TINT_BUILD_GLSL_VALIDATOR
 ################################################################################
 tint_add_target(tint_lang_glsl_writer_test test
+  lang/glsl/writer/call_test.cc
   lang/glsl/writer/constant_test.cc
   lang/glsl/writer/function_test.cc
 )
diff --git a/src/tint/lang/glsl/writer/BUILD.gn b/src/tint/lang/glsl/writer/BUILD.gn
index 966c7b0..1d2047c 100644
--- a/src/tint/lang/glsl/writer/BUILD.gn
+++ b/src/tint/lang/glsl/writer/BUILD.gn
@@ -90,6 +90,7 @@
   if (tint_build_glsl_writer && tint_build_glsl_validator) {
     tint_unittests_source_set("unittests") {
       sources = [
+        "call_test.cc",
         "constant_test.cc",
         "function_test.cc",
       ]
diff --git a/src/tint/lang/glsl/writer/call_test.cc b/src/tint/lang/glsl/writer/call_test.cc
new file mode 100644
index 0000000..b7148f1
--- /dev/null
+++ b/src/tint/lang/glsl/writer/call_test.cc
@@ -0,0 +1,116 @@
+// Copyright 2024 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/helper_test.h"
+
+using namespace tint::core::number_suffixes;  // NOLINT
+using namespace tint::core::fluent_types;     // NOLINT
+
+namespace tint::glsl::writer {
+namespace {
+
+// TODO(dsinclair): Enable when `let` is supported
+TEST_F(GlslWriterTest, DISABLED_CallWithoutParams) {
+    auto* f = b.Function("a", ty.bool_());
+    f->Block()->Append(b.Return(f, false));
+
+    auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetWorkgroupSize(1, 1, 1);
+    b.Append(ep->Block(), [&] {
+        b.Let("x", b.Call(f));
+        b.Return(ep);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.glsl;
+    EXPECT_EQ(output_.glsl, GlslHeader() + R"(
+bool a() {
+  return false;
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+    bool x = a();
+}
+
+)");
+}
+
+// TODO(dsinclair): Enable when FunctionParam is supported
+TEST_F(GlslWriterTest, DISABLED_CallWithParams) {
+    auto* p1 = b.FunctionParam("p1", ty.f32());
+    auto* p2 = b.FunctionParam("p2", ty.bool_());
+
+    auto* f = b.Function("a", ty.bool_());
+    f->SetParams({p1, p2});
+    f->Block()->Append(b.Return(f, p2));
+
+    auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetWorkgroupSize(1, 1, 1);
+    b.Append(ep->Block(), [&] {
+        b.Let("x", b.Call(f, 1.2_f, false));
+        b.Return(ep);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.glsl;
+    EXPECT_EQ(output_.glsl, GlslHeader() + R"(
+bool a(float p1, bool p2) {
+  return p2;
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+    bool x = a(1.2f, false);
+}
+
+)");
+}
+
+TEST_F(GlslWriterTest, CallStatement) {
+    auto* f = b.Function("a", ty.bool_());
+    f->Block()->Append(b.Return(f, false));
+
+    auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetWorkgroupSize(1, 1, 1);
+    b.Append(ep->Block(), [&] {
+        b.Call(f);
+        b.Return(ep);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.glsl;
+    EXPECT_EQ(output_.glsl, GlslHeader() + R"(
+bool a() {
+  return false;
+}
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  a();
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::glsl::writer
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index 02012a4..8ce53bd 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -30,10 +30,21 @@
 #include <string>
 #include <utility>
 
+#include "src/tint/lang/core/ir/access.h"
+#include "src/tint/lang/core/ir/bitcast.h"
+#include "src/tint/lang/core/ir/construct.h"
+#include "src/tint/lang/core/ir/core_binary.h"
+#include "src/tint/lang/core/ir/core_unary.h"
+#include "src/tint/lang/core/ir/exit_if.h"
 #include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/load.h"
+#include "src/tint/lang/core/ir/load_vector_element.h"
 #include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/next_iteration.h"
 #include "src/tint/lang/core/ir/return.h"
+#include "src/tint/lang/core/ir/swizzle.h"
 #include "src/tint/lang/core/ir/unreachable.h"
+#include "src/tint/lang/core/ir/user_call.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/type/bool.h"
 #include "src/tint/lang/core/type/f16.h"
@@ -59,7 +70,7 @@
   public:
     /// Constructor
     /// @param module the Tint IR module to generate
-    explicit Printer(const core::ir::Module& module) : ir_(module) {}
+    explicit Printer(core::ir::Module& module) : ir_(module) {}
 
     /// @param version the GLSL version information
     /// @returns the generated GLSL shader
@@ -80,7 +91,7 @@
         }
 
         // Emit module-scope declarations.
-        EmitBlockInstructions(ir_.root_block);
+        EmitBlock(ir_.root_block);
 
         // Emit functions.
         for (auto& func : ir_.functions) {
@@ -93,7 +104,7 @@
     }
 
   private:
-    const core::ir::Module& ir_;
+    core::ir::Module& ir_;
 
     /// The buffer holding preamble text
     TextBuffer preamble_buffer_;
@@ -105,6 +116,25 @@
 
     Hashset<std::string, 4> emitted_extensions_;
 
+    /// A hashmap of value to name
+    Hashmap<const core::ir::Value*, std::string, 32> names_;
+
+    /// @returns the name of the given value, creating a new unique name if the value is unnamed in
+    /// the module.
+    std::string NameOf(const core::ir::Value* value) {
+        return names_.GetOrAdd(value, [&] {
+            auto sym = ir_.NameOf(value);
+            return sym.IsValid() ? sym.Name() : UniqueIdentifier("v");
+        });
+    }
+
+    /// @return a new, unique identifier with the given prefix.
+    /// @param prefix optional prefix to apply to the generated identifier. If empty
+    /// "tint_symbol" will be used.
+    std::string UniqueIdentifier(const std::string& prefix /* = "" */) {
+        return ir_.symbols.New(prefix).Name();
+    }
+
     /// Emit the function
     /// @param func the function to emit
     void EmitFunction(const core::ir::Function* func) {
@@ -140,26 +170,38 @@
     /// Emit a block
     /// @param block the block to emit
     void EmitBlock(const core::ir::Block* block) {
-        // TODO(dsinclair): Handle marking inline
-        // MarkInlinable(block);
-
-        EmitBlockInstructions(block);
-    }
-
-    /// Emit the instructions in a block
-    /// @param block the block with the instructions to emit
-    void EmitBlockInstructions(const core::ir::Block* block) {
         TINT_SCOPED_ASSIGNMENT(current_block_, block);
 
         for (auto* inst : *block) {
             tint::Switch(
                 inst,                                                      //
+                [&](const core::ir::Call* i) { EmitCallStmt(i); },         //
                 [&](const core::ir::Return* r) { EmitReturn(r); },         //
                 [&](const core::ir::Unreachable*) { EmitUnreachable(); },  //
+
+                [&](const core::ir::NextIteration*) { /* do nothing */ },                //
+                [&](const core::ir::ExitIf*) { /* do nothing handled by transform */ },  //
+                                                                                         //
+                [&](const core::ir::Access*) { /* inlined */ },                          //
+                [&](const core::ir::Bitcast*) { /* inlined */ },                         //
+                [&](const core::ir::Construct*) { /* inlined */ },                       //
+                [&](const core::ir::CoreBinary*) { /* inlined */ },                      //
+                [&](const core::ir::CoreUnary*) { /* inlined */ },                       //
+                [&](const core::ir::Load*) { /* inlined */ },                            //
+                [&](const core::ir::LoadVectorElement*) { /* inlined */ },               //
+                [&](const core::ir::Swizzle*) { /* inlined */ },                         //
                 TINT_ICE_ON_NO_MATCH);
         }
     }
 
+    void EmitCallStmt(const core::ir::Call* c) {
+        if (!c->Result(0)->IsUsed()) {
+            auto out = Line();
+            EmitValue(out, c->Result(0));
+            out << ";";
+        }
+    }
+
     void EmitExtension(std::string name) {
         if (emitted_extensions_.Contains(name)) {
             return;
@@ -213,11 +255,31 @@
         tint::Switch(
             v,                                                           //
             [&](const core::ir::Constant* c) { EmitConstant(out, c); },  //
-
+            [&](const core::ir::InstructionResult* r) {
+                tint::Switch(
+                    r->Instruction(),                                            //
+                    [&](const core::ir::UserCall* c) { EmitUserCall(out, c); },  //
+                    TINT_ICE_ON_NO_MATCH);
+            },
             // TODO(dsinclair): Handle remaining value types
             TINT_ICE_ON_NO_MATCH);
     }
 
+    /// Emits a user call instruction
+    void EmitUserCall(StringStream& out, const core::ir::UserCall* c) {
+        out << NameOf(c->Target()) << "(";
+        size_t i = 0;
+        for (const auto* arg : c->Args()) {
+            if (i > 0) {
+                out << ", ";
+            }
+            ++i;
+
+            EmitValue(out, arg);
+        }
+        out << ")";
+    }
+
     void EmitConstant(StringStream& out, const core::ir::Constant* c) {
         EmitConstant(out, c->Value());
     }
@@ -240,7 +302,7 @@
 };
 }  // namespace
 
-Result<std::string> Print(const core::ir::Module& module, const Version& version) {
+Result<std::string> Print(core::ir::Module& module, const Version& version) {
     return Printer{module}.Generate(version);
 }
 
diff --git a/src/tint/lang/glsl/writer/printer/printer.h b/src/tint/lang/glsl/writer/printer/printer.h
index 6f75b39..49c6301 100644
--- a/src/tint/lang/glsl/writer/printer/printer.h
+++ b/src/tint/lang/glsl/writer/printer/printer.h
@@ -45,7 +45,7 @@
 /// @returns the generated GLSL shader on success, or failure
 /// @param module the Tint IR module to generate
 /// @param version the GLSL version information
-Result<std::string> Print(const core::ir::Module& module, const Version& version);
+Result<std::string> Print(core::ir::Module& module, const Version& version);
 
 }  // namespace tint::glsl::writer