ir/spirv-writer: Emit function declarations

Emit the return type, function type, and the OpFunction
instruction. The body is just a label and a hardcoded OpReturn for now
and will be emitted properly in a future patch, along with entry point
declarations and function parameters.

Bug: tint:1906
Change-Id: Id7117da078bccd77a00afb54a63c9c55b13236f9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/131600
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index bd4c605..f0a3ee75 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1859,6 +1859,7 @@
 
     if (tint_build_ir) {
       sources += [
+        "writer/spirv/generator_impl_function_test.cc",
         "writer/spirv/generator_impl_ir_test.cc",
         "writer/spirv/generator_impl_type_test.cc",
       ]
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 8b8daa4..26995d3 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -1227,6 +1227,7 @@
 
     if(${TINT_BUILD_IR})
       list(APPEND TINT_TEST_SRCS
+        writer/spirv/generator_impl_function_test.cc
         writer/spirv/generator_impl_ir_test.cc
         writer/spirv/generator_impl_type_test.cc
         writer/spirv/test_helper_ir.h
diff --git a/src/tint/writer/spirv/generator_impl_function_test.cc b/src/tint/writer/spirv/generator_impl_function_test.cc
new file mode 100644
index 0000000..e4cb71b
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_function_test.cc
@@ -0,0 +1,50 @@
+// 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/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Function_Empty) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+// Test that we do not emit the same function type more than once.
+TEST_F(SpvGeneratorImplTest, Function_DeduplicateType) {
+    auto* func = CreateFunction();
+    func->return_type = ir.types.Get<type::Void>();
+
+    generator_.EmitFunction(func);
+    generator_.EmitFunction(func);
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.cc b/src/tint/writer/spirv/generator_impl_ir.cc
index fabb3a9..e947e6c 100644
--- a/src/tint/writer/spirv/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/generator_impl_ir.cc
@@ -43,8 +43,10 @@
     // TODO(crbug.com/tint/1906): Emit variables.
     (void)zero_init_workgroup_memory_;
 
-    // TODO(crbug.com/tint/1906): Emit functions.
-    (void)ir_;
+    // Emit functions.
+    for (auto* func : ir_->functions) {
+        EmitFunction(func);
+    }
 
     // Serialize the module into binary SPIR-V.
     writer_.WriteHeader(module_.IdBound());
@@ -79,4 +81,46 @@
     });
 }
 
+void GeneratorImplIr::EmitFunction(const ir::Function* func) {
+    // Make an ID for the function.
+    auto id = module_.NextId();
+
+    // Emit the function name.
+    module_.PushDebug(spv::Op::OpName, {id, Operand(func->name.Name())});
+
+    // TODO(jrprice): Emit OpEntryPoint and OpExecutionMode declarations if needed.
+
+    // Get the ID for the return type.
+    auto return_type_id = Type(func->return_type);
+
+    // Get the ID for the function type (creating it if needed).
+    // TODO(jrprice): Add the parameter types when they are supported in the IR.
+    FunctionType function_type{return_type_id, {}};
+    auto function_type_id = function_types_.GetOrCreate(function_type, [&]() {
+        auto func_ty_id = module_.NextId();
+        OperandList operands = {func_ty_id, return_type_id};
+        operands.insert(operands.end(), function_type.param_type_ids.begin(),
+                        function_type.param_type_ids.end());
+        module_.PushType(spv::Op::OpTypeFunction, operands);
+        return func_ty_id;
+    });
+
+    // Declare the function.
+    auto decl = Instruction{spv::Op::OpFunction,
+                            {return_type_id, id, SpvFunctionControlMaskNone, function_type_id}};
+
+    // Create a function that we will add instructions to.
+    // TODO(jrprice): Add the parameter declarations when they are supported in the IR.
+    auto entry_block = module_.NextId();
+    Function current_function_(decl, entry_block, {});
+
+    // TODO(jrprice): Emit the body of the function.
+
+    // TODO(jrprice): Remove this when we start emitting OpReturn for branches to the terminator.
+    current_function_.push_inst(spv::Op::OpReturn, {});
+
+    // Add the function to the module.
+    module_.PushFunction(current_function_);
+}
+
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.h b/src/tint/writer/spirv/generator_impl_ir.h
index 44fefb5..d9aab3c 100644
--- a/src/tint/writer/spirv/generator_impl_ir.h
+++ b/src/tint/writer/spirv/generator_impl_ir.h
@@ -19,11 +19,13 @@
 
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/utils/hashmap.h"
+#include "src/tint/utils/vector.h"
 #include "src/tint/writer/spirv/binary_writer.h"
 #include "src/tint/writer/spirv/module.h"
 
 // Forward declarations
 namespace tint::ir {
+class Function;
 class Module;
 }  // namespace tint::ir
 namespace tint::type {
@@ -58,15 +60,47 @@
     /// @returns the result ID of the type
     uint32_t Type(const type::Type* ty);
 
+    /// Emit a function.
+    /// @param func the function to emit
+    void EmitFunction(const ir::Function* func);
+
   private:
     const ir::Module* ir_;
     spirv::Module module_;
     BinaryWriter writer_;
     diag::List diagnostics_;
 
+    /// A function type used for an OpTypeFunction declaration.
+    struct FunctionType {
+        uint32_t return_type_id;
+        utils::Vector<uint32_t, 4> param_type_ids;
+
+        /// Hasher provides a hash function for the FunctionType.
+        struct Hasher {
+            /// @param ft the FunctionType to create a hash for
+            /// @return the hash value
+            inline std::size_t operator()(const FunctionType& ft) const {
+                size_t hash = utils::Hash(ft.return_type_id);
+                for (auto& p : ft.param_type_ids) {
+                    hash = utils::HashCombine(hash, p);
+                }
+                return hash;
+            }
+        };
+
+        /// Equality operator for FunctionType.
+        bool operator==(const FunctionType& other) const {
+            return (param_type_ids == other.param_type_ids) &&
+                   (return_type_id == other.return_type_id);
+        }
+    };
+
     /// The map of types to their result IDs.
     utils::Hashmap<const type::Type*, uint32_t, 8> types_;
 
+    /// The map of function types to their result IDs.
+    utils::Hashmap<FunctionType, uint32_t, 8, FunctionType::Hasher> function_types_;
+
     bool zero_init_workgroup_memory_ = false;
 };
 
diff --git a/src/tint/writer/spirv/spv_dump.cc b/src/tint/writer/spirv/spv_dump.cc
index 6ffc56b..9a63786 100644
--- a/src/tint/writer/spirv/spv_dump.cc
+++ b/src/tint/writer/spirv/spv_dump.cc
@@ -56,9 +56,13 @@
 }
 
 std::string DumpBuilder(Builder& builder) {
+    return DumpModule(builder.Module());
+}
+
+std::string DumpModule(Module& module) {
     BinaryWriter writer;
-    writer.WriteHeader(builder.Module().IdBound());
-    writer.WriteModule(&builder.Module());
+    writer.WriteHeader(module.IdBound());
+    writer.WriteModule(&module);
     return Disassemble(writer.result());
 }
 
diff --git a/src/tint/writer/spirv/spv_dump.h b/src/tint/writer/spirv/spv_dump.h
index a0dec2c..359f0cb 100644
--- a/src/tint/writer/spirv/spv_dump.h
+++ b/src/tint/writer/spirv/spv_dump.h
@@ -32,6 +32,11 @@
 /// @returns the builder as a SPIR-V disassembly string
 std::string DumpBuilder(Builder& builder);
 
+/// Dumps the given module to a SPIR-V disassembly string
+/// @param module the module to convert
+/// @returns the module as a SPIR-V disassembly string
+std::string DumpModule(Module& module);
+
 /// Dumps the given instruction to a SPIR-V disassembly string
 /// @param inst the instruction to dump
 /// @returns the instruction as a SPIR-V disassembly string