[spirv-writer] Generate struct types

This CL adds generation of Struct types to the SPIR-V writer.

Bug: tint:5
Change-Id: Ibcabf7b1a688026297de682f4825d5195d8007d2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17701
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/ast/struct_member_offset_decoration.cc b/src/ast/struct_member_offset_decoration.cc
index 0a70a95..ec49b19 100644
--- a/src/ast/struct_member_offset_decoration.cc
+++ b/src/ast/struct_member_offset_decoration.cc
@@ -17,7 +17,7 @@
 namespace tint {
 namespace ast {
 
-StructMemberOffsetDecoration::StructMemberOffsetDecoration(size_t offset)
+StructMemberOffsetDecoration::StructMemberOffsetDecoration(uint32_t offset)
     : offset_(offset) {}
 
 StructMemberOffsetDecoration::~StructMemberOffsetDecoration() = default;
diff --git a/src/ast/struct_member_offset_decoration.h b/src/ast/struct_member_offset_decoration.h
index 8e15a63..4e59b2a 100644
--- a/src/ast/struct_member_offset_decoration.h
+++ b/src/ast/struct_member_offset_decoration.h
@@ -29,20 +29,20 @@
  public:
   /// constructor
   /// @param offset the offset value
-  explicit StructMemberOffsetDecoration(size_t offset);
+  explicit StructMemberOffsetDecoration(uint32_t offset);
   ~StructMemberOffsetDecoration() override;
 
   /// @returns true if this is an offset decoration
   bool IsOffset() const override { return true; }
 
   /// @returns the offset value
-  size_t offset() const { return offset_; }
+  uint32_t offset() const { return offset_; }
 
   /// @returns the decoration as a string
   std::string to_str() const override;
 
  private:
-  size_t offset_;
+  uint32_t offset_;
 };
 
 }  // namespace ast
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 1a386db..6a9063d 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -15,7 +15,11 @@
 #include "src/writer/spirv/builder.h"
 
 #include "spirv/unified1/spirv.h"
+#include "src/ast/struct.h"
+#include "src/ast/struct_member.h"
+#include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/type/matrix_type.h"
+#include "src/ast/type/struct_type.h"
 #include "src/ast/type/vector_type.h"
 
 namespace tint {
@@ -168,25 +172,19 @@
   } 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()) {
+    if (!GenerateMatrixType(type->AsMatrix(), result)) {
       return 0;
     }
-
-    push_type(spv::Op::OpTypeMatrix,
-              {result, Operand::Int(type_id), Operand::Int(mat->columns())});
+  } else if (type->IsStruct()) {
+    if (!GenerateStructType(type->AsStruct(), result)) {
+      return 0;
+    }
   } 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()) {
+    if (!GenerateVectorType(type->AsVector(), result)) {
       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 {
@@ -198,6 +196,84 @@
   return id;
 }
 
+bool Builder::GenerateMatrixType(ast::type::MatrixType* mat,
+                                 const Operand& result) {
+  ast::type::VectorType col_type(mat->type(), mat->rows());
+  auto col_type_id = GenerateTypeIfNeeded(&col_type);
+  if (has_error()) {
+    return false;
+  }
+
+  push_type(spv::Op::OpTypeMatrix,
+            {result, Operand::Int(col_type_id), Operand::Int(mat->columns())});
+  return true;
+}
+
+bool Builder::GenerateStructType(ast::type::StructType* struct_type,
+                                 const Operand& result) {
+  auto struct_id = result.to_i();
+  auto impl = struct_type->impl();
+
+  std::vector<Operand> ops;
+  ops.push_back(result);
+
+  if (impl->decoration() == ast::StructDecoration::kBlock) {
+    push_annot(spv::Op::OpDecorate,
+               {Operand::Int(struct_id), Operand::Int(SpvDecorationBlock)});
+  } else {
+    if (impl->decoration() != ast::StructDecoration::kNone) {
+      error_ = "unknown struct decoration";
+      return false;
+    }
+  }
+
+  auto& members = impl->members();
+  for (uint32_t i = 0; i < members.size(); ++i) {
+    auto mem_id = GenerateStructMember(struct_id, i, members[i].get());
+    if (mem_id == 0) {
+      return false;
+    }
+
+    ops.push_back(Operand::Int(mem_id));
+  }
+
+  push_type(spv::Op::OpTypeStruct, std::move(ops));
+  return true;
+}
+
+uint32_t Builder::GenerateStructMember(uint32_t struct_id,
+                                       uint32_t idx,
+                                       ast::StructMember* member) {
+  push_debug(spv::Op::OpMemberName, {Operand::Int(struct_id), Operand::Int(idx),
+                                     Operand::String(member->name())});
+
+  for (const auto& deco : member->decorations()) {
+    if (deco->IsOffset()) {
+      push_annot(spv::Op::OpMemberDecorate,
+                 {Operand::Int(struct_id), Operand::Int(idx),
+                  Operand::Int(SpvDecorationOffset),
+                  Operand::Int(deco->AsOffset()->offset())});
+    } else {
+      error_ = "unknown struct member decoration";
+      return 0;
+    }
+  }
+
+  return GenerateTypeIfNeeded(member->type());
+}
+
+bool Builder::GenerateVectorType(ast::type::VectorType* vec,
+                                 const Operand& result) {
+  auto type_id = GenerateTypeIfNeeded(vec->type());
+  if (has_error()) {
+    return false;
+  }
+
+  push_type(spv::Op::OpTypeVector,
+            {result, Operand::Int(type_id), Operand::Int(vec->size())});
+  return true;
+}
+
 }  // namespace spirv
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index 7a42e3e..31efbde 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "src/ast/module.h"
+#include "src/ast/struct_member.h"
 #include "src/writer/spirv/instruction.h"
 
 namespace tint {
@@ -117,7 +118,7 @@
     annotations_.push_back(Instruction{op, operands});
   }
   /// @returns the annotations
-  const std::vector<Instruction>& annot() const { return annotations_; }
+  const std::vector<Instruction>& annots() const { return annotations_; }
 
   /// Generates an entry point instruction
   /// @param ep the entry point
@@ -130,6 +131,30 @@
   /// @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);
+  /// Generates a matrix type declaration
+  /// @param mat the matrix to generate
+  /// @param result the result operand
+  /// @returns true if the matrix was successfully generated
+  bool GenerateMatrixType(ast::type::MatrixType* mat, const Operand& result);
+  /// Generates a vector type declaration
+  /// @param struct_type the vector to generate
+  /// @param result the result operand
+  /// @returns true if the vector was successfully generated
+  bool GenerateStructType(ast::type::StructType* struct_type,
+                          const Operand& result);
+  /// Generates a vector type declaration
+  /// @param vec the vector to generate
+  /// @param result the result operand
+  /// @returns true if the vector was successfully generated
+  bool GenerateVectorType(ast::type::VectorType* vec, const Operand& result);
+  /// Generates a struct member
+  /// @param struct_id the id of the parent structure
+  /// @param idx the index of the member
+  /// @param member the member to generate
+  /// @returns the id of the struct member or 0 on error.
+  uint32_t GenerateStructMember(uint32_t struct_id,
+                                uint32_t idx,
+                                ast::StructMember* member);
 
  private:
   /// @returns an Operand with a new result ID in it. Increments the next_id_
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 014d8c1..7dca01c 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -15,6 +15,9 @@
 #include <memory>
 
 #include "gtest/gtest.h"
+#include "src/ast/struct.h"
+#include "src/ast/struct_member.h"
+#include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
@@ -22,6 +25,7 @@
 #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/struct_type.h"
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type/void_type.h"
@@ -171,6 +175,104 @@
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
+TEST_F(BuilderTest_Type, GenerateStruct_Empty) {
+  auto s = std::make_unique<ast::Struct>();
+  ast::type::StructType s_type(std::move(s));
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&s_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 = OpTypeStruct
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct) {
+  ast::type::F32Type f32;
+
+  std::vector<std::unique_ptr<ast::StructMemberDecoration>> decos;
+  std::vector<std::unique_ptr<ast::StructMember>> members;
+  members.push_back(
+      std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
+
+  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
+                                         std::move(members));
+  ast::type::StructType s_type(std::move(s));
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&s_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpMemberName %1 0 "a"
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_Decorated) {
+  ast::type::F32Type f32;
+
+  std::vector<std::unique_ptr<ast::StructMemberDecoration>> decos;
+  std::vector<std::unique_ptr<ast::StructMember>> members;
+  members.push_back(
+      std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
+
+  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kBlock,
+                                         std::move(members));
+  ast::type::StructType s_type(std::move(s));
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&s_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpMemberName %1 0 "a"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Block
+)");
+}
+
+TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
+  ast::type::F32Type f32;
+
+  std::vector<std::unique_ptr<ast::StructMemberDecoration>> a_decos;
+  a_decos.push_back(std::make_unique<ast::StructMemberOffsetDecoration>(0));
+  std::vector<std::unique_ptr<ast::StructMemberDecoration>> b_decos;
+  b_decos.push_back(std::make_unique<ast::StructMemberOffsetDecoration>(8));
+
+  std::vector<std::unique_ptr<ast::StructMember>> members;
+  members.push_back(
+      std::make_unique<ast::StructMember>("a", &f32, std::move(a_decos)));
+  members.push_back(
+      std::make_unique<ast::StructMember>("b", &f32, std::move(b_decos)));
+
+  auto s = std::make_unique<ast::Struct>(ast::StructDecoration::kNone,
+                                         std::move(members));
+  ast::type::StructType s_type(std::move(s));
+
+  Builder b;
+  auto id = b.GenerateTypeIfNeeded(&s_type);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+%1 = OpTypeStruct %2 %2
+)");
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 1 Offset 8
+)");
+}
+
 TEST_F(BuilderTest_Type, GenerateU32) {
   ast::type::U32Type u32;