[ir][spirv-writer] Emit struct types

Emit offset decorations for each struct member. If a struct member is
a matrix type (or contains one via nested array elements), emit matrix
layout decorations as well.

Bug: tint:1906
Change-Id: I838bb25977435bb9e901c7d9bcdef4c87eac3e27
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135581
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 5b12094..89e6a1f 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -47,6 +47,7 @@
 #include "src/tint/type/i32.h"
 #include "src/tint/type/matrix.h"
 #include "src/tint/type/pointer.h"
+#include "src/tint/type/struct.h"
 #include "src/tint/type/type.h"
 #include "src/tint/type/u32.h"
 #include "src/tint/type/vector.h"
@@ -225,6 +226,7 @@
                     spv::Op::OpTypePointer,
                     {id, U32Operand(StorageClass(ptr->AddressSpace())), Type(ptr->StoreType())});
             },
+            [&](const type::Struct* str) { EmitStructType(id, str); },
             [&](Default) {
                 TINT_ICE(Writer, diagnostics_) << "unhandled type: " << ty->FriendlyName();
             });
@@ -245,6 +247,47 @@
     return block_labels_.GetOrCreate(block, [&]() { return module_.NextId(); });
 }
 
+void GeneratorImplIr::EmitStructType(uint32_t id, const type::Struct* str) {
+    // Helper to return `type` or a potentially nested array element type within `type` as a matrix
+    // type, or nullptr if no such matrix type is present.
+    auto get_nested_matrix_type = [&](const type::Type* type) {
+        while (auto* arr = type->As<type::Array>()) {
+            type = arr->ElemType();
+        }
+        return type->As<type::Matrix>();
+    };
+
+    OperandList operands = {id};
+    for (auto* member : str->Members()) {
+        operands.push_back(Type(member->Type()));
+
+        // Generate struct member offset decoration.
+        module_.PushAnnot(
+            spv::Op::OpMemberDecorate,
+            {operands[0], member->Index(), U32Operand(SpvDecorationOffset), member->Offset()});
+
+        // Emit matrix layout decorations if necessary.
+        if (auto* matrix_type = get_nested_matrix_type(member->Type())) {
+            const uint32_t effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
+            module_.PushAnnot(spv::Op::OpMemberDecorate,
+                              {id, member->Index(), U32Operand(SpvDecorationColMajor)});
+            module_.PushAnnot(spv::Op::OpMemberDecorate,
+                              {id, member->Index(), U32Operand(SpvDecorationMatrixStride),
+                               Operand(effective_row_count * matrix_type->type()->Size())});
+        }
+
+        if (member->Name().IsValid()) {
+            module_.PushDebug(spv::Op::OpMemberName,
+                              {operands[0], member->Index(), Operand(member->Name().Name())});
+        }
+    }
+    module_.PushType(spv::Op::OpTypeStruct, std::move(operands));
+
+    if (str->Name().IsValid()) {
+        module_.PushDebug(spv::Op::OpName, {operands[0], Operand(str->Name().Name())});
+    }
+}
+
 void GeneratorImplIr::EmitFunction(const ir::Function* func) {
     auto id = Value(func);
 
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.h b/src/tint/writer/spirv/ir/generator_impl_ir.h
index 5ce5838..7c3105b 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.h
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.h
@@ -46,6 +46,7 @@
 class Var;
 }  // namespace tint::ir
 namespace tint::type {
+class Struct;
 class Type;
 }  // namespace tint::type
 
@@ -97,6 +98,11 @@
     /// @returns the ID of the block's label
     uint32_t Label(const ir::Block* block);
 
+    /// Emit a struct type.
+    /// @param id the result ID to use
+    /// @param str the struct type to emit
+    void EmitStructType(uint32_t id, const type::Struct* str);
+
     /// Emit a function.
     /// @param func the function to emit
     void EmitFunction(const ir::Function* func);
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
index 284d421..d6c654f 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
@@ -185,6 +185,76 @@
     EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), "OpDecorate %1 ArrayStride 16\n");
 }
 
+TEST_F(SpvGeneratorImplTest, Type_Struct) {
+    auto* str = mod.Types().Get<type::Struct>(
+        mod.symbols.Register("MyStruct"),
+        utils::Vector{
+            mod.Types().Get<type::StructMember>(mod.symbols.Register("a"), mod.Types().f32(), 0u,
+                                                0u, 4u, 4u, type::StructMemberAttributes{}),
+            mod.Types().Get<type::StructMember>(mod.symbols.Register("b"),
+                                                mod.Types().vec4(mod.Types().i32()), 1u, 16u, 16u,
+                                                16u, type::StructMemberAttributes{}),
+        },
+        16u, 32u, 32u);
+    auto id = generator_.Type(str);
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 32
+%4 = OpTypeInt 32 1
+%3 = OpTypeVector %4 4
+%1 = OpTypeStruct %2 %3
+)");
+    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 1 Offset 16
+)");
+    EXPECT_EQ(DumpInstructions(generator_.Module().Debug()), R"(OpMemberName %1 0 "a"
+OpMemberName %1 1 "b"
+OpName %1 "MyStruct"
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_Struct_MatrixLayout) {
+    auto* str = mod.Types().Get<type::Struct>(
+        mod.symbols.Register("MyStruct"),
+        utils::Vector{
+            mod.Types().Get<type::StructMember>(mod.symbols.Register("m"),
+                                                mod.Types().mat3x3(mod.Types().f32()), 0u, 0u, 16u,
+                                                48u, type::StructMemberAttributes{}),
+            // Matrices nested inside arrays need layout decorations on the struct member too.
+            mod.Types().Get<type::StructMember>(
+                mod.symbols.Register("arr"),
+                mod.Types().array(mod.Types().array(mod.Types().mat2x4(mod.Types().f16()), 4), 4),
+                1u, 64u, 8u, 64u, type::StructMemberAttributes{}),
+        },
+        16u, 128u, 128u);
+    auto id = generator_.Type(str);
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), R"(%4 = OpTypeFloat 32
+%3 = OpTypeVector %4 3
+%2 = OpTypeMatrix %3 3
+%9 = OpTypeFloat 16
+%8 = OpTypeVector %9 4
+%7 = OpTypeMatrix %8 2
+%11 = OpTypeInt 32 0
+%10 = OpConstant %11 4
+%6 = OpTypeArray %7 %10
+%5 = OpTypeArray %6 %10
+%1 = OpTypeStruct %2 %5
+)");
+    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 ColMajor
+OpMemberDecorate %1 0 MatrixStride 16
+OpDecorate %6 ArrayStride 16
+OpDecorate %5 ArrayStride 64
+OpMemberDecorate %1 1 Offset 64
+OpMemberDecorate %1 1 ColMajor
+OpMemberDecorate %1 1 MatrixStride 8
+)");
+    EXPECT_EQ(DumpInstructions(generator_.Module().Debug()), R"(OpMemberName %1 0 "m"
+OpMemberName %1 1 "arr"
+OpName %1 "MyStruct"
+)");
+}
+
 // Test that we can emit multiple types.
 // Includes types with the same opcode but different parameters.
 TEST_F(SpvGeneratorImplTest, Type_Multiple) {