tint: validate max number of members in a struct

Bug: tint:1209
Change-Id: I248c1864d3b38d41eda56bc41d7f19fb5fdd1955
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/121220
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/tint/ast/disable_validation_attribute.cc b/src/tint/ast/disable_validation_attribute.cc
index 465a0ec..eff1c1f 100644
--- a/src/tint/ast/disable_validation_attribute.cc
+++ b/src/tint/ast/disable_validation_attribute.cc
@@ -45,6 +45,8 @@
             return "disable_validation__ignore_invalid_pointer_argument";
         case DisabledValidation::kIgnorePointerAliasing:
             return "disable_validation__ignore_pointer_aliasing";
+        case DisabledValidation::kIgnoreStructMemberLimit:
+            return "disable_validation__ignore_struct_member";
     }
     return "<invalid>";
 }
diff --git a/src/tint/ast/disable_validation_attribute.h b/src/tint/ast/disable_validation_attribute.h
index f52b107..9614bc1 100644
--- a/src/tint/ast/disable_validation_attribute.h
+++ b/src/tint/ast/disable_validation_attribute.h
@@ -45,6 +45,8 @@
     /// When applied to a function declaration, the validator will not complain if multiple
     /// pointer arguments alias when that function is called.
     kIgnorePointerAliasing,
+    /// When applied to a struct, validation of max number of members is skipped.
+    kIgnoreStructMemberLimit,
 };
 
 /// An internal attribute used to tell the validator to ignore specific
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 7471466..4292438 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -3573,6 +3573,20 @@
 }
 
 sem::Struct* Resolver::Structure(const ast::Struct* str) {
+    if (validator_.IsValidationEnabled(str->attributes,
+                                       ast::DisabledValidation::kIgnoreStructMemberLimit)) {
+        // Maximum number of members in a structure type
+        // https://gpuweb.github.io/gpuweb/wgsl/#limits
+        const size_t kMaxNumStructMembers = 16383;
+        if (str->members.Length() > kMaxNumStructMembers) {
+            AddError("struct '" + builder_->Symbols().NameFor(str->name->symbol) + "' has " +
+                         std::to_string(str->members.Length()) + " members, maximum is " +
+                         std::to_string(kMaxNumStructMembers),
+                     str->source);
+            return nullptr;
+        }
+    }
+
     if (!validator_.NoDuplicateAttributes(str->attributes)) {
         return nullptr;
     }
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index c9ce479..25155ff 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -2394,5 +2394,45 @@
 
 #endif  // !defined(NDEBUG)
 
+const size_t kMaxNumStructMembers = 16383;
+
+TEST_F(ResolverTest, MaxNumStructMembers_Valid) {
+    utils::Vector<const ast::StructMember*, 0> members;
+    members.Reserve(kMaxNumStructMembers);
+    for (size_t i = 0; i < kMaxNumStructMembers; ++i) {
+        members.Push(Member("m" + std::to_string(i), ty.i32()));
+    }
+    Structure("S", std::move(members));
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverTest, MaxNumStructMembers_Invalid) {
+    utils::Vector<const ast::StructMember*, 0> members;
+    members.Reserve(kMaxNumStructMembers + 1);
+    for (size_t i = 0; i < kMaxNumStructMembers + 1; ++i) {
+        members.Push(Member("m" + std::to_string(i), ty.i32()));
+    }
+    Structure(Source{{12, 34}}, "S", std::move(members));
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: struct 'S' has 16384 members, maximum is 16383");
+}
+
+TEST_F(ResolverTest, MaxNumStructMembers_WithIgnoreStructMemberLimit_Valid) {
+    utils::Vector<const ast::StructMember*, 0> members;
+    members.Reserve(kMaxNumStructMembers);
+    for (size_t i = 0; i < kMaxNumStructMembers; ++i) {
+        members.Push(Member("m" + std::to_string(i), ty.i32()));
+    }
+
+    // Add 10 more members, but we set the limit to be ignored on the struct
+    for (size_t i = 0; i < 10; ++i) {
+        members.Push(Member("ignored" + std::to_string(i), ty.i32()));
+    }
+
+    Structure("S", std::move(members),
+              utils::Vector{Disable(ast::DisabledValidation::kIgnoreStructMemberLimit)});
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/transform/pad_structs.cc b/src/tint/transform/pad_structs.cc
index 79bc631..8516aa3 100644
--- a/src/tint/transform/pad_structs.cc
+++ b/src/tint/transform/pad_structs.cc
@@ -18,6 +18,7 @@
 #include <unordered_map>
 #include <utility>
 
+#include "src/tint/ast/disable_validation_attribute.h"
 #include "src/tint/ast/parameter.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
@@ -36,7 +37,10 @@
                    utils::Hashset<const ast::StructMember*, 8>* padding_members,
                    ProgramBuilder* b,
                    uint32_t bytes) {
-    for (uint32_t i = 0; i < bytes / 4u; ++i) {
+    const size_t count = bytes / 4u;
+    padding_members->Reserve(count);
+    new_members->Reserve(count);
+    for (uint32_t i = 0; i < count; ++i) {
         auto name = b->Symbols().New("pad");
         auto* member = b->Member(name, b->ty.u32());
         padding_members->Add(member);
@@ -99,8 +103,15 @@
         if (offset < struct_size && !has_runtime_sized_array) {
             CreatePadding(&new_members, &padding_members, ctx.dst, struct_size - offset);
         }
-        auto* new_struct =
-            b.create<ast::Struct>(ctx.Clone(ast_str->name), std::move(new_members), utils::Empty);
+
+        utils::Vector<const ast::Attribute*, 1> struct_attribs;
+        if (!padding_members.IsEmpty()) {
+            struct_attribs =
+                utils::Vector{b.Disable(ast::DisabledValidation::kIgnoreStructMemberLimit)};
+        }
+
+        auto* new_struct = b.create<ast::Struct>(ctx.Clone(ast_str->name), std::move(new_members),
+                                                 std::move(struct_attribs));
         replaced_structs[ast_str] = new_struct;
         return new_struct;
     });
diff --git a/src/tint/transform/pad_structs_test.cc b/src/tint/transform/pad_structs_test.cc
index 6b1a70f..0826467 100644
--- a/src/tint/transform/pad_structs_test.cc
+++ b/src/tint/transform/pad_structs_test.cc
@@ -47,6 +47,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   x : i32,
   pad : u32,
@@ -81,6 +82,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   x : i32,
   pad : u32,
@@ -118,6 +120,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   x : i32,
   pad : u32,
@@ -158,6 +161,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   x : i32,
   pad : u32,
@@ -198,6 +202,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   x : i32,
   pad : u32,
@@ -273,6 +278,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : i32,
   pad : u32,
@@ -320,6 +326,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : i32,
   pad : u32,
@@ -367,6 +374,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : i32,
   pad : u32,
@@ -501,6 +509,7 @@
   b : i32,
 }
 
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : vec4<f32>,
   b : array<T, 1u>,
@@ -537,6 +546,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : f32,
   pad : u32,
@@ -573,6 +583,7 @@
 }
 )";
     auto* expect = R"(
+@internal(disable_validation__ignore_struct_member)
 struct S {
   a : f32,
   pad : u32,