tint/resolver: Validate @size only on creation-fixed footprint member types.

Fixed: tint:1623
Change-Id: Id553dfe57effb4084a16fd6e6b02ad0cef85f836
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106224
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 212f594..86d6825 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -809,11 +809,25 @@
 TEST_F(StructMemberAttributeTest, Size_Attribute_Override) {
     Override("val", ty.f32(), Expr(1.23_f));
 
-    Structure("mystruct", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize("val")})});
+    Structure("mystruct",
+              utils::Vector{
+                  Member("a", ty.f32(), utils::Vector{MemberSize(Expr(Source{{12, 34}}, "val"))}),
+              });
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(
         r()->error(),
-        R"(error: @size requires a const-expression, but expression is an override-expression)");
+        R"(12:34 error: @size requires a const-expression, but expression is an override-expression)");
+}
+
+TEST_F(StructMemberAttributeTest, Size_On_RuntimeSizedArray) {
+    Structure("mystruct",
+              utils::Vector{
+                  Member("a", ty.array<i32>(), utils::Vector{MemberSize(Source{{12, 34}}, 8_a)}),
+              });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: @size can only be applied to members where the member's type size can be fully determined at shader creation time)");
 }
 
 }  // namespace StructAndStructMemberTests
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 6dab977..7f1cecf 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -2186,46 +2186,69 @@
         const ast::InvariantAttribute* invariant_attribute = nullptr;
         const ast::InterpolateAttribute* interpolate_attribute = nullptr;
         for (auto* attr : member->Declaration()->attributes) {
-            if (!attr->IsAnyOf<ast::BuiltinAttribute,             //
-                               ast::InternalAttribute,            //
-                               ast::InterpolateAttribute,         //
-                               ast::InvariantAttribute,           //
-                               ast::LocationAttribute,            //
-                               ast::StructMemberOffsetAttribute,  //
-                               ast::StructMemberSizeAttribute,    //
-                               ast::StructMemberAlignAttribute>()) {
-                if (attr->Is<ast::StrideAttribute>() &&
-                    IsValidationDisabled(member->Declaration()->attributes,
-                                         ast::DisabledValidation::kIgnoreStrideAttribute)) {
-                    continue;
-                }
-                AddError("attribute is not valid for structure members", attr->source);
+            bool ok = Switch(
+                attr,  //
+                [&](const ast::InvariantAttribute* invariant) {
+                    invariant_attribute = invariant;
+                    return true;
+                },
+                [&](const ast::LocationAttribute* location) {
+                    has_location = true;
+                    TINT_ASSERT(Resolver, member->Location().has_value());
+                    if (!LocationAttribute(location, member->Location().value(), member->Type(),
+                                           locations, stage, member->Declaration()->source)) {
+                        return false;
+                    }
+                    return true;
+                },
+                [&](const ast::BuiltinAttribute* builtin) {
+                    if (!BuiltinAttribute(builtin, member->Type(), stage,
+                                          /* is_input */ false)) {
+                        return false;
+                    }
+                    if (builtin->builtin == ast::BuiltinValue::kPosition) {
+                        has_position = true;
+                    }
+                    return true;
+                },
+                [&](const ast::InterpolateAttribute* interpolate) {
+                    interpolate_attribute = interpolate;
+                    if (!InterpolateAttribute(interpolate, member->Type())) {
+                        return false;
+                    }
+                    return true;
+                },
+                [&](const ast::StructMemberSizeAttribute*) {
+                    if (!member->Type()->HasCreationFixedFootprint()) {
+                        AddError(
+                            "@size can only be applied to members where the member's type size "
+                            "can be fully determined at shader creation time",
+                            attr->source);
+                        return false;
+                    }
+                    return true;
+                },
+                [&](Default) {
+                    if (!attr->IsAnyOf<ast::BuiltinAttribute,             //
+                                       ast::InternalAttribute,            //
+                                       ast::InterpolateAttribute,         //
+                                       ast::InvariantAttribute,           //
+                                       ast::LocationAttribute,            //
+                                       ast::StructMemberOffsetAttribute,  //
+                                       ast::StructMemberAlignAttribute>()) {
+                        if (attr->Is<ast::StrideAttribute>() &&
+                            IsValidationDisabled(member->Declaration()->attributes,
+                                                 ast::DisabledValidation::kIgnoreStrideAttribute)) {
+                            return true;
+                        }
+                        AddError("attribute is not valid for structure members", attr->source);
+                        return false;
+                    }
+                    return true;
+                });
+            if (!ok) {
                 return false;
             }
-
-            if (auto* invariant = attr->As<ast::InvariantAttribute>()) {
-                invariant_attribute = invariant;
-            } else if (auto* location = attr->As<ast::LocationAttribute>()) {
-                has_location = true;
-                TINT_ASSERT(Resolver, member->Location().has_value());
-                if (!LocationAttribute(location, member->Location().value(), member->Type(),
-                                       locations, stage, member->Declaration()->source)) {
-                    return false;
-                }
-            } else if (auto* builtin = attr->As<ast::BuiltinAttribute>()) {
-                if (!BuiltinAttribute(builtin, member->Type(), stage,
-                                      /* is_input */ false)) {
-                    return false;
-                }
-                if (builtin->builtin == ast::BuiltinValue::kPosition) {
-                    has_position = true;
-                }
-            } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-                interpolate_attribute = interpolate;
-                if (!InterpolateAttribute(interpolate, member->Type())) {
-                    return false;
-                }
-            }
         }
 
         if (invariant_attribute && !has_position) {
diff --git a/src/tint/sem/abstract_numeric.cc b/src/tint/sem/abstract_numeric.cc
index 39615cd..eea9abe 100644
--- a/src/tint/sem/abstract_numeric.cc
+++ b/src/tint/sem/abstract_numeric.cc
@@ -18,7 +18,12 @@
 
 namespace tint::sem {
 
-AbstractNumeric::AbstractNumeric() : Base(TypeFlags{Flag::kConstructable}) {}
+AbstractNumeric::AbstractNumeric()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 AbstractNumeric::AbstractNumeric(AbstractNumeric&&) = default;
 AbstractNumeric::~AbstractNumeric() = default;
 
diff --git a/src/tint/sem/array.cc b/src/tint/sem/array.cc
index ed95e62..1cd6515 100644
--- a/src/tint/sem/array.cc
+++ b/src/tint/sem/array.cc
@@ -35,6 +35,15 @@
         if (element->IsConstructible()) {
             flags.Add(TypeFlag::kConstructable);
         }
+        if (element->HasCreationFixedFootprint()) {
+            flags.Add(TypeFlag::kCreationFixedFootprint);
+        }
+    }
+    if (std::holds_alternative<ConstantArrayCount>(count) ||
+        std::holds_alternative<OverrideArrayCount>(count)) {
+        if (element->HasFixedFootprint()) {
+            flags.Add(TypeFlag::kFixedFootprint);
+        }
     }
     return flags;
 }
diff --git a/src/tint/sem/array_test.cc b/src/tint/sem/array_test.cc
index e6efff2..51c1894 100644
--- a/src/tint/sem/array_test.cc
+++ b/src/tint/sem/array_test.cc
@@ -135,5 +135,25 @@
     EXPECT_FALSE(runtime_sized->IsConstructible());
 }
 
+TEST_F(ArrayTest, HasCreationFixedFootprint) {
+    auto* fixed_sized = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
+    auto* override_sized = create<Array>(create<U32>(), OverrideArrayCount{}, 4u, 8u, 32u, 16u);
+    auto* runtime_sized = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 16u);
+
+    EXPECT_TRUE(fixed_sized->HasCreationFixedFootprint());
+    EXPECT_FALSE(override_sized->HasCreationFixedFootprint());
+    EXPECT_FALSE(runtime_sized->HasCreationFixedFootprint());
+}
+
+TEST_F(ArrayTest, HasFixedFootprint) {
+    auto* fixed_sized = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
+    auto* override_sized = create<Array>(create<U32>(), OverrideArrayCount{}, 4u, 8u, 32u, 16u);
+    auto* runtime_sized = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 16u);
+
+    EXPECT_TRUE(fixed_sized->HasFixedFootprint());
+    EXPECT_TRUE(override_sized->HasFixedFootprint());
+    EXPECT_FALSE(runtime_sized->HasFixedFootprint());
+}
+
 }  // namespace
 }  // namespace tint::sem
diff --git a/src/tint/sem/atomic.cc b/src/tint/sem/atomic.cc
index 965ebe2..f8cf068 100644
--- a/src/tint/sem/atomic.cc
+++ b/src/tint/sem/atomic.cc
@@ -22,7 +22,12 @@
 
 namespace tint::sem {
 
-Atomic::Atomic(const sem::Type* subtype) : Base(TypeFlags{}), subtype_(subtype) {
+Atomic::Atomic(const sem::Type* subtype)
+    : Base(TypeFlags{
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }),
+      subtype_(subtype) {
     TINT_ASSERT(AST, !subtype->Is<Reference>());
 }
 
diff --git a/src/tint/sem/bool.cc b/src/tint/sem/bool.cc
index 8d3d1bf..fea6679 100644
--- a/src/tint/sem/bool.cc
+++ b/src/tint/sem/bool.cc
@@ -20,7 +20,12 @@
 
 namespace tint::sem {
 
-Bool::Bool() : Base(TypeFlags{Flag::kConstructable}) {}
+Bool::Bool()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 
 Bool::Bool(Bool&&) = default;
 
diff --git a/src/tint/sem/f16.cc b/src/tint/sem/f16.cc
index 0595821..158b826 100644
--- a/src/tint/sem/f16.cc
+++ b/src/tint/sem/f16.cc
@@ -21,7 +21,12 @@
 namespace tint {
 namespace sem {
 
-F16::F16() : Base(TypeFlags{Flag::kConstructable}) {}
+F16::F16()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 
 F16::F16(F16&&) = default;
 
diff --git a/src/tint/sem/f32.cc b/src/tint/sem/f32.cc
index c1f99eb..73c98e6 100644
--- a/src/tint/sem/f32.cc
+++ b/src/tint/sem/f32.cc
@@ -20,7 +20,12 @@
 
 namespace tint::sem {
 
-F32::F32() : Base(TypeFlags{Flag::kConstructable}) {}
+F32::F32()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 
 F32::F32(F32&&) = default;
 
diff --git a/src/tint/sem/i32.cc b/src/tint/sem/i32.cc
index 8047c29..6b23155 100644
--- a/src/tint/sem/i32.cc
+++ b/src/tint/sem/i32.cc
@@ -20,7 +20,12 @@
 
 namespace tint::sem {
 
-I32::I32() : Base(TypeFlags{Flag::kConstructable}) {}
+I32::I32()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 
 I32::I32(I32&&) = default;
 
diff --git a/src/tint/sem/matrix.cc b/src/tint/sem/matrix.cc
index 5a3eb09..1d0453d 100644
--- a/src/tint/sem/matrix.cc
+++ b/src/tint/sem/matrix.cc
@@ -23,7 +23,11 @@
 namespace tint::sem {
 
 Matrix::Matrix(const Vector* column_type, uint32_t columns)
-    : Base(TypeFlags{Flag::kConstructable}),
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }),
       subtype_(column_type->type()),
       column_type_(column_type),
       rows_(column_type->Width()),
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
index 4a0a3c1..d80a04a 100644
--- a/src/tint/sem/struct.cc
+++ b/src/tint/sem/struct.cc
@@ -30,11 +30,20 @@
 namespace {
 
 TypeFlags FlagsFrom(const StructMemberList& members) {
-    TypeFlags flags{TypeFlag::kConstructable};
+    TypeFlags flags{
+        TypeFlag::kConstructable,
+        TypeFlag::kCreationFixedFootprint,
+        TypeFlag::kFixedFootprint,
+    };
     for (auto* member : members) {
         if (!member->Type()->IsConstructible()) {
             flags.Remove(TypeFlag::kConstructable);
-            break;
+        }
+        if (!member->Type()->HasFixedFootprint()) {
+            flags.Remove(TypeFlag::kFixedFootprint);
+        }
+        if (!member->Type()->HasCreationFixedFootprint()) {
+            flags.Remove(TypeFlag::kCreationFixedFootprint);
         }
     }
     return flags;
diff --git a/src/tint/sem/struct_test.cc b/src/tint/sem/struct_test.cc
index 12ded84..a8b36ca 100644
--- a/src/tint/sem/struct_test.cc
+++ b/src/tint/sem/struct_test.cc
@@ -157,5 +157,71 @@
     EXPECT_FALSE(sem_outer_runtime_sized_array->IsConstructible());
 }
 
+TEST_F(StructTest, HasCreationFixedFootprint) {
+    auto* inner =  //
+        Structure("Inner", utils::Vector{
+                               Member("a", ty.i32()),
+                               Member("b", ty.u32()),
+                               Member("c", ty.f32()),
+                               Member("d", ty.vec3<f32>()),
+                               Member("e", ty.mat4x2<f32>()),
+                               Member("f", ty.array<f32, 32>()),
+                           });
+
+    auto* outer = Structure("Outer", utils::Vector{
+                                         Member("inner", ty.type_name("Inner")),
+                                     });
+
+    auto* outer_with_runtime_sized_array =
+        Structure("OuterRuntimeSizedArray", utils::Vector{
+                                                Member("inner", ty.type_name("Inner")),
+                                                Member("runtime_sized_array", ty.array<i32>()),
+                                            });
+
+    auto p = Build();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+
+    auto* sem_inner = p.Sem().Get(inner);
+    auto* sem_outer = p.Sem().Get(outer);
+    auto* sem_outer_with_runtime_sized_array = p.Sem().Get(outer_with_runtime_sized_array);
+
+    EXPECT_TRUE(sem_inner->HasCreationFixedFootprint());
+    EXPECT_TRUE(sem_outer->HasCreationFixedFootprint());
+    EXPECT_FALSE(sem_outer_with_runtime_sized_array->HasCreationFixedFootprint());
+}
+
+TEST_F(StructTest, HasFixedFootprint) {
+    auto* inner =  //
+        Structure("Inner", utils::Vector{
+                               Member("a", ty.i32()),
+                               Member("b", ty.u32()),
+                               Member("c", ty.f32()),
+                               Member("d", ty.vec3<f32>()),
+                               Member("e", ty.mat4x2<f32>()),
+                               Member("f", ty.array<f32, 32>()),
+                           });
+
+    auto* outer = Structure("Outer", utils::Vector{
+                                         Member("inner", ty.type_name("Inner")),
+                                     });
+
+    auto* outer_with_runtime_sized_array =
+        Structure("OuterRuntimeSizedArray", utils::Vector{
+                                                Member("inner", ty.type_name("Inner")),
+                                                Member("runtime_sized_array", ty.array<i32>()),
+                                            });
+
+    auto p = Build();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+
+    auto* sem_inner = p.Sem().Get(inner);
+    auto* sem_outer = p.Sem().Get(outer);
+    auto* sem_outer_with_runtime_sized_array = p.Sem().Get(outer_with_runtime_sized_array);
+
+    EXPECT_TRUE(sem_inner->HasFixedFootprint());
+    EXPECT_TRUE(sem_outer->HasFixedFootprint());
+    EXPECT_FALSE(sem_outer_with_runtime_sized_array->HasFixedFootprint());
+}
+
 }  // namespace
 }  // namespace tint::sem
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
index 7191fc6..0596a37 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/sem/type.cc
@@ -34,7 +34,11 @@
 
 namespace tint::sem {
 
-Type::Type(TypeFlags flags) : flags_(flags) {}
+Type::Type(TypeFlags flags) : flags_(flags) {
+    if (IsConstructible()) {
+        TINT_ASSERT(Semantic, HasCreationFixedFootprint());
+    }
+}
 
 Type::Type(Type&&) = default;
 
diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h
index 3b68946..38f5ba8 100644
--- a/src/tint/sem/type.h
+++ b/src/tint/sem/type.h
@@ -34,6 +34,12 @@
     /// Type is constructable.
     /// @see https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
     kConstructable,
+    /// Type has a creation-fixed footprint.
+    /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types
+    kCreationFixedFootprint,
+    /// Type has a fixed footprint.
+    /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types
+    kFixedFootprint,
 };
 
 /// An alias to utils::EnumSet<TypeFlag>
@@ -83,6 +89,16 @@
     /// https://gpuweb.github.io/gpuweb/wgsl/#constructible-types
     inline bool IsConstructible() const { return flags_.Contains(Flag::kConstructable); }
 
+    /// @returns true has a creation-fixed footprint.
+    /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types
+    inline bool HasCreationFixedFootprint() const {
+        return flags_.Contains(Flag::kCreationFixedFootprint);
+    }
+
+    /// @returns true has a fixed footprint.
+    /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types
+    inline bool HasFixedFootprint() const { return flags_.Contains(Flag::kFixedFootprint); }
+
     /// @returns true if this type is a scalar
     bool is_scalar() const;
     /// @returns true if this type is a numeric scalar
diff --git a/src/tint/sem/u32.cc b/src/tint/sem/u32.cc
index 91fd7d7..a93ff4c 100644
--- a/src/tint/sem/u32.cc
+++ b/src/tint/sem/u32.cc
@@ -20,7 +20,12 @@
 
 namespace tint::sem {
 
-U32::U32() : Base(TypeFlags{Flag::kConstructable}) {}
+U32::U32()
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }) {}
 
 U32::~U32() = default;
 
diff --git a/src/tint/sem/vector.cc b/src/tint/sem/vector.cc
index e6d3acf..32b7a34 100644
--- a/src/tint/sem/vector.cc
+++ b/src/tint/sem/vector.cc
@@ -22,7 +22,13 @@
 namespace tint::sem {
 
 Vector::Vector(Type const* subtype, uint32_t width)
-    : Base(TypeFlags{Flag::kConstructable}), subtype_(subtype), width_(width) {
+    : Base(TypeFlags{
+          Flag::kConstructable,
+          Flag::kCreationFixedFootprint,
+          Flag::kFixedFootprint,
+      }),
+      subtype_(subtype),
+      width_(width) {
     TINT_ASSERT(Semantic, width_ > 1);
     TINT_ASSERT(Semantic, width_ < 5);
 }