[ir] Emit struct declarations in the disassembly

Bug: tint:1718
Change-Id: Iff88d362c20381280ec98f868107a1ecd144c01f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/136260
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 1d6b421..31e3868 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -42,6 +42,7 @@
 #include "src/tint/ir/user_call.h"
 #include "src/tint/ir/var.h"
 #include "src/tint/switch.h"
+#include "src/tint/type/struct.h"
 #include "src/tint/type/type.h"
 #include "src/tint/utils/scoped_assignment.h"
 #include "src/tint/utils/string.h"
@@ -95,6 +96,12 @@
 }
 
 std::string Disassembler::Disassemble() {
+    for (auto* ty : mod_.Types()) {
+        if (auto* str = ty->As<type::Struct>()) {
+            EmitStructDecl(str);
+        }
+    }
+
     if (mod_.root_block) {
         Indent() << "# Root block" << std::endl;
         WalkInternal(mod_.root_block);
@@ -669,4 +676,33 @@
     out_ << std::endl;
 }
 
+void Disassembler::EmitStructDecl(const type::Struct* str) {
+    out_ << str->Name().Name() << " = struct @align(" << str->Align() << ") {" << std::endl;
+    for (auto* member : str->Members()) {
+        out_ << "  " << member->Name().Name() << ":" << member->Type()->FriendlyName();
+        out_ << " @offset(" << member->Offset() << ")";
+        if (member->Attributes().invariant) {
+            out_ << ", @invariant";
+        }
+        if (member->Attributes().location.has_value()) {
+            out_ << ", @location(" << member->Attributes().location.value() << ")";
+        }
+        if (member->Attributes().interpolation.has_value()) {
+            auto& interp = member->Attributes().interpolation.value();
+            out_ << ", @interpolate(" << interp.type;
+            if (interp.sampling != builtin::InterpolationSampling::kUndefined) {
+                out_ << ", " << interp.sampling;
+            }
+            out_ << ")";
+        }
+        if (member->Attributes().builtin.has_value()) {
+            out_ << ", @builtin(" << member->Attributes().builtin.value() << ")";
+        }
+
+        out_ << std::endl;
+    }
+    out_ << "}" << std::endl;
+    out_ << std::endl;
+}
+
 }  // namespace tint::ir
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index bf2f8fa..ea27470 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -29,6 +29,11 @@
 #include "src/tint/utils/hashset.h"
 #include "src/tint/utils/string_stream.h"
 
+// Forward declarations.
+namespace tint::type {
+class Struct;
+}
+
 namespace tint::ir {
 
 /// Helper class to disassemble the IR
@@ -74,6 +79,7 @@
     void EmitSwitch(const Switch* s);
     void EmitLoop(const Loop* l);
     void EmitIf(const If* i);
+    void EmitStructDecl(const type::Struct* str);
 
     const Module& mod_;
     utils::StringStream out_;
diff --git a/src/tint/ir/from_program_accessor_test.cc b/src/tint/ir/from_program_accessor_test.cc
index 9da49a1..d8e6601 100644
--- a/src/tint/ir/from_program_accessor_test.cc
+++ b/src/tint/ir/from_program_accessor_test.cc
@@ -90,7 +90,11 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(MyStruct = struct @align(4) {
+  foo:i32 @offset(0)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %a:ptr<function, MyStruct, read_write> = var
     %3:ptr<function, i32, read_write> = access %a, 0u
@@ -122,7 +126,16 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(Inner = struct @align(4) {
+  bar:f32 @offset(0)
+}
+
+Outer = struct @align(4) {
+  a:i32 @offset(0)
+  foo:Inner @offset(4)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %a:ptr<function, Outer, read_write> = var
     %3:ptr<function, f32, read_write> = access %a, 1u, 0u
@@ -158,7 +171,18 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(Inner = struct @align(16) {
+  b:i32 @offset(0)
+  c:f32 @offset(4)
+  bar:vec4<f32> @offset(16)
+}
+
+Outer = struct @align(16) {
+  a:i32 @offset(0)
+  foo:array<Inner, 4> @offset(16)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %a:ptr<function, array<Outer, 4>, read_write> = var
     %3:ptr<function, vec4<f32>, read_write> = access %a, 0u, 1u, 1u, 2u
@@ -281,7 +305,12 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(MyStruct = struct @align(16) {
+  a:i32 @offset(0)
+  foo:vec4<f32> @offset(16)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %a:ptr<function, MyStruct, read_write> = var
     %3:ptr<function, vec4<f32>, read_write> = access %a, 1u
@@ -352,7 +381,11 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(MyStruct = struct @align(4) {
+  foo:i32 @offset(0)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %b:i32 = access MyStruct(0i), 0u
     ret
@@ -382,7 +415,16 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(Inner = struct @align(4) {
+  bar:f32 @offset(0)
+}
+
+Outer = struct @align(4) {
+  a:i32 @offset(0)
+  foo:Inner @offset(4)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %b:f32 = access Outer(0i, Inner(0.0f)), 1u, 0u
     ret
@@ -416,7 +458,18 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(Inner = struct @align(16) {
+  b:i32 @offset(0)
+  c:f32 @offset(4)
+  bar:vec4<f32> @offset(16)
+}
+
+Outer = struct @align(16) {
+  a:i32 @offset(0)
+  foo:array<Inner, 4> @offset(16)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %b:vec4<f32> = access array<Outer, 4>(Outer(0i, array<Inner, 4>(Inner(0i, 0.0f, vec4<f32>(0.0f))))), 0u, 1u, 1u, 2u
     ret
@@ -508,7 +561,12 @@
     ASSERT_TRUE(m) << (!m ? m.Failure() : "");
 
     EXPECT_EQ(Disassemble(m.Get()),
-              R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+              R"(MyStruct = struct @align(16) {
+  a:i32 @offset(0)
+  foo:vec4<f32> @offset(16)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
   %b1 = block {
     %2:vec4<f32> = access MyStruct(0i, vec4<f32>(0.0f)), 1u
     %3:vec3<f32> = swizzle %2, zyx
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index 1520888..caffbfc 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -67,6 +67,9 @@
     Symbol SetName(const Value* value, std::string_view name);
 
     /// @return the type manager for the module
+    const type::Manager& Types() const { return constant_values.types; }
+
+    /// @return the type manager for the module
     type::Manager& Types() { return constant_values.types; }
 
     /// The block allocator