tint: Refactor sem::Constant to be less memory-hungry

Change sem::Constant to be an interface to the constant data. Implement
this so that zero-initialized data doesn't need to allocate the full
size of the type.

This also makes usage a lot cleaner (no more flattened-list of
elements!), and gives us a clear path for supporting constant
structures if/when we want to support them.

Bug: chromium:1339558
Bug: chromium:1339561
Bug: chromium:1339580
Bug: chromium:1339597
Change-Id: Ifcd456f69aee18d5b84befa896d7b0189d68c2dd
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94942
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 0ace1ca..1b935ee 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -73,12 +73,61 @@
     return o << "<unknown>";
 }
 
+template <typename CASE>
+class MaterializeTest : public resolver::ResolverTestWithParam<CASE> {
+  protected:
+    using ProgramBuilder::FriendlyName;
+
+    void CheckTypesAndValues(const sem::Expression* expr,
+                             const tint::sem::Type* expected_sem_ty,
+                             const std::variant<AInt, AFloat>& expected_value) {
+        std::visit([&](auto v) { CheckTypesAndValuesImpl(expr, expected_sem_ty, v); },
+                   expected_value);
+    }
+
+  private:
+    template <typename T>
+    void CheckTypesAndValuesImpl(const sem::Expression* expr,
+                                 const tint::sem::Type* expected_sem_ty,
+                                 T expected_value) {
+        EXPECT_TYPE(expr->Type(), expected_sem_ty);
+
+        auto* value = expr->ConstantValue();
+        ASSERT_NE(value, nullptr);
+        EXPECT_TYPE(expr->Type(), value->Type());
+
+        tint::Switch(
+            expected_sem_ty,  //
+            [&](const sem::Vector* v) {
+                for (uint32_t i = 0; i < v->Width(); i++) {
+                    auto* el = value->Index(i);
+                    ASSERT_NE(el, nullptr);
+                    EXPECT_TYPE(el->Type(), v->type());
+                    EXPECT_EQ(std::get<T>(el->Value()), expected_value);
+                }
+            },
+            [&](const sem::Matrix* m) {
+                for (uint32_t c = 0; c < m->columns(); c++) {
+                    auto* column = value->Index(c);
+                    ASSERT_NE(column, nullptr);
+                    EXPECT_TYPE(column->Type(), m->ColumnType());
+                    for (uint32_t r = 0; r < m->rows(); r++) {
+                        auto* el = column->Index(r);
+                        ASSERT_NE(el, nullptr);
+                        EXPECT_TYPE(el->Type(), m->type());
+                        EXPECT_EQ(std::get<T>(el->Value()), expected_value);
+                    }
+                }
+            },
+            [&](Default) { EXPECT_EQ(std::get<T>(value->Value()), expected_value); });
+    }
+};
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // MaterializeAbstractNumericToConcreteType
 // Tests that an abstract-numeric will materialize to the expected concrete type
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 namespace materialize_abstract_numeric_to_concrete_type {
-
 // How should the materialization occur?
 enum class Method {
     // var a : target_type = abstract_expr;
@@ -247,7 +296,7 @@
 }
 
 using MaterializeAbstractNumericToConcreteType =
-    resolver::ResolverTestWithParam<std::tuple<Expectation, Method, Data>>;
+    MaterializeTest<std::tuple<Expectation, Method, Data>>;
 
 TEST_P(MaterializeAbstractNumericToConcreteType, Test) {
     // Once built-in and ops using f16 is properly supported, we'll need to enable this:
@@ -323,30 +372,12 @@
             break;
     }
 
-    auto check_types_and_values = [&](const sem::Expression* expr) {
-        auto* target_sem_ty = data.target_sem_ty(*this);
-
-        EXPECT_TYPE(expr->Type(), target_sem_ty);
-        EXPECT_TYPE(expr->ConstantValue().Type(), target_sem_ty);
-
-        uint32_t num_elems = 0;
-        const sem::Type* target_sem_el_ty = sem::Type::DeepestElementOf(target_sem_ty, &num_elems);
-        EXPECT_TYPE(expr->ConstantValue().ElementType(), target_sem_el_ty);
-        expr->ConstantValue().WithElements([&](auto&& vec) {
-            using VEC_TY = std::decay_t<decltype(vec)>;
-            using EL_TY = typename VEC_TY::value_type;
-            ASSERT_TRUE(std::holds_alternative<EL_TY>(data.materialized_value));
-            VEC_TY expected(num_elems, std::get<EL_TY>(data.materialized_value));
-            EXPECT_EQ(vec, expected);
-        });
-    };
-
     switch (expectation) {
         case Expectation::kMaterialize: {
             ASSERT_TRUE(r()->Resolve()) << r()->error();
             auto* materialize = Sem().Get<sem::Materialize>(abstract_expr);
             ASSERT_NE(materialize, nullptr);
-            check_types_and_values(materialize);
+            CheckTypesAndValues(materialize, data.target_sem_ty(*this), data.materialized_value);
             break;
         }
         case Expectation::kNoMaterialize: {
@@ -354,7 +385,7 @@
             auto* sem = Sem().Get(abstract_expr);
             ASSERT_NE(sem, nullptr);
             EXPECT_FALSE(sem->Is<sem::Materialize>());
-            check_types_and_values(sem);
+            CheckTypesAndValues(sem, data.target_sem_ty(*this), data.materialized_value);
             break;
         }
         case Expectation::kInvalidConversion: {
@@ -414,8 +445,8 @@
 /// Methods that do not materialize
 constexpr Method kNoMaterializeMethods[] = {
     Method::kPhonyAssign,
-    // TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary ops:
-    // Method::kBuiltinArg, Method::kBinaryOp,
+    // TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary
+    // ops: Method::kBuiltinArg, Method::kBinaryOp,
 };
 INSTANTIATE_TEST_SUITE_P(
     MaterializeScalar,
@@ -703,7 +734,7 @@
 }
 
 using MaterializeAbstractNumericToDefaultType =
-    resolver::ResolverTestWithParam<std::tuple<Expectation, Method, Data>>;
+    MaterializeTest<std::tuple<Expectation, Method, Data>>;
 
 TEST_P(MaterializeAbstractNumericToDefaultType, Test) {
     const auto& param = GetParam();
@@ -751,32 +782,14 @@
             break;
     }
 
-    auto check_types_and_values = [&](const sem::Expression* expr) {
-        auto* expected_sem_ty = data.expected_sem_ty(*this);
-
-        EXPECT_TYPE(expr->Type(), expected_sem_ty);
-        EXPECT_TYPE(expr->ConstantValue().Type(), expected_sem_ty);
-
-        uint32_t num_elems = 0;
-        const sem::Type* expected_sem_el_ty =
-            sem::Type::DeepestElementOf(expected_sem_ty, &num_elems);
-        EXPECT_TYPE(expr->ConstantValue().ElementType(), expected_sem_el_ty);
-        expr->ConstantValue().WithElements([&](auto&& vec) {
-            using VEC_TY = std::decay_t<decltype(vec)>;
-            using EL_TY = typename VEC_TY::value_type;
-            ASSERT_TRUE(std::holds_alternative<EL_TY>(data.materialized_value));
-            VEC_TY expected(num_elems, std::get<EL_TY>(data.materialized_value));
-            EXPECT_EQ(vec, expected);
-        });
-    };
-
     switch (expectation) {
         case Expectation::kMaterialize: {
             ASSERT_TRUE(r()->Resolve()) << r()->error();
             for (auto* expr : abstract_exprs) {
                 auto* materialize = Sem().Get<sem::Materialize>(expr);
                 ASSERT_NE(materialize, nullptr);
-                check_types_and_values(materialize);
+                CheckTypesAndValues(materialize, data.expected_sem_ty(*this),
+                                    data.materialized_value);
             }
             break;
         }