[resolver]: Begin constant value evaluation

Move the bulk of the constant evaulation logic out of transform::FoldConstants and into Resolver and sem::Expression.

transform::FoldConstants now replace TypeConstructor nodes that have a constant value on the expression.

This is ground work to:
* Cleaning up the HLSL uniform buffer indexing, which is `/` and `%` arithmatic heavy
* Prepares us to handle `constexpr` when it lands in the spec
* Provide a centralized place to do constant evaluation, instead of the
  having similar logic scattered around the codebase.

Change-Id: I3e2f542be692046a8d243b62a82556db519953e7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57426
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 6f81fbc..a7c9334 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -493,6 +493,7 @@
     "reader/reader.cc",
     "reader/reader.h",
     "resolver/resolver.cc",
+    "resolver/resolver_constants.cc",
     "resolver/resolver.h",
     "scope_stack.h",
     "sem/array.h",
@@ -503,6 +504,8 @@
     "sem/bool_type.h",
     "sem/call.h",
     "sem/call_target.h",
+    "sem/constant.cc",
+    "sem/constant.h",
     "sem/depth_texture_type.cc",
     "sem/depth_texture_type.h",
     "sem/expression.h",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index dd3f2d1..9761dc1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -240,6 +240,7 @@
   reader/reader.cc
   reader/reader.h
   resolver/resolver.cc
+  resolver/resolver_constants.cc
   resolver/resolver.h
   scope_stack.h
   sem/array.cc
@@ -253,6 +254,8 @@
   sem/call_target.h
   sem/call.cc
   sem/call.h
+  sem/constant.cc
+  sem/constant.h
   sem/expression.cc
   sem/expression.h
   sem/function.cc
@@ -633,6 +636,7 @@
     resolver/is_storeable_test.cc
     resolver/ptr_ref_test.cc
     resolver/ptr_ref_validation_test.cc
+    resolver/resolver_constants_test.cc
     resolver/pipeline_overridable_constant_test.cc
     resolver/resolver_test_helper.cc
     resolver/resolver_test_helper.h
diff --git a/src/ast/matrix.cc b/src/ast/matrix.cc
index 3564513..23ab60b 100644
--- a/src/ast/matrix.cc
+++ b/src/ast/matrix.cc
@@ -30,6 +30,7 @@
       subtype_(subtype),
       rows_(rows),
       columns_(columns) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
   TINT_ASSERT(AST, rows > 1);
   TINT_ASSERT(AST, rows < 5);
   TINT_ASSERT(AST, columns > 1);
diff --git a/src/ast/vector.cc b/src/ast/vector.cc
index 781ef95..23619d7 100644
--- a/src/ast/vector.cc
+++ b/src/ast/vector.cc
@@ -26,6 +26,7 @@
                Type const* subtype,
                uint32_t size)
     : Base(program_id, source), subtype_(subtype), size_(size) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id);
   TINT_ASSERT(AST, size_ > 1);
   TINT_ASSERT(AST, size_ < 5);
 }
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index fe559f3..a324667 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -2067,7 +2067,7 @@
     ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
                                            ref->Access());
   }
-  SetType(expr, ret);
+  SetExprInfo(expr, ret);
 
   return true;
 }
@@ -2085,7 +2085,7 @@
     AddError("cannot cast to a pointer", expr->source());
     return false;
   }
-  SetType(expr, ty, expr->type()->FriendlyName(builder_->Symbols()));
+  SetExprInfo(expr, ty, expr->type()->FriendlyName(builder_->Symbols()));
   return true;
 }
 
@@ -2167,7 +2167,7 @@
 
   builder_->Sem().Add(
       call, builder_->create<sem::Call>(call, result, current_statement_));
-  SetType(call, result->ReturnType());
+  SetExprInfo(call, result->ReturnType());
 
   current_function_->intrinsic_calls.emplace_back(
       IntrinsicCallInfo{call, result});
@@ -2239,7 +2239,7 @@
 
   function_calls_.emplace(call,
                           FunctionCallInfo{callee_func, current_statement_});
-  SetType(call, callee_func->return_type, callee_func->return_type_name);
+  SetExprInfo(call, callee_func->return_type, callee_func->return_type_name);
 
   return true;
 }
@@ -2258,7 +2258,7 @@
       return false;
     }
 
-    SetType(expr, type, type_ctor->type()->FriendlyName(builder_->Symbols()));
+    auto type_name = type_ctor->type()->FriendlyName(builder_->Symbols());
 
     // Now that the argument types have been determined, make sure that they
     // obey the constructor type rules laid out in
@@ -2267,33 +2267,38 @@
       AddError("cannot cast to a pointer", expr->source());
       return false;
     }
+
+    bool ok = true;
     if (auto* vec_type = type->As<sem::Vector>()) {
-      return ValidateVectorConstructor(type_ctor, vec_type);
+      ok = ValidateVectorConstructor(type_ctor, vec_type, type_name);
+    } else if (auto* mat_type = type->As<sem::Matrix>()) {
+      ok = ValidateMatrixConstructor(type_ctor, mat_type, type_name);
+    } else if (type->is_scalar()) {
+      ok = ValidateScalarConstructor(type_ctor, type, type_name);
+    } else if (auto* arr_type = type->As<sem::Array>()) {
+      ok = ValidateArrayConstructor(type_ctor, arr_type);
+    } else if (auto* struct_type = type->As<sem::Struct>()) {
+      ok = ValidateStructureConstructor(type_ctor, struct_type);
     }
-    if (auto* mat_type = type->As<sem::Matrix>()) {
-      return ValidateMatrixConstructor(type_ctor, mat_type);
+    if (!ok) {
+      return false;
     }
-    if (type->is_scalar()) {
-      return ValidateScalarConstructor(type_ctor, type);
-    }
-    if (auto* arr_type = type->As<sem::Array>()) {
-      return ValidateArrayConstructor(type_ctor, arr_type);
-    }
-    if (auto* struct_type = type->As<sem::Struct>()) {
-      return ValidateStructureConstructor(type_ctor, struct_type);
-    }
-  } else if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
+    SetExprInfo(expr, type, type_name);
+    return true;
+  }
+
+  if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
     Mark(scalar_ctor->literal());
     auto* type = TypeOf(scalar_ctor->literal());
     if (!type) {
       return false;
     }
-    SetType(expr, type);
-  } else {
-    TINT_ICE(Resolver, diagnostics_)
-        << "unexpected constructor expression type";
+    SetExprInfo(expr, type);
+    return true;
   }
-  return true;
+
+  TINT_ICE(Resolver, diagnostics_) << "unexpected constructor expression type";
+  return false;
 }
 
 bool Resolver::ValidateStructureConstructor(
@@ -2366,7 +2371,8 @@
 
 bool Resolver::ValidateVectorConstructor(
     const ast::TypeConstructorExpression* ctor,
-    const sem::Vector* vec_type) {
+    const sem::Vector* vec_type,
+    const std::string& type_name) {
   auto& values = ctor->values();
   auto* elem_type = vec_type->type();
   size_t value_cardinality_sum = 0;
@@ -2423,7 +2429,7 @@
     }
     const Source& values_start = values[0]->source();
     const Source& values_end = values[values.size() - 1]->source();
-    AddError("attempted to construct '" + TypeNameOf(ctor) + "' with " +
+    AddError("attempted to construct '" + type_name + "' with " +
                  std::to_string(value_cardinality_sum) + " component(s)",
              Source::Combine(values_start, values_end));
     return false;
@@ -2450,7 +2456,8 @@
 
 bool Resolver::ValidateMatrixConstructor(
     const ast::TypeConstructorExpression* ctor,
-    const sem::Matrix* matrix_type) {
+    const sem::Matrix* matrix_type,
+    const std::string& type_name) {
   auto& values = ctor->values();
   // Zero Value expression
   if (values.empty()) {
@@ -2467,8 +2474,8 @@
     const Source& values_end = values[values.size() - 1]->source();
     AddError("expected " + std::to_string(matrix_type->columns()) + " '" +
                  VectorPretty(matrix_type->rows(), elem_type) +
-                 "' arguments in '" + TypeNameOf(ctor) +
-                 "' constructor, found " + std::to_string(values.size()),
+                 "' arguments in '" + type_name + "' constructor, found " +
+                 std::to_string(values.size()),
              Source::Combine(values_start, values_end));
     return false;
   }
@@ -2481,8 +2488,8 @@
         elem_type != value_vec->type()) {
       AddError("expected argument type '" +
                    VectorPretty(matrix_type->rows(), elem_type) + "' in '" +
-                   TypeNameOf(ctor) + "' constructor, found '" +
-                   TypeNameOf(value) + "'",
+                   type_name + "' constructor, found '" + TypeNameOf(value) +
+                   "'",
                value->source());
       return false;
     }
@@ -2493,7 +2500,8 @@
 
 bool Resolver::ValidateScalarConstructor(
     const ast::TypeConstructorExpression* ctor,
-    const sem::Type* type) {
+    const sem::Type* type,
+    const std::string& type_name) {
   if (ctor->values().size() == 0) {
     return true;
   }
@@ -2519,8 +2527,8 @@
       (type->Is<U32>() && value_type->IsAnyOf<I32, U32, F32>()) ||
       (type->Is<F32>() && value_type->IsAnyOf<I32, U32, F32>());
   if (!is_valid) {
-    AddError("cannot construct '" + TypeNameOf(ctor) +
-                 "' with a value of type '" + TypeNameOf(value) + "'",
+    AddError("cannot construct '" + type_name + "' with a value of type '" +
+                 TypeNameOf(value) + "'",
              ctor->source());
 
     return false;
@@ -2533,7 +2541,7 @@
   auto symbol = expr->symbol();
   VariableInfo* var;
   if (variable_stack_.get(symbol, &var)) {
-    SetType(expr, var->type, var->type_name);
+    SetExprInfo(expr, var->type, var->type_name);
 
     var->users.push_back(expr);
     set_referenced_from_function_if_needed(var, true);
@@ -2707,7 +2715,7 @@
     return false;
   }
 
-  SetType(expr, ret);
+  SetExprInfo(expr, ret);
 
   return true;
 }
@@ -2745,17 +2753,17 @@
   // Binary logical expressions
   if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
     if (matching_types && lhs_type->Is<Bool>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
   }
   if (expr->IsOr() || expr->IsAnd()) {
     if (matching_types && lhs_type->Is<Bool>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
     if (matching_types && lhs_vec_elem_type && lhs_vec_elem_type->Is<Bool>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
   }
@@ -2764,14 +2772,14 @@
   if (expr->IsArithmetic()) {
     // Binary arithmetic expressions over scalars
     if (matching_types && lhs_type->is_numeric_scalar()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
 
     // Binary arithmetic expressions over vectors
     if (matching_types && lhs_vec_elem_type &&
         lhs_vec_elem_type->is_numeric_scalar()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
 
@@ -2779,22 +2787,22 @@
     if (lhs_vec_elem_type && (lhs_vec_elem_type == rhs_type)) {
       if (expr->IsModulo()) {
         if (rhs_type->is_integer_scalar()) {
-          SetType(expr, lhs_type);
+          SetExprInfo(expr, lhs_type);
           return true;
         }
       } else if (rhs_type->is_numeric_scalar()) {
-        SetType(expr, lhs_type);
+        SetExprInfo(expr, lhs_type);
         return true;
       }
     }
     if (rhs_vec_elem_type && (rhs_vec_elem_type == lhs_type)) {
       if (expr->IsModulo()) {
         if (lhs_type->is_integer_scalar()) {
-          SetType(expr, rhs_type);
+          SetExprInfo(expr, rhs_type);
           return true;
         }
       } else if (lhs_type->is_numeric_scalar()) {
-        SetType(expr, rhs_type);
+        SetExprInfo(expr, rhs_type);
         return true;
       }
     }
@@ -2811,19 +2819,19 @@
       rhs_mat_elem_type->Is<F32>() &&
       (lhs_mat->columns() == rhs_mat->columns()) &&
       (lhs_mat->rows() == rhs_mat->rows())) {
-    SetType(expr, rhs_type);
+    SetExprInfo(expr, rhs_type);
     return true;
   }
   if (expr->IsMultiply()) {
     // Multiplication of a matrix and a scalar
     if (lhs_type->Is<F32>() && rhs_mat_elem_type &&
         rhs_mat_elem_type->Is<F32>()) {
-      SetType(expr, rhs_type);
+      SetExprInfo(expr, rhs_type);
       return true;
     }
     if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
         rhs_type->Is<F32>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
 
@@ -2831,8 +2839,8 @@
     if (lhs_vec_elem_type && lhs_vec_elem_type->Is<F32>() &&
         rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
         (lhs_vec->size() == rhs_mat->rows())) {
-      SetType(expr, builder_->create<sem::Vector>(lhs_vec->type(),
-                                                  rhs_mat->columns()));
+      SetExprInfo(expr, builder_->create<sem::Vector>(lhs_vec->type(),
+                                                      rhs_mat->columns()));
       return true;
     }
 
@@ -2840,8 +2848,8 @@
     if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
         rhs_vec_elem_type && rhs_vec_elem_type->Is<F32>() &&
         (lhs_mat->columns() == rhs_vec->size())) {
-      SetType(expr,
-              builder_->create<sem::Vector>(rhs_vec->type(), lhs_mat->rows()));
+      SetExprInfo(expr, builder_->create<sem::Vector>(rhs_vec->type(),
+                                                      lhs_mat->rows()));
       return true;
     }
 
@@ -2849,10 +2857,10 @@
     if (lhs_mat_elem_type && lhs_mat_elem_type->Is<F32>() &&
         rhs_mat_elem_type && rhs_mat_elem_type->Is<F32>() &&
         (lhs_mat->columns() == rhs_mat->rows())) {
-      SetType(expr, builder_->create<sem::Matrix>(
-                        builder_->create<sem::Vector>(lhs_mat_elem_type,
-                                                      lhs_mat->rows()),
-                        rhs_mat->columns()));
+      SetExprInfo(expr, builder_->create<sem::Matrix>(
+                            builder_->create<sem::Vector>(lhs_mat_elem_type,
+                                                          lhs_mat->rows()),
+                            rhs_mat->columns()));
       return true;
     }
   }
@@ -2862,13 +2870,13 @@
     if (matching_types) {
       // Special case for bools: only == and !=
       if (lhs_type->Is<Bool>() && (expr->IsEqual() || expr->IsNotEqual())) {
-        SetType(expr, builder_->create<sem::Bool>());
+        SetExprInfo(expr, builder_->create<sem::Bool>());
         return true;
       }
 
       // For the rest, we can compare i32, u32, and f32
       if (lhs_type->IsAnyOf<I32, U32, F32>()) {
-        SetType(expr, builder_->create<sem::Bool>());
+        SetExprInfo(expr, builder_->create<sem::Bool>());
         return true;
       }
     }
@@ -2877,14 +2885,14 @@
     if (matching_vec_elem_types) {
       if (lhs_vec_elem_type->Is<Bool>() &&
           (expr->IsEqual() || expr->IsNotEqual())) {
-        SetType(expr, builder_->create<sem::Vector>(
-                          builder_->create<sem::Bool>(), lhs_vec->size()));
+        SetExprInfo(expr, builder_->create<sem::Vector>(
+                              builder_->create<sem::Bool>(), lhs_vec->size()));
         return true;
       }
 
       if (lhs_vec_elem_type->is_numeric_scalar()) {
-        SetType(expr, builder_->create<sem::Vector>(
-                          builder_->create<sem::Bool>(), lhs_vec->size()));
+        SetExprInfo(expr, builder_->create<sem::Vector>(
+                              builder_->create<sem::Bool>(), lhs_vec->size()));
         return true;
       }
     }
@@ -2893,7 +2901,7 @@
   // Binary bitwise operations
   if (expr->IsBitwise()) {
     if (matching_types && lhs_type->is_integer_scalar_or_vector()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
   }
@@ -2905,13 +2913,13 @@
     // logical depending on lhs type).
 
     if (lhs_type->IsAnyOf<I32, U32>() && rhs_type->Is<U32>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
 
     if (lhs_vec_elem_type && lhs_vec_elem_type->IsAnyOf<I32, U32>() &&
         rhs_vec_elem_type && rhs_vec_elem_type->Is<U32>()) {
-      SetType(expr, lhs_type);
+      SetExprInfo(expr, lhs_type);
       return true;
     }
   }
@@ -3002,7 +3010,7 @@
       break;
   }
 
-  SetType(unary, type);
+  SetExprInfo(unary, type);
   return true;
 }
 
@@ -3131,18 +3139,20 @@
   return nullptr;
 }
 
-void Resolver::SetType(const ast::Expression* expr, const sem::Type* type) {
-  SetType(expr, type, type->FriendlyName(builder_->Symbols()));
-}
-
-void Resolver::SetType(const ast::Expression* expr,
-                       const sem::Type* type,
-                       const std::string& type_name) {
+void Resolver::SetExprInfo(const ast::Expression* expr,
+                           const sem::Type* type,
+                           std::string type_name) {
   if (expr_info_.count(expr)) {
     TINT_ICE(Resolver, diagnostics_)
-        << "SetType() called twice for the same expression";
+        << "SetExprInfo() called twice for the same expression";
   }
-  expr_info_.emplace(expr, ExpressionInfo{type, type_name, current_statement_});
+  if (type_name.empty()) {
+    type_name = type->FriendlyName(builder_->Symbols());
+  }
+  auto constant_value = EvaluateConstantValue(expr, type);
+  expr_info_.emplace(
+      expr, ExpressionInfo{type, std::move(type_name), current_statement_,
+                           std::move(constant_value)});
 }
 
 bool Resolver::ValidatePipelineStages() {
@@ -3313,10 +3323,11 @@
       // Create semantic node for the identifier expression if necessary
       auto* sem_expr = sem.Get(user);
       if (sem_expr == nullptr) {
-        auto* type = expr_info_.at(user).type;
-        auto* stmt = expr_info_.at(user).statement;
-        auto* sem_user =
-            builder_->create<sem::VariableUser>(user, type, stmt, sem_var);
+        auto& expr_info = expr_info_.at(user);
+        auto* type = expr_info.type;
+        auto* stmt = expr_info.statement;
+        auto* sem_user = builder_->create<sem::VariableUser>(
+            user, type, stmt, sem_var, expr_info.constant_value);
         sem_var->AddUser(sem_user);
         sem.Add(user, sem_user);
       } else {
@@ -3372,9 +3383,9 @@
       // Expression has already been assigned a semantic node
       continue;
     }
-    sem.Add(expr,
-            builder_->create<sem::Expression>(
-                const_cast<ast::Expression*>(expr), info.type, info.statement));
+    sem.Add(expr, builder_->create<sem::Expression>(
+                      const_cast<ast::Expression*>(expr), info.type,
+                      info.statement, info.constant_value));
   }
 }
 
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 04b7326..822d6bc 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -26,6 +26,7 @@
 #include "src/scope_stack.h"
 #include "src/sem/binding_point.h"
 #include "src/sem/block_statement.h"
+#include "src/sem/constant.h"
 #include "src/sem/function.h"
 #include "src/sem/struct.h"
 #include "src/utils/unique_vector.h"
@@ -152,6 +153,7 @@
     sem::Type const* type;
     std::string const type_name;  // Declared type name
     sem::Statement* statement;
+    sem::Constant constant_value;
   };
 
   /// Structure holding semantic information about a call expression to an
@@ -282,8 +284,6 @@
   bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
                                      const sem::Type* storage_type);
   bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
-  bool ValidateMatrixConstructor(const ast::TypeConstructorExpression* ctor,
-                                 const sem::Matrix* matrix_type);
   bool ValidateFunctionParameter(const ast::Function* func,
                                  const VariableInfo* info);
   bool ValidateNoDuplicateDefinition(Symbol sym,
@@ -305,9 +305,14 @@
                                    const std::string& rhs_type_name);
   bool ValidateVector(const sem::Vector* ty, const Source& source);
   bool ValidateVectorConstructor(const ast::TypeConstructorExpression* ctor,
-                                 const sem::Vector* vec_type);
+                                 const sem::Vector* vec_type,
+                                 const std::string& type_name);
+  bool ValidateMatrixConstructor(const ast::TypeConstructorExpression* ctor,
+                                 const sem::Matrix* matrix_type,
+                                 const std::string& type_name);
   bool ValidateScalarConstructor(const ast::TypeConstructorExpression* ctor,
-                                 const sem::Type* type);
+                                 const sem::Type* type,
+                                 const std::string& type_name);
   bool ValidateArrayConstructor(const ast::TypeConstructorExpression* ctor,
                                 const sem::Array* arr_type);
   bool ValidateTypeDecl(const ast::TypeDecl* named_type) const;
@@ -380,21 +385,14 @@
   /// @param lit the literal
   sem::Type* TypeOf(const ast::Literal* lit);
 
-  /// Creates a sem::Expression node with the resolved type `type`, and
-  /// assigns this semantic node to the expression `expr`.
-  /// @param expr the expression
-  /// @param type the resolved type
-  void SetType(const ast::Expression* expr, const sem::Type* type);
-
-  /// Creates a sem::Expression node with the resolved type `type`, the declared
-  /// type name `type_name` and assigns this semantic node to the expression
-  /// `expr`.
+  /// Records the semantic information for the expression node with the resolved
+  /// type `type` and optional declared type name `type_name`.
   /// @param expr the expression
   /// @param type the resolved type
   /// @param type_name the declared type name
-  void SetType(const ast::Expression* expr,
-               const sem::Type* type,
-               const std::string& type_name);
+  void SetExprInfo(const ast::Expression* expr,
+                   const sem::Type* type,
+                   std::string type_name = "");
 
   /// Resolve the value of a scalar const_expr.
   /// @param expr the expression
@@ -435,6 +433,26 @@
                          FunctionInfo* to,
                          CALLBACK&& callback) const;
 
+  //////////////////////////////////////////////////////////////////////////////
+  /// Constant value evaluation methods
+  //////////////////////////////////////////////////////////////////////////////
+  /// @return the Constant value of the given Expression
+  sem::Constant ConstantValueOf(const ast::Expression* expr);
+
+  /// Cast `Value` to `target_type`
+  /// @return the casted value
+  sem::Constant ConstantCast(const sem::Constant& value,
+                             const sem::Type* target_elem_type);
+
+  sem::Constant EvaluateConstantValue(const ast::Expression* expr,
+                                      const sem::Type* type);
+  sem::Constant EvaluateConstantValue(
+      const ast::ScalarConstructorExpression* scalar_ctor,
+      const sem::Type* type);
+  sem::Constant EvaluateConstantValue(
+      const ast::TypeConstructorExpression* type_ctor,
+      const sem::Type* type);
+
   ProgramBuilder* const builder_;
   diag::List& diagnostics_;
   std::unique_ptr<IntrinsicTable> const intrinsic_table_;
diff --git a/src/resolver/resolver_constants.cc b/src/resolver/resolver_constants.cc
new file mode 100644
index 0000000..45de987
--- /dev/null
+++ b/src/resolver/resolver_constants.cc
@@ -0,0 +1,153 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/resolver/resolver.h"
+
+#include "src/sem/constant.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+}  // namespace
+
+sem::Constant Resolver::ConstantCast(const sem::Constant& value,
+                                     const sem::Type* target_elem_type) {
+  if (value.ElementType() == target_elem_type) {
+    return value;
+  }
+
+  sem::Constant::Scalars elems;
+  for (size_t i = 0; i < value.Elements().size(); ++i) {
+    if (target_elem_type->Is<sem::I32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<i32>(s); }));
+    } else if (target_elem_type->Is<sem::U32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<u32>(s); }));
+    } else if (target_elem_type->Is<sem::F32>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<f32>(s); }));
+    } else if (target_elem_type->Is<sem::Bool>()) {
+      elems.emplace_back(
+          value.WithScalarAt(i, [](auto&& s) { return static_cast<bool>(s); }));
+    }
+  }
+
+  auto* target_type =
+      value.Type()->Is<sem::Vector>()
+          ? builder_->create<sem::Vector>(target_elem_type,
+                                          static_cast<uint32_t>(elems.size()))
+          : target_elem_type;
+
+  return sem::Constant(target_type, elems);
+}
+
+sem::Constant Resolver::ConstantValueOf(const ast::Expression* expr) {
+  auto it = expr_info_.find(expr);
+  if (it != expr_info_.end()) {
+    return it->second.constant_value;
+  }
+  return {};
+}
+
+sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr,
+                                              const sem::Type* type) {
+  if (auto* e = expr->As<ast::ScalarConstructorExpression>()) {
+    return EvaluateConstantValue(e, type);
+  }
+  if (auto* e = expr->As<ast::TypeConstructorExpression>()) {
+    return EvaluateConstantValue(e, type);
+  }
+  return {};
+}
+
+sem::Constant Resolver::EvaluateConstantValue(
+    const ast::ScalarConstructorExpression* scalar_ctor,
+    const sem::Type* type) {
+  auto* literal = scalar_ctor->literal();
+  if (auto* lit = literal->As<ast::SintLiteral>()) {
+    return {type, {lit->value_as_i32()}};
+  }
+  if (auto* lit = literal->As<ast::UintLiteral>()) {
+    return {type, {lit->value_as_u32()}};
+  }
+  if (auto* lit = literal->As<ast::FloatLiteral>()) {
+    return {type, {lit->value()}};
+  }
+  if (auto* lit = literal->As<ast::BoolLiteral>()) {
+    return {type, {lit->IsTrue()}};
+  }
+  TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
+  return {};
+}
+
+sem::Constant Resolver::EvaluateConstantValue(
+    const ast::TypeConstructorExpression* type_ctor,
+    const sem::Type* type) {
+  auto& ctor_values = type_ctor->values();
+  auto* vec = type->As<sem::Vector>();
+
+  // For now, only fold scalars and vectors
+  if (!type->is_scalar() && !vec) {
+    return {};
+  }
+
+  auto* elem_type = vec ? vec->type() : type;
+  int result_size = vec ? static_cast<int>(vec->size()) : 1;
+
+  // For zero value init, return 0s
+  if (ctor_values.empty()) {
+    if (elem_type->Is<sem::I32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0));
+    }
+    if (elem_type->Is<sem::U32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0u));
+    }
+    if (elem_type->Is<sem::F32>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f));
+    }
+    if (elem_type->Is<sem::Bool>()) {
+      return sem::Constant(type, sem::Constant::Scalars(result_size, false));
+    }
+  }
+
+  // Build value for type_ctor from each child value by casting to
+  // type_ctor's type.
+  sem::Constant::Scalars elems;
+  for (auto* cv : ctor_values) {
+    auto value = ConstantValueOf(cv);
+    if (!value.IsValid()) {
+      return {};
+    }
+    auto cast = ConstantCast(value, elem_type);
+    elems.insert(elems.end(), cast.Elements().begin(), cast.Elements().end());
+  }
+
+  // Splat single-value initializers
+  if (elems.size() == 1) {
+    for (int i = 0; i < result_size - 1; ++i) {
+      elems.emplace_back(elems[0]);
+    }
+  }
+
+  return sem::Constant(type, std::move(elems));
+}
+
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/resolver_constants_test.cc b/src/resolver/resolver_constants_test.cc
new file mode 100644
index 0000000..198cd3b
--- /dev/null
+++ b/src/resolver/resolver_constants_test.cc
@@ -0,0 +1,433 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/resolver/resolver.h"
+
+#include "gtest/gtest.h"
+#include "src/resolver/resolver_test_helper.h"
+#include "src/sem/expression.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using Scalar = sem::Constant::Scalar;
+
+using ResolverConstantsTest = ResolverTest;
+
+TEST_F(ResolverConstantsTest, Scalar_i32) {
+  auto* expr = Expr(99);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::I32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_u32) {
+  auto* expr = Expr(99u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::U32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_f32) {
+  auto* expr = Expr(9.9f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::F32>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
+}
+
+TEST_F(ResolverConstantsTest, Scalar_bool) {
+  auto* expr = Expr(true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  EXPECT_TRUE(sem->Type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_i32) {
+  auto* expr = vec3<i32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 0);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 0);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 0);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_u32) {
+  auto* expr = vec3<u32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 0u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) {
+  auto* expr = vec3<f32>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) {
+  auto* expr = vec3<bool>();
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, false);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_i32) {
+  auto* expr = vec3<i32>(99);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 99);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 99);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_u32) {
+  auto* expr = vec3<u32>(99u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 99u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 99u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_f32) {
+  auto* expr = vec3<f32>(9.9f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 9.9f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 9.9f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Splat_bool) {
+  auto* expr = vec3<bool>(true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_i32) {
+  auto* expr = vec3<i32>(1, 2, 3);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_u32) {
+  auto* expr = vec3<u32>(1u, 2u, 3u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_f32) {
+  auto* expr = vec3<f32>(1.f, 2.f, 3.f);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_FullConstruct_bool) {
+  auto* expr = vec3<bool>(true, false, true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_i32) {
+  auto* expr = vec3<i32>(1, vec2<i32>(2, 3));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_u32) {
+  auto* expr = vec3<u32>(vec2<u32>(1u, 2u), 3u);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_f32) {
+  auto* expr = vec3<f32>(1.f, vec2<f32>(2.f, 3.f));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
+  auto* expr = vec3<bool>(vec2<bool>(true, false), true);
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_32) {
+  auto* expr = vec3<i32>(vec3<f32>(1.1f, 2.2f, 3.3f));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
+}
+
+TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
+  auto* expr = vec3<f32>(vec3<u32>(10u, 20u, 30u));
+  WrapInFunction(expr);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(expr);
+  EXPECT_NE(sem, nullptr);
+  ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
+  EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
+  EXPECT_EQ(sem->Type()->As<sem::Vector>()->size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
+  EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
+  ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
+  EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 10.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 20.f);
+  EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 30.f);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/sem/call.cc b/src/sem/call.cc
index 3abb91e..774056d 100644
--- a/src/sem/call.cc
+++ b/src/sem/call.cc
@@ -22,7 +22,8 @@
 Call::Call(const ast::Expression* declaration,
            const CallTarget* target,
            Statement* statement)
-    : Base(declaration, target->ReturnType(), statement), target_(target) {}
+    : Base(declaration, target->ReturnType(), statement, Constant{}),
+      target_(target) {}
 
 Call::~Call() = default;
 
diff --git a/src/sem/constant.cc b/src/sem/constant.cc
new file mode 100644
index 0000000..8c5553b
--- /dev/null
+++ b/src/sem/constant.cc
@@ -0,0 +1,63 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/sem/constant.h"
+
+#include <utility>
+
+#include "src/debug.h"
+#include "src/program_builder.h"
+#include "src/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+namespace {
+
+const Type* ElemType(const Type* ty, size_t num_elements) {
+  diag::List diag;
+  if (ty->is_scalar()) {
+    if (num_elements != 1) {
+      TINT_ICE(Semantic, diag)
+          << "sem::Constant() type <-> num_element mismatch. type: '"
+          << ty->type_name() << "' num_elements: " << num_elements;
+    }
+    return ty;
+  }
+  if (auto* vec = ty->As<Vector>()) {
+    if (num_elements != vec->size()) {
+      TINT_ICE(Semantic, diag)
+          << "sem::Constant() type <-> num_element mismatch. type: '"
+          << ty->type_name() << "' num_elements: " << num_elements;
+    }
+    TINT_ASSERT(Semantic, vec->type()->is_scalar());
+    return vec->type();
+  }
+  TINT_UNREACHABLE(Semantic, diag) << "Unsupported sem::Constant type";
+  return nullptr;
+}
+
+}  // namespace
+
+Constant::Constant() {}
+
+Constant::Constant(const sem::Type* ty, Scalars els)
+    : type_(ty), elem_type_(ElemType(ty, els.size())), elems_(std::move(els)) {}
+
+Constant::Constant(const Constant&) = default;
+
+Constant::~Constant() = default;
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/sem/constant.h b/src/sem/constant.h
new file mode 100644
index 0000000..27895ff
--- /dev/null
+++ b/src/sem/constant.h
@@ -0,0 +1,130 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0(the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_SEM_CONSTANT_H_
+#define SRC_SEM_CONSTANT_H_
+
+#include <vector>
+
+#include "src/program_builder.h"
+#include "src/sem/type.h"
+
+namespace tint {
+namespace sem {
+
+/// A Constant is compile-time known expression value, expressed as a flattened
+/// list of scalar values. Value may be of a scalar or vector type.
+class Constant {
+  using i32 = ProgramBuilder::i32;
+  using u32 = ProgramBuilder::u32;
+  using f32 = ProgramBuilder::f32;
+
+ public:
+  /// Scalar holds a single constant scalar value, as a union of an i32, u32,
+  /// f32 or boolean.
+  union Scalar {
+    /// The scalar value as a i32
+    int32_t i32;
+    /// The scalar value as a u32
+    uint32_t u32;
+    /// The scalar value as a f32
+    float f32;
+    /// The scalar value as a bool
+    bool bool_;
+
+    /// Constructs the scalar with the i32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::i32 v) : i32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the u32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::u32 v) : u32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the f32 value `v`
+    /// @param v the value of the Scalar
+    Scalar(ProgramBuilder::f32 v) : f32(v) {}  // NOLINT
+
+    /// Constructs the scalar with the bool value `v`
+    /// @param v the value of the Scalar
+    Scalar(bool v) : bool_(v) {}  // NOLINT
+  };
+
+  /// Scalars is a list of scalar values
+  using Scalars = std::vector<Scalar>;
+
+  /// Constructs an invalid Constant
+  Constant();
+
+  /// Constructs a Constant of the given type and element values
+  /// @param ty the Constant type
+  /// @param els the Constant element values
+  Constant(const Type* ty, Scalars els);
+
+  /// Copy constructor
+  Constant(const Constant&);
+
+  /// Destructor
+  ~Constant();
+
+  /// @returns true if the Constant has been initialized
+  bool IsValid() const { return type_ != nullptr; }
+
+  /// @return true if the Constant has been initialized
+  operator bool() const { return IsValid(); }
+
+  /// @returns the type of the Constant
+  const sem::Type* Type() const { return type_; }
+
+  /// @returns the element type of the Constant
+  const sem::Type* ElementType() const { return elem_type_; }
+
+  /// @returns the constant's scalar elements
+  const Scalars& Elements() const { return elems_; }
+
+  /// Calls `func(s)` with s being the current scalar value at `index`.
+  /// `func` is typically a lambda of the form '[](auto&& s)'.
+  /// @param index the index of the scalar value
+  /// @param func a function with signature `T(S)`
+  /// @return the value returned by func.
+  template <typename Func>
+  auto WithScalarAt(size_t index, Func&& func) const {
+    auto* elem_type = ElementType();
+    if (elem_type->Is<I32>()) {
+      return func(elems_[index].i32);
+    }
+    if (elem_type->Is<U32>()) {
+      return func(elems_[index].u32);
+    }
+    if (elem_type->Is<F32>()) {
+      return func(elems_[index].f32);
+    }
+    if (elem_type->Is<Bool>()) {
+      return func(elems_[index].bool_);
+    }
+    diag::List diags;
+    TINT_UNREACHABLE(Semantic, diags)
+        << "invalid scalar type " << type_->type_name();
+    return func(~0);
+  }
+
+ private:
+  const sem::Type* type_ = nullptr;
+  const sem::Type* elem_type_ = nullptr;
+  Scalars elems_;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_SEM_CONSTANT_H_
diff --git a/src/sem/expression.cc b/src/sem/expression.cc
index b5ee7f4..f6ce44b 100644
--- a/src/sem/expression.cc
+++ b/src/sem/expression.cc
@@ -14,6 +14,8 @@
 
 #include "src/sem/expression.h"
 
+#include <utility>
+
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
 
 namespace tint {
@@ -21,10 +23,16 @@
 
 Expression::Expression(const ast::Expression* declaration,
                        const sem::Type* type,
-                       Statement* statement)
-    : declaration_(declaration), type_(type), statement_(statement) {
+                       Statement* statement,
+                       Constant constant)
+    : declaration_(declaration),
+      type_(type),
+      statement_(statement),
+      constant_(std::move(constant)) {
   TINT_ASSERT(Semantic, type_);
 }
 
+Expression::~Expression() = default;
+
 }  // namespace sem
 }  // namespace tint
diff --git a/src/sem/expression.h b/src/sem/expression.h
index 8c2e304..ccb7dd8 100644
--- a/src/sem/expression.h
+++ b/src/sem/expression.h
@@ -16,6 +16,7 @@
 #define SRC_SEM_EXPRESSION_H_
 
 #include "src/ast/expression.h"
+#include "src/sem/constant.h"
 #include "src/sem/node.h"
 
 namespace tint {
@@ -31,9 +32,14 @@
   /// @param declaration the AST node
   /// @param type the resolved type of the expression
   /// @param statement the statement that owns this expression
+  /// @param constant the constant value of the expression. May be invalid
   Expression(const ast::Expression* declaration,
              const sem::Type* type,
-             Statement* statement);
+             Statement* statement,
+             Constant constant);
+
+  /// Destructor
+  ~Expression() override;
 
   /// @return the resolved type of the expression
   sem::Type* Type() const { return const_cast<sem::Type*>(type_); }
@@ -41,6 +47,9 @@
   /// @return the statement that owns this expression
   Statement* Stmt() const { return statement_; }
 
+  /// @return the constant value of this expression
+  const Constant& ConstantValue() const { return constant_; }
+
   /// @returns the AST node
   ast::Expression* Declaration() const {
     return const_cast<ast::Expression*>(declaration_);
@@ -50,6 +59,7 @@
   const ast::Expression* declaration_;
   const sem::Type* const type_;
   Statement* const statement_;
+  Constant const constant_;
 };
 
 }  // namespace sem
diff --git a/src/sem/member_accessor_expression.cc b/src/sem/member_accessor_expression.cc
index 78f55b2..309785b 100644
--- a/src/sem/member_accessor_expression.cc
+++ b/src/sem/member_accessor_expression.cc
@@ -28,7 +28,7 @@
     ast::MemberAccessorExpression* declaration,
     const sem::Type* type,
     Statement* statement)
-    : Base(declaration, type, statement) {}
+    : Base(declaration, type, statement, Constant{}) {}
 
 MemberAccessorExpression::~MemberAccessorExpression() = default;
 
diff --git a/src/sem/type.h b/src/sem/type.h
index 86be66d..f48fa5f 100644
--- a/src/sem/type.h
+++ b/src/sem/type.h
@@ -52,7 +52,6 @@
   /// @returns the inner type if this is a reference, `this` otherwise
   const Type* UnwrapRef() const;
 
-
   /// @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/sem/variable.cc b/src/sem/variable.cc
index a7758dd..490f890 100644
--- a/src/sem/variable.cc
+++ b/src/sem/variable.cc
@@ -14,6 +14,8 @@
 
 #include "src/sem/variable.h"
 
+#include <utility>
+
 #include "src/ast/identifier_expression.h"
 #include "src/ast/variable.h"
 
@@ -50,8 +52,10 @@
 VariableUser::VariableUser(ast::IdentifierExpression* declaration,
                            const sem::Type* type,
                            Statement* statement,
-                           sem::Variable* variable)
-    : Base(declaration, type, statement), variable_(variable) {}
+                           sem::Variable* variable,
+                           Constant constant_value)
+    : Base(declaration, type, statement, std::move(constant_value)),
+      variable_(variable) {}
 
 }  // namespace sem
 }  // namespace tint
diff --git a/src/sem/variable.h b/src/sem/variable.h
index 55275cc..356ed9b 100644
--- a/src/sem/variable.h
+++ b/src/sem/variable.h
@@ -109,10 +109,12 @@
   /// @param type the resolved type of the expression
   /// @param statement the statement that owns this expression
   /// @param variable the semantic variable
+  /// @param constant_value the constant value for the variable. May be invalid
   VariableUser(ast::IdentifierExpression* declaration,
                const sem::Type* type,
                Statement* statement,
-               sem::Variable* variable);
+               sem::Variable* variable,
+               Constant constant_value);
 
   /// @returns the variable that this expression refers to
   const sem::Variable* Variable() const { return variable_; }
diff --git a/src/transform/fold_constants.cc b/src/transform/fold_constants.cc
index 6fcc798..6d7760a 100644
--- a/src/transform/fold_constants.cc
+++ b/src/transform/fold_constants.cc
@@ -19,271 +19,43 @@
 #include <vector>
 
 #include "src/program_builder.h"
+#include "src/sem/expression.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::FoldConstants);
 
 namespace tint {
+namespace transform {
 
-namespace {
+FoldConstants::FoldConstants() = default;
 
-using i32 = ProgramBuilder::i32;
-using u32 = ProgramBuilder::u32;
-using f32 = ProgramBuilder::f32;
+FoldConstants::~FoldConstants() = default;
 
-/// A Value is a sequence of scalars
-struct Value {
-  enum class Type {
-    i32,  //
-    u32,
-    f32,
-    bool_
-  };
-
-  union Scalar {
-    ProgramBuilder::i32 i32;
-    ProgramBuilder::u32 u32;
-    ProgramBuilder::f32 f32;
-    bool bool_;
-
-    Scalar(ProgramBuilder::i32 v) : i32(v) {}  // NOLINT
-    Scalar(ProgramBuilder::u32 v) : u32(v) {}  // NOLINT
-    Scalar(ProgramBuilder::f32 v) : f32(v) {}  // NOLINT
-    Scalar(bool v) : bool_(v) {}               // NOLINT
-  };
-
-  using Elems = std::vector<Scalar>;
-
-  Type type;
-  Elems elems;
-
-  Value() {}
-
-  Value(ProgramBuilder::i32 v) : type(Type::i32), elems{v} {}  // NOLINT
-  Value(ProgramBuilder::u32 v) : type(Type::u32), elems{v} {}  // NOLINT
-  Value(ProgramBuilder::f32 v) : type(Type::f32), elems{v} {}  // NOLINT
-  Value(bool v) : type(Type::bool_), elems{v} {}               // NOLINT
-
-  explicit Value(Type t, Elems e = {}) : type(t), elems(std::move(e)) {}
-
-  bool Valid() const { return elems.size() != 0; }
-  operator bool() const { return Valid(); }
-
-  void Append(const Value& value) {
-    TINT_ASSERT(Transform, value.type == type);
-    elems.insert(elems.end(), value.elems.begin(), value.elems.end());
-  }
-
-  /// Calls `func`(s) with s being the current scalar value at `index`.
-  /// `func` is typically a lambda of the form '[](auto&& s)'.
-  template <typename Func>
-  auto WithScalarAt(size_t index, Func&& func) const {
-    switch (type) {
-      case Value::Type::i32: {
-        return func(elems[index].i32);
-      }
-      case Value::Type::u32: {
-        return func(elems[index].u32);
-      }
-      case Value::Type::f32: {
-        return func(elems[index].f32);
-      }
-      case Value::Type::bool_: {
-        return func(elems[index].bool_);
-      }
+void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) {
+  ctx.ReplaceAll([&](ast::Expression* expr) -> ast::Expression* {
+    auto* sem = ctx.src->Sem().Get(expr);
+    if (!sem) {
+      return nullptr;
     }
-    TINT_ASSERT(Transform, false && "Unreachable");
-    return func(~0);
-  }
-};
 
-/// Returns the Value::Type that maps to the ast::Type*
-Value::Type AstToValueType(ast::Type* t) {
-  if (t->Is<ast::I32>()) {
-    return Value::Type::i32;
-  } else if (t->Is<ast::U32>()) {
-    return Value::Type::u32;
-  } else if (t->Is<ast::F32>()) {
-    return Value::Type::f32;
-  } else if (t->Is<ast::Bool>()) {
-    return Value::Type::bool_;
-  }
-  TINT_ASSERT(Transform, false && "Invalid type");
-  return {};
-}
-
-/// Cast `Value` to `target_type`
-/// @return the casted value
-Value Cast(const Value& value, Value::Type target_type) {
-  if (value.type == target_type) {
-    return value;
-  }
-
-  Value result(target_type);
-  for (size_t i = 0; i < value.elems.size(); ++i) {
-    switch (target_type) {
-      case Value::Type::i32:
-        result.Append(value.WithScalarAt(
-            i, [](auto&& s) { return static_cast<i32>(s); }));
-        break;
-
-      case Value::Type::u32:
-        result.Append(value.WithScalarAt(
-            i, [](auto&& s) { return static_cast<u32>(s); }));
-        break;
-
-      case Value::Type::f32:
-        result.Append(value.WithScalarAt(
-            i, [](auto&& s) { return static_cast<f32>(s); }));
-        break;
-
-      case Value::Type::bool_:
-        result.Append(value.WithScalarAt(
-            i, [](auto&& s) { return static_cast<bool>(s); }));
-        break;
+    auto value = sem->ConstantValue();
+    if (!value.IsValid()) {
+      return nullptr;
     }
-  }
 
-  return result;
-}
+    auto* ty = sem->Type();
 
-/// Type that maps `ast::Expression*` to `Value`
-using ExprToValue = std::unordered_map<const ast::Expression*, Value>;
-
-/// Adds mapping of `expr` to `value` to `expr_to_value`
-/// @returns true if add succeded
-bool AddExpr(ExprToValue& expr_to_value,
-             const ast::Expression* expr,
-             Value value) {
-  auto r = expr_to_value.emplace(expr, std::move(value));
-  return r.second;
-}
-
-/// @returns the `Value` in `expr_to_value` at `expr`, leaving it in the map, or
-/// invalid Value if not in map
-Value PeekExpr(ExprToValue& expr_to_value, ast::Expression* expr) {
-  auto iter = expr_to_value.find(expr);
-  if (iter != expr_to_value.end()) {
-    return iter->second;
-  }
-  return {};
-}
-
-/// @returns the `Value` in `expr_to_value` at `expr`, removing it from the map,
-/// or invalid Value if not in map
-Value TakeExpr(ExprToValue& expr_to_value, ast::Expression* expr) {
-  auto iter = expr_to_value.find(expr);
-  if (iter != expr_to_value.end()) {
-    auto result = std::move(iter->second);
-    expr_to_value.erase(iter);
-    return result;
-  }
-  return {};
-}
-
-/// Folds a `ScalarConstructorExpression` into a `Value`
-Value Fold(const ast::ScalarConstructorExpression* scalar_ctor) {
-  auto* literal = scalar_ctor->literal();
-  if (auto* lit = literal->As<ast::SintLiteral>()) {
-    return {lit->value_as_i32()};
-  }
-  if (auto* lit = literal->As<ast::UintLiteral>()) {
-    return {lit->value_as_u32()};
-  }
-  if (auto* lit = literal->As<ast::FloatLiteral>()) {
-    return {lit->value()};
-  }
-  if (auto* lit = literal->As<ast::BoolLiteral>()) {
-    return {lit->IsTrue()};
-  }
-  TINT_ASSERT(Transform, false && "Unreachable");
-  return {};
-}
-
-/// Folds a `TypeConstructorExpression` into a `Value` if possible.
-/// @returns a valid `Value` with 1 element for scalars, and 2/3/4 elements for
-/// vectors.
-Value Fold(const ast::TypeConstructorExpression* type_ctor,
-           ExprToValue& expr_to_value) {
-  auto& ctor_values = type_ctor->values();
-  auto* type = type_ctor->type();
-  auto* vec = type->As<ast::Vector>();
-
-  // For now, only fold scalars and vectors
-  if (!type->is_scalar() && !vec) {
-    return {};
-  }
-
-  auto* elem_type = vec ? vec->type() : type;
-  int result_size = vec ? static_cast<int>(vec->size()) : 1;
-
-  // For zero value init, return 0s
-  if (ctor_values.empty()) {
-    if (elem_type->Is<ast::I32>()) {
-      return Value(Value::Type::i32, Value::Elems(result_size, 0));
-    } else if (elem_type->Is<ast::U32>()) {
-      return Value(Value::Type::u32, Value::Elems(result_size, 0u));
-    } else if (elem_type->Is<ast::F32>()) {
-      return Value(Value::Type::f32, Value::Elems(result_size, 0.0f));
-    } else if (elem_type->Is<ast::Bool>()) {
-      return Value(Value::Type::bool_, Value::Elems(result_size, false));
+    auto* ctor = expr->As<ast::TypeConstructorExpression>();
+    if (!ctor) {
+      return nullptr;
     }
-  }
 
-  // If not all ctor_values are foldable, we can't fold this node
-  for (auto* cv : ctor_values) {
-    if (!PeekExpr(expr_to_value, cv)) {
-      return {};
-    }
-  }
-
-  // Build value for type_ctor from each child value by casting to
-  // type_ctor's type.
-  Value new_value(AstToValueType(elem_type));
-  for (auto* cv : ctor_values) {
-    auto value = TakeExpr(expr_to_value, cv);
-    new_value.Append(Cast(value, AstToValueType(elem_type)));
-  }
-
-  // Splat single-value initializers
-  if (new_value.elems.size() == 1) {
-    auto first_value = new_value;
-    for (int i = 0; i < result_size - 1; ++i) {
-      new_value.Append(first_value);
-    }
-  }
-
-  return new_value;
-}
-
-/// @returns a `ConstructorExpression` to replace `expr` with, or nullptr if we
-/// shouldn't replace it.
-ast::ConstructorExpression* Build(CloneContext& ctx,
-                                  const ast::Expression* expr,
-                                  const Value& value) {
-  // If original ctor expression had no init values, don't replace the
-  // expression
-  if (auto* ctor = expr->As<ast::TypeConstructorExpression>()) {
+    // If original ctor expression had no init values, don't replace the
+    // expression
     if (ctor->values().size() == 0) {
       return nullptr;
     }
-  }
 
-  auto make_ast_type = [&]() -> ast::Type* {
-    switch (value.type) {
-      case Value::Type::i32:
-        return ctx.dst->ty.i32();
-      case Value::Type::u32:
-        return ctx.dst->ty.u32();
-      case Value::Type::f32:
-        return ctx.dst->ty.f32();
-      case Value::Type::bool_:
-        return ctx.dst->ty.bool_();
-    }
-    return nullptr;
-  };
-
-  if (auto* type_ctor = expr->As<ast::TypeConstructorExpression>()) {
-    if (auto* vec = type_ctor->type()->As<ast::Vector>()) {
+    if (auto* vec = ty->As<sem::Vector>()) {
       uint32_t vec_size = static_cast<uint32_t>(vec->size());
 
       // We'd like to construct the new vector with the same number of
@@ -294,9 +66,9 @@
       //
       // In this case, creating a vec3 with 2 args is invalid, so we should
       // create it with 3. So what we do is construct with vec_size args,
-      // except if the original vector was single-value initialized, in which
-      // case, we only construct with one arg again.
-      uint32_t ctor_size = (type_ctor->values().size() == 1) ? 1 : vec_size;
+      // except if the original vector was single-value initialized, in
+      // which case, we only construct with one arg again.
+      uint32_t ctor_size = (ctor->values().size() == 1) ? 1 : vec_size;
 
       ast::ExpressionList ctors;
       for (uint32_t i = 0; i < ctor_size; ++i) {
@@ -304,44 +76,16 @@
             i, [&](auto&& s) { ctors.emplace_back(ctx.dst->Expr(s)); });
       }
 
-      return ctx.dst->vec(make_ast_type(), vec_size, ctors);
-    } else if (type_ctor->type()->is_scalar()) {
+      auto* el_ty = CreateASTTypeFor(&ctx, vec->type());
+      return ctx.dst->vec(el_ty, vec_size, ctors);
+    }
+
+    if (ty->is_scalar()) {
       return value.WithScalarAt(0, [&](auto&& s) { return ctx.dst->Expr(s); });
     }
-  }
-  return nullptr;
-}
 
-}  // namespace
-
-namespace transform {
-
-FoldConstants::FoldConstants() = default;
-
-FoldConstants::~FoldConstants() = default;
-
-void FoldConstants::Run(CloneContext& ctx, const DataMap&, DataMap&) {
-  ExprToValue expr_to_value;
-
-  // Visit inner expressions before outer expressions
-  for (auto* node : ctx.src->ASTNodes().Objects()) {
-    if (auto* scalar_ctor = node->As<ast::ScalarConstructorExpression>()) {
-      if (auto v = Fold(scalar_ctor)) {
-        AddExpr(expr_to_value, scalar_ctor, std::move(v));
-      }
-    }
-    if (auto* type_ctor = node->As<ast::TypeConstructorExpression>()) {
-      if (auto v = Fold(type_ctor, expr_to_value)) {
-        AddExpr(expr_to_value, type_ctor, std::move(v));
-      }
-    }
-  }
-
-  for (auto& kvp : expr_to_value) {
-    if (auto* ctor_expr = Build(ctx, kvp.first, kvp.second)) {
-      ctx.Replace(kvp.first, ctor_expr);
-    }
-  }
+    return nullptr;
+  });
 
   ctx.Clone();
 }
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 1582db4..84cd1f8 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -54,6 +54,10 @@
   manager.Add<ForLoopToLoop>();  // Must come after ZeroInitWorkgroupMemory
   auto transformedInput = manager.Run(in, data);
 
+  if (transformedInput.program.Diagnostics().contains_errors()) {
+    return transformedInput;
+  }
+
   auto* cfg = data.Get<Config>();
 
   ProgramBuilder out;
diff --git a/src/writer/append_vector.cc b/src/writer/append_vector.cc
index 9763c18..f8e1356 100644
--- a/src/writer/append_vector.cc
+++ b/src/writer/append_vector.cc
@@ -96,8 +96,9 @@
 
       for (uint32_t i = 0; i < packed_size - 1; i++) {
         auto* zero = buildZero();
-        b->Sem().Add(zero, b->create<sem::Expression>(zero, packed_el_sem_ty,
-                                                      statement));
+        b->Sem().Add(
+            zero, b->create<sem::Expression>(zero, packed_el_sem_ty, statement,
+                                             sem::Constant{}));
         packed.emplace_back(zero);
       }
     }
@@ -107,16 +108,18 @@
   if (packed_el_sem_ty != b->TypeOf(scalar)->UnwrapRef()) {
     // Cast scalar to the vector element type
     auto* scalar_cast = b->Construct(packed_el_ty, scalar);
-    b->Sem().Add(scalar_cast, b->create<sem::Expression>(
-                                  scalar_cast, packed_el_sem_ty, statement));
+    b->Sem().Add(scalar_cast,
+                 b->create<sem::Expression>(scalar_cast, packed_el_sem_ty,
+                                            statement, sem::Constant{}));
     packed.emplace_back(scalar_cast);
   } else {
     packed.emplace_back(scalar);
   }
 
   auto* constructor = b->Construct(packed_ty, std::move(packed));
-  b->Sem().Add(constructor, b->create<sem::Expression>(
-                                constructor, packed_sem_ty, statement));
+  b->Sem().Add(constructor,
+               b->create<sem::Expression>(constructor, packed_sem_ty, statement,
+                                          sem::Constant{}));
 
   return constructor;
 }
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index d4c0ed6..0f5fad6 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -1764,7 +1764,8 @@
     auto* i32 = builder_.create<sem::I32>();
     auto* zero = builder_.Expr(0);
     auto* stmt = builder_.Sem().Get(vector)->Stmt();
-    builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt));
+    builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt,
+                                                              sem::Constant{}));
     auto* packed = AppendVector(&builder_, vector, zero);
     return EmitExpression(out, packed);
   };
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 7439a56..2fd1d31 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -243,6 +243,7 @@
     "../src/resolver/pipeline_overridable_constant_test.cc",
     "../src/resolver/ptr_ref_test.cc",
     "../src/resolver/ptr_ref_validation_test.cc",
+    "../src/resolver/resolver_constants_test.cc",
     "../src/resolver/resolver_test.cc",
     "../src/resolver/resolver_test_helper.cc",
     "../src/resolver/resolver_test_helper.h",