Factor out structure layout print function

Simplifies Resolver::ValidateStorageClassLayout and would allow us to
use this function for displaying structure layout in a language server,
for example.

Bug: tint:1348
Change-Id: I9d83329f0a168e5d8c094b3282d07cd1ab321dca
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/76080
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/resolver/resolver_validation.cc b/src/resolver/resolver_validation.cc
index bc70f94..1df63ba 100644
--- a/src/resolver/resolver_validation.cc
+++ b/src/resolver/resolver_validation.cc
@@ -15,8 +15,6 @@
 #include "src/resolver/resolver.h"
 
 #include <algorithm>
-#include <cmath>
-#include <iomanip>
 #include <limits>
 #include <utility>
 
@@ -257,87 +255,6 @@
     return builder_->Symbols().NameFor(sm->Declaration()->symbol);
   };
 
-  auto type_name_of = [this](const sem::StructMember* sm) {
-    return TypeNameOf(sm->Type());
-  };
-
-  // TODO(amaiorano): Output struct and member decorations so that this output
-  // can be copied verbatim back into source
-  auto get_struct_layout_string = [this, member_name_of, type_name_of](
-                                      const sem::Struct* st) -> std::string {
-    std::stringstream ss;
-
-    if (st->Members().empty()) {
-      TINT_ICE(Resolver, diagnostics_) << "Validation should have ensured that "
-                                          "structs have at least one member";
-      return {};
-    }
-    const auto* const last_member = st->Members().back();
-    const uint32_t last_member_struct_padding_offset =
-        last_member->Offset() + last_member->Size();
-
-    // Compute max widths to align output
-    const auto offset_w =
-        static_cast<int>(::log10(last_member_struct_padding_offset)) + 1;
-    const auto size_w = static_cast<int>(::log10(st->Size())) + 1;
-    const auto align_w = static_cast<int>(::log10(st->Align())) + 1;
-
-    auto print_struct_begin_line = [&](size_t align, size_t size,
-                                       std::string struct_name) {
-      ss << "/*          " << std::setw(offset_w) << " "
-         << "align(" << std::setw(align_w) << align << ") size("
-         << std::setw(size_w) << size << ") */ struct " << struct_name
-         << " {\n";
-    };
-
-    auto print_struct_end_line = [&]() {
-      ss << "/*                         "
-         << std::setw(offset_w + size_w + align_w) << " "
-         << "*/ };";
-    };
-
-    auto print_member_line = [&](size_t offset, size_t align, size_t size,
-                                 std::string s) {
-      ss << "/* offset(" << std::setw(offset_w) << offset << ") align("
-         << std::setw(align_w) << align << ") size(" << std::setw(size_w)
-         << size << ") */   " << s << ";\n";
-    };
-
-    print_struct_begin_line(st->Align(), st->Size(), TypeNameOf(st));
-
-    for (size_t i = 0; i < st->Members().size(); ++i) {
-      auto* const m = st->Members()[i];
-
-      // Output field alignment padding, if any
-      auto* const prev_member = (i == 0) ? nullptr : st->Members()[i - 1];
-      if (prev_member) {
-        uint32_t padding =
-            m->Offset() - (prev_member->Offset() + prev_member->Size());
-        if (padding > 0) {
-          size_t padding_offset = m->Offset() - padding;
-          print_member_line(padding_offset, 1, padding,
-                            "// -- implicit field alignment padding --");
-        }
-      }
-
-      // Output member
-      std::string member_name = member_name_of(m);
-      print_member_line(m->Offset(), m->Align(), m->Size(),
-                        member_name_of(m) + " : " + type_name_of(m));
-    }
-
-    // Output struct size padding, if any
-    uint32_t struct_padding = st->Size() - last_member_struct_padding_offset;
-    if (struct_padding > 0) {
-      print_member_line(last_member_struct_padding_offset, 1, struct_padding,
-                        "// -- implicit struct size padding --");
-    }
-
-    print_struct_end_line();
-
-    return ss.str();
-  };
-
   if (!ast::IsHostShareable(sc)) {
     return true;
   }
@@ -348,7 +265,8 @@
 
     // Validate that member is at a valid byte offset
     if (m->Offset() % required_align != 0) {
-      AddError("the offset of a struct member of type '" + type_name_of(m) +
+      AddError("the offset of a struct member of type '" +
+                   m->Type()->UnwrapRef()->FriendlyName(builder_->Symbols()) +
                    "' in storage class '" + ast::ToString(sc) +
                    "' must be a multiple of " + std::to_string(required_align) +
                    " bytes, but '" + member_name_of(m) +
@@ -357,12 +275,12 @@
                    std::to_string(required_align) + ")]] on this member",
                m->Declaration()->source);
 
-      AddNote("see layout of struct:\n" + get_struct_layout_string(str),
+      AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
               str->Declaration()->source);
 
       if (auto* member_str = m->Type()->As<sem::Struct>()) {
         AddNote("and layout of struct member:\n" +
-                    get_struct_layout_string(member_str),
+                    member_str->Layout(builder_->Symbols()),
                 member_str->Declaration()->source);
       }
 
@@ -384,12 +302,12 @@
                 "'. Consider setting [[align(16)]] on this member",
             m->Declaration()->source);
 
-        AddNote("see layout of struct:\n" + get_struct_layout_string(str),
+        AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
                 str->Declaration()->source);
 
         auto* prev_member_str = prev_member->Type()->As<sem::Struct>();
         AddNote("and layout of previous member struct:\n" +
-                    get_struct_layout_string(prev_member_str),
+                    prev_member_str->Layout(builder_->Symbols()),
                 prev_member_str->Declaration()->source);
         return false;
       }
@@ -413,7 +331,7 @@
                       utils::RoundUp(required_align, arr->Stride())) +
                   ")]] on the array type",
               m->Declaration()->type->source);
-          AddNote("see layout of struct:\n" + get_struct_layout_string(str),
+          AddNote("see layout of struct:\n" + str->Layout(builder_->Symbols()),
                   str->Declaration()->source);
           return false;
         }
diff --git a/src/sem/sem_struct_test.cc b/src/sem/sem_struct_test.cc
index 104df11..ca1c8e4 100644
--- a/src/sem/sem_struct_test.cc
+++ b/src/sem/sem_struct_test.cc
@@ -56,6 +56,47 @@
   EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
+TEST_F(StructTest, Layout) {
+  auto* inner_st =  //
+      Structure("Inner", {
+                             Member("a", ty.i32()),
+                             Member("b", ty.u32()),
+                             Member("c", ty.f32()),
+                             Member("d", ty.vec3<f32>()),
+                             Member("e", ty.mat4x2<f32>()),
+                         });
+
+  auto* outer_st =
+      Structure("Outer", {
+                             Member("inner", ty.type_name("Inner")),
+                             Member("a", ty.i32()),
+                         });
+
+  auto p = Build();
+  ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+
+  auto* sem_inner_st = p.Sem().Get(inner_st);
+  auto* sem_outer_st = p.Sem().Get(outer_st);
+
+  EXPECT_EQ(sem_inner_st->Layout(p.Symbols()),
+            R"(/*            align(16) size(64) */ struct Inner {
+/* offset( 0) align( 4) size( 4) */   a : i32;
+/* offset( 4) align( 4) size( 4) */   b : u32;
+/* offset( 8) align( 4) size( 4) */   c : f32;
+/* offset(12) align( 1) size( 4) */   // -- implicit field alignment padding --;
+/* offset(16) align(16) size(12) */   d : vec3<f32>;
+/* offset(28) align( 1) size( 4) */   // -- implicit field alignment padding --;
+/* offset(32) align( 8) size(32) */   e : mat4x2<f32>;
+/*                               */ };)");
+
+  EXPECT_EQ(sem_outer_st->Layout(p.Symbols()),
+            R"(/*            align(16) size(80) */ struct Outer {
+/* offset( 0) align(16) size(64) */   inner : Inner;
+/* offset(64) align( 4) size( 4) */   a : i32;
+/* offset(68) align( 1) size(12) */   // -- implicit struct size padding --;
+/*                               */ };)");
+}
+
 }  // namespace
 }  // namespace sem
 }  // namespace tint
diff --git a/src/sem/struct.cc b/src/sem/struct.cc
index d764115..796eb39 100644
--- a/src/sem/struct.cc
+++ b/src/sem/struct.cc
@@ -14,6 +14,8 @@
 
 #include "src/sem/struct.h"
 
+#include <cmath>
+#include <iomanip>
 #include <string>
 #include <utility>
 
@@ -74,6 +76,82 @@
   return symbols.NameFor(name_);
 }
 
+std::string Struct::Layout(const tint::SymbolTable& symbols) const {
+  std::stringstream ss;
+
+  auto member_name_of = [&](const sem::StructMember* sm) {
+    return symbols.NameFor(sm->Declaration()->symbol);
+  };
+
+  if (Members().empty()) {
+    return {};
+  }
+  const auto* const last_member = Members().back();
+  const uint32_t last_member_struct_padding_offset =
+      last_member->Offset() + last_member->Size();
+
+  // Compute max widths to align output
+  const auto offset_w =
+      static_cast<int>(::log10(last_member_struct_padding_offset)) + 1;
+  const auto size_w = static_cast<int>(::log10(Size())) + 1;
+  const auto align_w = static_cast<int>(::log10(Align())) + 1;
+
+  auto print_struct_begin_line = [&](size_t align, size_t size,
+                                     std::string struct_name) {
+    ss << "/*          " << std::setw(offset_w) << " "
+       << "align(" << std::setw(align_w) << align << ") size("
+       << std::setw(size_w) << size << ") */ struct " << struct_name << " {\n";
+  };
+
+  auto print_struct_end_line = [&]() {
+    ss << "/*                         "
+       << std::setw(offset_w + size_w + align_w) << " "
+       << "*/ };";
+  };
+
+  auto print_member_line = [&](size_t offset, size_t align, size_t size,
+                               std::string s) {
+    ss << "/* offset(" << std::setw(offset_w) << offset << ") align("
+       << std::setw(align_w) << align << ") size(" << std::setw(size_w) << size
+       << ") */   " << s << ";\n";
+  };
+
+  print_struct_begin_line(Align(), Size(), UnwrapRef()->FriendlyName(symbols));
+
+  for (size_t i = 0; i < Members().size(); ++i) {
+    auto* const m = Members()[i];
+
+    // Output field alignment padding, if any
+    auto* const prev_member = (i == 0) ? nullptr : Members()[i - 1];
+    if (prev_member) {
+      uint32_t padding =
+          m->Offset() - (prev_member->Offset() + prev_member->Size());
+      if (padding > 0) {
+        size_t padding_offset = m->Offset() - padding;
+        print_member_line(padding_offset, 1, padding,
+                          "// -- implicit field alignment padding --");
+      }
+    }
+
+    // Output member
+    std::string member_name = member_name_of(m);
+    print_member_line(
+        m->Offset(), m->Align(), m->Size(),
+        member_name + " : " + m->Type()->UnwrapRef()->FriendlyName(symbols));
+  }
+
+  // Output struct size padding, if any
+  uint32_t struct_padding = Size() - last_member_struct_padding_offset;
+  if (struct_padding > 0) {
+    print_member_line(last_member_struct_padding_offset, 1, struct_padding,
+                      "// -- implicit struct size padding --");
+  }
+
+  print_struct_end_line();
+
+  return ss.str();
+}
+
 bool Struct::IsConstructible() const {
   return constructible_;
 }
diff --git a/src/sem/struct.h b/src/sem/struct.h
index d7eab4b..3c42c6c 100644
--- a/src/sem/struct.h
+++ b/src/sem/struct.h
@@ -150,6 +150,11 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
+  /// @param symbols the program's symbol table
+  /// @returns a multiline string that describes the layout of this struct,
+  /// including size and alignment information.
+  std::string Layout(const tint::SymbolTable& symbols) const;
+
   /// @returns true if constructible as per
   /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
   bool IsConstructible() const override;