[ir] Avoid validating an array element type multiple times

The logic in CheckType for validating element types of composites was
falling through to the case were we validate the type of every element
type separately. For large arrays this was causing excessively long IR
validation times.

Fix the logic so that we never do the per-element validation if every
element has the same type. Use an if-else block instead of an early
continue to make it clearer that these are mutually exclusive paths
(and add some comments).

The unit test takes from ~1 minute (release mode) to 10+ minutes
(debug mode) on my laptop without this change, and <1ms with the
change.

Change-Id: Iead1395f969ac54553d1b6f1f1c5684146376222
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/290575
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 0744061..b0c926d 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -2490,15 +2490,21 @@
             continue;
         }
 
+        // Visit the elements of a composite type.
         auto type_count = ty->Elements();
-        if (type_count.type && seen.Add(type_count.type)) {
-            stack.Push(type_count.type);
-            continue;
-        }
-
-        for (uint32_t i = 0; i < type_count.count; i++) {
-            if (auto* subtype = ty->Element(i); subtype && seen.Add(subtype)) {
-                stack.Push(subtype);
+        if (type_count.type) {
+            // Every element has the same type (e.g. array, vector, matrix, ...), so validate that
+            // type once if it has not been seen before.
+            if (seen.Add(type_count.type)) {
+                stack.Push(type_count.type);
+            }
+        } else {
+            // Different elements have different types (e.g. a struct), so we need to validate each
+            // of them if they have not been seen before.
+            for (uint32_t i = 0; i < type_count.count; i++) {
+                if (auto* subtype = ty->Element(i); subtype && seen.Add(subtype)) {
+                    stack.Push(subtype);
+                }
             }
         }
     }
diff --git a/src/tint/lang/core/ir/validator_type_test.cc b/src/tint/lang/core/ir/validator_type_test.cc
index 0f6c5eb..18e6a07 100644
--- a/src/tint/lang/core/ir/validator_type_test.cc
+++ b/src/tint/lang/core/ir/validator_type_test.cc
@@ -729,6 +729,28 @@
                                          std::make_tuple(true, TypeBuilder<core::type::Bool>),
                                          std::make_tuple(false, TypeBuilder<core::type::Void>)));
 
+// Test that validation time does not increase in relation to the size of the array.
+TEST_F(IR_ValidatorTest, LargeArrays) {
+    // The arrays are all different sizes so that they don't get skipped over by CheckType for
+    // having been "seen" already.
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"),
+                  {
+                      {mod.symbols.New("a0"), ty.u32(), {}},
+                      {mod.symbols.New("a1"), ty.array<u32, (1ull << 32ull) - 4u>(), {}},
+                      {mod.symbols.New("a2"), ty.array<u32, (1ull << 32ull) - 8u>(), {}},
+                      {mod.symbols.New("a3"), ty.array<u32, (1ull << 32ull) - 12u>(), {}},
+                      {mod.symbols.New("a4"), ty.array<u32, (1ull << 32ull) - 16u>(), {}},
+                      {mod.symbols.New("a5"), ty.array<u32, (1ull << 32ull) - 20u>(), {}},
+                      {mod.symbols.New("a6"), ty.array<u32, (1ull << 32ull) - 24u>(), {}},
+                      {mod.symbols.New("a7"), ty.array<u32, (1ull << 32ull) - 28u>(), {}},
+                      {mod.symbols.New("a8"), ty.array<u32, (1ull << 32ull) - 32u>(), {}},
+                  });
+    mod.root_block->Append(b.Var(ty.ptr<workgroup>(str_ty)));
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success) << res.Failure();
+}
+
 using Type_VectorElements = TypeTest;
 
 TEST_P(Type_VectorElements, Test) {