ir/spirv-writer: Add support for scalar types

Also add a test helper header with some base classes that derive from
the IR builder.

Bug: tint:1906
Change-Id: If642bc64a50b6cae10363018a8dea8547ab9f542
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/131441
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index e3fed4d..a866575 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1858,7 +1858,10 @@
     ]
 
     if (tint_build_ir) {
-      sources += [ "writer/spirv/generator_impl_ir_test.cc" ]
+      sources += [
+        "writer/spirv/generator_impl_ir_test.cc",
+        "writer/spirv/generator_impl_type_test.cc",
+      ]
       deps += [ ":libtint_ir_src" ]
     }
   }
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index d0cf092..daafbb4 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -1228,6 +1228,8 @@
     if(${TINT_BUILD_IR})
       list(APPEND TINT_TEST_SRCS
         writer/spirv/generator_impl_ir_test.cc
+        writer/spirv/generator_impl_type_test.cc
+        writer/spirv/test_helper_ir.h
       )
     endif()
   endif()
diff --git a/src/tint/writer/spirv/generator_impl_ir.cc b/src/tint/writer/spirv/generator_impl_ir.cc
index 23842b6..fabb3a9 100644
--- a/src/tint/writer/spirv/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/generator_impl_ir.cc
@@ -16,6 +16,14 @@
 
 #include "spirv/unified1/spirv.h"
 #include "src/tint/ir/module.h"
+#include "src/tint/switch.h"
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/type.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/void.h"
 #include "src/tint/writer/spirv/module.h"
 
 namespace tint::writer::spirv {
@@ -45,4 +53,30 @@
     return true;
 }
 
+uint32_t GeneratorImplIr::Type(const type::Type* ty) {
+    return types_.GetOrCreate(ty, [&]() {
+        auto id = module_.NextId();
+        Switch(
+            ty,  //
+            [&](const type::Void*) { module_.PushType(spv::Op::OpTypeVoid, {id}); },
+            [&](const type::Bool*) { module_.PushType(spv::Op::OpTypeBool, {id}); },
+            [&](const type::I32*) {
+                module_.PushType(spv::Op::OpTypeInt, {id, 32u, 1u});
+            },
+            [&](const type::U32*) {
+                module_.PushType(spv::Op::OpTypeInt, {id, 32u, 0u});
+            },
+            [&](const type::F32*) {
+                module_.PushType(spv::Op::OpTypeFloat, {id, 32u});
+            },
+            [&](const type::F16*) {
+                module_.PushType(spv::Op::OpTypeFloat, {id, 16u});
+            },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_) << "unhandled type: " << ty->FriendlyName();
+            });
+        return id;
+    });
+}
+
 }  // 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 7da1184..44fefb5 100644
--- a/src/tint/writer/spirv/generator_impl_ir.h
+++ b/src/tint/writer/spirv/generator_impl_ir.h
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/utils/hashmap.h"
 #include "src/tint/writer/spirv/binary_writer.h"
 #include "src/tint/writer/spirv/module.h"
 
@@ -25,6 +26,9 @@
 namespace tint::ir {
 class Module;
 }  // namespace tint::ir
+namespace tint::type {
+class Type;
+}  // namespace tint::type
 
 namespace tint::writer::spirv {
 
@@ -49,12 +53,20 @@
     /// @returns the list of diagnostics raised by the generator
     diag::List Diagnostics() const { return diagnostics_; }
 
+    /// Get the result ID of the type `ty`, emitting a type declaration instruction if necessary.
+    /// @param ty the type to get the ID for
+    /// @returns the result ID of the type
+    uint32_t Type(const type::Type* ty);
+
   private:
     const ir::Module* ir_;
     spirv::Module module_;
     BinaryWriter writer_;
     diag::List diagnostics_;
 
+    /// The map of types to their result IDs.
+    utils::Hashmap<const type::Type*, uint32_t, 8> types_;
+
     bool zero_init_workgroup_memory_ = false;
 };
 
diff --git a/src/tint/writer/spirv/generator_impl_ir_test.cc b/src/tint/writer/spirv/generator_impl_ir_test.cc
index 8e8f8e7..a202eea 100644
--- a/src/tint/writer/spirv/generator_impl_ir_test.cc
+++ b/src/tint/writer/spirv/generator_impl_ir_test.cc
@@ -12,22 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gtest/gtest.h"
-
-#include "src/tint/ir/module.h"
-#include "src/tint/writer/spirv/generator_impl_ir.h"
-#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper_ir.h"
 
 namespace tint::writer::spirv {
 namespace {
 
-using SpvGeneratorImplTest = testing::Test;
-
 TEST_F(SpvGeneratorImplTest, ModuleHeader) {
-    ir::Module module;
-    GeneratorImplIr generator(&module, false);
-    ASSERT_TRUE(generator.Generate()) << generator.Diagnostics().str();
-    auto got = Disassemble(generator.Result());
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    auto got = Disassemble(generator_.Result());
     EXPECT_EQ(got, R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
 )");
diff --git a/src/tint/writer/spirv/generator_impl_type_test.cc b/src/tint/writer/spirv/generator_impl_type_test.cc
new file mode 100644
index 0000000..c8a2e8e
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_type_test.cc
@@ -0,0 +1,87 @@
+// 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/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/type.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/void.h"
+#include "src/tint/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Type_Void) {
+    auto id = generator_.Type(ir.types.Get<type::Void>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeVoid\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_Bool) {
+    auto id = generator_.Type(ir.types.Get<type::Bool>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeBool\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_I32) {
+    auto id = generator_.Type(ir.types.Get<type::I32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 1\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_U32) {
+    auto id = generator_.Type(ir.types.Get<type::U32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 0\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_F32) {
+    auto id = generator_.Type(ir.types.Get<type::F32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 32\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_F16) {
+    auto id = generator_.Type(ir.types.Get<type::F16>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 16\n");
+}
+
+// Test that we do can emit multiple types.
+// Includes types with the same opcode but different parameters.
+TEST_F(SpvGeneratorImplTest, Type_Multiple) {
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::I32>()), 1u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::U32>()), 2u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::F32>()), 3u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::F16>()), 4u);
+    EXPECT_EQ(DumpTypes(), R"(%1 = OpTypeInt 32 1
+%2 = OpTypeInt 32 0
+%3 = OpTypeFloat 32
+%4 = OpTypeFloat 16
+)");
+}
+
+// Test that we do not emit the same type more than once.
+TEST_F(SpvGeneratorImplTest, Type_Deduplicate) {
+    auto* i32 = ir.types.Get<type::I32>();
+    EXPECT_EQ(generator_.Type(i32), 1u);
+    EXPECT_EQ(generator_.Type(i32), 1u);
+    EXPECT_EQ(generator_.Type(i32), 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 1\n");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/test_helper_ir.h b/src/tint/writer/spirv/test_helper_ir.h
new file mode 100644
index 0000000..6de6f48
--- /dev/null
+++ b/src/tint/writer/spirv/test_helper_ir.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_
+#define SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/tint/ir/builder.h"
+#include "src/tint/writer/spirv/generator_impl_ir.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+
+namespace tint::writer::spirv {
+
+/// Base helper class for testing the SPIR-V generator implementation.
+template <typename BASE>
+class SpvGeneratorTestHelperBase : public ir::Builder, public BASE {
+  public:
+    SpvGeneratorTestHelperBase() : generator_(&ir, false) {}
+
+  protected:
+    /// The SPIR-V generator.
+    GeneratorImplIr generator_;
+
+    /// @returns the disassembled types from the generated module.
+    std::string DumpTypes() { return DumpInstructions(generator_.Module().Types()); }
+};
+
+using SpvGeneratorImplTest = SpvGeneratorTestHelperBase<testing::Test>;
+
+template <typename T>
+using SpvGeneratorImplTestWithParam = SpvGeneratorTestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace tint::writer::spirv
+
+#endif  // SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_