[spirv-writer] Start emitting types

This CL starts the emitting of SPIR-V types. This CL adds code to emit
the `AliasType`, `BoolType`, `F32Type`, `I32Type`, `MatrixType`,
`U32Type`, `VectorType` and `VoidType`.

Bug: tint:5
Change-Id: Ic13026ca9006fc0a253d019b1a6dd984ae992fba
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17561
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b459584..72eb55e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -404,6 +404,7 @@
   list(APPEND TINT_TEST_SRCS
     writer/spirv/binary_writer_test.cc
     writer/spirv/builder_test.cc
+    writer/spirv/builder_type_test.cc
     writer/spirv/builder_entry_point_test.cc
     writer/spirv/instruction_test.cc
     writer/spirv/operand_test.cc
diff --git a/src/ast/type/matrix_type.cc b/src/ast/type/matrix_type.cc
index 64aef3e..08937d9 100644
--- a/src/ast/type/matrix_type.cc
+++ b/src/ast/type/matrix_type.cc
@@ -20,7 +20,7 @@
 namespace ast {
 namespace type {
 
-MatrixType::MatrixType(Type* subtype, size_t rows, size_t columns)
+MatrixType::MatrixType(Type* subtype, uint32_t rows, uint32_t columns)
     : subtype_(subtype), rows_(rows), columns_(columns) {
   assert(rows > 1);
   assert(rows < 5);
diff --git a/src/ast/type/matrix_type.h b/src/ast/type/matrix_type.h
index f37d12a..a3f95d3 100644
--- a/src/ast/type/matrix_type.h
+++ b/src/ast/type/matrix_type.h
@@ -30,7 +30,7 @@
   /// @param subtype type matrix type
   /// @param rows the number of rows in the matrix
   /// @param columns the number of columns in the matrix
-  MatrixType(Type* subtype, size_t rows, size_t columns);
+  MatrixType(Type* subtype, uint32_t rows, uint32_t columns);
   /// Move constructor
   MatrixType(MatrixType&&) = default;
   ~MatrixType() override;
@@ -41,9 +41,9 @@
   /// @returns the type of the matrix
   Type* type() const { return subtype_; }
   /// @returns the number of rows in the matrix
-  size_t rows() const { return rows_; }
+  uint32_t rows() const { return rows_; }
   /// @returns the number of columns in the matrix
-  size_t columns() const { return columns_; }
+  uint32_t columns() const { return columns_; }
 
   /// @returns the name for this type
   std::string type_name() const override {
@@ -53,8 +53,8 @@
 
  private:
   Type* subtype_ = nullptr;
-  size_t rows_ = 2;
-  size_t columns_ = 2;
+  uint32_t rows_ = 2;
+  uint32_t columns_ = 2;
 };
 
 }  // namespace type
diff --git a/src/ast/type/type.h b/src/ast/type/type.h
index cf66f3b..d2e171d 100644
--- a/src/ast/type/type.h
+++ b/src/ast/type/type.h
@@ -63,7 +63,7 @@
   /// @returns true if the type is a void type
   virtual bool IsVoid() const { return false; }
 
-  /// @returns the name for this type
+  /// @returns the name for this type. The |type_name| is unique over all types.
   virtual std::string type_name() const = 0;
 
   /// @returns the type as an alias type
diff --git a/src/ast/type/vector_type.cc b/src/ast/type/vector_type.cc
index 8530214..de97e04 100644
--- a/src/ast/type/vector_type.cc
+++ b/src/ast/type/vector_type.cc
@@ -20,7 +20,7 @@
 namespace ast {
 namespace type {
 
-VectorType::VectorType(Type* subtype, size_t size)
+VectorType::VectorType(Type* subtype, uint32_t size)
     : subtype_(subtype), size_(size) {
   assert(size_ > 1);
   assert(size_ < 5);
diff --git a/src/ast/type/vector_type.h b/src/ast/type/vector_type.h
index a773275..d2704c3 100644
--- a/src/ast/type/vector_type.h
+++ b/src/ast/type/vector_type.h
@@ -29,7 +29,7 @@
   /// Constructor
   /// @param subtype the vector element type
   /// @param size the number of elements in the vector
-  VectorType(Type* subtype, size_t size);
+  VectorType(Type* subtype, uint32_t size);
   /// Move constructor
   VectorType(VectorType&&) = default;
   ~VectorType() override;
@@ -40,7 +40,7 @@
   /// @returns the type of the vector elements
   Type* type() const { return subtype_; }
   /// @returns the size of the vector
-  size_t size() const { return size_; }
+  uint32_t size() const { return size_; }
 
   /// @returns the name for th type
   std::string type_name() const override {
@@ -49,7 +49,7 @@
 
  private:
   Type* subtype_ = nullptr;
-  size_t size_ = 2;
+  uint32_t size_ = 2;
 };
 
 }  // namespace type
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index b1a5c8b..1a386db 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -15,6 +15,8 @@
 #include "src/writer/spirv/builder.h"
 
 #include "spirv/unified1/spirv.h"
+#include "src/ast/type/matrix_type.h"
+#include "src/ast/type/vector_type.h"
 
 namespace tint {
 namespace writer {
@@ -137,14 +139,65 @@
 }
 
 void Builder::GenerateImport(ast::Import* imp) {
-  auto op = result_op();
-  auto id = op.to_i();
+  auto result = result_op();
+  auto id = result.to_i();
 
-  push_preamble(spv::Op::OpExtInstImport, {op, Operand::String(imp->path())});
+  push_preamble(spv::Op::OpExtInstImport,
+                {result, Operand::String(imp->path())});
 
   import_name_to_id_[imp->name()] = id;
 }
 
+uint32_t Builder::GenerateTypeIfNeeded(ast::type::Type* type) {
+  if (type->IsAlias()) {
+    return GenerateTypeIfNeeded(type->AsAlias()->type());
+  }
+
+  auto val = type_name_to_id_.find(type->type_name());
+  if (val != type_name_to_id_.end()) {
+    return val->second;
+  }
+
+  auto result = result_op();
+  auto id = result.to_i();
+
+  if (type->IsBool()) {
+    push_type(spv::Op::OpTypeBool, {result});
+  } else if (type->IsF32()) {
+    push_type(spv::Op::OpTypeFloat, {result, Operand::Int(32)});
+  } else if (type->IsI32()) {
+    push_type(spv::Op::OpTypeInt, {result, Operand::Int(32), Operand::Int(1)});
+  } else if (type->IsMatrix()) {
+    auto mat = type->AsMatrix();
+    ast::type::VectorType col_type(mat->type(), mat->rows());
+    auto type_id = GenerateTypeIfNeeded(&col_type);
+    if (has_error()) {
+      return 0;
+    }
+
+    push_type(spv::Op::OpTypeMatrix,
+              {result, Operand::Int(type_id), Operand::Int(mat->columns())});
+  } else if (type->IsU32()) {
+    push_type(spv::Op::OpTypeInt, {result, Operand::Int(32), Operand::Int(0)});
+  } else if (type->IsVector()) {
+    auto vec = type->AsVector();
+    auto col_type_id = GenerateTypeIfNeeded(vec->type());
+    if (has_error()) {
+      return 0;
+    }
+    push_type(spv::Op::OpTypeVector,
+              {result, Operand::Int(col_type_id), Operand::Int(vec->size())});
+  } else if (type->IsVoid()) {
+    push_type(spv::Op::OpTypeVoid, {result});
+  } else {
+    error_ = "unable to convert type: " + type->type_name();
+    return 0;
+  }
+
+  type_name_to_id_[type->type_name()] = id;
+  return id;
+}
+
 }  // namespace spirv
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index 91d67dc..7a42e3e 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -41,6 +41,8 @@
 
   /// @returns the error string or blank if no error was reported.
   const std::string& error() const { return error_; }
+  /// @returns true if the builder encountered an error
+  bool has_error() const { return !error_.empty(); }
 
   /// @returns the number of uint32_t's needed to make up the results
   uint32_t total_size() const;
@@ -99,7 +101,7 @@
     types_.push_back(Instruction{op, operands});
   }
   /// @returns the type instructions
-  const std::vector<Instruction>& type() const { return types_; }
+  const std::vector<Instruction>& types() const { return types_; }
   /// Adds an instruction to the instruction list
   /// @param op the op to set
   /// @param operands the operands for the instruction
@@ -124,8 +126,14 @@
   /// Generates an import instruction
   /// @param imp the import
   void GenerateImport(ast::Import* imp);
+  /// Generates a type if not already created
+  /// @param type the type to create
+  /// @returns the ID to use for the given type. Returns 0 on unknown type.
+  uint32_t GenerateTypeIfNeeded(ast::type::Type* type);
 
  private:
+  /// @returns an Operand with a new result ID in it. Increments the next_id_
+  /// automatically.
   Operand result_op();
 
   std::string error_;
@@ -138,6 +146,7 @@
 
   std::unordered_map<std::string, uint32_t> import_name_to_id_;
   std::unordered_map<std::string, uint32_t> func_name_to_id_;
+  std::unordered_map<std::string, uint32_t> type_name_to_id_;
 };
 
 }  // namespace spirv
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
new file mode 100644
index 0000000..014d8c1
--- /dev/null
+++ b/src/writer/spirv/builder_type_test.cc
@@ -0,0 +1,256 @@
+// Copyright 2020 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 <memory>
+
+#include "gtest/gtest.h"
+#include "src/ast/type/alias_type.h"
+#include "src/ast/type/array_type.h"
+#include "src/ast/type/bool_type.h"
+#include "src/ast/type/f32_type.h"
+#include "src/ast/type/i32_type.h"
+#include "src/ast/type/matrix_type.h"
+#include "src/ast/type/pointer_type.h"
+#include "src/ast/type/u32_type.h"
+#include "src/ast/type/vector_type.h"
+#include "src/ast/type/void_type.h"
+#include "src/writer/spirv/builder.h"
+#include "src/writer/spirv/spv_dump.h"
+
+namespace tint {
+namespace writer {
+namespace spirv {
+
+using BuilderTest_Type = testing::Test;
+
+TEST_F(BuilderTest_Type, GenerateAlias) {
+  ast::type::F32Type f32;
+  ast::type::AliasType alias_type("my_type", &f32);
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&alias_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedAlias) {
+  ast::type::I32Type i32;
+  ast::type::F32Type f32;
+  ast::type::AliasType alias_type("my_type", &f32);
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&alias_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&alias_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateBool) {
+  ast::type::BoolType bool_type;
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&bool_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  ASSERT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeBool
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedBool) {
+  ast::type::I32Type i32;
+  ast::type::BoolType bool_type;
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&bool_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&bool_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateF32) {
+  ast::type::F32Type f32;
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&f32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  ASSERT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeFloat 32
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedF32) {
+  ast::type::I32Type i32;
+  ast::type::F32Type f32;
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateI32) {
+  ast::type::I32Type i32;
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&i32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  ASSERT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 1
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedI32) {
+  ast::type::I32Type i32;
+  ast::type::F32Type f32;
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateMatrix) {
+  ast::type::F32Type f32;
+  ast::type::MatrixType mat_type(&f32, 3, 2);
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&mat_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(b.types().size(), 3);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypeVector %3 3
+%1 = OpTypeMatrix %2 2
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
+  ast::type::I32Type i32;
+  ast::type::MatrixType mat_type(&i32, 3, 4);
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&mat_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 3);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&mat_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateU32) {
+  ast::type::U32Type u32;
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&u32);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  ASSERT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 0
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedU32) {
+  ast::type::U32Type u32;
+  ast::type::F32Type f32;
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&u32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&f32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&u32), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateVector) {
+  ast::type::F32Type f32;
+  ast::type::VectorType vec_type(&f32, 3);
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&vec_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(b.types().size(), 2);
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeVector %2 3
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedVector) {
+  ast::type::I32Type i32;
+  ast::type::VectorType vec_type(&i32, 3);
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&vec_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&vec_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+TEST_F(BuilderTest_Type, GenerateVoid) {
+  ast::type::VoidType void_type;
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&void_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  ASSERT_EQ(b.types().size(), 1);
+  EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeVoid
+)");
+}
+
+TEST_F(BuilderTest_Type, ReturnsGeneratedVoid) {
+  ast::type::I32Type i32;
+  ast::type::VoidType void_type;
+
+  Builder b;
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&void_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&i32), 2);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(b.GenerateTypeIfNeeded(&void_type), 1);
+  ASSERT_FALSE(b.has_error()) << b.error();
+}
+
+}  // namespace spirv
+}  // namespace writer
+}  // namespace tint
diff --git a/src/writer/spirv/spv_dump.cc b/src/writer/spirv/spv_dump.cc
index e001a3a..c649acc 100644
--- a/src/writer/spirv/spv_dump.cc
+++ b/src/writer/spirv/spv_dump.cc
@@ -53,9 +53,7 @@
   tools.SetMessageConsumer(msg_consumer);
 
   std::string result;
-  if (!tools.Disassemble(data, &result,
-                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
-                             SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) {
+  if (!tools.Disassemble(data, &result, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) {
     printf("%s\n", spv_errors.c_str());
   }
   return result;
@@ -77,6 +75,15 @@
   return Disassemble(writer.result());
 }
 
+std::string DumpInstructions(const std::vector<Instruction>& insts) {
+  BinaryWriter writer;
+  writer.WriteHeader(kDefaultMaxIdBound);
+  for (const auto& inst : insts) {
+    writer.WriteInstruction(inst);
+  }
+  return Disassemble(writer.result());
+}
+
 }  // namespace spirv
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/spirv/spv_dump.h b/src/writer/spirv/spv_dump.h
index 295861c..34a023e 100644
--- a/src/writer/spirv/spv_dump.h
+++ b/src/writer/spirv/spv_dump.h
@@ -34,6 +34,11 @@
 /// @returns the instruction as a SPIR-V disassembly string
 std::string DumpInstruction(const Instruction& inst);
 
+/// Dumps the given instructions to a SPIR-V disassembly string
+/// @param insts the instructions to dump
+/// @returns the instruction as a SPIR-V disassembly string
+std::string DumpInstructions(const std::vector<Instruction>& insts);
+
 }  // namespace spirv
 }  // namespace writer
 }  // namespace tint